使用OpenCV 和 Dlib 实现人脸融合技术

文章目录

    • 引言
    • 一、技术概述
    • 二、环境准备
    • 三、关键代码解析
      • 1. 人脸关键点定义
      • 2. 获取人脸掩模
      • 3. 计算仿射变换矩阵
      • 4. 检测并提取人脸关键点
      • 5. 颜色校正
    • 四、完整流程
    • 五、效果展示
    • 六、总结

引言

本文将介绍如何使用Python、OpenCV和dlib库实现人脸融合技术,将一张人脸的特征无缝融合到另一张人脸上。

一、技术概述

人脸融合是一种将两张人脸的特征进行智能融合的技术,主要包含以下几个关键步骤:

  1. 人脸关键点检测
  2. 人脸对齐和仿射变换
  3. 人脸区域分割和融合
  4. 颜色校正

二、环境准备

import cv2
import numpy as np
import dlib

需要安装以下库:

  • OpenCV (pip install opencv-python)
  • NumPy (pip install numpy)
  • dlib (pip install dlib)

还需要下载dlib的预训练模型shape_predictor_68_face_landmarks.dat
链接在下方:
shape_predictor_68_face_landmarks.dat

三、关键代码解析

1. 人脸关键点定义

JAW_POINTS = list(range(0,17))  # 下巴轮廓点
RIGHT_BROW_POINTS = list(range(17,22))  # 右眉毛
LEFT_BROW_POINTS = list(range(22,27))  # 左眉毛
NOSE_POINTS = list(range(27,35))  # 鼻子
RIGHT_EYE_POINTS = list(range(36,42))  # 右眼
LEFT_EYE_POINTS = list(range(42,48))  # 左眼
MOUTH_POINTS = list(range(48,61))  # 嘴巴
FACE_POINTS = list(range(17,68))  # 全脸# 合并关键点集
POINTS = [LEFT_BROW_POINTS + RIGHT_EYE_POINTS + LEFT_EYE_POINTS + RIGHT_BROW_POINTS + NOSE_POINTS + MOUTH_POINTS]
POINTStupLe = tuple(POINTS)

2. 获取人脸掩模

def getFacemask(im, keyPoints):# 创建与图像同尺寸的全零矩阵im = np.zeros(im.shape[:2], dtype=np.float64)# 为每个面部区域创建凸包并填充for p in POINTS:points = cv2.convexHull(keyPoints[p])cv2.fillConvexPoly(im, points, color=1)# 转换为3通道并应用高斯模糊im = np.array([im, im, im]).transpose(1, 2, 0)im = cv2.GaussianBlur(im, (25, 25), 0)return im

这段代码的作用是根据给定的关键点(keyPoints)在图像上生成一个面部遮罩(face mask),并对其进行高斯模糊处理。下面是逐步解释:


(1)初始化空白图像

im = np.zeros(im.shape[:2], dtype=np.float64)
  • 创建一个与输入图像 im 大小相同的全黑(值为0)的单通道浮点型图像。

(2)绘制凸包填充区域

for p in POINTS:points = cv2.convexHull(keyPoints[p])cv2.fillConvexPoly(im, points, color=1)
  • 遍历 POINTS(可能是面部关键点的集合,比如眼睛、嘴巴等区域)。
  • 对每组关键点 keyPoints[p] 计算凸包(cv2.convexHull),得到一个凸多边形。
  • cv2.fillConvexPoly 在单通道图像 im 上填充这些凸多边形,填充值为 1(白色)。

(3) 扩展为三通道并模糊

im = np.array([im, im, im]).transpose(1, 2, 0)
  • 将单通道图像复制为三通道(RGB),通过 transpose 调整维度顺序为 (height, width, 3)
im = cv2.GaussianBlur(im, (25, 25), 0)
  • 对三通道图像应用高斯模糊(核大小为 25x25,标准差为 0 表示自动计算)。

(4)返回值

  • 返回模糊后的三通道遮罩图像,值范围是 [0, 1](原填充区域为 1,其余为 0,模糊后边缘过渡平滑)。

3. 计算仿射变换矩阵

