4. 降噪滤波器原理(下):双边滤波与NLM

上一章聊了高斯滤波和均值滤波,说白了它们都是「空间域」的平滑操作。但有个问题一直困扰着我——边缘也一起被抹掉了。你想想看,降噪和保边,这俩需求天然就是矛盾的。

今天要讲的两种滤波器,就是专门来解决这个矛盾的。它们一个比一个聪明,当然,计算量也一个比一个大。

4.1 双边滤波:空间+像素值的双重保险

双边滤波(Bilateral Filter)的核心思想很简单:不仅要看距离,还要看像素值差异。

高斯滤波只考虑空间距离——离中心近的权重大,远的权重小。但双边滤波多了一个维度:像素值差异。如果邻域内某个像素的亮度值和中心点相差太大,就算它离得再近,权重也会被大幅降低。

我刚开始做ISP时,总觉得双边滤波就是个「加了边缘保护的高斯滤波」。后来在项目中调试夜景降噪才发现,这个理解太浅了。它的参数调起来,其实挺讲究的。

4.1.1 数学原理

双边滤波的权重由两部分相乘得到:

W(i,j) = Ws(i,j) × Wr(i,j)

其中:
Ws(i,j) = exp(-[(i-x)² + (j-y)²] / (2σs²))   // 空间权重
Wr(i,j) = exp(-[I(i,j) - I(x,y)]² / (2σr²))   // 像素值权重

最终输出就是邻域内所有像素的加权平均:

I'(x,y) = ΣW(i,j) × I(i,j) / ΣW(i,j)

这里有两个关键参数:

参数 含义 调大效果 调小效果
σs 空间标准差(滤波半径) 平滑更强,边缘变模糊 降噪不足,保留细节
σr 像素值标准差(灰度容差) 边缘保护变弱,类似高斯 边缘保留好,但降噪弱

核心理解:σr 决定了「多大的边缘才算边缘」。σr 越小,只有像素值非常接近的点才会参与平均,边缘保留得越完整;σr 越大,双边滤波就越接近普通高斯滤波。

4.1.2 适用场景

  • 轻度噪声 + 强边缘场景:比如文字图像、线条图、边缘清晰的物体
  • 需要保留纹理细节的降噪:比如皮肤纹理、织物纹理
  • 作为边缘感知滤波的前处理:很多高级算法会先用双边滤波做保边平滑

我曾经踩过的坑:双边滤波对高斯噪声效果不错,但对椒盐噪声几乎无效。有一次我在项目中直接用双边滤波处理传感器原始数据,结果噪声没去掉,反而把坏点扩散成了「小亮点团」。后来才意识到,椒盐噪声的像素值差异太大,双边滤波的像素值权重直接把噪声点当成了「边缘」来保护。

4.1.3 代码实现

OpenCV 里直接调用就行,但参数得自己试:

import cv2
import numpy as np

# 读取图像
img = cv2.imread('noisy_image.jpg', cv2.IMREAD_GRAYSCALE)

# 双边滤波
# 参数:d=9 邻域直径,sigmaColor=75 像素值标准差,sigmaSpace=75 空间标准差
filtered = cv2.bilateralFilter(img, d=9, sigmaColor=75, sigmaSpace=75)

# 显示结果
cv2.imshow('Original', img)
cv2.imshow('Bilateral', filtered)
cv2.waitKey(0)

我个人习惯的调参思路:先固定 σs(一般取 5-15),然后从 σr=30 开始往上调,观察边缘保留和降噪效果的平衡点。

4.2 非局部均值滤波(NLM)

双边滤波虽然保边,但它有个硬伤——只考虑局部邻域。如果噪声比较强,局部邻域里全是噪声,那再怎么加权平均也救不回来。

NLM 的思路就完全不一样了。它认为:图像里有很多相似的局部块。比如一块纯色天空,左上角和右下角的小块纹理应该是一样的。把这些相似块找出来一起平均,降噪效果会好得多。

