4、人脸对齐与预处理:关键点检测、仿射变换、归一化处理

说实话,很多人觉得人脸识别就是“拍一张照片,然后比对”。但真正做过工程的人都知道,预处理这一步没做好,后面算法再牛也白搭。我见过太多项目,模型选得挺好,训练数据也够,结果上线后识别率惨不忍睹——查来查去,问题就出在输入的人脸没对齐。

这一章,我们就来聊聊人脸对齐和预处理。说白了,就是把一张歪七扭八、光照不均的人脸照片,变成一张“标准证件照”。

核心观点:人脸对齐不是可选项,而是人脸识别系统的“地基”。地基不稳,上层建筑迟早要塌。

4.1 关键点检测:找到脸的“锚点”

要对齐人脸,首先得知道脸在哪儿、五官在哪儿。关键点检测就是干这个的。我个人习惯把关键点分为两类:

  • 稀疏关键点(5点/21点):速度快,适合实时场景。比如眼睛、鼻子、嘴角。
  • 密集关键点(68点/106点):精度高,适合精细分析。比如人脸轮廓、眉毛形状。

我在项目中遇到过一个问题:用5点检测做对齐,速度是快了,但遇到大角度侧脸时,眼睛和嘴角的位置偏差很大,导致后续识别率直接掉了十几个点。后来换成68点检测,虽然慢了一点,但稳定性好很多。

关键点类型 典型点数 适用场景 速度 精度
稀疏 5点(眼×2、鼻、嘴角×2) 实时视频流、移动端 快(<1ms) 中等
密集 68点(轮廓+五官) 离线处理、高精度需求 较慢(5-15ms) 高
超密集 106点及以上 3D重建、表情分析 慢(>20ms) 极高
我的建议:如果做安防或门禁,用5点就够了;如果做金融支付或证件比对,至少上68点。别为了省那几毫秒,把精度搭进去。

4.2 仿射变换:把歪脸“掰正”

有了关键点,下一步就是把人脸转正。这里用到的核心技术叫仿射变换。你想想看,它其实就是做三件事:旋转、缩放、平移。

具体怎么做?我一般分三步走:

  1. 计算变换矩阵:根据检测到的关键点(比如两只眼睛的位置),和目标位置(比如标准正脸的眼睛坐标),算出一个2×3的矩阵。
  2. 应用变换:用这个矩阵对整张图做映射。OpenCV里一行 cv2.warpAffine 就搞定了。
  3. 裁剪输出:对齐后的人脸,统一裁剪成固定尺寸(比如112×112或224×224)。
import cv2
import numpy as np

# 假设检测到的左眼、右眼坐标
left_eye = np.array([120, 150])
right_eye = np.array([200, 148])

# 目标位置:标准正脸的眼睛坐标
target_left = np.array([40, 60])
target_right = np.array([72, 60])

# 计算仿射变换矩阵
M = cv2.getAffineTransform(
    np.float32([left_eye, right_eye, (left_eye + right_eye) / 2]),
    np.float32([target_left, target_right, (target_left + target_right) / 2])
)

# 应用变换
aligned = cv2.warpAffine(img, M, (112, 112))
我曾经踩过的坑:直接用 cv2.getRotationMatrix2D 做旋转,结果发现人脸虽然正了,但位置偏了。后来才意识到,仿射变换不仅要考虑旋转,还要考虑缩放和位移。尤其是不同摄像头拍出来的脸大小不一样,必须统一缩放到目标尺寸。

4.3 归一化处理:消除“干扰项”

对齐只是第一步。你想想看,同一张脸,在强光下和暗光下拍出来,像素值差很多。如果不做归一化,算法会误以为这是两个人。归一化处理主要解决三个问题:

4.3.1 光照归一化

我常用的方法有两种:

  • 直方图均衡化:简单粗暴,能提升对比度,但容易过曝。
  • DoG(高斯差分)滤波:能去除光照不均匀,保留纹理细节。我在做活体检测时特别喜欢用这个。

举个例子,如果人脸一半亮一半暗,直方图均衡化会把暗部提亮,但亮部可能就爆了。这时候用DoG滤波,效果会好很多。

4.3.2 角度归一化

仿射变换已经解决了平面内的旋转(左右歪头)。但平面外的旋转(抬头、低头、侧脸)怎么办?

嗯,这里要注意:仿射变换只能处理2D旋转。对于大角度的侧脸(比如超过30度),你需要用3D人脸重建,把脸转正。这个技术比较复杂,我后面会专门讲。

4.3.3 大小归一化

这个最简单,就是统一缩放到固定尺寸。但有个细节:不要直接resize。我建议先检测人脸框,然后根据关键点位置,把人脸区域裁剪出来,再resize。这样能保证人脸在图像中的占比一致。

归一化类型 常用方法 适用场景 注意事项
光照 直方图均衡化、DoG滤波 光照不均、逆光 避免过曝或丢失细节
角度 仿射变换(2D)、3D重建(3D) 姿态矫正 大角度需3D方法
大小 裁剪+resize 统一输入尺寸 先裁剪后resize
我的经验:归一化的顺序很重要。我一般先做关键点检测→仿射变换→裁剪→resize→光照归一化。如果先做光照归一化再做对齐,可能会因为光照变化导致关键点检测不准。

4.4 完整流程:从原始图像到标准人脸

说了这么多,我们来画个流程图,把整个预处理流程串起来。

人脸对齐与预处理完整流程 原始图像 关键点检测(5点/68点) 仿射变换(旋转+缩放+平移) 裁剪 + Resize(112×112) 光照归一化(DoG/直方图均衡) 标准人脸(对齐+归一化) 关键点检测是基础 检测不准→对齐失败 建议用68点提升鲁棒性 仿射变换注意 不要只旋转,要同时 考虑缩放和位移 归一化顺序 先对齐后归一化 避免光照影响关键点检测

4.5 避坑指南:我踩过的那些坑

做这行快十年了,有些坑我是一步一步踩过来的。分享几个最常见的:

  • 坑一:关键点检测模型太老。我曾经用MTCNN的5点检测,结果在戴口罩的场景下直接崩了。后来换成RetinaFace,好很多。
  • 坑二:仿射变换的插值方法选错。默认的线性插值在缩小图像时没问题,但放大时会出现锯齿。我建议用双三次插值(INTER_CUBIC)。
  • 坑三:光照归一化过度。有一次我把直方图均衡化用在所有图像上,结果暗光下人脸是清楚了,但正常光照下的脸变得惨白。后来我改成自适应:先判断光照条件,再决定是否做归一化。
  • 坑四:忽略边界处理。仿射变换后,图像边缘可能会出现黑边。我一般用边界填充(BORDER_REFLECT),而不是直接填0。
特别提醒:如果你做的是1:N人脸检索,预处理的一致性非常重要。训练时怎么处理,推理时就要一模一样。我曾经因为训练时用了DoG滤波,推理时忘了加,结果检索精度直接腰斩。