ORB-SLAM2 --- LoopClosing::ComputeSim3 函数

news2025/1/10 20:34:48

目录

1.什么是sim3,为什么要做sim3

2.函数流程 

3.code 

4.函数解析 

4.1 准备工作

4.2 遍历闭环候选帧集,初步筛选出与当前关键帧的匹配特征点数大于20的候选帧集合,并为每一个候选帧构造一个Sim3Solver 

4.3 对每一个候选帧用Sim3Solver 迭代匹配,直到有一个候选帧匹配成功,或者全部失败 

4.4 取出与当前帧闭环匹配上的关键帧及其共视关键帧,以及这些共视关键帧的地图点 

4.5 将闭环关键帧及其连接关键帧的所有地图点投影到当前关键帧进行投影匹配 

4.6 统计当前帧与闭环关键帧的匹配地图点数目,超过40个说明成功闭环,否则失败 


1.什么是sim3,为什么要做sim3

        用三对点求取相似变换。

         为什么需要计算Sim3? 均摊误差、自然过渡。

        当前关键帧、闭环关键帧之间其实是隔了很多帧的,他们的位姿信息都是由邻近信息得到的,经过了很久后,很可能有累计的位姿误差(单目尺度漂移所以有尺度误差,双目或RGBD认为没有尺度误差), 如果你闭环时直接根据各自的位姿强制把相隔了很久的两个位姿接上,很可能会导致明显的错位。 我们用扫描人脸来做个比喻,可以认为开始扫描的脸和最后闭环的脸之间你是直接强制缝合,那很可能两张脸接不上,五官错位。

        用SIM3就是把相隔很久的两个要缝合的关键帧(及其周围关键帧)重新建立连接,做一个软过渡, 尽可能的将累计误差分摊到要缝合的关键帧(及其周围关键帧),也就是闭环调整。相当于我们把要缝合的人脸两侧都做了一定的调整使得缝合不那么生硬,起码看起来更协调。

2.函数流程 

Sim3 计算流程说明:
        1. 通过Bow加速描述子的匹配,利用RANSAC粗略地计算出当前帧与闭环帧的Sim3(当前帧---闭环帧)          
        2. 根据估计的Sim3,对3D点进行投影找到更多匹配,通过优化的方法计算更精确的Sim3(当前帧---闭环帧)   
        3. 将闭环帧以及闭环帧相连的关键帧的地图点与当前帧的点进行匹配(当前帧---闭环帧+相连关键帧)     
        注意以上匹配的结果均都存在成员变量mvpCurrentMatchedPoints中,实际的更新步骤见CorrectLoop()步骤3
        对于双目或者是RGBD输入的情况。计算得到的尺度=1。

3.code 