我第一次看到 NLM 的时,觉得这个想法太优雅了。但实际跑起来……嗯,慢得让人怀疑人生。

4.2.1 核心原理

NLM 的流程可以概括为三步:

  1. 提取参考块:以当前像素为中心,取一个固定大小的块(比如 7×7)
  2. 搜索相似块:在整张图像(或一个搜索窗口内)寻找与参考块相似的块
  3. 加权平均:相似度越高的块,权重越大,最终加权平均得到输出

相似度的计算方式:

w(i,j) = exp(-||v(Ni) - v(Nj)||² / h²)

其中:
v(Ni) 和 v(Nj) 分别是以像素 i 和 j 为中心的图像块
||·||² 是两个块的欧氏距离平方
h 是滤波强度参数

参数 h 的作用:h 越大,相似度权重分布越均匀,降噪越强但细节丢失越多;h 越小,只有非常相似的块才会被纳入平均,细节保留好但降噪不足。我一般从 h=10 开始试,根据噪声水平上下调整。

4.2.2 适用场景

场景 推荐度 说明
高斯噪声 ★★★★★ NLM 对高斯噪声效果极好,尤其是中等强度噪声
纹理丰富的图像 ★★★★☆ 能保留纹理细节,但计算量大
实时视频处理 ★☆☆☆☆ 原始 NLM 太慢,需要优化版本
医学图像 ★★★★★ 对细节要求高,NLM 是经典选择

4.2.3 代码实现

OpenCV 提供了 fastNlMeansDenoising 系列函数:

import cv2
import numpy as np

# 读取图像
img = cv2.imread('noisy_image.jpg')

# NLM 降噪
# 参数:h=10 滤波强度,templateWindowSize=7 模板块大小,searchWindowSize=21 搜索窗口大小
denoised = cv2.fastNlMeansDenoisingColored(img, None, h=10, 
                                            templateWindowSize=7, 
                                            searchWindowSize=21)

# 显示结果
cv2.imshow('Original', img)
cv2.imshow('NLM Denoised', denoised)
cv2.waitKey(0)

性能警告:searchWindowSize 每增加一倍,计算量增加四倍。我建议在移动端或嵌入式平台上,searchWindowSize 不要超过 21,templateWindowSize 不要超过 7。否则帧率会掉到没法看。

4.3 三种滤波器对比

我把高斯滤波、双边滤波、NLM 放在一起对比一下,方便你选型:

特性 高斯滤波 双边滤波 NLM
保边能力 差 好 极好
降噪强度 中等 中等 强
计算速度 极快 中等 慢
参数数量 1个(σ) 2个(σs, σr) 3个(h, template, search)
适用噪声 高斯噪声 高斯噪声 高斯噪声、均匀噪声
实时性 ★★★★★ ★★★☆☆ ★☆☆☆☆

4.4 知识结构图

下面这张图帮你理清今天讲的内容:

降噪滤波器分类与对比 降噪滤波器 高斯滤波 双边滤波 NLM 仅空间距离 速度快 不保边 空间+像素值 保边较好 速度中等 全局相似块 保边极好 速度慢 适用场景 适用场景 适用场景 预处理、实时系统 保边降噪、纹理保留 强噪声、医学图像

4.5 选型建议

在实际项目中,我一般这样选:

  • 实时性要求高(比如视频预览):用高斯滤波,或者优化版的双边滤波
  • 需要保边降噪(比如拍照后处理):用双边滤波,参数调好效果不错
  • 噪声特别强(比如暗光环境):上 NLM,虽然慢但效果好
  • 移动端/嵌入式:别用原始 NLM,可以考虑积分图加速的双边滤波

我的个人习惯:在 ISP pipeline 里,我会把双边滤波放在去马赛克之后、锐化之前。这样既能降噪,又不会把噪声放大。NLM 我一般只用在最终输出前的后处理阶段,或者离线处理场景。

好了,这一章的内容就到这里。双边滤波和 NLM 都是「保边降噪」的经典方法,理解它们的原理和参数含义,后面调优的时候会顺手很多。