3. 图像预处理基础:从原始图像到可分析数据
做光学玻璃缺陷检测,第一步就是把相机拍到的原始图像,变成计算机能理解、能分析的数据。这一步做不好,后面所有算法都是白搭。我见过太多项目,算法调了几个月没效果,最后发现是预处理环节出了问题。
说白了,图像预处理就是给图像“洗个澡”,把杂质去掉,把特征凸显出来。今天咱们就聊聊这个洗澡流程里的几个关键步骤。
3.1 图像读取与显示:你的第一行代码
不管用什么库,OpenCV也好,Halcon也好,第一步都是把图像从硬盘读到内存里。我个人习惯用OpenCV的imread,简单直接。
import cv2
# 读取图像,默认是彩色图
img = cv2.imread('glass_defect.jpg', cv2.IMREAD_COLOR)
# 显示图像
cv2.imshow('Original Image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
读取图像时,第二个参数很关键。我整理了一张表,方便你对照使用:
| 标志 | 值 | 说明 |
|---|---|---|
cv2.IMREAD_COLOR |
1 | 加载彩色图像,忽略透明度 |
cv2.IMREAD_GRAYSCALE |
0 | 直接以灰度模式加载 |
cv2.IMREAD_UNCHANGED |
-1 | 保留所有通道,包括Alpha |
做缺陷检测时,我建议你直接读成灰度图。为什么?因为玻璃缺陷检测通常只关心亮度变化,颜色信息反而是干扰。
3.2 灰度化与二值化:把问题简化
灰度化就是把彩色图变成黑白图。每个像素用一个0-255的值表示亮度。0是纯黑,255是纯白。
二值化就更狠了——直接把像素分成两类:要么是0(黑),要么是255(白)。这就像把世界分成好人和坏人,简单粗暴但有效。
# 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 全局阈值二值化
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 自适应阈值二值化(推荐用于光照不均的情况)
adaptive_binary = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2
)
阈值选择是个技术活。常用的方法有:
- 固定阈值:简单,但只适合光照稳定的场景
- Otsu算法:自动计算最佳阈值,适合双峰直方图
- 自适应阈值:每个像素用邻域内的阈值,抗光照不均
- 三角法:适合单峰直方图,比如玻璃划痕检测
3.3 直方图均衡化:让对比度“站”起来
你想想看,如果一张图像整体偏暗,或者整体偏亮,缺陷就很难看出来。直方图均衡化就是干这个的——把像素值的分布拉开,让暗的更暗,亮的更亮。
# 全局直方图均衡化
equ = cv2.equalizeHist(gray)
# 限制对比度自适应直方图均衡化(CLAHE)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
clahe_result = clahe.apply(gray)
这里我要特别说一下CLAHE。普通的直方图均衡化会把噪声也放大,尤其是玻璃表面的细微纹理。CLAHE通过限制对比度增幅,能有效避免这个问题。
直方图均衡化前后的效果对比:
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 全局均衡化 | 简单、速度快 | 容易放大噪声 | 光照均匀的玻璃 |
| CLAHE | 抑制噪声、局部自适应 | 参数需要调 | 光照不均、纹理复杂 |
3.4 滤波去噪:把“雪花”扫干净
相机传感器、环境光、电路干扰……这些都会给图像带来噪声。噪声就像玻璃上的灰尘,会干扰缺陷检测。滤波就是把这些噪声去掉,同时尽量保留缺陷的边缘。
常用的两种滤波:高斯滤波和中值滤波。
# 高斯滤波
gaussian = cv2.GaussianBlur(gray, (5, 5), 1.5)
# 中值滤波
median = cv2.medianBlur(gray, 5)
这两种滤波的区别,我画了张图帮你理解:
嗯,这里要注意一个关键点:滤波核的大小。核越大,去噪越强,但细节丢失也越多。我一般从3x3开始试,效果不够就换5x5,很少用到7x7以上。
3.5 预处理流程:把步骤串起来
在实际项目中,这些步骤不是孤立使用的。我总结了一个标准流程:
- 读取图像 → 确定是彩色还是灰度
- 灰度化 → 如果读的是彩色图
- 直方图均衡化 → 提升对比度,推荐CLAHE
- 滤波去噪 → 根据噪声类型选择高斯或中值
- 二值化 → 把缺陷从背景中分离出来
def preprocess_pipeline(img_path):
# 1. 读取
img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
# 2. CLAHE增强
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
enhanced = clahe.apply(img)
# 3. 中值滤波去噪
denoised = cv2.medianBlur(enhanced, 5)
# 4. 自适应二值化
binary = cv2.adaptiveThreshold(
denoised, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV, 11, 2
)
return binary
这个流程我用了好几年,在大多数玻璃缺陷检测场景下都管用。当然,具体参数要根据你的实际图像来调。没有万能参数,只有不断试错。
debug_show()函数,一键查看所有中间图像。
图像预处理说难不难,说简单也不简单。关键是要理解每一步在干什么,为什么要这么干。等你做多了,看到一张图像,脑子里自然就会浮现出该用哪套预处理方案。这就是经验。