4、人脸对齐与预处理:关键点检测、仿射变换、归一化处理
说实话,很多人觉得人脸识别就是“拍一张照片,然后比对”。但真正做过工程的人都知道,预处理这一步没做好,后面算法再牛也白搭。我见过太多项目,模型选得挺好,训练数据也够,结果上线后识别率惨不忍睹——查来查去,问题就出在输入的人脸没对齐。
这一章,我们就来聊聊人脸对齐和预处理。说白了,就是把一张歪七扭八、光照不均的人脸照片,变成一张“标准证件照”。
4.1 关键点检测:找到脸的“锚点”
要对齐人脸,首先得知道脸在哪儿、五官在哪儿。关键点检测就是干这个的。我个人习惯把关键点分为两类:
- 稀疏关键点(5点/21点):速度快,适合实时场景。比如眼睛、鼻子、嘴角。
- 密集关键点(68点/106点):精度高,适合精细分析。比如人脸轮廓、眉毛形状。
我在项目中遇到过一个问题:用5点检测做对齐,速度是快了,但遇到大角度侧脸时,眼睛和嘴角的位置偏差很大,导致后续识别率直接掉了十几个点。后来换成68点检测,虽然慢了一点,但稳定性好很多。
| 关键点类型 | 典型点数 | 适用场景 | 速度 | 精度 |
|---|---|---|---|---|
| 稀疏 | 5点(眼×2、鼻、嘴角×2) | 实时视频流、移动端 | 快(<1ms) | 中等 |
| 密集 | 68点(轮廓+五官) | 离线处理、高精度需求 | 较慢(5-15ms) | 高 |
| 超密集 | 106点及以上 | 3D重建、表情分析 | 慢(>20ms) | 极高 |
4.2 仿射变换:把歪脸“掰正”
有了关键点,下一步就是把人脸转正。这里用到的核心技术叫仿射变换。你想想看,它其实就是做三件事:旋转、缩放、平移。
具体怎么做?我一般分三步走:
- 计算变换矩阵:根据检测到的关键点(比如两只眼睛的位置),和目标位置(比如标准正脸的眼睛坐标),算出一个2×3的矩阵。
- 应用变换:用这个矩阵对整张图做映射。OpenCV里一行
cv2.warpAffine就搞定了。 - 裁剪输出:对齐后的人脸,统一裁剪成固定尺寸(比如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 |
4.4 完整流程:从原始图像到标准人脸
说了这么多,我们来画个流程图,把整个预处理流程串起来。
4.5 避坑指南:我踩过的那些坑
做这行快十年了,有些坑我是一步一步踩过来的。分享几个最常见的:
- 坑一:关键点检测模型太老。我曾经用MTCNN的5点检测,结果在戴口罩的场景下直接崩了。后来换成RetinaFace,好很多。
- 坑二:仿射变换的插值方法选错。默认的线性插值在缩小图像时没问题,但放大时会出现锯齿。我建议用双三次插值(INTER_CUBIC)。
- 坑三:光照归一化过度。有一次我把直方图均衡化用在所有图像上,结果暗光下人脸是清楚了,但正常光照下的脸变得惨白。后来我改成自适应:先判断光照条件,再决定是否做归一化。
- 坑四:忽略边界处理。仿射变换后,图像边缘可能会出现黑边。我一般用边界填充(BORDER_REFLECT),而不是直接填0。