这里总结一下orbslam2的初始化过程,和之前vins的视觉初始化比较看看有什么不同
orbslam2单目的程序入口为
// Pass the image to the SLAM system
SLAM.TrackMonocular(im,tframe);
我们都知道orbslam2分成三个线程并行,初始化部分是在tracking线程中进行的
void Tracking::Track()
{
// track包含两部分:估计运动、跟踪局部地图
// mState为tracking的状态机
// SYSTME_NOT_READY, NO_IMAGE_YET, NOT_INITIALIZED, OK, LOST
// 如果图像复位过、或者第一次运行,则为NO_IMAGE_YET状态
if(mState==NO_IMAGES_YET)
{
mState = NOT_INITIALIZED;
}
// mLastProcessedState存储了Tracking最新的状态,用于FrameDrawer中的绘制
mLastProcessedState=mState;
// Get Map Mutex -> Map cannot be changed 互斥锁详解见https://blog.csdn.net/fengbingchun/article/details/78638138?locationNum=2&fps=1 lishuwei 2018.11.24
unique_lock<mutex> lock(mpMap->mMutexMapUpdate); //当有效时,lock锁管理模板类管理锁对象,周期结束后自动解锁 ,意思为锁住地图更新 lishuwei 2018.11.24
// 步骤1:初始化
if(mState==NOT_INITIALIZED)
{
if(mSensor==System::STEREO || mSensor==System::RGBD)
StereoInitialization();
else
MonocularInitialization();
mpFrameDrawer->Update(this);
if(mState!=OK)
return;
}
else// 步骤2:跟踪
在这个函数里主要进行单目的初始化
/**
* @brief 单目的地图初始化 主要是对极几何
*
* 并行地计算基础矩阵和单应性矩阵,选取其中一个模型,恢复出最开始两帧之间的相对姿态以及点云
* 得到初始两帧的匹配、相对运动、初始MapPoints
*/
void Tracking::MonocularInitialization()
{
// 如果单目初始器还没有被创建,则创建单目初始器
if(!mpInitializer)
{
// Set Reference Frame
// 单目初始帧的特征点数必须大于100
if(mCurrentFrame.mvKeys.size()>100)
{
// 步骤1:得到用于初始化的第一帧,初始化需要两帧
mInitialFrame = Frame(mCurrentFrame);
// 记录最近的一帧
mLastFrame = Frame(mCurrentFrame);
// mvbPrevMatched最大的情况就是所有特征点都被跟踪上
mvbPrevMatched.resize(mCurrentFrame.mvKeysUn.size());
for(size_t i=0; i<mCurrentFrame.mvKeysUn.size(); i++)
mvbPrevMatched[i]=mCurrentFrame.mvKeysUn[i].pt;
//空的时候不进去,直接跳过到下面,生成一个指针,然后就能去else了
//最前面的if得是mpInitializer为空才进来,那么到这里mpInitializer肯定是空的,这个if进不去
// 这两句是多余的
if(mpInitializer)
delete mpInitializer;
// 由当前帧构造初始器 sigma:1.0 iterations:200
mpInitializer = new Initializer(mCurrentFrame,1.0,200);
fill(mvIniMatches.begin(),mvIniMatches.end(),-1);
return;
}
}
else
{
// Try to initialize
// 步骤2:如果当前帧特征点数大于100,则得到用于单目初始化的第二帧
// 如果当前帧特征点太少,重新构造初始器
// 因此只有连续两帧的特征点个数都大于100时,才能继续进行初始化过程
if((int)mCurrentFrame.mvKeys.size()<=100)
{
//如果第一帧大于100了,但是第二帧小于等于100,再把mpInitializer变成0
delete mpInitializer;
//delete只会释放内存空间,不会删除指针本身,不置为空就变成野指针了
mpInitializer = static_cast<Initializer*>(NULL);
fill(mvIniMatches.begin(),mvIniMatches.end(),-1);
return;
}
// Find correspondences
// 步骤3:在mInitialFrame与mCurrentFrame中找匹配的特征点对
// mvbPrevMatched为前一帧的特征点,存储了mInitialFrame中哪些点将进行接下来的匹配
// mvIniMatches存储mInitialFrame,mCurrentFrame之间匹配的特征点
ORBmatcher matcher(0.9,true);
int nmatches = matcher.SearchForInitialization(mInitialFrame,mCurrentFrame,mvbPrevMatched,mvIniMatches,100);
// Check if there are enough correspondences
// 步骤4:如果初始化的两帧之间的匹配点太少,重新初始化
if(nmatches<100)
{
delete mpInitializer;
mpInitializer = static_cast<Initializer*>(NULL);
return;
}
cv::Mat Rcw; // Current Camera Rotation
cv::Mat tcw; // Current Camera Translation
vector<bool> vbTriangulated; // Triangulated Correspondences (mvIniMatches) 三角对应
// 步骤5:通过H模型或F模型进行单目初始化,得到两帧间相对运动、初始MapPoints
if(mpInitializer->Initialize(mCurrentFrame, mvIniMatches, Rcw, tcw, mvIniP3D, vbTriangulated))
{
// 步骤6:删除那些无法进行三角化的匹配点
for(size_t i=0, iend=mvIniMatches.size(); i<iend;i++)
{
if(mvIniMatches[i]>=0 && !vbTriangulated[i])
{
mvIniMatches[i]=-1;
nmatches--;
}
}
// Set Frame Poses
// 将初始化的第一帧作为世界坐标系,因此第一帧变换矩阵为单位矩阵
mInitialFrame.SetPose(cv::Mat::eye(4,4,CV_32F));
// 由Rcw和tcw构造Tcw,并赋值给mTcw,mTcw为世界坐标系到该帧的变换矩阵
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);
// 步骤6:将三角化得到的3D点包装成MapPoints
// Initialize函数会得到mvIniP3D,
// mvIniP3D是cv::Point3f类型的一个容器,是个存放3D点的临时变量,
// CreateInitialMapMonocular将3D点包装成MapPoint类型存入KeyFrame和Map中
CreateInitialMapMonocular();
}
}
}
注意要连续两帧的特征点大于100才能开始初始化流程,同时他们之间的匹配点数量也要大于100,否则重新开始初始化
得到匹配点之后,则可以通过匹配点计算两帧的相对位姿了,需要注意的是,这里是并行计算H和F两种模型,再通过评估来确定是哪一种模型,vins中只是用匹配点计算了E矩阵
得到初始两帧坐标系后删除不能三角化的匹配点,并将第一帧作为世界坐标系,创建初始的单目地图
/**
* @brief CreateInitialMapMonocular
*
* 为单目摄像头三角化生成MapPoints
*/
void Tracking::CreateInitialMapMonocular()
{
// Create KeyFrames
KeyFrame* pKFini = new KeyFrame(mInitialFrame,mpMap,mpKeyFrameDB);
KeyFrame* pKFcur = new KeyFrame(mCurrentFrame,mpMap,mpKeyFrameDB);
// 步骤1:将初始关键帧的描述子转为BoW
pKFini->ComputeBoW();
// 步骤2:将当前关键帧的描述子转为BoW
pKFcur->ComputeBoW();
// Insert KFs in the map
// 步骤3:将关键帧插入到地图
// 凡是关键帧,都要插入地图
mpMap->AddKeyFrame(pKFini);
mpMap->AddKeyFrame(pKFcur);
// Create MapPoints and asscoiate to keyframes
// 步骤4:将3D点包装成MapPoints
for(size_t i=0; i<mvIniMatches.size();i++)
{
if(mvIniMatches[i]<0)
continue;
//Create MapPoint.
cv::Mat worldPos(mvIniP3D[i]);
// 步骤4.1:用3D点构造MapPoint
MapPoint* pMP = new MapPoint(worldPos,pKFcur,mpMap);
// 步骤4.2:为该MapPoint添加属性:
// a.观测到该MapPoint的关键帧
// b.该MapPoint的描述子
// c.该MapPoint的平均观测方向和深度范围
// 步骤4.3:表示该KeyFrame的哪个特征点可以观测到哪个3D点
pKFini->AddMapPoint(pMP,i);
pKFcur->AddMapPoint(pMP,mvIniMatches[i]);
// a.表示该MapPoint可以被哪个KeyFrame的哪个特征点观测到
pMP->AddObservation(pKFini,i);
pMP->AddObservation(pKFcur,mvIniMatches[i]);
// b.从众多观测到该MapPoint的特征点中挑选区分读最高的描述子
pMP->ComputeDistinctiveDescriptors();
// c.更新该MapPoint平均观测方向以及观测距离的范围
pMP->UpdateNormalAndDepth();
//Fill Current Frame structure
mCurrentFrame.mvpMapPoints[mvIniMatches[i]] = pMP;
mCurrentFrame.mvbOutlier[mvIniMatches[i]] = false;
//Add to Map
// 步骤4.4:在地图中添加该MapPoint
mpMap->AddMapPoint(pMP);
}
// Update Connections
// 步骤5:更新关键帧间的连接关系
// 在3D点和关键帧之间建立边,每个边有一个权重mVelocity,边的权重是该关键帧与当前帧公共3D点的个数
pKFini->UpdateConnections();
pKFcur->UpdateConnections();
// Bundle Adjustment
cout << "New Map created with " << mpMap->MapPointsInMap() << " points" << endl;
// 步骤5:BA优化
Optimizer::GlobalBundleAdjustemnt(mpMap,20);
// Set median depth to 1
// 步骤6:!!!将MapPoints的中值深度归一化到1,并归一化两帧之间变换
// 评估关键帧场景深度,q=2表示中值
float medianDepth = pKFini->ComputeSceneMedianDepth(2);
float invMedianDepth = 1.0f/medianDepth;
if(medianDepth<0 || pKFcur->TrackedMapPoints(1)<100)
{
cout << "Wrong initialization, reseting..." << endl;
Reset();
return;
}
// Scale initial baseline
cv::Mat Tc2w = pKFcur->GetPose();
// x/z y/z 将z归一化到1
Tc2w.col(3).rowRange(0,3) = Tc2w.col(3).rowRange(0,3)*invMedianDepth;
pKFcur->SetPose(Tc2w);
// Scale points
// 把3D点的尺度也归一化到1
vector<MapPoint*> vpAllMapPoints = pKFini->GetMapPointMatches();
for(size_t iMP=0; iMP<vpAllMapPoints.size(); iMP++)
{
if(vpAllMapPoints[iMP])
{
MapPoint* pMP = vpAllMapPoints[iMP];
pMP->SetWorldPos(pMP->GetWorldPos()*invMedianDepth);
}
}
// 这部分和SteroInitialization()相似
mpLocalMapper->InsertKeyFrame(pKFini);
mpLocalMapper->InsertKeyFrame(pKFcur);
mCurrentFrame.SetPose(pKFcur->GetPose());
mnLastKeyFrameId=mCurrentFrame.mnId;
mpLastKeyFrame = pKFcur;
mvpLocalKeyFrames.push_back(pKFcur);
mvpLocalKeyFrames.push_back(pKFini);
mvpLocalMapPoints=mpMap->GetAllMapPoints();
mpReferenceKF = pKFcur;
mCurrentFrame.mpReferenceKF = pKFcur;
mLastFrame = Frame(mCurrentFrame);
mpMap->SetReferenceMapPoints(mvpLocalMapPoints);
mpMapDrawer->SetCurrentCameraPose(pKFcur->GetPose());
mpMap->mvpKeyFrameOrigins.push_back(pKFini);
mState=OK;// 初始化成功,至此,初始化过程完成
}
在这里将关键帧特征点的描述子转为BoW,将3D点添加入地图中,并将视图和点的观测添加进去,同时更新当前特诊点的最佳描述子
在构建好初始地图后,对地图进行一个ba优化,优化后将MapPoints的中值深度归一化为1,并将两帧间的变换和其他3D点深度归一化,将视图和点都加入到LocalMap中,完成归一化过程
总的来说,vins视觉初始化和orbslam2初始的区别主要在于vins是对整个窗口进行初始化,除了初始化的前两帧利用匹配点计算相对位姿,窗口内的其他帧都是利用PnP方法计算的位姿,而orbslam2的初始化只涉及到头两帧