ORB-SLAM2 特征点法SLAM 单目 双目 rgbd相机SLAM 单应/本质矩阵恢复运动 小图大图地图优化

article/2025/9/11 8:56:43

ORB-SLAM2 ORB特征点法SLAM 支持单目、双目、rgbd相机

安装测试

本文github链接

orbslam2 + imu

ORB-SLAM是一个基于特征点的实时单目SLAM系统,在大规模的、小规模的、室内室外的环境都可以运行。
该系统对剧烈运动也很鲁棒,支持宽基线的闭环检测和重定位,包括全自动初始化。
该系统包含了所有SLAM系统共有的模块:跟踪(Tracking)、建图(Mapping)、重定位(Relocalization)、闭环检测(Loop closing)。
由于ORB-SLAM系统是基于特征点的SLAM系统,故其能够实时计算出相机的轨线,并生成场景的稀疏三维重建结果。
ORB-SLAM2在ORB-SLAM的基础上,还支持标定后的双目相机和RGB-D相机。

系统框架

贡献

1. 相关论文:

ORB-SLAM 单目Monocular特征点法

ORB-SLAM2 单目双目rgbd

词袋模型DBoW2 Place Recognizer

原作者目录:

Raul Mur-Artal

Juan D. Tardos,

J. M. M. Montiel

Dorian Galvez-Lopez

(DBoW2)

2. 简介

ORB-SLAM2 是一个实时的 SLAM  库,
可用于 **单目Monocular**, **双目Stereo** and **RGB-D** 相机,
用来计算 相机移动轨迹 camera trajectory 以及稀疏三维重建sparse 3D reconstruction 。在 **双目Stereo** 和 **RGB-D** 相机 上的实现可以得到真是的 场景尺寸稀疏三维 点云图
可以实现 实时回环检测detect loops 、相机重定位relocalize the camera 。 
提供了在  

KITTI 数据集 上运行的 SLAM 系统实例,支持双目stereo 、单目monocular。

TUM 数据集 上运行的实例,支持 RGB-D相机 、单目相机 monocular,

EuRoC 数据集支持 双目相机 stereo 、单目相机 monocular.

也提供了一个 ROS 节点 实时运行处理  单目相机 monocular, 双目相机stereo 以及 RGB-D 相机 数据流。
提供一个GUI界面 可以来 切换 SLAM模式 和重定位模式

**支持的模式:

1. SLAM Mode  建图定位模式默认的模式,三个并行的线程: 跟踪Tracking, 局部建图 Local Mapping 以及 闭环检测Loop Closing. 定位跟踪相机localizes the camera, 建立新的地图builds new map , 检测到过得地方 close loops.2. Localization Mode 重定位模式适用与在工作地方已经有一个好的地图的情况下。执行 局部建图 Local Mapping 以及 闭环检测Loop Closing 两个线程.  使用重定位模式,定位相机

3. 系统工作原理

可以看到ORB-SLAM主要分为三个线程进行,
也就是论文中的下图所示的,

分别是Tracking、LocalMapping和LoopClosing。
ORB-SLAM2的工程非常清晰漂亮,
三个线程分别存放在对应的三个文件中,
分别是:
Tracking.cpp、
LocalMapping.cpp 和
LoopClosing.cpp 文件中,很容易找到。 

A. 跟踪(Tracking)

  前端位姿跟踪线程采用恒速模型,并通过优化重投影误差优化位姿,这一部分主要工作是从图像中提取ORB特征,根据上一帧进行姿态估计,或者进行通过全局重定位初始化位姿,然后跟踪已经重建的局部地图,优化位姿,再根据一些规则确定新的关键帧。

B. 建图(LocalMapping)

  局部地图线程通过MapPoints维护关键帧之间的共视关系,通过局部BA优化共视关键帧位姿和MapPoints,这一部分主要完成局部地图构建。包括对关键帧的插入,验证最近生成的地图点并进行筛选,然后生成新的地图点,使用局部捆集调整(Local BA),最后再对插入的关键帧进行筛选,去除多余的关键帧。

C. 闭环检测(LoopClosing)

  闭环检测线程通过bag-of-words加速闭环匹配帧的筛选,并通过Sim3优化尺度,通过全局BA优化Essential Graph和MapPoints,这一部分主要分为两个过程:分别是: 闭环探测 和 闭环校正。闭环检测: 先使用WOB进行探测,然后通过Sim3算法计算相似变换。闭环校正:主要是闭环融合和Essential Graph的图优化。

D. 重定位 Localization

使用bag-of-words加速匹配帧的筛选,并使用EPnP算法完成重定位中的位姿估计。

4. 代码分析

ORB-SLAM2详解(二)代码逻辑

4.1 应用程序框架

单目相机app框架:

        1. 创建 单目ORB_SLAM2::System SLAM 对象2. 载入图片 或者 相机捕获图片 im = cv::imread();3. 记录时间戳 tframe ,并计时,std::chrono::steady_clock::now();    // c++11std::chrono::monotonic_clock::now();4. 把图像和时间戳 传给 SLAM系统, SLAM.TrackMonocular(im,tframe); 5. 计时结束,计算时间差,处理时间。6. 循环2-5步。7. 结束,关闭slam系统,关闭所有线程 SLAM.Shutdown();8. 保存相机轨迹, SLAM.SaveKeyFrameTrajectoryTUM("KeyFrameTrajectory.txt");

双目相机app程序框架:

	1. 读取相机配置文件(内参数 畸变矫正参数 双目对齐变换矩阵) =======================cv::FileStorage fsSettings(setting_filename, cv::FileStorage::READ);fsSettings["LEFT.K"] >> K_l;//内参数fsSettings["LEFT.D"] >> D_l;// 畸变矫正fsSettings["LEFT.P"] >> P_l;// P_l,P_r --左右相机在校准后坐标系中的投影矩阵 3×4fsSettings["LEFT.R"] >> R_l;// R_l,R_r --左右相机校准变换(旋转)矩阵  3×32. 计算双目矫正映射矩阵========================================================cv::Mat M1l,M2l,M1r,M2r;cv::initUndistortRectifyMap(K_l,D_l,R_l,P_l.rowRange(0,3).colRange(0,3),cv::Size(cols_l,rows_l),CV_32F,M1l,M2l);cv::initUndistortRectifyMap(K_r,D_r,R_r,P_r.rowRange(0,3).colRange(0,3),cv::Size(cols_r,rows_r),CV_32F,M1r,M2r);3. 创建双目系统=============================================================== ORB_SLAM2::System SLAM(vocabulary_filepath, setting_filename, ORB_SLAM2::System::STEREO, true);4. 从双目设备捕获图像,设置分辨率捕获图像========================================cv::VideoCapture CapAll(deviceid); //打开相机设备 //设置分辨率   1280*480  分成两张 640*480  × 2 左右相机CapAll.set(CV_CAP_PROP_FRAME_WIDTH,1280);  CapAll.set(CV_CAP_PROP_FRAME_HEIGHT, 480); 5. 获取左右相机图像===========================================================CapAll.read(src_img);imLeft  = src_img(cv::Range(0, 480), cv::Range(0, 640));   imRight = src_img(cv::Range(0, 480), cv::Range(640, 1280));   6. 使用2步获取的双目矫正映射矩阵 矫正 左右相机图像==============================cv::remap(imLeft,imLeftRect,M1l,M2l,cv::INTER_LINEAR);cv::remap(imRight,imRightRect,M1r,M2r,cv::INTER_LINEAR);7. 记录时间戳 time ,并计时===================================================#ifdef COMPILEDWITHC11std::chrono::steady_clock::time_point    t1 = std::chrono::steady_clock::now();#elsestd::chrono::monotonic_clock::time_point t1 = std::chrono::monotonic_clock::now();#endif8. 把左右图像和时间戳 传给 SLAM系统===========================================SLAM.TrackStereo(imLeftRect, imRightRect, time);9. 计时结束,计算时间差,处理时间============================================= 10.循环执行 5-9步              =============================================11.结束,关闭slam系统,关闭所有线程===========================================   12.保存相机轨迹                   ========================================== 

ORB_SLAM2::System SLAM 对象框架:

        在主函数中,我们创建了一个ORB_SLAM2::System的对象SLAM,这个时候就会进入到SLAM系统的主接口System.cc。这个代码是所有调用SLAM系统的主入口,在这里,我们将看到前面博客所说的ORB_SLAM的三大模块:Tracking、LocalMapping 和 LoopClosing。System类的初始化函数:1. 创建字典 mpVocabulary = new ORBVocabulary();并从文件中载入字典 mpVocabulary = new ORBVocabulary();           // 创建关键帧字典数据库// 读取 txt格式或者bin格式的 orb特征字典, mpVocabulary->loadFromTextFile(strVocFile);   // txt格式mpVocabulary->loadFromBinaryFile(strVocFile); // bin格式2. 使用特征字典mpVocabulary 创建关键帧数据库 mpKeyFrameDatabase = new KeyFrameDatabase(*mpVocabulary);3. 创建地图对象 mpMap mpMap = new Map();4. 创建地图显示(mpMapDrawer) 帧显示(mpFrameDrawer) 两个显示窗口   mpMapDrawer = new MapDrawer(mpMap, strSettingsFile);//地图显示mpFrameDrawer = new FrameDrawer(mpMap);//关键帧显示5. 初始化 跟踪线程(mpTracker) 对象 未启动mpTracker = new Tracking(this, mpVocabulary, mpFrameDrawer, mpMapDrawer,mpMap, mpKeyFrameDatabase, strSettingsFile, mSensor); 6. 初始化 局部地图构建线程(mptLocalMapping) 并启动线程mpLocalMapper = new LocalMapping(mpMap, mSensor==MONOCULAR);mptLocalMapping = new thread(&ORB_SLAM2::LocalMapping::Run,mpLocalMapper);7. 初始化 闭环检测线程(mptLoopClosing) 并启动线程mpLoopCloser = new LoopClosing(mpMap, mpKeyFrameDatabase, mpVocabulary, mSensor!=MONOCULAR);mptLoopClosing = new thread(&ORB_SLAM2::LoopClosing::Run, mpLoopCloser);8. 初始化 跟踪线程可视化 并启动mpViewer = new Viewer(this, mpFrameDrawer,mpMapDrawer,mpTracker,strSettingsFile);mptViewer = new thread(&Viewer::Run, mpViewer);mpTracker->SetViewer(mpViewer);9. 线程之间传递指针 Set pointers between threadsmpTracker->SetLocalMapper(mpLocalMapper);   // 跟踪线程 关联 局部建图和闭环检测线程mpTracker->SetLoopClosing(mpLoopCloser);mpLocalMapper->SetTracker(mpTracker);       // 局部建图线程 关联 跟踪和闭环检测线程mpLocalMapper->SetLoopCloser(mpLoopCloser);mpLoopCloser->SetTracker(mpTracker);        // 闭环检测线程 关联 跟踪和局部建图线程mpLoopCloser->SetLocalMapper(mpLocalMapper);
    如下图所示: 

