视觉里程计(1)
数据集链接:Karlsruhe Dataset: Stereo Video Sequences + rough GPS Poses
github代码 :
https://github.com/srv/viso2
https://github.com/BrainSpawnInfosphere/libviso2
libviso2【转载】
libviso
一直以来被称为在视觉里程计(VO)中的老牌开源算法。它通过corner,chessboard
两种kernel的响应以及非极大值抑制的方式提取特征,并用sobel
算子与原图卷积的结果作为特征点的描述子。在位姿的计算方面,则通过RANSAC迭代的方式,每次迭代随机抽取3个点,根据这三个点,用高斯牛顿法计算出一个RT矩阵,表示两帧图像之间,相机的姿态变换。而位姿的计算也是libviso
中较为抽象的一部分,接下来,本文将在读者已经对立体视觉的基本原理,以及libviso
的场景流匹配熟悉的前提下,对这个过程进行详细分析。
1 运动描述
在libviso
的实际位姿计算过程中,实质上是通过含有6个变量的向量
T={rx,ry,rz,tx,ty,tz}
来表示位姿变换的:
其中 rx,ry,rz 和 tx,ty,tz 分别表示两帧之间相机绕 x,y,z 轴之间的旋转和平移。计算出这6个变量之后,再转换成描述两帧之间位置变化的 R|t 矩阵。
2 位姿计算过程
在位姿计算的过程中,输入的是n组通过场景流匹配得到的二维坐标点,每组4个:
(u1c,v1c),(u1p,v1p),(u2c,v2c),(u1p,v1p),
分别代表左图、右图,当前时刻,上一时刻图像中的匹配点(这里用了与代码中相同的符号表示,1下标代表左图,2代表右图,c代表上一阵,p代表当前帧),
(X1c,Y1c,Z1c),(X1p,Y1p,Z1p),(X2c,Y2c,Z2c),(X1p,Y1p,Z1p)
是其对应的三维坐标,根据立体视觉的原理,三维坐标可以通过匹配点坐标结合相机内参数算出。输出是1中描述的6个变量。这6个变量是通过RANSAC
迭代,在每次迭代中都从匹配点中随机抽取3个点,基于这3个点,通过高斯牛顿法的方式求出来的。计算出一次迭代中的参数之后,利用这个参数计算出局内点(inlier)
的占比。最终取占比最高的参数,得到结果。下面将对每次迭代中进行的操作细节进行分析。
2.1 参数的更新
在每次迭代中,参数是通过梯度下降的方式求出来的。而高斯牛顿实质上也是一种通过迭代求解的方式。位姿解算的过程,可以看成是在RANSAC迭代中,再嵌套了一个迭代求解过程。高斯牛顿法计算的过程中,主要的计算工作包括两步:残差以及雅克比的计算,参数的迭代。
2.1.1 残差以及雅克比的计算
假设在当前的梯度下降迭代(第i次)中,参数的值为
Ti=rxi,ryi,rzi,txi,tyi,tzi
,当前的迭代,是根据RANSAC中抽取的3个点,更新这6个参数的值,使之更加接近正确解。
对于rxi,ryi,rzi首先,计算这三个量对应的旋转矩阵R,如下所示(为了简便,以下x指代rx,y与z 以此类推)
Rx=⎡⎣⎢1000cos(x)sin(x)0−sin(x)cos(x)⎤⎦⎥
Ry=⎡⎣⎢cos(y)0−sin(y)010sin(y)0cos(y)⎤⎦⎥
Rz=⎡⎣⎢cos(z)sin(z)0−sin(z)cos(z)0001⎤⎦⎥
R=Rx∗Ry∗Rz=[cos(y)cos(z)cos(x)sin(z)+cos(z)sin(x)sin(y)sin(x)sin(z)−cos(x)cos(z)sin(y)−cos(y)sin(z)cos(x)cos(z)−sin(x)sin(y)sin(z)cos(z)sin(x)+cos(x)∗sin(y)∗sin(z)sin(y)−cos(y)sin(x)cos(x)cos(y)]
然后分别计算R中关于x,y,z的偏导数,如下所示:


