3. 多帧降噪原理

多帧降噪,说白了就是「用多张照片换一张干净照片」。你想想看,单帧降噪再怎么折腾,细节和噪声之间总得做个取舍。但如果你手里有同一场景的多帧数据呢?那情况就完全不一样了。

我刚开始接触这个方向时,总觉得多帧不就是求个平均嘛,有什么难的?后来在实际项目中踩了不少坑,才发现帧间配准才是真正的拦路虎。这一章,咱们就把时域滤波的底牌翻出来看看。

3.1 时域滤波的核心思想

时域滤波,英文叫 Temporal Filtering。它跟空间滤波(比如高斯模糊、双边滤波)最大的区别在于——它不是在单张图里做文章,而是沿着时间轴去处理。

假设你拍摄了 N 帧图像,每帧在同一个像素位置 (x, y) 的亮度值分别是 I₁, I₂, ..., IN。如果场景是静止的,那这些值应该差不多。但因为有噪声,它们会上下波动。

时域滤波要做的,就是把这些波动抹平。

核心假设:信号在时间上是相关的,噪声在时间上是独立的。

这个假设成立,多帧降噪才有意义。

3.2 叠加平均降噪的数学推导

咱们先从最简单的模型说起。假设每帧图像都包含真实信号 S 和加性噪声 N:

Iᵢ(x, y) = S(x, y) + Nᵢ(x, y)

其中 Nᵢ 是均值为 0、方差为 σ² 的高斯白噪声。不同帧之间的噪声相互独立。

对 N 帧做平均:

I_avg(x, y) = (1/N) * Σᵢ Iᵢ(x, y)
            = S(x, y) + (1/N) * Σᵢ Nᵢ(x, y)

噪声项的方差会变成:

Var[(1/N) * Σᵢ Nᵢ] = (1/N²) * Σᵢ Var[Nᵢ] = (1/N²) * N * σ² = σ² / N

标准差就是 σ / √N。

这意味着什么? 叠加 4 帧,噪声标准差降为原来的 1/2;叠加 9 帧,降为 1/3;叠加 16 帧,降为 1/4。

帧数 N 噪声标准差倍数 信噪比提升 (dB)
1 1.00 0
4 0.50 6.02
9 0.33 9.54
16 0.25 12.04
64 0.125 18.06

嗯,这里要注意——这个推导假设所有帧已经完美对齐。如果没对齐,那平均出来的就不是干净信号,而是重影。

3.3 帧间配准:多帧降噪的前提

现实世界中,你不可能让相机纹丝不动。手持拍摄时,总会有微小的平移和旋转。这就是帧间配准要解决的问题。

我个人习惯把配准分为两个层次:

  • 全局配准(Global Registration): 整张图像做同一个变换,比如平移、旋转、仿射变换。适合相机抖动的情况。
  • 局部配准(Local Registration): 图像不同区域有不同的运动,比如画面中有行人走过、树叶被风吹动。这时候需要光流或块匹配。

我的经验: 在手机多帧降噪中,90% 的情况用全局配准就够了。但如果你做的是监控视频降噪,局部运动是常态,必须上光流。

3.3.1 块匹配配准

块匹配是最直观的方法。把参考帧分成一个个小块,然后在相邻帧的搜索窗口里找最相似的位置。

// 伪代码:块匹配流程
for each block B in reference frame:
    best_match = 无穷大
    for each candidate position (dx, dy) in search window:
        cost = SAD(reference_block, candidate_block)
        if cost < best_match:
            best_match = cost
            motion_vector = (dx, dy)
    output motion_vector for block B

常用的匹配代价函数有:

  • SAD(差和): Σ|I₁ - I₂|,计算快,硬件友好
  • SSD(平方差和): Σ(I₁ - I₂)²,对大偏移更敏感
  • NCC(归一化互相关): 对光照变化鲁棒,但计算量大

我曾经在一个低光照项目中吃过亏——用 SAD 做匹配,结果因为两帧之间的亮度差异太大,匹配全错了。后来换成 NCC,问题才解决。

3.3.2 光流法配准

光流法假设像素在时间上的亮度不变,通过求解光流方程得到每个像素的运动矢量。

I(x, y, t) = I(x + dx, y + dy, t + dt)

泰勒展开后得到光流约束方程:
Iₓ * u + Iᵧ * v + Iₜ = 0

其中 u = dx/dt, v = dy/dt 是光流矢量

一个方程有两个未知数,解不唯一。所以需要加约束——Lucas-Kanade 方法假设局部窗口内运动一致,Horn-Schunck 方法则加全局平滑约束。

避坑指南: 我曾经在纹理稀疏的区域(比如白墙、天空)用光流法,结果光流矢量全是噪声。后来我学乖了——在纹理不足的区域,直接回退到全局配准,或者用相邻像素的运动做插值。

3.4 时域滤波的工程实现

理论讲完了,咱们看看工程上怎么做。我常用的多帧降噪流程是这样的:

  1. 选参考帧: 通常选中间帧或清晰度最高的帧
  2. 帧间配准: 计算每帧到参考帧的运动场
  3. 运动补偿: 根据运动场将每帧 warp 到参考帧坐标系
  4. 时域滤波: 对对齐后的像素做加权平均或递归滤波
  5. 后处理: 空间降噪、锐化、色调映射

下面这张图展示了整个流程:

多帧降噪核心流程 输入帧序列 选参考帧 帧间配准 运动补偿 时域滤波 后处理 输出降噪帧 配准失败时重选参考帧 关键说明: 1. 配准是瓶颈——配准不准,后续滤波全是白做 2. 时域滤波可以选择简单平均或递归滤波(如指数平滑) 3. 后处理通常用双边滤波或BM3D做空间域补充

3.5 递归时域滤波

如果你有视频流,不可能等攒够 N 帧再平均。这时候要用递归滤波。

最简单的递归形式是指数平滑:

I_out(t) = α * I_in(t) + (1 - α) * I_out(t-1)

α 是更新率,取值在 0 到 1 之间。α 越小,平滑效果越强,但对运动响应越慢。

我建议的做法是让 α 自适应:

  • 在运动区域,α 设大一点(比如 0.5~0.8),快速跟上新帧
  • 在静止区域,α 设小一点(比如 0.05~0.1),充分降噪

自适应 α 的经验公式:

α = α_min + (α_max - α_min) * motion_level

其中 motion_level 可以用帧差或光流幅值归一化得到。

3.6 多帧降噪的局限性

说了这么多好处,也得聊聊它的短板。

问题 原因 我的应对方案
运动模糊 帧间运动补偿不准 运动区域降低时域权重,转用空间降噪
鬼影 大运动物体未对齐 做运动检测,对鬼影区域做掩膜处理
闪烁 帧间亮度不一致 先做直方图匹配或增益补偿
存储/带宽 需要缓存多帧 用递归滤波代替批处理,只存一帧历史

我记得有一次做夜景多帧降噪,拍了 8 帧,结果因为风吹树叶,配准全乱了。后来我加了一个运动检测模块——运动大的区域直接跳过时域滤波,只做空间降噪。效果好了很多。

3.7 小结

这一章咱们把多帧降噪的底牌翻了个遍:

  • 叠加平均能把噪声标准差降到 σ/√N,前提是帧间对齐
  • 帧间配准是核心难点,块匹配和光流法各有适用场景
  • 递归滤波适合实时处理,α 自适应是关键
  • 运动区域要特殊处理,否则鬼影和模糊会让你头疼