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

文章目录

引言

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

一、技术概述

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

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

二、环境准备

python 复制代码
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. 人脸关键点定义

python 复制代码
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. 获取人脸掩模

python 复制代码
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)初始化空白图像

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

(2)绘制凸包填充区域

python 复制代码
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) 扩展为三通道并模糊

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

(4)返回值

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

3. 计算仿射变换矩阵

python 复制代码
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 -= c1
    points2 -= c2
    
    s1 = np.std(points1)
    s2 = np.std(points2)
    points1 /= s1
    points2 /= s2
    
    # 奇异值分解计算旋转矩阵
    U, S, Vt = np.linalg.svd(points1.T * points2)
    R = (U * Vt).T
    
    return np.hstack(((s2/s1)*R, c2.T-(s2/s1)*R*c1.T))

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


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

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

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

python 复制代码
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)计算完整的相似变换矩阵

python 复制代码
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. 检测并提取人脸关键点

python 复制代码
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)人脸检测

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

(2)关键点检测

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

(3)关键点格式转换

python 复制代码
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. 颜色校正

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

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


(1) 高斯模糊处理

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

(2) 计算颜色权重

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

(3) 处理除零和无穷大

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

(4) 应用颜色校正

python 复制代码
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. 加载图像并检测关键点
python 复制代码
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. 生成人脸掩模
python 复制代码
aMask = getFacemask(a, aKeyPoints)
bMask = getFacemask(b, bKeyPoints)
  1. 计算变换矩阵并应用
python 复制代码
M = getM(aKeyPoints[POINTStupLe], bKeyPoints[POINTStupLe])
bMaskWarp = cv2.warpAffine(bMask, M, dsize, 
                          borderMode=cv2.BORDER_TRANSPARENT,
                          flags=cv2.WARP_INVERSE_MAP)
  1. 融合人脸区域
python 复制代码
mask = np.max([aMask, bMaskWarp], axis=0)
bWarp = cv2.warpAffine(b, M, dsize,
                      borderMode=cv2.BORDER_TRANSPARENT,
                      flags=cv2.WARP_INVERSE_MAP)
  1. 颜色校正和最终融合
python 复制代码
bcolor = normalColor(a, bWarp)
out = a * (1.0 - mask) + bcolor * mask

五、效果展示

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

六、总结

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

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

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

相关推荐
NAGNIP3 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab5 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab5 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP8 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年8 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼9 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS9 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区10 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈10 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang11 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx