4、经典去噪算法(上):均值滤波、高斯滤波、中值滤波的原理与OpenCV实现
图像去噪,说白了就是给图像“磨皮”。但怎么磨,磨完会不会把细节也磨没了,这里头门道不少。今天咱们聊三个最经典的滤波算法——均值滤波、高斯滤波、中值滤波。它们算是去噪领域的“老三样”,我入行那会儿第一个接触的就是它们。
你可能会问,都2025年了,深度学习满天飞,还学这些传统算法干嘛?嗯,我跟你讲,在实际落地项目里,尤其是资源受限的嵌入式设备上,这些算法依然是“救火队员”。速度快、原理简单、效果稳定,很多时候比跑个模型划算得多。
4.1 均值滤波:最朴素的“平均主义”
均值滤波的思路特别直白——把窗口内所有像素值加起来,除以像素个数,得到平均值,然后把这个平均值赋给窗口中心的像素。
举个例子,一个3×3的窗口,中心像素原本是100,周围一圈像素分别是110、120、90、80……算完平均后,中心像素可能变成了95。这样一来,那些突兀的噪声点(比如一个孤立的255)就会被周围的像素“拉”回来。
我在项目中遇到过一个问题:用均值滤波去噪,图像确实平滑了,但边缘也糊了。为什么会这样?因为均值滤波把边缘两侧的像素也一起平均了,边缘就变成了渐变带。
OpenCV实现
import cv2
import numpy as np
# 读取图像
img = cv2.imread('noisy_image.jpg')
# 均值滤波:ksize为卷积核大小,必须是奇数
blurred = cv2.blur(img, (3, 3)) # 3x3核
blurred_5 = cv2.blur(img, (5, 5)) # 5x5核
cv2.imshow('Original', img)
cv2.imshow('Blur 3x3', blurred)
cv2.imshow('Blur 5x5', blurred_5)
cv2.waitKey(0)
4.2 高斯滤波:带权重的“平均主义”
均值滤波有个问题——它把窗口内所有像素一视同仁。但你想,离中心越近的像素,理论上和中心像素越像,应该权重更大才对。高斯滤波就是干这个的。
它用一个高斯核(符合高斯分布的权重矩阵)去和图像做卷积。中心像素权重最大,越往边缘权重越小。这样平滑效果更自然,边缘保留也比均值滤波好一些。
我记得有一次做工业检测项目,产品表面有轻微纹理噪声。用均值滤波会把纹理和噪声一起抹掉,但换成高斯滤波,纹理保留得明显更好。这就是带权重的优势。
OpenCV实现
# 高斯滤波:ksize为核大小,sigmaX为X方向标准差
gaussian = cv2.GaussianBlur(img, (3, 3), 0) # sigma=0表示自动计算
gaussian_5 = cv2.GaussianBlur(img, (5, 5), 1.5) # 手动指定sigma
cv2.imshow('Gaussian 3x3', gaussian)
cv2.imshow('Gaussian 5x5 sigma1.5', gaussian_5)
cv2.waitKey(0)
| 参数 | 说明 | 我的建议 |
|---|---|---|
| ksize | 卷积核大小,必须为奇数 | 3×3或5×5最常用 |
| sigmaX | X方向标准差,控制权重分布 | 设为0让OpenCV自动算,省心 |
| sigmaY | Y方向标准差,默认与sigmaX相同 | 一般不用单独设 |
4.3 中值滤波:专治“椒盐噪声”的利器
前面两种滤波都是线性滤波,说白了就是做加权平均。但有些噪声,比如椒盐噪声(黑白点),用平均的方法效果很差——一个白点会把周围像素都“染”白。
中值滤波的思路完全不同:它把窗口内所有像素排序,取中间那个值作为输出。这样一来,一个孤立的噪声点(极大或极小值)根本不会影响中值,因为它排不到中间去。
我做过一个文档扫描项目,扫描件上全是椒盐噪声。用高斯滤波去噪,文档文字边缘还是模糊。换成中值滤波,噪声去得干干净净,文字边缘一点没糊。从那以后,遇到椒盐噪声我第一个想到的就是中值滤波。
OpenCV实现
# 中值滤波:ksize必须为奇数,且大于1
median_3 = cv2.medianBlur(img, 3) # 3x3邻域
median_5 = cv2.medianBlur(img, 5) # 5x5邻域
cv2.imshow('Median 3x3', median_3)
cv2.imshow('Median 5x5', median_5)
cv2.waitKey(0)
4.4 三种滤波对比:一张图看懂
下面这张SVG图,我画了三种滤波的核心流程对比。你看一眼就能明白它们的区别。
4.5 实际项目中的选择策略
你可能会问,那到底该用哪个?我一般按这个顺序判断:
- 先看噪声类型——如果是黑白点(椒盐噪声),直接上中值滤波,别犹豫。
- 再看实时性要求——如果帧率要求高(比如30fps以上),优先均值滤波,计算量最小。
- 最后看边缘保留需求——如果边缘很重要(比如医学图像、文档扫描),用高斯滤波或中值滤波。
4.6 一个完整的去噪流程示例
下面是我在实际项目中用过的一个去噪pipeline,你可以参考:
def denoise_pipeline(img):
# 第一步:中值滤波去椒盐噪声
img_median = cv2.medianBlur(img, 3)
# 第二步:高斯滤波平滑纹理噪声
img_gaussian = cv2.GaussianBlur(img_median, (3, 3), 0)
# 第三步:可选——双边滤波保留边缘(后续章节会讲)
# img_bilateral = cv2.bilateralFilter(img_gaussian, 9, 75, 75)
return img_gaussian
# 测试
noisy = cv2.imread('noisy_image.jpg')
clean = denoise_pipeline(noisy)
cv2.imshow('Denoised', clean)
cv2.waitKey(0)
好了,三种经典滤波算法就聊到这儿。均值滤波简单粗暴,高斯滤波更“聪明”,中值滤波专治椒盐噪声。你可以在自己的项目里试试,看看哪种效果最好。记住,没有最好的算法,只有最合适的算法。