第二章 图像预处理实战:滤波、增强与几何变换
图像预处理,说白了就是给原始图像“洗个澡”。
在工业现场,相机拍回来的图很少是完美的。光照不均、传感器噪声、工件抖动,这些问题不处理,后面的检测算法根本跑不动。我刚开始做视觉项目时,就吃过这个亏——算法在实验室跑得好好的,上了产线全崩。后来才发现,是预处理没做扎实。
核心观点:预处理不是可选项,是必选项。它直接决定了后续检测的稳定性和精度。
2.1 图像滤波:去噪是第一步
滤波的目的很简单:保留有用信息,干掉噪声。工业场景中常见的噪声有高斯噪声(传感器热噪声)和椒盐噪声(电磁干扰)。
2.1.1 高斯滤波
高斯滤波用加权平均的方式平滑图像。离中心越近的像素权重越大,符合自然分布。它对付高斯噪声效果很好。
import cv2
import numpy as np
# 读取图像
img = cv2.imread('defect_sample.jpg', cv2.IMREAD_GRAYSCALE)
# 高斯滤波,核大小5x5,sigma=1.5
gaussian_blur = cv2.GaussianBlur(img, (5, 5), 1.5)
cv2.imshow('Gaussian Blur', gaussian_blur)
cv2.waitKey(0)
我的经验:核大小选奇数,3、5、7常用。sigma值一般设1.0~2.0。太大图像会糊,太小去噪不干净。我在检测手机屏幕划痕时,sigma设1.2效果最好。
2.1.2 中值滤波
中值滤波用邻域像素的中位数代替中心像素。它对椒盐噪声有奇效,而且能保留边缘细节。
# 中值滤波,核大小5
median_blur = cv2.medianBlur(img, 5)
cv2.imshow('Median Blur', median_blur)
cv2.waitKey(0)
注意:中值滤波的核大小也是奇数。核越大,去噪越强,但图像细节损失也越大。我曾经在检测PCB焊点时,核设到7,结果把微小裂纹也给滤掉了,漏检率飙升。后来改回3,问题解决。
2.1.3 双边滤波
双边滤波是“保边去噪”的利器。它同时考虑空间距离和像素值差异,边缘处权重降低,所以边缘能保留下来。
# 双边滤波,d=9,sigmaColor=75,sigmaSpace=75
bilateral_blur = cv2.bilateralFilter(img, 9, 75, 75)
cv2.imshow('Bilateral Filter', bilateral_blur)
cv2.waitKey(0)
适用场景:需要保留边缘的缺陷检测,比如划痕、裂纹、毛刺。双边滤波计算量大,实时性要求高的产线慎用。
2.2 图像增强:让缺陷“现形”
增强的目的,是把人眼或算法难以分辨的特征凸显出来。说白了,就是让该亮的地方更亮,该暗的地方更暗。
2.2.1 直方图均衡化
直方图均衡化把图像的灰度分布拉伸到整个范围。对比度低的图像,处理后细节会清晰很多。
# 全局直方图均衡化
equalized = cv2.equalizeHist(img)
cv2.imshow('Equalized', equalized)
cv2.waitKey(0)
避坑指南:全局均衡化有时会过度增强噪声。我建议用CLAHE(自适应直方图均衡化),它分块处理,效果更自然。
# CLAHE
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
clahe_img = clahe.apply(img)
cv2.imshow('CLAHE', clahe_img)
cv2.waitKey(0)
2.2.2 伽马校正
伽马校正通过非线性变换调整图像亮度。gamma值小于1,暗部变亮;大于1,亮部变暗。
# 伽马校正,gamma=0.5
gamma = 0.5
gamma_corrected = np.power(img / 255.0, gamma) * 255.0
gamma_corrected = gamma_corrected.astype(np.uint8)
cv2.imshow('Gamma Corrected', gamma_corrected)
cv2.waitKey(0)
实战经验:检测金属表面反光缺陷时,我常用gamma=0.6~0.8压低高光,让划痕更明显。检测暗色塑料件时,gamma=1.2~1.5提亮暗部。
2.3 图像几何变换:对齐与校正
产线上工件位置不可能完全一致。几何变换的作用,就是把图像统一到标准位置,方便后续处理。
2.3.1 仿射变换
仿射变换保持直线和平行关系。平移、旋转、缩放、剪切都属于仿射变换。它需要3个对应点来计算变换矩阵。
# 仿射变换:旋转+平移
rows, cols = img.shape
M = cv2.getRotationMatrix2D((cols/2, rows/2), 45, 1.0) # 旋转45度
affine_img = cv2.warpAffine(img, M, (cols, rows))
cv2.imshow('Affine Transform', affine_img)
cv2.waitKey(0)
我的习惯:做仿射变换前,先用模板匹配或特征点定位找到工件的基准点。这样变换后的图像才能和模板对齐。
2.3.2 透视变换
透视变换可以改变视角,把倾斜的物体“拉正”。它需要4个对应点,变换后直线仍然是直线,但平行关系不一定保持。
# 透视变换:矫正倾斜的工件
pts1 = np.float32([[50,50],[200,50],[50,200],[200,200]])
pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])
M = cv2.getPerspectiveTransform(pts1, pts2)
perspective_img = cv2.warpPerspective(img, M, (300, 300))
cv2.imshow('Perspective Transform', perspective_img)
cv2.waitKey(0)
注意:透视变换计算量大,实时性要求高的场景要优化。我曾在一条每小时检测6000个零件的产线上,把透视变换换成仿射变换,速度提升了30%,精度没降。
2.4 本章知识体系
下面这张图总结了本章的核心内容,方便你快速回顾。
2.5 三种滤波对比
| 滤波类型 | 适用噪声 | 边缘保留 | 计算速度 | 典型场景 |
|---|---|---|---|---|
| 高斯滤波 | 高斯噪声 | 一般 | 快 | 传感器热噪声、光照均匀场景 |
| 中值滤波 | 椒盐噪声 | 好 | 中等 | 电磁干扰、焊点检测 |
| 双边滤波 | 高斯噪声 | 优秀 | 慢 | 划痕、裂纹、毛刺检测 |
总结:预处理没有银弹。不同场景要选不同方法。我的建议是——先试中值滤波,速度快、效果好。如果边缘保留不够,再换双边滤波。高斯滤波适合噪声均匀的场景。
嗯,这一章的内容就到这里。预处理是视觉检测的基石,花时间把它调好,后面的工作会轻松很多。