2. 传统降噪方法(上):均值滤波与高斯滤波
各位同学,欢迎来到第二章。上一章我们聊了噪声的来龙去脉,今天该动真格的了——咱们亲手把噪声“抹掉”。
传统降噪方法,说白了就是“用周围像素的平均值代替当前像素”。听起来简单吧?但这里面的门道,够你踩好几个坑的。我个人习惯把这类方法叫做“空间域滤波”,因为它们直接在像素坐标上操作,不涉及频域变换。
今天我们先讲两个最基础的:均值滤波和高斯滤波。别看它们老,很多现代深度学习降噪网络的设计思路,追根溯源都能回到这里。
2.1 均值滤波:最朴素的“和稀泥”
均值滤波的原理,一句话就能说清楚:用一个窗口扫过图像,窗口内所有像素求平均,用这个平均值替换中心像素。
你想想看,如果某个像素是噪声点(比如椒盐噪声里的白点),它和周围正常像素一平均,值就被拉回来了。这就是降噪的本质——平滑。
2.1.1 数学定义与核
均值滤波的“核”(Kernel)长这样:
K = (1 / (ksize * ksize)) * [[1, 1, 1],
[1, 1, 1],
[1, 1, 1]]
对于一个 3×3 的核,每个权重都是 1/9。说白了就是“大家投票,票数相等”。
用公式表达就是:
g(x, y) = (1 / N) * Σ f(i, j) (i,j ∈ 窗口)
其中 N 是窗口内像素总数。
2.1.2 代码实现
OpenCV 里一行搞定:
import cv2
import numpy as np
img = cv2.imread('noisy_image.jpg', cv2.IMREAD_GRAYSCALE)
# 均值滤波,核大小 5×5
blurred = cv2.blur(img, (5, 5))
cv2.imshow('Original', img)
cv2.imshow('Blurred', blurred)
cv2.waitKey(0)
如果你想自己手写,也不难:
def my_average_filter(img, ksize=3):
h, w = img.shape
pad = ksize // 2
padded = np.pad(img, pad, mode='reflect')
result = np.zeros_like(img)
for i in range(h):
for j in range(w):
window = padded[i:i+ksize, j:j+ksize]
result[i, j] = np.mean(window)
return result.astype(np.uint8)
2.1.3 均值滤波的优缺点
| 优点 | 缺点 |
|---|---|
| 实现简单,计算速度快 | 所有像素一视同仁,不分远近 |
| 对高斯噪声有一定效果 | 严重破坏边缘和细节 |
| 适合做图像预处理中的粗平滑 | 对椒盐噪声效果很差(会扩散噪声) |
| —— | 核越大,图像越模糊,细节丢失越严重 |
2.2 高斯滤波:给邻居“分个三六九等”
均值滤波的问题很明显——它认为窗口里所有像素对中心像素的贡献是一样的。但直觉告诉我们:离中心越近的像素,应该越有发言权。
高斯滤波就是基于这个想法。它用高斯函数来生成权重,距离中心越近,权重越大;越远,权重越小。
2.2.1 高斯函数与核
二维高斯函数长这样:
G(x, y) = (1 / (2πσ²)) * exp(-(x² + y²) / (2σ²))
其中 σ(sigma)控制着权重的分布——σ 越大,曲线越平坦,远处的像素也能分到不少权重;σ 越小,曲线越尖,只有紧挨着的邻居才有话语权。
举个例子,σ=1.0 时,一个 3×3 的高斯核大概长这样(归一化前):
[[0.075, 0.124, 0.075],
[0.124, 0.204, 0.124],
[0.075, 0.124, 0.075]]
你看,中心像素的权重是 0.204,是角落像素(0.075)的近 3 倍。这就是“远近亲疏有别”。
2.2.2 代码实现
OpenCV 里同样简单:
# 高斯滤波,核大小 5×5,sigma=1.5
gaussian_blur = cv2.GaussianBlur(img, (5, 5), 1.5)
cv2.imshow('Gaussian Blur', gaussian_blur)
cv2.waitKey(0)
手写实现也不复杂,关键是要先生成高斯核:
def gaussian_kernel(ksize, sigma):
kernel = np.zeros((ksize, ksize), dtype=np.float32)
center = ksize // 2
for i in range(ksize):
for j in range(ksize):
x, y = i - center, j - center
kernel[i, j] = np.exp(-(x**2 + y**2) / (2 * sigma**2))
# 归一化,让所有权重之和为 1
kernel /= kernel.sum()
return kernel
def my_gaussian_filter(img, ksize=5, sigma=1.5):
kernel = gaussian_kernel(ksize, sigma)
h, w = img.shape
pad = ksize // 2
padded = np.pad(img, pad, mode='reflect')
result = np.zeros_like(img)
for i in range(h):
for j in range(w):
window = padded[i:i+ksize, j:j+ksize]
result[i, j] = np.sum(window * kernel)
return result.astype(np.uint8)
2.2.3 高斯滤波的优缺点
| 优点 | 缺点 |
|---|---|
| 权重分布符合自然规律,平滑更自然 | 仍然会模糊边缘,只是比均值滤波好一些 |
| 可通过 σ 控制平滑程度,灵活性高 | 计算量比均值滤波稍大(需要浮点乘法) |
| 对高斯噪声效果很好(“专业对口”) | 对脉冲噪声(椒盐)效果依然不理想 |
| 可分离性:二维高斯可拆成两个一维高斯,加速计算 | σ 选择不当,要么欠平滑,要么过平滑 |
2.3 核心知识图谱
下面这张图帮你理清今天讲的内容:
2.4 如何选择:均值 vs 高斯
在实际项目中,我一般这样选:
- 快速预览或粗处理:用均值滤波,速度快,看一眼效果就行
- 噪声是高斯型的:首选高斯滤波,σ 设为 1.0~1.5 之间
- 需要保留边缘:两个都不行,等后面章节讲的双边滤波或深度学习方案
- 核大小怎么定:一般 3×3 或 5×5 就够了,7×7 以上基本只剩模糊
2.5 本章小结
今天我们学了两种最基础的降噪方法:
- 均值滤波:简单粗暴,所有邻居平等投票。适合快速去噪,但细节丢失严重。
- 高斯滤波:按距离分配权重,平滑更自然。通过 σ 控制平滑程度,是实际项目中最常用的基础滤波器。
这两种方法都有一个共同的“死穴”——不分区域地平滑。边缘和噪声在它们眼里是一样的,都会被抹平。