ORB-SLAM2 --- LoopClosing::CorrectLoop函数

news2024/11/20 21:31:32

目录

1.函数作用

2.函数流程 

3.code

4.函数解析

4.1 结束局部地图线程、全局BA,为闭环矫正做准备

4.2 根据共视关系更新当前关键帧与其它关键帧之间的连接关系 

4.3 通过位姿传播,得到Sim3优化后,与当前帧相连的关键帧的位姿,以及它们的地图点 

4.3 检查当前帧的地图点与经过闭环匹配后该帧的地图点是否存在冲突,对冲突的进行替换或填补 

4.4 将闭环相连关键帧组mvpLoopMapPoints 投影到当前关键帧组中,进行匹配,融合,新增或替换当前关键帧组中KF的地图点 

4.5 更新当前关键帧组之间的两级共视相连关系,得到因闭环时地图点融合而新得到的连接关系 

4.6 结尾工作

 


1.函数作用

        1. 通过求解的Sim3以及相对姿态关系,调整与当前帧相连的关键帧位姿以及这些关键帧观测到的地图点位置(相连关键帧---当前帧) 
        2. 将闭环帧以及闭环帧相连的关键帧的地图点和与当前帧相连的关键帧的点进行匹配(当前帧+相连关键帧---闭环帧+相连关键帧)     
        3. 通过MapPoints的匹配关系更新这些帧之间的连接关系,即更新covisibility graph                 4. 对Essential Graph(Pose Graph)进行优化,MapPoints的位置则根据优化后的位姿做相对应的调整                        
        5. 创建线程进行全局Bundle Adjustment

2.函数流程 

        Step 0:结束局部地图线程、全局BA,为闭环矫正做准备
        Step 1:根据共视关系更新当前帧与其它关键帧之间的连接
        Step 2:通过位姿传播,得到Sim3优化后,与当前帧相连的关键帧的位姿,以及它们的MapPoints
        Step 3:检查当前帧的MapPoints与闭环匹配帧的MapPoints是否存在冲突,对冲突的MapPoints进行替换或填补
        Step 4:通过将闭环时相连关键帧的mvpLoopMapPoints投影到这些关键帧中,进行MapPoints检查与替换
        Step 5:更新当前关键帧之间的共视相连关系,得到因闭环时MapPoints融合而新得到的连接关系
        Step 6:进行EssentialGraph优化,LoopConnections是形成闭环后新生成的连接关系,不包括步骤7中当前帧与闭环匹配帧之间的连接关系
        Step 7:添加当前帧与闭环匹配帧之间的边(这个连接关系不优化)
        Step 8:新建一个线程用于全局BA优化

3.code

