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 时域滤波的工程实现
理论讲完了,咱们看看工程上怎么做。我常用的多帧降噪流程是这样的:
- 选参考帧: 通常选中间帧或清晰度最高的帧
- 帧间配准: 计算每帧到参考帧的运动场
- 运动补偿: 根据运动场将每帧 warp 到参考帧坐标系
- 时域滤波: 对对齐后的像素做加权平均或递归滤波
- 后处理: 空间降噪、锐化、色调映射
下面这张图展示了整个流程:
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,前提是帧间对齐
- 帧间配准是核心难点,块匹配和光流法各有适用场景
- 递归滤波适合实时处理,α 自适应是关键
- 运动区域要特殊处理,否则鬼影和模糊会让你头疼