bool LoopClosing::ComputeSim3()
{
    // Sim3 计算流程说明:
    // 1. 通过Bow加速描述子的匹配,利用RANSAC粗略地计算出当前帧与闭环帧的Sim3(当前帧---闭环帧)          
    // 2. 根据估计的Sim3,对3D点进行投影找到更多匹配,通过优化的方法计算更精确的Sim3(当前帧---闭环帧)   
    // 3. 将闭环帧以及闭环帧相连的关键帧的地图点与当前帧的点进行匹配(当前帧---闭环帧+相连关键帧)     
    // 注意以上匹配的结果均都存在成员变量mvpCurrentMatchedPoints中,实际的更新步骤见CorrectLoop()步骤3
    // 对于双目或者是RGBD输入的情况,计算得到的尺度=1


    //  准备工作
    // For each consistent loop candidate we try to compute a Sim3
    // 对每个(上一步得到的具有足够连续关系的)闭环候选帧都准备算一个Sim3
    const int nInitialCandidates = mvpEnoughConsistentCandidates.size();

    // We compute first ORB matches for each candidate
    // If enough matches are found, we setup a Sim3Solver
    ORBmatcher matcher(0.75,true);

    // 存储每一个候选帧的Sim3Solver求解器
    vector<Sim3Solver*> vpSim3Solvers;
    vpSim3Solvers.resize(nInitialCandidates);

    // 存储每个候选帧的匹配地图点信息
    vector<vector<MapPoint*> > vvpMapPointMatches;
    vvpMapPointMatches.resize(nInitialCandidates);

    // 存储每个候选帧应该被放弃(True)或者 保留(False)
    vector<bool> vbDiscarded;
    vbDiscarded.resize(nInitialCandidates);

    // 完成 Step 1 的匹配后,被保留的候选帧数量
    int nCandidates=0;

    // Step 1. 遍历闭环候选帧集,初步筛选出与当前关键帧的匹配特征点数大于20的候选帧集合,并为每一个候选帧构造一个Sim3Solver
    for(int i=0; i<nInitialCandidates; i++)
    {
        // Step 1.1 从筛选的闭环候选帧中取出一帧有效关键帧pKF
        KeyFrame* pKF = mvpEnoughConsistentCandidates[i];

        // 避免在LocalMapping中KeyFrameCulling函数将此关键帧作为冗余帧剔除
        pKF->SetNotErase();

        // 如果候选帧质量不高,直接PASS
        if(pKF->isBad())
        {
            vbDiscarded[i] = true;
            continue;
        }

        // Step 1.2 将当前帧 mpCurrentKF 与闭环候选关键帧pKF匹配
        // 通过bow加速得到 mpCurrentKF 与 pKF 之间的匹配特征点
        // vvpMapPointMatches 是匹配特征点对应的地图点,本质上来自于候选闭环帧
        int nmatches = matcher.SearchByBoW(mpCurrentKF,pKF,vvpMapPointMatches[i]);

        // 粗筛:匹配的特征点数太少,该候选帧剔除
        if(nmatches<20)
        {
            vbDiscarded[i] = true;
            continue;
        }
        else
        {
            // Step 1.3 为保留的候选帧构造Sim3求解器
            // 如果 mbFixScale(是否固定尺度) 为 true,则是6 自由度优化(双目 RGBD)
            // 如果是false,则是7 自由度优化(单目)
            Sim3Solver* pSolver = new Sim3Solver(mpCurrentKF,pKF,vvpMapPointMatches[i],mbFixScale);

            // Sim3Solver Ransac 过程置信度0.99,至少20个inliers 最多300次迭代
            pSolver->SetRansacParameters(0.99,20,300);
            vpSim3Solvers[i] = pSolver;
        }

        // 保留的候选帧数量
        nCandidates++;
    }

    // 用于标记是否有一个候选帧通过Sim3Solver的求解与优化
    bool bMatch = false;

    // Step 2 对每一个候选帧用Sim3Solver 迭代匹配,直到有一个候选帧匹配成功,或者全部失败
    while(nCandidates>0 && !bMatch)
    {
        // 遍历每一个候选帧
        for(int i=0; i<nInitialCandidates; i++)
        {
            if(vbDiscarded[i])
                continue;

            KeyFrame* pKF = mvpEnoughConsistentCandidates[i];

            // 内点(Inliers)标志
            // 即标记经过RANSAC sim3 求解后,vvpMapPointMatches中的哪些作为内点
            vector<bool> vbInliers; 
        
            // 内点(Inliers)数量
            int nInliers;

            // 是否到达了最优解
            bool bNoMore;

            // Step 2.1 取出从 Step 1.3 中为当前候选帧构建的 Sim3Solver 并开始迭代
            Sim3Solver* pSolver = vpSim3Solvers[i];

            // 最多迭代5次,返回的Scm是候选帧pKF到当前帧mpCurrentKF的Sim3变换(T12)
            cv::Mat Scm  = pSolver->iterate(5,bNoMore,vbInliers,nInliers);

            // If Ransac reachs max. iterations discard keyframe
            // 总迭代次数达到最大限制还没有求出合格的Sim3变换,该候选帧剔除
            if(bNoMore)
            {
                vbDiscarded[i]=true;
                nCandidates--;
            }

            // If RANSAC returns a Sim3, perform a guided matching and optimize with all correspondences
            // 如果计算出了Sim3变换,继续匹配出更多点并优化。因为之前 SearchByBoW 匹配可能会有遗漏
            if(!Scm.empty())
            {
                // 取出经过Sim3Solver 后匹配点中的内点集合
                vector<MapPoint*> vpMapPointMatches(vvpMapPointMatches[i].size(), static_cast<MapPoint*>(NULL));
                for(size_t j=0, jend=vbInliers.size(); j<jend; j++)
                {
                    // 保存内点
                    if(vbInliers[j])
                       vpMapPointMatches[j]=vvpMapPointMatches[i][j];
                }

                // Step 2.2 通过上面求取的Sim3变换引导关键帧匹配,弥补Step 1中的漏匹配
                // 候选帧pKF到当前帧mpCurrentKF的R(R12),t(t12),变换尺度s(s12)
                cv::Mat R = pSolver->GetEstimatedRotation();
                cv::Mat t = pSolver->GetEstimatedTranslation();
                const float s = pSolver->GetEstimatedScale();

                // 查找更多的匹配(成功的闭环匹配需要满足足够多的匹配特征点数,之前使用SearchByBoW进行特征点匹配时会有漏匹配)
                // 通过Sim3变换,投影搜索pKF1的特征点在pKF2中的匹配,同理,投影搜索pKF2的特征点在pKF1中的匹配
                // 只有互相都成功匹配的才认为是可靠的匹配
                matcher.SearchBySim3(mpCurrentKF,pKF,vpMapPointMatches,s,R,t,7.5);

                // Step 2.3 用新的匹配来优化 Sim3,只要有一个候选帧通过Sim3的求解与优化,就跳出停止对其它候选帧的判断
                // OpenCV的Mat矩阵转成Eigen的Matrix类型
                // gScm:候选关键帧到当前帧的Sim3变换
                g2o::Sim3 gScm(Converter::toMatrix3d(R),Converter::toVector3d(t),s);
            
                // 如果mbFixScale为true,则是6 自由度优化(双目 RGBD),如果是false,则是7 自由度优化(单目)
                // 优化mpCurrentKF与pKF对应的MapPoints间的Sim3,得到优化后的量gScm
                const int nInliers = Optimizer::OptimizeSim3(mpCurrentKF, pKF, vpMapPointMatches, gScm, 10, mbFixScale);

                // 如果优化成功,则停止while循环遍历闭环候选
                if(nInliers>=20)
                {
                    // 为True时将不再进入 while循环
                    bMatch = true;
                    // mpMatchedKF就是最终闭环检测出来与当前帧形成闭环的关键帧
                    mpMatchedKF = pKF;

                    // gSmw:从世界坐标系 w 到该候选帧 m 的Sim3变换,都在一个坐标系下,所以尺度 Scale=1
                    g2o::Sim3 gSmw(Converter::toMatrix3d(pKF->GetRotation()),Converter::toVector3d(pKF->GetTranslation()),1.0);

                    // 得到g2o优化后从世界坐标系到当前帧的Sim3变换
                    mg2oScw = gScm*gSmw;
                    mScw = Converter::toCvMat(mg2oScw);
                    mvpCurrentMatchedPoints = vpMapPointMatches;

                    // 只要有一个候选帧通过Sim3的求解与优化,就跳出停止对其它候选帧的判断
                    break;
                }
            }
        }
    }

    // 退出上面while循环的原因有两种,一种是求解到了bMatch置位后出的,另外一种是nCandidates耗尽为0
    if(!bMatch)
    {
        // 如果没有一个闭环匹配候选帧通过Sim3的求解与优化
        // 清空mvpEnoughConsistentCandidates,这些候选关键帧以后都不会在再参加回环检测过程了
        for(int i=0; i<nInitialCandidates; i++)
            mvpEnoughConsistentCandidates[i]->SetErase();
        // 当前关键帧也将不会再参加回环检测了
        mpCurrentKF->SetErase();
        // Sim3 计算失败,退出了
        return false;
    }

    // Step 3:取出与当前帧闭环匹配上的关键帧及其共视关键帧,以及这些共视关键帧的地图点
    // 注意是闭环检测出来与当前帧形成闭环的关键帧 mpMatchedKF
    // 将mpMatchedKF共视的关键帧全部取出来放入 vpLoopConnectedKFs
    // 将vpLoopConnectedKFs的地图点取出来放入mvpLoopMapPoints
    vector<KeyFrame*> vpLoopConnectedKFs = mpMatchedKF->GetVectorCovisibleKeyFrames();

    // 包含闭环匹配关键帧本身,形成一个“闭环关键帧小组“
    vpLoopConnectedKFs.push_back(mpMatchedKF);
    mvpLoopMapPoints.clear();

    // 遍历这个组中的每一个关键帧
    for(vector<KeyFrame*>::iterator vit=vpLoopConnectedKFs.begin(); vit!=vpLoopConnectedKFs.end(); vit++)
    {
        KeyFrame* pKF = *vit;
        vector<MapPoint*> vpMapPoints = pKF->GetMapPointMatches();

        // 遍历其中一个关键帧的所有有效地图点
        for(size_t i=0, iend=vpMapPoints.size(); i<iend; i++)
        {
            MapPoint* pMP = vpMapPoints[i];
            if(pMP)
            {
                // mnLoopPointForKF 用于标记,避免重复添加
                if(!pMP->isBad() && pMP->mnLoopPointForKF!=mpCurrentKF->mnId)
                {
                    mvpLoopMapPoints.push_back(pMP);
                    // 标记一下
                    pMP->mnLoopPointForKF=mpCurrentKF->mnId;
                }
            }
        }
    }

    // Find more matches projecting with the computed Sim3
    // Step 4:将闭环关键帧及其连接关键帧的所有地图点投影到当前关键帧进行投影匹配
    // 根据投影查找更多的匹配(成功的闭环匹配需要满足足够多的匹配特征点数)
    // 根据Sim3变换,将每个mvpLoopMapPoints投影到mpCurrentKF上,搜索新的匹配对
    // mvpCurrentMatchedPoints是前面经过SearchBySim3得到的已经匹配的点对,这里就忽略不再匹配了
    // 搜索范围系数为10
    matcher.SearchByProjection(mpCurrentKF, mScw, mvpLoopMapPoints, mvpCurrentMatchedPoints,10);

    // If enough matches accept Loop
    // Step 5: 统计当前帧与闭环关键帧的匹配地图点数目,超过40个说明成功闭环,否则失败
    int nTotalMatches = 0;
    for(size_t i=0; i<mvpCurrentMatchedPoints.size(); i++)
    {
        if(mvpCurrentMatchedPoints[i])
            nTotalMatches++;
    }

    if(nTotalMatches>=40)
    {
        // 如果当前回环可靠,保留当前待闭环关键帧,其他闭环候选全部删掉以后不用了
        for(int i=0; i<nInitialCandidates; i++)
            if(mvpEnoughConsistentCandidates[i]!=mpMatchedKF)
                mvpEnoughConsistentCandidates[i]->SetErase();
        return true;
    }
    else   
    {
        // 闭环不可靠,闭环候选及当前待闭环帧全部删除
        for(int i=0; i<nInitialCandidates; i++)
            mvpEnoughConsistentCandidates[i]->SetErase();
        mpCurrentKF->SetErase();
        return false;
    }
}