void LoopClosing::CorrectLoop()
{

    cout << "Loop detected!" << endl;
    // Step 0:结束局部地图线程、全局BA,为闭环矫正做准备
    // 请求局部地图停止,防止在回环矫正时局部地图线程中InsertKeyFrame函数插入新的关键帧
    mpLocalMapper->RequestStop();

    if(isRunningGBA())
    {
        // 如果有全局BA在运行,终止掉,迎接新的全局BA
        unique_lock<mutex> lock(mMutexGBA);
        mbStopGBA = true;
        // 记录全局BA次数
        mnFullBAIdx++;
        if(mpThreadGBA)
        {
            // 停止全局BA线程
            mpThreadGBA->detach();
            delete mpThreadGBA;
        }
    }

    // Wait until Local Mapping has effectively stopped
    // 一直等到局部地图线程结束再继续
    while(!mpLocalMapper->isStopped())
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }

    // Ensure current keyframe is updated
    // Step 1:根据共视关系更新当前关键帧与其它关键帧之间的连接关系
    // 因为之前闭环检测、计算Sim3中改变了该关键帧的地图点,所以需要更新
    mpCurrentKF->UpdateConnections();

    // Retrive keyframes connected to the current keyframe and compute corrected Sim3 pose by propagation
    // Step 2:通过位姿传播,得到Sim3优化后,与当前帧相连的关键帧的位姿,以及它们的地图点
    // 当前帧与世界坐标系之间的Sim变换在ComputeSim3函数中已经确定并优化,
    // 通过相对位姿关系,可以确定这些相连的关键帧与世界坐标系之间的Sim3变换

    // 取出当前关键帧及其共视关键帧,称为“当前关键帧组”
    mvpCurrentConnectedKFs = mpCurrentKF->GetVectorCovisibleKeyFrames();
    mvpCurrentConnectedKFs.push_back(mpCurrentKF);

    // CorrectedSim3:存放闭环g2o优化后当前关键帧的共视关键帧的世界坐标系下Sim3 变换
    // NonCorrectedSim3:存放没有矫正的当前关键帧的共视关键帧的世界坐标系下Sim3 变换
    KeyFrameAndPose CorrectedSim3, NonCorrectedSim3;
    // 先将mpCurrentKF的Sim3变换存入,认为是准的,所以固定不动
    CorrectedSim3[mpCurrentKF]=mg2oScw;
    // 当前关键帧到世界坐标系下的变换矩阵
    cv::Mat Twc = mpCurrentKF->GetPoseInverse();

    // 对地图点操作
    {
        // Get Map Mutex
        // 锁定地图点
        unique_lock<mutex> lock(mpMap->mMutexMapUpdate);

        // Step 2.1:通过mg2oScw(认为是准的)来进行位姿传播,得到当前关键帧的共视关键帧的世界坐标系下Sim3 位姿
        // 遍历"当前关键帧组""
        for(vector<KeyFrame*>::iterator vit=mvpCurrentConnectedKFs.begin(), vend=mvpCurrentConnectedKFs.end(); vit!=vend; vit++)
        {
            KeyFrame* pKFi = *vit;
            cv::Mat Tiw = pKFi->GetPose();
            if(pKFi!=mpCurrentKF)      //跳过当前关键帧,因为当前关键帧的位姿已经在前面优化过了,在这里是参考基准
            {
                // 得到当前关键帧 mpCurrentKF 到其共视关键帧 pKFi 的相对变换
                cv::Mat Tic = Tiw*Twc;
                cv::Mat Ric = Tic.rowRange(0,3).colRange(0,3);
                cv::Mat tic = Tic.rowRange(0,3).col(3);

                // g2oSic:当前关键帧 mpCurrentKF 到其共视关键帧 pKFi 的Sim3 相对变换
                // 这里是non-correct, 所以scale=1.0
                g2o::Sim3 g2oSic(Converter::toMatrix3d(Ric),Converter::toVector3d(tic),1.0);
                // 当前帧的位姿固定不动,其它的关键帧根据相对关系得到Sim3调整的位姿
                g2o::Sim3 g2oCorrectedSiw = g2oSic*mg2oScw;
                // Pose corrected with the Sim3 of the loop closure
                // 存放闭环g2o优化后当前关键帧的共视关键帧的Sim3 位姿
                CorrectedSim3[pKFi]=g2oCorrectedSiw;
            }

            cv::Mat Riw = Tiw.rowRange(0,3).colRange(0,3);
            cv::Mat tiw = Tiw.rowRange(0,3).col(3);
            g2o::Sim3 g2oSiw(Converter::toMatrix3d(Riw),Converter::toVector3d(tiw),1.0);
            // Pose without correction
            // 存放没有矫正的当前关键帧的共视关键帧的Sim3变换
            NonCorrectedSim3[pKFi]=g2oSiw;
        }

        // Correct all MapPoints obsrved by current keyframe and neighbors, so that they align with the other side of the loop
        // Step 2.2:得到矫正的当前关键帧的共视关键帧位姿后,修正这些共视关键帧的地图点
        // 遍历待矫正的共视关键帧(不包括当前关键帧)
        for(KeyFrameAndPose::iterator mit=CorrectedSim3.begin(), mend=CorrectedSim3.end(); mit!=mend; mit++)
        {
            // 取出当前关键帧连接关键帧
            KeyFrame* pKFi = mit->first;
            // 取出经过位姿传播后的Sim3变换
            g2o::Sim3 g2oCorrectedSiw = mit->second;
            g2o::Sim3 g2oCorrectedSwi = g2oCorrectedSiw.inverse();
            // 取出未经过位姿传播的Sim3变换
            g2o::Sim3 g2oSiw =NonCorrectedSim3[pKFi];

            vector<MapPoint*> vpMPsi = pKFi->GetMapPointMatches();
            // 遍历待矫正共视关键帧中的每一个地图点
            for(size_t iMP=0, endMPi = vpMPsi.size(); iMP<endMPi; iMP++)
            {
                MapPoint* pMPi = vpMPsi[iMP];
                // 跳过无效的地图点
                if(!pMPi)
                    continue;
                if(pMPi->isBad())
                    continue;
                // 标记,防止重复矫正
                if(pMPi->mnCorrectedByKF==mpCurrentKF->mnId) 
                    continue;

                // 矫正过程本质上也是基于当前关键帧的优化后的位姿展开的
                // Project with non-corrected pose and project back with corrected pose
                // 将该未校正的eigP3Dw先从世界坐标系映射到未校正的pKFi相机坐标系,然后再反映射到校正后的世界坐标系下
                cv::Mat P3Dw = pMPi->GetWorldPos();
                // 地图点世界坐标系下坐标
                Eigen::Matrix<double,3,1> eigP3Dw = Converter::toVector3d(P3Dw);
                // map(P) 内部做了相似变换 s*R*P +t  
                // 下面变换是:eigP3Dw: world →g2oSiw→ i →g2oCorrectedSwi→ world
                Eigen::Matrix<double,3,1> eigCorrectedP3Dw = g2oCorrectedSwi.map(g2oSiw.map(eigP3Dw));

                cv::Mat cvCorrectedP3Dw = Converter::toCvMat(eigCorrectedP3Dw);
                pMPi->SetWorldPos(cvCorrectedP3Dw);
                // 记录矫正该地图点的关键帧id,防止重复
                pMPi->mnCorrectedByKF = mpCurrentKF->mnId;
                // 记录该地图点所在的关键帧id
                pMPi->mnCorrectedReference = pKFi->mnId;
                // 因为地图点更新了,需要更新其平均观测方向以及观测距离范围
                pMPi->UpdateNormalAndDepth();
            }

            // Update keyframe pose with corrected Sim3. First transform Sim3 to SE3 (scale translation)
            // Step 2.3:将共视关键帧的Sim3转换为SE3,根据更新的Sim3,更新关键帧的位姿
            // 其实是现在已经有了更新后的关键帧组中关键帧的位姿,但是在上面的操作时只是暂时存储到了 KeyFrameAndPose 类型的变量中,还没有写回到关键帧对象中
            // 调用toRotationMatrix 可以自动归一化旋转矩阵
            Eigen::Matrix3d eigR = g2oCorrectedSiw.rotation().toRotationMatrix(); 
            Eigen::Vector3d eigt = g2oCorrectedSiw.translation();                  
            double s = g2oCorrectedSiw.scale();
            // 平移向量中包含有尺度信息,还需要用尺度归一化
            eigt *=(1./s); 

            cv::Mat correctedTiw = Converter::toCvSE3(eigR,eigt);
            // 设置矫正后的新的pose
            pKFi->SetPose(correctedTiw);

            // Make sure connections are updated
            // Step 2.4:根据共视关系更新当前帧与其它关键帧之间的连接
            // 地图点的位置改变了,可能会引起共视关系\权值的改变 
            pKFi->UpdateConnections();
        }

        // Start Loop Fusion
        // Update matched map points and replace if duplicated
        // Step 3:检查当前帧的地图点与经过闭环匹配后该帧的地图点是否存在冲突,对冲突的进行替换或填补
        // mvpCurrentMatchedPoints 是当前关键帧和闭环关键帧组的所有地图点进行投影得到的匹配点
        for(size_t i=0; i<mvpCurrentMatchedPoints.size(); i++)
        {
            if(mvpCurrentMatchedPoints[i])
            {
                //取出同一个索引对应的两种地图点,决定是否要替换
                // 匹配投影得到的地图点
                MapPoint* pLoopMP = mvpCurrentMatchedPoints[i];
                // 原来的地图点
                MapPoint* pCurMP = mpCurrentKF->GetMapPoint(i); 
                if(pCurMP)
                    // 如果有重复的MapPoint,则用匹配的地图点代替现有的
                    // 因为匹配的地图点是经过一系列操作后比较精确的,现有的地图点很可能有累计误差
                    pCurMP->Replace(pLoopMP);
                else
                {
                    // 如果当前帧没有该MapPoint,则直接添加
                    mpCurrentKF->AddMapPoint(pLoopMP,i);
                    pLoopMP->AddObservation(mpCurrentKF,i);
                    pLoopMP->ComputeDistinctiveDescriptors();
                }
            }
        } 

    }

    // Project MapPoints observed in the neighborhood of the loop keyframe
    // into the current keyframe and neighbors using corrected poses.
    // Fuse duplications.
    // Step 4:将闭环相连关键帧组mvpLoopMapPoints 投影到当前关键帧组中,进行匹配,融合,新增或替换当前关键帧组中KF的地图点
    // 因为 闭环相连关键帧组mvpLoopMapPoints 在地图中时间比较久经历了多次优化,认为是准确的
    // 而当前关键帧组中的关键帧的地图点是最近新计算的,可能有累积误差
    // CorrectedSim3:存放矫正后当前关键帧的共视关键帧,及其世界坐标系下Sim3 变换
    SearchAndFuse(CorrectedSim3);


    // After the MapPoint fusion, new links in the covisibility graph will appear attaching both sides of the loop
    // Step 5:更新当前关键帧组之间的两级共视相连关系,得到因闭环时地图点融合而新得到的连接关系
    // LoopConnections:存储因为闭环地图点调整而新生成的连接关系
    map<KeyFrame*, set<KeyFrame*> > LoopConnections;

    // Step 5.1:遍历当前帧相连关键帧组(一级相连)
    for(vector<KeyFrame*>::iterator vit=mvpCurrentConnectedKFs.begin(), vend=mvpCurrentConnectedKFs.end(); vit!=vend; vit++)
    {
        KeyFrame* pKFi = *vit;
        // Step 5.2:得到与当前帧相连关键帧的相连关键帧(二级相连)
        vector<KeyFrame*> vpPreviousNeighbors = pKFi->GetVectorCovisibleKeyFrames();

        // Update connections. Detect new links.
        // Step 5.3:更新一级相连关键帧的连接关系(会把当前关键帧添加进去,因为地图点已经更新和替换了)
        pKFi->UpdateConnections();
        // Step 5.4:取出该帧更新后的连接关系
        LoopConnections[pKFi]=pKFi->GetConnectedKeyFrames();
        // Step 5.5:从连接关系中去除闭环之前的二级连接关系,剩下的连接就是由闭环得到的连接关系
        for(vector<KeyFrame*>::iterator vit_prev=vpPreviousNeighbors.begin(), vend_prev=vpPreviousNeighbors.end(); vit_prev!=vend_prev; vit_prev++)
        {
            LoopConnections[pKFi].erase(*vit_prev);
        }
        // Step 5.6:从连接关系中去除闭环之前的一级连接关系,剩下的连接就是由闭环得到的连接关系
        for(vector<KeyFrame*>::iterator vit2=mvpCurrentConnectedKFs.begin(), vend2=mvpCurrentConnectedKFs.end(); vit2!=vend2; vit2++)
        {
            LoopConnections[pKFi].erase(*vit2);
        }
    }

    // Optimize graph
    // Step 6:进行本质图优化,优化本质图中所有关键帧的位姿和地图点
    // LoopConnections是形成闭环后新生成的连接关系,不包括步骤7中当前帧与闭环匹配帧之间的连接关系
    Optimizer::OptimizeEssentialGraph(mpMap, mpMatchedKF, mpCurrentKF, NonCorrectedSim3, CorrectedSim3, LoopConnections, mbFixScale);

    // Add loop edge
    // Step 7:添加当前帧与闭环匹配帧之间的边(这个连接关系不优化)
    // 它在下一次的本质图优化里面使用
    mpMatchedKF->AddLoopEdge(mpCurrentKF);
    mpCurrentKF->AddLoopEdge(mpMatchedKF);

    // Launch a new thread to perform Global Bundle Adjustment
    // Step 8:新建一个线程用于全局BA优化
    // OptimizeEssentialGraph只是优化了一些主要关键帧的位姿,这里进行全局BA可以全局优化所有位姿和MapPoints
    mbRunningGBA = true;
    mbFinishedGBA = false;
    mbStopGBA = false;
    mpThreadGBA = new thread(&LoopClosing::RunGlobalBundleAdjustment,this,mpCurrentKF->mnId);

    // Loop closed. Release Local Mapping.
    mpLocalMapper->Release();    

    cout << "Loop Closed!" << endl;

    mLastLoopKFid = mpCurrentKF->mnId;
}

