2、几何变换基础:旋转、平移、缩放、剪切变换的原理与实现
做数据增强,几何变换是最基础、最常用的一类操作。说白了,就是让图片在空间上「动一动」——转个角度、挪个位置、拉近拉远、甚至歪一歪。你想想看,真实世界里的人脸哪能每次都端端正正地出现在镜头正中央?
我刚开始做人脸识别那会儿,总觉得模型训练好了就能应付各种角度。结果一上线,用户侧脸、低头、歪头的情况全来了,模型直接崩了。嗯,从那以后,我再也不敢小看几何变换了。
2.1 几何变换的核心思想
几何变换的本质,就是建立原始图像像素和目标图像像素之间的映射关系。我们用一个变换矩阵来描述这种映射。对于二维图像,这个矩阵是 2×3 的,但为了统一处理,我们通常用齐次坐标,把它扩展成 3×3 矩阵。
核心公式:
[x'] [a b tx] [x]
[y'] = [c d ty] [y]
[1 ] [0 0 1 ] [1]
其中 (x, y) 是原始坐标,(x', y') 是变换后的坐标。矩阵中的参数决定了变换的类型和程度。
我个人习惯把几何变换分成两类:
- 刚体变换:只包含旋转和平移,保持物体形状和大小不变。人脸在平面内转动就属于这类。
- 仿射变换:包含旋转、平移、缩放、剪切,保持平行线平行,但角度和长度会变。人脸从正面到侧面的近似效果可以用剪切来模拟。
2.2 旋转变换
旋转是最常见的增强手段。人脸在照片里很少是正着的,稍微偏个 10°、20° 都很正常。
旋转矩阵:
[cosθ -sinθ tx]
[sinθ cosθ ty]
[0 0 1 ]
θ 是旋转角度,顺时针为正。tx 和 ty 是平移量,用来保证旋转后图像不跑出画布。
我的经验:旋转角度一般控制在 ±15° 以内。超过 30°,人脸特征就开始严重变形,模型反而学不到有效信息。我在一个安防项目里试过 ±45° 旋转,结果模型把倒立的人脸当成了新类别,哭笑不得。
实现要点:
- 旋转中心通常选图像中心,而不是左上角
- 旋转后会产生空白区域,需要填充策略(补黑、补边缘像素、镜像填充)
- 角度步长建议 5° 或 10°,太密了数据冗余,太疏了覆盖不全
2.3 平移变换
平移就是让人脸在图像里「溜达」一下。人脸检测框不可能每次都完美居中,平移能模拟这种偏移。
平移矩阵:
[1 0 tx]
[0 1 ty]
[0 0 1 ]
tx 和 ty 分别是水平和垂直方向的偏移量,单位是像素。
| 参数 | 推荐范围 | 说明 |
|---|---|---|
| tx | ±10% 图像宽度 | 水平偏移,过大导致人脸出框 |
| ty | ±10% 图像高度 | 垂直偏移,过大导致人脸出框 |
注意:平移后一定要检查人脸关键点是否还在图像内。我曾经因为平移过大,导致模型学到了「半张脸」的特征,推理时遇到半脸就误判。
2.4 缩放变换
缩放模拟的是人脸距离摄像头的远近变化。同一个人的脸,在 1 米和 2 米处拍出来,大小差很多。
缩放矩阵:
[sx 0 0]
[0 sy 0]
[0 0 1]
sx 和 sy 分别是水平和垂直方向的缩放因子。sx = sy 是等比例缩放,不等比例会把人脸拉胖或拉瘦。
我的建议:
- 等比例缩放范围:0.8 ~ 1.2
- 不等比例缩放:sx 和 sy 差异不要超过 0.1,否则人脸变形太明显
- 缩放后图像尺寸会变,需要裁剪或填充到统一大小
避坑指南:我曾经在一个人脸支付项目里,把缩放因子设到了 0.5 ~ 2.0,想着覆盖更多场景。结果模型把「大头照」和「远景小人」当成了不同的人。后来我把范围缩到 0.85 ~ 1.15,效果反而更好。数据增强不是越猛越好,适度才是关键。
2.5 剪切变换
剪切变换(Shear)很多人不常用,但我觉得它特别有用。它能模拟人脸从正面到侧面的过渡效果,或者摄像头倾斜拍摄时的透视变形。
剪切矩阵:
[1 shx 0]
[shy 1 0]
[0 0 1]
shx 是水平剪切因子,shy 是垂直剪切因子。shx > 0 时,图像向右倾斜;shy > 0 时,图像向下倾斜。
| 剪切类型 | 参数范围 | 模拟场景 |
|---|---|---|
| 水平剪切 | ±0.2 | 左右转头 |
| 垂直剪切 | ±0.15 | 上下点头 |
小技巧:剪切变换可以和旋转组合使用,效果更接近真实的人脸姿态变化。我通常先做剪切,再做旋转,这样计算量更小,效果也更自然。
2.6 四种变换的对比与组合
这四种变换不是孤立的。在实际项目中,我通常把它们组合起来用。下面这张图展示了它们的关系:
2.7 代码实现示例
下面我用 OpenCV 演示四种变换的实现。这是我在项目中常用的代码模板:
import cv2
import numpy as np
def geometric_augmentation(image, angle=10, tx=10, ty=10, scale=1.1, shear=0.1):
h, w = image.shape[:2]
center = (w // 2, h // 2)
# 1. 旋转矩阵
rot_mat = cv2.getRotationMatrix2D(center, angle, 1.0)
# 2. 平移矩阵
trans_mat = np.float32([[1, 0, tx], [0, 1, ty]])
# 3. 缩放矩阵
scale_mat = np.float32([[scale, 0, 0], [0, scale, 0]])
# 4. 剪切矩阵
shear_mat = np.float32([[1, shear, 0], [shear, 1, 0]])
# 组合变换:先剪切,再旋转,再缩放,最后平移
combined = shear_mat @ rot_mat[:2, :] # 注意矩阵乘法顺序
combined = scale_mat @ combined
combined = trans_mat @ combined
result = cv2.warpAffine(image, combined, (w, h))
return result
重要提醒:矩阵乘法的顺序会影响最终结果。我习惯从右往左读:先应用的变换放在最右边。上面代码的顺序是:剪切 → 旋转 → 缩放 → 平移。顺序不同,效果完全不同。
2.8 实战建议总结
做了这么多年人脸识别,我总结了几条几何变换的实战经验:
- 不要贪多:每种变换的参数范围要合理,过度增强反而降低模型性能
- 组合优于单一:多种变换组合使用,比单一变换效果更好
- 保持人脸完整性:变换后要确保人脸关键点仍在图像内
- 随机化参数:每次训练时随机生成参数,而不是固定几种变换
- 验证集不做增强:几何变换只用于训练集,验证集和测试集保持原始数据
我的习惯:每次新增一种几何变换策略,我都会先在验证集上跑一轮,看看准确率变化。如果准确率下降超过 1%,说明这个策略有问题,需要调整参数或者直接去掉。数据增强不是越多越好,有效才是硬道理。