单目跟踪SLAM.TrackMonocular()框架

	1. 模式变换的检测  跟踪+定位  or  跟踪+定位+建图2. 检查跟踪tracking线程重启3. 单目跟踪mpTracker->GrabImageMonocular(im,timestamp);// Tracking.cc中// 图像转换成灰度图,创建帧Frame对象(orb特征提取等)// 使用Track()进行跟踪: 0. 单目初始化(最开始执行) MonocularInitialization();// 单目初始化a. 两帧跟踪得到初始化位姿(跟踪上一帧/跟踪参考帧/重定位)b. 跟踪局部地图,多帧局部地图G2O优化位姿, 新建关键帧

双目跟踪System::TrackStereo()框架

	1. 模式变换的检测  跟踪+定位  or  跟踪+定位+建图2. 检查跟踪tracking线程重启3. 双目跟踪mpTracker->GrabImageStereo(imLeft,imRight,timestamp); // Tracking.cc中// 图像转换成灰度图,创建帧Frame对象(orb特征提取器,分块特征匹配,视差计算深度)// 使用Track()进行跟踪:0. 双目初始化(最开始执行) StereoInitialization();// 双目 / 深度初始化a. 两帧跟踪得到初始化位姿(跟踪上一帧/跟踪参考帧/重定位)b. 跟踪局部地图,多帧局部地图G2O优化位姿, 新建关键帧

深度相机跟踪System::TrackRGBD()框架

	1. 模式变换的检测  跟踪+定位  or  跟踪+定位+建图2. 检查跟踪tracking线程重启3. 双目跟踪mpTracker->GrabImageRGBD(im,depthmap,timestamp); // Tracking.cc中// 图像转换成灰度图,创建帧Frame对象(orb特征提取器,深度图初始化特征点深度)// 使用Track()进行跟踪: 0. RGBD初始化(最开始执行) StereoInitialization();// 双目 / 深度初始化a. 两帧跟踪得到初始化位姿(跟踪上一帧/跟踪参考帧/重定位)b. 跟踪局部地图,多帧局部地图G2O优化位姿, 新建关键帧

4.2 单目/双目/RGBD初始化, 单应变换/本质矩阵恢复3d点,创建初始地图

参考

1. 单目初始化 Tracking::MonocularInitialization()

系统的第一步是初始化,ORB_SLAM使用的是一种自动初始化方法。
这里同时计算两个模型:
1. 用于 平面场景的单应性矩阵H( 4对 3d-2d点对,线性方程组,奇异值分解) 
2. 用于 非平面场景的基础矩阵F(8对 3d-2d点对,线性方程组,奇异值分解),

推到 参考 单目slam基础

然后通过一个评分规则来选择合适的模型,恢复相机的旋转矩阵R和平移向量t。
函数调用关系:
Tracking::GrabImageMonocular() 创建帧对象(第一帧提取orb特征点数量较多,为后面帧的两倍) -> 
Tracking::Track() ->  初始化 MonocularInitialization();// 单目初始化|  1. 第一帧关键点个数超过 100个,进行初始化  mpInitializer = new Initializer(mCurrentFrame,1.0,200);|  2. 第二帧关键点个数 小于100个,删除初始化器,跳到第一步重新初始化。|  3. 第二帧关键点个数 也大于100个(只有连续的两帧特征点 均>100 个才能够成功构建初始化器)|     构建 两两帧 特征匹配器    ORBmatcher::ORBmatcher matcher(0.9,true)|     金字塔分层块匹配搜索匹配点对  100为搜索窗口大小尺寸尺度 |     int nmatches=matcher.SearchForInitialization(mInitialFrame,mCurrentFrame,mvbPrevMatched,mvIniMatches,100);|  4. 如果两帧匹配点对过少(nmatches<100),跳到第一步,重新初始化。|  5. 匹配点数量足够多(nmatches >= 100),进行单目初始化:|     第一帧位置设置为:单位阵 Tcw = cv::Mat::eye(4,4,CV_32F);|     使用 单应性矩阵H 和 基础矩阵F 同时计算两个模型,通过一个评分规则来选择合适的模型,|     恢复第二帧相机的旋转矩阵Rcw 和 平移向量 tcw,同时 三角变换得到 部分三维点 mvIniP3D|     mpInitializer->Initialize(mCurrentFrame, mvIniMatches, Rcw, tcw, mvIniP3D, vbTriangulated))|  6. 设置初始参考帧的世界坐标位姿态:|     mInitialFrame.SetPose(cv::Mat::eye(4,4,CV_32F));|  7. 设置第二帧(当前帧)的位姿|     cv::Mat Tcw = cv::Mat::eye(4,4,CV_32F);|     Rcw.copyTo(Tcw.rowRange(0,3).colRange(0,3));|     tcw.copyTo(Tcw.rowRange(0,3).col(3)); |     mCurrentFrame.SetPose(Tcw);|  8. 创建初始地图 使用 最小化重投影误差BA 进行 地图优化 优化位姿 和地图点|     Tracking::CreateInitialMapMonocular()||-> 后面帧的跟踪 -> 两两帧的跟踪得到初始位姿|  1. 有运动速度,使用恒速运动模式跟踪上一帧   Tracking::TrackWithMotionModel()|  2. 运动量小或者 1.跟踪失败,跟踪参考帧模式  Tracking::TrackReferenceKeyFrame()|  3. 1 和 2都跟踪失败的话,使用重定位跟踪,跟踪可视参考帧群 Tracking::Relocalization()| 之后 -> 跟踪局部地图(一级二级相连关键帧), 使用图优化对位姿进行精细化调整 Tracking::TrackLocalMap()|-> 判断是否需要新建关键帧   Tracking::NeedNewKeyFrame();  Tracking::CreateNewKeyFrame();||-> 跟踪失败后的处理

初始化时,orb匹配点的搜索 ORBmatcher::SearchForInitialization()

金字塔分层分块orb特征匹配,然后通过三个限制,滤除不好的匹配点对:1. 最小值阈值限制;2. 最近匹配距离一定比例小于次近匹配距离;3. 特征方向误差一致性判断(方向差直方图统计,保留3个最高的方向差一致性最多的点,一致性较大)ORBmatcher::ComputeThreeMaxima(); // 统计数组中最大 的几个数算法,参考性较大ORBmatcher.cc 1912行 优秀的算法值得参考
步骤:步骤1:为帧1的每一个关键点在帧2中寻找匹配点(同一金字塔层级,对应位置方块内的点,多个匹配点)Frame::GetFeaturesInArea()步骤2:计算 1对多 匹配点对描述子之间的距离(二进制变量差异)ORBmatcher::DescriptorDistance(d1,d2); // 只计算了前八个 二进制位 的差异ORBmatcher.cc 1968行 优秀的算法值得参考步骤3:保留最小和次小距离对应的匹配点ORBmatcher.cc 570行  优秀的算法值得参考步骤4:确保最小距离小于阈值 50步骤5:确保最佳匹配比次佳匹配明显要好,那么最佳匹配才真正靠谱,并统计方向差值直方图步骤6:特征方向误差一致性判断(方向差直方图统计,保留3个最高的方向差一致性最多的点,一致性较大)步骤7:更新匹配信息,用最新的匹配更新之前记录的匹配

单目位姿恢复分析 Initializer::Initialize(mCurrentFrame, mvIniMatches, Rcw, tcw, mvIniP3D, vbTriangulated))

步骤1:根据 matcher.SearchForInitialization 得到的初始匹配点对,筛选后得到好的特征匹配点对
步骤2:在所有匹配特征点对中随机选择8对特征匹配点对为一组,共选择 mMaxIterations 组
步骤3:调用多线程分别用于计算fundamental matrix(基础矩阵F) 和 homography(单应性矩阵)Initializer::FindHomography();   得分为SHInitializer::FindFundamental();  得分为SF
步骤4:计算评价得分 RH,用来选取某个模型float RH = SH / (SH + SF);// 计算 选着标志
步骤5:根据评价得分,从单应矩阵H 或 基础矩阵F中恢复R,tif(RH>0.40)// 更偏向于 平面  使用  单应矩阵恢复return ReconstructH(vbMatchesInliersH,H,mK,R21,t21,vP3D,vbTriangulated,1.0,50);else //if(pF_HF>0.6) // 偏向于非平面  使用 基础矩阵 恢复return ReconstructF(vbMatchesInliersF,F,mK,R21,t21,vP3D,vbTriangulated,1.0,50);

0. 2D-2D配点对求变换矩阵前先进行标准化处理去 均值后再除以绝对矩 Initializer::Normalize()

步骤1:计算两坐标的均值mean_x  =  sum(ui) / N ;mean_y  =  sum(vi) / N;
步骤2:计算绝对局倒数绝对矩:mean_x_dev = sum(abs(ui - mean_x))/ N ;mean_y_dev = sum(abs(vi - mean_y))/ N ; 绝对矩倒数:sX = 1/mean_x_dev sY = 1/mean_y_dev
步骤3:计算标准化后的点坐标ui' = (ui - mean_x) * sXvi' = (vi - mean_y) * sY 
步骤4:计算并返回标准化矩阵 ui' = (ui - mean_x) * sX =  ui * sX + vi * 0  + (-mean_x * sX) * 1vi' = (vi - mean_y) * sY =  ui * 0  + vi * sY + (-mean_y * sY) * 1   1   =                       ui * 0  + vi * 0  +      1         * 1可以得到:ui'     sX  0   (-mean_x * sX)      uivi' =   0   sY  (-mean_y * sY)   *  vi1       0   0        1              1标准化后的的坐标 = 标准化矩阵T * 原坐标所以标准化矩阵:T =  sX  0   (-mean_x * sX) 0   sY  (-mean_y * sY)0   0        1 而由标准化坐标 还原 回 原坐标(左乘T 逆):原坐标 = 标准化矩阵T 逆矩阵 * 标准化后的的坐标再者用于还原 单应矩阵 下面需要用到:p1'  ------> Hn -------> p2'   , p2'   = Hn*p1'T1*p1 -----> Hn -------> T2*p2 , T2*p2 = Hn*(T1*p1)左乘 T2逆 ,得到   p2 = T2逆 * Hn*(T1*p1)= H21i*p1H21i = T2逆 * Hn * T1