/**
 * @brief 将闭环相连关键帧组mvpLoopMapPoints 投影到当前关键帧组中,进行匹配,新增或替换当前关键帧组中KF的地图点
 * 因为 闭环相连关键帧组mvpLoopMapPoints 在地图中时间比较久经历了多次优化,认为是准确的
 * 而当前关键帧组中的关键帧的地图点是最近新计算的,可能有累积误差
 * 
 * @param[in] CorrectedPosesMap         矫正的当前KF对应的共视关键帧及Sim3变换
 */
void LoopClosing::SearchAndFuse(const KeyFrameAndPose &CorrectedPosesMap)
{

    // 定义ORB匹配器
    ORBmatcher matcher(0.8);

    // Step 1 遍历待矫正的当前KF的相连关键帧
    for(KeyFrameAndPose::const_iterator mit=CorrectedPosesMap.begin(), mend=CorrectedPosesMap.end(); mit!=mend;mit++)
    {
        KeyFrame* pKF = mit->first;
        // 矫正过的Sim 变换
        g2o::Sim3 g2oScw = mit->second;
        cv::Mat cvScw = Converter::toCvMat(g2oScw);

        // Step 2 将mvpLoopMapPoints投影到pKF帧匹配,检查地图点冲突并融合
        // mvpLoopMapPoints:与当前关键帧闭环匹配上的关键帧及其共视关键帧组成的地图点
        vector<MapPoint*> vpReplacePoints(mvpLoopMapPoints.size(),static_cast<MapPoint*>(NULL));
        // vpReplacePoints:存储mvpLoopMapPoints投影到pKF匹配后需要替换掉的新增地图点,索引和mvpLoopMapPoints一致,初始化为空
        // 搜索区域系数为4
        matcher.Fuse(pKF,cvScw,mvpLoopMapPoints,4,vpReplacePoints);

        // Get Map Mutex
        // 之所以不在上面 Fuse 函数中进行地图点融合更新的原因是需要对地图加锁
        unique_lock<mutex> lock(mpMap->mMutexMapUpdate);
        const int nLP = mvpLoopMapPoints.size();
        // Step 3 遍历闭环帧组的所有的地图点,替换掉需要替换的地图点
        for(int i=0; i<nLP;i++)
        {
            MapPoint* pRep = vpReplacePoints[i];
            if(pRep)
            {
                // 如果记录了需要替换的地图点
                // 用mvpLoopMapPoints替换掉vpReplacePoints里记录的要替换的地图点
                pRep->Replace(mvpLoopMapPoints[i]);
            }
        }
    }
}