4.函数解析 

4.1 准备工作

        对每个(上一步得到的具有足够连续关系的)闭环候选帧(存储在mvpEnoughConsistentCandidates容器中)都准备算一个Sim3。

        用nInitialCandidates变量保存在DetectLoop中计算出来的回环候选关键帧的数量。

        用vpSim3Solvers容器存储每个闭环候选关键帧的Sim3Solver求解器。

        用vvpMapPointMatches变量(vector<vector<MapPoint*> >)存储每个每个候选帧的匹配地图点信息。

        用vbDiscarded存储每个候选帧应该被放弃(True)或者 保留(False)。

4.2 遍历闭环候选帧集,初步筛选出与当前关键帧的匹配特征点数大于20的候选帧集合,并为每一个候选帧构造一个Sim3Solver 

        遍历每个闭环候选关键帧,为其设置SetNotErase选项避免在LocalMapping中KeyFrameCulling函数将此关键帧作为冗余帧剔除,如果候选帧质量不高,直接PASS。

        通过BoW匹配匹配当前帧 mpCurrentKF 与闭环候选关键帧的特征点匹配信息:

ORB-SLAM2 ---- Frame::ComputeBoW函数解析https://blog.csdn.net/qq_41694024/article/details/128007040ORB-SLAM2 ---- ORBmatcher::SearchByBoW函数解析https://blog.csdn.net/qq_41694024/article/details/126322962       

        用nmatches返回SearchByBoW函数成功匹配到的两帧的特征点个数,用vvpMapPointMatches[i]保存特征点匹配的信息。

        粗筛:匹配的特征点数太少,该候选帧剔除:

        ①将此帧的候选关键帧地位废除vbDiscarded[i] = true;。

        若匹配的特征点满足要求:

        ①为该候选关键帧构造Sim3Solver求解器。

        ②将此求解器复制到vpSim3Solvers求解器容器中。

        返回保留的候选帧数量nCandidates

