TUM 德国 慕尼黑工业大学(Technische Universität München)
DSO属于稀疏直接法的视觉里程计。它不是完整的SLAM,因为它不包含回环检测、地图复用的功能。
因此,它不可避免地会出现累计误差,尽管很小,但不能消除。
DSO (Direct sparse odometry) [24] 系统基于直接法的拓展 ,
使用光度值误差最小化几何和光度学参数.
DSO 对图像中有梯度、边缘或亮度平滑变化.
DSO甚至没有“匹配点”这个概念。
每一个三维点,从某个主导帧(host frame)出发,乘上深度值之后投影至另一个目标帧(target frame),从而建立一个投影残差(residual)。
只要残差在合理范围内,就可以认为这些点是由同一个点投影的。
从数据关联角度看,在这个过程中并没有a1-b1, a2-b2这样的关系,也可能存在a1-b1, a2-b1, a3-b1这样的情况。
但是DSO并不在意这些,只要残差不大,我们就看成是同一个点。这是很重要的一点。
在特征点法中,我们可以找到一个地图点分别在哪些帧中被看到,乃至找到各帧中的图像描述子是什么;
但在DSO中,我们会尝试把 每个点 投影到 所有帧 中,计算它在各帧中的残差,而并不在意点和点之间的一一对应关系。
从后端来看,DSO使用一个由若干个关键帧组成的滑动窗口作为它的后端。
这个窗口在整个VO过程中一直存在,并有一套方法来管理新数据的加入以及老数据的去除。
具体来说,这个窗口通常保持5到7个关键帧。
前端追踪部分,会通过一定的条件,来判断新来的帧是否可作为新的关键帧插入后端。
同时,如果后端发现关键帧数已经大于窗口大小,也会通过特定的方法,选择其中一个帧进行去除。
请注意被去除的帧并不一定是时间线上最旧的那个帧,而是会有一些复杂条件的。
后端除了维护这个窗口中的关键帧与地图点外,还会维护与优化相关的结构。
特别地,这里指Gauss-Newton或Levenburg-Marquardt方法中的Hessian矩阵和b向量(仅先验部分)。
当我们增加新的关键帧时,就必须扩展H和b的维度;
反之,如果需要去掉某个关键帧(以及它携带的地图点)时,也需要降低H和b的维度。
这个过程还需要将被删掉帧和点的信息,转移到窗口内剩余帧当中,这一步被称为边缘化(Marginalization)。
由于直接法需要比较图像信息,其结果容易受光照干扰。
于是,DSO提出了光度标定,认为对相机的曝光时间、暗角、伽马响应等参数进行标定后,能够让直接法更加鲁棒。
对于未进行光度标定的相机,DSO也会在优化中动态估计 光度参数(具体来说,是一个仿射变化的参数,记作a和b)。
这个过程建模了相机的成像过程,因此对于由相机曝光不同引起的图像明暗变化,会有更好的表现。
但是,如果由于环境光源发生变化,光度标定也是无能为力的。
1. 将光度模型、集合模型结合起来,对相机内参、相机姿态、像素点的深度等参数做联合估计;
2. 由于引入了光度模型,算法忽略了其他直接法采用的对图像进行平滑的预处理,
而是将相机看做一个传感器,对图像像素进行均匀采样,进行计算。
3. 提出的模型集成了完整的光度校准,考虑了曝光时间、透镜渐晕和非线性相应函数。
4. 通过实验验证,该算法在跟踪精度和鲁棒性方面性能超过了state-of-the-art的算法水平。
2.1 代码框架与数据表示
DSO整体代码由四个部分组成:
系统与各算法集成于src/FullSystem,
后端优化位于src/OptimizationBackend,
这二者组成了DSO大部分核心内容。
src/utils和src/IOWrapper为一些去畸变、数据集读写和可视化UI代码。
先来看核心部分的FullSystem和OptimizationBackend。
在FullSystem里,DSO致力于维护一个滑动窗口内部的关键帧序列。
整体思路同ORBSLAM很像,并没有太大的不同。
决定使用哪些点、图像帧,以及点和图像帧之间的关系。
提供初始值。
滑动窗口内包含7帧关键帧,帧1为最新的关键帧,帧7为最旧的关键帧。关键帧marg的策略如下:
1. 始终保持最新的两帧。
2. 若旧关键帧中的点不足5%为新关键帧观察到,则旧关键帧被marg。
3. 若满足保留条件的关键帧多于7, 对除了最新两帧的关键帧计算"distance score",
消去得分高的关键帧以保持所有关键帧在三维空间中有个较好的分布。
直接法选取的点较特征点法有所不同,
直接法允许在所有的可得数据中进行采样,包括弱纹理区域及重复区域。
为了满足实时性的要求,DSO仅优化2000个地图点及7个关键帧,
又出于备用点需足够的角度,在每一关键帧中选择2000个点作为备用。
每个帧的数据存储于FrameHessian结构体中,FrameHessian即是一个带着状态变量与Hessian信息的帧。
然后,每个帧亦携带一些地图点的信息,包括:
pointHessians 是所有活跃点的信息。
所谓活跃点,是指它们在相机的视野中,其残差项仍在参与优化部分的计算;
pointHessiansMarginalized 是已经边缘化的地图点。
pointHessiansOut 是被判为外点(outlier)的地图点。
immaturePoints 为未成熟地图点的信息。
在单目SLAM中,所有地图点在一开始被观测到时,都只有一个2D的像素坐标,其深度是未知的。这种点在DSO中称为未成熟的地图点。
随着相机的运动,DSO会在每张图像上追踪这些未成熟的地图点,
这个过程称为trace——实际上是一个沿着极线搜索的过程,十分类似于svo的depth filter。
Trace的过程会确定每个Immature Point的逆深度和它的变化范围。
如果Immature Point的深度(实际中为深度的倒数,即逆深度)在这个过程中收敛,
那么我们就可以确定这个未成熟地图点的三维坐标,形成了一个正常的地图点。
具有三维坐标的地图点,在DSO中称为PointHessian。
与FrameHessian相对,PointHessian亦记录了这个点的三维坐标,以及Hessian信息。
与很多其他SLAM方案不同,DSO使用单个参数描述一个地图点,即它的逆深度。
而ORB-SLAM等多数方案,则会记录地图点的x,y,z三个坐标。
逆深度参数化形式具有形式简单、类似高斯分布、对远处场景更为鲁棒等优点,
但基于逆深度参数化的Bundle adjustment,每个残差项需要比通常的BA多计算一个雅可比矩阵。
为了使用逆深度,每个PointHessian必须拥有一个主导帧(host frame),说明这个点是由该帧反投影得到的。
于是,滑动窗口的所有信息,可以由若干个FrameHessian,加上每个帧带有的PointHessian来描述。
所有的PointHessian又可以在除主导帧外的任意一帧中进行投影,形成一个残差项,记录于PointHessian::residuals中。
所有的残差加起来,就构成了DSO需要求解的优化问题。
当然,由于运动、遮挡的原因,并非每个点都可以成功地投影到其余任意一帧中去,
于是我们还需要设置每个点的状态:有效的/被边缘化的/无效的。
不同状态的点,被存储于它主导帧的pointHessians/pointHessianMarginalized/PointHessiansOut三个容器内。
除此之外,DSO将相机的内参、曝光参数等信息,亦作为优化变量考虑在内。相
机内参由针孔相机参数fx, fy, cx, cy表达,曝光参数则由两个参数a,b描述。
这部分内容在光度标定一节内。
后端优化部分单独具有独立的Frame, Point, Residual结构。
由于DSO的优化目标是最小化能量(energy,和误差类似),所以有关后端的类均以EF开头,
且与FullSystem中存储的实例一一对应,互相持有对方的指针。
优化部分由EnergyFunctional类统一管理。
它从FullSystem中获取所有帧和点的数据,进行优化后,再将优化结果返回。
它也包含整个滑动窗口内的所有帧和点信息,负责处理实际的非线性优化矩阵运算。
每当新的图像到来时,DSO将处理此图像的信息,流程如下:
从上图可以简单总结出DSO的行为:
1. 对于非关键帧,DSO仅计算它的位姿,并用该帧图像更新每个未成熟点的深度估计;
2. 后端仅处理关键帧部分的优化。
除去一些内存维护操作,对每个关键帧主要做的处理有:
A. 增加新的残差项、
B. 去除错误的残差项、
C. 提取新未成熟点。
3. 整个流程在一个线程内,但内部可能有多线程的操作。
滑动窗口内部构成了一个非线性最小二乘问题。表示成因子图(或图优化)的形式,如下所示:
每一个关键帧的状态为八维:
六自由度的运动位姿(T) 加上 两个描述光度的参数(a,b);
每个地图点的状态变量为一维,即该点在主导帧(Host)里的逆深度(d)。
于是,每个残差项(或能量项E),将关联两个关键帧与一个逆深度。
事实上,还有一个全局的相机内参数亦参与了优化,但未在此图中表示。