4.函数解析

4.1 结束局部地图线程、全局BA,为闭环矫正做准备

        请求局部地图停止,防止在回环矫正时局部地图线程中InsertKeyFrame函数插入新的关键帧。

// 外部线程调用,请求停止当前线程的工作; 其实是回环检测线程调用,来避免在进行全局优化的过程中局部建图线程添加新的关键帧
void LocalMapping::RequestStop()
{
    unique_lock<mutex> lock(mMutexStop);
    mbStopRequested = true;
    unique_lock<mutex> lock2(mMutexNewKFs);
    mbAbortBA = true;
}
    // 在回环纠正的时候调用,查看当前是否已经有一个全局优化的线程在进行
    bool isRunningGBA(){
        unique_lock<std::mutex> lock(mMutexGBA);
        return mbRunningGBA;
    }
    /// 全局BA线程句柄
    std::thread* mpThreadGBA;

        如果有全局BA在运行,终止掉,迎接新的全局BA。记录全局BA次数(mnFullBAIdx)如果全局BA句柄存在则停止全局BA线程,一直等到局部建图线程结束再进行下面步骤。

4.2 根据共视关系更新当前关键帧与其它关键帧之间的连接关系 

        因为之前闭环检测、计算Sim3中改变了该关键帧的地图点,所以需要更新。

        此函数主要更新的信息是:

        @mConnectedKeyFrameWeights:当前关键帧的共视信息,记录当前关键帧共视关键帧的信息(哪一帧和当前关键帧有共视,共视程度是多少)

        @mvpOrderedConnectedKeyFrames:对mConnectedKeyFrameWeights中超过共视阈值的关键帧(按照共视程度从大到小排序)

        @mvOrderedWeights:对mConnectedKeyFrameWeights中超过共视阈值的关键帧的共视程度(按照共视程度从大到小排序)

        @mpParent:共视程度最高的那个关键帧

        @mspChildrens:建立双向关系,将当前帧的共视程度最高的那个关键帧的子关键帧添加当前帧
        具体解析请参阅我的博客:

