天天看点

ORB_SLAM2代码技术路线代码入口Tracking主线程LocalMapping线程LocalClosing线程(闭环检测)

ORB_SLAM2代码技术路线

  • 代码入口
  • Tracking主线程
  • LocalMapping线程
  • LocalClosing线程(闭环检测)

代码入口

ORB_SLAM2具有多种工作模式,总体可以分为ROS版本的和非ROS版本,由于本人更喜欢ROS版本,因此在此只讨论ROS版本的代码。事实上,ROS版本和非ROS版本只是数据获取方式不同,比如ROS的图像是通过ROS话题的形式读取的,而非ROS版本则是从本地读取图片。除此之外,后续的处理代码用的是同一套代码。

首先找到可执行文件所对应的源文件,在Example文件夹的ROS文件夹下。分别是ros_mono.cc ros_rgbd.cc ros_stereo.cc。ROS版本的代码又可以分成三种模式,分别是单目、RGBD和双目版本。

代码大致分为三部分,追踪,局部地图和闭环,三部分都包含了位姿的计算,追踪部分是计算两帧之间的位姿,局部地图相当于一个滑动窗口,计算从追踪部分添加进来的一定数量内的关键帧之间的位姿信息,而回环部分则是检测是否有闭环的出现,并且进行全局的位姿优化。

我们从单目版本开始分析,找到main()函数。

首先创建一个SLAM对象:

------------->ros_mono.cc
// Create SLAM system. It initializes all system threads and gets ready to process frames.
ORB_SLAM2::System SLAM(argv[1],argv[2],ORB_SLAM2::System::MONOCULAR,true);
ImageGrabber igb(&SLAM);
           

在System.cc中完成了SLAM系统的初始化:

------------->System.cc
// Initialize the SLAM system. It launches the Local Mapping, Loop Closing and Viewer threads.
//构造函数,用来初始化整个系统。
System(const string &strVocFile,            //指定ORB字典文件的路径
       const string &strSettingsFile,       //指定配置文件的路径
       const eSensor sensor,                //指定所使用的传感器类型
       const bool bUseViewer = true);       //指定是否使用可视化界面 
           

这个函数中设置了相机类型,系统除了在主线程中进行运动追踪工作外,会创建局部建图线程、回环检测线程和查看器线程(就是可视化工具)。

分别如下:

------------->System.cc
//在本主线程中初始化追踪线程
//Initialize the Tracking thread
//(it will live in the main thread of execution, the one that called this constructor)
mpTracker = new Tracking(this,						//现在还不是很明白为什么这里还需要一个this指针  TODO  
    					 mpVocabulary,				//字典
    					 mpFrameDrawer, 			//帧绘制器
    					 mpMapDrawer,				//地图绘制器
                         mpMap, 					//地图
                         mpKeyFrameDatabase, 		//关键帧地图
                         strSettingsFile, 			//设置文件路径
                         mSensor);					//传感器类型iomanip

//初始化局部建图线程并运行
//Initialize the Local Mapping thread and launch
mpLocalMapper = new LocalMapping(mpMap, 				//指定使iomanip
    			    mSensor==MONOCULAR);	
//运行这个局部建图线程
mptLocalMapping = new thread(&ORB_SLAM2::LocalMapping::Run,	//这个线程会调用的函数
    				mpLocalMapper);				//这个调用函数的参数

//Initialize the Loop Closing thread and launchiomanip
mpLoopCloser = new LoopClosing(mpMap, 						//地图
    				mpKeyFrameDatabase, 			//关键帧数据库
    				mpVocabulary, 				//ORB字典
    				mSensor!=MONOCULAR);			//当前的传感器是否是单目
//运行这个回环检测线程
mptLoopClosing = new thread(&ORB_SLAM2::LoopClosing::Run,	//线程的主函数
    				mpLoopCloser);					//该函数的参数
           

线程三个x函数就是最重要的函数,点进去可以查看函数具体内容。

并且在类ImageGrabber中定义了回调函数。

------------->ros_mono.cc
class ImageGrabber
{
public:
    ImageGrabber(ORB_SLAM2::System* pSLAM):mpSLAM(pSLAM){}

    void GrabImage(const sensor_msgs::ImageConstPtr& msg);

    ORB_SLAM2::System* mpSLAM;
};
           

定义ROS句柄,设置图像消息,其他相机模式所订阅的消息会不一样,感兴趣的小伙伴可以去看看。

------------->ros_mono.cc
ros::NodeHandle nodeHandler;
ros::Subscriber sub = nodeHandler.subscribe("/camera/image_raw", 1, &ImageGrabber::GrabImage,&igb);
           

Tracking主线程

接下来系统将不断地进入回调函数中执行功能函数。这个回调函数有点像单片机的硬件中断,只要接收到图像,就会唤起回调函数进行处理。

回调函数执行功能函数:

------------->ros_mono.cc
mpSLAM->TrackMonocular(cv_ptr->image,cv_ptr->header.stamp.toSec());
           

进入该函数内部,首先是判断系统是否为定位模式,如果是定位模式,则关闭局部地图线程。

------------->Tracking.cc
//获取相机位姿的估计结果,在这里进行位姿估计
cv::Mat Tcw = mpTracker->GrabImageMonocular(im,timestamp);
           

再点进去

------------->Tracking.cc
//获取相机位姿的估计结果,在这里进行位姿估计
//构造Frame
if(mState==NOT_INITIALIZED || mState==NO_IMAGES_YET)// 没有成功初始化的前一个状态就是NO_IMAGES_YET
    mCurrentFrame = Frame(
    mImGray,
    timestamp,
    mpIniORBextractor,      //LNOTICE 这个使用的是初始化的时候的ORB特征点提取器
    mpORBVocabulary,
    mK,
    mDistCoef,
    mbf,
    mThDepth);