a. fundamental matrix(基础矩阵F) 随机采样 找到最好的 基础矩阵 Initializer::FindFundamental()

思想:
计算基础矩阵F,随机采样序列8点法,采用归一化的直接线性变换(normalized DLT)求解,
极线几何约束(current frame 1 变换到 reference frame 2),
在最大迭代次数内调用 ComputeF21计算F,
使用 CheckFundamental 计算此 基础矩阵的得分,
在最大迭代次数内保留最高得分的基础矩阵F。步骤:
步骤1:将两帧上对应的2d-2d匹配点对进行归一化Initializer::Normalize(mvKeys1,vPn1, T1);//  mvKeys1原坐标 vPn1归一化坐标,T1标准化矩阵Initializer::Normalize(mvKeys2,vPn2, T2);// cv::Mat T2inv = T2.inv();// 标准化矩阵 逆矩阵
步骤2:在最大迭代次数mMaxIterations内,从标准化后的点中随机选取8对点对int idx = mvSets[it][j];//随机数集合 总匹配点数范围内vPn1i[j] = vPn1[mvMatches12[idx].first];vPn2i[j] = vPn2[mvMatches12[idx].second]; 
步骤3:通过标准化逆矩阵 和 标准化点对的基础矩阵 计算原点对的 基础矩阵   cv::Mat Fn = ComputeF21(vPn1i,vPn2i);// 计算 标准化后的点对 对应的 基础矩阵 Fncv::Mat T2t = T2.t(); // 标准化矩阵的 转置矩阵推到:x2 = R*x1 + tt 叉乘 x2 = t 叉乘 R*x1x2转置 * t 叉乘 x2 = x2转置 * t 叉乘 R*x1 = 0得到: x2转置 * t 叉乘 R  * x1 = x2转置 * R21  * x1 =  0(K逆*p2)转置 * t 叉乘 R  * (K逆*p1) = p2转置 * K转置逆 * t 叉乘 R  * K逆 * p1 = p2转置 * F21 *  p1上面求到的是 归一化后的点对的变换矩阵:p2'转置 * Fn * p1' = 0(T2*p2)转置 * Fn * (T1 * p1) = 0p2转置 * T2转置 * Fn * T1 * p1 = 0所以得到:未归一化点对对应的变换矩阵F21为:F21 =  T2转置 * Fn * T1 = T2t * Fn * T1步骤4:通过计算重投影误差来计算单应矩阵的好坏,得分 currentScore =Initializer::CheckFundamental(); 
步骤5:保留迭代中,得分最高的单应矩阵和对应的得分if(currentScore > score)//此次迭代 计算的单应H的得分较高{F21 = F21i.clone();// 保留最优的 基础矩阵 FvbMatchesInliers = vbCurrentInliers;//对应的匹配点对   标记内点score = currentScore;// 最高的得分}

Initializer::ComputeF21(vPn1i,vPn2i) 8对点求解 基础矩阵F

 推到:x2 = R*x1 + tt 叉乘 x2 = t 叉乘 R*x1x2转置 * t 叉乘 x2 = x2转置 * t 叉乘 R*x1 = 0得到: x2转置 * t 叉乘 R  * x1 = x2转置 * R21  * x1 =  0(K逆*p2)转置 * t 叉乘 R  * (K逆*p1) = p2转置 * K转置逆 * t 叉乘 R  * K逆 * p1 = p2转置 * F21 *  p1一对2d-2d点对 p1-p2得到:p2转置 * F21 *  p1
写成矩阵形式:|f1   f2   f3|     |u1|
|u2 v2 1| *  |f4   f5   f6|  *  |v1|    = 0 , 应该=0 不等于零的就是误差|f7   f8   f9|     |1|
前两项展开得到:a1 = f1*u2 + f4*v2 + f7;b1 = f2*u2 + f5*v2 + f8;c1 = f3*u2 + f6*v2 + f9;
得到:|u1||a1 b1 c1| * |v1| = 0|1|
得到:a1*u1+ b1*v1 + c1= 0
一个点对 得到一个约束方程:f1*u1*u2 + f2*v1*u2  + f3*u2 + f4*u1*v2  + f5*v1*v2 + f6*v2 +  f7*u1 + f8*v1 + f9 = 0
写成矩阵形式:|u1*u2 v1*u2 u2 u1*v2 v1*v2 v2 u1 v1 1| * |f1 f2 f3 f4 f5 f6 f7 f8 f9|转置 = 0
采样8个点对 可以到八个约束, f 9个参数,8个自由度,另一个为尺度因子(单目尺度不确定的来源)
线性方程组 求解 A * f = 0  
对矩阵A进行奇异值分解得到f ,对A进行SVD奇异值分解 [U,S,V]=svd(A),其中U和V代表二个相互正交矩阵,而S代表一对角矩阵
cv::SVDecomp(A,S,U,VT,SVD::FULL_UV);  //后面的FULL_UV表示把U和VT补充称单位正交方阵
Fpre  = VT.row(8).reshape(0, 3);      // V的最后一列
F矩阵的其秩为2,需要再对Fpre进行奇异值分解, 后取对角矩阵U, 秩为2,后再合成F。
cv::SVDecomp(Fpre,S,U,VT,cv::SVD::MODIFY_A | cv::SVD::FULL_UV);
S.at<float>(2)=0;                     //  基础矩阵的秩为2,重要的约束条件
F21 = U * cv::Mat::diag(S)* VT        // 再合成F

Initializer::CheckFundamental() 计算基本矩阵得分 得分为SF

思想:1. 根据基本矩阵,可以求得两组对点相互变换得到的误差平方和,2. 基于卡方检验计算出的阈值(假设测量有一个像素的偏差),3. 误差大的记录为外点,误差小的记录为内点,4. 使用阈值减去内点的误差,得到该匹配点对的得分(误差小的,得分高),5. 记录相互变换中所有内点的得分之和作为该单元矩阵的得分,并更新匹配点对的内点外点标记。步骤1: 误差阈值 th = 3.841; 得分最大值 thScore = 5.991; 误差方差倒数 invSigmaSquare
步骤2: 遍历每一对2d-2d点对计算误差:如: p2 ->  F12  -> p1 |f1   f2   f3|     |u1||u2 v2 1| *  |f4   f5   f6|  *  |v1|    = 0 , 应该=0 不等于零的就是误差|f7   f8   f9|     |1|
前两项展开得到:a1 = f1*u2 + f4*v2 + f7;b1 = f2*u2 + f5*v2 + f8;c1 = f3*u2 + f6*v2 + f9;
得到:|u1||a1 b1 c1| * |v1| = 0 ,其实就是点 (u1, v1) 在线段 (a1, b1, c1) 上的形式|1|
极线l1:a1*x + b1*y + c1 = 0,这里把p2投影到帧1平面上对应的极线形式,p1应该在上面。
点p1,(u1,v1) 到 l1的距离为:    d = |a1*u + b1*v + c| / sqrt(a1^2 + b1^2) 
距离平方:chiSquare1 = d^2 = (a1*u + b1*v + c)^2 / (a1^2 + b1^2)根据方差归一化误差:const float chiSquare1 = squareDist1*invSigmaSquare;使用阈值更新内外点标记 并记录内点得分:if(chiSquare1 > th)bIn = false;                  // 距离大于阈值  该点 变换的效果差,记录为外点elsescore += thScore - chiSquare1;// 得分上限 - 距离差值 得到 得分,差值越小,得分越高      得分为SF同时记录 p1 ->  F21  -> p2 的误差,也如上述步骤。
更新内外点记录数组:if(bIn)vbMatchesInliers[i]=true;// 是内点  误差较小elsevbMatchesInliers[i]=false;// 是野点 误差较大

b. homography(单应性矩阵) 随机采样 找到最好的单元矩阵 Initializer::FindHomography()

思想:
计算单应矩阵,随机采样序列4点法,采用归一化的直接线性变换(normalized DLT)求解,
假设场景为平面情况下通过前两帧求取Homography矩阵(current frame 2 到 reference frame 1),
在最大迭代次数内调用 ComputeH21计算H,
使用 CheckHomography 计算此单应变换得分,
在最大迭代次数内保留最高得分的单应矩阵H。步骤:
步骤1:将两帧上对应的2d-2d匹配点对进行归一化Initializer::Normalize(mvKeys1,vPn1, T1);//  mvKeys1原坐标 vPn1归一化坐标,T1标准化矩阵Initializer::Normalize(mvKeys2,vPn2, T2);// cv::Mat T2inv = T2.inv();                // 标准化矩阵 逆矩阵
步骤2:在最大迭代次数mMaxIterations内,从标准化后的点中随机选取8对点对int idx = mvSets[it][j];                 //随机数集合 总匹配点数范围内vPn1i[j] = vPn1[mvMatches12[idx].first];vPn2i[j] = vPn2[mvMatches12[idx].second]; 
步骤3:通过标准化逆矩阵和标准化点对的单应变换计算原点对的单应变换矩阵 Initializer::ComputeH21()cv::Mat Hn = ComputeH21(vPn1i,vPn2i);// 计算 标准化后的点对 对应的 单应矩阵Hn// H21i = T2逆 * Hn * T1  见上面  0步骤的推导H21i = T2inv * Hn * T1;// 原始点    p1 -----------> p2 的单应H12i = H21i.inv();     // 原始点    p2 -----------> p1 的单应
步骤4:通过计算重投影误差来计算单应矩阵的好坏,得分 currentScore =Initializer::CheckHomography(); 
步骤5:保留迭代中,得分最高的单应矩阵和对应的得分if(currentScore > score)//此次迭代 计算的单应H的得分较高{H21 = H21i.clone();//保留较高得分的单应vbMatchesInliers = vbCurrentInliers;//对应的匹配点对   score = currentScore;// 最高的得分}

Initializer::ComputeH21() 4对点直接线性变换求解H矩阵

