3. 全局运动估计:基于相位相关的全局配准方法

运动估计,说白了就是搞清楚两张图之间到底「动了多少」。在 HDR 合成里,这个步骤尤其关键——你想想看,手持拍摄的多帧曝光序列,怎么可能纹丝不动?哪怕上了三脚架,按快门那一下也会有微位移。

我最早做 HDR 时,用的是最朴素的模板匹配。后来发现,一旦遇到曝光差异大的帧对,亮度变化会直接把匹配结果带偏。直到我接触到相位相关这个方法,才算是找到了一个真正鲁棒的全局运动估计方案。

3.1 相位相关的核心思想

相位相关为什么能抗亮度变化?因为它根本不看像素值本身,而是看频域里的相位差。

两张图如果只有平移,它们的傅里叶变换幅度谱是一样的,差别只体现在相位上。具体来说:

  • 对参考图和待配准图分别做 FFT
  • 计算互功率谱的归一化形式
  • 对互功率谱做逆 FFT,得到一个脉冲函数
  • 脉冲峰值的位置,就是平移量

这个脉冲有多尖锐?理论上,如果图像内容完全一致且只有平移,你会看到一个完美的 Dirac 脉冲。实际中因为噪声和曝光差异,峰值会扩散一些,但依然非常明显。

关键优势:相位相关对光照变化、曝光差异几乎免疫。我在项目中测试过 ±3EV 的曝光差,峰值依然清晰可辨。

3.2 数学原理速览

这里我尽量不堆公式,但核心逻辑你得理解。假设参考图 \( f_1(x,y) \) 和待配准图 \( f_2(x,y) \) 满足:

f₂(x,y) = f₁(x - Δx, y - Δy)

傅里叶变换后:

F₂(u,v) = F₁(u,v) · e^{-j2π(uΔx + vΔy)}

互功率谱就是:

R(u,v) = (F₁(u,v) · conj(F₂(u,v))) / |F₁(u,v) · conj(F₂(u,v))|

逆变换后:

r(x,y) = δ(x - Δx, y - Δy)

嗯,就这么简单。你只需要找到 r(x,y) 的最大值位置,就能得到平移量。

3.3 算法实现步骤

我习惯把流程拆成 6 步,每一步都有坑要留意:

  1. 预处理:将输入图像转为灰度,并应用窗函数(如汉宁窗)抑制边缘效应。这一步不做,频域里会出现高频泄漏。
  2. FFT 变换:对两帧图像分别做快速傅里叶变换。建议使用浮点精度,避免量化误差。
  3. 计算互功率谱:按公式计算归一化互功率谱。注意分母加一个小常数防止除零。
  4. 逆 FFT:对互功率谱做逆变换,得到相关面。
  5. 峰值检测:找到相关面的最大值坐标。如果峰值不够突出(低于阈值),说明运动模型可能不适用。
  6. 亚像素细化:对峰值邻域做二次曲面拟合,获得亚像素精度的位移。
我的经验:亚像素细化这一步,很多人会忽略。但 HDR 合成对对齐精度要求很高,整数像素的误差就会在边缘产生鬼影。我通常用 3×3 邻域做抛物面拟合,精度能到 0.1 像素。

3.4 代码示例:OpenCV 实现

下面是我常用的一个实现片段。注意,OpenCV 自带的 phaseCorrelate 函数已经封装好了,但了解底层实现还是有必要的:

import cv2
import numpy as np

def phase_correlation(img1, img2):
    # 1. 灰度转换
    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
    
    # 2. 应用汉宁窗
    h, w = gray1.shape
    hanning = np.outer(np.hanning(h), np.hanning(w))
    win1 = gray1 * hanning
    win2 = gray2 * hanning
    
    # 3. FFT
    f1 = np.fft.fft2(win1)
    f2 = np.fft.fft2(win2)
    
    # 4. 互功率谱
    cross = f1 * np.conj(f2)
    eps = 1e-10
    norm_cross = cross / (np.abs(cross) + eps)
    
    # 5. 逆FFT
    result = np.fft.ifft2(norm_cross)
    result = np.abs(result)
    
    # 6. 峰值检测
    max_loc = np.unravel_index(np.argmax(result), result.shape)
    
    # 7. 亚像素细化(抛物面拟合)
    y, x = max_loc
    if 1 < y < h-1 and 1 < x < w-1:
        dx = (result[y, x+1] - result[y, x-1]) / (2 * result[y, x])
        dy = (result[y+1, x] - result[y-1, x]) / (2 * result[y, x])
    else:
        dx, dy = 0, 0
    
    # 8. 处理中心偏移
    shift_x = x - w//2 + dx
    shift_y = y - h//2 + dy
    
    return shift_x, shift_y
注意:FFT 产生的位移是循环移位,需要做中心偏移校正。另外,如果图像尺寸不是 2 的幂次,FFT 效率会下降,建议提前做 padding 到合适的尺寸。

3.5 相位相关的局限性

没有银弹。相位相关虽然强大,但也有它的软肋:

局限 原因 应对策略
仅支持平移模型 相位相关假设纯平移,对旋转、缩放不敏感 结合对数极坐标变换处理旋转缩放
对周期性纹理敏感 周期性图案会产生多个峰值 增加峰值对比度阈值,或结合空间域验证
图像内容过少时失效 平坦区域缺乏高频信息 检测图像梯度能量,低于阈值时改用其他方法
大位移时精度下降 位移超过图像尺寸的 1/4 时,相关面变模糊 采用图像金字塔,由粗到细逐步估计

我曾经在一个夜景 HDR 项目里吃过亏——画面大部分是黑色天空,只有零星灯光。相位相关直接给出了一个离谱的位移。后来我加了一步预处理:先计算图像的梯度图,再用梯度图做相位相关,效果好了很多。

3.6 知识体系图

下面这张图总结了全局运动估计中相位相关的完整知识脉络:

相位相关全局运动估计知识体系 相位相关全局运动估计知识体系 相位相关核心原理 输入:参考帧 + 待配准帧 输出:平移量 (Δx, Δy) 预处理(灰度+窗函数) FFT变换 互功率谱计算 逆FFT 峰值检测 亚像素细化 局限性:仅平移模型 | 周期性纹理敏感 | 平坦区域失效 | 大位移精度下降 对数极坐标(旋转缩放) 图像金字塔(大位移) 梯度预处理(平坦区域)

3.7 工程实践建议

最后,分享几个我在实际项目中积累的经验:

  • 先做质量检查:计算相关面的峰值与次峰值之比,如果比值小于 1.5,说明匹配不可靠,建议跳过这一帧。
  • 多帧一致性校验:在 HDR 序列中,相邻帧之间的位移应该是平滑变化的。如果某帧的位移突然跳变,大概率是误匹配。
  • 结合空间域验证:用相位相关得到初值后,可以在小范围内用 NCC(归一化互相关)做精匹配,两者互补效果更好。
避坑指南:我曾经在 4K 分辨率下直接做全图相位相关,速度慢得让人崩溃。后来改成先降采样到 512×512 做粗估计,再在原分辨率下做局部精匹配,速度提升了 10 倍以上,精度几乎没有损失。

相位相关不是万能的,但在 HDR 全局运动估计这个场景下,它确实是最优雅、最鲁棒的方案之一。掌握了它,你就解决了 HDR 合成中 80% 的对齐问题。