3. 经典降噪算法(下):双边滤波、非局部均值去噪(NLM)的原理与调参技巧
上一章我们聊了均值滤波和高斯滤波,说白了它们都是“局部加权平均”。但有个问题——它们会把边缘也抹平了。你想想看,一张照片里,边缘才是视觉重点,边缘糊了,整张图就废了。
这一章我们来看两种更聪明的算法:双边滤波和非局部均值去噪(NLM)。它们都能在去噪的同时保住边缘。我在实际项目中用过很多次,尤其是双边滤波,几乎成了我处理相机原始数据的标配。
3.1 双边滤波:保边去噪的经典方案
双边滤波的核心思想很简单:不仅要看空间距离,还要看像素值差异。
高斯滤波只考虑“离我近不近”,双边滤波多了一个维度——“跟我像不像”。
公式长这样:
BF[I]_p = (1/W_p) * Σ G_s(||p - q||) * G_r(|I_p - I_q|) * I_q
其中:
G_s:空间域高斯核,控制空间距离权重G_r:值域高斯核,控制像素相似度权重W_p:归一化因子
说白了,就是两个高斯核相乘。空间上离得近、值上又相似的像素,权重最大。
关键理解:在平坦区域,像素值差异小,值域权重接近1,双边滤波退化为高斯滤波;在边缘处,像素值差异大,值域权重很小,边缘像素几乎不参与平均,边缘就保住了。
3.1.1 两个核心参数
| 参数 | 符号 | 作用 | 典型范围 |
|---|---|---|---|
| 空间标准差 | σs | 控制空间邻域大小 | 1.0 ~ 5.0 |
| 值域标准差 | σr | 控制像素相似度敏感度 | 0.05 ~ 0.3(归一化后) |
调参时我有个习惯:先定σs,再调σr。σs决定了模糊范围,σr决定了边缘保护力度。
我的经验:σr设得太小(比如0.02),边缘是保住了,但噪声几乎没去掉;设得太大(比如0.5),边缘也开始模糊,跟高斯滤波没区别了。我一般从0.1开始试,上下微调。
3.1.2 代码示例
import cv2
import numpy as np
img = cv2.imread('noisy_image.jpg', 0)
# 双边滤波
# 参数:d=9(邻域直径), sigmaColor=75, sigmaSpace=75
result = cv2.bilateralFilter(img, d=9, sigmaColor=75, sigmaSpace=75)
cv2.imwrite('bilateral_result.jpg', result)
注意:双边滤波的计算复杂度是O(N·r²),其中r是邻域半径。如果d设得太大(比如d>15),处理一张1080p图像可能要好几秒。我曾经在嵌入式设备上吃过这个亏,后来改用快速近似算法才跑起来。
3.2 非局部均值去噪(NLM):用整张图的信息去噪
双边滤波虽然保边,但它还是只看了局部邻域。NLM的思路更大胆:整张图里所有相似的块都可以用来去噪。
你想想看,一张图像里有很多重复的纹理结构。比如草地、墙面、布料。NLM就是找到这些相似的块,把它们加权平均。
公式:
NLM[I](i) = Σ w(i,j) * I(j)
w(i,j) = (1/Z(i)) * exp(-||N_i - N_j||² / h²)
其中:
N_i、N_j:以像素i、j为中心的图像块||N_i - N_j||²:两个块的欧氏距离h:滤波强度参数Z(i):归一化常数
核心思想:块越相似,权重越大。即使两个像素空间距离很远,只要它们周围的纹理结构相似,就能互相贡献。
3.2.1 NLM的三个关键参数
| 参数 | 说明 | 典型值 | 调参方向 |
|---|---|---|---|
| h | 滤波强度,控制指数衰减速度 | 10 ~ 30 | h越大,去噪越强,但细节损失越多 |
| templateWindowSize | 块大小,用于计算相似度 | 5×5 或 7×7 | 块越大,相似度判断越准确,但计算越慢 |
| searchWindowSize | 搜索窗口大小 | 21×21 或 31×31 | 窗口越大,找到的相似块越多,但计算量暴增 |
调参时我一般先固定templateWindowSize为7×7,然后从h=15开始试。如果噪声残留多,就增大h;如果细节糊了,就减小h。
避坑指南:我曾经在纹理丰富的图像上把h设到30,结果纹理全变成油画效果了。后来我学乖了——纹理区用小h,平坦区用大h。现在很多自适应NLM算法就是这么干的。
3.2.2 代码示例
import cv2
img = cv2.imread('noisy_image.jpg', 0)
# NLM去噪
# 参数:h=15, templateWindowSize=7, searchWindowSize=21
result = cv2.fastNlMeansDenoising(img, h=15,
templateWindowSize=7,
searchWindowSize=21)
cv2.imwrite('nlm_result.jpg', result)
3.3 双边滤波 vs NLM:怎么选?
这个问题我经常被问到。直接给结论:
| 场景 | 推荐算法 | 原因 |
|---|---|---|
| 实时处理(视频、嵌入式) | 双边滤波 | 计算量小,可优化 |
| 高噪声图像(σ>30) | NLM | 利用全局信息,去噪更彻底 |
| 纹理丰富的图像 | NLM | 双边滤波容易产生梯度翻转伪影 |
| 边缘锐利度要求高 | 双边滤波 | NLM在强边缘处可能引入块状伪影 |
| 计算资源充足 | NLM | 效果上限更高 |
我个人习惯是:先试双边滤波,效果不满意再上NLM。毕竟双边滤波快,调参也简单。
3.4 知识结构图
下面这张图帮你理清本章的核心逻辑:
3.5 调参实战建议
最后给几条实在的建议:
- 先降噪后增强:不要试图用边缘增强来掩盖噪声,那只会让噪声更明显。
- 分区域调参:平坦区用强去噪,纹理区用弱去噪。OpenCV的
fastNlMeansDenoisingMulti支持逐帧调参。 - 注意边界效应:双边滤波在图像边界处权重计算会出问题,我一般会先padding再处理。
- NLM的加速技巧:用积分图加速块距离计算,或者用FFT加速搜索。OpenCV的实现已经做了优化,但如果你自己写,记得用这些技巧。
我曾经踩过的坑:在医疗影像上直接用默认参数的NLM,结果把微小的钙化点给去掉了。后来我改用自适应h值——在疑似病灶区域降低滤波强度。所以,永远不要盲目相信默认参数,一定要根据你的图像特点来调。
好了,这一章就到这里。双边滤波和NLM是保边去噪的两大基石,理解透了,后面讲BM3D和深度学习去噪时你会觉得轻松很多。