基于 OpenCV 与 Dlib 的人脸替换

一、核心原理

1.检测两张人脸图片的 68 个特征关键点;

2.根据关键点计算人脸间的仿射变换矩阵,实现人脸位置 / 角度的对齐;

3.生成人脸掩膜,区分人脸区域与背景区域;

4.对替换人脸进行颜色归一化,匹配目标人脸的色彩风格;

5.融合掩膜区域,完成最终的换脸效果。

二、环境准备

1.下载人脸关键点模型

需下载 Dlib 官方的 68 个人脸关键点预测模型:shape_predictor_68_face_landmarks.dat

三、代码解析

1. 人脸关键点分区

人脸的 68 个关键点可分为不同区域(如下巴、眉毛、眼睛、鼻子、嘴巴等),我们只需关注核心面部区域(排除下巴),用于后续掩膜生成和变换计算

python 复制代码
import cv2
import dlib
import numpy as np

# 人脸68关键点分区定义
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. 函数 :生成人脸掩膜

掩膜(Mask)的作用是标记出图片中的人脸区域,后续仅对该区域进行替换操作,避免影响背景。通过关键点的凸包(convexHull)生成人脸轮廓,再填充并高斯模糊使边缘更自然:

python 复制代码
def getFaceMask(im, keyPoints):
    """
    根据人脸关键点生成人脸掩膜
    :param im: 输入图像
    :param keyPoints: 人脸68关键点矩阵
    :return: 归一化且高斯模糊后的人脸掩膜(0-1之间)
    """
    # 创建与原图同尺寸的全0矩阵(单通道)
    im_mask = np.zeros(im.shape[:2], dtype=np.float64)
    for p in POINTS:
        # 根据关键点生成凸包(人脸轮廓)
        hull = cv2.convexHull(keyPoints[p])
        # 填充凸包区域为1(标记人脸区域)
        cv2.fillConvexPoly(im_mask, hull, color=1)
    
    # 将单通道掩膜转换为3通道(匹配RGB图像)
    im_mask = np.array([im_mask, im_mask, im_mask]).transpose((1,2,0))
    # 高斯模糊:45x45核使掩膜边缘过渡更自然,避免替换后边缘生硬
    im_mask = cv2.GaussianBlur(im_mask, (45,45), 0)
    return im_mask

3. 函数 :计算仿射变换矩阵

要将 B 人脸对齐到 A 人脸的位置 / 角度,需计算两者的仿射变换矩阵 M。核心步骤是归一化关键点 + 奇异值分解(SVD) 求解旋转矩阵,最终得到完整的仿射变换矩阵

python 复制代码
def getM(points1, points2):
    """
    计算从points1到points2的仿射变换矩阵
    :param points1: 目标人脸关键点(A图)
    :param points2: 源人脸关键点(B图)
    :return: 仿射变换矩阵M
    """
    # 转换为浮点型,避免整数运算误差
    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
    
    # 奇异值分解(SVD)求解旋转矩阵
    U, S, Vt = np.linalg.svd(points1.T @ points2)
    R = (U @ Vt).T  # 旋转矩阵
    
    # 组合仿射变换矩阵:尺度*旋转 + 平移
    M = np.hstack(((s2/s1)*R, c2.T - (s2/s1)*R @ c1.T))
    return M

4. 函数 :人脸关键点检测

基于 Dlib 的检测器和关键点预测模型,提取图片中的 68 个人脸关键点:

python 复制代码
def getKeyPoints(im):
    """
    检测图像中的人脸关键点
    :param im: 输入图像
    :return: 68个关键点的矩阵(68x2)
    """
    # 检测人脸位置(矩形框)
    detector = dlib.get_frontal_face_detector()
    rects = detector(im, 1)
    # 预测关键点(取第一个检测到的人脸)
    predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
    shape = predictor(im, rects[0])
    # 转换为矩阵格式(方便后续计算)
    key_points = np.matrix([[p.x, p.y] for p in shape.parts()])
    return key_points

5. 函数 :颜色归一化

直接替换人脸会出现色彩不匹配的问题,通过高斯滤波计算色彩权重,将 B 人脸的色彩风格匹配到 A 人脸:

