4. 图像预处理实战:高斯滤波、中值滤波、直方图均衡化、ROI提取、图像缩放与仿射变换
各位同学,欢迎来到第四讲。上一章我们聊了相机标定和图像采集,那都是「把图拿到手」的功夫。这一章,咱们要干点更实在的——把图收拾干净,把感兴趣的区域拎出来。
说实话,我在分拣机项目上踩过最大的坑,不是算法选型不对,而是预处理没做好。你想想看,一张满是噪点的图,你拿什么深度学习模型去识别都白搭。所以这一章,我把自己这些年积累的预处理实战经验,全盘托出。
4.1 高斯滤波:去噪但不模糊边缘
高斯滤波,说白了就是用周围像素的加权平均来替换中心像素。权重由高斯函数决定——离中心越近,权重越大。
为什么用高斯?因为自然噪声(比如传感器热噪声)通常服从高斯分布。用高斯滤波去噪,数学上最匹配。
我在做食品分拣机时,遇到过一种情况:传送带震动导致图像出现高频抖动噪声。用均值滤波,边缘糊成一团;用中值滤波,处理速度跟不上。最后是高斯滤波+适当核大小搞定的。
import cv2
import numpy as np
# 读取图像
img = cv2.imread('part_on_belt.jpg', cv2.IMREAD_GRAYSCALE)
# 高斯滤波
gauss_blur = cv2.GaussianBlur(img, (5, 5), 1.5)
# 显示对比
cv2.imshow('Original', img)
cv2.imshow('Gaussian Blur', gauss_blur)
cv2.waitKey(0)
4.2 中值滤波:对付椒盐噪声的利器
中值滤波,就是用邻域内所有像素的中位数代替中心像素。它对椒盐噪声(黑白点噪声)有奇效。
为什么?因为椒盐噪声的像素值要么极大(255),要么极小(0),取中位数时会被正常像素「挤掉」。你想想看,一个 3x3 区域里,8个正常像素+1个白点,中位数肯定是正常值。
我曾经在电子元件分拣项目中,遇到一批 PCB 板图像全是白点——那是反光造成的。用高斯滤波去不掉,用中值滤波,核大小选 3x3,一遍就干净了。
# 中值滤波
median_blur = cv2.medianBlur(img, 5) # 核大小5x5
cv2.imshow('Median Blur', median_blur)
4.3 直方图均衡化:让对比度「活」过来
直方图均衡化,就是把图像的灰度分布从「挤在一起」拉伸到「均匀分布」。说白了,让暗的地方更暗,亮的地方更亮,中间细节全出来。
我在做物流包裹分拣时,仓库光线不均匀,有的地方暗得看不清条码。直方图均衡化一上,条码立刻清晰可辨。
但要注意:全局均衡化有时会过度增强噪声。所以我更推荐 CLAHE(对比度受限自适应直方图均衡化)。
# 全局直方图均衡化
equ = cv2.equalizeHist(img)
# CLAHE(推荐)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
clahe_img = clahe.apply(img)
cv2.imshow('Global Equalization', equ)
cv2.imshow('CLAHE', clahe_img)
4.4 ROI提取:只关心该关心的
ROI(Region of Interest)提取,就是从大图中切出你真正要处理的那一小块。在分拣机上,物料位置是固定的,我们只需要处理物料所在区域。
这样做的好处:
- 减少计算量:只处理 ROI,速度翻倍
- 排除干扰:背景中的传送带、螺丝孔不会影响识别
- 简化算法:在固定 ROI 内做阈值分割,参数不用频繁调整
# 假设物料在图像中心区域
x, y, w, h = 200, 150, 300, 250
roi = img[y:y+h, x:x+w]
cv2.imshow('ROI', roi)
4.5 图像缩放:保持比例还是牺牲精度?
图像缩放,最常用的就是 cv2.resize。但这里有个坑:插值方法的选择。
| 场景 | 推荐插值方法 | 原因 |
|---|---|---|
| 缩小图像 | INTER_AREA | 避免摩尔纹,保留整体信息 |
| 放大图像 | INTER_CUBIC 或 INTER_LINEAR | 平滑,减少锯齿 |
| 实时处理 | INTER_LINEAR | 速度快,效果可接受 |
# 缩小到一半
small = cv2.resize(img, (0,0), fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
# 放大到2倍
large = cv2.resize(img, (0,0), fx=2.0, fy=2.0, interpolation=cv2.INTER_CUBIC)
4.6 仿射变换:旋转、平移、缩放一把抓
仿射变换,就是用矩阵乘法对图像做线性变换。旋转、平移、缩放、剪切,全都能做。
在分拣机上,物料可能歪着放。我们需要先做仿射变换把它「摆正」,再识别。否则 OCR 识别歪着的字符,准确率直接腰斩。
# 获取图像尺寸
h, w = img.shape[:2]
# 定义变换矩阵:旋转30度,中心点缩放
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, 30, 1.0) # 旋转30度,不缩放
# 执行仿射变换
rotated = cv2.warpAffine(img, M, (w, h))
cv2.imshow('Rotated 30 deg', rotated)
更复杂的场景——比如物料在传送带上位置不固定——我们需要先定位,再变换。定位可以用模板匹配或特征点匹配,找到物料上的几个关键点,然后计算变换矩阵。
# 带边界填充的仿射变换
rotated_filled = cv2.warpAffine(img, M, (w, h),
borderMode=cv2.BORDER_REPLICATE)
4.7 综合实战:分拣机物料预处理流水线
好了,知识点都讲完了。咱们来串一个完整的预处理流水线。这是我在实际项目中用过的流程:
- 读图:从相机获取灰度图
- ROI提取:根据传送带位置,切出物料区域
- 高斯滤波:去除传感器噪声(核大小 5x5)
- CLAHE:增强对比度,让物料细节出来
- 仿射变换:根据物料角度,旋转摆正
- 缩放:统一缩放到 224x224(给神经网络用)
def preprocess_pipeline(img, roi_rect, angle):
# 1. ROI提取
x, y, w, h = roi_rect
roi = img[y:y+h, x:x+w]
# 2. 高斯滤波
blurred = cv2.GaussianBlur(roi, (5,5), 1.5)
# 3. CLAHE
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
enhanced = clahe.apply(blurred)
# 4. 仿射变换(旋转摆正)
h_roi, w_roi = enhanced.shape[:2]
center = (w_roi // 2, h_roi // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
aligned = cv2.warpAffine(enhanced, M, (w_roi, h_roi),
borderMode=cv2.BORDER_REPLICATE)
# 5. 缩放到固定尺寸
final = cv2.resize(aligned, (224, 224),
interpolation=cv2.INTER_AREA)
return final
# 使用示例
processed = preprocess_pipeline(img, (100, 80, 400, 300), -15.0)
cv2.imshow('Final Preprocessed', processed)
好了,这一章的内容就到这里。预处理是图像处理的「地基」,地基不牢,后面再好的算法也白搭。下一章,咱们要聊边缘检测和轮廓提取——那是从「像素」到「特征」的关键一步。
有什么问题,欢迎在课程群里讨论。咱们下章见。