空间域降噪算法(一):均值滤波与高斯滤波、中值滤波与双边滤波、非局部均值(NLM)降噪、BM3D算法原理与实现

各位好,欢迎来到第四章。

前面我们聊了噪声模型和图像质量评价。从这章开始,要动真格的了——我们正式进入降噪算法的核心地带。空间域降噪,说白了就是直接在像素点上做文章。它不依赖频域变换,思路更直观,但门道一点都不少。

我个人习惯把空间域算法分成两类:局部滤波和非局部滤波。局部滤波看的是像素周围一个小窗口,非局部滤波则在整个图像里找相似块。这章我们把这几个经典算法串起来讲,你会发现它们之间有一条清晰的进化脉络。

本章核心脉络: 从最简单的均值滤波出发,逐步引入权重、边缘保护、块匹配思想,最终过渡到BM3D这个“空间域天花板”。
空间域降噪算法知识体系 空间域降噪算法演进路线 均值滤波 / 高斯滤波 线性平滑,模糊边缘 中值滤波 非线性,去椒盐噪声 双边滤波 空域+值域权重 非局部均值(NLM) 块匹配+加权平均 BM3D 分组+协同滤波+维纳 关键演进逻辑 1. 均值→高斯:从均匀权重到距离权重,减少振铃 2. 高斯→双边:引入像素值差异权重,保护边缘 3. 双边→NLM:从局部邻域扩展到全图搜索,利用自相似性 4. NLM→BM3D:从空间域加权到变换域协同滤波,信噪比飞跃 5. BM3D至今仍是很多ISP降噪模块的参考基准

4.1 均值滤波与高斯滤波

先聊最基础的。均值滤波,就是把窗口内所有像素值加起来求平均。你想想看,如果窗口里有噪声点,周围像素一平均,噪声确实被压下去了,但边缘也糊了。为什么?因为边缘两侧的像素值差异很大,平均之后边缘就变成了平滑过渡。

高斯滤波稍微聪明一点。它给窗口中心附近的像素更高的权重,离中心越远权重越低。这个权重分布服从高斯函数。我在项目中遇到过一个问题:用高斯滤波处理星空夜景,星星全被抹成了光晕。后来发现是sigma参数设太大了。

特性 均值滤波 高斯滤波
权重策略 均匀权重 距离高斯权重
边缘保护 差 较差
计算复杂度 O(k²) O(k²)
主要问题 振铃效应 细节丢失
实战建议: 高斯滤波的sigma一般取窗口半径的1/3左右。比如5x5窗口,sigma取0.8~1.0。太大等于均值滤波,太小等于没滤波。

4.2 中值滤波

中值滤波的思路完全不同。它不求和,不平均,而是把窗口内所有像素值排序,取中间那个值。这招对付椒盐噪声特别管用。为什么?因为椒盐噪声是极端值,排序后要么在最前要么在最后,永远选不到中间。

我曾经在调试一款安防摄像头的ISP时,发现图像上全是白点。一开始以为是sensor坏了,后来发现是传输链路上有干扰,产生了椒盐噪声。中值滤波一上,干净了。但要注意,中值滤波对高斯噪声效果一般,而且会抹掉细线条——比如电线、树枝这些。

// 中值滤波核心实现(C语言风格伪代码)
unsigned char median_filter(unsigned char *window, int size) {
    // 1. 将窗口像素复制到临时数组
    unsigned char temp[size * size];
    memcpy(temp, window, size * size);
    
    // 2. 排序(这里用简单的冒泡,实际会用快速选择)
    for (int i = 0; i < size * size - 1; i++) {
        for (int j = 0; j < size * size - 1 - i; j++) {
            if (temp[j] > temp[j + 1]) {
                unsigned char t = temp[j];
                temp[j] = temp[j + 1];
                temp[j + 1] = t;
            }
        }
    }
    
    // 3. 返回中值
    return temp[size * size / 2];
}
注意: 中值滤波的窗口大小选择很关键。3x3保留细节好但去噪弱,5x5去噪强但细节损失大。对于ISP管线,我建议椒盐噪声用3x3中值,配合其他算法处理高斯噪声。

4.3 双边滤波

双边滤波是我个人非常喜欢的一个算法。它解决了高斯滤波的一个核心痛点:边缘模糊。怎么做呢?它同时考虑两个因素:空间距离和像素值差异。

空间距离权重和高斯滤波一样——离中心近的权重大。像素值差异权重呢?如果邻域像素和中心像素值相差很大,就认为它属于另一个区域,权重给得很小。这样一来,在平坦区域,双边滤波就像高斯滤波一样平滑;在边缘处,它只取同侧像素做平均,边缘就保住了。

嗯,这里要注意一个坑。双边滤波的参数有三个:空间sigma(σs)和值域sigma(σr)。σs控制空间范围,σr控制像素值差异的敏感度。我调试夜景照片时,σr设太小,噪声去不掉;设太大,边缘又糊了。后来总结了一个经验公式:σr取图像噪声标准差的两倍左右。

