4、直接法前端:光流法原理、直接法跟踪、半直接法(SVO)、边缘特征与线特征
好,咱们进入第四章。这一章聊的是直接法前端。
说实话,特征点法虽然稳定,但有个硬伤——它只用了图像里很少的信息。那些纹理弱的墙面、白茫茫的走廊,特征点法基本就废了。直接法不一样,它直接用像素亮度,说白了就是「看整张图」。我当年在实验室跑数据集,特征点法在纹理稀疏的楼道里直接崩了,换成直接法反而能撑住。嗯,这就是它的价值。
4.1 光流法原理
光流法,是直接法的基石。它的核心假设很简单:同一个空间点,在相邻两帧图像里的亮度不变。
数学上写出来就是:
I(x, y, t) = I(x+dx, y+dy, t+dt)
左边是t时刻的像素亮度,右边是t+dt时刻移动后的亮度。我们假设它们相等。
然后做一阶泰勒展开,忽略高阶项,得到:
∂I/∂x * dx/dt + ∂I/∂y * dy/dt + ∂I/∂t = 0
这就是光流约束方程。两个未知数(dx/dt, dy/dt),一个方程,解不了。怎么办?加约束。
Lucas-Kanade光流法的做法是:假设一个小窗口内所有像素的运动相同。这样就有多个方程,用最小二乘解。
我个人习惯用OpenCV的calcOpticalFlowPyrLK,它自带金字塔,能处理大位移。我在项目中遇到过一个问题:如果图像有运动模糊,LK光流很容易跟丢。后来我加了图像锐化预处理,效果好了不少。
4.2 直接法跟踪
直接法跟踪,不提取特征,不计算描述子。它直接优化像素亮度误差。
假设我们有一个3D点P,它在参考帧的像素位置是u1,在当前帧的投影位置是u2。理想情况下,这两个位置的亮度应该一样。但实际上有差异,这个差异就是光度误差:
e = I1(u1) - I2(u2)
我们要优化的是相机位姿T,让所有点的光度误差之和最小。
这其实是一个非线性最小二乘问题。用高斯牛顿法或者LM算法求解。雅可比矩阵怎么算?链式法则:
∂e/∂T = ∂I2/∂u2 * ∂u2/∂P * ∂P/∂T
第一项是图像梯度,第二项是投影几何,第三项是位姿变换。
直接法有个好处:它不需要匹配特征点,所以计算量小。但坏处也很明显——它对初始位姿很敏感。你想想看,如果初始位姿差太远,投影点对不上,误差函数全是坑,优化直接掉进局部最优。
4.3 半直接法(SVO)
SVO,全称Semi-direct Visual Odometry。它是个混合体——既用特征点,又用直接法。
为什么叫半直接?因为它只在特征点周围用直接法匹配,而不是全图都用。
SVO的工作流程大致分三步:
- 稀疏图像对齐:用直接法匹配上一帧的特征块到当前帧,得到粗略位姿。
- 特征对齐:用光流法精匹配每个特征点的位置。
- 位姿与结构优化:用匹配好的2D-3D点对做BA优化。
我特别喜欢SVO的一点是它速度快。因为它只在少数特征点上做直接法,不像纯直接法那样要处理所有像素。我在无人机平台上跑过SVO,在嵌入式设备上能跑到30帧以上。
但SVO也有短板。它依赖特征点,如果场景纹理太少,特征点不够,它也会挂。说白了,它继承了特征点法和直接法各自的缺点。
4.4 边缘特征与线特征
前面说的都是点特征。但点特征在弱纹理场景下不够用。这时候,边缘和线特征就派上用场了。
边缘特征:用Canny或Sobel提取边缘点,然后在这些点上做直接法匹配。边缘点的梯度大,光度误差的雅可比矩阵非零,优化能正常进行。
我在项目中遇到过一个问题:边缘点太多,计算量太大。后来我做了下采样,只保留梯度最大的那些边缘点,效果差不多,速度翻倍。
线特征:用LSD算法提取线段,然后用LBD描述子匹配。线特征对光照变化不敏感,而且能提供结构信息——比如墙角、门框这些。
线特征的匹配比点特征复杂。因为线段有长度和方向,不能简单用欧氏距离。一般用几何约束:
- 两条线的夹角要小
- 两条线的距离要近
- 两条线的重叠长度要够
我建议在结构化场景(比如室内、城市)里优先用线特征。在自然场景(比如森林、山地)里,线特征很少,还是老老实实用点特征。
| 特征类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 点特征 | 成熟、稳定 | 弱纹理失效 | 纹理丰富场景 |
| 边缘特征 | 梯度大、计算快 | 边缘点太多需筛选 | 弱纹理、结构场景 |
| 线特征 | 光照鲁棒、结构信息 | 匹配复杂 | 室内、城市 |
4.5 本章知识体系
下面这张图总结了直接法前端的核心逻辑:
直接法前端,说白了就是「不用特征点也能跟踪」。光流法是基础,直接法跟踪是核心,SVO是折中方案,边缘和线特征是锦上添花。你在实际项目中,可以根据场景灵活组合。
嗯,这一章就到这。下一章我们聊后端优化——图优化与BA。