降噪算法(一):均值滤波与高斯滤波

图像降噪,说白了就是去掉画面里那些不该出现的「噪点」。你想想看,传感器感光不均匀、光线不足、传输干扰……这些都会让图像变得脏兮兮的。我刚开始做图像处理那会儿,总觉得降噪就是「模糊一下」,后来才发现——降噪的本质,是在平滑和细节之间找平衡。

这一章我们先聊两个最基础的线性滤波算法:均值滤波和高斯滤波。它们虽然简单,但却是理解更复杂降噪算法的基石。

核心观点:均值滤波是「平均主义」,高斯滤波是「远近亲疏」。两者都是线性滤波,但高斯滤波在保留边缘方面明显优于均值滤波。

均值滤波:最简单的降噪方式

均值滤波的思路非常直白——用一个窗口扫过图像,把窗口内所有像素的灰度值取平均,作为中心像素的新值。

举个例子,一个 3×3 的窗口:

原始像素值:
[10, 12, 11]
[ 9, 50, 10]   ← 中心点 50 是噪点
[11, 13, 12]

均值滤波后中心值 = (10+12+11+9+50+10+11+13+12) / 9 = 138 / 9 ≈ 15.33

你看,原本 50 的噪点被拉低到了 15 左右,和周围像素更接近了。这就是均值滤波的降噪原理。

但问题也来了——它把所有像素一视同仁。窗口内的每个像素权重都是 1/9,不管它离中心多远,也不管它是不是边缘。我在项目中遇到过这种情况:用均值滤波去噪,结果人物的轮廓变得模糊,像没对上焦一样。

均值滤波的致命伤:它会把边缘也「平均」掉。边缘两侧的像素值差异很大,一旦被平均,边缘就消失了。所以均值滤波只适合噪声较小、对细节要求不高的场景。

高斯滤波:给像素「论资排辈」

高斯滤波和均值滤波的区别,就在于权重分配。它不再搞平均主义,而是根据像素到中心点的距离,分配不同的权重——离中心越近,权重越大;离中心越远,权重越小。

这个权重分布,就是高斯函数:

G(x, y) = (1 / (2πσ²)) * exp(-(x² + y²) / (2σ²))

其中 σ(sigma)控制着权重的衰减速度。σ 越大,权重分布越平坦,滤波效果越接近均值滤波;σ 越小,中心像素的权重越大,保留细节的能力越强。

我习惯把 σ 理解为「视野范围」——σ 小,只看眼前;σ 大,兼顾全局。

3×3 高斯核的计算示例

假设 σ = 1.0,我们计算一个 3×3 高斯核:

位置 (x, y) 距离平方 x²+y² 未归一化权重 归一化权重
(-1, -1) 2 0.1353 0.075
(-1, 0) 1 0.3679 0.204
(-1, 1) 2 0.1353 0.075
(0, -1) 1 0.3679 0.204
(0, 0) 0 1.0000 0.554
(0, 1) 1 0.3679 0.204
(1, -1) 2 0.1353 0.075
(1, 0) 1 0.3679 0.204
(1, 1) 2 0.1353 0.075

归一化后的高斯核:

[[0.075, 0.204, 0.075],
 [0.204, 0.554, 0.204],
 [0.075, 0.204, 0.075]]

你看,中心像素的权重 0.554 远高于角落的 0.075。这意味着高斯滤波在平滑噪声的同时,能更好地保留中心像素的原始值——也就是保留了更多细节。

我的经验:实际项目中,我一般用 5×5 或 7×7 的高斯核,σ 取 1.0~2.0。核太小降噪效果有限,核太大又容易模糊。你可以先试 σ=1.5,然后根据效果微调。

两种滤波的对比

对比维度 均值滤波 高斯滤波
权重分配 均匀分布 中心权重高,边缘权重低
边缘保留 差 较好
降噪效果 一般 较好
计算复杂度 低 略高(需计算权重)
适用场景 噪声均匀、细节要求低 通用降噪、细节保留要求高

代码实现:从零手写高斯滤波

理论说完了,我们来写代码。我个人习惯用 Python + NumPy 实现,这样能清楚看到每一步在做什么。

import numpy as np
import cv2

def gaussian_kernel(size, sigma):
    """生成高斯核"""
    kernel = np.zeros((size, size))
    center = size // 2
    
    for i in range(size):
        for j in range(size):
            x = i - center
            y = j - center
            kernel[i, j] = np.exp(-(x**2 + y**2) / (2 * sigma**2))
    
    # 归一化
    kernel = kernel / np.sum(kernel)
    return kernel

def gaussian_filter(image, kernel_size=5, sigma=1.5):
    """高斯滤波"""
    kernel = gaussian_kernel(kernel_size, sigma)
    pad = kernel_size // 2
    
    # 边界填充(镜像模式)
    padded = np.pad(image, pad, mode='reflect')
    
    # 卷积操作
    output = np.zeros_like(image)
    for i in range(image.shape[0]):
        for j in range(image.shape[1]):
            region = padded[i:i+kernel_size, j:j+kernel_size]
            output[i, j] = np.sum(region * kernel)
    
    return output

# 使用示例
img = cv2.imread('noisy_image.jpg', 0)  # 灰度图
result = gaussian_filter(img, kernel_size=5, sigma=1.5)

边界处理要注意:卷积操作时,图像边缘的像素没有完整的邻域。我建议用「镜像填充」(reflect mode),而不是补零。补零会在边缘产生突兀的暗边,影响视觉效果。

核心知识体系

下面这张图帮你理清本章的知识脉络:

降噪算法(一):均值滤波与高斯滤波 线性滤波 均值滤波 高斯滤波 权重均匀分布 边缘模糊严重 计算简单快速 中心权重高 边缘保留较好 σ控制平滑度 选择建议:优先使用高斯滤波,σ=1.0~2.0,核大小5×5

避坑指南

  • 核大小必须是奇数:3×3、5×5、7×7……这样才能保证中心像素存在。偶数核会导致输出图像偏移半个像素。
  • σ 和核大小要匹配:核太小而 σ 太大,高斯核的权重在边缘处几乎为 0,相当于浪费了计算资源。一般建议核大小 ≥ 6σ+1。
  • 彩色图像要逐通道处理:对 RGB 三个通道分别做高斯滤波,不要直接对三通道取平均,否则颜色会乱掉。

我曾经踩过的坑:有一次做工业检测,用均值滤波去噪,结果把产品表面的细微划痕也给抹平了,导致漏检。后来换成高斯滤波,σ 调到 1.2,既去掉了传感器噪声,又保留了划痕特征。所以——选对算法比调参更重要。