4.3 对每一个候选帧用Sim3Solver 迭代匹配,直到有一个候选帧匹配成功,或者全部失败 

        我们到这先说几个变量:

        vbDiscarded:候选帧是否被抛弃的向量,我们在4.2节中设置判定条件:如果候选帧质量不高,直接PASS;如果通过BoW粗匹配的特征点数太少,该候选帧剔除。

        bMatch:用于标记是否有一个候选帧通过Sim3Solver的求解与优化

        nCandidates:经过4.2节筛选后的候选帧的数量

        遍历每一个候选帧(DetectLoop传来的mvpEnoughConsistentCandidates容器):

        判断:
        ①该帧是否被标志为被遗弃vbDiscarded。跳过该闭环候选关键帧。

        取出vpSim3Solvers中的关于该帧的Sim3Solver开始迭代,得到的的Scm是候选帧pKF到当前帧mpCurrentKF的Sim3变换(T12),若总迭代次数达到最大限制还没有求出合格的Sim3变换,该候选帧剔除(vbDiscarded设置为true,nCandidates--)。

        如果计算出了Sim3变换,继续匹配出更多点并优化。因为之前 SearchByBoW 匹配可能会有遗漏。利用SearchBySim3函数得到更多特征点匹配:

ORB-SLAM2 --- ORBmatcher::SearchBySim3函数解析icon-default.png?t=MBR7https://blog.csdn.net/qq_41694024/article/details/128585315        用新的匹配来优化 Sim3,只要有一个候选帧通过Sim3的求解与优化,就跳出停止对其它候选帧的判断:

        用g2o优化,若优化后的内点数nInliers>=20,优化成功,则停止while循环遍历闭环候选帧,得到最终的闭环候选关键帧mpMatchedKF,得到g2o优化后从世界坐标系到当前帧的Sim3变换mg2oScw、mScw,得到mvpCurrentMatchedPoints是当前帧mpCurrentKF到最终闭环帧mpMatchedKF的特征点匹配信息。

        退出上面while循环的原因有两种,一种是求解到了bMatch置位后出的,另外一种是nCandidates耗尽为0。

        如果bMatch置位为true,则我们找到了与当前帧mpCurrentKF的闭环关键帧。

        如果bMatch置位为false,我们没有找到闭环候选关键帧,将闭环候选关键帧储存的容器mvpEnoughConsistentCandidates置为空,当前关键帧也将不会再参加回环检测了,向上层函数返回false表示闭环失败。

