第四章 对齐与配准:让多帧画面“严丝合缝”
夜景拍摄,说白了就是跟光线不足作斗争。单帧信噪比不够,那就多帧合成。但有个前提——你得保证这几帧画面里,同一个像素点拍的是同一个物体。人手持机会抖,风吹树叶会动,行人会走过。这些运动如果不处理,合成出来的就是重影、模糊,还不如单帧。
对齐与配准,就是解决这个问题的。我做了这么多年ISP,可以负责任地说:对齐做不好,夜景算法基本废了一半。
4.1 为什么需要对齐?
先看一个简单场景。你按下快门,ISP连续抓了4帧RAW。理想情况是每帧像素位置完全一样。但现实呢?
- 全局运动:手持抖动、平移、旋转。整幅画面一起动。
- 局部运动:行人走过、汽车开过、树叶摇晃。只有部分区域在动。
- 卷帘快门效应:逐行曝光,快速移动物体会变形。
对齐的目标,就是找到每帧之间的运动矢量,然后把它们“掰”到同一个坐标系下。我见过不少团队,花大把精力调降噪参数,结果对齐没做好,合成效果还不如单帧。嗯,方向错了。
4.2 三大主流对齐方法
实际工程中,常用的对齐方法就三种。各有各的脾气,我一个个说。
| 方法 | 核心思想 | 精度 | 速度 | 适用场景 |
|---|---|---|---|---|
| 基于梯度的光流法 | 利用像素亮度梯度,求解运动方程 | 高(亚像素级) | 慢 | 小运动、纹理丰富区域 |
| 块匹配算法 | 在搜索窗口内找最相似的图像块 | 中(整像素级) | 快 | 大运动、纹理稀疏区域 |
| 金字塔分层策略 | 从粗到细逐层估计运动 | 高 | 中等 | 大运动+高精度需求 |
4.3 基于梯度的光流法
光流法的假设很简单:同一个物体点在相邻帧之间,亮度保持不变。用数学表达就是:
I(x, y, t) = I(x + dx, y + dy, t + dt)
泰勒展开后,忽略高阶项,得到光流约束方程:
Ix * u + Iy * v + It = 0
其中 Ix、Iy 是空间梯度,It 是时间梯度,u、v 就是我们要找的运动矢量。
一个方程两个未知数,解不了。所以需要加约束。最经典的是Lucas-Kanade方法,它假设一个局部窗口内所有像素运动一致。这样就有多个方程,用最小二乘法求解。
核心步骤:
- 计算图像梯度 Ix、Iy(Sobel或Scharr算子)
- 计算时间差分 It = I(t+1) - I(t)
- 对每个像素,取邻域窗口(如5x5),构建方程组
- 用最小二乘法求解 u、v
我在项目中遇到过一个问题:光流法对纹理很敏感。平坦区域(比如夜空、白墙)梯度几乎为零,方程就病态了。算出来的运动矢量全是噪声。所以实际工程中,光流法通常只用在纹理丰富的区域,或者配合其他方法一起用。
避坑指南:我曾经在一个夜景项目中,直接用光流法处理整个画面。结果天空区域算出一堆乱七八糟的矢量,合成后反而引入了伪影。后来加了梯度幅值阈值,只对梯度大于某个值的像素做光流估计,问题就解决了。
4.4 块匹配算法
块匹配的思路更直观。你把当前帧切成一个个小块(比如8x8或16x16),然后在参考帧的搜索窗口里,找最像的那个块。相似度用SAD(差值和)或SSD(平方差值和)来衡量。
SAD = Σ|I_ref(x+i, y+j) - I_cur(x+dx+i, y+dy+j)|
SSD = Σ(I_ref(x+i, y+j) - I_cur(x+dx+i, y+dy+j))²
搜索策略也有讲究:
- 全搜索:遍历搜索窗口内每个位置。精度最高,但慢。
- 三步搜索:先大步跳,再小步精调。速度快很多,精度损失不大。
- 菱形搜索:用菱形模板逐步逼近最优位置。我比较喜欢这个,平衡性好。
块匹配的优点是鲁棒。不管你是平移、旋转还是局部运动,只要块够小,都能找到对应关系。缺点也很明显:精度只能到整像素。想提高精度?得配合亚像素插值。
个人经验:块大小选16x16比较通用。太大容易包含不同运动物体,太小又容易匹配到噪声。搜索窗口大小取决于你预估的最大运动幅度。手持夜景,一般±16像素就够了。
4.5 金字塔分层策略
光流法精度高但怕大运动,块匹配能处理大运动但精度有限。怎么办?把两者结合起来——这就是金字塔分层策略的核心思想。
你想想看,如果两帧之间有20个像素的位移,直接算光流,迭代收敛很慢,还容易掉进局部最优。但如果先把图像缩小4倍,位移就变成了5个像素。再缩小4倍,变成1.25个像素。这时候用光流法,轻松搞定。
具体流程是这样的:
- 构建金字塔:对每帧图像,逐层下采样,生成多分辨率图像。一般3~5层。
- 顶层估计:在最粗糙的顶层,用块匹配或光流法估计初始运动矢量。
- 逐层上采样:把上一层的运动矢量放大,作为下一层的初始值。
- 逐层精调:在每一层,用光流法对运动矢量做小范围修正。
- 得到最终运动场:最底层(原始分辨率)的运动矢量就是最终结果。
为什么金字塔有效?
- 顶层图像小,搜索范围小,速度快
- 大运动在顶层变成小运动,光流法能处理
- 逐层精调,精度逐步提高,最终达到亚像素级
我习惯用3层金字塔。层数太多,下采样损失细节太多,反而影响精度。层数太少,对大运动又不够鲁棒。3层是个不错的折中。
4.6 工程实现中的关键细节
理论说完了,聊聊落地。我踩过的坑,你们就别再踩了。
4.6.1 运动一致性检查
算出来的运动矢量不一定都靠谱。尤其是遮挡区域(物体边缘)、运动边界(前景和背景运动不一致的地方)。我习惯加一个前向后向一致性检查:
- 从帧A到帧B算一个运动矢量 u_forward
- 从帧B到帧A算一个运动矢量 u_backward
- 如果 u_forward + u_backward 的模大于某个阈值,说明这个点的运动估计不可靠,标记为无效
无效区域怎么办?用周围有效的运动矢量做插值填充。或者干脆不参与后续的合成。
4.6.2 亚像素精度
块匹配只能到整像素。但夜景合成需要亚像素精度,不然合成图像会有锯齿感。常用的方法:
- 抛物线拟合:在最优匹配点周围,用SAD值拟合一个二次曲面,求极值点位置
- 双线性插值:对图像做亚像素插值,再重新计算匹配
我个人偏好抛物线拟合,计算量小,精度也够用。
4.6.3 多帧对齐的策略
多帧夜景,一般选一帧作为参考帧(通常是中间帧或质量最好的帧),其他帧都对齐到它。但要注意:
- 参考帧选第一帧?不行。第一帧往往曝光不稳定。
- 选中间帧?可以。前后帧到中间帧的运动距离最小,误差累积少。
- 选质量最好的帧?也行。用梯度幅值或对比度来评估。
我的习惯:先快速评估每帧的清晰度(用拉普拉斯方差),选最清晰的那帧做参考。然后其他帧用金字塔光流法对齐到它。如果某帧运动太大(超过预设阈值),直接丢弃,不参与合成。
4.7 性能优化:嵌入式平台怎么跑得快
手机芯片、安防摄像头,算力有限。金字塔光流法虽然效果好,但直接跑肯定扛不住。几个优化方向:
| 优化手段 | 做法 | 加速比 | 精度影响 |
|---|---|---|---|
| 降分辨率处理 | 在1/2或1/4分辨率下算运动矢量,再放大 | 4x~16x | 小 |
| 稀疏化计算 | 只在特征点或网格点上算运动,其余插值 | 10x~50x | 中 |
| NEON/SSE向量化 | 用SIMD指令并行计算SAD和梯度 | 4x~8x | 无 |
| 提前终止 | SAD值低于阈值就停止搜索 | 2x~5x | 极小 |
我做过一个项目,在ARM Cortex-A55上跑4K夜景对齐。一开始全分辨率金字塔光流,一帧要800ms。后来降到1/4分辨率+稀疏网格(16x16一个点),优化到30ms。精度损失?肉眼几乎看不出来。
注意:降分辨率处理时,运动矢量也要按比例缩放。比如在1/4分辨率下算出的运动是(2, 3),对应原始分辨率就是(8, 12)。这个细节容易忘,忘了就全错了。
4.8 本章小结
对齐与配准,是夜景多帧合成的基石。光流法精度高但怕大运动,块匹配鲁棒但精度有限。金字塔分层策略把两者结合起来,从粗到细,兼顾了精度和鲁棒性。
实际工程中,没有银弹。你得根据场景、算力、精度要求,灵活组合这些方法。我见过有人只用块匹配,效果也不错。也见过有人把光流法优化到极致,在低端芯片上跑得飞起。关键是要理解每种方法的本质,知道它擅长什么、怕什么。