ORBSLAM3 --- 闭环及地图融合线程

本文深入解析ORBSLAM3的闭环检测与地图融合过程,包括闭环检测队列的检查,寻找闭环/融合候选帧,以及地图的纯视觉和惯性模式融合,旨在理解如何提高全局定位的精度和地图一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.闭环及地图合并线程的目的和意义

2.闭环及地图合并流程

3.ORBSLAM3中的闭环与地图融合线程解析

3.1 查看闭环检测队列mlpLoopKeyFrameQueue中有没有关键帧进来

3.2 寻找闭环/融合候选帧

3.3 如果检测到融合(当前关键帧与其他地图有关联), 则合并地图

3.3.1 融合前的操作

3.3.2 纯视觉融合 LoopClosing::MergeLocal

3.3.3 惯性模式下的地图融合

3.4 回环矫正(如果(没有检测到融合)检测到回环,则回环矫正)


1.闭环及地图合并线程的目的和意义

        1.建立更多的中长期数据关联(距离很远的数据帧的数据关联)。寻找闭环/融合候选关键帧、窗口内welding BA、本质图BA、全局BA。
        2.可以将多个子地图连接成一个精确的全局地图。
        3.极大的降低整体的位姿和地图点误差,从而获得全局一致的地图和准确的位姿估计。

闭环和地图合并示意图

        我们沿着圆圈走了一圈到了红色的帧的位置(本来应该回到虚线的原点,但因为累计误差回不到了),我们要做的就是找到当前关键帧pKF和候选关键帧组(绿色的帧)(可以是闭环候选-位于一个地图,融合候选-位于不同地图),通过DBoW匹配。

        窗口内welding BA:在当前关键帧画了一个窗口,候选关键帧也开了一个窗口,将两个窗口闭在一起。

2.闭环及地图合并流程

        对比ORB-SLAM2和ORB-SLAM3闭环线程:

