在 OpenCV 与 dlib 的人脸处理体系中,仿射变换是很多高级功能的地基——人脸对齐、换脸术、表情迁移,都离不开它。本文从代码出发,先讲透仿射变换本身,再深入换脸术的完整实现链路。


一、仿射变换:三点定乾坤

1.1 原理

仿射变换(Affine Transform)是一种线性变换 + 平移,可表示为:

x' = a·x + b·y + e
y' = c·x + d·y + f

用矩阵写就是 2×3 的变换矩阵 M

M = | a  b  e |
    | c  d  f |

只要知道 3 个对应点对,就能唯一确定 M。这是仿射变换最核心的特性:三点定乾坤。

原图三点              目标图三点
P1(0,0)     ──────►   P1'(0,0)
P2(0,h-1)   ──────►   P2'(50,h-50)
P3(w-1,0)   ──────►   P3'(w-50,50)

仿射变换保平行线(平行线变换后仍平行),但不保长度和角度。适用于:平移、旋转、缩放、倾斜

1.2 代码实现

import cv2
import numpy as np

img = cv2.imread("handou2.jpg")
h, w = img.shape[:2]

# 选取三个控制点
mat_src = np.float32([[0, 0], [0, h-1], [w-1, 0]])
mat_dst = np.float32([[0, 0], [50, h-50], [w-50, 50]])

# 获取 2x3 仿射变换矩阵
M = cv2.getAffineTransform(mat_src, mat_dst)

# 应用仿射变换
dst = cv2.warpAffine(img, M, (w, h))

# 拼接原图和结果图对比
imas = np.hstack([img, dst])
cv2.imshow('imgs', imas)
cv2.waitKey(0)

流程图解:

在这里插入图片描述

[原图]  --选取3点-->  mat_src, mat_dst
                          |
                          v
                  getAffineTransform()
                          |
                          v
                       矩阵 M [2x3]
                          |
                          v
                  warpAffine(M)
                          |
                          v
                       [变换结果]

二、换脸术:技术链路全解析

2.1 整体流程

换脸(Face Swap)的本质是把 B 脸"贴"到 A 脸上,但不是简单叠加,而是经过以下 7 步精细处理:

在这里插入图片描述

① 读取图像A、B
        ↓
② dlib检测人脸 → 提取68个关键点
        ↓
③ 对68点求凸包 → 构建脸部掩膜
        ↓
④ SVD相似变换 → 求A→B的变换矩阵M
        ↓
⑤ warpAffine → 把B的掩膜变形对齐到A
        ↓
⑥ 高斯颜色迁移 → 让B的颜色与A一致
        ↓
⑦ 掩膜加权融合 → 最终换脸结果

2.2 核心技术一:凸包掩膜构建

POINTS = (LEFT_BROW + RIGHT_EYE + LEFT_EYE +
          RIGHT_BROW + NOSE + MOUTH)  # 眉毛+眼睛区域

def getFaceMask(im, keyPoints):
    im_mask = np.zeros(im.shape[:2], dtype=np.float64)
    for p in POINTS:
        hull = cv2.convexHull(keyPoints[p])       # 求凸包
        cv2.fillConvexPoly(im_mask, hull, color=1)  # 填充凸包(值在0~1)
    # 单通道 -> 三通道,适应OpenCV
    im_mask = np.array([im_mask]*3).transpose((1, 2, 0))
    im_mask = cv2.GaussianBlur(im_mask, (29, 29), 0)  # 边缘羽化
    return im_mask

关键点: 凸包填充是为了得到精确的脸部区域;高斯模糊((29,29) 大核)的目的是羽化边缘,让融合更自然,不留接缝。

在这里插入图片描述

2.3 核心技术二:SVD相似变换

这是换脸的核心数学。其目标是:找到 A 的 68 点到 B 的 68 点之间的相似变换矩阵 M(缩放 + 旋转 + 平移)。

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)  # SVD分解
    R = (U * Vt).T               # 旋转矩阵
    return np.hstack(((s2/s1)*R, c2.T - (s2/s1)*R*c1.T))  # M = [sR | t]

四步推导:

步骤 操作 目的
① 归一化 X -= mean; X /= std 消除尺度和位置差异
② SVD分解 U,S,Vt = svd(X·Yᵀ) 找到最优旋转矩阵
③ 求旋转R R = U·Vtᵀ 旋转矩阵(正交矩阵)
④ 构建M M = [(s₂/s₁)R | c₂-(s₂/s₁)Rc₁] 尺度 + 旋转 + 平移

几何含义:

B脸关键点群
    │
    │  减去质心 c2,除标准差 s2
    ▼
    ▼  SVD分解找最优旋转 R
    ▼
    ▼  缩放 s2/s1,旋转 R
    ▼
    ▼  加上 A 的质心 c1
    ▼
