Map Points: 每个地图点 p i p_{i} pi 都存着以下信息。
Keyframe: 每个关键帧 K i \mathbf{K}_i Ki 都存着以下信息。
地图点和关键帧的选择和建立都是非常 generous 的,然后会有一个 exigent point culling policy 来剔除多余的关键帧,这保证了系统建图的 robustness。
Covisibility Graph 被表示成一个 Undirected Weighted Graph,其中每一个 node 表示一个 keyframe, 每条edge表示了两个keyframes有至少15个共同可观测到的map point, edge 的 weight θ \theta θ 为共同可观测到的点的数量。
相比于 covisibility graph, essential graph 有同样的关键帧,但是较少的edge,这是因为如果在loop closure 里使用covisibility graph的话,整个地图还是太dense了,优化起来计算量比较大。地图系统不断的扩展关键帧的数量,生成covisibility graph, 同时地,系统也在生成essential graph,当一个新关键帧生成的时候,这个关键帧会被链接到和他共有最多共同观测点当关键帧上。 The Essential Graph 包含了 spanning tree, 还有covisibility graph edges的高共视度(with high covisibility θ m i n = 100 \theta_{min} = 100 θmin=100)的子集,还有loop closure 的edge,保证了相机poses形成了一个强健的网络。
下图是ORBSLAM论文1里面关于 Covisibility Graph and Essential Graph 的描述图。
The vocabulary is created offline with the ORB descriptors extracted from a large set of images.2
Tracking 线程是ORBSLAM系统的主线程,我们从系统的启动来分析。
首先,系统通过System.h文件constructor初始化,且Tracking.h包含的constructor也一并初始化。
接下来,对于每一张输入的图片,执行以下函数,其中包含对于当前状态的判断,需不需要reset,是不是localisation only的模式,然后跳到Tracking.h文件里面。
cv::Mat System::TrackMonocular(const cv::Mat &im, const double ×tamp)
{
if(mSensor!=MONOCULAR)
{
cerr << "ERROR: you called TrackMonocular but input sensor was not set to Monocular." << endl;
exit(-1);
}
// Check mode change
{
unique_lock<mutex> lock(mMutexMode);
if(mbActivateLocalizationMode)
{
mpLocalMapper->RequestStop();
// Wait until Local Mapping has effectively stopped
while(!mpLocalMapper->isStopped())
{
//usleep(1000);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
mpTracker->InformOnlyTracking(true);// 定位时,只跟踪
mbActivateLocalizationMode = false;// 防止重复执行
}
if(mbDeactivateLocalizationMode)
{
mpTracker->InformOnlyTracking(false);
mpLocalMapper->Release();
mbDeactivateLocalizationMode = false;// 防止重复执行
}
}
// Check reset
{
unique_lock<mutex> lock(mMutexReset);
if(mbReset)
{
mpTracker->Reset();
mbReset = false;
}
}
return mpTracker->GrabImageMonocular(im,timestamp);
}
接下来进入,mpTracker->GrabImageMonocular(im,timestamp); 在这个函数里面,注意mpTracker指的是我们之前初始化过的Tracking类。
cv::Mat Tracking::GrabImageMonocular(const cv::Mat &im, const double ×tamp)
{
mImGray = im;
// 步骤1:将RGB或RGBA图像转为灰度图像
if(mImGray.channels()==3)
{
if(mbRGB)
cvtColor(mImGray,mImGray,CV_RGB2GRAY);
else
cvtColor(mImGray,mImGray,CV_BGR2GRAY);
}
else if(mImGray.channels()==4)
{
if(mbRGB)
cvtColor(mImGray,mImGray,CV_RGBA2GRAY);
else
cvtColor(mImGray,mImGray,CV_BGRA2GRAY);
}
// 步骤2:构造Frame
if(mState==NOT_INITIALIZED || mState==NO_IMAGES_YET)// 没有成功初始化的前一个状态就是NO_IMAGES_YET
mCurrentFrame = Frame(mImGray,timestamp,mpIniORBextractor,mpORBVocabulary,mK,mDistCoef,mbf,mThDepth);
else
mCurrentFrame = Frame(mImGray,timestamp,mpORBextractorLeft,mpORBVocabulary,mK,mDistCoef,mbf,mThDepth);
// 步骤3:跟踪
Track();
return mCurrentFrame.mTcw.clone();
}
在这个函数里,我们主要做了以下几件事情:
那么我们先来看一下ORBSLAM是如何对每张输入图片构建Frame的。 **注意:**这里氛围初始化还是不是初始化的情况,初始化的时候mpIniORBextractor使用了两倍的特征点数量,代码如下,在Tracking.h的constructor定义。
// tracking过程都会用到mpORBextractorLeft作为特征点提取器
mpORBextractorLeft = new ORBextractor(nFeatures,fScaleFactor,nLevels,fIniThFAST,fMinThFAST);
// 在单目初始化的时候,会用mpIniORBextractor来作为特征点提取器
if(sensor==System::MONOCULAR)
mpIniORBextractor = new ORBextractor(2*nFeatures,fScaleFactor,nLevels,fIniThFAST,fMinThFAST);
mK,mDistCoef 分别是intrinsic matrix 和 distortion 矫正参数。
mbf,mThDepth这两个参数在Monocular里面是用不到的,但是还是传递了进去。
回到上面的第2点,构造此图像对应的Frame。Frame的构造函数,也就是代码里的步骤二展示的部分,展开可以看到:
// 单目初始化
Frame::Frame(const cv::Mat &imGray, const double &timeStamp, ORBextractor* extractor,ORBVocabulary* voc, cv::Mat &K, cv::Mat &distCoef, const float &bf, const float &thDepth)
:mpORBvocabulary(voc),mpORBextractorLeft(extractor),mpORBextractorRight(static_cast<ORBextractor*>(NULL)),
mTimeStamp(timeStamp), mK(K.clone()),mDistCoef(distCoef.clone()), mbf(bf), mThDepth(thDepth)
{
// Frame ID
mnId=nNextId++;
// Scale Level Info
mnScaleLevels = mpORBextractorLeft->GetLevels();
mfScaleFactor = mpORBextractorLeft->GetScaleFactor();
mfLogScaleFactor = log(mfScaleFactor);
mvScaleFactors = mpORBextractorLeft->GetScaleFactors();
mvInvScaleFactors = mpORBextractorLeft->GetInverseScaleFactors();
mvLevelSigma2 = mpORBextractorLeft->GetScaleSigmaSquares();
mvInvLevelSigma2 = mpORBextractorLeft->GetInverseScaleSigmaSquares();
// ORB extraction
ExtractORB(0,imGray);
N = mvKeys.size();
if(mvKeys.empty())
return;
// 调用OpenCV的矫正函数矫正orb提取的特征点
UndistortKeyPoints();
// Set no stereo information
mvuRight = vector<float>(N,-1);
mvDepth = vector<float>(N,-1);
mvpMapPoints = vector<MapPoint*>(N,static_cast<MapPoint*>(NULL));
mvbOutlier = vector<bool>(N,false);
// This is done only for the first Frame (or after a change in the calibration)
if(mbInitialComputations)
{
ComputeImageBounds(imGray);
mfGridElementWidthInv=static_cast<float>(FRAME_GRID_COLS)/static_cast<float>(mnMaxX-mnMinX);
mfGridElementHeightInv=static_cast<float>(FRAME_GRID_ROWS)/static_cast<float>(mnMaxY-mnMinY);
fx = K.at<float>(0,0);
fy = K.at<float>(1,1);
cx = K.at<float>(0,2);
cy = K.at<float>(1,2);
invfx = 1.0f/fx;
invfy = 1.0f/fy;
mbInitialComputations=false;
}
mb = mbf/fx;
AssignFeaturesToGrid();
}
首先设定关于ORB feature的参数,然后提取特征点:
// Extract ORB on the image. 0 for left image and 1 for right image.
// 提取的关键点存放在mvKeys和mDescriptors中
// ORB是直接调orbExtractor提取的
void ExtractORB(int flag, const cv::Mat &im);
构建Frame这一步最重要的就是提取ORB feature了,然后我们就可以进行到第3步,Track();
ORB-SLAM: a Versatile and Accurate Monocular SLAM System ↩︎
Bags of Binary Words for Fast Place Recognition in Image Sequences ↩︎