def getM(points1, points2):points1 = points1.astype(np.float64)points2 = points2.astype(np.float64)# 中心化和归一化c1 = np.mean(points1, axis=0)c2 = np.mean(points2, axis=0)points1 -= c1points2 -= c2s1 = np.std(points1)s2 = np.std(points2)points1 /= s1points2 /= s2# 奇异值分解计算旋转矩阵U, S, Vt = np.linalg.svd(points1.T * points2)R = (U * Vt).Treturn np.hstack(((s2/s1)*R, c2.T-(s2/s1)*R*c1.T))

这段代码的作用是计算两组点集(points1points2)之间的相似变换矩阵(Similarity Transformation Matrix),用于将 points1 映射到 points2 的坐标系中。相似变换包括缩放(scale)、旋转(rotation)和平移(translation),但不包括倾斜或剪切变换。


(1)数据预处理(归一化)

points1 = points1.astype(np.float64)  
points2 = points2.astype(np.float64)  
  • 将输入的点集转换为 float64 类型,避免整数运算带来的精度问题。
c1 = np.mean(points1, axis=0)  
c2 = np.mean(points2, axis=0)  
points1 -= c1  
points2 -= c2  
  • 计算两组点的均值 c1c2(相当于中心点)。
  • 将点集中心化(减去均值),使得它们的中心都位于 (0, 0),方便后续计算旋转和缩放。
s1 = np.std(points1)  
s2 = np.std(points2)  
points1 /= s1  
points2 /= s2  
  • 计算两组点的标准差 s1s2(衡量点的分布范围)。
  • 对点集进行缩放归一化(除以标准差),使它们的尺度一致(类似于 Z-score 标准化)。

(2)计算旋转矩阵(SVD 分解)

U, S, Vt = np.linalg.svd(points1.T @ points2)  
R = (U @ Vt).T  
  • points1.T @ points2 进行奇异值分解(SVD),得到 USVt
  • 旋转矩阵 R 的计算方式为 R = U @ Vt,并转置(.T)使其适用于列向量变换。
  • 这一步的目的是找到最优旋转,使得 points1points2 对齐。

(3)计算完整的相似变换矩阵

return np.hstack(((s2/s1)*R, c2.T-(s2/s1)*R*c1.T))  
  • (s2/s1)*R:缩放因子 s2/s1 乘以旋转矩阵 R,表示 points1 需要缩放 s2/s1 倍并旋转 R 才能匹配 points2 的尺度。
  • c2.T - (s2/s1)*R @ c1.T:计算平移向量,使得变换后的 points1 中心 c1 能对齐 points2 的中心 c2
  • np.hstack:将缩放旋转部分和平移部分水平拼接,形成完整的 3×3 相似变换矩阵(如果是 2D 点,则是 2×3 矩阵,最后一行为 [0, 0, 1] 的齐次坐标形式)。

(4)数学表示
最终的变换矩阵 M 可以表示为:
在这里插入图片描述

其中:

  • ( s = \frac{s2}{s1} )(缩放因子)
  • ( R )(旋转矩阵)
  • ( t = c2 - s \cdot R \cdot c1 )(平移向量)

(5)注意事项

  • 输入 points1points2 必须是 相同数量 的点(如 68 个人脸关键点)。
  • 如果点集分布差异过大(如极端遮挡),SVD 可能无法得到正确的旋转矩阵。
  • 该变换不适用于仿射或透视变换(仅适用于相似变换,即旋转 + 缩放 + 平移)。

4. 检测并提取人脸关键点

def getKeyPoints(im):rects = detector(im, 1)shape = predictor(im, rects[0])s = np.matrix([[p.x, p.y] for p in shape.parts()])return s

这段代码的作用是检测输入图像 im 中的人脸,并提取人脸关键点(facial landmarks)。它使用了 dlib 库的预训练人脸检测器和关键点预测器。以下是逐步解析:


(1)人脸检测

rects = detector(im, 1)
  • detector
    这是一个 dlib 的预训练人脸检测器(通常是 dlib.get_frontal_face_detector() 返回的对象)。
  • 输入
    im 是输入图像(需为灰度图或 RGB 图)。
    1 表示对图像进行上采样一次(提高检测小脸的能力)。
  • 输出
    rects 是一个列表,包含检测到的所有人脸矩形框(dlib.rectangle 对象)。
    rects[0] 表示选择第一张检测到的人脸(假设图像中只有一张人脸)。

(2)关键点检测

