2. 图像预处理:灰度化、直方图均衡化、滤波去噪、边缘增强
拿到一张材料缺陷的原始图像,别急着往上套检测算法。我做了这么多年缺陷检验,吃过不少亏——原始图像里全是噪声、光照不均、对比度低,直接处理的话,后面标注出来的全是假缺陷。
图像预处理,说白了就是给原始图像“洗个澡”,把干扰信息去掉,把缺陷特征凸显出来。这一步做不好,后面再牛的算法也白搭。
2.1 灰度化:去掉颜色干扰
大多数材料缺陷图像是彩色图,但颜色信息对缺陷检测帮助不大。比如金属表面的裂纹、陶瓷的气孔,灰度图就足够表达了。彩色图还多出两个通道的数据,处理起来慢,没必要。
常用的灰度化方法:
| 方法 | 公式 | 适用场景 |
|---|---|---|
| 平均值法 | Gray = (R+G+B)/3 | 通用场景,但细节损失较多 |
| 加权平均法 | Gray = 0.299R + 0.587G + 0.114B | 符合人眼视觉特性,推荐使用 |
| 最大值法 | Gray = max(R,G,B) | 高亮区域保留好,但暗部细节丢失 |
我的习惯: 除非有特殊需求,否则一律用加权平均法。OpenCV 里的
cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 就是用的这个权重,省心。
import cv2
import numpy as np
# 读取彩色图像
img = cv2.imread('defect_sample.jpg')
# 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 保存结果
cv2.imwrite('gray_defect.jpg', gray)
2.2 直方图均衡化:把对比度拉出来
灰度化之后,你可能会发现图像灰蒙蒙的,缺陷和背景分不开。为什么会这样?因为像素值都挤在中间一段,没有拉开。
直方图均衡化就是干这个的——把像素值的分布“拉伸”开,让暗的更暗、亮的更亮。我见过不少新手直接拿原始灰度图去检测,结果裂纹和背景几乎一个颜色,漏检率极高。
两种均衡化方式:
- 全局直方图均衡化: 对整个图像做一次均衡。简单粗暴,但容易放大噪声。
- 自适应直方图均衡化(CLAHE): 把图像分成小块,每块单独均衡,最后拼接。效果更细腻,不会过度增强噪声。
我曾经踩过的坑: 有一次处理钛合金表面的微裂纹,用全局均衡化后,裂纹是清楚了,但表面的加工纹理也被放大,导致误检率飙升。后来换成 CLAHE,限制对比度参数设到 2.0,才把问题解决。
# 全局直方图均衡化
equ = cv2.equalizeHist(gray)
# CLAHE 自适应均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
clahe_result = clahe.apply(gray)
2.3 滤波去噪:别让噪声骗了你的算法
图像里总有些“脏东西”——传感器噪声、光照反射、灰尘颗粒。这些在缺陷检测里都是干扰项。滤波的目的就是把这些噪声去掉,同时尽量保留缺陷的边缘。
常用滤波器对比:
| 滤波器 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 均值滤波 | 邻域像素取平均 | 速度快 | 边缘模糊严重 |
| 高斯滤波 | 加权平均,中心权重高 | 平滑效果好,边缘保留较好 | 对椒盐噪声效果一般 |
| 中值滤波 | 取邻域中值 | 去椒盐噪声极好 | 速度较慢 |
| 双边滤波 | 考虑像素值和空间距离 | 边缘保留最强 | 计算量大 |
我的建议: 处理材料缺陷图像时,优先试中值滤波或双边滤波。缺陷的边缘信息太宝贵了,均值滤波和高斯滤波容易把细小的裂纹边缘“抹平”。
# 中值滤波
median = cv2.medianBlur(clahe_result, 5)
# 双边滤波
bilateral = cv2.bilateralFilter(clahe_result, 9, 75, 75)
2.4 边缘增强:让缺陷轮廓“跳”出来
滤波去噪之后,图像干净了,但缺陷的边缘也可能被削弱了。这时候需要做边缘增强,把缺陷的轮廓重新“勾”出来。
我个人最常用的是 拉普拉斯算子 和 Sobel 算子。拉普拉斯对细节敏感,但容易放大噪声;Sobel 有方向性,适合检测特定方向的裂纹。
增强的两种思路:
- 直接增强: 原图 + 边缘图 × 权重。适合缺陷边缘清晰的情况。
- 反锐化掩模: 原图 - 模糊图 + 原图。适合缺陷边缘模糊的情况。
一个小技巧: 如果你不确定用哪种增强方式,可以先用 Sobel 分别算 x 和 y 方向的梯度,然后取绝对值相加。这样各个方向的缺陷都能被增强。
# Sobel 边缘增强
sobelx = cv2.Sobel(median, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(median, cv2.CV_64F, 0, 1, ksize=3)
sobel_combined = cv2.addWeighted(cv2.convertScaleAbs(sobelx), 0.5,
cv2.convertScaleAbs(sobely), 0.5, 0)
# 拉普拉斯增强
laplacian = cv2.Laplacian(median, cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)
# 原图 + 边缘增强
enhanced = cv2.addWeighted(median, 1.0, laplacian, 0.5, 0)
2.5 预处理流程总览
上面四个步骤不是每次都要全做。你想想看,如果原始图像质量已经很好,滤波和增强反而会引入伪影。我一般按这个流程判断:
核心原则: 预处理不是流水线,每一步都要问自己“这一步对我的缺陷检测有帮助吗?” 做多了反而坏事。
2.6 避坑指南
这些年我见过不少同行在预处理上翻车,总结几个常见问题:
- 过度滤波: 把缺陷边缘也滤掉了,细小裂纹直接消失。记住,滤波的核大小不要超过缺陷尺寸的一半。
- 均衡化过度: 背景噪声被放大,看起来像缺陷。CLAHE 的 clipLimit 从 1.5 开始试,别一上来就设 5.0。
- 顺序搞反: 先增强再滤波,噪声会被放大。正确的顺序是:灰度化 → 均衡化 → 滤波 → 增强。
- 忽略图像格式: 保存中间结果时用 PNG 或 TIFF,别用 JPEG。JPEG 的有损压缩会引入块状伪影,我吃过这个亏。
我曾经犯过的错: 有一次处理一批不锈钢焊缝的缺陷图像,我直接用了默认参数跑完整套预处理流程。结果发现裂纹边缘被高斯滤波抹得一干二净,漏检了 30% 的缺陷。后来改成中值滤波 + 双边滤波的组合,才把召回率拉回来。所以,参数一定要根据实际图像调,别偷懒。
预处理做扎实了,后面的缺陷标注和检测才能靠谱。嗯,这一步花的时间,后面会十倍地省回来。