双边滤波公式:
BF[I]p = (1/Wp) · Σq∈S Gσs(||p-q||) · Gσr(|Ip-Iq|) · Iq
其中Wp是归一化因子,Gσ是高斯函数。

4.4 非局部均值(NLM)降噪

双边滤波虽然能保边,但它还是局限在一个小窗口里。NLM的思路就大胆多了——它认为图像里有很多重复的纹理和结构,可以在整张图里找相似块来做平均。

具体怎么做?对于每个像素点,以它为中心取一个块(比如7x7),然后在全图搜索相似的块。相似度用块之间的欧氏距离衡量。距离越小,权重越大。最后把所有相似块加权平均,得到去噪后的像素值。

我在做手机夜景降噪时试过NLM。效果确实好,纹理保留得比双边滤波强很多。但代价也大——计算量太大了。一张1200万像素的照片,全图搜索基本跑不动。后来用了搜索窗口限制(比如21x21),再加上积分图加速,才勉强在工程上可用。

算法 搜索范围 相似度度量 计算量 去噪质量
双边滤波 局部窗口 像素值差 低 中等
NLM 全局/大窗口 块间欧氏距离 高 好
BM3D 全局+变换域 块匹配+协同滤波 很高 极好
工程优化: NLM的搜索窗口大小和块大小需要权衡。我常用的配置:搜索窗口15x15,块大小5x5,相似度阈值根据噪声水平自适应调整。

4.5 BM3D算法原理与实现

终于到BM3D了。这个算法全称是Block-Matching and 3D Filtering,块匹配三维滤波。它可以说是空间域降噪的集大成者,也是很多ISP降噪模块的参考基准。

BM3D的核心思想分两步:分组和协同滤波。

第一步,分组。和NLM类似,BM3D也在图像里搜索相似块。但BM3D把这些相似块叠在一起,形成一个三维数组。你想想看,每个块是二维的,叠起来就是三维的——两个空间维度加一个块索引维度。

第二步,协同滤波。对这个三维数组做三维变换(通常是DCT或小波),在变换域里做硬阈值或维纳滤波,再反变换回去。为什么这么做?因为相似块在变换域里,噪声是随机分布的,而真实信号是相关的。阈值一砍,噪声就没了。

BM3D有两个阶段:基础估计和最终估计。基础估计用硬阈值滤波,得到一个初步的去噪结果。最终估计用维纳滤波,基于基础估计的结果做更精细的滤波。

// BM3D算法流程(伪代码)
void BM3D(float *image, float *denoised, int width, int height) {
    // 阶段一:基础估计
    for each reference block at position (i, j) {
        // 1. 块匹配:在全图搜索相似块
        List blocks = block_matching(image, i, j, block_size, search_window);
        
        // 2. 分组:将相似块叠成3D数组
        float group[block_size][block_size][blocks.count];
        for each matched block {
            group[:, :, k] = matched_block;
        }
        
        // 3. 协同硬阈值滤波
        // 3.1 3D变换(DCT + 小波)
        // 3.2 硬阈值处理
        // 3.3 3D反变换
        float filtered_group = collaborative_hard_threshold(group, sigma);
        
        // 4. 聚合:将滤波后的块加权放回原图
        aggregate(filtered_group, i, j, weight);
    }
    
    // 阶段二:最终估计(使用维纳滤波)
    for each reference block {
        // 使用基础估计结果做维纳滤波
        float final_group = collaborative_wiener_filter(
            original_group, basic_estimate_group, sigma);
        aggregate(final_group, i, j, weight);
    }
}
BM3D的工程陷阱:
  • 计算量极大,在移动端ISP上基本跑不动实时。我见过有人用GPU加速,或者只对关键帧做BM3D。
  • 参数敏感:块大小、搜索窗口、阈值、步长,每个参数都影响最终效果。
  • 纹理重复度低的图像(比如纯风景),BM3D的优势不明显。

4.6 算法对比与选型建议

讲了这么多,到底在实际项目中怎么选?我根据自己的经验给一个参考:

  • 实时预览场景(30fps以上):用高斯滤波或双边滤波,配合查表加速。BM3D想都别想。
  • 夜景多帧合成:先做帧间对齐,然后用NLM或BM3D做单帧降噪。我一般用NLM,因为BM3D太慢了。
  • 椒盐噪声:中值滤波是首选,3x3窗口足够。
  • 高ISO照片(ISO 3200以上):BM3D效果最好,但要做好计算资源规划。
  • 边缘保护要求高:双边滤波或NLM,别用均值/高斯。
一句话总结: 均值滤波和高斯滤波是基础,中值滤波专治椒盐,双边滤波保边入门,NLM和BM3D是追求极致画质的选择。没有最好的算法,只有最适合场景的算法。