一点对:p2   =  H21 * p1
写成矩阵形式:u2         h1  h2  h3       u1v2  =      h4  h5  h6    *  v11          h7  h8  h9       1  可以使用叉乘 得到0    p2叉乘p2 = H21 *p1 = 0 | 0 -1  v2|    |h1 h2 h3|      |u1|    |0|| 1  0 -u2| *  |h4 h5 h6| *    |v1| =  |0||-v2 u2  0|    |h7 h8 h9|      |1 |    |0|也可以展开得到(使用第三项进行归一化):u2 = (h1*u1 + h2*v1 + h3) /( h7*u1 + h8*v1 + h9)v2 = (h4*u1 + h5*v1 + h6) /( h7*u1 + h8*v1 + h9)
写成矩阵形式:-((h4*u1 + h5*v1 + h6) - ( h7*u1*v2 + h8*v1*v2 + h9*v2))=0  右乘分子,再移动得0h1*u1    + h2*v1 + h3  - ( h7*u1*u2 + h8*v1*u2 + h9*u2) =0|0    0   0  -u1  -v1  -1   u1*v2   v1*v2    v2||u1 v1    1  0    0    0   -u1*u2  - v1*u2  -u2| *|h1 h2 h3 h4 h5 h6 h7 h8 h9|转置  = 0
一对点提供两个约束:H 9个元素,8个自由度,包含一个比例因子(尺度来源),需要四对点
四对点提供8个约束方程,可以写成矩阵形式:A * h = 0
对A进行SVD奇异值分解 [U,S,V]=svd(A),其中U和V代表二个相互正交矩阵,而S代表一对角矩阵
cv::SVDecomp(A,S,U,VT,SVD::FULL_UV);  //后面的FULL_UV表示把U和VT补充称单位正交方阵	
H = VT.row(8).reshape(0, 3);// v的最后一列SVD奇异值分解 解齐次方程组(Ax = 0)原理:把问题转化为最小化|| Ax ||2的非线性优化问题,我们已经知道了x = 0是该方程组的一个特解,为了避免x = 0这种情况(因为在实际的应用中x = 0往往不是我们想要的),我们增加一个约束,比如|| x ||2 = 1,这样,问题就变为:min(|| Ax ||2) , || x ||2 = 1 或 min(|| Ax ||) , || x || = 1对矩阵A进行分解 A = UDV'|| Ax || = || UDV' x || = || DV' x||( 对于一个正交矩阵U,满足这样一条性质: || UD || = || D || , 正交矩阵,正交变换,仅仅对向量,只产生旋转,无尺度缩放,和变形,即模长不变)令y = V'x, 因此,问题变为min(|| Dy ||), 因为|| x || = 1, V'为正交矩阵,则|| y || = 1。由于D是一个对角矩阵,对角元的元素按递减的顺序排列,因此最优解在y = (0, 0,..., 1)'又因为x = Vy, 所以最优解x,就是V的最小奇异值对应的列向量,比如,最小奇异值在第8行8列,那么x = V的第8个列向量。

计算单应变换得分 Initializer::CheckHomography() 得分为SH

思想:1. 根据单应变换,可以求得两组对点相互变换得到的误差平方和,2. 基于卡方检验计算出的阈值(假设测量有一个像素的偏差),3. 误差大的记录为外点,误差小的记录为内点,4. 使用阈值减去内点的误差,得到该匹配点对的得分(误差小的,得分高),5. 记录相互变换中所有内点的得分之和作为该单元矩阵的得分,并更新匹配点对的内点外点标记。步骤1:获取单应变换误差阈值th,以及误差归一化的方差倒数    const float th = 5.991;                        // 单应变换误差 阈值const float invSigmaSquare = 1.0/(sigma*sigma);//方差 倒数,用于将误差归一化步骤2:遍历每个点对,计算单应矩阵 变换 时产生 的 对称的转换误差p1点 变成 p2点  p2 = H21*p1---------------------------------------|u2'|      |h11  h12  h13|     |u1|    |h11*u1 + h12*v1 + h13||v2'|  =   |h21  h22  h23|  *  |v1| =  |h21*u1 + h22*v1 + h23||1|        |h31  h32  h33|     |1|     |h31*u1 + h32*v1 + h33|使用第三行进行归一化: u2' = (h11*u1 + h12*v1 + h13)/(h31*u1 + h32*v1 + h33);v2' = (h21*u1 + h22*v1 + h23)/(h31*u1 + h32*v1 + h33);所以 p1通过 H21 投影到另一帧上 应该和p2的左边一致,但是实际不会一致,可以求得坐标误差误差平方和            squareDist2 = (u2-u2')*(u2-u2') + (v2-v2')*(v2-v2');使用方差倒数进行归一化 chiSquare2 = squareDist2*invSigmaSquare;使用阈值更新内外点标记 并记录内点得分:if(chiSquare2>th)bIn = false;              //距离大于阈值  该点 变换的效果差,记录为外点elsescore += th - chiSquare2; // 阈值 - 距离差值 得到 得分,差值越小  得分越高p2点 变成 p1点  p1 = H12 * p2 ------------------------------------|u1'|     |h11inv   h12inv   h13inv|    |u2|   |h11inv*u2 + h12inv*v2 + h13inv||v1'|  =  |h21inv   h22inv   h23inv|  * |v2| = |h21inv*u2 + h22inv*v2 + h23inv||1|       |h31inv   h32inv   h33inv|    |1|    |h31inv*u2 + h32inv*v2 + h33inv|使用第三行进行归一化: u1' = (h11inv*u2 + h12inv*v2 + h13inv)/(h31inv*u2 + h32inv*v2 + h33inv);v1' = (h21inv*u2 + h22inv*v2 + h23inv)/(h31inv*u2 + h32inv*v2 + h33inv);所以 p1通过 H21 投影到另一帧上 应该和p2的左边一致,但是实际不会一致,可以求得坐标误差误差平方和            squareDist1 = (u1-u1')*(u1-u1') + (v1-v1')*(v1-v1');使用方差倒数进行归一化 chiSquare1 = squareDist1*invSigmaSquare; 使用阈值更新内外点标记 并记录内点得分:if(chiSquare1>th)    bIn = false;             // 距离大于阈值  该点 变换的效果差,记录为外点elsescore += th - chiSquare1;// 阈值 - 距离差值 得到 得分,差值越小  得分越高  SH更新内外点记录数组:if(bIn)vbMatchesInliers[i]=true;// 是内点  误差较小elsevbMatchesInliers[i]=false;// 是野点 误差较大

从两个模型 H F 得分为 Sh Sf 中选着一个 最优秀的 模型 的方法为

文中认为,当场景是一个平面、或近似为一个平面、或者视差较小的时候,可以使用单应性矩阵H恢复运动,
当场景是一个非平面、视差大的场景时,使用基础矩阵F恢复运动,
两个变换矩阵得分分别为 SH 、SF,根据两者得分计算一个评价指标RH:
RH = SH / ( SH + SF)
当大于0.45时,选择从单应性变换矩阵还原运动,反之使用基础矩阵恢复运动。
不过ORB_SLAM2源代码中使用的是0.4作为阈值。

c. 单应矩阵H恢复R,t Initializer::ReconstructH()

单应矩阵恢复  旋转矩阵 R 和平移向量t
p2   =  H21 * p1   
p2 = K( RP + t)  = KTP = H21 * KP  
A = T =  K 逆 * H21*K = [ R t; 0 0 0 1]
对A进行奇异值分解
cv::SVD::compute(A,w,U,Vt,cv::SVD::FULL_UV);
使用FAUGERAS的论文[1]的方法,提出8种运动假设,分别可以得到8组R,t
正常来说,第二帧能够看到的点都在相机的前方,即深度值Z大于0.
但是如果在低视差的情况下,使用 三角变换Initializer::Triangulate() 得到的3d点云,
使用 Initializer::CheckRT() 统计符合该R,t的内点数量。

Initializer::Triangulate() 使用2d-2d点对 和 变换矩阵 R,t 三角变换恢复2d点对应的3d点

Trianularization: 已知匹配特征点对{p1  p2} 和 
各自相机投影矩阵{P1 P2}, P1 = K*[I 0], P2 = K*[R t], 尺寸为[3,4]
估计三维点 X3Dp1 = P1 * X3Dp2 = P2 * X3D
采用直接线性变换DLT的方法(将式子变换成A*X=0的形式后使用SVD奇异值分解求解线性方程组):
对于 p1 = P1 * X3D: 方程两边 左边叉乘 p1,可使得式子为0
p1叉乘P1*X3D = 0
其叉乘矩阵为:
叉乘矩阵 =  |0  -1  y||1   0 -x| |-y  x  0| 
上述等式可写: |0  -1  y|  |P1.row(0)|  |1   0 -x| *|P1.row(1)|* X3D = 0|-y  x  0|  |P1.row(2)|  对于第一行 |0  -1  y| 会与P的三行分别相乘 得到四个值 与齐次3d点坐标相乘得到 0
有 (y * P1.row(2) - P1.row(1) ) * X3D = 0
对于第二行 |1   0 -x|有:
有 (x * P1.row(2) - P1.row(0) ) * X3D = 0
得到两个约束,另外一个点 p2 = P2 * X3D,也可以得到两个式子:
(y‘ * P2.row(2) - P2.row(1) ) * X3D = 0
(x’ * P2.row(2) - P2.row(0) ) * X3D = 0
写成 A*X = 0的形式有:
A =(维度4*4)
|y * P1.row(2) - P1.row(1) |
|x * P1.row(2) - P1.row(0) |
|y‘ * P2.row(2) - P2.row(1)|
|x’ * P2.row(2) - P2.row(0)|
对A进行奇异值分解求解X
cv::SVD::compute(A,w,u,vt,cv::SVD::MODIFY_A| cv::SVD::FULL_UV);
x3D = vt.row(3).t();// vt的最后一列,为X的解
x3D = x3D.rowRange(0,3)/x3D.at<float>(3);//  转换成非齐次坐标  归一化

initializer::CheckRT() 计算R , t 的得分(内点数量)

统计规则,使用 Initializer::Triangulate()  三角化的3d点,
会有部分点云跑到相机的后面(Z<0),该部分点是噪点,需要剔除.
另外一个准则是将上述符合条件的3d点分别重投影会前后两帧上,
与之前的orb特征点对的坐标做差,误差平方超过阈值,剔除。
统计剩余符合条件的点对数量。
在8种假设中,记录得到最大内点数量 和 次大内点数量 
当最大内点数量 远大于 次大内点数量( 有明显的差异性),
该最大内点数量对应的R,t,具有较大的可靠性,返回该,R,t。

d. 基础矩阵F恢复R,t Initializer::ReconstructF()

计算 本质矩阵 E  =  K转置 * F  * K
E = t叉乘 R
奇异值分解 E= U C  V   , U 、V 为正交矩阵, C 为奇异值矩阵  C =  diag(1, 1, 0)
从本质矩阵E 恢复 旋转矩阵R 和 平移向量t
有四种假设 得到四种 R,t
理论参考 Result 9.19 in Multiple View Geometry in Computer Vision
使用 initializer::CheckRT() 计算R , t 的得分(内点数量)
使用4中情况中得分最高的R,t 作为最终恢复的R,t

e. 单目创建初始地图 CreateInitialMapMonocular();