python 复制代码
def normalcolor(a, b):
    """
    将B图的色彩风格匹配到A图
    :param a: 目标图像(A图)
    :param b: 源图像(变换后的B图)
    :return: 色彩归一化后的B图
    """
    # 大核高斯滤波:提取图像的整体色彩风格(忽略细节)
    ksize = (111, 111)
    aGauss = cv2.GaussianBlur(a, ksize, 0)
    bGauss = cv2.GaussianBlur(b, ksize, 0)
    
    # 计算色彩权重(处理除0无穷大的情况)
    weight = aGauss / bGauss
    weight[np.isinf(weight)] = 0  # 无穷大值置0
    
    # 应用权重,匹配色彩
    return b * weight

6. 整合所有步骤完成换脸

python 复制代码
if __name__ == "__main__":
    # 1. 读取图片
    a = cv2.imread("0.png")  # 目标图片(保留背景,替换人脸)
    b = cv2.imread("7.png")  # 源图片(提供要替换的人脸)
    
    # 2. 初始化检测器和预测器
    detector = dlib.get_frontal_face_detector()
    predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
    
    # 3. 检测人脸关键点
    aKeyPoints = getKeyPoints(a)
    bKeyPoints = getKeyPoints(b)
    
    # 4. 生成人脸掩膜
    aMask = getFaceMask(a, aKeyPoints)
    bMask = getFaceMask(b, bKeyPoints)
    
    # 5. 计算仿射变换矩阵(B对齐到A)
    M = getM(aKeyPoints[POINTStuple], bKeyPoints[POINTStuple])
    
    # 6. 变换B的掩膜和人脸图像
    dsize = a.shape[:2][::-1]  # 目标尺寸(宽, 高)
    # 变换B的掩膜
    bMaskWarp = cv2.warpAffine(bMask, M, dsize, borderMode=cv2.BORDER_TRANSPARENT, flags=cv2.WARP_INVERSE_MAP)
    # 变换B的人脸图像
    bWrap = cv2.warpAffine(b, M, dsize, borderMode=cv2.BORDER_TRANSPARENT, flags=cv2.WARP_INVERSE_MAP)
    
    # 7. 融合掩膜(取A和变换后B掩膜的最大值)
    mask = np.max([aMask, bMaskWarp], axis=0)
    
    # 8. 颜色归一化
    bcolor = normalcolor(a, bWrap)
    
    # 9. 融合图像:掩膜区域用B的人脸,非掩膜区域用A的背景
    out = a * (1.0 - mask) + bcolor * mask
    
    # 10. 显示结果
    cv2.imshow("原始图A", a)
    cv2.imshow("原始图B", b)
    cv2.imshow("换脸结果", out / 255)  # 归一化到0-1显示
    cv2.waitKey(0)
    cv2.destroyAllWindows()

四、关键参数调整技巧

  1. 高斯模糊核大小:getFaceMask中的(45,45)normalcolor中的(111,111)是核心参数。核越大,边缘越模糊自然,但过大会导致人脸细节丢失;核越小,边缘越清晰,但可能出现生硬的拼接痕迹,建议根据图片分辨率调整(分辨率高则核大,反之则小)。

  2. 关键点区域选择:本文排除了下巴关键点(仅用 17-68),若需替换完整人脸(包括下巴),可将POINTS改为FACE_POINTS

相关推荐
LaughingZhu3 小时前
Product Hunt 每日热榜 | 2026-03-21
人工智能·经验分享·深度学习·神经网络·产品运营
qzhqbb3 小时前
差分隐私与大模型+差分隐私在相关领域应用的论文总结
人工智能·算法
一招定胜负3 小时前
基于通义千问 API 的课堂话语智能分类分析工具实现
人工智能·分类·数据挖掘
阿_旭3 小时前
基于YOLO26深度学习的【桃子成熟度检测与分割系统】【python源码+Pyqt5界面+数据集+训练代码】图像分割、人工智能
人工智能·python·深度学习·桃子成熟度检测
CoderJia程序员甲3 小时前
GitHub 热榜项目 - 日榜(2026-03-22)
人工智能·ai·大模型·github·ai教程
剑穗挂着新流苏3123 小时前
109_神经网络的决策层:线性层(Linear Layer)与数据展平详解
人工智能·pytorch·深度学习
机器白学3 小时前
OpenClaw本地Docker安装部署+自定义配置国内大模型
人工智能
逄逄不是胖胖3 小时前
《动手学深度学习》-69BERT预训练实现
人工智能·深度学习
LSssT.3 小时前
【02】线性回归:机器学习的入门第一课
人工智能·机器学习·线性回归
多年小白3 小时前
今日AI科技简报(2026年3月18日)
人工智能·科技