ORB-SLAM2 --- KeyFrame::UpdateConnections 函数解析https://blog.csdn.net/qq_41694024/article/details/128516249

4.3 通过位姿传播,得到Sim3优化后,与当前帧相连的关键帧的位姿,以及它们的地图点 

        先解释几个变量:

        g2oSic: 当前关键帧 mpCurrentKF 到其共视关键帧 pKFi 的Sim3 相对变换
        mg2oScw: 世界坐标系到当前关键帧的 Sim3 变换。由ComputeSim3函数计算出来
        g2oCorrectedSiw:世界坐标系到当前关键帧共视关键帧的Sim3 变换

        这个函数的流程是这样的:

        我们在计算sim3的过程中计算出了当前帧mpCurrentKF的世界坐标系坐标,我们认为这是准确的,我们接下来要根据这个坐标修正当前帧mpCurrentKF的共视关键帧的坐标。

        我们首先将当前帧mpCurrentKF和其共视相连的关键帧加入容器mvpCurrentConnectedKFs中。对应图一的红色圈中的关键帧。

        定义了两个变量CorrectedSim3, NonCorrectedSim3

    typedef map<KeyFrame*,                  //键
                g2o::Sim3,                  //值
                std::less<KeyFrame*>,       //排序算法
                Eigen::aligned_allocator<std::pair<const KeyFrame*, g2o::Sim3> > // 指定分配器,和内存空间开辟有关. 为了能够使用Eigen库中的SSE和AVX指令集加速,需要将传统STL容器中的数据进行对齐处理
                > KeyFrameAndPose;

        我们先将mpCurrentKF的Sim3变换存入,认为是准的,所以固定不动:

CorrectedSim3[mpCurrentKF]=mg2oScw;

        遍历所有共视关键帧

        获取当前关键帧到世界坐标系下的变换矩阵Tiw

// 获取位姿的逆
cv::Mat KeyFrame::GetPoseInverse()
{
    unique_lock<mutex> lock(mMutexPose);
    return Twc.clone();
}

        T_{iw}是每个共视关键帧的世界坐标,我们通过下列计算求得T_{ic}即当前关键帧mpCurrentKF到其相连关键帧的变换矩阵。

