深度滤波器主要是计算地图点的深度,供Feature Alignment使用。

深度滤波器是一个收敛过程,观测量是每次三角化计算的深度值,高翔《视觉SLAM十四讲》第二版把这个过程概括为四步:

1)假设所有像素的深度满足某个初始的高斯分布

2)当新数据产生时,通过极线搜索确定投影点位置

3)根据几何关系计算三角化后的深度及不确定性

4)将当前观测融合进上一次的估计中,若收敛,则停止计算,否则返回第2步

贺一加的博客,对SVO中的深度估计做了更详细的介绍(博客链接:blog.csdn.net/heyijia03),这里借用一下,内容如下:

最基本的深度估计就是三角化,这是多视角几何的基础内容。我们知道通过两帧图像的匹配点就可以计算出这一点的深度值,如果有多幅图像,那就能计算出这一点的多个深度值。这就像对同一个状态变量我们进行了多次测量,因此,可以用贝叶斯估计来对多个测量值进行融合,使得估计的不确定性缩小。如下图所示:

一开始深度估计的不确定性较大(浅绿色部分),通过三角化得到一个深度估计值以后,能够极大的缩小这个不确定性(墨绿色部分)。

在这里,先简单介绍下svo中的三角化计算深度的过程,主要是极线搜索确定匹配点。在参考帧IrIr中,我们知道了一个特征的图像位置,假设它的深度值在[ dmin , dmax ]之间,那么根据这两个端点深度值,我们能够计算出他们在当前帧IkIk中的位置,如上图中草绿色圆圈中的线段。确定了特征出现的极线段位置,就可以进行特征搜索匹配了。如果极线段很短,小于两个像素,那直接使用上面求位姿时提到的Feature Alignment光流法就可以比较准确地预测特征位置。如果极线段很长,那分两步走,第一步在极线段上间隔采样,对采样的多个特征块一一和参考帧中的特征块匹配,用Zero mean Sum of Squared Differences 方法对各采样特征块评分,那个得分最高,说明他和参考帧中的特征块最匹配。第二步就是在这个得分最高点附近使用Feature Alignment得到次像素精度的特征点位置。像素点位置确定了,就可以三角化计算深度了。

最后,得到一个新的深度估计值以后,就可以用贝叶斯概率模型对深度值更新。

在深度估计的过程中,除了计算深度值外,这个深度值的不确定性也是需要计算的,它在很多地方都会用到,如极线搜索中确定极线的起始位置和长度,如用贝叶斯概率更新深度的过程中用它来确定更新权重(就像卡尔曼滤波中协方差矩阵扮演的角色),如判断这个深度点是否收敛了,如果收敛就插入地图等等。SVO的作者Forster作为第二作者发表的《REMODE: Probabilistic, Monocular Dense Reconstruction in Real Time》中对由于特征定位不准导致的三角化深度误差进行了分析,如下图:

它是通过假设特征点定位差一个像素偏差,来计算深度估计的不确定性。具体推导见原论文,简单的几何关系。

好了,下面进入我们的代码环节

深度滤波器功能相对独立,所以它的代码主要集中在DepthFilter.cpp和DepthFilter.h中

下面看一下深度滤波器代码流程

1. DepthFilter新进来一个关键帧,则需要生成新的seed(种子)

1)新的关键帧到来时,则中断updateSeeds(),因为这时有很大可能正在处理普通帧,而不是关键帧,所以就丢掉了,即,关键帧优先

2)setExistingFeatures()设置包含feature的栅格为占据状态,这时的feature只来自与相邻关键帧匹配得到,所以不需要通过深度滤波器再优化了

3)使用feature_detector_->detect()检测新的feature,然后加入到seeds_中

2. 使用updateSeeds()遍历每一个seed

1)首先检测该seed在当前帧中是否可见,

2)接着使用findEpipolarMatchDirect()处理这个seed,

  • 对于边缘特征(Feature::EDGELET),如果把梯度仿射过来后,梯度的方向与极线方向的夹角大于45度,就认为沿着极线找,图块像素也不会变化很大,就不搜索了,直接返回false
  • 沿着极线方向搜索,在当前帧上找到与ref_ftr对应的图像块最匹配的图像块,接着使用align2D()进一步优化,得到更为精准的px_cur_,而后就可以使用seed的对应的ref_ftr与px_cur_进行三角化,得到3D point,也就得到了待估计的深度(depth)

3)上一步得到深度,接下来使用updateSeed()得到深度的协方差,这里要参考论文:《 Video-based, Real-Time Multi View Stereo》

4)如果协方差小于阈值,就认为收敛了,它就不再是种子点,而是candidate点,使用回调函数,加入到候选点队列中

5)如果第2步匹配成功,则得到px_cur_,如果当前帧是关键帧的话,就将matcher_.px_cur_所在的栅格设置为占据状态