shape = predictor(im, rects[0])
  • predictor
    这是一个 dlib 的预训练人脸关键点检测器(通常是 dlib.shape_predictor 加载的模型,如 shape_predictor_68_face_landmarks.dat)。
  • 输入
    im 是原始图像,rects[0] 是检测到的人脸矩形框。
  • 输出
    shape 是一个包含人脸关键点的对象(68 个点,涵盖五官轮廓)。

(3)关键点格式转换

s = np.matrix([[p.x, p.y] for p in shape.parts()])
  • shape.parts()
    返回所有关键点的列表(dlib.point 对象),每个点有 xy 属性。
  • 列表推导式
    将关键点转换为 (x, y) 坐标的列表,例如 [[x1, y1], [x2, y2], ...]
  • np.matrix
    将列表转换为 NumPy 矩阵(形状为 68×2,68 个点,每个点 2D 坐标)。

(4)返回值

  • s
    返回一个 68×2 的矩阵,表示 68 个人脸关键点的坐标(例如,第 0 点是下巴,第 27 点是鼻尖等)。

5. 颜色校正

def normalColor(a, b):ksize = (111, 111)aGauss = cv2.GaussianBlur(a, ksize, 0)bGauss = cv2.GaussianBlur(b, ksize, 0)weight = aGauss / bGausswhere_are_inf = np.isinf(weight)weight[where_are_inf] = 0return b * weight

这段代码的作用是 对图像 b 进行颜色校正,使其颜色分布与图像 a 相似,通常用于图像融合或颜色迁移任务。以下是逐步解析:


(1) 高斯模糊处理

ksize = (111, 111)  # 高斯核大小(非常大,用于提取低频颜色信息)
aGauss = cv2.GaussianBlur(a, ksize, 0)  # 对图像a进行高斯模糊
bGauss = cv2.GaussianBlur(b, ksize, 0)  # 对图像b进行高斯模糊
  • 高斯模糊
    使用一个非常大的核(111×111)对输入图像 ab 进行模糊,目的是提取图像的低频颜色信息(即整体色调,忽略细节)。
  • 为什么用大核?
    核越大,模糊程度越高,越能保留图像的全局颜色特征而非局部纹理。

(2) 计算颜色权重

weight = aGauss / bGauss  # 计算颜色调整权重
  • 权重计算
    通过 aGauss / bGauss 得到一个比例矩阵 weight,表示 ab 在低频颜色上的差异。
    • 如果 aGaussbGauss 亮(例如 aGauss=200bGauss=100),则 weight=2,表示需要将 b 的对应区域变亮。
    • 如果 aGaussbGauss 暗(例如 aGauss=50bGauss=100),则 weight=0.5,表示需要将 b 的对应区域变暗。

(3) 处理除零和无穷大

where_are_inf = np.isinf(weight)  # 找到无穷大的位置
weight[where_are_inf] = 0         # 将无穷大替换为0
  • 问题
    bGauss 中某些像素值为0时,aGauss / 0 会导致 weight 出现无穷大(inf)。
  • 解决
    通过 np.isinf 找到这些位置,并强制设为 0(即不调整这些像素的颜色)。

(4) 应用颜色校正

return b * weight
  • 输出
    将原始图像 b 与权重 weight 逐像素相乘,得到颜色校正后的图像。
    • 例如,b 的某个像素值为 [100, 150, 200]weight[1.2, 0.8, 1.0],则输出像素为 [120, 120, 200]

(5)注意事项

  1. 输入范围
    ab 应为浮点型([0, 1])或整型([0, 255]),需保持一致。
  2. 核大小调整
    ksize 越大,颜色迁移越全局化;越小则保留更多局部对比(但可能引入噪声)。
  3. 除零问题
    如果 bGauss 有大片黑色区域(值为0),会导致权重失效,需提前检查图像内容。

(6)数学本质
该操作近似于对图像 b 的每个颜色通道进行 逐像素的线性变换
在这里插入图片描述

其中低频分量通过高斯模糊提取。


四、完整流程

  1. 加载图像并检测关键点
a = cv2.imread("chendulin.jpg")
b = cv2.imread("linyuner.jpg")detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")aKeyPoints = getKeyPoints(a)
bKeyPoints = getKeyPoints(b)
  1. 生成人脸掩模
aMask = getFacemask(a, aKeyPoints)
bMask = getFacemask(b, bKeyPoints)
  1. 计算变换矩阵并应用