while(1)
{
    // 检查是否有新关键帧
    if(CheckNewKeyFrames())
    {
        // 检测闭环候选关键帧和共视连续性
        if(DetectLoop())
        {
        // 计算Sim3变换[sR|t],在双目/RGBD模式下s=1
            if(ComputeSim3())
            {
                // 闭环矫正融合及位姿图优化
                CorrectLoop();
            }
        }
    }
}
void LoopClosing::Run()
{
    mbFinished =false;

    // 线程主循环
    while(1)
    {

        //NEW LOOP AND MERGE DETECTION ALGORITHM
        //----------------------------

        // Loopclosing中的关键帧是LocalMapping发送过来的,LocalMapping是Tracking中发过来的
        // 在LocalMapping中通过 InsertKeyFrame 将关键帧插入闭环检测队列mlpLoopKeyFrameQueue
        // Step 1 查看闭环检测队列mlpLoopKeyFrameQueue中有没有关键帧进来
        if(CheckNewKeyFrames())
        {
            // 这部分后续未使用
            if(mpLastCurrentKF)
            {
                mpLastCurrentKF->mvpLoopCandKFs.clear();
                mpLastCurrentKF->mvpMergeCandKFs.clear();
            }
#ifdef REGISTER_TIMES
            std::chrono::steady_clock::time_point time_StartPR = std::chrono::steady_clock::now();
#endif
            // Step 2 检测有没有共视的区域
            bool bFindedRegion = NewDetectCommonRegions();

#ifdef REGISTER_TIMES
            std::chrono::steady_clock::time_point time_EndPR = std::chrono::steady_clock::now();

            double timePRTotal = std::chrono::duration_cast<std::chrono::duration<double,std::milli> >(time_EndPR - time_StartPR).count();
            vdPRTotal_ms.push_back(timePRTotal);
#endif
            if(bFindedRegion)
            {
                // Step 3 如果检测到融合(当前关键帧与其他地图有关联), 则合并地图
                if(mbMergeDetected)
                {
                    // 在imu没有初始化就放弃融合
                    if ((mpTracker->mSensor==System::IMU_MONOCULAR || mpTracker->mSensor==System::IMU_STEREO || mpTracker->mSensor==System::IMU_RGBD) &&
                        (!mpCurrentKF->GetMap()->isImuInitialized()))
                    {
                        cout << "IMU is not initilized, merge is aborted" << endl;
                    }
                    else
                    {
                        // 拿到融合帧在自己地图所在坐标系(w2)下的位姿
                        Sophus::SE3d mTmw = mpMergeMatchedKF->GetPose().cast<double>();
                        g2o::Sim3 gSmw2(mTmw.unit_quaternion(), mTmw.translation(), 1.0);
                        // 拿到当前帧在自己地图所在坐标系(w1)下的位姿
                        Sophus::SE3d mTcw = mpCurrentKF->GetPose().cast<double>();
                        g2o::Sim3 gScw1(mTcw.unit_quaternion(), mTcw.translation(), 1.0);
                        // 根据共同区域检测时的Sim3结果得到当前帧在w2下的位姿
                        // mg2oMergeSlw 里存放的是融合候选关键帧所在的世界坐标系w2到当前帧的Sim3位姿
                        // l = c , w2是融合候选关键帧所在的世界坐标系
                        g2o::Sim3 gSw2c = mg2oMergeSlw.inverse();
                        // 这个没有用到 : 融合帧在w1下的位姿
                        g2o::Sim3 gSw1m = mg2oMergeSlw;

                        // 记录焊接变换(Sim3) T_w2_w1 , 这个量实际是两个地图坐标系的关系 T_w2_w1 = T_w2_c * T_c_w1
                        mSold_new = (gSw2c * gScw1);

                        // 如果是imu模式
                        if(mpCurrentKF->GetMap()->IsInertial() && mpMergeMatchedKF->GetMap()->IsInertial())
                        {
                            cout << "Merge check transformation with IMU" << endl;
                            // 如果尺度变换太大, 认为累积误差较大,则放弃融合
                            if(mSold_new.scale()<0.90||mSold_new.scale()>1.1){
                                mpMergeLastCurrentKF->SetErase();
                                mpMergeMatchedKF->SetErase();
                                mnMergeNumCoincidences = 0;
                                mvpMergeMatchedMPs.clear();
                                mvpMergeMPs.clear();
                                mnMergeNumNotFound = 0;
                                mbMergeDetected = false;
                                Verbose::PrintMess("scale bad estimated. Abort merging", Verbose::VERBOSITY_NORMAL);
                                continue;
                            }
                            // If inertial, force only yaw
                            // 如果是imu模式并且完成了初始化,强制将焊接变换的 roll 和 pitch 设为0
                            // 通过物理约束来保证两个坐标轴都是水平的
                            if ((mpTracker->mSensor==System::IMU_MONOCULAR || mpTracker->mSensor==System::IMU_STEREO || mpTracker->mSensor==System::IMU_RGBD) &&
                                   mpCurrentKF->GetMap()->GetIniertialBA1())
                            {
                                Eigen::Vector3d phi = LogSO3(mSold_new.rotation().toRotationMatrix());
                                phi(0)=0;
                                phi(1)=0;
                                mSold_new = g2o::Sim3(ExpSO3(phi),mSold_new.translation(),1.0);
                            }
                        }

                        // 这个变量没有用到
                        mg2oMergeSmw = gSmw2 * gSw2c * gScw1;

                        // 更新mg2oMergeScw
                        mg2oMergeScw = mg2oMergeSlw;

                        //mpTracker->SetStepByStep(true);

                        Verbose::PrintMess("*Merge detected", Verbose::VERBOSITY_QUIET);

#ifdef REGISTER_TIMES
                        std::chrono::steady_clock::time_point time_StartMerge = std::chrono::steady_clock::now();

                        nMerges += 1;
#endif
                        // TODO UNCOMMENT
                        // 如果是imu模式,则开启 Visual-Inertial Map Merging
                        // 如果是纯视觉模式,则开启 Visual-Welding Map Merging
                        if (mpTracker->mSensor==System::IMU_MONOCULAR ||mpTracker->mSensor==System::IMU_STEREO || mpTracker->mSensor==System::IMU_RGBD)
                            MergeLocal2();
                        else
                            MergeLocal();

#ifdef REGISTER_TIMES
                        std::chrono::steady_clock::time_point time_EndMerge = std::chrono::steady_clock::now();

                        double timeMergeTotal = std::chrono::duration_cast<std::chrono::duration<double,std::milli> >(time_EndMerge - time_StartMerge).count();
                        vdMergeTotal_ms.push_back(timeMergeTotal);
#endif

                        Verbose::PrintMess("Merge finished!", Verbose::VERBOSITY_QUIET);
                    }
                    // 记录时间戳
                    vdPR_CurrentTime.push_back(mpCurrentKF->mTimeStamp);
                    vdPR_MatchedTime.push_back(mpMergeMatchedKF->mTimeStamp);
                    // 标记Place recognition结果为地图融合
                    vnPR_TypeRecogn.push_back(1);

                    // Reset all variables
                    // 重置所有融合相关变量
                    mpMergeLastCurrentKF->SetErase();
                    mpMergeMatchedKF->SetErase();
                    mnMergeNumCoincidences = 0;
                    mvpMergeMatchedMPs.clear();
                    mvpMergeMPs.clear();
                    mnMergeNumNotFound = 0;
                    mbMergeDetected = false;

                    // 重置所有回环相关变量, 说明对与当前帧同时有回环和融合的情况只进行融合
                    if(mbLoopDetected)
                    {
                        // Reset Loop variables
                        mpLoopLastCurrentKF->SetErase();
                        mpLoopMatchedKF->SetErase();
                        mnLoopNumCoincidences = 0;
                        mvpLoopMatchedMPs.clear();
                        mvpLoopMPs.clear();
                        mnLoopNumNotFound = 0;
                        mbLoopDetected = false;
                    }

                }

                // Step 4 如果(没有检测到融合)检测到回环, 则回环矫正
                if(mbLoopDetected)
                {
                    // 标记时间戳
                    bool bGoodLoop = true;
                    vdPR_CurrentTime.push_back(mpCurrentKF->mTimeStamp);
                    vdPR_MatchedTime.push_back(mpLoopMatchedKF->mTimeStamp);
                    vnPR_TypeRecogn.push_back(0);

                    Verbose::PrintMess("*Loop detected", Verbose::VERBOSITY_QUIET);
                    // 更新 mg2oLoopScw
                    mg2oLoopScw = mg2oLoopSlw; //*mvg2oSim3LoopTcw[nCurrentIndex];
                    // 如果是带imu的模式则做下判断,纯视觉跳过
                    if(mpCurrentKF->GetMap()->IsInertial())
                    {
                        // 拿到当前关键帧相对于世界坐标系的位姿
                        Sophus::SE3d Twc = mpCurrentKF->GetPoseInverse().cast<double>();
                        g2o::Sim3 g2oTwc(Twc.unit_quaternion(),Twc.translation(),1.0);

                        // mg2oLoopScw是通过回环检测的Sim3计算出的回环矫正后的当前关键帧的初始位姿, Twc是当前关键帧回环矫正前的位姿.
                        // g2oSww_new 可以理解为correction
                        g2o::Sim3 g2oSww_new = g2oTwc*mg2oLoopScw;

                        // 拿到 roll ,pitch ,yaw
                        Eigen::Vector3d phi = LogSO3(g2oSww_new.rotation().toRotationMatrix());
                        cout << "phi = " << phi.transpose() << endl; 
                        // 这里算是通过imu重力方向验证回环结果, 如果pitch或roll角度偏差稍微有一点大,则回环失败. 对yaw容忍比较大(20度)
                        if (fabs(phi(0))<0.008f && fabs(phi(1))<0.008f && fabs(phi(2))<0.349f)
                        {
                            // 如果是imu模式
                            if(mpCurrentKF->GetMap()->IsInertial())
                            {
                                // If inertial, force only yaw
                                // 如果是imu模式,强制将焊接变换的的 roll 和 pitch 设为0
                                if ((mpTracker->mSensor==System::IMU_MONOCULAR ||mpTracker->mSensor==System::IMU_STEREO || mpTracker->mSensor==System::IMU_RGBD) &&
   
<think>好的,用户想了解ORB-SLAM3算法的实现原理、代码示例使用教。首先,我需要回忆ORB-SLAM3的基本结构。ORB-SLAM3是一个多系统的SLAM框架,支持单目、双目RGB-D相机,还能处理IMU数据。它基于特征点,使用ORB特征,这点之前的版本类似,但加入了IMU紧耦合地图集管理。 接下来,实现原理部分应该分模块说明。比如系统架构、特征提取、地图初始化、跟踪与优化、闭环检测地图融合。需要强调ORB特征的优势,比如旋转不变性计算效率。同时,IMU的紧耦合如何处理,可能是预积分或者优化过中的IMU约束。 关于代码示例,用户可能需要一个简单的例子,比如如何初始化系统、读取图像运行跟踪。不过ORB-SLAM3本身是C++项目,代码结构比较复杂,可能需要指导用户去官方仓库查看。另外,要提醒用户安装依赖项,比如OpenCV、Eigen、Pangolin等。 使用教方面,需要分步骤:环境配置、下载代码、安装依赖、编译运行,可能还要提一下数据集的使用,比如EuRoC或KITTI。用户可能遇到的问题比如依赖项版本不兼容,编译错误,这时候要给出常见解决办法,或者建议查看官方文档社区。 然后,根据用户提供的格式,需要生成相关问题。可能的问题包括ORB-SLAM3的改进点、与VINS-MONO的区别、实际部署中的挑战、如何添加新传感器等。这些问题能帮助用户深入理解。 还要注意引用格式,可能引用ORB-SLAM3的论文或者官方文档。比如在提到多传感器融合时引用相关部分。同时,确保所有数学公式用正确的LaTeX格式,比如视觉重投影误差IMU预积分的公式需要用$$...$$包围,行内变量用$...$。 最后,检查内容是否准确,避免错误信息。比如ORB-SLAM3确实支持视觉惯性紧耦合,而地图集管理是其创新点之一。代码示例部分可能需要简化,因为完整的代码太复杂,这里可以指导用户参考GitHub的具体文件,比如System.cc或Tracking.cc。 总结:结构上先讲原理,分模块说明,然后代码示例给出关键部分,使用教分步骤,最后生成相关问题。确保符合用户的中文需求,并且技术细节正确。</think>### ORB-SLAM3算法实现原理 ORB-SLAM3是首个支持单目、双目、RGB-D相机地图管理的视觉惯性SLAM系统[^1],其核心原理包括: 1. **多传感器融合** 通过视觉特征(ORB)与IMU数据紧耦合,提升运动估计精度。视觉重投影误差与IMU预积分误差联合优化: $$E = \sum \rho \left( \| e_{visual} \|^2_{\Sigma_v} \right) + \sum \rho \left( \| e_{imu} \|^2_{\Sigma_i} \right)$$ 其中$\rho$为鲁棒核函数,$\Sigma$为协方差矩阵[^2]。 2. **地图集(Atlas)管理** 动态维护多个子地图,支持场景切换长期鲁棒性。通过DBoW2词袋模型实现跨地图闭环检测[^3]。 3. **跟踪与优化** - **跟踪线**:基于稀疏直接法快速定位相机位姿 - **局部建图**:BA优化局部地图点与关键帧 - **闭环修正**:使用位姿图优化全局一致性 --- ### 代码示例(关键模块) ORB-SLAM3代码库包含以下核心文件: ```cpp // 1. 系统初始化(System.cc) System::System(const string &strSettingsFile, eSensor sensor) { mpVocabulary = new ORBVocabulary(); mpVocabulary->loadFromTextFile(strVocFile); mpMapAtlas = new Atlas(); } // 2. 特征提取(ORBextractor.cc) void ORBextractor::operator()(cv::Mat image, vector<KeyPoint>& keypoints) { cv::FAST(image, keypoints, ThFAST, true); computeOrientation(image, keypoints); } ``` 完整代码需参考[官方仓库](https://github.com/UZ-SLAMLab/ORB_SLAM3)--- ### 使用教 #### 环境配置 1. 安装依赖: ```bash sudo apt-get install libopencv-dev libeigen3-dev libpangolin-dev ``` 2. 克隆代码库: ```bash git clone https://github.com/UZ-SLAMLab/ORB_SLAM3.git ``` 3. 编译: ```bash chmod +x build.sh ./build.sh ``` #### 运行示例(EuRoC数据集) ```bash ./Examples/Monocular/mono_euroc \ Vocabulary/ORBvoc.txt \ Examples/Monocular/EuRoC.yaml \ PATH_TO_DATASET/MH_01_easy \ Dataset-SLAM ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

APS2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值