基于 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

相关推荐
无忧智库3 小时前
某市“十五五“知识产权大数据监管平台与全链条保护系统建设方案深度解读(WORD)
大数据·人工智能
顾北123 小时前
AI对话应用接口开发全解析:同步接口+SSE流式+智能体+前端对接
前端·人工智能
综合热讯3 小时前
股票融资融券交易时间限制一览与制度说明
大数据·人工智能·区块链
AEIC学术交流中心3 小时前
【快速EI检索 | ICPS出版】2026年计算机技术与可持续发展国际学术会议(CTSD 2026)
人工智能·计算机网络
玄同7653 小时前
Python Random 模块深度解析:从基础 API 到 AI / 大模型工程化实践
人工智能·笔记·python·学习·算法·语言模型·llm
风指引着方向3 小时前
昇腾 AI 开发生产力工具:CANN CLI 的高级使用与自动化脚本编写
运维·人工智能·自动化
算法狗23 小时前
大模型面试题:1B的模型和1T的数据大概要训练多久
人工智能·深度学习·机器学习·语言模型
23遇见3 小时前
CANN与开源生态:如何融入并赋能主流AI框架的NPU后端支持
人工智能