假设X1p,Y1p,Z1p为左图匹配点对应的三维坐标,根据当前的参数R,tx,ty,tz,可以计算出在目前参数下,(X1c,Y1c,Z1c)的估计值:
⎡⎣⎢X1cY1cZ1c⎤⎦⎥=R∗⎡⎣⎢X1pY1pZ1p⎤⎦⎥+⎡⎣⎢txtytz⎤⎦⎥
根据对极几何原理,(X2c,Y2c,Z2c),可以通过以下方式求得
⎡⎣⎢X2cY2cZ2c⎤⎦⎥=⎡⎣⎢X1c−bY1cZ1c⎤⎦⎥
其中b为立体相机基线长度。将(X1c,Y1c,Z1c),(X2c,Y2c,Z2c) 变换回对应的二维坐标的过程,称为重投影,具体计算方式如下:
u=fXZ
v=fYZ
给定一个重投影后的二维坐标点(u,v),其关于Ti的雅克比矩阵的计算方式如下:
Ju,v|T=⎡⎣∂u∂rx∂v∂rx∂u∂ry∂v∂ry∂u∂rz∂v∂rz∂u∂tx∂v∂tx∂u∂ty∂v∂ty∂u∂tz∂v∂tz⎤⎦
其中,
∂u∂rx=ZXrx−XZrxZ2
v关于rx的偏导数以此类推Xrx表示的是X在rx方向上的偏导数,X,Y,Z为当前帧下的三维点坐标(即X1c,Y1c,Z1c或X2c,Y2c,Z2c),
通过上一帧的三维点以上述的公式计算可得,而其偏导数则通过下面的公式计算:
⎡⎣⎢XrxYrxZrx⎤⎦⎥=⎡⎣⎢⎢⎢⎢∂Xc∂rx∂Yc∂rx∂Zc∂rx⎤⎦⎥⎥⎥⎥=∂(R∗⎡⎣⎢⎢XpYpZp⎤⎦⎥⎥+⎡⎣⎢⎢txtytz⎤⎦⎥⎥)∂rx=∂R∂rx∗⎡⎣⎢XpYpZp⎤⎦⎥
ry,rz的偏导数以此类推,由上述关于tx偏导数的表达式,可得:
⎡⎣⎢XtxYtxZtx⎤⎦⎥=⎡⎣⎢100⎤⎦⎥
基于上述计算方式,可以算出抽样得到的三组点关于(u1c,v1c)以及(u2c,v2c)的雅可比矩阵(对于每一组来说,都用一个4∗6的矩阵来表示)。接下来,还需要计算残差,供最优化T使用
对于一组点,残差实质上就是(u,v)的观测值(匹配点坐标)与其重投影后的坐标的差值,再乘以权重:
r=w∗[(u,v)−(u^,v^)]
乘上权重w是为了减少标定参数不准确带来的误差,远离相机主点的特征点会赋予更低的权值,具体计算方式为:
w=cu|u−cu|
2.1.2 高斯牛顿迭代
在libviso
的迭代过程中使用的高斯分布,实际上用了一个小技巧:将二次偏导数省略,只通过雅克比矩阵来进行迭代。通过计算三组点中的雅克比矩阵,最终,我们可以得到这样的一个矩阵:
J=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢∂u1c1∂rx∂u1c1∂ry∂u1c1∂rz∂u1c1∂tx∂u1c1∂ty∂u1c1∂tz∂v1c1∂rx∂v1c1∂ry∂v1c1∂rz∂v1c1∂tx∂v1c1∂ty∂v1c1∂tz∂u2c1∂rx∂u2c1∂ry∂u2c1∂rz∂u2c1∂tx∂u2c1∂ty∂u2c1∂tz∂v2c1∂rx∂v2c1∂ry∂v2c1∂rz∂v2c1∂tx∂v2c1∂ty∂v2c1∂tz⋯ ⋯ ⋯ ⋯ ⋯ ⋯ ∂u1cN∂rx∂u1cN∂ry∂u1cN∂rz∂u1cN∂tx∂u1cN∂ty∂u1cN∂tz∂v1cN∂rx∂v1cN∂ry∂v1cN∂rz∂v1cN∂tx∂v1cN∂ty∂v1cN∂tz∂u2cN∂rx∂u2cN∂ry∂u2cN∂rz∂u2cN∂tx∂u2cN∂ty∂u2cN∂tz∂v2cN∂rx∂v2cN∂ry∂v2cN∂rz∂v2cN∂tx∂v2cN∂ty∂v2cN∂tz⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥
N为抽样的点个数(3),令
A=JJT
b⃗ =Jr⃗
,其中r⃗ 为残差组成的向量:
r⃗ =⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢r1c1r2c1r1c2⋮r1cNr2cN⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥
最后,高斯牛顿法通过下面的公式,进行T的迭代:
T(i+1)=T(i)+A−1r⃗
往复迭代,直到T收敛
|T(i+1)−T(i)|<ε
2.2 最优位姿选取
从匹配点中选取3个,并通过高斯牛顿法,求出位姿T,即完成了一次RANSAC迭代。然而,每次迭代之后,我们都要判断计算出来的T的精确度。在libviso
中,精确度是通过局内点(inlier)的个数来衡量的,若通过当前的位姿T
求得的局内点更多,就将这个位姿替换成当前最好的位姿。
局内点的计算方式如下:先计算出(u^,v^),随后,满足下式的点即为局内点:
∑i∈1c,2c(ui^−ui)2+(vi^−vi)2<t
其中t为人为设定的参数。
经过多次RANSAC迭代,最终能够快速鲁棒地找到两帧之间的位姿变换。这里的位姿变换是通过T
来表征的,而最终需要换算到R|t矩阵,所需要的公式在此不再赘述。
3 小结
本文详细地描述了在libviso
中用到的位姿求解的过程。其主要思路是利用随机抽样,每次从匹配点中取出3组,进行高斯牛顿迭代,求出基于这3组点的位姿,再通过内点判断这个位姿的精确度,多次抽样——迭代后,得到一个准确的位姿。抽样能够能够加快高斯牛顿法的收敛速度,而RANSAC
的思路则保证了这种抽样的鲁棒性,降低了动态点对算法的影响,是一种值得借鉴的思路。
libviso
的project主页
位于
http://www.cvlibs.net/software/libviso/
,另外,我将其中的位姿求解模块单独抽取出来,供读者参考:
https://github.com/RichardChe/libviso_pose_estimation