思想:使用单目相机前两帧生成的3d点创建初始地图,使用全局地图优化 Optimizer::GlobalBundleAdjustemnt(mpMap,20)优化地图点,计算场景的深度中值(地图点深度的中位数),使用平均逆深度归一化 两帧的平移变换量t,使用 平均逆深度归一化地图点。
步骤:
步骤1:使用前两帧创建关键帧,并把这 两帧 关键帧 加入地图,加入关键帧数据库。
步骤2:使用之前三角化得到的3d点创建 地图点。
步骤3:地图点和每一帧的2d点对应起来,在关键帧中添加这种对应关系。 关键帧关联地图点。
步骤4:关键帧和2d点对应起来,在地图点中添加这种关系。            地图点关联关键帧。
步骤5:一个地图点会被许多个关键帧观测到,那么就会关联到许多个2d点,需要更新地图点对应2d点的orb特征描述子。
步骤6:当前帧关联地图点,地图点加入到地图。
步骤7:更新关键帧的 连接关系,被地图点观测到的次数。
步骤8:全局优化地图 BA最小化重投影误差,这两帧姿态进行全局优化。Optimizer::GlobalBundleAdjustemnt(mpMap,20);// 注意这里使用的是全局优化,和回环检测调整后的大回环优化使用的是同一个函数。// 放在后面再进行解析
步骤9:计算场景深度中值,以及逆深度中值,对位姿的平移量进行尺度归一化,对地图点进行尺度归一化。

2. 双目/RGBD初始化 Tracking::StereoInitialization()根据视差计算深度(深度相机直接获取深度)计算3d点,创建地图点,创建初始地图

步骤:当前帧 特征点个数 大于500 进行初始化设置第一帧为关键帧,并设置为位姿为 T = [I 0],世界坐标系 创建关键帧,地图添加关键帧,根据每一个2d点的视差(左右两张图像,orb特征点金字塔分层分块匹配得到视差d)求得的深度D=fB/d,计算对应的3D点,并创建地图点,地图点关联观测帧 地图点计算所有关键帧中最好的描述子并更新地图点的方向和距离,关键帧关联地图点,地当前帧添加地图点  地图添加地图点局部地图中添加该初始关键帧。

4.3 两帧跟踪得到初始化位姿(跟踪上一帧/跟踪参考帧/重定位)

参考

本文做匹配的过程中,是用的都是ORB特征描述子。
先在8层图像金字塔中,提取FAST特征点。
提取特征点的个数根据图像分辨率不同而不同,高分辨率的图像提取更多的角点。
然后对检测到的特征点用ORB来描述,用于之后的匹配和识别。
跟踪这部分主要用了几种模型:
运动模型(Tracking with motion model)、
关键帧(Tracking with reference keyframe)和
重定位(Relocalization)。
if(mVelocity.empty() || mCurrentFrame.mnId<mnLastRelocFrameId+2)
// 没有移动(跟踪参考关键帧的运动模型是空的)  或 刚完成重定位不久
{bOK = TrackReferenceKeyFrame();// 跟踪参考帧模式
}
else
{bOK = TrackWithMotionModel();// 跟踪上一帧模式if(!bOK)//没成功bOK = TrackReferenceKeyFrame();//再次使用 跟踪参考帧模式
}
if(!bOK)// 当前帧与最近邻关键帧的匹配也失败了,那么意味着需要重新定位才能继续跟踪。
{       // 此时,只有去和所有关键帧匹配,看能否找到合适的位置。bOK = Relocalization();//重定位  BOW搜索,PnP 3d-2d匹配 求解位姿
}

a. Tracking::TrackWithMotionModel() 跟踪上一帧模式,两帧相差不大,对应位置区域领域半径搜索匹配点对

