本项目是由我和leeayu共同完成,历时一个半月时间,采用的框架是VINS-mono,开源代码如下:
VINS-Mono-Optimization
运行结果视频
VINS-Mono点线紧耦合优化视频
下面主要从四个方面进行技术报告。
对于课题要求我们先是进行了大量的文献调研,调研结果一共有如下三种方案。
这是2014年的一篇文章[1],框架的思路大致是利用超像素作为前端,采用优化的方式进行匹配。因为超像素是像素的集合,对于低纹理环境拥有较好的描述,因此可以用在低纹理环境下。这是论文的视频链接,我们认为这种思路只是理论上可行,在论文的视频里可以看出来,运行的环境其实是有限的而且非常慢,因此我们首先排除掉了这种方案。
这里主要是2017年的一篇文章[2][3],算法的思路大致是不是角点比较少嘛,但是肯定有边的呀,没有角点我们跟踪边界点就好了嘛,因此文章就采用了一种较为鲁棒的的边界点提取方式,然后采用光流法进行跟踪,看上去合情合理,视频链接看上去效果也不错,因此我们第一步先实施了这种方案,但是效果并不理想,进而我们转战PL-SLAM。
点线结合的SLAM相关文章由很多,我们主要参考的是贺博在2018年的PL-VIO[4]和谢晓佳师兄的毕业硕士论文[5],实施的具体细节在后文中进行展开,我们最后提交的就是这个方案实施的代码。
上文提到,我们主要对Edge SLAM和PL SLAM两种方案进行了实施,因此我们把两部分的工作都介绍下,其中Edge SLAM的方案因为最后没有采用所以就进行简述
我们刚开始是希望寻找一种简便的方式完成任务,Edge SLAM的方案只需要修改前端,后端完全不用改,工作量相对较小(事实证明我们想多了,移植算法也花了不少时间,关键效果还不好),论文中对于算法是这样描述的:
Our proposed Edge SLAM pipeline detects edge points from images and tracks those using optical flow for point correspondence. We further refine these point correspondences using geometrical relationship among three views.
大致意思就是说先对边界点进行提取,然后采用光流法进行跟踪,采用三视图的方法进行外点的剔除,其中对边界点的提取方法如下:
e find the DoG based edge detector is reliable due to its robustness in illumination and contrast changes. We thin the edges further to generate edges of a single pixel width. We apply an edge filtering process described by Juan and Sol upon the thinned edges to calculate connectivity of the edge points.
采用高斯差分算子进行提取,然后通过滤波的方式进行边缘细化,使得每条边界只有一个像素点宽。
当我们按照论文中的方式实施算法之后发现,进行光流跟踪之后匹配的边界点会沿着边界进行滑移,由于这种滑移会使得边界点在外点剔除后数量急剧减少,因此导致边界点过少而跟踪失败。
我们对此进行了原因分析,我们的结论是:因为光流法假设的是光度不变性,而边界上的点都会具有相似的光度,因此光流法跟踪会使得匹配的边界点沿着边界进行滑移,这是光流法的原理导致的问题,我们并不清楚原论文中采用了怎样的trick解决的这个问题,再我们开会讨论之后决定放弃这种方案,转战点线特征结合的SLAM。
我们整个的方案实施实在VINS-mono上完成的,关于VINS-mono的介绍我就不再赘述了,这里主要说明下线特征是如何添加到VINS-mono中的。
线特征的提取和描述采用的是LSD提取算法+LBD描述子的经典方案,关于这两者的细节也不在此展开,我们调用的是OpenCV库里面的接口。
线特征的更踪和点特征稍由不同的是我们提取的是每一帧图片上的线特征,然后进行前后帧匹配,以达到更踪的效果,而不是像点特征那样采用光流法更踪。
描述线特征的是线段的两个端点,我们通过将这两个端点投影到归一化平面上,然后和点特征一起发布到同一个topic上,在后端进行进一步处理, 下图是线特征跟踪的效果图,其中颜色越红代表更踪的时间越长。
在点特征传到后端之后,首先是将点特征放入FeatureManager类中进行特征管理和三角化,同样的,我们实现了一个LineFeatureManager类,实现的函数和FeatureManager类中基本是一致的,只是采用的方法不同。其中值得说明的是如下两点:
直线三角化的方法:我们采用的通过匹配线的两端点以及相机坐标系的原点构建一个平面,然后观测到特征线相距最远的两帧下的两个平面相交所获得的直线就是两匹配线段构成的空间直线,原理如下图所示:
直线特征的维护方式,上面通过直线三角化出来的是普吕克矩阵 L ∗ = [ [ d ] × n − n ⊤ 0 ] \mathbf{L}^{*}=\left[\begin{array}{cc}{[\mathbf{d}]_{ \times}} & {\mathbf{n}} \\ {-\mathbf{n}^{\top}} & {0}\end{array}\right] L∗=[[d]×−n⊤n0]我们可以非常方便地从中提取线特征的普吕克坐标 d \mathbf{d} d和 n \mathbf{n} n。普吕克坐标方便用来进行空间变换,但是不方便用来进行优化后更新,因此我们在程序中采用的维护方式是在LineFeatureManager类中维护线特征的正交表示形式,以一个五维向量进行存储,方便后端优化更新时可以直接进行更新,稍微不方便的是在进行空间变换的时候再将其转化为原始的普吕克坐标形式才进行变换。
普吕克坐标的原始表示形式为 L = ( n ⊤ , d ⊤ ) ⊤ \mathcal{L}=\left(\mathbf{n}^{\top}, \mathbf{d}^{\top}\right)^{\top} L=(n⊤,d⊤)⊤,其中 n \mathbf{n} n和 d \mathbf{d} d是和空间直线相关的两个向量,可以通过下图理解:
因为我们维护的线特征在世界坐标系下的普吕克坐标,因此我们想求得线特征的参差的话只需要将线特征的空间坐标投影到某一观测帧的归一化平面上(估计值),然后通过这帧上观测值(线段的两端点),按照下面方式定义线特征的残差: r l ( z L l ′ c i , X ) = [ d ( s l c i , l l c i ) d ( e l c i , l l c i ) ] \mathbf{r}_{l}\left(\mathbf{z}_{\mathcal{L}_{l}^{\prime}}^{c_{i}}, \mathcal{X}\right)=\left[\begin{array}{l}{d\left(\mathbf{s}_{l}^{c_{i}}, \mathbf{l}_{l}^{c_{i}}\right)} \\ {d\left(\mathbf{e}_{l}^{c_{i}}, \mathbf{l}_{l}^{c_{i}}\right)}\end{array}\right] rl(zLl′ci,X)=[d(slci,llci)d(elci,llci)]其中 d ( s , l ) = s ⊤ 1 l 1 2 + l 2 2 d(\mathbf{s}, \mathbf{l})=\frac{\mathbf{s}^{\top} \mathbf{1}}{\sqrt{l_{1}^{2}+l_{2}^{2}}} d(s,l)=l12+l22s⊤1其中 l \mathbf{l} l为空间线特征投影到归一化平面的直线, e l c i \mathbf{e}_{l}^{c_{i}} elci和 e l e i \mathbf{e}_{l}^{e_{i}} elei分别为观测值的两端点。
然后我们定义和线特征相关的优化变量为:世界坐标系下的线特征的普吕克坐标的正交表示形式 O l \mathcal{O}_{l} Ol和对应观测帧的空间位姿 x i \mathcal{x}^{i} xi。
雅克比矩阵对优化变量的雅克比矩阵如下: J l = ∂ r l ∂ l c i ∂ l c i ∂ L c i [ ∂ L c i ∂ δ x i ∂ L c i ∂ L w ∂ L w ∂ δ O ] \mathbf{J}_{l}=\frac{\partial \mathbf{r}_{l}}{\partial \mathbf{l}^{c_{i}}} \frac{\partial \mathbf{l}^{c_{i}}}{\partial \mathcal{L}^{c_{i}}}\left[\frac{\partial \mathcal{L}^{c_{i}}}{\partial \delta \mathbf{x}^{i}} \quad \frac{\partial \mathcal{L}^{c_{i}}}{\partial \mathcal{L}^{w}} \frac{\partial \mathcal{L}^{w}}{\partial \delta \mathcal{O}}\right] Jl=∂lci∂rl∂Lci∂lci[∂δxi∂Lci∂Lw∂Lci∂δO∂Lw]其中 ∂ r l ∂ l c i = [ − l 1 ( s l c i ) ⊤ 1 ( l 1 2 + l 2 2 ) ( 3 2 ) + u s ( l 1 2 + l 2 2 ) ( 1 2 ) − l 2 ( s l c i ) ⊤ 1 ( l 1 2 + l 2 2 ) ( 3 2 ) + v S ( l 1 2 + l 2 2 ) ( 1 2 ) 1 ( l 1 2 + l 2 2 ) ( 1 2 ) − l 1 ( e l c i ) ⊤ 1 ( l 1 2 + l 2 2 ) ( 3 2 ) + u e ( l 1 2 + l 2 2 ) ( 1 2 ) − l 2 ( e l c i ) ⊤ 1 ( l 1 2 + l 2 2 ) ( 3 2 ) + v e ( l 1 2 + l 2 2 ) ( 1 2 ) 1 ( l 1 2 + l 2 2 ) ( 1 2 ) ] 2 × 3 \frac{\partial \mathbf{r}_{l}}{\partial \mathbf{l}^{c_{i}}}=\left[\begin{array}{ccc}\frac{-l_{1}\left(\mathbf{s}_{l}^{c_{i}}\right)^{\top} 1}{\left(l_{1}^{2}+l_{2}^{2}\right)^{\left(\frac{3}{2}\right)}}+\frac{u_{s}}{\left(l_{1}^{2}+l_{2}^{2}\right)^{\left(\frac{1}{2}\right)}} & \frac{-l_{2}\left(\mathbf{s}_{l}^{c_{i}}\right)^{\top} \mathbf{1}}{\left(l_{1}^{2}+l_{2}^{2}\right)^{\left(\frac{3}{2}\right)}}+\frac{v_{S}}{\left(l_{1}^{2}+l_{2}^{2}\right)^{\left(\frac{1}{2}\right)}} & \frac{1}{\left(l_{1}^{2}+l_{2}^{2}\right)^{\left(\frac{1}{2}\right)}} \\ \frac{-l_{1}\left(\mathbf{e}_{l}^{c_{i}}\right)^{\top} 1}{\left(l_{1}^{2}+l_{2}^{2}\right)^{\left(\frac{3}{2}\right)}}+\frac{u_{e}}{\left(l_{1}^{2}+l_{2}^{2}\right)^{\left(\frac{1}{2}\right)}} & \frac{-l_{2}\left(\mathbf{e}_{l}^{c_{i}}\right)^{\top} 1}{\left(l_{1}^{2}+l_{2}^{2}\right)^{\left(\frac{3}{2}\right)}}+\frac{v_{e}}{\left(l_{1}^{2}+l_{2}^{2}\right)^{\left(\frac{1}{2}\right)}} & \frac{1}{\left(l_{1}^{2}+l_{2}^{2}\right)^{\left(\frac{1}{2}\right)}}\end{array}\right]_{2 \times 3} ∂lci∂rl=⎣⎢⎢⎢⎡(l12+l22)(23)−l1(slci)⊤1+(l12+l22)(21)us(l12+l22)(23)−l1(elci)⊤1+(l12+l22)(21)ue(l12+l22)(23)−l2(slci)⊤1+(l12+l22)(21)vS(l12+l22)(23)−l2(elci)⊤1+(l12+l22)(21)ve(l12+l22)(21)1(l12+l22)(21)1⎦⎥⎥⎥⎤2×3 ∂ l c i ∂ L c i = [ I 0 ] 3 × 6 \frac{\partial \mathbf{l}^{c_{i}}}{\partial \mathcal{L}^{c_{i}}}=[\mathcal{I} \quad \mathbf{0}]_{3 \times 6} ∂Lci∂lci=[I0]3×6 ∂ r l ∂ l c i = [ T b c − 1 [ R w b ⊤ [ d w ] × 0 3 × 3 ] T b c − 1 [ [ R w b ⊤ ( n w + [ d w ] × p w b ) ] × ] [ R w b ⊤ d w ] × ] 0 0 0 ] 6 × 15 \frac{\partial \mathbf{r}_{l}}{\partial \mathbf{l}^{c_{i}}}=\left[\begin{array}{ccc} \mathcal{T}_{b c}^{-1}\left[\begin{array}{c}{\mathbf{R}_{w b}^{\top}\left[\mathbf{d}^{w}\right] \times} \\ {\mathbf{0}_{3 \times 3}}\end{array}\right] & \mathcal{T}_{b c}^{-1}\left[\begin{array}{c}{\left[\mathbf{R}_{w b}^{\top}\left(\mathbf{n}^{w}+\left[\mathbf{d}^{w}\right] \times \mathbf{p}_{w b}\right)\right] \times ]} \\ {\left[\mathbf{R}_{w b}^{\top} \mathbf{d}^{w}\right]_{ \times}}\end{array}\right] & \mathbf{0} & \mathbf{0} & \mathbf{0}\end{array}\right]_{6 \times 15} ∂lci∂rl=[Tbc−1[Rwb⊤[dw]×03×3]Tbc−1[[Rwb⊤(nw+[dw]×pwb)]×][Rwb⊤dw]×]000]6×15 ∂ L c i ∂ L w ∂ L w ∂ δ O = T w c − 1 [ 0 − w 1 u 3 w 1 u 2 − w 2 u 1 w 2 u 3 0 − w 2 u 1 w 1 u 2 ] 6 × 4 \frac{\partial \mathcal{L}^{c_{i}}}{\partial \mathcal{L}^{w}} \frac{\partial \mathcal{L}^{w}}{\partial \delta \mathcal{O}}=\mathcal{T}_{w c}^{-1}\left[\begin{array}{cccc}{\mathbf{0}} & {-w_{1} \mathbf{u}_{3}} & {w_{1} \mathbf{u}_{2}} & {-w_{2} \mathbf{u}_{1}} \\ {w_{2} \mathbf{u}_{3}} & {\mathbf{0}} & {-w_{2} \mathbf{u}_{1}} & {w_{1} \mathbf{u}_{2}}\end{array}\right]_{6 \times 4} ∂Lw∂Lci∂δO∂Lw=Twc−1[0w2u3−w1u30w1u2−w2u1−w2u1w1u2]6×4其中,因为我们程序中观测值为归一化平面上的坐标点,因此 ∂ l c i ∂ L c i \frac{\partial \mathbf{l}^{c_{i}}}{\partial \mathcal{L}^{c_{i}}} ∂Lci∂lci中第一项为单位阵,如果是图像平面上的点的话第一项为内参矩阵。在程序中我们写了一个LineProjectionFactor类来玩成这项工作。
在完成非线性优化之后就需要对优化变量进行更新,优化变量的更新方式在贺博的文章中是按照下面方式进行更新的 U ′ ≈ U ( I + [ δ ψ ] x ) W ′ ≈ W ( I + [ 0 − δ ϕ δ ϕ 0 ] ) \begin{aligned} \mathbf{U}^{\prime} & \approx \mathbf{U}\left(\mathbf{I}+[\delta \psi]_{x}\right) \\ \mathbf{W}^{\prime} & \approx \mathbf{W}\left(\mathbf{I}+\left[\begin{array}{cc}{0} & {-\delta \phi} \\ {\delta \phi} & {0}\end{array}\right]\right) \end{aligned} U′W′≈U(I+[δψ]x)≈W(I+[0δϕ−δϕ0])但是经过我们讨论,和论文中不同的是,我们对于 U \mathbf{U} U采用的是四元数的更新方式,而 W \mathbf{W} W采用的是 ϕ + δ ϕ \phi + \delta\phi ϕ+δϕ的更新方式,这一部分工作在LineLocalParameterization类中。
对于线特征的变换化操作和点特征有所不同的是,因为点特征的深度是建立在起始帧的坐标系上的,点特征边缘化的时候需要将起始帧为最老帧上的点特征的深度从最老帧的坐标系转移到次老帧坐标系,而在线特征中,因为我们建立的线特征是在世界坐标系下,因此不需要进行上述操作,相对来说要简单一些,线特征的相关操作在LineFeatureManager类中。
由于我们始终没能拿到官方要求的弱纹理环境测试数据集,因此我们在EUROC数据集上测试了我们的算法,并拿开源版本的VINS-mono在同一台机器上进行了对比,我们使用的评价工具是开源工具evo[6],对比内容是RMSE误差,结果如下:
RMSE
|
VINS-Mono | PL VINS-Mono |
---|---|---|
MH_01_easy | 0.257660 | 0.252635 |
MH_02_easy | 0.269083 | 0.254644 |
MH_04_difficult | 0.514302 | 0.506168 |
MH_05_difficult | 0.453249 | 0.478479 |
其中PL VINS-Mono就是我们实现的算法,从表格对比中可以看出来,在大部分数据集上我们的算法的精度都有所提高,达到了预期的效果,下图分别是我们的算法在MH_01_easy、MH_02_easy、MH_04_difficult、MH_05_difficult在evo工具下画的轨迹图。(另外在我们的附件中由我们的算法在MH-05-difficult数据集上运行的视频)
我们通过一个月的课余时间完成了这个项目,但是因为临近七月底之后要开始准备秋招找工作,因此就没有继续再花太多时间在这个上面,我们对这个算法进行一个总结:
总而言之,算法虽然取得了一定的效果,但是还是由很大空间可以提升的。
最后想说的是,我们决定将代码和文档开源,有任何问题欢迎大家指正交流,希望和更多学SLAM的朋友一起进步,学SLAM虽然很辛苦,但是也真心很有趣!
[1] Concha, A. , & Civera, J. . (2014). Using superpixels in monocular SLAM. IEEE International Conference on Robotics & Automation. IEEE.
[2] Maity, S., Saha, A., & Bhowmick, B. (2017). Edge slam: Edge points based monocular visual slam. In Proceedings of the IEEE International Conference on Computer Vision (pp. 2408-2417).
[3] Tomono, M. (2009, May). Robust 3D SLAM with a stereo camera based on an edge-point ICP algorithm. In 2009 IEEE International Conference on Robotics and Automation (pp. 4306-4311). IEEE.
[4]He, Y., Zhao, J., Guo, Y., He, W., & Yuan, K. (2018). Pl-vio: Tightly-coupled monocular visual–inertial odometry using point and line features. Sensors, 18(4), 1159.
[5] (2017). 基于点线综合特征的双目视觉SLAM方法. (Doctoral dissertation, 浙江大学).
[6] https://github.com/MichaelGrupp/evo