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)
避坑: OpenCV 默认是 BGR 顺序,不是 RGB。用 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(自适应直方图均衡化)效果更好。它把图像分成小块分别均衡化,避免整体过曝。
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)
我曾经踩过的坑: 核大小选 5×5 以上,结果把细小的裂纹也给腐蚀掉了。后来改成 3×3 的矩形核,效果刚好。记住:核越大,丢失的细节越多。对于精细检测,从 3×3 开始试。

3.6 完整预处理流水线

实际项目中,这些操作是串在一起的。我一般按这个顺序:

  1. 读取图像 → cv2.imread()
  2. 缩放 → 统一到 YOLO 输入尺寸
  3. 灰度化 → 降维
  4. 高斯滤波 → 去噪
  5. 直方图均衡化 → 增强对比
  6. 形态学操作 → 修整特征
  7. 二值化(如果需要) → 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() 函数,调试完再注释掉。