参考:这个模型是假设物体处于匀速运动,例如匀速运动的汽车、机器人、行人等,就可以用上一帧的位姿和速度来估计当前帧的位姿。使用的函数为 Tracking::TrackWithMotionModel()。这里匹配是通过投影来与上一帧看到的地图点匹配,使用的是 matcher.SearchByProjection()。思想:移动模式跟踪  跟踪前后两帧  得到 变换矩阵。上一帧的地图3d点反投影到当前帧图像像素坐标上,在不同尺度下不同的搜索半径内,做描述子匹配 搜索 可以加快匹配。在投影点附近根据描述子距离进行匹配(需要>20对匹配,否则匀速模型跟踪失败,运动变化太大时会出现这种情况),然后以运动模型预测的位姿为初值,优化当前位姿,优化完成后再剔除外点,若剩余的匹配依然>=10对,则跟踪成功,否则跟踪失败,需要Relocalization:
步骤:步骤1:创建 ORB特征点匹配器 最小距离 < 0.9*次小距离 匹配成功ORBmatcher matcher(0.9,true);步骤2:更新上一帧的位姿和地图点Tracking::UpdateLastFrame();上一帧位姿 = 上一帧到其参考帧位姿*其参考帧到世界坐标系(系统第一帧)位姿后面单目不执行双目或rgbd相机,根据深度值为上一帧产生新的MapPoints步骤3:使用当前的运动速度(之前前后两帧位姿变换)和上一帧的位姿来初始化 当前帧的位姿R,t步骤4:在当前帧和上一帧之间搜索匹配点(需要>20对匹配,否则匀速模型跟踪失败ORBmatcher::SearchByProjection(mCurrentFrame,mLastFrame,th,...)通过投影(使用当前帧的位姿R,t),对上一帧的特征点(地图点)进行跟踪.上一帧中包含了MapPoints,对这些MapPoints进行跟踪tracking,由此增加当前帧的MapPoints.上一帧3d点投影到当前坐标系下,在该2d点半径th范围内搜索可以匹配的匹配点,遍历可以匹配的点,计算描述子距离,记录最小的匹配距离,小于阈值的,再记录匹配点特征方向差值如果需要进行方向验证,剔除方向差直方图统计中,方向差值数量少的点对,保留前三个数量多的点对。步骤5:如果找到的匹配点对如果少于20,则扩大搜索半径th=2*th,使用 ORBmatcher::SearchByProjection()再次进行搜索。步骤6:使用匹配点对对当前帧的位姿进行优化 G2O图优化Optimizer::PoseOptimization(&mCurrentFrame);// 仅仅优化单个普通帧的位姿,地图点不优化输入前一帧3d点 和 当前帧2d点对,以及帧的初始化位姿Tcw3D-2D 最小化重投影误差 e = (u,v) - project(Tcw*Pw),只优化Frame的Tcw,不优化MapPoints的坐标。1. 顶点 Vertex: g2o::VertexSE3Expmap(),初始值为当前帧的Tcw2. 边 Edge:单目- g2o::EdgeSE3ProjectXYZOnlyPose(),一元边 BaseUnaryEdge+ 顶点 Vertex:待优化当前帧的Tcw+ 测量值 measurement:MapPoint在当前帧中的二维位置(u,v)+ 误差信息矩阵 InfoMatrix: Eigen::Matrix2d::Identity()*invSigma2(与特征点所在的尺度有关)+ 附加信息: 相机内参数: e->fx fy cx cy3d点坐标  : e->Xw[0] Xw[1] Xw[2] 2d点对应的上一帧的3d点双目 - g2o::EdgeStereoSE3ProjectXYZOnlyPose(),BaseUnaryEdge+ Vertex:待优化当前帧的Tcw+ measurement:MapPoint在当前帧中的二维位置(ul,v, ur) 左相机3d点 右相机横坐标匹配点坐标ur+ InfoMatrix: invSigma2(与特征点所在的尺度有关)+ 附加信息: 相机内参数: e->fx fy cx cy3d点坐标  : e->Xw[0] Xw[1] Xw[2] 2d点对应的上一帧的3d点优化多次,根据误差,更新2d-3d匹配质量内外点标记,当前帧设置优化后的位姿。步骤7:如果2d-3d匹配效果差,被标记为外点,则当前帧2d点对于的3d点设置为空,留着以后再优化步骤8:根据内点的匹配数量,判断 跟踪上一帧是否成功。

b. Tracking::TrackReferenceKeyFrame() 跟踪参考帧模式,bow特征向量加速匹配

思想: 当使用运动模式匹配到的特征点数较少时,就会选用关键帧模式。即尝试和最近一个关键帧去做匹配。为了快速匹配,本文利用了bag of words(BoW)来加速匹配。首先,计算当前帧的BoW,并设定初始位姿为上一帧的位姿;其次,根据两帧的BoW特征向量同属于一个node下来加速搜索匹配点对,使用函数matcher.SearchByBoW();最后,利用匹配的特征优化位姿。步骤:步骤1:创建 ORB特征点匹配器 最小距离 < 0.7*次小距离 匹配成功ORBmatcher matcher(0.7,true);步骤2: 在当前帧 和 参考关键帧帧之间搜索匹配点对(需要>15对匹配点对,否者失败)ORBmatcher::SearchByBoW(mpReferenceKF,mCurrentFrame,vpMapPointMatches);计算当前帧 和 参考帧 orb特征的 词袋表示的 词典表示向量,属于同一个词典node下的点才可能是匹配点对遍历所有的参考帧特征点 和 当前帧特征点:同一个词典node下,有一些属于参考帧的点,也有一些属于当前帧的点,其中有一些匹配点。遍历属于一个node下的 参考帧的点遍历对应node下的当前帧的点计算orb描述子之间的距离记录最小的距离和次小的距离最小的距离小于阈值,且最小的距离明显小于次小的距离的话,记录次匹配点对特征的方向差,统计到方向差直方图如果需要根据方向一致性剔除不好的匹配点:计算方向差直方图中三个计数最多的方向差,根据数量差异选择保留第一/第一+第二/第一+第二+第三方向差对应的匹配点对。步骤3:设置当前帧位置为上一帧的位姿,使用步骤2得到的匹配点对3d-2d点对,使用BA 进行 当前帧位姿的优化Optimizer::PoseOptimization(&mCurrentFrame);输入前一帧3d点 和 当前帧2d点对,以及帧的初始化位姿Tcw3D-2D 最小化重投影误差 e = (u,v) - project(Tcw*Pw),只优化Frame的Tcw,不优化MapPoints的坐标。1. 顶点 Vertex: g2o::VertexSE3Expmap(),初始值为当前帧的Tcw2. 边 Edge:单目- g2o::EdgeSE3ProjectXYZOnlyPose(),一元边 BaseUnaryEdge+ 顶点 Vertex:待优化当前帧的Tcw+ 测量值 measurement:MapPoint在当前帧中的二维位置(u,v)+ 误差信息矩阵 InfoMatrix: Eigen::Matrix2d::Identity()*invSigma2(与特征点所在的尺度有关)+ 附加信息: 相机内参数: e->fx fy cx cy3d点坐标  : e->Xw[0] Xw[1] Xw[2] 2d点对应的上一帧的3d点双目 - g2o::EdgeStereoSE3ProjectXYZOnlyPose(),BaseUnaryEdge+ Vertex:待优化当前帧的Tcw+ measurement:MapPoint在当前帧中的二维位置(ul,v, ur) 左相机3d点 右相机横坐标匹配点坐标ur+ InfoMatrix: invSigma2(与特征点所在的尺度有关)+ 附加信息: 相机内参数: e->fx fy cx cy3d点坐标  : e->Xw[0] Xw[1] Xw[2] 2d点对应的上一帧的3d点优化多次,根据误差,更新2d-3d匹配质量内外点标记,当前帧设置优化后的位姿。步骤4:如果2d-3d匹配效果差,被标记为外点,则当前帧2d点对于的3d点设置为空,留着以后再优化步骤5:根据内点的匹配数量,判断 跟踪上一帧是否成功。

c. bool Tracking::Relocalization() 上面两种模式都没有跟踪成功,需要使用重定位模式,使用orb字典编码在关键帧数据库中找相似的关键帧,进行匹配跟踪

思想:位置丢失后,需要在之前的关键帧中匹配最相近的关键帧,进而求出位姿信息。使用当前帧的BoW特征映射,在关键帧数据库中寻找相似的候选关键帧,因为这里没有好的初始位姿信息,需要使用传统的3D-2D匹配点的EPnP算法来求解一个初始位姿,之后再使用最小化重投影误差来优化更新位姿。
步骤:步骤1:计算当前帧的BoW映射词典 N个M维的单词,一帧的描述子,n个M维的描述子,生成一个 N*1的向量,记录一帧的描述子使用词典单词的情况。 步骤2:在关键帧数据库中找到与当前帧相似的候选关键帧组,词典单词线性表示向量距离较近的一些关键帧。步骤3:创建 ORB特征点匹配器 最小距离 < 0.75*次小距离 匹配成功。ORBmatcher matcher(0.75,true);步骤4:创建 PnPsolver 位姿变换求解器数组,对应上面的多个候选关键帧。步骤5:遍历每一个候选关键帧使用BOW特征向量加速匹配,如果匹配点数少于15,跳过此候选关键帧。ORBmatcher::SearchByBoW(mpReferenceKF,mCurrentFrame,vpMapPointMatches);计算当前帧 和 参考帧 orb特征的 词袋表示的 词典表示向量,属于同一个词典node下的点才可能是匹配点对遍历所有的参考帧特征点 和 当前帧特征点:同一个词典node下,有一些属于参考帧的点,也有一些属于当前帧的点,其中有一些匹配点。遍历属于一个node下的 参考帧的点遍历对应node下的当前帧的点计算orb描述子之间的距离记录最小的距离和次小的距离最小的距离小于阈值,且最小的距离明显小于次小的距离的话,记录次匹配点对特征的方向差,统计到方向差直方图如果需要根据方向一致性剔除不好的匹配点:计算方向差直方图中三个计数最多的方向差,根据数量差异选择保留第一/第一+第二/第一+第二+第三方向差对应的匹配点对。步骤6:使用PnPsolver 位姿变换求解器,更加3d-2d匹配点,s1 * (u,v,1) = T * (X, Y, Z, 1)  = T * P6点直接线性变换DLT,后使用QR分解得到 R,t, 或者使用(P3P),3点平面匹配算法求解。这里可参考 文档--双目slam基础.md--里面的3d-2d匹配点对求解算法。这里会结合 Ransac 随采样序列一致性算法,来提高求解的鲁棒性。  步骤7:设置上面PnP算法求解的位置为当前帧的初始位姿,使用最小化重投影误差BA算法来优化位姿Optimizer::PoseOptimization(&mCurrentFrame);输入前一帧3d点 和 当前帧2d点对,以及帧的初始化位姿Tcw3D-2D 最小化重投影误差 e = (u,v) - project(Tcw*Pw),只优化Frame的Tcw,不优化MapPoints的坐标。1. 顶点 Vertex: g2o::VertexSE3Expmap(),初始值为当前帧的Tcw2. 边 Edge:单目- g2o::EdgeSE3ProjectXYZOnlyPose(),一元边 BaseUnaryEdge+ 顶点 Vertex:待优化当前帧的Tcw+ 测量值 measurement:MapPoint在当前帧中的二维位置(u,v)+ 误差信息矩阵 InfoMatrix: Eigen::Matrix2d::Identity()*invSigma2(与特征点所在的尺度有关)+ 附加信息: 相机内参数: e->fx fy cx cy3d点坐标  : e->Xw[0] Xw[1] Xw[2] 2d点对应的上一帧的3d点双目 - g2o::EdgeStereoSE3ProjectXYZOnlyPose(),BaseUnaryEdge+ Vertex:待优化当前帧的Tcw+ measurement:MapPoint在当前帧中的二维位置(ul,v, ur) 左相机3d点 右相机横坐标匹配点坐标ur+ InfoMatrix: invSigma2(与特征点所在的尺度有关)+ 附加信息: 相机内参数: e->fx fy cx cy3d点坐标  : e->Xw[0] Xw[1] Xw[2] 2d点对应的上一帧的3d点优化多次,根据误差,更新2d-3d匹配质量内外点标记,当前帧设置优化后的位姿。步骤8:如果优化时记录的匹配点对内点数量少于50,会把参考关键中还没有在当前帧有2d匹配的点反投影到当前帧下,再次搜索2d匹配点ORBmatcher::SearchByProjection();步骤9: 如果再次反投影搜索的匹配点数量和之前得到的匹配点数量 大于50,再次使用 Optimizer::PoseOptimization() 优化算法进行优化步骤10:如果上面优化后的内点数量还比较少可以再次搜索 matcher2.SearchByProjection(),再次优化  Optimizer::PoseOptimization()步骤11:如果内点数量 大于等于50 ,则重定位成功。

4.4 Tracking::TrackLocalMap() 跟踪局部地图,多帧局部地图G2O优化位姿, 新建关键帧

参考

思想:上面完成初始位姿的跟踪后,需要 使用局部地图(参考帧的一二级共视帧组成) 来 进行局部地图优化,来提高鲁棒性。局部地图中与当前帧有相同点的关键帧序列成为一级相关帧K1,而与一级相关帧K1有共视地图点的关键帧序列成为二级相关帧K2,把局部地图中的局部地图点,投影到当前帧上,如果在当前帧的视野内使用 位姿优化 Optimizer::PoseOptimization(&mCurrentFrame), 进行优化,更新 地图点的信息(关键帧的观测关系)
步骤:步骤1:首先对局部地图进行更新(UpdateLocalMap) 生成对应当前帧的局部地图(小图)Tracking::UpdateLocalMap();---更新局部关键帧:Tracking::UpdateLocalKeyFrames();始终限制局部关键帧(小图中关键帧的数量)数量不超过80包含三个部分:1. 共视化程度高的关键帧: 观测到当前帧的地图点次数多的关键帧;2. 这些关键帧的孩子关键帧;(这里没看到具体的方式,应该就是根据时间顺序记录了一些父子关键帧)3. 这些关键帧的父亲关键帧。---更新局部地图点:Tracking::UpdateLocalPoints();所有局部关键帧 包含的地图点构成 局部地图点遍历每一个局部关键帧的每一个地图点:加入到局部地图点中: mvpLocalMapPoints.push_back(pMP);同时设置地图点更新标志,来避免重复添加出现在多帧上的地图点。步骤2:在局部地图中为当前帧搜索匹配点对Tracking::SearchLocalPoints();// 在对应当前帧的局部地图内搜寻和 当前帧地图点匹配点的 局部地图点1. 遍历当前帧的特征点,如果已经有相应的3D地图点,则进行标记,不需要进行重投影匹配,并且标记已经被遍历过2. 遍历局部地图的所有地图点,如果没有被遍历过,把地图点反投影到当前帧下,保留在当前帧视野内的地图点3. 根据反投影后的2d位置,设置一个半径为th的范围进行搜索匹配点SearchByProjection(mCurrentFrame,mvpLocalMapPoints,th);遍历可以匹配的点,计算描述子距离,记录最小的匹配距离,小于阈值的,再记录匹配点特征方向差值如果需要进行方向验证,剔除方向差直方图统计中,方向差值数量少的点对,保留前三个数量多的点对。步骤3:使用之前得到的初始位姿和 在局部地图中搜索到的3d-2d匹配点对,使用最小化重投影误差BA算法来优化位姿Optimizer::PoseOptimization(&mCurrentFrame);输入前一帧3d点 和 当前帧2d点对,以及帧的初始化位姿Tcw3D-2D 最小化重投影误差 e = (u,v) - project(Tcw*Pw),只优化Frame的Tcw,不优化MapPoints的坐标。1. 顶点 Vertex: g2o::VertexSE3Expmap(),初始值为当前帧的Tcw2. 边 Edge:单目- g2o::EdgeSE3ProjectXYZOnlyPose(),一元边 BaseUnaryEdge+ 顶点 Vertex:待优化当前帧的Tcw+ 测量值 measurement:MapPoint在当前帧中的二维位置(u,v)+ 误差信息矩阵 InfoMatrix: Eigen::Matrix2d::Identity()*invSigma2(与特征点所在的尺度有关)+ 附加信息: 相机内参数: e->fx fy cx cy3d点坐标  : e->Xw[0] Xw[1] Xw[2] 2d点对应的上一帧的3d点双目 - g2o::EdgeStereoSE3ProjectXYZOnlyPose(),BaseUnaryEdge+ Vertex:待优化当前帧的Tcw+ measurement:MapPoint在当前帧中的二维位置(ul,v, ur) 左相机3d点 右相机横坐标匹配点坐标ur+ InfoMatrix: invSigma2(与特征点所在的尺度有关)+ 附加信息: 相机内参数: e->fx fy cx cy3d点坐标  : e->Xw[0] Xw[1] Xw[2] 2d点对应的上一帧的3d点优化多次,根据误差,更新2d-3d匹配质量内外点标记,当前帧设置优化后的位姿。步骤4:更新地图点状态步骤5:如果刚刚进行过重定位 则需要 内点匹配点对数 大于50 才认为 成功正常情况下,找到的内点匹配点对数 大于30 算成功

4.5 跟踪成功之后判断是否需要新建关键帧

判断是否需要创建关键帧 Tracking::NeedNewKeyFrame();

确定关键帧的标准如下:(1)在上一个全局重定位后,又过了20帧               (时间过了许久);(2)局部建图闲置,或在上一个关键帧插入后,又过了20帧(时间过了许久);(3) 当前帧跟踪到点数量比较少,tracking质量较弱     (跟踪要跪的节奏);(4)当前帧跟踪到的点比参考关键帧的点少90%          (环境变化较大了)。
步骤:步骤1:系统模式判断,如果仅仅需要跟踪定位,不需要建图,那么不需要新建关键帧。步骤2:根据地图中关键帧的数量设置一些参数(系统一开始关键帧少的时候,可以放宽一些条件,多创建一些关键帧)步骤3:很长时间没有插入关键帧 bool c1a = mCurrentFrame.mnId >= mnLastKeyFrameId + mMaxFrames;步骤4:在过去一段时间但是局部建图闲置bool c1b = (mCurrentFrame.mnId >= mnLastKeyFrameId+mMinFrames && bLocalMappingIdle步骤5:当前帧跟踪到点数量比较少,tracking质量较弱bool c1c = mnMatchesInliers < nRefMatches*0.25步骤6:上面条件成立之前必须当 当前帧与之前参考帧(最近的一个关键帧)重复度不是太高。bool c2 = ((mnMatchesInliers < nRefMatches*thRefRatio|| bNeedToInsertClose) && mnMatchesInliers>15)步骤7:需要新建关键帧的条件 (c1a || c1b || c1c) && c2步骤8:当待插入的关键帧队列里关键帧数量不多时在插入,队列里不能阻塞太多关键帧。

创建关键帧 Tracking::CreateNewKeyFrame();

步骤:步骤1:将当前帧构造成关键帧步骤2:将当前关键帧设置为当前帧的参考关键帧步骤3:对于双目或rgbd摄像头,为当前帧生成新的MapPoints将深度距离比较近的点包装成MapPoints这些添加属性的操作是每次创建MapPoint后都要做的:地图点关键关键帧关键帧关联地图点地图点更新最优区别性的描述子地图点更新深度地图添加地图点

局部建图线程 LocalMapping::LocalMapping()

功能总览:LocalMapping作用是将Tracking中送来的关键帧放在mlNewKeyFrame列表中;处理新关键帧,地图点检查剔除,生成新地图点,Local BA,关键帧剔除。主要工作在于维护局部地图,也就是SLAM中的Mapping。Tracking线程 只是判断当前帧是否需要加入关键帧,并没有真的加入地图,因为Tracking线程的主要功能是局部定位, 而处理地图中的关键帧,地图点,包括如何加入,如何删除的工作是在LocalMapping线程完成的void LocalMapping::Run():局部建图 :处理新的关键帧 KeyFrame 完成局部地图构建,插入关键帧 ------>  处理地图点(筛选生成的地图点 生成地图点)  -------->  局部 BA最小化重投影误差  -调整-------->  筛选 新插入的 关键帧步骤:mlNewKeyFrames     list 列表队列存储关键帧步骤1:设置进程间的访问标志 告诉Tracking线程,LocalMapping线程正在处理新的关键帧,处于繁忙状态步骤2:检查队列  LocalMapping::CheckNewKeyFrames(); 等待处理的关键帧列表不为空步骤3:处理新关键帧,计算关键帧特征点的词典单词向量BoW映射,将关键帧插入地图  更新地图点MapPoints 和 关键帧 KepFrame 的关联关系  UpdateConnections() 更新关联关系LocalMapping::ProcessNewKeyFrame();步骤4:创建新的地图点 相机运动过程中与相邻关键帧通过三角化恢复出一些新的地图点MapPointsLocalMapping::CreateNewMapPoints();步骤5:对新添加的地图点进行融合处理 ,剔除 地图点 MapPoints对于 ProcessNewKeyFrame 和 CreateNewMapPoints 中 最近添加的MapPoints进行检查剔除删除地图中新添加的但 质量不好的 地图点LocalMapping::MapPointCulling();a)  IncreaseFound / IncreaseVisible < 25%b) 观测到该 点的关键帧太少步骤6:相邻帧地图点融合  LocalMapping::SearchInNeighbors();检测当前关键帧和相邻 关键帧(两级相邻) 中 重复的 地图点 步骤7:局部地图BA 最小化重投影误差Optimizer::LocalBundleAdjustment(mpCurrentKeyFrame,&mbAbortBA, mpMap);和当前关键帧相邻的关键帧 中相匹配的 地图点对 最局部 BA最小化重投影误差优化点坐标 和 位姿步骤7:关键帧融合,剔除当前帧相邻的关键帧中冗余的关键帧LocalMapping::KeyFrameCulling();其90%以上的 地图点 能够被其他 共视 关键帧(至少3个) 观测到,认为该关键帧多余,可以删除步骤8:将当前帧加入到闭环检测队列中 mpLoopCloser步骤9:等待线程空闲 完成一帧关键帧的插入融合工作步骤10:告诉 Tracking 线程,Local Mapping 线程现在空闲,可一处理接收下一个关键帧。