T_{ic} = T_{iw}*T_{wc}

        由于当前关键帧和其共视关键帧在同一邻域内,我们认为其没有尺度漂移,我们利用这个信息来计算准确的位姿信息:

        世界坐标系到当前帧mpCurrentKF的变换mg2oScw是准确的,我们又计算出了同一尺度下的当前帧到mpCurrentKF它的共视关键帧的变换g2oSic,我们用g2oSic*mg2oScw得到了世界到当前帧的共视关键帧的变换,我们认为这是准确的,将这个信息存储在CorrectedSim3中。

        将没有矫正过的位姿信息存放在CorrectedSim3容器中。

        到此为止,我们得到了CorrectedSim3、CorrectedSim3。

        存储着世界坐标系到当前帧坐标系的变换矩阵(修正过的、没修正过的)。

        得到矫正的当前关键帧的共视关键帧位姿后,修正这些共视关键帧的地图点:

        矫正地图点的操作如下:

        我们遍历每一个mpCurrentKF的共视关键帧pKFi:取出经过位姿传播后的Sim3变换g2oCorrectedSwi(世界坐标系到当前坐标系pKFi变换矩阵的逆矩阵),取出未经过位姿传播的Sim3变换g2oSiw(未经位姿传播的世界坐标系当当前坐标系pKFi的变换矩阵),对于帧pKFi中的每个地图点:

        我们用未修正的变换矩阵g2oSiw将地图点从世界坐标系变换到该帧pKFi的相机坐标系中,再用修正后的变换矩阵g2oCorrectedSwi将地图点从该帧的坐标系转换到世界坐标系中重新得到该地图点在世界坐标系下的坐标cvCorrectedP3Dw。        

        最后更新地图点、记录矫正该地图点的关键帧id,防止重复、记录该地图点所在的关键帧id、更新其平均观测方向以及观测距离范围。

        通过这一步,我们完成了对mpCurrentKF的共视关键帧的地图点的位姿矫正

        现在将共视关键帧的Sim3转换为SE3,根据更新的Sim3,更新关键帧的位姿。其实是现在已经有了更新后的关键帧组中关键帧的位姿,但是在上面的操作时只是暂时存储到了 KeyFrameAndPose 类型的变量中,还没有写回到关键帧对象中。

        就是将关键帧的位姿信息更新:pKFi->SetPose(correctedTiw);

        更新关键帧的连接关系:

        更新变量

        @mConnectedKeyFrameWeights:当前关键帧的共视信息,记录当前关键帧共视关键帧的信息(哪一帧和当前关键帧有共视,共视程度是多少)

        @mvpOrderedConnectedKeyFrames:对mConnectedKeyFrameWeights中超过共视阈值的关键帧(按照共视程度从大到小排序)

        @mvOrderedWeights:对mConnectedKeyFrameWeights中超过共视阈值的关键帧的共视程度(按照共视程度从大到小排序)

        @mpParent:共视程度最高的那个关键帧

        @mspChildrens:建立双向关系,将当前帧的共视程度最高的那个关键帧的子关键帧添加当前帧
        具体讲解详见我的博客:

ORB-SLAM2 --- KeyFrame::UpdateConnections 函数解析https://blog.csdn.net/qq_41694024/article/details/128516249        因此,我们在这步中,更新了mpCurrentKF和其共视关键帧的位姿,更新了mpCurrentKF和其共视关键帧的地图点的信息。

4.3 检查当前帧的地图点与经过闭环匹配后该帧的地图点是否存在冲突,对冲突的进行替换或填补 

        mvpCurrentMatchedPointsmpCurrentKF到其闭环匹配帧的特征点匹配关系。(经过投影匹配后修正过的)

        取出同一个索引对应的两种地图点,决定是否要替换:

        如果有重复的MapPoint,则用匹配的地图点代替现有的,因为匹配的地图点是经过一系列操作后比较精确的,现有的地图点很可能有累计误差。

        如果当前帧没有该MapPoint,则直接添加。

4.4 将闭环相连关键帧组mvpLoopMapPoints 投影到当前关键帧组中,进行匹配,融合,新增或替换当前关键帧组中KF的地图点 

        本步主要是将mvpLoopMapPoints中的地图点(对应图中的最终闭环关键帧和其共视关键帧的)与每一个当前帧的共视关键帧(对应图中黄色的帧CorrectedSim3)进行地图点融合。

        详见我的博客:

ORB-SLAM2 --- LoopClosing::SearchAndFuse函数解析icon-default.png?t=MBR7https://blog.csdn.net/qq_41694024/article/details/128597967