4.4 取出与当前帧闭环匹配上的关键帧及其共视关键帧,以及这些共视关键帧的地图点 

        将mpMatchedKF共视的关键帧和它自己全部取出来放入 vpLoopConnectedKFs 中,将vpLoopConnectedKFs的地图点取出来放入mvpLoopMapPoints中。

4.5 将闭环关键帧及其连接关键帧的所有地图点投影到当前关键帧进行投影匹配 

/**
 * @brief 根据Sim3变换,将闭环KF及其共视KF的所有地图点(不考虑当前KF已经匹配的地图点)投影到当前KF,生成新的匹配点对
 * 
 * @param[in] pKF               当前KF
 * @param[in] Scw               当前KF和闭环KF之间的Sim3变换
 * @param[in] vpPoints          闭环KF及其共视KF的地图点
 * @param[in] vpMatched         当前KF的已经匹配的地图点
 * @param[in] th                搜索范围
 * @return int                  返回新的成功匹配的点对的数目
 */
int ORBmatcher::SearchByProjection(KeyFrame* pKF, cv::Mat Scw, const vector<MapPoint*> &vpPoints, vector<MapPoint*> &vpMatched, int th)
{
    // Get Calibration Parameters for later projection
    const float &fx = pKF->fx;
    const float &fy = pKF->fy;
    const float &cx = pKF->cx;
    const float &cy = pKF->cy;

    // Decompose Scw
    // Step 1 分解Sim变换矩阵
    // 这里的尺度在Pc归一化时会被约掉。可以理解为投影的时候不需要尺度,因为变换到了射线上,尺度无关
    // 尺度会在后面优化的时候用到
    cv::Mat sRcw = Scw.rowRange(0,3).colRange(0,3);
    const float scw = sqrt(sRcw.row(0).dot(sRcw.row(0)));   // 计算得到尺度s
    cv::Mat Rcw = sRcw/scw;                                 // 保证旋转矩阵行列式为1
    cv::Mat tcw = Scw.rowRange(0,3).col(3)/scw;             // 去掉尺度后的平移向量
    cv::Mat Ow = -Rcw.t()*tcw;                              // 世界坐标系下相机光心坐标

    // Set of MapPoints already found in the KeyFrame
    // 使用set类型,记录前面已经成功的匹配关系,避免重复匹配。并去除其中无效匹配关系(NULL)
    set<MapPoint*> spAlreadyFound(vpMatched.begin(), vpMatched.end());
    spAlreadyFound.erase(static_cast<MapPoint*>(NULL));

    int nmatches=0;

    // For each Candidate MapPoint Project and Match
    // Step 2 遍历闭环KF及其共视KF的所有地图点(不考虑当前KF已经匹配的地图点)投影到当前KF
    for(int iMP=0, iendMP=vpPoints.size(); iMP<iendMP; iMP++)
    {
        MapPoint* pMP = vpPoints[iMP];

        // Discard Bad MapPoints and already found
        // Step 2.1 丢弃坏点,跳过当前KF已经匹配上的地图点
        if(pMP->isBad() || spAlreadyFound.count(pMP))
            continue;

        // Get 3D Coords.
        // Step 2.2 投影到当前KF的图像坐标并判断是否有效
        cv::Mat p3Dw = pMP->GetWorldPos();

        // Transform into Camera Coords.
        cv::Mat p3Dc = Rcw*p3Dw+tcw;

        // Depth must be positive
        // 深度值必须为正
        if(p3Dc.at<float>(2)<0.0)
            continue;

        // Project into Image
        const float invz = 1/p3Dc.at<float>(2);
        const float x = p3Dc.at<float>(0)*invz;
        const float y = p3Dc.at<float>(1)*invz;

        const float u = fx*x+cx;
        const float v = fy*y+cy;

        // Point must be inside the image
        // 在图像范围内
        if(!pKF->IsInImage(u,v))
            continue;

        // Depth must be inside the scale invariance region of the point
        // 判断距离是否在有效距离内
        const float maxDistance = pMP->GetMaxDistanceInvariance();
        const float minDistance = pMP->GetMinDistanceInvariance();
        // 地图点到相机光心的向量
        cv::Mat PO = p3Dw-Ow;
        const float dist = cv::norm(PO);

        if(dist<minDistance || dist>maxDistance)
            continue;

        // Viewing angle must be less than 60 deg
        // 观察角度小于60°
        cv::Mat Pn = pMP->GetNormal();

        if(PO.dot(Pn)<0.5*dist)
            continue;

        // 根据当前这个地图点距离当前KF光心的距离,预测该点在当前KF中的尺度(图层)
        int nPredictedLevel = pMP->PredictScale(dist,pKF);

        // Search in a radius
        // 根据尺度确定搜索半径
        const float radius = th*pKF->mvScaleFactors[nPredictedLevel];

        //  Step 2.3 搜索候选匹配点
        const vector<size_t> vIndices = pKF->GetFeaturesInArea(u,v,radius);

        if(vIndices.empty())
            continue;

        // Match to the most similar keypoint in the radius
        const cv::Mat dMP = pMP->GetDescriptor();

        int bestDist = 256;
        int bestIdx = -1;
        //  Step 2.4 遍历候选匹配点,找到最佳匹配点
        for(vector<size_t>::const_iterator vit=vIndices.begin(), vend=vIndices.end(); vit!=vend; vit++)
        {
            const size_t idx = *vit;
            if(vpMatched[idx])
                continue;

            const int &kpLevel= pKF->mvKeysUn[idx].octave;

            // 不在一个尺度也不行
            if(kpLevel<nPredictedLevel-1 || kpLevel>nPredictedLevel)
                continue;

            const cv::Mat &dKF = pKF->mDescriptors.row(idx);

            const int dist = DescriptorDistance(dMP,dKF);

            if(dist<bestDist)
            {
                bestDist = dist;
                bestIdx = idx;
            }
        }

        // 该MapPoint与bestIdx对应的特征点匹配成功
        if(bestDist<=TH_LOW)
        {
            vpMatched[bestIdx]=pMP;
            nmatches++;
        }

    }
    //  Step 3 返回新的成功匹配的点对的数目
    return nmatches;
}

        这里主要是增加地图点的匹配。