恢复3d点 LocalMapping::CreateNewMapPoints()

思想:相机运动过程中和共视程度比较高的关键帧,通过三角化恢复出一些MapPoints地图点根据当前关键帧恢复出一些新的地图点,不包括和当前关键帧匹配的局部地图点(已经在ProcessNewKeyFrame中处理)先处理新关键帧与局部地图点之间的关系,然后对局部地图点进行检查,最后再通过新关键帧恢复 新的局部地图点:CreateNewMapPoints()
步骤:步骤1:在当前关键帧的 共视关键帧 中找到 共视程度 最高的前nn帧 相邻帧vpNeighKFs步骤2:遍历和当前关键帧 相邻的 每一个关键帧vpNeighKFs步骤3:判断相机运动的基线在(两针间的相机相对坐标)是不是足够长步骤4:根据两个关键帧的位姿计算它们之间的基本矩阵 F =  inv(K1 转置) * t12 叉乘 R12 * inv(K2)步骤5:通过帧间词典向量加速匹配,极线约束限制匹配时的搜索范围,进行特征点匹配步骤6:对每对匹配点 2d-2d 通过三角化生成3D点,和 Triangulate函数差不多步骤6.1:取出匹配特征点步骤6.2:利用匹配点反投影得到视差角   用来决定使用三角化恢复(视差角较大) 还是 直接2d点反投影(视差角较小)步骤6.3:对于双目,利用双目基线 深度 得到视差角步骤6.4:视差角较大时 使用 三角化恢复3D点 (两个点,四个方程,奇异值分解求解)步骤6.4:对于双目 视差角较小时, 2d点利用深度值 反投影 成三维点 ,单目的话直接跳过步骤6.5:检测生成的3D点是否在相机前方(Z>0)步骤6.6:计算3D点在当前关键帧下的重投影误差,误差较大的跳过步骤6.7:计算3D点在邻接关键帧 下的重投影误差,误差较大的跳过步骤6.9:三角化生成3D点成功,构造成地图点 MapPoint步骤6.9:为该MapPoint添加属性 地图点关键关键帧关键帧关联地图点地图点更新最优区别性的描述子地图点更新深度地图添加地图点   步骤6.10:将新产生的点放入检测队列 mlpRecentAddedMapPoints  交给 MapPointCulling() 检查生成的点是否合适

4.7 闭环检测线程 LoopClosing

参考
思想:
闭环检测线程
对新加入的关键帧,
1. 进行回环检测(WOB 二进制词典匹配检测,通过Sim3算法计算相似变换)--------->
2. 闭环校正(闭环融合 和 图优化)

LoopClosing::Run() 闭环检测步骤:mlpLoopKeyFrameQueue  闭环检测关键帧队列(localMapping线程 加入的)只要闭环检测关键帧队列不为空下面的检测会一直执行
步骤0:进行闭环检测 LoopClosing::DetectLoop()步骤1:从队列中抽取一帧 mpCurrentKF = mlpLoopKeyFrameQueue.front();mlpLoopKeyFrameQueue.pop_front();//出队步骤2:判断距离上次闭环检测是否超过10帧,如果数量过少则不进行闭环检测。步骤3:遍历所有共视关键帧,计算当前关键帧与每个共视关键帧 的bow相似度得分,并得到最低得分minScore GetVectorCovisibleKeyFrames(); // 当前帧的所有 共视关键帧mpORBVocabulary->score(CurrentBowVec, BowVec);// bow向量相似性得分步骤4:在所有关键帧数据库中找出与当前帧按最低得分minScore 匹配得到的闭环候选帧 vpCandidateKFs步骤5:在候选帧中检测具有连续性的候选帧,相邻一起的分成一组,组与组相邻的再成组(pKF, minScore)  步骤6:找出与当前帧有 公共单词的 非相连 关键帧 lKFsharingwords步骤7:统计候选帧中 与 pKF 具有共同单词最多的单词数 maxcommonwords步骤8:得到阈值 minCommons = 0.8 × maxcommonwords  步骤9:筛选共有单词大于 minCommons  且词带 BOW 匹配 最低得分  大于  minScore , lscoreAndMatch步骤10:将存在相连的分为一组,计算组最高得分 bestAccScore,同时得到每组中得分最高的关键帧  lsAccScoreAndMatch步骤11:得到阈值 minScoreToRetain = 0.75  ×  bestAccScore  步骤12:得到 闭环检测候选帧
步骤13:计算 闭环处两针的 相似变换  [sR|t]LoopClosing::ComputeSim3()
步骤14:闭环融合位姿图优化LoopClosing::CorrectLoop()

LoopClosing::ComputeSim3() 计算当前帧与闭环帧的Sim3变换

思想:通过Bow词袋量化加速描述子的匹配,利用RANSAC粗略地计算出当前帧与闭环帧的Sim3(当前帧---闭环帧)根据估计的Sim3,对3D点进行投影找到更多匹配,通过优化的方法计算更精确的Sim3(当前帧---闭环帧)将闭环帧以及闭环帧相连的关键帧的MapPoints与当前帧的点进行匹配(当前帧---闭环帧+相连关键帧)注意以上匹配的结果均都存在成员变量 mvpCurrentMatchedPoints 中,实际的更新步骤见CorrectLoop()步骤3:Start Loop Fusion
步骤:步骤1:遍历每一个闭环候选关键帧  构造 sim3 求解器步骤2:从筛选的闭环候选帧中取出一帧关键帧pKF步骤3:将当前帧mpCurrentKF与闭环候选关键帧pKF匹配 得到匹配点对步骤3.1 跳过匹配点对数少的 候选闭环帧步骤3.2:  根据匹配点对 构造Sim3求解器步骤4:迭代每一个候选闭环关键帧  Sim3利用 相似变换求解器求解 候选闭环关键帧到档期帧的 相似变换步骤5:通过步骤4求取的Sim3变换,使用sim3变换匹配得到更多的匹配点 弥补步骤3中的漏匹配步骤6:G2O Sim3优化,只要有一个候选帧通过Sim3的求解与优化,就跳出停止对其它候选帧的判断步骤7:如果没有一个闭环匹配候选帧通过Sim3的求解与优化 清空候选闭环关键帧步骤8:取出闭环匹配上 关键帧的相连关键帧,得到它们的地图点MapPoints放入mvpLoopMapPoints步骤9:将闭环匹配上关键帧以及相连关键帧的 地图点 MapPoints 投影到当前关键帧进行投影匹配 为当前帧查找更多的匹配步骤10:判断当前帧 与检测出的所有闭环关键帧是否有足够多的MapPoints匹配	步骤11:满足匹配点对数>40 寻找成功 清空mvpEnoughConsistentCandidates

