2. 多帧降噪核心原理

多帧降噪,说白了就是「用时间换信噪比」。你想想看,单帧图像里的噪声是随机的,但场景内容是固定的。如果我们能对齐多帧图像,把固定信号叠加起来,随机噪声就会相互抵消——这就是时域滤波最朴素的想法。

但事情没那么简单。我刚开始做ISP时,天真地以为把几帧图像直接平均就行。结果呢?运动物体拖影得一塌糊涂,被老大骂了一顿。从那以后我明白了一个道理:没有运动补偿的时域滤波,就是耍流氓。

时域滤波基础:从递归平均说起

最简单的时域滤波是递归平均。公式长这样:

// 递归时域滤波核心公式
output[n] = α * input[n] + (1 - α) * output[n-1]

这里的 α 是滤波系数,范围 0~1。α 越小,时域平滑越强,但运动拖尾也越严重。我习惯把 α 叫做「遗忘因子」——它决定了当前帧对历史帧的遗忘速度。

α 取值 降噪强度 运动拖尾 适用场景
0.1 ~ 0.3 强 严重 静态场景、三脚架拍摄
0.4 ~ 0.6 中 中等 手持轻微抖动
0.7 ~ 0.9 弱 轻微 快速运动场景
我的经验:α 不要固定不变。我一般在平坦区域用 0.2,纹理区域用 0.6,边缘区域用 0.8。这叫「空域自适应时域滤波」,效果比固定系数好得多。

帧间运动估计:光流法与块匹配

运动估计是多帧降噪的命门。你估计不准,后面的滤波全是白搭。主流方法有两种:光流法和块匹配法。

光流法:像素级的精细活

光流法假设像素亮度在运动前后不变,通过求解光流方程得到每个像素的运动矢量。听起来很美好,但实际坑很多。

// 光流基本约束方程
I(x, y, t) = I(x + dx, y + dy, t + dt)

// 泰勒展开后得到:
Ix * u + Iy * v + It = 0

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

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

我曾经踩过的坑:光流法在纹理贫乏区域(比如白墙)会完全失效。有一次我在夜景场景用光流做运动补偿,结果白墙区域的光流矢量全是噪声,降噪后反而出现了闪烁伪影。后来我加了纹理置信度权重,低纹理区域改用块匹配,才解决问题。

块匹配:工程界的扛把子

块匹配更接地气。把当前帧分成 N×N 的块,在参考帧的搜索窗口里找最相似的块。相似度用 SAD(差和)或 SSD(平方差和)衡量。

// SAD 计算示例
int computeSAD(uint8_t* block1, uint8_t* block2, int blockSize) {
    int sad = 0;
    for (int y = 0; y < blockSize; y++) {
        for (int x = 0; x < blockSize; x++) {
            sad += abs(block1[y * blockSize + x] - 
                       block2[y * blockSize + x]);
        }
    }
    return sad;
}

块匹配的三大参数:

  • 块大小:8×8 或 16×16 最常见。块越大越鲁棒,但运动细节丢失越多
  • 搜索范围:±16 像素是起步,运动剧烈时得 ±64
  • 匹配准则:SAD 计算快,SSD 对大差异更敏感
工程对比:光流法精度高但计算量大,适合高端相机;块匹配速度快,适合手机平台。我做过一个项目,用光流法每帧要 50ms,换成块匹配+三步搜索法,直接降到 3ms——代价是运动边界精度差一些。

降噪增益与运动模糊的权衡

这是多帧降噪最核心的博弈。你降噪越狠,运动模糊越重。怎么平衡?我总结了三板斧。

第一板斧:运动置信度加权

不要一刀切。对每个像素或块,计算运动置信度——匹配误差越小,置信度越高。然后根据置信度动态调整时域滤波强度。

// 运动置信度加权时域滤波
float motionConfidence = computeConfidence(sad, threshold);
float alpha_adaptive = alpha_base * motionConfidence;
output = alpha_adaptive * current + (1 - alpha_adaptive) * history;

第二板斧:多级运动检测

我习惯用三级检测:

  1. 全局运动:整帧的平移/旋转,用仿射模型估计
  2. 局部运动:16×16 块级别的运动矢量
  3. 像素运动:亚像素级别的精细补偿

全局运动用 3 参数模型就够了,局部运动用块匹配,像素运动用光流微调。三级联动,既快又准。

第三板斧:时空联合滤波

时域滤波搞不定的地方,交给空域。运动区域用双边滤波或引导滤波,静态区域用强时域滤波。我管这叫「时域为主,空域为辅」。

一个实用的调参技巧:先固定空域滤波强度,调时域 α 到运动边界刚好不糊;然后固定时域参数,调空域滤波到噪声刚好不可见。来回迭代两三次,基本就能找到最佳平衡点。

知识体系总览

下面这张图概括了多帧降噪的核心流程和权衡关系:

多帧降噪核心流程与权衡 多帧输入序列 帧间运动估计 光流法 / 块匹配 / 三级检测 运动补偿与对齐 自适应时域滤波 降噪输出帧 核心权衡:降噪增益 ↑ → 运动模糊 ↑ → 需运动置信度加权 + 时空联合滤波 运动估计方法 光流法:精度高,计算大 块匹配:速度快,精度中 三级检测:兼顾速度与精度 关键权衡参数 α 滤波系数:0.1~0.9 块大小:8×8 ~ 32×32 搜索范围:±16 ~ ±64

避坑指南:我踩过的三个大坑

  • 坑一:运动估计精度不够就强滤波——结果运动物体出现「鬼影」。后来我加了运动矢量平滑和异常值剔除,鬼影减少 80%。
  • 坑二:全局运动补偿用仿射模型——手机手持拍摄时,旋转和缩放同时存在,仿射模型不够用。换成单应性矩阵(8 参数)才搞定。
  • 坑三:时域滤波帧数越多越好——我曾经用 16 帧做时域平均,结果存储带宽爆炸,而且场景变化稍快就糊成一片。后来控制在 4~8 帧,效果和效率都满意。
一句话总结:多帧降噪的本质是「用运动估计的精度,换取时域滤波的强度」。运动估计越准,降噪就可以越狠;运动估计越糙,降噪就得越保守。这个 trade-off 贯穿整个 ISP 多帧处理 pipeline。