M = getM(aKeyPoints[POINTStupLe], bKeyPoints[POINTStupLe])
bMaskWarp = cv2.warpAffine(bMask, M, dsize, borderMode=cv2.BORDER_TRANSPARENT,flags=cv2.WARP_INVERSE_MAP)
  1. 融合人脸区域
mask = np.max([aMask, bMaskWarp], axis=0)
bWarp = cv2.warpAffine(b, M, dsize,borderMode=cv2.BORDER_TRANSPARENT,flags=cv2.WARP_INVERSE_MAP)
  1. 颜色校正和最终融合
bcolor = normalColor(a, bWarp)
out = a * (1.0 - mask) + bcolor * mask

五、效果展示

如图,我们选择一张林允儿和陈都灵的照片进行换脸,效果显示如下:
在这里插入图片描述

六、总结

  1. 关键点检测:使用dlib的68点人脸检测模型精确定位面部特征
  2. 人脸对齐:通过仿射变换将源人脸与目标人脸对齐
  3. 区域融合:使用高斯模糊的掩模实现平滑过渡
  4. 颜色校正:保持目标图像的光照和肤色一致性

通过这篇博客,我们详细讲解了基于Python的人脸融合技术实现。希望这能帮助你理解计算机视觉中人脸处理的基本原理和方法。

理想的风会吹进现实,熬过的夜也会变成光。加油各位!🚀🚀🚀

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/78892.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

skywalking服务安装与启动

skywalking服务安装并启动 1、介绍2、下载apache-skywalking-apm3、解压缩文件4、创建数据库及用户5、修改配置文件6、下载 MySQL JDBC 驱动7、启动 OAP Serve,需要jkd11,需指定jkd版本,可以修改文件oapService.sh8、启动 Web UI,需要jkd11,需指定jkd版本,可以修改文件oapServi…

计算方法实验四 解线性方程组的间接方法

【实验性质】 综合性实验。 【实验目的】 掌握迭代法求解线性方程组。 【实验内容】 应用雅可比迭代法和Gauss-Sediel迭代法求解下方程组: 【理论基础】 线性方程组的数值解法分直接算法和迭代算法。迭代法将方程组的求解转化为构造一个向量序列&…

G919-GAS软件 JSON格式数据通讯协议-阵列数据解析

G919-GAS软件 JSON格式数据通讯协议-阵列数据解析 版本记录 DateAuthorVersionNote2024.04.07Dog TaoV1.0发布通讯协议。2025.05.06Dog TaoV1.11. 增加了【高速采样】模式下的通讯协议。2. 增加了“软件开发建议”小节。 文章目录 G919-GAS软件 JSON格式数据通讯协议-阵列数据…

TCGA数据库临床亚型可用!贝叶斯聚类+特征网络分析,这篇 NC 提供的方法可以快速用起来了!

生信碱移 贝叶斯网络聚类 CANclust是一种基于贝叶斯的聚类方法,系统性地对基因突变、细胞遗传学信息和临床指标进行联合建模,用于多种模态数据的联合聚类分析,并识别在患者群体中反复出现的特征模式。 个体的遗传与环境背景决定其应对疾病的…

【算法】随机快速排序和随机选择算法

文章目录 1、随机快速排序1.1 什么是随机快排1.2 随机快排的好处 2、随机选择算法 前言: 快速排序就是每次划分前,通过一种方法将一个基准值的位置确定好,再进入不同的部分重复相同的工作以此确定好每个值的位置以达到有序。如果你之前并不了…

网络技术基础,NAT,桥接,交换机,路由器

什么是NAT Network Address Translation(网络地址转换),它负责将目标IP或源IP进行了改变,相当于一个中间代理,我们家庭常用的路由器就是一个NAT设备,NAT是为了解决IPv4的IP地址快要耗尽的问题,…

DVWA靶场保姆级通关教程--03CSRF跨站请求伪造

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 目录 文章目录 前言 一、low级别的源码分析 二、medium级别源码分析 安全性分析 增加了一层 Referer 验证: 关键点是:在真实的网络环境中&a…

【Ansible自动化运维实战:从Playbook到负载均衡指南】

本文是「VagrantVirtualBox虚拟化环境搭建」的续篇,深入探索Ansible在自动化运维中的核心应用: ✅ Ansible核心技能:Playbook编写、角色(Roles)模块化、标签(Tags)精准控制 ✅ 实战场景覆盖&a…

