3. 传统降噪方法(下):中值滤波、双边滤波的原理与实现,边缘保留特性分析
上一节我们聊了均值滤波和高斯滤波。这两种方法,说白了都是「平均主义」——把邻域内的像素揉在一起取个平均值。平滑效果不错,但代价也很明显:边缘糊了。
那有没有一种方法,既能去掉噪声,又能把边缘这条「命根子」保住?
有。中值滤波和双边滤波,就是传统图像处理里两个最经典的「边缘保留」选手。我入行那会儿,第一个接触的非线性滤波器就是中值滤波,当时觉得这玩意儿太聪明了。后来做双边滤波,才真正体会到什么叫「既要又要」的工程艺术。
3.1 中值滤波:简单粗暴,但有效
3.4.1 原理:排序代替平均
中值滤波的思路非常直接:不用平均,用排序。
具体做法:
- 选定一个窗口(比如 3×3)
- 把窗口内所有像素值排序
- 取排序后的中间值,作为当前像素的新值
举个例子。一个 3×3 窗口里的像素值是这样的:
原始像素值:
[10, 12, 11]
[13, 255, 9] ← 中间那个 255 是椒盐噪声
[8, 11, 10]
排序后:8, 9, 10, 10, 11, 11, 12, 13, 255
中间值(第5个):11
新像素值:11
你看,那个突兀的 255 直接被干掉了,换成了 11。这就是中值滤波的核心——对离群点免疫。
核心结论:中值滤波对椒盐噪声(salt-and-pepper noise)效果极佳,因为噪声点通常排在序列两端,永远不会被选中。
3.4.2 为什么能保留边缘?
这个问题我当年也困惑过。你想想看,边缘是什么?是像素值的「跳变」。比如从 50 跳到 150。
如果窗口刚好跨在边缘上:
窗口跨边缘:
[50, 50, 50]
[50, 150, 150]
[50, 150, 150]
排序后:50, 50, 50, 50, 50, 150, 150, 150, 150
中间值:50 或 150(取决于窗口中心在哪一侧)
结果要么是 50,要么是 150。边缘还在。而均值滤波会把 50 和 150 平均成 100,边缘就糊了。
这就是中值滤波的「边缘保留」本质——它不创造新值,只从现有值里选一个。
3.4.3 代码实现
OpenCV 里一句话搞定:
import cv2
import numpy as np
# 读取图像
img = cv2.imread('noisy_image.jpg', cv2.IMREAD_GRAYSCALE)
# 中值滤波,核大小必须为奇数
denoised = cv2.medianBlur(img, 3) # 3x3 窗口
# 显示结果
cv2.imshow('Original', img)
cv2.imshow('Median Filter', denoised)
cv2.waitKey(0)
如果自己实现,核心逻辑也不复杂:
def median_filter(image, kernel_size=3):
h, w = image.shape
pad = kernel_size // 2
padded = np.pad(image, pad, mode='reflect')
result = np.zeros_like(image)
for i in range(h):
for j in range(w):
window = padded[i:i+kernel_size, j:j+kernel_size]
result[i, j] = np.median(window)
return result
个人经验:中值滤波的窗口大小很关键。3×3 对轻度噪声够用,5×5 能去更重的噪声,但也会开始抹掉细小的纹理。我一般从 3 开始试,不行再往上加。
3.4.4 中值滤波的局限性
| 优点 | 缺点 |
|---|---|
| 对椒盐噪声效果极好 | 对高斯噪声效果一般 |
| 边缘保留能力强 | 计算量大(需要排序) |
| 实现简单 | 窗口越大,细节丢失越多 |
| 没有平均带来的模糊 | 对纹理区域不友好 |
我曾经在一个工业检测项目里用中值滤波去椒盐噪声,效果非常好。但换成高斯噪声后,图像反而出现了「块状感」。后来我才意识到——中值滤波对高斯噪声的抑制能力有限,因为高斯噪声的每个像素都偏离了真值,排序后中间值也不准。
3.2 双边滤波:空间+灰度,双管齐下
3.2.1 原理:两个高斯核的乘积
双边滤波(Bilateral Filter)是我个人非常喜欢的一个滤波器。它的想法很优雅:不仅要考虑像素之间的距离,还要考虑像素值之间的差异。
公式长这样:
I_filtered(x) = (1/W_p) * Σ I(x_i) * G_s(||x - x_i||) * G_r(|I(x) - I(x_i)|)
其中:
- G_s: 空间高斯核(距离越近,权重越大)
- G_r: 灰度高斯核(灰度越接近,权重越大)
- W_p: 归一化系数
说白了就是:
- 空间近的像素,权重高
- 灰度值接近的像素,权重高
- 两者都满足的像素,权重最高
这就意味着:在平坦区域,周围像素灰度值都差不多,双边滤波退化为高斯滤波,平滑效果很好。但在边缘处,边缘另一侧的像素灰度值差异大,权重被压低,所以边缘被保留了下来。
一句话总结:双边滤波 = 高斯滤波(空间域) × 灰度相似性(值域)。
3.2.2 两个关键参数
双边滤波有两个核心参数,调参的时候要特别留意:
| 参数 | 符号 | 作用 | 调参建议 |
|---|---|---|---|
| 空间标准差 | σspace | 控制空间邻域大小 | 越大,平滑范围越广 |
| 灰度标准差 | σrange | 控制灰度相似性阈值 | 越大,边缘保留越弱 |
我刚开始用双边滤波的时候,经常把 σrange 设得太大,结果边缘全没了,跟高斯滤波没区别。后来才明白——σrange 决定了「什么算边缘」。设小了,连纹理都当边缘保留;设大了,真正的边缘也被平滑掉。
避坑指南:我曾经在一个医学图像去噪项目里,把 σrange 设成了 100(图像是 8 位,范围 0-255),结果边缘全糊了。后来改成 20-30,效果才出来。记住:σrange 一般取图像灰度范围的 10%-15%。
3.2.3 代码实现
OpenCV 实现:
import cv2
img = cv2.imread('noisy_image.jpg')
# 双边滤波
# 参数:d=9(邻域直径),sigmaColor=75(灰度标准差),sigmaSpace=75(空间标准差)
denoised = cv2.bilateralFilter(img, d=9, sigmaColor=75, sigmaSpace=75)
cv2.imshow('Bilateral Filter', denoised)
cv2.waitKey(0)
自己实现的核心逻辑:
def bilateral_filter(image, d=9, sigma_color=75, sigma_space=75):
h, w = image.shape
pad = d // 2
padded = np.pad(image, pad, mode='reflect')
result = np.zeros_like(image, dtype=np.float32)
# 预计算空间高斯权重
x, y = np.meshgrid(range(-pad, pad+1), range(-pad, pad+1))
space_weight = np.exp(-(x**2 + y**2) / (2 * sigma_space**2))
for i in range(h):
for j in range(w):
window = padded[i:i+d, j:j+d].astype(np.float32)
center_val = image[i, j]
# 灰度高斯权重
range_weight = np.exp(-((window - center_val)**2) / (2 * sigma_color**2))
# 总权重 = 空间权重 × 灰度权重
weight = space_weight * range_weight
weight /= weight.sum() # 归一化
result[i, j] = np.sum(window * weight)
return result.astype(np.uint8)
性能提示:双边滤波的计算量很大,因为每个像素都要重新计算灰度权重。实际项目中可以用「双边网格」(Bilateral Grid)加速,或者用 OpenCV 的优化版本。我一般先用小图调参,再跑全图。
3.3 边缘保留特性对比分析
为了让你更直观地理解,我画了一张对比图:
3.4 如何选择?
在实际项目中,我一般这样选:
- 椒盐噪声 → 中值滤波。又快又好,没商量。
- 高斯噪声 + 需要保留边缘 → 双边滤波。但要注意调参。
- 实时性要求高 → 中值滤波。双边滤波太慢。
- 纹理丰富的区域 → 双边滤波。中值滤波会把细纹理当成噪声抹掉。
我的建议:不要迷信任何一种滤波器。实际项目中,我经常把中值滤波和双边滤波组合使用——先用中值滤波去椒盐噪声,再用双边滤波做精细平滑。效果往往比单独用任何一种都好。
3.5 小结
这一节我们聊了中值滤波和双边滤波。中值滤波靠排序,对椒盐噪声有奇效;双边滤波靠空间+灰度双权重,能在去噪的同时保住边缘。两者都是传统图像处理里的「边缘保留」利器。
但说实话,这两种方法都有天花板。中值滤波对高斯噪声力不从心,双边滤波计算量大、参数敏感。到了深度学习时代,我们有更好的选择——那就是后面要讲的卷积神经网络去噪方法。
不过,理解这些传统方法很重要。它们的思想——利用局部统计特性、区分噪声和边缘——在深度学习里依然被广泛借鉴。很多去噪网络的设计,都能看到中值滤波和双边滤波的影子。