LoopClosing::CorrectLoop() 闭环融合 全局优化

步骤: 步骤1:通过求解的Sim3以及相对姿态关系,调整与 当前帧相连的关键帧 mvpCurrentConnectedKFs 位姿 以及这些 关键帧观测到的MapPoints的位置(相连关键帧---当前帧)步骤2:用当前帧在闭环地图点 mvpLoopMapPoints 中匹配的 当前帧闭环匹配地图点 mvpCurrentMatchedPoints  更新当前帧 之前的 匹配地图点 mpCurrentKF->GetMapPoint(i)步骤3:将闭环帧以及闭环帧相连的关键帧的 所有地图点 mvpLoopMapPoints 和  当前帧相连的关键帧的点进行匹配 步骤4:通过MapPoints的匹配关系更新这些帧之间的连接关系,即更新covisibility graph步骤5:对Essential Graph(Pose Graph)进行优化,MapPoints的位置则根据优化后的位姿做相对应的调整步骤6:创建线程进行全局Bundle AdjustmentmvpCurrentConnectedKFs    当前帧相关联的关键帧
vpLoopConnectedKFs        闭环帧相关联的关键帧  这些关键帧的地图点 闭环地图点 mvpLoopMapPoints
mpMatchedKF               与当前帧匹配的  闭环帧
mpCurrentKF 当前关键帧     优化的位姿 mg2oScw     原先的地图点 mpCurrentKF->GetMapPoint(i)
mvpCurrentMatchedPoints   当前帧在闭环地图点中匹配的地图点  当前帧闭环匹配地图点 

5. 数学理论总结

参考

在这里插入图片描述


http://chatgpt.dhexx.cn/article/Ubejo96l.shtml

相关文章

opencv4算法库学习笔记(5万多字超长干货——纪念奋战的自己)

整理于2020年初三个月的日夜积累。。。 参考链接 opencv安装 安装脚本链接&#xff1a;https://github.com/milq/milq/blob/master/scripts/bash/install-opencv.sh 源码编译安装参考&#xff1a;https://blog.csdn.net/liuli2008212/article/details/128169266?spm1001.2…

【数字图像处理课程设计】期中、期末综合考试题目整理总结(共四个图像处理算法应用题)

目录 一、下面两幅图像中有几处不同&#xff0c;编程把它们找出来、并在图中突出显示&#xff08;关键步骤不能调用内置函数&#xff09;。 1.算法原理 2.解题步骤 3.程序代码 4.处理结果 二、下图含有干扰条纹&#xff08;moir pattern&#xff09;、并且低灰度区域的细…

MATLAB各个产品概述----哪些产品需要安装?哪些产品不需要安装?阅完了然

MATLAB产品概述 文章目录 1 MATLAB2 Simulink3 5G Toolbox&#xff08;5G工具箱&#xff09;4 Aerospace Blockset&#xff08;航空区块集&#xff09;5 Aerospace Toolbox&#xff08;航空航天工具箱&#xff09;6 Antenna Toolbox&#xff08;天线工具箱&#xff09;7 Audio…

Latex相关符号

函数、符号及特殊字符 声调 语法效果语法效果语法效果\bar{x}\acute{\eta}\check{\alpha}\grave{\eta}\breve{a}\ddot{y}\dot{x}\hat{\alpha}\tilde{\iota} 函数 语法效果语法效果语法效果\sin\theta\cos\theta\tan\theta\arcsin\frac{L}{r}\arccos\frac{T}{r}\arctan\frac{L…

一文读懂人脸识别技术

2019-08-27 17:06:26 本文内容涵盖人脸识别发展历程、市场研究、核心技术、商业应用以及产业落地、个人看法等干货研究。注意&#xff0c;本文干货满满&#xff0c;约有2万7千字&#xff0c;强烈建议大家先收藏后学习&#xff01; 01 发展史 1. 人脸识别的理解 人脸识别(Fa…

综述 | 基于特征的视觉同步定位和建图

点击上方“计算机视觉工坊”&#xff0c;选择“星标” 干货第一时间送达 Feature‑based visual simultaneous localization and mapping: a survey Rana Azzam1 Tarek Taha2 Shoudong Huang3 Yahya Zweiri4 接收日期&#xff1a;2019 年 10 月 30 日/接受时间&#xff1a;…

ORB_SLAM2 源码解析 单目初始化器Initializer(三)

目录 一、地图点初始化 二、重新记录特征点的匹配关系 1、构建旋转直方图 1.1、在半径窗口内搜索当前帧F2中所有的候选匹配特征点GetFeaturesInArea 1.2、表示一个图像像素相当于多少个图像网格列和行 1.4、遍历圆形区域内的所有网格&#xff0c;寻找满足条件的候选特征点&…

Zotero文献管理软件使用指南——入门篇

一、安装与注册 zotero下载地址 二、文献导入 2.1 方法一&#xff1a;Zoreto Connector插件 2.1.1 下载插件 还是刚刚那个网址&#xff0c;点击红色方框下载插件。 下载完成之后浏览器上方会有如下图所示的小图标 2.1.2 导入举例 以知网为例 找到你想看的…

【EndNote】功能强大的文献管理软件

EndNote X9是一款功能强大的文献管理软件,使用这款EndNote X9破解版可以让你直接将其安装到Windows操作系统上使用,如果您正需要这款免费版工具,马上下载EndNote X9使用吧。 基本简介 EndNote 是一款主流文献管理软件&#xff0c;有数以百万计的研究人员、学生和图书管理员使用…

文献管理软件Zotero配置及使用

文献管理软件-Zotero常用插件安装及配置使用 一、Zotero安装与同步盘配置 1、下载Zotero并安装2、配置Zotero &#xff08;1&#xff09;配置同步盘&#xff08;以onedrive为例&#xff09;——如果不配置同步盘&#xff0c;这一步可以跳过&#xff08;2&#xff09;配置输出文…

文献管理软件//Zotero导入文献的五种方式(九)

Zotero导入文献的五种方式 一、利用zotero插件自动获取pdf文件二、利用DOI获取pdf文件三、从剪贴板导入pdf文件3.1 导入单篇文献3.2 导入多篇文献 四、利用endnote格式导入文献五、通过已下载的PDF文件导入文献 一、利用zotero插件自动获取pdf文件 首先&#xff0c;可以通过以…

文献管理软件Mendeley基本使用教程

一、文献管理软件 文献管理软件是学者或者作者用于记录、组织、调阅引用文献的计算机程序&#xff0c;其便利之处在于&#xff1a; 1.直接联网不同数据库进行文献检索&#xff0c;提高效率&#xff1b; 2.方便快捷管理文献信息&#xff0c;包括文摘、全文、笔记记录、以及其…

文献管理软件Zotero常用插件安装及配置使用

文献管理软件-Zotero常用插件安装及配置使用 一、Zotero安装与同步盘配置1、下载Zotero并安装2、配置Zotero&#xff08;1&#xff09;配置同步盘&#xff08;以onedrive为例&#xff09;——如果不配置同步盘&#xff0c;这一步可以跳过&#xff08;2&#xff09;配置输出文献…

文献管理软件zotero|电脑和平板文献管理实现同步

高效管理文献——实现PC和ipad同步 作为一个科研打工人&#xff0c;读论文是我们每个人基本天天都要做的事&#xff0c;但论文越来越多如何实现论文高效管理&#xff1f;利用文献管理软件zotero&#xff0c;能实现高效管理文献。 之前也用过&#xff0c;mendeley软件也用过&a…

科研必备文献管理软件EndNote

什么是ENDNOTE&#xff1f; Endnote是一款被广泛使用的文献管理软件&#xff0c;其是SCI&#xff08;Thomson Scientific 公司&#xff09;的官方软件&#xff0c;支持国际期刊的参考文献格式有3776 种【也可以自定义期刊引用格式】。 软件非常方便科研狗进行文献整理&#xf…

四款主流文献管理软件,总有一款适合你

本文作者&#xff1a;生信不是人学的 看文献是每个科研人都必须做的事情 但随着阅读量的增加&#xff0c;面对几十甚至上百篇文献&#xff0c;单纯靠自己的记忆来整理文献是一件不太可能的事情。 因此需要一款合适的软件来帮助我们进行文献管理&#xff0c;提高科研效率。 接下…

如何让vim编辑器永久显示行号

在Linux环境下的编辑器有vi、vim、gedit等等。进入这些编辑器之后&#xff0c;为了方便我们需要编辑器显示出当前的行号&#xff0c;可偏偏编辑器默认是不会显示行号的。我们有二种办法可以解决&#xff1a; 第一种是&#xff0c;手动显示&#xff1a;在vim命令行模式下输入 …

【LINUX-vim命令】设置vim显示行号

【vim命令】设置vim显示行号 linux环境下&#xff0c;使用vim查看或编辑文件&#xff0c;vim打开的文件默认是不显示行号的&#xff0c;问题&#xff1a;怎么才能让vim打开的文件显示行号呢&#xff1f; 1. 临时显示行号 set number2. 永久显示行号 # 打开 /etc/vimrc文件 vi…

vim显示/隐藏行号,永久显示行号

显示行号 在vim命令中输入下面的内容可以给文本文件显示行号&#xff1a; :set nu或:set number 隐藏行号 下面的命令可以将行号隐藏&#xff1a; :set nonu或:set nonumber 永久显示行号 修改vim配置文件可以设置默认显示或隐藏行号&#xff1a; vim /etc/vimrc #全局…

设置VIM编辑器显示行号

方式1&#xff1a; 临时显示行号 在命令行模式下直接输入“&#xff1a;set number”即可显示行号&#xff0c;退出以后再次打开vim编辑器依然没有行号。 方式2&#xff1a;永久显示行号 (1)如果想让vim永久显示行号&#xff0c;则需要修改vim配置文件vimrc。如果没有此文件…