一、核心原理
1.检测两张人脸图片的 68 个特征关键点;
2.根据关键点计算人脸间的仿射变换矩阵,实现人脸位置 / 角度的对齐;
3.生成人脸掩膜,区分人脸区域与背景区域;
4.对替换人脸进行颜色归一化,匹配目标人脸的色彩风格;
5.融合掩膜区域,完成最终的换脸效果。
二、环境准备
1.下载人脸关键点模型
需下载 Dlib 官方的 68 个人脸关键点预测模型:shape_predictor_68_face_landmarks.dat
三、代码解析
1. 人脸关键点分区
人脸的 68 个关键点可分为不同区域(如下巴、眉毛、眼睛、鼻子、嘴巴等),我们只需关注核心面部区域(排除下巴),用于后续掩膜生成和变换计算
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)生成人脸轮廓,再填充并高斯模糊使边缘更自然:
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))
# 高斯模糊:45×45核使掩膜边缘过渡更自然,避免替换后边缘生硬
im_mask = cv2.GaussianBlur(im_mask, (45,45), 0)
return im_mask
3. 函数 :计算仿射变换矩阵
要将 B 人脸对齐到 A 人脸的位置 / 角度,需计算两者的仿射变换矩阵 M。核心步骤是归一化关键点 + 奇异值分解(SVD) 求解旋转矩阵,最终得到完整的仿射变换矩阵
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 个人脸关键点:
def getKeyPoints(im):
"""
检测图像中的人脸关键点
:param im: 输入图像
:return: 68个关键点的矩阵(68×2)
"""
# 检测人脸位置(矩形框)
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 人脸:
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. 整合所有步骤完成换脸
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()
四、关键参数调整技巧
高斯模糊核大小:getFaceMask中的(45,45)和normalcolor中的(111,111)是核心参数。核越大,边缘越模糊自然,但过大会导致人脸细节丢失;核越小,边缘越清晰,但可能出现生硬的拼接痕迹,建议根据图片分辨率调整(分辨率高则核大,反之则小)。
关键点区域选择:本文排除了下巴关键点(仅用 17-68),若需替换完整人脸(包括下巴),可将POINTS改为FACE_POINTS。
网硕互联帮助中心





评论前必须登录!
注册