4. 去噪(Denoise)

去噪这章,我琢磨了很久该怎么讲。说白了,去噪就是跟图像里的“脏东西”做斗争。但斗争不能太狠,否则把细节也干掉了,图像就变成“塑料感”。

我个人习惯把去噪分成三个维度:空域、时域、3D。这三个维度不是孤立的,实际项目中往往是组合拳。你想想看,一个视频流进来,单帧有噪声,帧间有闪烁,光靠一种方法根本搞不定。

4.1 空域去噪:单帧内的“扫雷”

空域去噪,就是只盯着当前这一帧图像,在像素的“邻居”里做文章。核心思想很简单:噪声是高频的,细节也是高频的,怎么区分?靠统计。

4.1.1 均值滤波与高斯滤波

均值滤波,就是把一个像素周围一圈的像素值加起来取平均。这方法简单粗暴,但副作用也明显——边缘会糊。我在项目里测过,3x3的均值滤波,边缘的MTF(调制传递函数)能掉20%以上。

高斯滤波好一些,它给中心像素更高的权重,边缘保留能力更强。但本质上还是低通滤波,遇到纹理丰富的区域,照样糊。

关键指标:高斯滤波的σ(标准差)控制着平滑强度。σ越大,平滑越强,细节丢失越多。我一般建议σ不超过1.5,否则图像就“雾化”了。

4.1.2 双边滤波:边缘保留的“聪明”滤波

双边滤波是我在项目中用得最多的空域去噪方法。它不光考虑像素的空间距离,还考虑像素值的差异。说白了,就是“颜色差太多的邻居不参与平均”。

公式不复杂,但效果很直观:

// 双边滤波核心思想
// 空间权重:离中心越近,权重越大
// 值域权重:像素值差异越大,权重越小
// 最终权重 = 空间权重 × 值域权重

// 伪代码示例
for each pixel (i, j):
    sum_weight = 0
    sum_value = 0
    for each neighbor (m, n) in window:
        spatial_weight = gaussian( distance((i,j), (m,n)), sigma_s )
        range_weight = gaussian( |I(i,j) - I(m,n)|, sigma_r )
        weight = spatial_weight * range_weight
        sum_weight += weight
        sum_value += weight * I(m,n)
    output(i,j) = sum_value / sum_weight

这里有两个关键参数:sigma_s(空间域标准差)和sigma_r(值域标准差)。sigma_s控制滤波窗口大小,sigma_r控制边缘保留的敏感度。

我的经验:sigma_r 设得太小,去噪效果差;设得太大,边缘就保不住了。我一般从0.05开始调(归一化到0-1),根据噪声水平逐步增加。遇到高ISO场景,sigma_r可以到0.15,但再高就要小心了。

4.1.3 非局部均值去噪(NLM)

NLM是个更“聪明”的算法。它不只看局部邻居,而是在整张图里找相似的图像块来做平均。我记得第一次在项目中跑NLM,效果确实惊艳,但速度也让人崩溃——一张1080p的图,纯CPU跑要好几秒。

后来我做了优化:限制搜索窗口大小(比如21x21),用积分图加速相似度计算,才勉强跑到实时。但说实话,在移动端ISP里,NLM还是太贵了,更多用在高端相机或者后处理里。

4.2 时域去噪:让时间帮你“平均”

时域去噪,就是利用视频帧之间的冗余信息。静态场景下,多帧平均能把噪声压得很低。但动态场景就麻烦了——运动物体如果不处理,就会出现“鬼影”。

4.2.1 帧平均与运动补偿

最简单的时域去噪就是帧平均:把前后几帧的对应像素取平均。但前提是场景不能动。一旦有运动,就得做运动估计(ME)和运动补偿(MC)。

我在项目中遇到过一个问题:运动估计不准时,补偿后的图像反而比原始帧还差。后来我加了一个“运动置信度”的判定——如果运动向量匹配的残差太大,就降低该像素的时域融合权重。

方法 优点 缺点 适用场景
简单帧平均 实现简单,效果好(静态) 运动场景鬼影严重 三脚架、夜景长曝光
运动补偿帧平均 支持运动场景,去噪强 计算量大,运动估计不准时反而差 手持夜景、视频防抖
递归时域滤波 内存占用小,实时性好 有拖影,收敛慢 实时视频流、低功耗设备

4.2.2 递归时域滤波(IIR)

递归滤波是时域去噪的“轻量级”方案。它只保留上一帧的结果,用指数加权的方式融合当前帧:

// 递归时域滤波
output(t) = alpha * input(t) + (1 - alpha) * output(t-1)

// alpha 是融合系数,范围0~1
// alpha 越大,当前帧权重越高,去噪越弱,但响应快
// alpha 越小,历史帧权重越高,去噪越强,但拖影明显

alpha的取值很关键。我一般这样设:静态区域alpha=0.1~0.3,运动区域alpha=0.7~0.9。怎么判断动静?用帧差法或者运动向量。