基于STM32、HAL库的STC31-C-R3气体传感器驱动程序设计

一、简介: STC31-C-R3是Sensirion公司推出的一款基于CMOSens技术的CO2传感器,具有以下特点: 测量范围:0-100%体积浓度 I2C数字接口 低功耗设计 高精度和长期稳定性 小尺寸封装(5mm x 5mm) 二、硬件接口: STC31-C-R3 STM32L4xx ---------------------------- VDD (P…

Nginx篇之限制公网IP访问特定接口url实操

一、nginx配置限制IP访问 要在 Nginx 配置中添加 IP 限制,阻止来自指定公网 IP 地址段的访问,并且只对特定路径进行限制,可以在 location 配置中使用 deny 和 allow 指令来控制访问。 二、案例 1. 需求 对来自特定公网的地址段&#xff0…

算法研习:无重复字符的最长子串问题剖析

算法研习:无重复字符的最长子串问题剖析 一、引言 在算法的广袤天地中,字符串相关问题一直是备受关注的焦点。“无重复字符的最长子串”这一问题,不仅在面试中频繁出现,更是对算法思维和编程技巧的一次深度考验。它要求我们从给定字符串中找出不含有重复字符的最长子串的长…

Spring Cloud Gateway路由+断言+过滤

目录 介绍核心功能三大核心Route以服务名动态获取URLPredicate常用断言Path Route PredicateAfter Route PredicateBefore Route PredicateBetween Route PredicateCookie Route PredicateHeader Route PredicateHost Route PredicateQuery Route PredicateRemoteAddr Route Pr…

springboot集成langchain4j记忆对话

流式输出 LLM 一次生成一个标记(token),因此许多 LLM 提供商提供了一种方式,可以逐个标记地流式传输响应,而不是等待整个文本生成完毕。 这显著改善了用户体验,因为用户不需要等待未知的时间,几…

【SpringCloud GateWay】Connection prematurely closed BEFORE response 报错分析与解决方案

一、背景 今天业务方调用我们的网关服务报错: Connection prematurely closed BEFORE response二、原因分析 三、解决方案 第一步: 增加 SCG 服务的JVM启动参数,调整连接获取策略。 将连接池获取策略由默认的 FIFO(先进先出)变更为 LIFO&#xff08…

使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第十一讲)

这一期讲解lvgl中下拉框的基础使用,下拉列表允许用户从选项列表中选择一个值,下拉列表的选项表默认是关闭的,其中的选项可以是单个值或预定义文本。 当单击下拉列表后,其将创建一个列表,用户可以从中选择一个选项。 当…

【神经网络与深度学习】VAE 在解码前进行重参数化

在 VAE 中,解码之前进行重参数化主要有以下几个重要原因: 可微分性 在深度学习里,模型是通过反向传播算法来学习的,而这需要计算梯度。若直接从潜在变量的分布 (q_{\theta}(z|x))(由编码器输出的均值 (\mu) 和方差 (…

BBDM学习笔记

1. configs 1.1 LBBDM: Latent BBDM [readme]

mysql主从复制搭建,并基于‌Keepalived + VIP实现高可用

以下是基于 ‌Keepalived VIP‌ 实现 MySQL 主从复制高可用的详细步骤,涵盖主从复制搭建与故障自动切换: 一、MySQL 主从复制搭建(基础步骤回顾) 1. ‌主库(Master)配置‌ 修改配置文件‌ /etc/my.cnf&…

CD36.【C++ Dev】STL库的string的使用 (下)

目录 1.reserve函数(不是reverse) 代码示例 2.resize 代码示例 3.reserve和resize的区别 4.shrink_to_fit 代码示例 5.与C语言的配合的接口函数: c_str 代码示例 6.rfind 知识回顾:find函数 rfind 代码示例 练习题: 字符串最后一个单词的长度 代码 提交结果 ​…

STM32的网络天气时钟项目

一、项目概述与硬件架构 1.1 核心功能 本智能天气时钟系统集成了实时天气获取、网络时间同步、环境监测和低功耗管理四大核心功能: 网络数据获取: 通过ESP8266 WiFi模块连接心知天气API(每小时更新)获取北京标准时间服务器的时…