4、图像预处理基础:灰度化、直方图均衡化、高斯滤波、中值滤波的原理与OpenCV实现
图像预处理,说白了就是给原始图像“洗把脸”。
在工业缺陷检测里,相机拍回来的图往往带着各种“脏东西”——光照不均、传感器噪声、背景纹理干扰。你直接拿这些图去做检测,算法很容易被带偏。我刚开始做项目时,就吃过这个亏。当时一个手机玻璃盖板划痕检测项目,死活跑不出效果,折腾了两周才发现是光源频闪导致的灰度波动没处理。
从那以后,我养成了一个习惯:预处理做扎实了,后面的算法才能站得住脚。
核心观点:预处理不是“可有可无的步骤”,它是整个检测流程的基石。你花在预处理上的每一分钟,都会在后续算法调试中加倍回报给你。
4.1 灰度化:扔掉颜色,保留结构
工业相机拍出来的图,大部分是彩色图(BGR格式)。但缺陷检测真的需要颜色信息吗?
说实话,90%的工业场景不需要。划痕、凹坑、脏污、裂纹——这些缺陷在灰度图上就已经足够明显。彩色图反而会引入额外的计算量和干扰。
灰度化的本质,就是把每个像素的R、G、B三个通道,按一定权重合并成一个值。OpenCV里最常用的方法是:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
这行代码背后,OpenCV用的是这个公式:
Gray = 0.299 * R + 0.587 * G + 0.114 * B
为什么权重不一样?因为人眼对绿色最敏感,对蓝色最不敏感。这个权重是心理学实验测出来的,咱们直接用就行。
我的经验:如果你处理的图像是单色光源(比如红光照明),可以考虑直接用单通道提取。比如用红光照明,直接取R通道的效果往往比标准灰度化更好。我在一个金属表面划痕项目中试过,对比度提升了约15%。
4.2 直方图均衡化:让暗的地方亮起来
你想想看,如果一张图整体偏暗,或者整体偏亮,缺陷区域和背景的对比度就会很低。这时候就需要直方图均衡化出手了。
直方图均衡化的原理,说白了就是把像素值的分布“拉平”。原本集中在某个小范围的灰度值,被映射到整个0-255区间。这样暗的地方变亮,亮的地方变暗,对比度就上来了。
OpenCV实现分两种:
| 方法 | 适用场景 | 代码 |
|---|---|---|
| 全局均衡化 | 光照整体不均匀 | cv2.equalizeHist(gray) |
| CLAHE(自适应) | 局部光照差异大 | cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) |
这里我要特别说一下CLAHE。全局均衡化有个毛病——它会放大噪声。尤其是图像中原本比较均匀的区域,均衡化后会出现“块状感”。
避坑指南:我曾经在一个PCB焊点检测项目里,直接用全局均衡化,结果把背景纹理的噪声放大了,导致误检率飙升到30%。后来换成CLAHE,把clipLimit设到2.0,tileGridSize用8x8,误检率直接降到3%以下。记住:工业场景优先用CLAHE。
import cv2
# 全局均衡化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
eq_global = cv2.equalizeHist(gray)
# CLAHE自适应均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
eq_clahe = clahe.apply(gray)
4.3 高斯滤波:温柔的模糊
高斯滤波是工业视觉里最常用的平滑滤波器。它的核心思想很简单:用邻域像素的加权平均来替代中心像素。权重由高斯函数决定,离中心越近的像素权重越大。
为什么叫“高斯”?因为权重分布服从高斯分布(正态分布)。
OpenCV里一行代码搞定:
blurred = cv2.GaussianBlur(gray, (5,5), sigmaX=1.5)
参数说明:
- 核大小 (5,5):必须是奇数。核越大,模糊越强,但细节丢失也越多。
- sigmaX:X方向的标准差。一般设1.0-2.0之间。如果设为0,OpenCV会根据核大小自动计算。
关键点:高斯滤波擅长处理高斯噪声——也就是那种在图像上随机分布的、幅度符合高斯分布的噪声。这种噪声在工业场景中很常见,比如传感器热噪声、低光照下的电子噪声。
我个人的习惯是:先试5x5核,sigmaX=1.5。如果噪声还明显,再加大核大小。但要注意,核太大容易把缺陷边缘也模糊掉。在一个轴承表面划痕项目里,我用7x7的核,结果把细划痕直接抹平了……后来换回5x5才搞定。
4.4 中值滤波:专治“椒盐噪声”
中值滤波的思路和高斯滤波完全不同。它不是算平均,而是取邻域内所有像素值的中位数来替代中心像素。
这招对“椒盐噪声”特别有效。什么是椒盐噪声?就是图像上随机出现的黑白点——像撒了盐和胡椒一样。这种噪声在工业相机传输过程中偶尔会出现,尤其是线缆接触不良的时候。
median = cv2.medianBlur(gray, ksize=5)
参数ksize也是奇数。3、5、7是常用值。
为什么中值滤波能干掉椒盐噪声?你想想看,一个黑点(像素值0)周围都是正常像素(比如值128),取中位数后,这个黑点就被“拉回”到正常值了。而高斯滤波遇到这种情况,会把黑点的影响“扩散”到周围,反而让一片区域都变暗。
我的经验:中值滤波的核大小不要超过7。我见过有人用9x9甚至11x11,结果图像变得像油画一样,细节全没了。工业场景里,3x3或5x5基本够用。
4.5 如何选择?一张表说清楚
| 噪声类型 | 推荐滤波器 | 典型参数 | 注意事项 |
|---|---|---|---|
| 高斯噪声(传感器热噪声) | 高斯滤波 | 核5x5, sigmaX=1.5 | 核太大会模糊边缘 |
| 椒盐噪声(传输干扰) | 中值滤波 | 核3x3或5x5 | 核不要超过7x7 |
| 均匀噪声(光照波动) | 高斯滤波 + CLAHE | 先滤波后均衡化 | 顺序不能反 |
| 混合噪声 | 先中值后高斯 | 中值3x3 + 高斯5x5 | 先去除离群点再平滑 |
4.6 完整预处理流水线
在实际项目中,我通常会把这几个步骤串起来。下面是一个典型的预处理流水线:
import cv2
import numpy as np
def preprocess_pipeline(img_path):
# 1. 读取图像
img = cv2.imread(img_path)
if img is None:
raise ValueError("图像读取失败,检查路径")
# 2. 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 3. CLAHE自适应均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
enhanced = clahe.apply(gray)
# 4. 判断噪声类型并滤波
# 这里简单起见,先用高斯滤波
filtered = cv2.GaussianBlur(enhanced, (5,5), sigmaX=1.5)
# 如果发现椒盐噪声,可以再加一步中值滤波
# filtered = cv2.medianBlur(filtered, 3)
return filtered
重要提醒:预处理不是越复杂越好。我见过有人把预处理搞了七八步,结果图像质量反而下降了。记住一个原则:能用一步解决的,绝不用两步。每多一步,都会引入额外的信息损失。
嗯,到这里,图像预处理的基础内容就差不多了。灰度化帮你扔掉冗余的颜色信息,直方图均衡化让对比度拉满,高斯滤波和中值滤波各司其职。把这些基础打牢了,后面做缺陷分割、特征提取的时候,你会省很多力气。