图像预处理基础:灰度化、直方图均衡化、滤波与形态学
图像预处理,说白了就是给原始图像“洗个澡”。
在工业现场,相机拍回来的图往往带着各种“脏东西”——光照不均、传感器噪声、背景干扰。你想想看,如果直接把这种图扔给检测模型,结果能好吗?
我做了这么多年视觉算法,可以负责任地告诉你:预处理做得好,后续算法事半功倍;预处理糊弄事,后面调参调到怀疑人生。
核心观点:预处理不是“可有可无的步骤”,而是决定检测系统鲁棒性的基石。一个稳定的预处理管线,能帮你挡住现场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 |
重要提醒:预处理参数不是一成不变的。换一条产线、换一个光源,参数就得重新调。我见过太多工程师把开发环境里的参数直接搬到现场,结果效果一塌糊涂。记住:没有通用的“最佳参数”,只有针对性的“最优组合”。
嗯,这一章的内容就到这里。灰度化、直方图均衡化、滤波、形态学——这四个基础操作,是后续所有高级算法的地基。你可以在自己的项目里多试试不同的组合,慢慢就会找到感觉。