3. 多帧合成原理:多帧降噪的核心思想、帧间配准、时域滤波基础

多帧降噪,说白了就是「用数量换质量」。一张照片噪点多,那我拍三张、五张、甚至十几张,把它们合成一张干净的。这个思路在夜景模式里是核心中的核心。

我刚开始做这个方向时,总觉得单帧算法调调参数也能用。后来在暗光场景下测试,发现单帧再怎么调,暗部细节都是糊的。从那以后,我才真正重视起多帧合成这条路。

3.1 多帧降噪的核心思想

多帧降噪的底层逻辑其实很简单:信号是相关的,噪声是随机的。同一个场景拍多张,每张里的真实场景内容基本不变,但噪声每张都不一样。把多张图叠加平均,噪声就会相互抵消,信号则会保留下来。

举个例子:你拍一张夜景,某个像素的真实亮度是100,噪声是±10。单张可能看到90或110。但如果你拍4张,平均一下,噪声会降到±5左右。拍9张,降到±3.3。拍16张,降到±2.5。这就是多帧降噪的数学基础——信噪比提升与帧数的平方根成正比。

核心公式(直观理解版)

SNR多帧 ≈ SNR单帧 + 10 × log10(N) (dB)

其中 N 是帧数。4帧提升约6dB,9帧提升约9.5dB。

但这里有个坑:帧数不是越多越好。帧数多了,手持拍摄时的抖动累积、场景中的运动物体、甚至光线变化都会引入新的问题。我个人习惯是夜景模式用3-6帧,极端暗光场景最多用到9帧。再往上,收益递减,副作用递增。

帧数 理论信噪比提升 实际效果 典型场景
2帧 +3 dB 改善有限,暗部仍有明显噪点 快速抓拍
3-4帧 +4.8 ~ +6 dB 明显降噪,细节保留较好 手持夜景
6-9帧 +7.8 ~ +9.5 dB 大幅降噪,但需注意运动伪影 三脚架夜景
12帧以上 +10.8 dB+ 收益递减,对齐难度剧增 专业长曝光

3.2 帧间配准

多帧降噪的前提是:所有帧必须对齐。你想想看,如果每张图里同一个物体位置不一样,直接平均就会产生重影。这就是帧间配准要解决的问题。

帧间配准的核心是估计帧与帧之间的运动矢量。我把它分为两个层次:

  1. 全局运动估计:处理手持抖动、平移、旋转。通常用仿射变换或透视变换模型。
  2. 局部运动估计:处理场景中物体的独立运动,比如行人、车辆、树叶晃动。这个更难,需要光流或块匹配。

实际项目中,我一般先用全局配准把整体对齐,再用局部配准处理运动区域。这样效率高,效果也稳。

我的经验

全局配准用金字塔光流法,从低分辨率到高分辨率逐层细化。这样既能处理大位移,又能保证精度。我曾经试过直接在高分辨率做块匹配,结果慢得没法用。

帧间配准的常见方法对比:

方法 精度 速度 适用场景
相位相关 中等 快 纯平移运动
特征点匹配(SIFT/ORB) 高 中等 旋转+缩放场景
金字塔光流 高 中等 手持抖动+局部运动
块匹配(BM3D风格) 很高 慢 静态场景高精度需求

避坑指南

我曾经在配准时忽略了「运动物体」的处理。结果夜景模式拍街道,行人被平均成了半透明的鬼影。后来我加了运动掩膜,对运动区域做特殊处理——要么不参与平均,要么用更保守的融合权重。

3.3 时域滤波基础

配准做完,接下来就是时域滤波。时域滤波的核心是:沿着时间轴(帧序列)对每个像素做滤波。

最简单的时域滤波就是平均滤波:

// 伪代码:多帧平均
for each pixel (x, y):
    sum = 0
    for each frame i:
        sum += frame_i[x][y]
    result[x][y] = sum / N

但实际项目中,我很少用纯平均。原因有两个:

  • 配准不可能完美,总有残差。纯平均会把残差也平均进去,导致边缘模糊。
  • 运动区域需要保护,不能简单平均。

所以实际用的是加权时域滤波:

// 伪代码:加权时域滤波
for each pixel (x, y):
    weight_sum = 0
    result = 0
    for each frame i:
        // 根据配准置信度、运动检测结果计算权重
        w = compute_weight(frame_i[x][y], reference_frame[x][y])
        result += w * frame_i[x][y]
        weight_sum += w
    result[x][y] /= weight_sum

权重怎么算?我常用的策略:

  • 基于像素差异:当前帧像素与参考帧像素差异越大,权重越低。这能抑制配准残差和运动区域。
  • 基于梯度:边缘区域的权重降低,保护细节。
  • 基于帧序号:离参考帧越远的帧,权重越低。这能处理缓慢的光线变化。

还有一种更高级的时域滤波——递归时域滤波。它不需要缓存所有帧,而是逐帧更新:

// 伪代码:递归时域滤波
result_0 = frame_0
for each frame i (i > 0):
    alpha = compute_alpha(frame_i, result_{i-1})
    result_i = alpha * frame_i + (1 - alpha) * result_{i-1}

alpha 是融合系数,范围0~1。alpha 越大,当前帧贡献越大,响应快但降噪弱;alpha 越小,历史帧贡献大,降噪强但容易产生拖影。我一般把 alpha 设在0.3~0.7之间,根据运动检测结果动态调整。

时域滤波的关键权衡

降噪强度 vs 运动响应速度。降噪越强,运动拖影越明显。这个矛盾没法完全消除,只能通过运动检测和自适应权重来平衡。

3.4 多帧降噪全流程框架

把上面三个环节串起来,就是多帧降噪的完整流程。我画了一张图,方便你理解整体结构。

多帧降噪全流程框架图 多帧降噪全流程框架 多帧输入序列 步骤1:帧间配准 全局运动估计 → 局部运动估计 → 运动掩膜 步骤2:时域滤波 加权平均 / 递归滤波 / 自适应融合 步骤3:后处理 空间降噪 → 细节增强 → 色调映射 最终合成图像 关键:配准精度决定降噪上限,时域滤波策略决定细节保留程度 运动检测贯穿全流程,是避免重影和拖影的核心

这个流程里,每一步都有很多细节可以深挖。但核心逻辑就是:先对齐,再融合,最后精修。我在实际项目中,花在配准上的时间往往比滤波还多。因为配准做不好,后面再怎么调滤波参数都是白搭。

我的建议

刚开始做多帧降噪时,别急着上复杂算法。先用简单的平均滤波跑通流程,感受一下「配准误差」对结果的影响。等你亲眼看到重影和模糊,再去研究光流和自适应权重,理解会深刻得多。