4. 图像预处理:高斯滤波、中值滤波、图像二值化、形态学操作

各位同学,欢迎来到实战环节的核心基础课。图像预处理,说白了就是给原始图像“洗个澡、化个妆”,让它变成我们算法能轻松读懂的样子。我做了这么多年视觉项目,可以负责任地告诉你:预处理做得好,后面识别定位就成功了一半。

咱们的套色标记,在真实拍摄环境下,免不了有噪声、光照不均、背景干扰。直接拿原始图去识别?那结果肯定惨不忍睹。所以,这一章我们就把预处理四板斧练熟:高斯滤波、中值滤波、图像二值化、形态学操作

核心逻辑:去噪 → 增强 → 分割 → 修形。每一步都有它的使命,缺一不可。

图像预处理四步流程 ① 高斯滤波 去除高斯噪声 ② 中值滤波 去除椒盐噪声 ③ 二值化 前景/背景分离 ④ 形态学 腐蚀/膨胀 为什么按这个顺序? 先滤波去噪 → 再二值化分割 → 最后形态学修形 顺序乱了,效果会大打折扣。我踩过这个坑。

4.1 高斯滤波:温柔的模糊

高斯滤波,我习惯叫它“温柔的模糊”。它不像均值滤波那么粗暴,而是根据高斯分布给中心像素更高的权重,边缘像素权重低。这样既能平滑噪声,又能尽量保留边缘细节。

为什么会这样?因为真实世界的噪声往往服从高斯分布,用高斯核去卷积,正好是“对症下药”。

我在项目中遇到过这样的情况:套色标记表面有细微的纹理噪声,用均值滤波把标记边缘也搞模糊了,后来换成高斯滤波,边缘清晰度明显提升。

import cv2
import numpy as np

# 读取图像
img = cv2.imread('marker.jpg')

# 高斯滤波,核大小5x5,sigmaX=1.0
gaussian = cv2.GaussianBlur(img, (5, 5), 1.0)

# 显示结果
cv2.imshow('Gaussian', gaussian)
cv2.waitKey(0)

小技巧:核大小必须是奇数,比如3、5、7。sigmaX一般设1.0~1.5就够用。太大图像会过度模糊,太小没效果。

4.2 中值滤波:椒盐噪声的克星

中值滤波,说白了就是把窗口内的像素排序,取中间值代替中心像素。这招对付椒盐噪声(黑白点噪声)特别管用。

你想想看,椒盐噪声是极端值(0或255),排序后肯定在两端,中间值大概率是正常像素。所以中值滤波能完美去除椒盐噪声,同时不模糊边缘。

我记得有一次做工业检测,相机传感器有坏点,图像上全是白点。用高斯滤波怎么调都没用,换成中值滤波,世界清净了。

# 中值滤波,核大小5
median = cv2.medianBlur(img, 5)

# 对比一下效果
cv2.imshow('Median', median)

注意:中值滤波的核大小也必须是奇数。核越大,去噪越强,但图像细节损失也越大。一般3~7之间选。

4.3 图像二值化:黑白分明

二值化,就是把灰度图变成只有黑(0)和白(255)的图像。这一步是为了把套色标记从背景中分离出来。

最常用的是大津法(OTSU),它能自动计算最佳阈值。我个人的习惯是:先用OTSU试试,效果不好再手动调阈值。

# 先转灰度
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 高斯滤波后再二值化,效果更好
blurred = cv2.GaussianBlur(gray, (5, 5), 1.0)

# OTSU二值化
_, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

# 显示二值图
cv2.imshow('Binary', binary)

核心要点:二值化前一定要先滤波!否则噪声会被当成前景,导致二值图全是小亮点。我曾经犯过这个错,排查了半天才发现是顺序问题。

4.4 形态学操作:腐蚀与膨胀

二值化之后,图像上往往还有小孔洞、毛刺、孤立点。这时候就需要形态学操作来“修形”了。

  • 腐蚀:让白色区域缩小,消除细小的白色噪点。
  • 膨胀:让白色区域扩大,填充小孔洞,连接断裂区域。
  • 开运算:先腐蚀后膨胀,用于消除小物体、平滑边界。
  • 闭运算:先膨胀后腐蚀,用于填充小孔洞、连接邻近物体。

对于套色标记,我一般先用开运算去掉背景上的小噪点,再用闭运算填充标记内部的孔洞。

# 定义结构元素,3x3矩形
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))

# 腐蚀
eroded = cv2.erode(binary, kernel, iterations=1)

# 膨胀
dilated = cv2.dilate(binary, kernel, iterations=1)

# 开运算
opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

# 闭运算
closing = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)

避坑指南:迭代次数不要太多,1~2次就够了。迭代多了,标记形状会严重变形,影响后续定位精度。

4.5 完整预处理流程

好了,我们把四步串起来,形成一个完整的预处理管线。这也是我在实际项目中反复验证过的流程。

def preprocess(img):
    # 1. 高斯滤波去噪
    blurred = cv2.GaussianBlur(img, (5, 5), 1.0)
    
    # 2. 转灰度
    gray = cv2.cvtColor(blurred, cv2.COLOR_BGR2GRAY)
    
    # 3. OTSU二值化
    _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    
    # 4. 形态学开运算 + 闭运算
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
    cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=1)
    cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel, iterations=1)
    
    return cleaned

# 调用
result = preprocess(img)
cv2.imshow('Final', result)
步骤 作用 参数建议
高斯滤波 去除高斯噪声 核5x5, sigma=1.0
中值滤波 去除椒盐噪声 核3~5
二值化 前景背景分离 OTSU自动阈值
形态学操作 修形、去噪点、填孔洞 3x3矩形核, 迭代1~2次

重要提醒:预处理参数不是固定的!光照变了、相机换了、标记材质不同,参数都要微调。我建议你准备一套测试图像,反复调参找到最优组合。

嗯,到这里,图像预处理的四板斧就讲完了。你可能会问:为什么先高斯再中值?其实顺序可以灵活,但我的经验是:高斯滤波处理全局噪声,中值滤波处理孤立噪点,先全局后局部,效果更稳定。

下一章,我们就要用预处理后的图像去做标记定位了。到时候你会发现,前面预处理做得越干净,后面定位就越轻松。


专注资料整理