A脸关键点群位置(对齐!)

在这里插入图片描述

2.4 核心技术三:仿射变形 + 颜色迁移

# 用 M 将 B 的掩膜变形对齐到 A
bMaskWarp = cv2.warpAffine(bMask, M, dsize,
                    borderMode=cv2.BORDER_TRANSPARENT,
                    flags=cv2.WARP_INVERSE_MAP)

# 高斯颜色迁移:调整B的颜色分布匹配A
def normalColor(a, b):
    aGauss = cv2.GaussianBlur(a, (111, 111), 0)
    bGauss = cv2.GaussianBlur(b, (111, 111), 0)
    weight = aGauss / bGauss
    weight[np.isinf(weight)] = 0
    return b * weight

bcolor = normalColor(a, bWrap)

颜色迁移原理: 用超大核 (111,111) 高斯模糊获取两张脸的整体颜色分布,用比值 A_blur / B_blur 调整 B 的颜色。isinf 处理是防止 bGauss 中出现 0 导致除零。

2.5 核心技术四:掩膜加权融合

# 取两张掩膜的最大值(处理重叠区域)
mask = np.maximum(aMask, bMaskWarp)

# 掩膜加权融合
out = a * (1.0 - mask) + bcolor * mask

融合公式:

换脸结果 = A图 × (1 - mask) + B颜色图 × mask

mask = 0  → 显示A(原图)
mask = 1  → 显示B(换上的脸)
mask = 0.5 → 两图各50%混合(边缘过渡)

三、两技术对比

在这里插入图片描述

维度 仿射变换 换脸术
定位 基础工具(底层) 上层应用
核心原理 三点线性变换 68点SVD + 仿射 + 颜色迁移
变换矩阵 cv2.getAffineTransform() 自定义 getM() (SVD)
适用范围 人脸对齐、文档校正、UI变换 娱乐换脸、人脸替换
代码复杂度 极简(~5行) 中等(~50行)
关键参数 三点坐标、warpAffine 68点、GaussianBlur核大小

四、完整代码

import cv2
import dlib
import numpy as np

POINTS = (LEFT_BROW + RIGHT_EYE + LEFT_EYE +
          RIGHT_BROW + NOSE + MOUTH)
POINTStuple = tuple(POINTS)

def getFaceMask(im, keyPoints):
    im_mask = np.zeros(im.shape[:2], dtype=np.float64)
    for p in POINTS:
        hull = cv2.convexHull(keyPoints[p])
        cv2.fillConvexPoly(im_mask, hull, color=1)
    im_mask = np.array([im_mask]*3).transpose((1, 2, 0))
    return cv2.GaussianBlur(im_mask, (29, 29), 0)

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 getKeyPoints(im):
    rects = detector(im, 1)
    shape = predictor(im, rects[0])
    return np.matrix([[p.x, p.y] for p in shape.parts()])

def normalColor(a, b):
    weight = cv2.GaussianBlur(a, (111,111), 0) / cv2.GaussianBlur(b, (111,111), 0)
    weight[np.isinf(weight)] = 0
    return b * weight

a = cv2.imread("hg.jpg")
b = cv2.imread("pyy.jpg")
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")

aMask = getFaceMask(a, getKeyPoints(a))
bMask = getFaceMask(b, getKeyPoints(b))
M = getM(getKeyPoints(a)[POINTStuple], getKeyPoints(b)[POINTStuple])
dsize = a.shape[:2][::-1]

bMaskWarp = cv2.warpAffine(bMask, M, dsize, borderMode=cv2.BORDER_TRANSPARENT, flags=cv2.WARP_INVERSE_MAP)
mask = np.maximum(aMask, bMaskWarp)
bWrap = cv2.warpAffine(b, M, dsize, borderMode=cv2.BORDER_TRANSPARENT, flags=cv2.WARP_INVERSE_MAP)
out = a * (1.0 - mask) + normalColor(a, bWrap) * mask

cv2.imshow('out', out / 255)
cv2.waitKey(0)

五、总结与扩展

应用方向 说明
人脸对齐 用仿射变换将不同角度的人脸对齐到标准姿势,是人脸识别的预处理必备步骤
表情迁移 将 A 的表情关键点映射到 B,实现换表情效果
DeepFake 现代换脸基于深度学习 GAN,精度更高,但计算量更大
AR 贴纸 口罩、眼镜等 AR 效果本质上是局部掩膜 + 仿射变换

本文两段代码的核心知识点:

① 仿射变换  =  getAffineTransform(3对点) + warpAffine(矩阵M)
② 换脸术    =  dlib68点 → 凸包掩膜 → SVD相似变换
             → 高斯颜色迁移 → 掩膜加权融合
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