else
    mCurrentFrame = Frame(
    mImGray,
    timestamp,
    mpORBextractorLeft,     //NOTICE 当程序正常运行的时候使用的是正常的ORB特征点提取器
    mpORBVocabulary,
    mK,
    mDistCoef,
    mbf,
    mThDepth);
           

进入Frame()方法内,该方法主要是对图像进行特征点检测,这里不同的相机类型会有所差别,弹幕和RGBD提取特征点的方法一样,RGBD会多一个深度赋值,双目会开两个线程分别对两张图像同时处理

------------->Frame.cc
//图像跟踪
//单目和RGBD图像进行提取特征点操作是一样的
ExtractORB(0,imGray);
//-------------------------------------------分割线,下面是双目的
// 同时对左右目提特征,还同时开了两个线程
thread threadLeft(&Frame::ExtractORB,this,0,imLeft);
//对右目图像提取ORB特征,参数含义同上
thread threadRight(&Frame::ExtractORB,this,1,imRight);
//等待两张图像特征点提取过程完成
threadLeft.join();
threadRight.join();

//将特征点分配到图像网格中
//应该是文章中介绍的让特征点均匀分布的具体实现
AssignFeaturesToGrid();
           

Frame()执行完之后,执行Track(),进入该函数,包含了位姿估计初始化和跟踪

------------->Tracking.cc
//忽略部分代码
// step 1:初始化
if(mState==NOT_INITIALIZED)
{
    if(mSensor==System::STEREO || mSensor==System::RGBD)
        //双目初始化(RGBD相机也当做为双目相机来看待)
        StereoInitialization();
    else
        //单目初始化
        MonocularInitialization();
}
           

如果初始化已经完成,下面进行追踪,当然实际过程还有更复杂的情况,主要代码就是下面这两个函数

作者允许允许两种模式,第一种是非纯追踪模式,第二种是纯追踪模式,在第二种模式中可以看作是定位模式,关闭局部地图,不插入关键帧,我们只讨论第一种模式

------------->Tracking.cc
//恒速模型方法
// 通过投影的方式在参考帧中找当前帧特征点的匹配点
TrackWithMotionModel();
//参考关键帧方法
//通过BoW的方式在参考帧中找当前帧特征点的匹配点
// 优化每个特征点都对应3D点重投影误差即可得到位姿
TrackReferenceKeyFrame()
           

这两个函数表示的是两种求位姿的方法,首选第一个函数,该函数是在系统正常追踪的时候采用的方法,如果正常追踪失败了,才用第二个函数。如果还没进行初始化则进行重定位。以上两个函数都是寻找匹配的点进行位姿计算。分别如下:

------------->Tracking.cc
int nmatches = matcher.SearchByProjection(mCurrentFrame,mLastFrame,th,mSensor==System::MONOCULAR);
//and
int nmatches = matcher.SearchByBoW( mpReferenceKF,mCurrentFrame,vpMapPointMatches);
           

然后两个函数都讲匹配点存储到 mCurrentFrame类中。有了匹配的点,就可以进行优化了,优化代码如下,其中传入mCurrentFrame类,这个函数是位姿估计的重头戏,通过g2o优化器进行优化。

------------->Tracking.cc
//通过优化3D-2D的重投影误差来获得位姿
Optimizer::PoseOptimization(&mCurrentFrame);
           

点进这个函数,里面就是g2o的优化套路,最后计算得到位姿。

------------->Optimizer.cc
// 得到优化后的当前帧的位姿
cv::Mat pose = Converter::toCvMat(SE3quat_recov);
pFrame->SetPose(pose);
           

LocalMapping线程

回顾前面讲到的线程创建代码中,在System.cc

------------->Optimizer.cc
mptLocalMapping = new thread(&ORB_SLAM2::LocalMapping::Run,	//这个线程会调用的函数
    				mpLocalMapper);				//这个调用函数的参数
           

这个线程的实现函数在LocalMapping.cc文件中

------------->LocalMapping.cc
void LocalMapping::Run()
{
// 查看列表中是否有等待被插入的关键帧
if(CheckNewKeyFrames())
{
	// 计算关键帧特征点的BoW映射,将关键帧插入地图
	ProcessNewKeyFrame();
	// 剔除ProcessNewKeyFrame函数中引入的不合格MapPoints
    MapPointCulling();
    // 相机运动过程中与相邻关键帧通过三角化恢复出一些MapPoints
    CreateNewMapPoints();
    // 检查并融合当前关键帧与相邻帧(两级相邻)重复的MapPoints
    SearchInNeighbors();
    进行局部地图的BA
    Optimizer::LocalBundleAdjustment(mpCurrentKeyFrame,&mbAbortBA, mpMap);
	// 检测并剔除当前帧相邻的关键帧中冗余的关键帧
	KeyFrameCulling();
	// 将当前帧加入到闭环检测队列中
	mpLoopCloser->InsertKeyFrame(mpCurrentKeyFrame);
}
}
           

代码中还有许多线程协同工作的细节,本文只是理清楚代码的粗略运行方式,所以不深究

LocalClosing线程(闭环检测)

------------->LoopClosing.cc
void LoopClosing::Run()
{
    if(CheckNewKeyFrames())
    {
        // Detect loop candidates and check covisibility consistency
        //检测是否有回环
        if(DetectLoop())
        {
            // Compute similarity transformation [sR|t]---sim3比se3多了一个相对尺度s
            // In the stereo/RGBD case s=1
            if(ComputeSim3())
            {
                // Perform loop fusion and pose graph optimization
                //在此处进行回环的BA
                CorrectLoop();
            }
        }
    }
}
           

继续阅读