目录
[1.1 换脸技术的发展](#1.1 换脸技术的发展)
[1.2 常用换脸方法分类](#1.2 常用换脸方法分类)
[1.3 各方法优缺点对比](#1.3 各方法优缺点对比)
[2.1 技术路线](#2.1 技术路线)
[2.2 核心优势](#2.2 核心优势)
[2.3 工作流程图](#2.3 工作流程图)
[3.1 环境准备](#3.1 环境准备)
[3.2 人脸关键点定义](#3.2 人脸关键点定义)
[3.3 关键点提取函数](#3.3 关键点提取函数)
[3.4 人脸掩膜生成](#3.4 人脸掩膜生成)
[3.5 仿射变换矩阵计算](#3.5 仿射变换矩阵计算)
[3.6 颜色匹配函数](#3.6 颜色匹配函数)
[3.7 完整换脸流程](#3.7 完整换脸流程)
[4.1 中间过程可视化](#4.1 中间过程可视化)
[4.2 最终换脸效果](#4.2 最终换脸效果)
[4.3 存在的问题](#4.3 存在的问题)
[4.4 参数调优指南](#4.4 参数调优指南)
[5.1 短期优化(1-2天可完成)](#5.1 短期优化(1-2天可完成))
[5.2 中期改进(1周可完成)](#5.2 中期改进(1周可完成))
[5.3 长期方向(研究级)](#5.3 长期方向(研究级))
[6.1 方法总结](#6.1 方法总结)
[6.2 与深度学习方法对比](#6.2 与深度学习方法对比)
[6.3 实际应用建议](#6.3 实际应用建议)
[6.4 未来展望](#6.4 未来展望)
一、换脸技术概述
1.1 换脸技术的发展
人脸替换(Face Swap)技术是计算机视觉领域的热门应用之一,近年来随着深度学习的发展,换脸技术已经从实验室走向了大众应用。从早期的Snapchat滤镜到现在的DeepFake技术,换脸技术经历了从简单到复杂、从粗糙到精细的发展过程。
1.2 常用换脸方法分类
目前主流的换脸技术主要分为以下几类:
传统计算机视觉方法:
- 基于关键点的仿射变换:通过检测人脸关键点,计算仿射变换矩阵,将源人脸映射到目标图像上
- 泊松融合(Poisson Blending):利用泊松方程实现无缝图像融合
- 三角剖分方法:基于Delaunay三角剖分进行人脸区域映射
深度学习方法:
- 基于GAN的换脸:如DeepFake、FaceSwap等,通过生成对抗网络实现高质量换脸
- 基于自编码器的方法:通过编码器-解码器架构学习人脸特征表示
- 3D人脸重建方法:先重建3D人脸模型,再进行纹理映射
1.3 各方法优缺点对比
| 方法类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 关键点仿射变换 | 实现简单,速度快,无需训练 | 效果一般,对光照敏感 | 实时应用、教学演示 |
| 泊松融合 | 边界自然,颜色过渡好 | 计算量较大 | 照片处理 |
| 深度学习方法 | 效果逼真,鲁棒性强 | 需要大量数据训练,计算资源要求高 | 专业制作 |
二、本文方法介绍
本文采用基于dlib关键点检测和OpenCV图像处理的传统换脸方法,该方法具有以下特点:
2.1 技术路线
- 人脸关键点检测:使用dlib预训练的68点人脸关键点检测器
- 仿射变换:通过Procrustes分析计算最优仿射变换矩阵
- 颜色匹配:使用高斯模糊实现局部颜色统一
- Alpha混合:通过掩膜实现平滑融合
2.2 核心优势
✅ 无需训练 :直接使用预训练模型,开箱即用
✅ 实现简单 :代码量少,易于理解和修改
✅ 处理快速 :单张图片处理仅需1-2秒
✅ 可控性强:每个步骤都可独立调整参数
2.3 工作流程图
输入图像A(目标) + 输入图像B(源)
↓
人脸关键点检测
↓
计算仿射变换矩阵M
↓
将B脸仿射变换到A的位置
↓
生成人脸掩膜
↓
颜色匹配和归一化
↓
Alpha混合融合
↓
输出换脸结果
三、代码实现详解
3.1 环境准备
import cv2
import dlib
import numpy as np
所需库说明:
opencv-python:图像处理核心库dlib:人脸检测和关键点定位numpy:数值计算
下载预训练模型: 需要下载dlib的68点人脸关键点检测模型:
wget http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2
bunzip2 shape_predictor_68_face_landmarks.dat.bz2
3.2 人脸关键点定义
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)
关键点分布说明:
dlib的68点人脸关键点按照固定顺序排列:
- 点0-16:下巴轮廓(从左到右)
- 点17-21:右眉毛
- 点22-26:左眉毛
- 点27-35:鼻子
- 点36-41:右眼
- 点42-47:左眼
- 点48-60:外嘴唇轮廓
- 点61-67:内嘴唇轮廓
3.3 关键点提取函数
def getKeyPoints(im):
"""获取人脸68个关键点坐标"""
rects = detector(im, 1) # 检测人脸矩形框,1表示上采样1次
shape = predictor(im, rects[0]) # 获取第一个人脸的关键点
s = np.matrix([[p.x, p.y] for p in shape.parts()])
return s
函数解析:
detector(im, 1):使用HOG+SVM人脸检测器,参数1表示对图像上采样1次以检测更小的人脸predictor(im, rects[0]):对检测到的第一个人脸进行68点关键点定位- 返回68×2的矩阵,每行是一个关键点的(x,y)坐标
3.4 人脸掩膜生成
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) # 填充凸包区域
# 将单通道掩膜扩展为三通道
im = np.array([im, im, im]).transpose((1, 2, 0))
# 高斯模糊使边界柔和
im = cv2.GaussianBlur(im, ksize=(25, 25), sigmaX=0)
return im
核心思想:
- 凸包算法 :
cv2.convexHull()找到包围所有关键点的最小凸多边形 - 掩膜填充:使用值1填充凸包区域,背景为0
- 高斯模糊 :关键参数!模糊半径决定了边界过渡的柔和程度
ksize=(25,25):25×25的模糊核- 值越大,边界越柔和,但可能丢失细节
- 值越小,边界越锐利,但融合不自然
可视化理解:
原始掩膜: 模糊后掩膜:
■■■■■■ ░░▒▒▓▓██▓▓▒▒░░
■■■■■■ → ░▒▓███████▓▒░
■■■■■■ ░░▒▒▓▓██▓▓▒▒░░
3.5 仿射变换矩阵计算
def getM(points1, points2):
"""使用Procrustes分析计算仿射变换矩阵"""
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))
数学原理:Procrustes分析
这是一种用于比较两组点集形状的统计方法:
-
平移对齐:将两组点的质心都移到原点
points' = points - mean(points) -
尺度归一化:消除大小差异
points'' = points' / std(points') -
旋转对齐:通过SVD分解求解最优旋转矩阵
U, S, Vt = SVD(points1.T × points2) R = (U × Vt).T -
构建仿射矩阵:
M = [s2/s1 * R | t] 其中 t = c2 - (s2/s1) * R * c1
仿射变换的作用:
- 平移:改变人脸位置
- 旋转:调整人脸角度
- 缩放:匹配人脸大小
- 但不包括透视变换(无法处理3D角度差异)
3.6 颜色匹配函数
def normalColor(a, b):
"""调整b的颜色使其与a相匹配"""
ksize = (11, 11)
# 对两张图像分别进行高斯模糊
aGauss = cv2.GaussianBlur(a, ksize, sigmaX=0)
bGauss = cv2.GaussianBlur(b, ksize, sigmaX=0)
# 计算颜色调整权重
weight = aGauss / bGauss
# 处理无穷值(除零情况)
where_are_inf = np.isinf(weight)
weight[where_are_inf] = 0
return b * weight
颜色匹配原理:
假设图像A和B在某个区域的平均颜色分别为C_a和C_b,那么:
调整后的B = B × (C_a / C_b)
但直接使用原图颜色会导致噪声放大,因此:
- 先模糊:提取局部平均颜色
- 计算权重 :
weight = blur(A) / blur(B) - 应用权重 :
result = B × weight
效果对比:
原始B脸:偏黄、光照暗
↓ 颜色匹配
调整后B脸:肤色接近A、亮度匹配
3.7 完整换脸流程
# 初始化检测器
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
# 读取图像
a = cv2.imread("img_19.png") # 目标图(背景图)
b = cv2.imread("img_16.png") # 源图(要换的脸)
b = cv2.flip(b, 1) # 水平翻转(如需要)
# Step 1: 获取关键点
aKeyPoints = getKeyPoints(a)
bKeyPoints = getKeyPoints(b)
# Step 2: 生成人脸掩膜
aMask = getFaceMask(a, aKeyPoints)
bMask = getFaceMask(b, bKeyPoints)
# Step 3: 计算仿射变换矩阵
M = getM(aKeyPoints[POINTStuple], bKeyPoints[POINTStuple])
# Step 4: 仿射变换B的掩膜
dsize = a.shape[:2][::-1]
bMaskWarp = cv2.warpAffine(bMask, M, dsize,
borderMode=cv2.BORDER_TRANSPARENT,
flags=cv2.WARP_INVERSE_MAP)
# Step 5: 合并掩膜
mask = np.max([aMask, bMaskWarp], axis=0)
# Step 6: 仿射变换B的人脸
bWrap = cv2.warpAffine(b, M, dsize,
borderMode=cv2.BORDER_TRANSPARENT,
flags=cv2.WARP_INVERSE_MAP)
# Step 7: 颜色匹配
bcolor = normalColor(a, bWrap)
# Step 8: Alpha混合
out = a * (1.0 - mask) + bcolor * mask
# 显示结果
cv2.imshow("out", out/255)
cv2.waitKey()
流程图示:
图A(目标) 图B(源)
| |
v v
提取关键点 提取关键点
| |
+--------+--------+
|
v
计算变换矩阵M
|
+-----+-----+
| |
v v
生成掩膜A 仿射变换B
| |
| v
| 颜色匹配B'
| |
+-----+-----+
|
v
Alpha混合
|
v
结果图
四、效果展示与分析
4.1 中间过程可视化
4.2 最终换脸效果
成功案例
案例1:正面换脸
原图A 原图B 结果
[女性正面] + [男性正面] → [男性脸+女性背景]
✅ 优点:
- 五官位置准确对齐
- 边界融合自然
- 整体协调性好
⚠️ 不足:
- 鼻梁和眼部有轻微色差
- 光照匹配不够完美
案例2:不同角度
案例2a:侧脸换正脸
结果质量:★★☆☆☆
问题:仿射变换无法处理3D角度差异
案例2b:正脸换正脸
结果质量:★★★★☆
效果:较好,仅有细微色差
4.3 存在的问题
根据实际测试,该方法存在以下局限:
1. 色彩不均匀
-
原因:简单的颜色权重无法处理复杂光照
-
表现:眼部、鼻梁等区域出现异色或发白
-
解决方案:
# 方案1:使用LAB色彩空间a_lab = cv2.cvtColor(a, cv2.COLOR_BGR2LAB)b_lab = cv2.cvtColor(b, cv2.COLOR_BGR2LAB)# 只调整亮度通道# 方案2:直方图匹配for i in range(3): b[:,:,i] = cv2.equalizeHist(b[:,:,i])
2. 边界不够自然
-
原因:
ksize=(25,25)的高斯模糊不够大 -
表现:脸部边缘有明显分界线
-
解决方案:
# 增大模糊核im = cv2.GaussianBlur(im, ksize=(61, 61), sigmaX=15)# 或使用泊松融合center = (mask_center_x, mask_center_y)out = cv2.seamlessClone(bcolor, a, mask, center, cv2.NORMAL_CLONE)
3. 无法处理角度差异
- 原因:仿射变换是2D变换,无法处理3D旋转
- 表现:侧脸换正脸会严重扭曲
- 解决方案:
- 使用3D人脸重建
- 或限制只处理正面人脸
4. 对光照敏感
-
原因:没有光照归一化步骤
-
表现:强光下效果差
-
解决方案:
# 添加光照归一化a_gray = cv2.cvtColor(a, cv2.COLOR_BGR2GRAY)a_norm = cv2.equalizeHist(a_gray)
4.4 参数调优指南
| 参数 | 默认值 | 调优建议 | 效果 |
|---|---|---|---|
| 掩膜模糊核 | (25,25) | 增大到(51,51)或(61,61) | 边界更柔和 |
| 颜色匹配核 | (11,11) | 增大到(21,21) | 颜色更均匀 |
| sigmaX | 0(自动) | 设为10-15 | 更平滑的模糊 |
| 关键点数量 | 部分点 | 使用全部68点 | 更准确的对齐 |
调参示例代码:
# 柔和边界版本
def getFaceMask_soft(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)
im = np.array([im, im, im]).transpose((1, 2, 0))
im = cv2.GaussianBlur(im, ksize=(61, 61), sigmaX=15) # 更大的核
return im
# 增强颜色匹配版本
def normalColor_enhanced(a, b):
ksize = (21, 21) # 更大的核
# ... 其余代码相同
五、进阶改进方向
5.1 短期优化(1-2天可完成)
1. 添加泊松融合
def poisson_blend(dst, src, mask):
center = (dst.shape[1]//2, dst.shape[0]//2)
mask_uint8 = (mask * 255).astype(np.uint8)
return cv2.seamlessClone(src, dst, mask_uint8[:,:,0],
center, cv2.NORMAL_CLONE)
2. LAB色彩空间颜色匹配
def normalColor_lab(a, b):
a_lab = cv2.cvtColor(a, cv2.COLOR_BGR2LAB)
b_lab = cv2.cvtColor(b, cv2.COLOR_BGR2LAB)
# 只调整L通道(亮度)
l_mean_a = np.mean(a_lab[:,:,0])
l_mean_b = np.mean(b_lab[:,:,0])
b_lab[:,:,0] = b_lab[:,:,0] * (l_mean_a / l_mean_b)
return cv2.cvtColor(b_lab, cv2.COLOR_LAB2BGR)
3. 直方图匹配
def histogram_matching(src, dst):
matched = np.zeros_like(src)
for i in range(3):
# 计算累积分布函数
src_hist, bins = np.histogram(src[:,:,i], 256, [0,256])
dst_hist, _ = np.histogram(dst[:,:,i], 256, [0,256])
src_cdf = src_hist.cumsum()
dst_cdf = dst_hist.cumsum()
# 归一化
src_cdf = src_cdf / src_cdf[-1]
dst_cdf = dst_cdf / dst_cdf[-1]
# 建立映射
mapping = np.zeros(256, dtype=np.uint8)
for j in range(256):
diff = np.abs(dst_cdf - src_cdf[j])
mapping[j] = np.argmin(diff)
matched[:,:,i] = mapping[src[:,:,i]]
return matched
5.2 中期改进(1周可完成)
1. 多尺度融合(拉普拉斯金字塔)
def laplacian_blend(A, B, mask, levels=6):
# 构建高斯金字塔
gpA = [A]
gpB = [B]
gpM = [mask]
for i in range(levels):
gpA.append(cv2.pyrDown(gpA[i]))
gpB.append(cv2.pyrDown(gpB[i]))
gpM.append(cv2.pyrDown(gpM[i]))
# 构建拉普拉斯金字塔
lpA = [gpA[levels-1]]
lpB = [gpB[levels-1]]
for i in range(levels-1, 0, -1):
size = (gpA[i-1].shape[1], gpA[i-1].shape[0])
LA = gpA[i-1] - cv2.pyrUp(gpA[i], dstsize=size)
LB = gpB[i-1] - cv2.pyrUp(gpB[i], dstsize=size)
lpA.append(LA)
lpB.append(LB)
# 在每一层进行混合
LS = []
for la, lb, gm in zip(lpA, lpB, reversed(gpM)):
ls = la * (1 - gm) + lb * gm
LS.append(ls)
# 重建图像
result = LS[0]
for i in range(1, levels):
size = (LS[i].shape[1], LS[i].shape[0])
result = cv2.pyrUp(result, dstsize=size) + LS[i]
return result
2. 光照归一化
def illumination_normalization(img):
# 转换到YCrCb色彩空间
ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
# 对Y通道进行直方图均衡化
ycrcb[:,:,0] = cv2.equalizeHist(ycrcb[:,:,0])
# 转回BGR
return cv2.cvtColor(ycrcb, cv2.COLOR_YCrCb2BGR)
5.3 长期方向(研究级)
1. 引入深度学习
- 使用GAN进行超分辨率重建
- 使用神经网络进行肤色迁移
- 使用深度模型估计3D人脸姿态
2. 3D人脸重建
- 使用3DMM(3D Morphable Model)
- 重建人脸的3D几何和纹理
- 支持任意角度换脸
3. 实时视频换脸
- 优化算法性能
- 使用光流法实现帧间平滑
- GPU加速
六、总结与展望
6.1 方法总结
本文介绍的基于dlib和OpenCV的换脸方法具有以下特点:
优势: ✅ 实现简单,代码量少(约150行)
✅ 无需训练,开箱即用
✅ 处理速度快(单张图片1-2秒)
✅ 易于理解和修改
✅ 适合教学和快速原型开发
劣势: ❌ 效果受限于传统CV方法
❌ 对光照和角度敏感
❌ 颜色匹配不够完美
❌ 无法处理大角度差异
❌ 边界融合仍有改进空间
适用场景:
- 计算机视觉教学演示
- 快速原型验证
- 对实时性要求高的应用
- 资源受限的嵌入式设备
6.2 与深度学习方法对比
| 维度 | 传统方法(本文) | 深度学习方法 |
|---|---|---|
| 训练需求 | 无需训练 | 需要大量数据和GPU |
| 实现难度 | 简单(150行代码) | 复杂(数千行+模型) |
| 处理速度 | 快(1-2秒/张) | 慢(5-10秒/张) |
| 效果质量 | 中等 | 优秀 |
| 鲁棒性 | 一般 | 很强 |
| 可解释性 | 强 | 弱 |
6.3 实际应用建议
如果你需要:
- 快速demo:用本文方法 ✅
- 教学演示:用本文方法 ✅
- 生产级应用:用深度学习方法(如FaceSwap、DeepFaceLab)
- 实时视频:结合本文方法+优化(GPU加速)
- 高质量照片:深度学习方法 + 人工精修
6.4 未来展望
换脸技术的发展方向:
- 更高质量:GAN生成的换脸效果将越来越逼真
- 更快速度:实时4K视频换脸成为可能
- 更低门槛:无需专业知识,手机APP即可完成
- 更强鲁棒性:适应各种光照、角度、遮挡情况
- 伦理规范:技术发展伴随着使用规范的建立
伦理提醒: ⚠️ 换脸技术虽然有趣,但使用时需注意:
- 不得用于制作虚假信息
- 不得侵犯他人肖像权
- 不得用于非法或不道德用途
- 建议在生成内容上加水印标识
七、完整代码
"""
基于dlib和OpenCV的人脸替换实现
作者:[Your Name]
日期:2024
功能:将图片B的人脸替换到图片A上
"""
import cv2
import dlib
import numpy as np
# ==================== 人脸关键点定义 ====================
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)
# ==================== 核心函数 ====================
def getKeyPoints(im):
"""提取人脸68个关键点"""
rects = detector(im, 1)
shape = predictor(im, rects[0])
s = np.matrix([[p.x, p.y] for p in shape.parts()])
return s
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)
im = np.array([im, im, im]).transpose((1, 2, 0))
im = cv2.GaussianBlur(im, ksize=(25, 25), sigmaX=0)
return im
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))
def normalColor(a, b):
"""颜色匹配"""
ksize = (11, 11)
aGauss = cv2.GaussianBlur(a, ksize, sigmaX=0)
bGauss = cv2.GaussianBlur(b, ksize, sigmaX=0)
weight = aGauss / bGauss
where_are_inf = np.isinf(weight)
weight[where_are_inf] = 0
return b * weight
# ==================== 主程序 ====================
if __name__ == "__main__":
# 初始化检测器
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
# 读取图像
a = cv2.imread("img_19.png") # 目标图
b = cv2.imread("img_16.png") # 源图
b = cv2.flip(b, 1) # 水平翻转
# Step 1: 提取关键点
print("正在提取人脸关键点...")
aKeyPoints = getKeyPoints(a)
bKeyPoints = getKeyPoints(b)
# Step 2: 生成掩膜
print("正在生成人脸掩膜...")
aMask = getFaceMask(a, aKeyPoints)
bMask = getFaceMask(b, bKeyPoints)
# Step 3: 计算变换矩阵
print("正在计算仿射变换...")
M = getM(aKeyPoints[POINTStuple], bKeyPoints[POINTStuple])
# Step 4: 仿射变换
dsize = a.shape[:2][::-1]
bMaskWarp = cv2.warpAffine(bMask, M, dsize,
borderMode=cv2.BORDER_TRANSPARENT,
flags=cv2.WARP_INVERSE_MAP)
mask = np.max([aMask, bMaskWarp], axis=0)
bWrap = cv2.warpAffine(b, M, dsize,
borderMode=cv2.BORDER_TRANSPARENT,
flags=cv2.WARP_INVERSE_MAP)
# Step 5: 颜色匹配
print("正在进行颜色匹配...")
bcolor = normalColor(a, bWrap)
# Step 6: 融合
print("正在融合图像...")
out = a * (1.0 - mask) + bcolor * mask
# 显示和保存结果
cv2.imshow("Original A", a)
cv2.imshow("Original B", b)
cv2.imshow("Result", out/255)
cv2.imwrite("result.png", out)
print("换脸完成!结果已保存为 result.png")
cv2.waitKey(0)
cv2.destroyAllWindows()