4.5 更新当前关键帧组之间的两级共视相连关系,得到因闭环时地图点融合而新得到的连接关系 

        由于我们在4.4节更新了地图点的观测,因此我们在这要对其进行校正。

        mvpCurrentConnectedKFs存储的是当前关键帧和其共视关键帧的集合。

        遍历当前帧mpCurrentKF相连关键帧组,得到与当前帧相连关键帧的相连关键帧(二级相连)存放在变量vpPreviousNeighbors中,更新一级相连关键帧的连接关系(会把当前关键帧添加进去,因为地图点已经更新和替换了):ORB-SLAM2 --- KeyFrame::UpdateConnections 函数解析icon-default.png?t=MBR7https://blog.csdn.net/qq_41694024/article/details/128516249        通过此函数更新关键帧之间的连接图。

        更新变量

        @mConnectedKeyFrameWeights:当前关键帧的共视信息,记录当前关键帧共视关键帧的信息(哪一帧和当前关键帧有共视,共视程度是多少)

        @mvpOrderedConnectedKeyFrames:对mConnectedKeyFrameWeights中超过共视阈值的关键帧(按照共视程度从大到小排序)

        @mvOrderedWeights:对mConnectedKeyFrameWeights中超过共视阈值的关键帧的共视程度(按照共视程度从大到小排序)

        @mpParent:共视程度最高的那个关键帧

        @mspChildrens:建立双向关系,将当前帧的共视程度最高的那个关键帧的子关键帧添加当前帧

// 得到与该关键帧连接(>15个共视地图点)的关键帧(没有排序的)
set<KeyFrame*> KeyFrame::GetConnectedKeyFrames()
{
    unique_lock<mutex> lock(mMutexConnections);

    set<KeyFrame*> s;
    for(map<KeyFrame*,int>::iterator mit=mConnectedKeyFrameWeights.begin();mit!=mConnectedKeyFrameWeights.end();mit++)
        s.insert(mit->first);
    return s;
}

        用变量map<KeyFrame*, set<KeyFrame*> > LoopConnections存储因为闭环地图点调整而新生成的连接关系。

        如果更新前后有重叠的部分将其删除掉。

4.6 结尾工作

        进行本质图优化,优化本质图中所有关键帧的位姿和地图点。

        添加当前帧与闭环匹配帧之间的边(这个连接关系不优化)。

        新建一个线程用于全局BA优化。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/159210.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是计算机中的高速公路-总线?

文章目录总线是什么&#xff1f;常见总线类型有哪些&#xff1f;总线的串行和并行的区别&#xff1f;数据总线地址总线CPU的寻址能力32位CPU最大支持4G内存&#xff1f;控制总线总线的共享性和独自性系统总线的结构单总线结构双总线结构三总线结构总线传输的四个阶段总线仲裁集…

谷粒商城项目笔记之高级篇(二)

目录1.7 认证服务1.7.1 环境搭建1&#xff09;、创建认证服务微服务2&#xff09;、引入依赖3)、添加相应的域名4&#xff09;、动静分离5&#xff09;、nacos中注册6&#xff09;、配置网关7)、测试访问登录页面8&#xff09;、实现各个页面之间跳转1.7.2 验证码功能1)、验证码…

C++的类介绍(封装特性)

一、类的定义 1.1定义 类是c语言对编程思想的概括深化&#xff0c;其前期的C语言使能面向过程的语言&#xff0c;思想是注重对程序每一步的理解&#xff1b;而面向过程的是C语言之父把生活的类与对象的思想应用于程序设计之中&#xff0c;把程序抽象成一个个对象。 C面向对象…

将时间序列转换为指定的频率并指定填充方法来填充缺失值的DataFrame.asfreq()方法

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 时间序列的插值操作 提升序列中时间密度 DataFrame.asfreq() 选择题 关于以下python代码说法错误的一项是? import pandas as pd myIndexpd.date_range(1/12/2023,periods3,freqT) dfpd.D…

深入了解 LinkedBlockingQueue阻塞队列

1. 前言 今天来逐个方法解析下LinkedBlockingQueue. 其实跟上篇文件深入了解ArrayBlockingQueue 阻塞队列很类似。只不过底层实现不同。其实看名字就能看出来到底依据什么实现的。好了&#xff0c;废话不多说了&#xff0c;接下来让我们开始吧 至于API的使用情况跟ArrayBlockin…

JS垃圾回收

什么是GC GC就是Garbage Collection,程序工作过程中会产生很多垃圾&#xff0c;这些垃圾是程序不用的内存或者是之前用过了&#xff0c;以后不会再用的内存空间&#xff0c;而GC就是负责回收垃圾的。当然也不是所有的语言都会自带GC&#xff0c;比如Java、Python、Javascript等…

电脑修改用户(User)文件夹名称

情景&#xff1a;Windows 11 的用户名与 C 盘&#xff08;系统盘&#xff09;中的文件夹名称不对应&#xff08;可能是由于重装系统导致的&#xff09;&#xff0c;例如我笔记本中系统用户名是 “fly”&#xff0c;但文件夹名称却是“16490”。 Step 1&#xff1a;打开Administ…

智能图像处理:基于边缘去除和迭代式内容矫正的复杂文档图像校正

