图像预处理基础:灰度化、直方图均衡化、滤波与形态学

图像预处理,说白了就是给原始图像“洗个澡”。

在工业现场,相机拍回来的图往往带着各种“脏东西”——光照不均、传感器噪声、背景干扰。你想想看,如果直接把这种图扔给检测模型,结果能好吗?

我做了这么多年视觉算法,可以负责任地告诉你:预处理做得好,后续算法事半功倍;预处理糊弄事,后面调参调到怀疑人生。

核心观点:预处理不是“可有可无的步骤”,而是决定检测系统鲁棒性的基石。一个稳定的预处理管线,能帮你挡住现场80%的异常情况。

图像预处理知识体系 图像预处理 灰度化 直方图均衡化 滤波去噪 形态学操作 加权平均法 最大值法 高斯滤波 中值滤波 腐蚀 膨胀 开/闭运算 💡 预处理管线设计原则 灰度化 → 直方图均衡化 → 滤波去噪 → 形态学增强 顺序不可随意调换,否则效果大打折扣

1. 灰度化:扔掉颜色,保留结构

工业检测中,大部分场景其实不需要彩色信息。你检测的是划痕、脏污、缺料,这些东西在灰度图上一样看得清清楚楚。

灰度化的本质,就是把RGB三个通道压缩成一个通道。常用的方法有三种:

方法 公式 特点 适用场景
加权平均法 Gray = 0.299R + 0.587G + 0.114B 符合人眼视觉特性 通用场景,推荐使用
平均值法 Gray = (R+G+B)/3 计算简单,但丢失对比度 对颜色不敏感的场景
最大值法 Gray = max(R,G,B) 保留高亮细节 高光检测、反光表面

我的习惯:除非有特殊需求,否则一律用加权平均法。OpenCV里一行 cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 就搞定了。别自己手写公式,容易踩坑。

2. 直方图均衡化:让暗部亮起来

现场打光不均匀是常态。有时候工件左边亮右边暗,有时候整体偏暗。直方图均衡化就是用来解决这个问题的。

它的原理很简单:把像素值的分布“拉平”,让暗的地方变亮,亮的地方适当压暗,从而增强全局对比度。

我曾经踩过的坑:有一次在手机屏幕划痕检测项目中,直接对整图做全局直方图均衡化,结果把背景纹理也增强了,误检率飙升。后来改用自适应直方图均衡化(CLAHE),只对局部区域做均衡,效果立竿见影。

CLAHE 的核心参数有两个:

  • clipLimit:对比度限制值,默认40。值越大,增强效果越强,但噪声也会被放大。
  • tileGridSize:分块大小,默认8x8。块越小,局部适应性越好,但计算量也越大。
import cv2
import numpy as np

# 全局直方图均衡化
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_eq = cv2.equalizeHist(img_gray)

# CLAHE(推荐)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
img_clahe = clahe.apply(img_gray)

3. 滤波去噪:高斯 vs 中值

工业相机在高ISO下会产生噪声,尤其是CMOS传感器。滤波的目的就是把这些“椒盐”去掉,同时尽量保留边缘。

两种最常用的滤波器,各有各的脾气:

滤波器 原理 优点 缺点 典型应用
高斯滤波 加权平均,中心权重最大 平滑效果好,边缘保留尚可 对椒盐噪声效果一般 光照均匀、噪声较小的场景
中值滤波 取邻域像素的中位数 对椒盐噪声极有效 计算稍慢,可能模糊细节 传感器噪声、颗粒状缺陷

经验之谈:在金属表面划痕检测中,我习惯先用中值滤波去掉孤立噪点,再用高斯滤波做一次轻量平滑。顺序不能反——先高斯再中值,会把噪声“抹匀”到周围像素,反而更难去除。

# 高斯滤波
img_gaussian = cv2.GaussianBlur(img, (5,5), sigmaX=1.0)

# 中值滤波
img_median = cv2.medianBlur(img, ksize=5)

# 组合使用:先中值后高斯
img_denoised = cv2.GaussianBlur(cv2.medianBlur(img, 3), (3,3), 0.5)

4. 形态学操作:修修补补的艺术

形态学操作,说白了就是“膨胀”和“腐蚀”这两个基本动作的组合。它们处理的是二值图,但也可以用在灰度图上。

4.1 腐蚀与膨胀

  • 腐蚀:让白色区域“瘦一圈”。用于去除小白点、断开粘连。
  • 膨胀:让白色区域“胖一圈”。用于填充小孔、连接断开的区域。

4.2 开运算与闭运算

  • 开运算 = 先腐蚀后膨胀:去除孤立的小白点,同时保持主体形状不变。
  • 闭运算 = 先膨胀后腐蚀:填充小黑洞,连接邻近区域。

我常用的套路:在PCB焊点检测中,先用开运算去掉焊盘周围的锡珠噪声,再用闭运算把焊点内部的空洞填上。核大小选3x3或5x5,迭代次数1-2次就够了,多了会把真实缺陷也抹掉。

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))

# 腐蚀
img_erode = cv2.erode(img_binary, kernel, iterations=1)

# 膨胀
img_dilate = cv2.dilate(img_binary, kernel, iterations=1)

# 开运算
img_open = cv2.morphologyEx(img_binary, cv2.MORPH_OPEN, kernel)

# 闭运算
img_close = cv2.morphologyEx(img_binary, cv2.MORPH_CLOSE, kernel)

5. 预处理管线实战建议

在实际项目中,我不会把上面所有操作都堆上去。而是根据缺陷类型和现场条件,选择最合适的组合。

缺陷类型 推荐预处理流程 关键参数
金属划痕 灰度化 → CLAHE → 中值滤波 → 开运算 CLAHE clipLimit=2.0, 中值核5x5
PCB焊点缺陷 灰度化 → 高斯滤波 → 闭运算 高斯核3x3, 闭运算核3x3
纺织物脏污 灰度化 → 直方图均衡化 → 中值滤波 全局均衡, 中值核7x7
玻璃表面气泡 灰度化 → CLAHE → 高斯滤波 → 开运算 CLAHE tile=16x16, 高斯核5x5

重要提醒:预处理参数不是一成不变的。换一条产线、换一个光源,参数就得重新调。我见过太多工程师把开发环境里的参数直接搬到现场,结果效果一塌糊涂。记住:没有通用的“最佳参数”,只有针对性的“最优组合”。

嗯,这一章的内容就到这里。灰度化、直方图均衡化、滤波、形态学——这四个基础操作,是后续所有高级算法的地基。你可以在自己的项目里多试试不同的组合,慢慢就会找到感觉。