4. 图像配准:全局运动估计(仿射变换)与局部运动估计(光流法)

图像配准,说白了就是让多张图「对齐」。多帧降噪也好,HDR合成也好,前提都是像素能对上。要是没对齐就硬叠,画面就糊了,还不如单帧。

我刚开始做多帧降噪时,觉得配准不就是找偏移量嘛,平移一下就行。后来发现,手持拍摄时不仅有平移,还有旋转、缩放,甚至局部物体在动。嗯,这里就得把运动估计拆成两个层面来聊。

4.1 全局运动估计:仿射变换模型

全局运动,指的是整张图像的整体运动。比如你手持相机拍夜景,手抖了一下,整张图都跟着平移+旋转了。这种运动,用仿射变换就能描述。

仿射变换的数学形式很简单:

x' = a*x + b*y + c
y' = d*x + e*y + f

六个参数(a, b, c, d, e, f),就能搞定平移、旋转、缩放、剪切。我习惯用最小二乘法来解这六个参数,前提是得先找到两帧之间的匹配点对。

4.1.1 特征点提取与匹配

常用的特征点有SIFT、ORB、AKAZE。在ISP场景里,我更推荐ORB——速度快,算力友好。SIFT精度高,但手机端跑起来有点吃力。

步骤大致是这样:

  1. 提取参考帧和当前帧的特征点
  2. 用暴力匹配或FLANN做粗匹配
  3. 用RANSAC剔除误匹配
  4. 用剩下的内点求解仿射矩阵
我的经验: RANSAC的阈值别设太死。我调过一款夜景算法,阈值设0.5像素,结果内点太少,仿射矩阵直接崩了。后来放宽到1.5像素,效果反而更好。阈值要根据噪声水平动态调整。

4.1.2 仿射矩阵求解

有了至少三对匹配点(不共线),就能解六个参数。实际项目中我会用OpenCV的 estimateAffinePartial2D,它只估计平移+旋转+缩放,更稳定。

// C++ 示例:仿射变换估计
std::vector<cv::Point2f> pts_ref, pts_cur;
// ... 填充匹配点对 ...
cv::Mat affine = cv::estimateAffinePartial2D(pts_cur, pts_ref);
// affine 就是 2x3 的仿射矩阵

拿到仿射矩阵后,把当前帧 warp 到参考帧坐标系下,就完成了全局配准。

4.2 局部运动估计:光流法

全局仿射能搞定相机运动,但解决不了局部运动。比如夜景里有人在走路,树叶被风吹动——这些局部区域,仿射变换是管不了的。

这时候就得请出光流法。光流,说白了就是每个像素的运动矢量。我最早接触光流是在做视频稳像时,后来发现多帧降噪里也离不开它。

4.2.1 稀疏光流 vs 稠密光流

类型 代表算法 输出 算力消耗 适用场景
稀疏光流 Lucas-Kanade 特征点处的运动矢量 低 实时、算力受限
稠密光流 Farneback、RAFT 每个像素的运动矢量 高 高精度、离线或旗舰芯片

在手机ISP里,我见过的大多是稀疏光流+插值。稠密光流精度高,但功耗扛不住。不过最近几年DSP和NPU性能上来了,有些旗舰机已经开始用轻量级稠密光流了。

4.2.2 Lucas-Kanade 光流原理

L-K光流基于三个假设:亮度恒定、小运动、空间一致。公式推导我就不展开了,核心思想是解一个超定方程组:

Ix * u + Iy * v = -It

其中 Ix, Iy 是图像梯度,It 是时间梯度
u, v 是待求的光流矢量

在一个小窗口内(比如5x5),有25个方程,解两个未知数,用最小二乘法搞定。

注意: 亮度恒定假设在夜景里经常被打破。暗光下噪声大,像素值不稳定。我曾经在调试时发现光流矢量乱跳,后来加了高斯预滤波,把噪声压一压,光流才稳定下来。

4.2.3 金字塔光流

小运动假设在手持拍摄时经常不成立。手抖一下,位移可能十几个像素。这时候就得用金字塔光流——从粗到细,逐层优化。

流程是这样的:

  1. 构建图像金字塔(比如4层)
  2. 从顶层开始计算光流
  3. 把结果映射到下一层,作为初始值
  4. 在下一层做精细化
  5. 重复直到最底层

这样做的好处是:顶层能捕捉大运动,底层能保证精度。我习惯用3层金字塔,再多了边际效益递减。

4.3 全局+局部:混合配准策略

在实际的多帧降噪和HDR合成中,我很少只用一种方法。我的惯用策略是:

  1. 先做全局仿射配准:消除相机运动带来的整体偏移
  2. 计算残差光流:在全局对齐的基础上,计算局部运动
  3. 运动掩膜生成:光流大的区域标记为运动区域,做特殊处理

为什么要分两步?因为直接算光流,计算量大,而且容易受全局运动干扰。先对齐再算局部,光流矢量会小很多,精度也更高。

核心思路: 全局仿射负责「粗对齐」,局部光流负责「细修正」。两者配合,才能做到像素级精准配准。

4.4 知识结构总览

下面这张图,把本章的核心逻辑串起来了。你可以看到全局运动估计和局部运动估计各自的位置,以及它们如何汇合到最终的配准结果。

图像配准:全局 + 局部运动估计 参考帧 + 当前帧 全局运动估计(仿射变换) 特征点提取 + RANSAC 求解 6 参数仿射矩阵 Warp 到参考帧坐标系 局部运动估计(光流法) 金字塔光流(粗到细) Lucas-Kanade 求解 运动掩膜生成 混合配准结果 对齐后的多帧图像 全局粗对齐 → 局部细修正 两者缺一不可

4.5 避坑指南与工程建议

  • 运动边界处理:光流在运动物体的边缘容易出错。我习惯在运动掩膜上做膨胀+中值滤波,避免边缘出现鬼影。
  • 算力与精度的权衡:别一上来就上稠密光流。先评估你的平台,如果算力有限,稀疏光流+插值也能用。
  • 多帧配准的参考帧选择:我建议选中间帧作为参考。选第一帧的话,后面的帧偏移太大,配准容易失败。
  • 失败回退机制:如果RANSAC内点太少,或者光流一致性检查不过,直接放弃这一帧,别硬配。
一个小技巧: 在算光流之前,先把图像下采样到1/4分辨率,算完再上采样回原图。这样能大幅提速,而且精度损失不大。我在多个项目里都这么干过。

好了,图像配准这块就聊到这儿。全局仿射和局部光流,一个管整体,一个管细节,配合好了,后面的多帧降噪和HDR合成才能有好的基础。