4.6 统计当前帧与闭环关键帧的匹配地图点数目,超过40个说明成功闭环,否则失败 

        如果当前回环可靠,保留当前待闭环关键帧,其他闭环候选全部删掉以后不用了。

        向上层函数返回候选关键帧。

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

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

相关文章

实例分析Linux内存泄漏检测方法

一、mtrace分析内存泄露 mtrace&#xff08;memory trace&#xff09;&#xff0c;是 GNU Glibc 自带的内存问题检测工具&#xff0c;它可以用来协助定位内存泄露问题。它的实现源码在glibc源码的malloc目录下&#xff0c;其基本设计原理为设计一个函数 void mtrace ()&#x…

解决Tinkphp的success跳转“使用路由别名后模块和路由器访问不了”问题

遇到的问题&#xff1a;我的thinkphp5网站添加了以下路由别名&#xff1a;Route::alias([ index>index/index, ]);使用http://域名/Index/user/password.html访问正常但使用http://域名/index/user/password.html就访问失败使用$this->success(修改密码成功);进行提示跳转…

Java 链表与LinkedList

链表的组合形式 ①有头结点、无头结点 ②单向链表、双向链表 ③循环链表、非循环链表 根据自由组合&#xff0c;可以得到8种不同形式的链表&#xff0c;那么在刷题种常碰到的是不带有头结点的单向非循环链表和不带头结点的双向非循环链表。 模拟实现不带头结点的单向非循环链表…

