3、图像预处理与对齐:全局运动估计、局部运动估计与去鬼影预处理
好,咱们进入第三章。前面两章我们把HDR成像的基础和噪声模型聊透了。这一章,是真正动手干活前的关键一步——图像对齐。
你想想看,手持拍摄运动场景,三帧之间手会抖,人会在画面里走动,树叶会被风吹动。如果不做对齐就直接合成,结果就是重影、模糊、边缘发虚。我当年第一次做HDR合成,偷懒没做对齐,结果合成出来的照片像毕加索的画——嗯,抽象派。从那以后,我再也不敢跳过预处理了。
3.1 为什么需要图像对齐?
HDR合成需要多帧不同曝光的图像。理想情况下,这些图像除了亮度不同,其他应该完全一样。但现实是:
- 相机运动:手持拍摄时的微小抖动
- 场景运动:行人、车辆、流水、树叶
- 视差变化:多帧之间存在透视差异
对齐的目的,就是把不同帧的像素映射到同一个坐标系下。说白了,就是让每一帧的同一个物体,出现在同一个像素位置。
核心原则:对齐精度直接影响HDR合成质量。对齐误差超过1个像素,鬼影就会肉眼可见。
3.2 全局运动估计
全局运动估计,适用于相机运动占主导的场景。比如你手持相机拍摄静态风景,画面整体平移或旋转。这时候我们用一个统一的变换模型来描述整幅图像的运动。
3.2.1 仿射变换
仿射变换是最常用的全局运动模型。它支持平移、旋转、缩放、剪切。数学上用一个2×3矩阵表示:
// 仿射变换矩阵
M = [a b tx]
[c d ty]
// 变换公式
x' = a*x + b*y + tx
y' = c*x + d*y + ty
仿射变换有6个自由度。计算时,我们至少需要3组匹配点对。实际项目中,我会用特征点匹配(比如ORB或SIFT)找到几十组点对,然后用RANSAC剔除误匹配,最后用最小二乘法拟合出最优的M矩阵。
我的经验:仿射变换对旋转角度不大的场景效果很好。但如果画面有较大透视变化(比如你从侧面拍一个平面),仿射就不够用了。
3.2.2 透视变换
透视变换(也叫单应性变换)比仿射更强大。它用3×3矩阵表示,有8个自由度。可以处理梯形畸变、视角变化。
// 透视变换矩阵
H = [h11 h12 h13]
[h21 h22 h23]
[h31 h32 h33]
// 变换公式(齐次坐标)
x' = (h11*x + h12*y + h13) / (h31*x + h32*y + h33)
y' = (h21*x + h22*y + h23) / (h31*x + h32*y + h33)
透视变换至少需要4组匹配点对。我建议用8组以上,配合RANSAC,效果更稳定。
| 变换类型 | 自由度 | 最少匹配点数 | 适用场景 |
|---|---|---|---|
| 平移 | 2 | 1 | 纯平移运动 |
| 刚体(旋转+平移) | 3 | 2 | 相机旋转 |
| 仿射 | 6 | 3 | 一般相机运动 |
| 透视 | 8 | 4 | 大视角变化 |
3.3 局部运动估计
全局运动模型搞不定局部运动。比如画面里有人在走路,你不能用一个矩阵把整个人都对齐。这时候就需要局部运动估计。
3.3.1 光流法
光流法计算每个像素的运动矢量。它假设:
- 亮度恒定:同一个点在相邻帧之间亮度不变
- 小运动:运动幅度不大
- 空间一致性:相邻像素运动相似
经典的Lucas-Kanade光流法,用局部窗口内的像素求解运动矢量。OpenCV里直接用calcOpticalFlowFarneback就能算稠密光流。
// OpenCV稠密光流示例
cv::Mat flow;
cv::calcOpticalFlowFarneback(prevGray, currGray, flow,
0.5, // 金字塔尺度
3, // 金字塔层数
15, // 窗口大小
3, // 迭代次数
5, // 多项式邻域
1.2, // 标准差
0); // 标志位
注意:光流法计算量大。1080p图像上跑稠密光流,一帧可能要几百毫秒。我一般先降采样到640×360再算,精度损失不大,速度能快5倍。
3.3.2 块匹配
块匹配是另一种局部运动估计方法。把图像分成小块,在参考帧的搜索窗口内找最相似的块。相似度用SAD(差和)或SSD(平方差和)衡量。
// 块匹配伪代码
for each block in current frame:
best_match = INF
best_offset = (0, 0)
for each offset in search_window:
sad = computeSAD(block, reference_block)
if sad < best_match:
best_match = sad
best_offset = offset
motion_vector[block] = best_offset
块匹配比光流快,但精度稍低。我个人的习惯是:
- 需要高精度时用光流法
- 需要实时处理时用块匹配
- 两者结合:先用块匹配做粗对齐,再用光流做精对齐
3.4 去鬼影预处理
对齐之后,还有个大问题——鬼影。鬼影是运动物体在不同曝光帧中位置不同导致的。即使做了对齐,运动物体本身还是对不齐的。
去鬼影预处理,就是在合成之前,先检测出运动区域,然后决定如何处理这些区域。
3.4.1 运动区域检测
常用的方法:
- 帧差法:两帧相减,值大于阈值的就是运动区域。简单粗暴,但对噪声敏感。
- 光流幅值法:计算光流,幅值大的区域就是运动区域。更准确,但计算量大。
- 一致性检测:用多帧之间的亮度关系判断。比如,过曝区域和正常曝光区域之间的亮度关系应该符合相机响应曲线,不符合的就是运动区域。
避坑指南:我曾经用帧差法检测运动,结果把树叶晃动也当成运动区域,导致合成结果出现大量空洞。后来改用光流幅值+形态学滤波,效果好了很多。
3.4.2 运动区域处理策略
检测到运动区域后,有三种处理方式:
| 策略 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 丢弃 | 运动区域只用参考帧 | 简单,无鬼影 | 可能丢失细节 |
| 融合 | 运动区域用多帧加权平均 | 保留细节 | 可能有轻微鬼影 |
| 替换 | 运动区域用单帧最佳曝光 | 无鬼影,细节好 | 需要选择最佳帧 |
我个人推荐第三种——替换策略。先找出运动区域,然后从多帧中选出曝光最合适的那一帧,把运动区域替换过去。这样既没有鬼影,又能保留细节。
3.5 本章知识体系
下面这张图,把本章的核心逻辑串起来了:
3.6 实战建议
最后,给几个实战中的小建议:
- 先全局后局部:先用仿射或透视做全局对齐,再用光流或块匹配做局部修正。这样效率高,精度也够。
- 多尺度策略:在低分辨率上做粗对齐,再在高分辨率上做精对齐。速度能提升3-5倍。
- 运动区域要膨胀:检测到运动区域后,用形态学膨胀操作扩大一点。否则边缘处容易残留鬼影。
- 别忘了中值滤波:对齐后的运动矢量图,用中值滤波平滑一下,能去除很多孤立的错误匹配。
我的习惯:每次做HDR合成前,我都会先跑一遍对齐质量评估。把对齐后的帧叠在一起,用差值图看看有没有明显的未对齐区域。这一步花不了几秒钟,但能省掉后面大量调试时间。