注意:递归滤波的拖影问题很难完全消除。我曾经在一个监控项目里,alpha设了0.15,结果人走过去之后,背景上还留着半秒的“残影”。后来我加了运动检测的自适应alpha,才把这个问题压下去。

4.3 3D去噪:空域+时域的“合体技”

3D去噪,就是把空域和时域结合起来。先做运动补偿,然后在时空立方体里做联合滤波。效果比单独用空域或时域都好,但计算量也最大。

4.3.1 VBM3D/VBM4D 原理

VBM3D(Video Block-Matching 3D)是图像去噪领域的经典算法。它的思路是:

  1. 块匹配:在当前帧和前后帧里找相似的图像块,组成一个3D组(空间+时间)。
  2. 3D变换:对这个3D组做3D DCT或3D小波变换。
  3. 阈值收缩:在变换域里,噪声对应的是小系数,直接置零或收缩。
  4. 逆变换:把处理后的系数变换回像素域,得到去噪结果。

VBM4D是VBM3D的升级版,支持4D(3D空间+时间),效果更好,但计算量也翻倍。

实际落地情况:VBM3D在PC上处理一段720p视频,大概要几秒一帧。在手机ISP里,目前还做不到实时。但它的思想被很多轻量级算法借鉴了,比如用3D DCT做局部去噪。

4.3.2 工程化的3D去噪方案

在真正的ISP芯片里,3D去噪不会用VBM3D那么重的算法。我参与过一个项目,用的是“3D降采样+小波去噪”的方案:

  • 先把图像降采样到1/4分辨率,在低分辨率下做运动估计和时域融合。
  • 然后在低分辨率图像上做小波去噪,利用小波的多尺度特性分离噪声和细节。
  • 最后用引导滤波上采样回原分辨率,同时保留边缘。

这个方案在骁龙某款平台上跑到了30fps,PSNR比纯空域去噪高了2-3dB。

4.4 去噪强度与细节平衡:永远在“钢丝上走路”

去噪的本质是“取舍”。你压了噪声,就必然损失细节。怎么平衡?我总结了三个原则:

4.4.1 噪声水平估计是前提

你不知道噪声有多大,怎么确定去噪强度?我习惯用暗区统计法:在图像的暗部(比如天空、阴影)取一块区域,计算局部方差。这个方差基本就是噪声的方差。

// 噪声估计伪代码
// 1. 选取图像中最暗的10%像素区域
// 2. 在这些区域里取N个8x8块
// 3. 计算每个块的方差,取中位数
// 4. 这个中位数就是噪声方差的估计值

noise_level = median( variance(blocks) )

4.4.2 自适应强度控制

去噪强度不能全局统一。亮区噪声少,可以弱去噪;暗区噪声多,需要强去噪。纹理丰富的区域,去噪要弱,否则纹理就没了;平坦区域,去噪可以强,反正也没细节。

我常用的方法是:根据局部亮度和局部纹理复杂度,生成一个“去噪强度图”。这个图的值在0~1之间,0表示不去噪,1表示最强去噪。

区域类型 亮度 纹理复杂度 推荐去噪强度
暗区平坦(如夜空) 低 低 0.8~1.0
暗区纹理(如夜景建筑) 低 高 0.4~0.6
亮区平坦(如白墙) 高 低 0.3~0.5
亮区纹理(如草地、布料) 高 高 0.1~0.3

4.4.3 细节保护的后处理

即使做了自适应,去噪后还是可能损失细节。我习惯在去噪之后加一个“细节恢复”步骤:

  • 把原始图像和去噪图像做差,得到“残差图”。
  • 对残差图做高通滤波,提取高频细节。
  • 把高频细节按一定比例加回去。

这个比例很关键。加多了,噪声又回来了;加少了,细节没恢复。我一般控制在0.2~0.4之间,具体看噪声水平。

避坑指南:我曾经在一个项目里,为了追求“干净”,把去噪强度拉得很高,结果图像像“油画”一样,细节全没了。后来我加了一个“细节保护”的开关——当检测到去噪强度超过0.7时,自动启用细节恢复,把边缘和纹理的损失补回来。

4.5 知识结构总览

下面这张图总结了去噪的完整知识体系,从方法分类到工程落地,一目了然:

去噪(Denoise)知识体系总览 空域去噪 时域去噪 3D去噪 均值/高斯滤波 双边滤波 非局部均值NLM 帧平均 运动补偿MC 递归IIR滤波 VBM3D/VBM4D 3D DCT/小波 工程化轻量方案 去噪强度与细节平衡 噪声水平估计 自适应强度控制 细节保护后处理 工程落地关键:实时性、内存、功耗、画质权衡 鬼影/拖影 细节丢失/油画感 计算量过大

去噪这条路,没有银弹。空域快但糊,时域强但怕动,3D效果好但贵。我做了这么多年ISP,最大的体会是:去噪不是算法竞赛,而是系统工程。你得根据硬件平台、功耗预算、场景特点,找到那个“刚刚好”的点。

嗯,去噪就讲到这里。记住一句话:去噪的终点不是“干净”,而是“自然”。