Notes可以手动签名了

大家好&#xff0c;才是真的好。 Notes/Domino 12.0.2陆续有人下载测试了&#xff0c;关于Notes的新功能中&#xff0c;我们上一篇也介绍到了可以手动签名。 字面上的意思&#xff0c;就是你可以调出手写板&#xff0c;然后使用触屏或鼠标来进行签名&#xff0c;可以在Nomad …

javaEE 初阶 — 定时器

文章目录定时器1 什么是定时器2 标准库中定时器3 实现一个定时器3.1 实现的思路3.2 为什么要使用优先级队列来保存任务3.3 开始实现定时器 1 什么是定时器 定时器 类似于一个 “闹钟”&#xff0c;达到一个设定的时间之后&#xff0c;就执行某个指定好的代码。 定时器是一种实…

印染行业APS智能排程排产的应用意义

不得不说的印染之“痛” 在印染行业&#xff0c;因排产无法自动化、智能化&#xff0c;企业在交期、成本、生产管理方面承受着巨大的压力&#xff0c;尤其当下印染企业生产管理正从传统的粗放式转向精细化&#xff0c;这些痛点愈加凸显。 一方面&#xff0c;客户和企业面临一个…

httpd安装

一、离线安装 1、去 https://pkgs.org/ 下载httpd所依赖的7个rpm包 [基于CentOS 7 x86_64系统&#xff0c;如需其他环境可前往官网直接下载] apr-1.4.8-5.el7.x86_64.rpm apr-util-1.5.2-6.el7.x86_64.rpm apr-util-ldap-1.5.2-6.el7.x86_64.rpm postgresql-libs-9.2.24-1.el…

互联互通-标准化成熟度指标分析(未完成)

整体分析1 医疗机构基本情况2 数据资源标准化建设情况&#xff08;30 分&#xff09;2.1数据集标准化情况&#xff08;15 分&#xff09;2.1.1电子病历基本数据集 第1部分&#xff1a;病历概要&#xff08;1-4数据集&#xff09;2.1.2电子病历基本数据集 第2部分&#xff1a;门…

Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(手撕源码)

本文是我去年首发于稀土掘金平台的文章 全文较长&#xff1a;共1万5千字&#xff0c;适合有耐心❤️的人学习 有些概念不懂的可以去4.部分概念详解这个目录先稍微学习一下 Compose源码基于最新的Compose 版本&#xff1a;1.0.1 系统源码基于最新的Android11 版本 注意&#xff…

【蓝桥杯基础题】2020年省赛填空题—回文日期

