4. 经典去噪算法(二):双边滤波、非局部均值去噪(NLM)的原理与调参技巧

上一章我们聊了均值滤波和高斯滤波,说白了就是「周围像素取平均」。这招对付高斯噪声还行,但一碰到边缘就露馅了——边缘也被抹平了。今天要讲的这两个算法,才是真正有点「智能」的去噪方法。

我个人习惯把双边滤波叫做「带脑子的高斯滤波」,而NLM则是「全局找相似」的暴力美学。嗯,咱们一个一个来。

4.1 双边滤波:边缘保留的平滑

先想想高斯滤波的问题在哪?它只考虑了空间距离——离中心越近的像素权重越大。但边缘两侧的像素,虽然空间上挨着,像素值却差很多。强行平均,边缘就糊了。

双边滤波的改进很简单:再加一个像素值相似度的权重。也就是说,不仅要看距离近不近,还要看颜色像不像。

核心公式:

输出像素 = 邻域内每个像素的加权平均

权重 = 空间高斯权重 × 像素值高斯权重

空间权重:离中心越近,权重越大。
值域权重:像素值与中心越接近,权重越大。

这样一来,在平坦区域,大家像素值都差不多,值域权重接近1,效果跟高斯滤波差不多。但在边缘处,边缘另一侧的像素值差异大,值域权重变得很小,几乎不影响中心像素——边缘就保住了。

4.1.1 两个关键参数

参数 符号 作用 典型范围
空间标准差 σspace 控制空间邻域大小 1~10
值域标准差 σrange 控制像素值相似度的敏感度 10~100(8位图)

调参经验:

  • σspace 越大,平滑范围越大,但计算量也越大
  • σrange 越小,边缘保护越强,但去噪能力下降
  • σrange 太大,双边滤波退化成高斯滤波

我的经验:处理自然图像时,我一般先固定 σspace=3,然后调整 σrange。从30开始试,如果边缘还太锐利就加大,如果噪声没去干净就减小。说白了就是个平衡游戏。

4.1.2 代码示例

import cv2
import numpy as np

def bilateral_filter_demo(img, d=9, sigma_color=50, sigma_space=3):
    """
    d: 邻域直径
    sigma_color: 值域标准差(对应 σ_range)
    sigma_space: 空间标准差(对应 σ_space)
    """
    result = cv2.bilateralFilter(img, d, sigma_color, sigma_space)
    return result

# 使用示例
img = cv2.imread('noisy_image.jpg', cv2.IMREAD_GRAYSCALE)
denoised = bilateral_filter_demo(img, d=9, sigma_color=50, sigma_space=3)
cv2.imwrite('bilateral_result.jpg', denoised)

注意:双边滤波的计算量跟邻域大小直接相关。d=9时,每个像素要计算81个权重。如果图像很大,建议先缩小再处理,或者用OpenCV的快速近似版本。

4.2 非局部均值去噪(NLM)

双边滤波虽然保边,但它只看了局部邻域。你想想看,图像里很多结构是重复的——比如墙上的纹理、草地上的图案。这些重复区域,其实可以互相帮忙去噪。

NLM的核心思想就是:在整个图像中寻找与当前像素块相似的块,然后加权平均。说白了,就是「你不是一个人在战斗」。

4.2.1 算法流程

  1. 以当前像素为中心,取一个小块(比如7×7)
  2. 在整个图像(或搜索窗口内)搜索相似块
  3. 计算每个相似块与当前块的相似度(通常用欧氏距离)
  4. 根据相似度给每个块分配权重,加权平均得到去噪结果

关键点:相似度不是比单个像素,而是比整个块的像素分布。这样即使有噪声,块的整体结构也能匹配上。

4.2.2 三个核心参数

参数 说明 典型值
搜索窗口大小 在多大范围内找相似块 21×21
块大小 用于比较的块尺寸 7×7
滤波强度(h) 控制权重的衰减速度 10~30

调参技巧:

  • 搜索窗口越大,找到相似块的机会越多,但计算量暴增
  • 块大小太小,区分度不够;块太大,找不到相似块
  • h值越大,平滑越强,但细节丢失也越多

避坑指南:我曾经在一个纹理丰富的图像上用了默认参数,结果等了半分钟才出结果。后来把搜索窗口从21×21缩小到11×11,速度提升了4倍,效果几乎没差别。记住:不是搜索范围越大越好,够用就行。

4.2.3 代码示例

import cv2

def nlm_denoise_demo(img, h=10, templateWindowSize=7, searchWindowSize=21):
    """
    h: 滤波强度,越大平滑越强
    templateWindowSize: 块大小(必须是奇数)
    searchWindowSize: 搜索窗口大小(必须是奇数)
    """
    result = cv2.fastNlMeansDenoising(img, None, h, templateWindowSize, searchWindowSize)
    return result

# 使用示例
img = cv2.imread('noisy_image.jpg', cv2.IMREAD_GRAYSCALE)
denoised = nlm_denoise_demo(img, h=15, templateWindowSize=7, searchWindowSize=21)
cv2.imwrite('nlm_result.jpg', denoised)

4.3 双边滤波 vs NLM:怎么选?

场景 推荐算法 理由
边缘清晰的图像(如文字、图标) 双边滤波 速度快,边缘保护极好
纹理丰富的图像(如布料、草地) NLM 能利用重复纹理,去噪更干净
实时处理需求 双边滤波 NLM太慢,不适合实时
高噪声图像 NLM 全局搜索能获得更多有效信息

重要提醒:NLM的OpenCV实现默认只处理单通道灰度图。如果是彩色图,需要用 fastNlMeansDenoisingColored,它会分别处理YCrCb空间的亮度通道和色度通道。

4.4 知识体系结构图

经典去噪算法(二)知识体系 双边滤波 空间权重 × 值域权重 σ_space: 控制邻域大小 σ_range: 控制边缘敏感度 优点: 速度快, 保边好 缺点: 局部搜索, 纹理区弱 非局部均值去噪 (NLM) 全局搜索相似块加权平均 搜索窗口: 找块的范围 块大小: 相似度比较单位 h值: 滤波强度 优点: 纹理去噪强 缺点: 计算量大 对比 调参核心建议 1. 先确定应用场景(边缘优先 vs 纹理优先) 2. 从保守参数开始,逐步调整 3. 用肉眼判断效果,不要只看PSNR 没有最好的算法,只有最合适的参数

嗯,到这里双边滤波和NLM的核心内容就讲完了。这两个算法在实际项目中非常实用,尤其是当你需要保留边缘细节的时候。我个人建议初学者先从双边滤波入手,参数少、效果好、速度快。等你对去噪有了感觉,再挑战NLM——你会发现它处理纹理噪声时确实有两把刷子。


专注资料整理