本文简要介绍ACM MM 2022录用论文“Marior: Margin Removal and Iterative Content Rectification for Document Dewarping in the Wild”的主要工作。该论文针对现有的矫正方法只能在紧密裁剪的文档图像上获得较为理想的矫正效果这一不足&#xff0c;提出了一个新的矫正方法Ma…

基于webrtc多人音视频的研究(一)

众所周知&#xff0c;WebRTC非常适合点对点&#xff08;即一对一&#xff09;的音视频会话。然而&#xff0c;当我们的客户要求超越一对一&#xff0c;即一对多、多对一设置多对多的解决方案或者服务&#xff0c;那么问题就来了&#xff1a;“我们应该采用什么样的架构&#xf…

利用AirTest实现自动安装APK-跳过vivo手机安装验证

利用AirTest实现自动安装APK-跳过vivo手机安装验证 前言 最近在帮测试组看个问题&#xff0c;他们在自动化测试的时候&#xff0c;通过adb install 命令在vivo手机上安装apk的时候出现”外部来源应用&#xff0c;未经vivo安全性和兼容性检测&#xff0c;请谨慎安装“的提示页面…

仅需一行Python代码,为图片上版权!

哈啰&#xff0c;大家好&#xff0c;我是派森酱&#xff0c;一个Python技术爱好者。今天一个朋友跟我吐槽&#xff1a;前段时间&#xff0c;我辛辛苦苦整理的一份XX攻略&#xff0c;分享给自己的一些朋友&#xff0c;结果今天看到有人堂而皇之地拿着这份攻略图片去引流&#xf…

多项目同时进行时,做好进度管理很重要

进行多项目同时进行时&#xff0c;做好进度管理非常重要。最简单的方法是使用项目管理软件&#xff0c;可以帮助你组织和跟踪多项目的进度。 此外&#xff0c;需要定期审核每个项目的进度&#xff0c;并对项目进行必要的调整&#xff0c;以确保每个项目都能按时完成。 1、多项…

1579_AURIX_TC275_MTU中的ECC机理以及各种寄存器实现

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 这一夜的信息全是寄存器地址信息&#xff0c;在了解功能的时候都是非关键信息。后续的内容整理中&#xff0c;这部分类似的信息我都会跳过。 在这个系列的MCU中&#xff0c;ECC实现了单bit…

Angular CLI命令详解

Angular CLI自身操作 显示版本 ng version 或 ng v 这条命令除了显示当前的cli的版本号&#xff0c;还显示LOGO&#xff0c;运行环境等内容&#xff1a; 显示帮助 ng --help 或 ng <sub cmd> --help 比如&#xff1a; ng build --help 如果记不住命令&#x…

数据库,计算机网络、操作系统刷题笔记32

数据库&#xff0c;计算机网络、操作系统刷题笔记32 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle…

BGP-路由反射器、联邦实验(1.11)

目标&#xff1a; 1、首先需要基于该与拓扑图对172.16.0.0/16进行子网划分&#xff1a; 题中一共需要八个网段的环回和一个骨干链路共8个网段&#xff1b; 172.16.0.0 20 骨干 再分为八个&#xff1a; 172.16.0.0 30 172.16.0.4 30 172.16.0.8 30 172.16.0.12 30 172.16.0.…

1、基本数据类型

目录 一、数值类型 1.整数类型 2.浮点数类型 3.复数 4.无穷量&#xff08;Inf&#xff09;和非数值量&#xff08;NaN&#xff09; 二、逻辑类型 一、数值类型 数值类型数据的分类&#xff1a; 注意&#xff1a;在未加说明与特殊定义时&#xff0c;MATLAB对所有数值按照…

如何myabtis使用注解对数据库进行操作呢?

引入&#xff1a; mybatis进行数据查询既可以通过配置xml文件&#xff0c;也可以通过注解&#xff0c;前几篇文章中&#xff0c;我们一直使用的都是通过配置xml文件&#xff0c;对于使用注解并没有详细的解释&#xff0c;只是简单的使用了以下&#xff0c;下面这篇文章&#x…

生命在于学习——信息收集的一些知识(一)

一、checklist 1、域名信息 &#xff08;1&#xff09;备案信息 在线查询&#xff1a; https://beian.miit.gov.cn/#/Integrated/index https://www.tianyancha.com/ http://cha.fute.com/index http://icp.chinaz.com/ 工具查询&#xff1a;无 &#xff08;2&#xff09;企…

事件总线 + 函数计算构建云上最佳事件驱动架构应用

作者 | 史明伟&#xff08;世如&#xff09; 距离阿里云事件总线&#xff08;EventBridge&#xff09;和 Serverless 函数计算&#xff08;Function Compute&#xff0c;FC&#xff09;宣布全面深度集成已经过去一年。站在系统元数据互通&#xff0c;产品深度集成的肩膀上&…