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)
我的习惯: 核大小一般从3×3开始试,效果不够再往上加。核越大,平滑越强,但细节丢失也越严重。5×5以上我基本不用,除非图像噪声特别大。

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相同 一般不用单独设
避坑指南: 我曾经在实时视频流处理中把sigma设得太大(比如5.0),结果图像变得跟毛玻璃一样,帧率还掉了一半。sigma越大,平滑越强,但计算量也越大。实时场景下建议sigma不超过2.0。

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)
性能提示: 中值滤波的排序操作比均值滤波的加法慢不少。在ARM嵌入式平台上,3×3中值滤波还能跑实时,5×5就开始吃力了。如果必须用大核,可以考虑用“快速中值滤波”算法优化。

4.4 三种滤波对比:一张图看懂

下面这张SVG图,我画了三种滤波的核心流程对比。你看一眼就能明白它们的区别。

三种经典滤波算法核心流程对比 均值滤波 窗口内所有像素 求和 → 除以像素个数 → 平均值赋给中心 缺点:边缘模糊严重 高斯滤波 窗口内像素加权平均 权重由高斯分布决定 中心权重最大 缺点:对椒盐噪声效果差 中值滤波 窗口内像素排序 取中间值赋给中心 非线性的统计滤波 缺点:计算速度较慢 适用场景速查 均值滤波:高斯噪声、均匀噪声 | 高斯滤波:高斯噪声、轻微纹理噪声 | 中值滤波:椒盐噪声、脉冲噪声

4.5 实际项目中的选择策略

你可能会问,那到底该用哪个?我一般按这个顺序判断:

  1. 先看噪声类型——如果是黑白点(椒盐噪声),直接上中值滤波,别犹豫。
  2. 再看实时性要求——如果帧率要求高(比如30fps以上),优先均值滤波,计算量最小。
  3. 最后看边缘保留需求——如果边缘很重要(比如医学图像、文档扫描),用高斯滤波或中值滤波。
我的经验: 很多时候,我会把三种滤波组合使用。比如先用中值滤波去椒盐噪声,再用高斯滤波做一次平滑。但要注意,滤波次数越多,细节丢失越严重。一般组合不超过两次。

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)
注意: 这个pipeline不是万能的。如果图像噪声特别复杂,或者你处理的是超分辨率任务中的低分辨率图像,传统滤波可能会把高频细节(纹理、边缘)一起抹掉。这时候就需要用到更高级的去噪算法,比如非局部均值去噪(NLM)或者基于深度学习的去噪网络。这些内容我们后面章节会展开。

好了,三种经典滤波算法就聊到这儿。均值滤波简单粗暴,高斯滤波更“聪明”,中值滤波专治椒盐噪声。你可以在自己的项目里试试,看看哪种效果最好。记住,没有最好的算法,只有最合适的算法。