首页 关于
树枝想去撕裂天空 / 却只戳了几个微小的窟窿 / 它透出天外的光亮 / 人们把它叫做月亮和星星
目录

局部地图中关键帧筛选 & LocalMapping 针对双目/深度相机的调整

局部建图的最后一个环节是关键帧筛选,其实它没有什么特别的内容,就是其字面意思。 删除掉那些冗余的关键帧,保证系统的运行效率。由于 LocalMapping 主要是针对关键帧和地图点进行的操作,不再直接处理图像,所以传感器类型对于它的实现影响不大。 LocalMapping 也就是在进行局部 BA 优化的时候针对 Stereo 类型的地图点专门描述了约束边的观测量。

本文先详细分析局部地图中关键帧筛选过程,再介绍针对双目/深度相机如何构造约束边的观测量。

1. 局部地图中关键帧筛选

出于效率的考虑,局部地图管理器还会检查关键帧,删除那些冗余的关键帧。这样可以防止随着系统的长时间运行,关键帧无限制的增长。 检查共视图上当前关键帧的所有邻接,并根据冗余程度,删除那些90%的地图点都能被其它至少三个关键帧观测到的关键帧。 下面是函数 KeyFrameCulling() 的代码片段,首先获取所有邻接关键帧,并遍历它们。由于第一个关键帧是整个地图的初始化基准,所以不能删去。

        void LocalMapping::KeyFrameCulling() {
            vector<KeyFrame*> vpLocalKeyFrames = mpCurrentKeyFrame->GetVectorCovisibleKeyFrames();
            for (auto vit=vpLocalKeyFrames.begin(); vit != vpLocalKeyFrames.end(); vit++) {
                KeyFrame* pKF = *vit;
                if (pKF->mnId == 0) continue;

接下来获取各个关键帧的地图点,并定义了一些临时的变量用于统计关键帧的冗余程度。

                const vector<MapPoint*> vpMapPoints = pKF->GetMapPointMatches();
                int nObs = 3;                 // 除了给下面的 thObs 赋了个值,实际上没啥用
                const int thObs=nObs;         // 地图点观测数阈值
                int nRedundantObservations=0; // 冗余的地图点数量
                int nMPs=0;                   // 有效地图点计数

在一个for循环中遍历所有的地图点,这里为了排版美观,我在第13行对判定条件做了一些等价的修改,跳过那些没有匹配的或者标记无效的地图点。 此外对于双目和深度相机,我们需要刨去那些远处的、相机背后的地图点。 在针对双目和深度相机的调整(TRACKING)中曾经提到, ORB-SLAM2 以40倍基线长度为阈值(pKF->mThDepth),将双目地图点分成了近(close)、远(far)两种形式。

                for (size_t i = 0; i < vpMapPoints.size(); i++) {
                    MapPoint* pMP = vpMapPoints[i];
                    if (!pMP || pMP->isBad()) continue;
                    if(!mbMonocular)
                        if (pKF->mvDepth[i] > pKF->mThDepth || pKF->mvDepth[i] < 0)
                            continue;

然后通过 nMPs++ 计数有效地图点。如果有至少3个关键帧在相同或者更细(finer scale)的尺度上观测到地图点,则认为该地图点是冗余的。 在第19行中通过变量 scaleLevel 记录地图点在第 i 个关键帧中的图像金字塔层级。

                    nMPs++;
                    if (pMP->Observations() <= thObs) continue;
                    const int & scaleLevel = pKF->mvKeysUn[i].octave;

遍历所有观测到地图点的关键帧集合,检查其它关键帧的金字塔层级。如果同级或者更细,则通过局部变量 nObs 记录下来。如第29,32行所示,超过了三帧则判定地图点冗余,退出循环。

                    int nObs=0;
                    const map<KeyFrame*, size_t> observations = pMP->GetObservations();
                    for (auto mit=observations.begin(); mit != observations.end(); mit++) {
                        KeyFrame* pKFi = mit->first;
                        if (pKFi == pKF) continue;
                        const int & scaleLeveli = pKFi->mvKeysUn[mit->second].octave;
                        if (scaleLeveli <= scaleLevel+1) {
                            nObs++;
                            if (nObs >= thObs) break;
                        }
                    }
                    if (nObs >= thObs)
                        nRedundantObservations++;
                }

如果超过90%的地图点都是冗余的,那么该关键帧就是冗余的,通过其接口 SetBadFlag() 标记下来,从地图中移除。

            if (nRedundantObservations > 0.9*nMPs)
                    pKF->SetBadFlag();
        }   }

2. 针对双目/深度相机的调整

在 LocalMapping 中也没有太多的针对双目/深度相机的调整。除了上面筛选关键帧的时候,针对双目地图点跳过了那些远处的点外,就是进行局部BA优化时,专门针对双目地图点设计了约束。

我们在局部BA优化中, 详细分析了函数Optimizer::LocalBundleAdjustment的实现。 该函数以当前帧、一级邻接关键帧、二级邻接关键帧,以及出现在当前帧和一级邻接中的地图点构造了一个局部的地图,通过 g2o 库进行 BA 优化。 下面左侧是该函数的代码片段,其中容器 vpEdgesStereo 是用来保存根据 Stereo 类型的地图点构建的约束边,以后简称为双目边。 vpEdgeKFStereo 和 vpMapPointEdgeStereo 分别记录了双目边连接的关键帧和地图点。thHuberStereo 是一个自由度为3,显著性水平为0.05的卡方阈值,后面用于剔除误差较大的边。

        vector<g2o::EdgeStereoSE3ProjectXYZ*> vpEdgesStereo;
        vector<KeyFrame*> vpEdgeKFStereo;
        vector<MapPoint*> vpMapPointEdgeStereo;
        const float thHuberStereo = sqrt(7.815);
        Eigen::Matrix<double,3,1> obs;
        const float kp_ur = pKFi->mvuRight[mit->second];

        obs << kpUn.pt.x, kpUn.pt.y, kp_ur;

上面右侧的代码片段,是在一个 for 循环中针对 stereo 类型的地图点构建观测量 obs。其中包含了地图点在左目中的xy像素坐标,以及右目中的像素x坐标。 在下面的代码中,构造了双目边对象,并设定了它的链接的两个节点分别是一个地图点和一个关键帧。id是地图点在局部地图中的节点索引。 此外,还在第4行中设定了双目边的观测量 obs。

        g2o::EdgeStereoSE3ProjectXYZ* e = new g2o::EdgeStereoSE3ProjectXYZ();
        e->setVertex(0, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(id)));
        e->setVertex(1, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(pKFi->mnId)));
        e->setMeasurement(obs);

其余的操作与 mono 类型的地图点没有什么大的差别。无非是 Huber 核的参数,以及误差判定阈值为 thHuberStereo 而已。

3. 完

本文中,我们先分析了局部地图中关键帧筛选的实现。如果当前关键帧的邻接关键帧中 90% 的地图点都是冗余的将被剔除掉。判定地图点冗余的条件是,至少有三个关键帧在相同或者更细的尺度上观测到该地图点。

初次之外,还讨论了在局部 BA 优化时针对双目/深度相机做的调整。主要差别在于,观测量中增加了右目相机的像素横坐标,以及 Huber 核函数的参数。




Copyright @ 高乙超. All Rights Reserved. 京ICP备16033081号-1