3. OpenCV图像预处理实战
图像预处理,说白了就是给算法“洗菜”。你想想看,产线上拍出来的图,光照忽明忽暗、产品表面有灰尘、镜头还有噪点——这种脏数据直接扔给 YOLO,它根本学不明白。我做了这么多年视觉项目,可以负责任地告诉你:预处理做得好,模型训练事半功倍;预处理糊弄事,调参调到怀疑人生。
这一章,咱们就把 OpenCV 里最常用的几个预处理操作挨个过一遍。我会结合工业场景,告诉你每个操作到底解决什么问题,以及我踩过的坑。
3.1 图像缩放:统一输入尺寸
YOLO 要求输入尺寸固定,比如 416×416 或 640×640。但产线上相机拍出来的图,分辨率五花八门。所以第一步,必须把图缩放到统一尺寸。
OpenCV 里用 cv2.resize(),我个人习惯用 INTER_LINEAR 做放大,INTER_AREA 做缩小。为什么?INTER_AREA 在缩小时能有效避免摩尔纹,这在检测细小的划痕或针脚时特别重要。
import cv2
img = cv2.imread('product.jpg')
# 缩放到 YOLO 标准输入
resized = cv2.resize(img, (416, 416), interpolation=cv2.INTER_AREA)
cv2.copyMakeBorder() 补边,保持原始比例。否则检测框会偏。
3.2 灰度化:降维打击
彩色图有三个通道,灰度图只有一个。对于很多工业检测任务,颜色信息其实没用——比如检测划痕、裂纹、尺寸。去掉颜色,计算量直接降到三分之一。
用 cv2.cvtColor() 就行。但注意:别在灰度化之前做滤波。先灰度化再滤波,效率更高。
gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
cv2.imread() 读进来就是 BGR。转灰度没问题,但如果你要显示或保存,记得转回 BGR。
3.3 高斯滤波:干掉噪点
工业相机在弱光环境下,噪点特别明显。高斯滤波就是用一个“模糊核”滑过整张图,把每个像素替换成周围像素的加权平均。权重符合高斯分布——离中心越近,权重越大。
核大小选多少?我一般用 5×5。太小了去不掉噪点,太大了边缘也糊了。对于检测精细特征(比如 PCB 焊点),3×3 就够了。
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
第三个参数 sigmaX 设 0,OpenCV 会自动根据核大小计算标准差。嗯,省心。
3.4 直方图均衡化:让暗部亮起来
产线上光照不均匀是常态。有时候产品左边亮右边暗,或者整体偏暗。直方图均衡化能拉伸像素值的分布范围,让暗的地方变亮,亮的地方更均匀。
OpenCV 提供 cv2.equalizeHist(),但只接受单通道。所以先灰度化,再均衡化。
equalized = cv2.equalizeHist(blurred)
效果立竿见影。我曾经做过一个手机中框划痕检测项目,车间灯光从侧面打过来,中框一半亮一半暗。均衡化之后,划痕在整个中框上都清晰可见,检测率从 82% 直接跳到 96%。
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
clahe_img = clahe.apply(blurred)
3.5 形态学操作:修修补补
形态学操作处理二值图(黑白图)最拿手。但灰度图也能用。核心操作就四个:腐蚀、膨胀、开运算、闭运算。
| 操作 | 效果 | 工业场景 |
|---|---|---|
| 腐蚀 | 白色区域缩小,消除小白点 | 去除噪点、断开粘连 |
| 膨胀 | 白色区域扩大,填充小孔 | 连接断裂区域、加粗特征 |
| 开运算 | 先腐蚀后膨胀 | 去噪点,不改变主体大小 |
| 闭运算 | 先膨胀后腐蚀 | 填充小孔,保持主体大小 |
举个例子。检测螺丝有没有拧紧,需要提取螺纹边缘。但图像上可能有灰尘颗粒(小白点),螺纹中间也可能有断点。这时候:
- 先做开运算,去掉灰尘颗粒
- 再做闭运算,把断开的螺纹连起来
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
# 开运算:先腐蚀后膨胀
opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
# 闭运算:先膨胀后腐蚀
closing = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
3.6 完整预处理流水线
实际项目中,这些操作是串在一起的。我一般按这个顺序:
- 读取图像 →
cv2.imread() - 缩放 → 统一到 YOLO 输入尺寸
- 灰度化 → 降维
- 高斯滤波 → 去噪
- 直方图均衡化 → 增强对比
- 形态学操作 → 修整特征
- 二值化(如果需要) →
cv2.threshold()
def preprocess_pipeline(img_path):
# 1. 读取
img = cv2.imread(img_path)
# 2. 缩放
img = cv2.resize(img, (416, 416), interpolation=cv2.INTER_AREA)
# 3. 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 4. 高斯滤波
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# 5. 直方图均衡化
equalized = cv2.equalizeHist(blurred)
# 6. 二值化(OTSU自动阈值)
_, binary = cv2.threshold(equalized, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 7. 形态学开运算去噪
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
return cleaned
cv2.imshow() 看一眼中间结果。别等到最后才发现问题。我习惯在代码里加一个 show_step() 函数,调试完再注释掉。