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()
注意:OpenCV读取彩色图像时,默认通道顺序是BGR,不是RGB。如果你用matplotlib显示,记得转换一下。我曾经因为这个坑,花了整整一个下午排查颜色不对的问题。

读取图像时,第二个参数很关键。我整理了一张表,方便你对照使用:

标志 值 说明
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
)
我的经验:玻璃缺陷检测中,全局阈值往往不够用。因为玻璃表面光照不均匀,同一个缺陷在不同位置亮度不一样。自适应阈值能很好地解决这个问题。我在检测钢化玻璃气泡时,就是靠自适应阈值把误检率从15%降到了3%。

阈值选择是个技术活。常用的方法有:

  • 固定阈值:简单,但只适合光照稳定的场景
  • 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之后,划痕清晰可见,检测准确率从60%直接跳到92%。

直方图均衡化前后的效果对比:

方法 优点 缺点 适用场景
全局均衡化 简单、速度快 容易放大噪声 光照均匀的玻璃
CLAHE 抑制噪声、局部自适应 参数需要调 光照不均、纹理复杂

3.4 滤波去噪:把“雪花”扫干净

相机传感器、环境光、电路干扰……这些都会给图像带来噪声。噪声就像玻璃上的灰尘,会干扰缺陷检测。滤波就是把这些噪声去掉,同时尽量保留缺陷的边缘。

常用的两种滤波:高斯滤波和中值滤波。

# 高斯滤波
gaussian = cv2.GaussianBlur(gray, (5, 5), 1.5)

# 中值滤波
median = cv2.medianBlur(gray, 5)

这两种滤波的区别,我画了张图帮你理解:

高斯滤波 vs 中值滤波 高斯滤波 加权平均邻域像素 对高斯噪声效果好 边缘会被模糊 中值滤波 取邻域像素的中位数 对椒盐噪声效果极好 边缘保持较好 选择建议: 玻璃划痕检测用中值滤波,表面颗粒检测用高斯滤波

嗯,这里要注意一个关键点:滤波核的大小。核越大,去噪越强,但细节丢失也越多。我一般从3x3开始试,效果不够就换5x5,很少用到7x7以上。

避坑指南:我曾经在一个项目中,为了去噪把滤波核设成了15x15。结果噪声是没了,但微小的裂纹也全被抹平了。检测率直接掉到零。记住:滤波的目的是去噪,不是毁图。

3.5 预处理流程:把步骤串起来

在实际项目中,这些步骤不是孤立使用的。我总结了一个标准流程:

  1. 读取图像 → 确定是彩色还是灰度
  2. 灰度化 → 如果读的是彩色图
  3. 直方图均衡化 → 提升对比度,推荐CLAHE
  4. 滤波去噪 → 根据噪声类型选择高斯或中值
  5. 二值化 → 把缺陷从背景中分离出来
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()函数,一键查看所有中间图像。

图像预处理说难不难,说简单也不简单。关键是要理解每一步在干什么,为什么要这么干。等你做多了,看到一张图像,脑子里自然就会浮现出该用哪套预处理方案。这就是经验。