4. 数据预处理与增强
数据预处理和增强,说白了就是给模型“喂饭”前的准备工作。你想想看,工业相机拍出来的原始图像,直接扔给神经网络,十有八九要出问题。我刚开始做缺陷检测项目时,就吃过这个亏——模型在实验室跑得好好的,一上产线就崩。后来才发现,是数据预处理没做好。
这一节,我们重点聊四个核心环节:图像归一化、尺寸统一、数据增强策略,以及一个比较高级的技巧——CutMix。每个环节我都会结合项目实战经验来讲,希望能帮你少走弯路。
4.1 图像归一化:消除光照差异的“魔法”
工业场景中,光照变化是最大的敌人。同一款产品,早上拍和下午拍,亮度可能差30%。如果直接把原始像素值喂给模型,它会把光照变化当成缺陷来学。这问题我遇到过不止一次。
归一化,就是让图像数据变成“标准格式”。常用的方法有两种:
| 归一化方法 | 公式 | 适用场景 | 我的建议 |
|---|---|---|---|
| Min-Max归一化 | (x - min) / (max - min) | 图像对比度差异大 | 适合简单场景,但易受异常像素干扰 |
| Z-score归一化 | (x - μ) / σ | 光照变化复杂 | 工业场景首选,鲁棒性更好 |
| 直方图均衡化 | 基于累积分布函数 | 低对比度图像 | 慎用,可能放大噪声 |
我个人习惯用Z-score归一化。为什么?因为工业产线的光照变化往往是全局性的,不是局部的。Z-score能很好地消除这种全局偏移。举个例子:
import cv2
import numpy as np
def z_score_normalize(image):
"""
Z-score归一化
适用于工业缺陷检测场景
"""
# 计算均值和标准差
mean = np.mean(image)
std = np.std(image)
# 防止除零
if std == 0:
return image
# 归一化
normalized = (image - mean) / std
# 缩放到0-255范围
normalized = ((normalized - normalized.min()) /
(normalized.max() - normalized.min()) * 255).astype(np.uint8)
return normalized
# 使用示例
img = cv2.imread('defect_sample.jpg', cv2.IMREAD_GRAYSCALE)
img_norm = z_score_normalize(img)
4.2 尺寸统一:别让模型“看不清”
工业相机拍出来的图像,尺寸五花八门。有的产线用500万像素相机,有的用1200万。如果不统一尺寸,模型训练时batch size都设不了——你想想看,不同尺寸的图像怎么拼成一个tensor?
尺寸统一有两种主流策略:
- 直接Resize:简单粗暴,但会改变缺陷的形态。比如一个圆形缺陷,Resize后可能变成椭圆。这在精密检测中是不能接受的。
- Padding + Resize:先按比例缩放,再用黑色或灰色填充到目标尺寸。这样缺陷的形态保持不变,但会引入无效区域。
我建议用第二种方法。特别是对于PCB焊点检测这类场景,缺陷的几何形态是判断是否合格的关键指标。直接Resize会丢失这些信息。
def resize_with_padding(image, target_size=(224, 224), pad_color=0):
"""
保持宽高比的尺寸统一
target_size: (width, height)
pad_color: 填充颜色,0为黑色
"""
h, w = image.shape[:2]
target_w, target_h = target_size
# 计算缩放比例
scale = min(target_w / w, target_h / h)
new_w = int(w * scale)
new_h = int(h * scale)
# 缩放图像
resized = cv2.resize(image, (new_w, new_h))
# 创建画布并填充
canvas = np.full((target_h, target_w) + image.shape[2:],
pad_color, dtype=np.uint8)
# 计算偏移量
x_offset = (target_w - new_w) // 2
y_offset = (target_h - new_h) // 2
# 粘贴缩放后的图像
canvas[y_offset:y_offset+new_h, x_offset:x_offset+new_w] = resized
return canvas
4.3 数据增强策略:让模型见过“世面”
工业缺陷检测最大的痛点是——正负样本极度不平衡。正常产品占99%,缺陷产品只有1%。如果不做数据增强,模型会学成一个“永远说正常”的废物。
常用的数据增强策略,我按实用程度排个序:
| 增强方法 | 适用场景 | 增强幅度建议 | 注意事项 |
|---|---|---|---|
| 水平翻转 | 所有场景 | 50%概率 | 注意文字、方向性缺陷 |
| 旋转(±15°) | 圆形、对称产品 | 30%概率 | 旋转后需裁剪或填充 |
| 高斯噪声 | 低光照场景 | σ=0.01~0.05 | 噪声太强会掩盖缺陷 |
| 亮度调整 | 光照变化大的产线 | ±20% | 模拟早晚光照差异 |
| Cutout | 遮挡场景 | 随机遮挡10%区域 | 遮挡区域用随机值填充 |
这里我要特别强调一点:数据增强不是越多越好。我见过有人把旋转角度设到±90°,结果模型把“竖着的缺陷”和“横着的缺陷”当成了两种不同的东西。过犹不及。
4.4 CutMix:小样本缺陷的“救星”
CutMix是我最近两年在工业项目中用得越来越多的技术。它的核心思想很简单:把两张图“拼”在一起,让模型同时学习两种特征。
具体做法是:
- 从训练集中随机选两张图:一张正常产品,一张缺陷产品
- 在正常产品上随机裁剪一个矩形区域
- 用缺陷产品的对应区域替换掉这个矩形
- 标签也按面积比例混合:比如正常占70%,缺陷占30%
这样做的好处是:模型不会只盯着“整张图是不是正常”,而是学会关注“局部区域有没有异常”。这对于小缺陷检测特别有效。
def cutmix(image1, image2, mask1, mask2, alpha=1.0):
"""
CutMix数据增强
image1, image2: 输入图像
mask1, mask2: 对应的标签(缺陷掩码)
alpha: Beta分布的参数,控制混合比例
"""
h, w = image1.shape[:2]
# 随机生成混合比例
lam = np.random.beta(alpha, alpha)
# 随机裁剪区域
cut_ratio = np.sqrt(1. - lam)
cut_w = int(w * cut_ratio)
cut_h = int(h * cut_ratio)
cx = np.random.randint(w)
cy = np.random.randint(h)
x1 = np.clip(cx - cut_w // 2, 0, w)
y1 = np.clip(cy - cut_h // 2, 0, h)
x2 = np.clip(cx + cut_w // 2, 0, w)
y2 = np.clip(cy + cut_h // 2, 0, h)
# 混合图像
mixed_image = image1.copy()
mixed_image[y1:y2, x1:x2] = image2[y1:y2, x1:x2]
# 混合标签(按面积比例)
area_ratio = (x2 - x1) * (y2 - y1) / (w * h)
mixed_mask = mask1.copy()
mixed_mask[y1:y2, x1:x2] = mask2[y1:y2, x1:x2]
return mixed_image, mixed_mask, 1. - area_ratio
4.5 实战中的“坑”与“解”
说了这么多理论,最后分享几个我在项目中踩过的坑:
- 坑一:数据增强后忘记同步标签。比如旋转图像后,缺陷的坐标位置也变了,但标签没更新。结果模型学了一堆错误的位置信息。
- 坑二:增强参数设置太激进。有一次我把亮度调整范围设到±50%,结果模型把“暗图”和“亮图”当成了两种不同的缺陷类型。
- 坑三:验证集也做了增强。这是新手最容易犯的错误——验证集应该保持原始数据分布,增强只用在训练集上。
数据预处理和增强,看似简单,实则是工业缺陷检测项目中最容易出问题的环节。做好了,模型泛化能力提升30%不是问题;做不好,后面再怎么调参也是白费。希望这一节的内容能帮你打好基础,少走弯路。