3、运动补偿模型:全局运动模型(仿射/透视)、局部运动模型(光流法)、混合模型

运动补偿,说白了就是解决「怎么把两张图对齐」的问题。你拍多帧 HDR 时,手一抖、风一吹、人一动,像素就全跑偏了。不对齐就融合,鬼影能让你怀疑人生。

我这些年做运动补偿,最深的体会是:没有万能的模型,只有合适的场景。全局运动、局部运动、混合模型,各有各的脾气。咱们一个一个拆开聊。

3.1 全局运动模型:仿射与透视

全局运动模型假设:整张图的运动可以用一个统一的变换来描述。说白了,就是认为画面里所有像素都跟着同一个「大动作」在动——比如相机平移、旋转、缩放。

3.1.1 仿射变换(6 参数)

仿射变换是我用得最多的全局模型。它用 6 个参数描述运动:

// 仿射变换矩阵形式
[x']   [a  b  tx] [x]
[y'] = [c  d  ty] [y]
[1 ]   [0  0  1 ] [1]

// 展开后:
x' = a*x + b*y + tx
y' = c*x + d*y + ty

这 6 个参数能干什么?平移、旋转、缩放、剪切,全包了。但注意,它不能处理透视变形——平行线变换后还是平行线。

我的经验:仿射变换最适合「相机绕光心小角度旋转 + 平移」的场景。比如手持手机拍 HDR,只要不是剧烈抖动,仿射模型基本够用。我曾经在一个夜景 HDR 项目里,直接用仿射模型跑了 90% 的帧对,效果很稳。

3.1.2 透视变换(8 参数)

透视变换比仿射多 2 个参数,能处理「近大远小」的透视效果:

// 透视变换矩阵形式
[x']   [a  b  c] [x]
[y'] = [d  e  f] [y]
[w ]   [g  h  1] [1]

// 实际坐标:
x'' = x' / w
y'' = y' / w

多了 g 和 h 两个参数,就能描述「梯形变形」了。比如你拍一个广告牌,从侧面拍过去,透视变换能把歪的拉正。

注意:透视变换参数多,计算量也大。而且参数越多,越容易过拟合——尤其当画面里运动物体很多时,透视模型反而会「学歪」。我曾经在一个场景里,背景是静止的,但前景有人走动,用透视模型硬算,结果背景对齐了,人却变成了「鬼影叠中叠」。

3.1.3 全局模型的参数估计

怎么算这些参数?最经典的方法是 Lucas-Kanade 光流 + RANSAC。流程大致是:

  1. 提取特征点(比如 ORB、SIFT)
  2. 用光流法跟踪特征点
  3. 用 RANSAC 剔除离群点
  4. 用最小二乘法拟合变换矩阵

嗯,这里要注意:RANSAC 的阈值设置很关键。设得太小,好点被误杀;设得太大,坏点混进来。我一般从 3 个像素开始试,根据图像分辨率微调。

3.2 局部运动模型:光流法

全局模型搞不定「局部运动」——比如人走路、树叶摇晃、汽车开过。这时候就得请出光流法了。

光流法的核心假设:同一个像素点在相邻帧之间,亮度不变。数学表达就是:

I(x, y, t) = I(x+dx, y+dy, t+dt)

展开泰勒级数,忽略高阶项,得到光流约束方程:

Ix * u + Iy * v + It = 0

其中:
Ix = ∂I/∂x, Iy = ∂I/∂y (空间梯度)
It = ∂I/∂t (时间梯度)
u = dx/dt, v = dy/dt (光流向量)

一个方程两个未知数,解不了。所以需要加约束——这就是各种光流法的区别所在。

3.2.1 稀疏光流 vs 稠密光流

类型 代表算法 输出 速度 适用场景
稀疏光流 Lucas-Kanade 仅特征点处的运动向量 快 特征跟踪、全局模型初始化
稠密光流 Farneback、RAFT、FlowNet 每个像素的运动向量 慢 逐像素运动补偿、视频插帧

我个人习惯:先跑稀疏光流做全局对齐,再用稠密光流做局部修正。这样又快又准。

避坑指南:我曾经在一个大光比场景里直接用稠密光流做运动补偿,结果过曝区域的光流全是错的——因为亮度不变假设在过曝区直接失效。后来我加了「梯度幅值掩码」,只在高梯度区域计算光流,效果才正常。

3.2.2 光流法的常见问题

  • 大运动失效:光流法假设运动很小,运动太大时金字塔层数不够就崩了。我一般用 3-5 层金字塔,每层缩小 2 倍。
  • 纹理缺失:纯色区域没有梯度,光流算不出来。这时候需要结合全局模型做先验。
  • 遮挡问题:物体移开后露出的背景,光流是「无中生有」的。需要做双向光流 + 一致性检查。

3.3 混合模型:全局 + 局部

实战中,我几乎不用纯全局或纯局部模型。原因很简单:真实场景既有相机运动,又有物体运动。混合模型才是王道。

3.3.1 混合策略

我常用的混合流程:

  1. 全局对齐:用仿射或透视模型,把参考帧和待对齐帧做粗对齐
  2. 残差计算:对齐后的两帧做差,得到残差图
  3. 局部修正:在残差大的区域,用稠密光流做精细对齐
  4. 融合权重:根据运动置信度,决定用全局结果还是局部结果
我的经验:混合模型的关键在于「什么时候切到局部」。我一般设一个阈值:如果全局对齐后的残差中位数超过 5 个像素,就启动局部修正。这个阈值我是在一个无人机航拍数据集上调出来的,不同场景可能需要微调。

3.3.2 混合模型的实现框架

下面是我在一个项目里用过的混合模型框架,用伪代码表示:

def hybrid_motion_compensation(ref_img, target_img):
    # 1. 全局对齐
    global_params = estimate_affine(ref_img, target_img)
    warped_global = warp_affine(target_img, global_params)
    
    # 2. 计算残差
    residual = abs(ref_img - warped_global)
    
    # 3. 生成运动掩码
    motion_mask = residual > threshold  # 阈值自适应
    
    # 4. 局部修正(只在运动区域)
    if motion_mask.sum() > 0:
        local_flow = compute_dense_flow(ref_img, warped_global)
        warped_local = warp_flow(warped_global, local_flow)
    else:
        warped_local = warped_global
    
    # 5. 融合
    result = blend_global_local(warped_global, warped_local, motion_mask)
    
    return result

3.4 三种模型的对比与选择

模型 参数数量 计算量 运动类型 典型场景
仿射变换 6 低 旋转、平移、缩放 手持相机、三脚架微动
透视变换 8 中 透视变形 广角镜头、倾斜拍摄
稠密光流 2×像素数 高 任意局部运动 人物走动、树叶摇晃
混合模型 全局+局部 中高 全局+局部 大多数真实场景

你想想看,如果场景里只有相机在动,仿射模型就够了。但如果画面里有人在挥手,光靠全局模型肯定不行。反过来,如果相机在剧烈抖动,光流法又容易算飞。所以混合模型才是工程上的最优解。

3.5 本章知识体系

下面这张图总结了三种运动补偿模型的关系和适用场景:

运动补偿模型体系 运动补偿模型 全局运动模型 局部运动模型 混合模型 仿射变换 (6参数) 透视变换 (8参数) 稀疏光流 (LK) 稠密光流 (Farneback) 全局对齐 + 局部修正 置信度融合 适用场景速查 • 仿射/透视:相机运动为主,场景静止或刚性运动 • 光流法:局部运动为主,相机静止或已做全局对齐 • 混合模型:相机运动 + 局部运动同时存在(大多数真实场景) 核心原则:先全局后局部,根据残差自适应切换
核心总结:运动补偿没有银弹。仿射模型快但局限,光流法灵活但慢,混合模型是工程上的最佳折中。我建议你从混合模型入手,先跑通全局对齐,再逐步加入局部修正。这样既能保证速度,又能处理大多数真实场景。