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% 的缺陷。后来改成中值滤波 + 双边滤波的组合,才把召回率拉回来。所以,参数一定要根据实际图像调,别偷懒。

预处理做扎实了,后面的缺陷标注和检测才能靠谱。嗯,这一步花的时间,后面会十倍地省回来。