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

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