&#x1f451;专栏内容&#xff1a;蓝桥杯刷题⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;前路未远&#xff0c;步履不停 目录一、题目背景二、题目描述1.问题描述2.输入格式3.输出格式4.一个例子5. 评测用例规模与约定三、题目分析1.获取位数2.回文…

236页10万字精选数据中台建设方案2022版

【版权声明】本资料来源网络&#xff0c;知识分享&#xff0c;仅供个人学习&#xff0c;请勿商用。【侵删致歉】如有侵权请联系小编&#xff0c;将在收到信息后第一时间删除&#xff01;完整资料领取见文末&#xff0c;部分资料内容&#xff1a; 目录 1. 数据中台平台建设方案 …

数据存储大小端 网络字节序

一、概念 大端模式&#xff1a;数据的低位存放在内存的高地址中 小端模式&#xff1a;数据的低位存放在内存的低地址中 二、数据的高低位 首先需要清楚一段数据存储高低位区分 联想记忆最右边为16^0 下来16^1 比如下图二进制为例&#xff1a; 三、内存的高低位 以vs2019为…

JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配

文章目录前言一、排序规范1、happens-before原则2、找文档位置二、对象创建的过程&#xff08;后面回答的就是这几个问题&#xff09;1、一线互联网企业面试题&#xff1a; 关于对象2、对象创建过程三、对象在内存中的存储布局1、1.8版本虚拟机配置2、对象的内存布局a、普通对象…

字节三面:如何设计一个高并发系统

前言 大家好&#xff0c;我是田螺。 记得很久之前&#xff0c;去面试过字节跳动。被三面的面试官问了一道场景设计题目&#xff1a;如何设计一个高并发系统。当时我回答得比较粗糙&#xff0c;最近回想起来&#xff0c;所以整理了设计高并发系统的15个锦囊&#xff0c;相信大…

【EHub_tx1_tx2_E100】Ubuntu18.04 + ROS_ Melodic + 万集716 单线激光 测试

简介&#xff1a;介绍 万集716 单线激光 在EHub_tx1_tx2_E100载板&#xff0c;TX1核心模块环境&#xff08;Ubuntu18.04&#xff09;下测试ROS驱动&#xff0c;打开使用RVIZ 查看点云数据&#xff0c;本文的前提条件是你的TX1里已经安装了ROS版本&#xff1a;Melodic。关于测试…

三、k8s资源管理

文章目录1 k8s资源管理介绍2 YAML语言介绍3 资源管理方式3.1 命令式对象管理3.2 命令式对象配置3.3 声明式对象配置3.4 如何编写YAML1 k8s资源管理介绍 在kubernetes中&#xff0c;所有的内容都抽象为资源&#xff0c;用户需要通过操作资源来管理kubernetes。 kubernetes的本质…

MySQL字符集和排序规则详解

一. 相关概念1. 字符集MySQL提供了多种字符集和排序规则选择&#xff0c;其中字符集设置和数据存储以及客户端与MySQL实例的交互相关&#xff0c;排序规则和字符串的对比规则相关(1). 字符集的设置可以在MySQL实例、数据库、表、列四个级别(2). MySQL设置字符集支持在InnoDB, M…

Git学习:工作流学习实践

文章目录一、前言二、开发过程一、前言 在实践的项目开发过程中&#xff0c;会使用Git或者类似的版本控制工具来管理代码。下面介绍基于Git工具在实际项目开发过程中的使用流程。 如上图所示显示了项目开发的一个简化流程。在开发一个新需求/版本的时候&#xff0c;一般会从主…

筛法求欧拉函数

欧拉函数的定义 在数论中&#xff0c;对正整数n&#xff0c;欧拉函数是小于n的正整数中与n互质的数的数目. 欧拉函数的重要性质 若(即m与n互质)&#xff0c;则若为质数&#xff0c;则若为质数&#xff0c;则对于性质2&#xff0c;若为质数&#xff0c;则小于的个数都互质&am…

北大陈斌Python算法笔记(二)

前言 &#x1f340;作者简介&#xff1a;被吉师散养、喜欢前端、学过后端、练过CTF、玩过DOS、不喜欢java的不知名学生。 &#x1f341;个人主页&#xff1a;红中 &#x1f342;不就是蓝桥杯嘛&#xff0c;干他&#xff01;&#xff01;我堂堂 栈的应用&#xff1a;简单括号匹…