4. 经典数据增强方法(上)
数据增强,说白了就是「无中生有」。但这不是瞎编,而是用合理的变换让模型看到更多样的数据。我做了这么多年缺陷检测,可以负责任地告诉你:一个靠谱的增强策略,往往比调模型结构更能提升泛化能力。
这一章我们先聊最经典的两大类:几何变换和颜色变换。它们简单、高效,是每个实战项目里几乎必用的手段。
4.1 几何变换:让模型学会「换个角度看缺陷」
几何变换的核心思想很简单:缺陷出现在图像的不同位置、不同角度,本质上还是同一个缺陷。模型如果学不会这个,就会把「左上角的划痕」和「右下角的划痕」当成两种东西。
4.1.1 旋转(Rotation)
旋转是最直观的几何变换。我习惯把旋转分为两类:
- 小角度旋转(±10°~±30°):模拟拍摄时轻微的倾斜,适合大多数工业场景
- 大角度旋转(90°、180°、270°):适合圆形对称工件,或者方向无关的缺陷
这里有个坑——旋转后的黑边。默认的旋转操作会在图像角落填充黑色像素,这些黑边会被模型当成「特征」学进去。我曾经在一个项目中,模型莫名其妙对图像四角特别敏感,排查了半天才发现是旋转增强留下的黑边在作祟。
cv2.warpAffine 时,设置 borderMode=cv2.BORDER_REFLECT 或 cv2.BORDER_WRAP,避免黑边污染。
import cv2
import numpy as np
def rotate_image(image, angle, scale=1.0):
h, w = image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, scale)
# 关键:用 BORDER_REFLECT 代替默认的 BORDER_CONSTANT
rotated = cv2.warpAffine(image, M, (w, h),
borderMode=cv2.BORDER_REFLECT)
return rotated
# 使用示例:随机旋转 -20° 到 20°
angle = np.random.uniform(-20, 20)
aug_img = rotate_image(original_img, angle)
4.1.2 翻转(Flip)
翻转分两种:水平翻转和垂直翻转。水平翻转最常见,相当于照镜子。垂直翻转在工业场景中要谨慎使用——你想想看,一个螺丝钉倒过来放,跟正着放,在物理世界中其实是两种状态。
我个人习惯这样判断:如果缺陷的方向性不重要,就用翻转;如果缺陷有明确的方向(比如划痕的方向),就别乱翻。
| 翻转类型 | 适用场景 | 慎用场景 |
|---|---|---|
| 水平翻转 | 对称工件、纹理缺陷、印刷品 | 有左右方向标识的工件 |
| 垂直翻转 | 圆形工件、无方向性缺陷 | 有重力方向依赖的缺陷(如流痕) |
4.1.3 裁剪(Cropping)
裁剪有两种玩法:
- 随机裁剪:从原图中随机切出一块,再缩放到原图大小。这相当于让模型看到缺陷的局部特写。
- 中心裁剪:固定裁剪中心区域,适合去除边缘干扰。
嗯,这里要注意——裁剪比例别太狠。我见过有人把 640×640 的图裁到 224×224,结果缺陷特征完全丢失了。一般来说,裁剪比例控制在 0.7~0.9 之间比较安全。
4.2 颜色变换:应对光照变化的「特效药」
颜色变换解决的是另一个问题:同一个缺陷,在不同光照下看起来不一样。车间里的光源会老化、会更换、会有阴影,模型如果没见过这些变化,上线就崩。
4.2.1 亮度(Brightness)
亮度调整说白了就是给每个像素加上或减去一个值。在 HSV 色彩空间里操作会更自然——只调 V 通道,不影响颜色本身。
def adjust_brightness(image, delta):
"""delta 范围:-50 ~ 50"""
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
v = cv2.add(v, delta)
v = np.clip(v, 0, 255)
hsv = cv2.merge([h, s, v])
return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
# 随机调整亮度
delta = np.random.randint(-30, 30)
aug_img = adjust_brightness(original_img, delta)
我一般把亮度调整范围控制在 ±30 以内。太暗了细节全丢,太亮了过曝,反而引入噪声。
4.2.2 对比度(Contrast)
对比度调整会让「亮的更亮,暗的更暗」。这在缺陷检测中特别有用——有些缺陷在低对比度下几乎看不见,模型需要学会在各种对比度下都能识别。
对比度的数学本质是:new_pixel = alpha * (pixel - 128) + 128。alpha 大于 1 时增强对比度,小于 1 时降低。
4.2.3 饱和度(Saturation)
饱和度控制颜色的鲜艳程度。在工业场景中,饱和度调整主要用来模拟不同材质表面的颜色差异。比如同一批塑料件,不同批次可能有轻微色差,饱和度增强可以覆盖这种差异。
操作上,还是在 HSV 空间调 S 通道:
def adjust_saturation(image, factor):
"""factor 范围:0.5 ~ 1.5"""
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
s = cv2.multiply(s, factor)
s = np.clip(s, 0, 255)
hsv = cv2.merge([h, s, v])
return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
4.3 组合策略:1+1 > 2
单独用某一种变换效果有限。我习惯把几何变换和颜色变换串起来用:
- 先做几何变换(旋转 + 翻转 + 裁剪)
- 再做颜色变换(亮度 + 对比度 + 饱和度)
顺序为什么这么定?因为几何变换会改变像素位置,如果先调颜色再旋转,旋转后的插值计算会轻微改变颜色值,造成不必要的误差。
imgaug 或 albumentations 库可以轻松组合多个增强操作。我个人偏爱 albumentations,它的速度比 OpenCV 原生实现还快,而且自动处理标注框的变换。
import albumentations as A
transform = A.Compose([
A.Rotate(limit=20, border_mode=cv2.BORDER_REFLECT, p=0.8),
A.HorizontalFlip(p=0.5),
A.RandomCrop(height=512, width=512, p=0.5),
A.RandomBrightnessContrast(brightness_limit=0.2,
contrast_limit=0.2, p=0.8),
A.HueSaturationValue(hue_shift_limit=0,
sat_shift_limit=20,
val_shift_limit=0, p=0.5),
])
# 使用
augmented = transform(image=image, mask=mask)
aug_img = augmented['image']
aug_mask = augmented['mask']
4.4 参数选择的「黄金法则」
参数调太大,增强后的图像失真,模型学坏;参数调太小,等于没做。我总结了一个经验表格:
| 增强操作 | 推荐参数范围 | 过大的后果 |
|---|---|---|
| 旋转角度 | ±15° ~ ±30° | 缺陷变形严重,失去物理意义 |
| 亮度偏移 | ±20 ~ ±40 | 过曝或过暗,细节丢失 |
| 对比度系数 | 0.7 ~ 1.3 | 图像出现「塑料感」,不真实 |
| 饱和度系数 | 0.6 ~ 1.4 | 颜色失真,引入伪缺陷 |
| 裁剪比例 | 0.7 ~ 0.9 | 缺陷被裁掉,标注失效 |