按照规矩,先讲一下ORBSLAM3中的初始化大致流程
根据ORB-SLAM3的论文介绍, IMU的初始化方法是基于下面3点:
1) 纯视觉SLAM可以提供很好的位姿估计,所以可以用纯视觉的结果来改善IMU的估计;
2) 显示地将尺度表示为一个优化的变量, 尺度会更快地收敛;
3)在IMU初始化阶段,忽略传感器的不确定度将会产生更多不可预知错误。
整个初始化过程分为:
1.Vision-only MAP Estimation;
2.Inertial-only MAP Estimation;
3.Visual-Inertial MAP Estimation。
4.To improve the initial estimation, visual-inertial BA is performed 5 and 15
seconds after initialization。
与vins-mono不同的是,vins-mono大部分都是用LDLT直接法进行求解,同时还有在线标定外参,具体请参考VINS-MONO初始化
纯视觉初始化和以前的ORBSLAM2是一样的,是利用基础矩阵和单应矩阵进行初始化。然后再采用之前纯视觉估计出来的相机位姿单独的进行纯IMU初始化,主要估计imu的bias和单目尺度,以及坐标系的对齐,具体的步骤如下图:
由图可知,imu初始化实在LocalMapping主函数中,红色框框的是纯IMU初始化,绿色的是纯视觉初始化,蓝色的VIO初始化,代码具体如下:
void LocalMapping::Run()
{
// 标记状态,表示当前run函数正在运行,尚未结束
mbFinished = false;
// 主循环
while(1)
{
// Tracking will see that Local Mapping is busy
// Step 1 告诉Tracking,LocalMapping正处于繁忙状态,请不要给我发送关键帧打扰我
// LocalMapping线程处理的关键帧都是Tracking线程发过来的
SetAcceptKeyFrames(false);
// Check if there are keyframes in the queue
// 等待处理的关键帧列表不为空 并且imu正常
if(CheckNewKeyFrames() && !mbBadImu)
{
// std::cout << "LM" << std::endl;
std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
// BoW conversion and insertion in Map
// Step 2 处理列表中的关键帧,包括计算BoW、更新观测、描述子、共视图,插入到地图等
ProcessNewKeyFrame();
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
// Check recent MapPoints
// Step 3 根据地图点的观测情况剔除质量不好的地图点
MapPointCulling();
std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
// Triangulate new MapPoints
// Step 4 当前关键帧与相邻关键帧通过三角化产生新的地图点,使得跟踪更稳
CreateNewMapPoints();
std::chrono::steady_clock::time_point t3 = std::chrono::steady_clock::now();
mbAbortBA = false;
// 已经处理完队列中的最后的一个关键帧
if(!CheckNewKeyFrames())
{
// Find more matches in neighbor keyframes and fuse point duplications
// Step 5 检查并融合当前关键帧与相邻关键帧帧(两级相邻)中重复的地图点
SearchInNeighbors();
}
std::chrono::steady_clock::time_point t4 = std::chrono::steady_clock::now();
std::chrono::steady_clock::time_point t5 = t4, t6 = t4;
// mbAbortBA = false;
int num_FixedKF_BA = 0;
// 已经处理完队列中的最后的一个关键帧,并且闭环检测没有请求停止LocalMapping
if(!CheckNewKeyFrames() && !stopRequested())
{
// 地图中关键帧数目大于2个
if(mpAtlas->KeyFramesInMap()>2)
{
// Step 6.1 处于IMU模式并且当前关键帧所在的地图已经完成IMU初始化。这里是因为图像出问题了,采用imu数据?
if(mbInertial && mpCurrentKeyFrame->GetMap()->isImuInitialized())
{
// 计算上一帧到当前帧相机光心的距离 + 上上帧到上一帧相机光心的距离
float dist = cv::norm(mpCurrentKeyFrame->mPrevKF->GetCameraCenter() - mpCurrentKeyFrame->GetCameraCenter()) +
cv::norm(mpCurrentKeyFrame->mPrevKF->mPrevKF->GetCameraCenter() - mpCurrentKeyFrame->mPrevKF->GetCameraCenter());
// 如果距离大于5厘米,记录当前KF和上一KF时间戳的差,累加到mTinit
if(dist>0.05)
mTinit += mpCurrentKeyFrame->mTimeStamp - mpCurrentKeyFrame->mPrevKF->mTimeStamp;
// 当前关键帧所在的地图已经完成IMU BA2
if(!mpCurrentKeyFrame->GetMap()->GetIniertialBA2())
{
// 如果累计时间差小于10s 并且 距离小于2厘米,认为运动幅度太小,不足以初始化IMU,将mbBadImu设置为true
if((mTinit<10.f) && (dist<0.02))
{
cout << "Not enough motion for initializing. Reseting..." << endl;
unique_lock lock(mMutexReset);
mbResetRequestedActiveMap = true;
mpMapToReset = mpCurrentKeyFrame->GetMap();
mbBadImu = true;
}
}
// 条件---------1.1、跟踪成功的内点数目大于75-----1.2、并且是单目--或--2.1、跟踪成功的内点数目大于100-----2.2、并且不是单目
bool bLarge = ((mpTracker->GetMatchesInliers()>75)&&mbMonocular)||((mpTracker->GetMatchesInliers()>100)&&!mbMonocular);
// BA优化局部地图IMU
Optimizer::LocalInertialBA(mpCurrentKeyFrame, &mbAbortBA, mpCurrentKeyFrame->GetMap(), bLarge, !mpCurrentKeyFrame->GetMap()->GetIniertialBA2());
}
else
{
// Step 6.2 不是IMU模式或者当前关键帧所在的地图还未完成IMU初始化
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
// 局部地图BA,不包括IMU数据
// 注意这里的第二个参数是按地址传递的,当这里的 mbAbortBA 状态发生变化时,能够及时执行/停止BA
Optimizer::LocalBundleAdjustment(mpCurrentKeyFrame,&mbAbortBA, mpCurrentKeyFrame->GetMap(),num_FixedKF_BA);
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
}
}
t5 = std::chrono::steady_clock::now();
// Initialize IMU here
// Step 7 当前关键帧所在地图的IMU初始化
if(!mpCurrentKeyFrame->GetMap()->isImuInitialized() && mbInertial)
{
if (mbMonocular)
InitializeIMU(1e2, 1e10, true);
else
InitializeIMU(1e2, 1e5, true);
}
// Check redundant local Keyframes
// Step 8 检测并剔除当前帧相邻的关键帧中冗余的关键帧
// 冗余的判定:该关键帧的90%的地图点可以被其它关键帧观测到
KeyFrameCulling();
t6 = std::chrono::steady_clock::now();
// Step 9 如果累计时间差小于100s 并且 是IMU模式,进行VIBA
if ((mTinit<100.0f) && mbInertial)
{
// Step 9.1 根据条件判断是否进行VIBA1
// 条件:1、当前关键帧所在的地图还未完成IMU初始化---并且--------2、正常跟踪状态----------
if(mpCurrentKeyFrame->GetMap()->isImuInitialized() && mpTracker->mState==Tracking::OK) // Enter here everytime local-mapping is called
{
// 当前关键帧所在的地图还未完成VIBA 1
if(!mpCurrentKeyFrame->GetMap()->GetIniertialBA1()){
// 如果累计时间差大于5s,开始VIBA 1
if (mTinit>5.0f)
{
cout << "start VIBA 1" << endl;
mpCurrentKeyFrame->GetMap()->SetIniertialBA1();
if (mbMonocular)
InitializeIMU(1.f, 1e5, true); // 1.f, 1e5
else
InitializeIMU(1.f, 1e5, true); // 1.f, 1e5
cout << "end VIBA 1" << endl;
}
}
//else if (mbNotBA2){
// Step 9.2 根据条件判断是否进行VIBA2
// 当前关键帧所在的地图还未完成VIBA 2
else if(!mpCurrentKeyFrame->GetMap()->GetIniertialBA2()){
// 如果累计时间差大于15s,开始VIBA 2
if (mTinit>15.0f){ // 15.0f
cout << "start VIBA 2" << endl;
mpCurrentKeyFrame->GetMap()->SetIniertialBA2();
if (mbMonocular)
InitializeIMU(0.f, 0.f, true); // 0.f, 0.f
else
InitializeIMU(0.f, 0.f, true);
cout << "end VIBA 2" << endl;
}
}
// scale refinement
// Step 9.3 尺度优化
if (((mpAtlas->KeyFramesInMap())<=100) &&
((mTinit>25.0f && mTinit<25.5f)||
(mTinit>35.0f && mTinit<35.5f)||
(mTinit>45.0f && mTinit<45.5f)||
(mTinit>55.0f && mTinit<55.5f)||
(mTinit>65.0f && mTinit<65.5f)||
(mTinit>75.0f && mTinit<75.5f))){
cout << "start scale ref" << endl;
//也就是说这里会无限制的在100s内优化尺度和重力
if (mbMonocular)
ScaleRefinement();
cout << "end scale ref" << endl;
}
}
}
}
std::chrono::steady_clock::time_point t7 = std::chrono::steady_clock::now();
// Step 10 将当前帧加入到闭环检测队列中
mpLoopCloser->InsertKeyFrame(mpCurrentKeyFrame);
std::chrono::steady_clock::time_point t8 = std::chrono::steady_clock::now();
double t_procKF = std::chrono::duration_cast >(t1 - t0).count();
double t_MPcull = std::chrono::duration_cast >(t2 - t1).count();
double t_CheckMP = std::chrono::duration_cast >(t3 - t2).count();
double t_searchNeigh = std::chrono::duration_cast >(t4 - t3).count();
double t_Opt = std::chrono::duration_cast >(t5 - t4).count();
double t_KF_cull = std::chrono::duration_cast >(t6 - t5).count();
double t_Insert = std::chrono::duration_cast >(t8 - t7).count();
}
else if(Stop() && !mbBadImu) // 当要终止当前线程的时候
{
// Safe area to stop
while(isStopped() && !CheckFinish())
{
// cout << "LM: usleep if is stopped" << endl;
// 如果还没有结束利索,那么等等它
usleep(3000);
}
// 然后确定终止了就跳出这个线程的主循环
if(CheckFinish())
break;
}
// 查看是否有复位线程的请求
ResetIfRequested();
// Tracking will see that Local Mapping is busy
SetAcceptKeyFrames(true);
// 如果当前线程已经结束了就跳出主循环
if(CheckFinish())
break;
// cout << "LM: normal usleep" << endl;
usleep(3000);
}
// 设置线程已经终止
SetFinish();
}
其中纯IMU初始化的接口在这里使用
if(!mpCurrentKeyFrame->GetMap()->isImuInitialized() && mbInertial)
{
if (mbMonocular)
InitializeIMU(1e2, 1e10, true);
else
InitializeIMU(1e2, 1e5, true);
}
1e2表示bias的权重,属于经验值吧
void LocalMapping::InitializeIMU(float priorG, float priorA, bool bFIBA)
{
if (mbResetRequested)
return;
float minTime;
int nMinKF;
if (mbMonocular)
{
minTime = 2.0;
nMinKF = 10;
}
else
{
minTime = 1.0;
nMinKF = 10;
}
if(mpAtlas->KeyFramesInMap() lpKF;
KeyFrame* pKF = mpCurrentKeyFrame;
//找到当前帧的最前一帧
while(pKF->mPrevKF)
{
lpKF.push_front(pKF);
pKF = pKF->mPrevKF;
}
// lpKF放置的是最前一帧...前一帧,当前帧
lpKF.push_front(pKF);
vector vpKF(lpKF.begin(),lpKF.end());
// 如果小于关键帧10,则返回
if(vpKF.size()mTimeStamp;
// 如果两帧之间的时间差相对较小,则返回
if(mpCurrentKeyFrame->mTimeStamp-mFirstTsGetMap()->isImuInitialized())
{
cv::Mat cvRwg;//世界坐标系和重力方向的旋转量
cv::Mat dirG = cv::Mat::zeros(3,1,CV_32F);//dirG重力
for(vector::iterator itKF = vpKF.begin(); itKF!=vpKF.end(); itKF++)
{
if (!(*itKF)->mpImuPreintegrated)
continue;
if (!(*itKF)->mPrevKF)
continue;
// GetImuRotation拿到的是imu到世界的旋转量相当于Rwi
// 通过视觉估计的结果计算对应关键帧时刻IMU的速度
// 假设初始时刻IMU系统的加速度较小, IMU测量主要就是重力,所以这里采用速度去估计重力?
//感觉可以采用加速度均值去估计重力方向,不过这里应该没关系,因为这个是初始值,后面会优化的
dirG -= (*itKF)->mPrevKF->GetImuRotation()*(*itKF)->mpImuPreintegrated->GetUpdatedDeltaVelocity();
// 计算两帧之间的平均速度
cv::Mat _vel = ((*itKF)->GetImuPosition() - (*itKF)->mPrevKF->GetImuPosition())/(*itKF)->mpImuPreintegrated->dT;
(*itKF)->SetVelocity(_vel);
(*itKF)->mPrevKF->SetVelocity(_vel);
}
dirG = dirG/cv::norm(dirG);
//vhat = (gI x gw) / |gI x gw|
//惯性系下实际重力的方向向量坐标gI
cv::Mat gI = (cv::Mat_(3,1) << 0.0f, 0.0f, -1.0f);
cv::Mat v = gI.cross(dirG);
const float nv = cv::norm(v);
const float cosg = gI.dot(dirG);
const float ang = acos(cosg);
cv::Mat vzg = v*ang/nv;
//mRwg惯性系到世界系的旋转矩阵
cvRwg = IMU::ExpSO3(vzg);
mRwg = Converter::toMatrix3d(cvRwg);
mTinit = mpCurrentKeyFrame->mTimeStamp-mFirstTs;
}
else
{
mRwg = Eigen::Matrix3d::Identity();
mbg = Converter::toVector3d(mpCurrentKeyFrame->GetGyroBias());
mba = Converter::toVector3d(mpCurrentKeyFrame->GetAccBias());
}
mScale=1.0;
mInitTime = mpTracker->mLastFrame.mTimeStamp-vpKF.front()->mTimeStamp;
//进行初始化, 以IMU位姿为基准, 估计IMU bias, 尺度和惯性系与世界系(首帧IMU系)之间的旋转矩阵
// 这里对应论文中的Inertial-only MAP Estimation,InertialOptimization初始化过程中不涉及点, 只有imu预积分的边,各个关键帧时刻对应的IMU位姿固定不变, 但速度会被优化
std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
Optimizer::InertialOptimization(mpAtlas->GetCurrentMap(), mRwg, mScale, mbg, mba, mbMonocular, infoInertial, false, false, priorG, priorA);
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
/*cout << "scale after inertial-only optimization: " << mScale << endl;
cout << "bg after inertial-only optimization: " << mbg << endl;
cout << "ba after inertial-only optimization: " << mba << endl;*/
if (mScale<1e-1)
{
cout << "scale too small" << endl;
bInitializing=false;
return;
}
// Before this line we are not changing the map
unique_lock lock(mpAtlas->GetCurrentMap()->mMutexMapUpdate);
std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
if ((fabs(mScale-1.f)>0.00001)||!mbMonocular)
{
//恢复尺度,对齐世界坐标系
mpAtlas->GetCurrentMap()->ApplyScaledRotation(Converter::toCvMat(mRwg).t(),mScale,true);
mpTracker->UpdateFrameIMU(mScale,vpKF[0]->GetImuBias(),mpCurrentKeyFrame);
}
std::chrono::steady_clock::time_point t3 = std::chrono::steady_clock::now();
// Check if initialization OK
if (!mpAtlas->isImuInitialized())
for(int i=0;ibImu = true;
}
/*cout << "Before GIBA: " << endl;
cout << "ba: " << mpCurrentKeyFrame->GetAccBias() << endl;
cout << "bg: " << mpCurrentKeyFrame->GetGyroBias() << endl;*/
std::chrono::steady_clock::time_point t4 = std::chrono::steady_clock::now();
if (bFIBA)
{
// BA优化
if (priorA!=0.f)
Optimizer::FullInertialBA(mpAtlas->GetCurrentMap(), 100, false, 0, NULL, true, priorG, priorA);
else
Optimizer::FullInertialBA(mpAtlas->GetCurrentMap(), 100, false, 0, NULL, false);
}
std::chrono::steady_clock::time_point t5 = std::chrono::steady_clock::now();
// If initialization is OK
//更新tracker端维护的local map
mpTracker->UpdateFrameIMU(1.0,vpKF[0]->GetImuBias(),mpCurrentKeyFrame);
if (!mpAtlas->isImuInitialized())
{
cout << "IMU in Map " << mpAtlas->GetCurrentMap()->GetId() << " is initialized" << endl;
mpAtlas->SetImuInitialized();
mpTracker->t0IMU = mpTracker->mCurrentFrame.mTimeStamp;
mpCurrentKeyFrame->bImu = true;
}
mbNewInit=true;
mnKFs=vpKF.size();
mIdxInit++;
for(list::iterator lit = mlNewKeyFrames.begin(), lend=mlNewKeyFrames.end(); lit!=lend; lit++)
{
(*lit)->SetBadFlag();
delete *lit;
}
mlNewKeyFrames.clear();
mpTracker->mState=Tracking::OK;
bInitializing = false;
/*cout << "After GIBA: " << endl;
cout << "ba: " << mpCurrentKeyFrame->GetAccBias() << endl;
cout << "bg: " << mpCurrentKeyFrame->GetGyroBias() << endl;
double t_inertial_only = std::chrono::duration_cast >(t1 - t0).count();
double t_update = std::chrono::duration_cast >(t3 - t2).count();
double t_viba = std::chrono::duration_cast >(t5 - t4).count();
cout << t_inertial_only << ", " << t_update << ", " << t_viba << endl;*/
mpCurrentKeyFrame->GetMap()->IncreaseChangeIndex();
return;
}
函数中我们可以得出,重力对齐在InertialOptimization之后进行一次,FullInertialBA分两种情况, 第一种用于初始化, 这种在IMU初始化的时候使用,IMU bias处理方式与InertialOptimization一样, 所有时刻bias都一样, 只有一个vertex, bias优化过程中约束到初始值;另一种用于IMU初始化之后, 各个预积分内的bias不一致,优化时约束前后两个相邻预积分bias之间的差值。
void Optimizer::InertialOptimization(Map *pMap, Eigen::Matrix3d &Rwg, double &scale, Eigen::Vector3d &bg, Eigen::Vector3d &ba, bool bMono, Eigen::MatrixXd &covInertial, bool bFixedVel, bool bGauss, float priorG, float priorA)
{
Verbose::PrintMess("inertial optimization", Verbose::VERBOSITY_NORMAL);
int its = 200; // Check number of iterations
long unsigned int maxKFid = pMap->GetMaxKFid();
const vector vpKFs = pMap->GetAllKeyFrames();
// Setup optimizer
g2o::SparseOptimizer optimizer;
g2o::BlockSolverX::LinearSolverType * linearSolver;
linearSolver = new g2o::LinearSolverEigen();
g2o::BlockSolverX * solver_ptr = new g2o::BlockSolverX(linearSolver);
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg(solver_ptr);
if (priorG!=0.f)
solver->setUserLambdaInit(1e3);
optimizer.setAlgorithm(solver);
// Set KeyFrame vertices (fixed poses and optimizable velocities)
for(size_t i=0; imnId>maxKFid)
continue;
VertexPose * VP = new VertexPose(pKFi);
VP->setId(pKFi->mnId);
VP->setFixed(true);
optimizer.addVertex(VP);
VertexVelocity* VV = new VertexVelocity(pKFi);
VV->setId(maxKFid+(pKFi->mnId)+1);
if (bFixedVel)//初始化的时候bFixedVel为false
VV->setFixed(true);
else
VV->setFixed(false);
optimizer.addVertex(VV);
}
// Biases
VertexGyroBias* VG = new VertexGyroBias(vpKFs.front());
VG->setId(maxKFid*2+2);
if (bFixedVel)
VG->setFixed(true);
else
VG->setFixed(false);
optimizer.addVertex(VG);
VertexAccBias* VA = new VertexAccBias(vpKFs.front());
VA->setId(maxKFid*2+3);
if (bFixedVel)
VA->setFixed(true);
else
VA->setFixed(false);
optimizer.addVertex(VA);
// prior acc bias
// acc 和 gyro bias 在优化过程中约束在0值附近, 且初始化所有时刻的bias认为是相同的,所有用同一个vertex
EdgePriorAcc* epa = new EdgePriorAcc(cv::Mat::zeros(3,1,CV_32F));
epa->setVertex(0,dynamic_cast(VA));
double infoPriorA = priorA;
epa->setInformation(infoPriorA*Eigen::Matrix3d::Identity());
optimizer.addEdge(epa);
EdgePriorGyro* epg = new EdgePriorGyro(cv::Mat::zeros(3,1,CV_32F));
epg->setVertex(0,dynamic_cast(VG));
double infoPriorG = priorG;
epg->setInformation(infoPriorG*Eigen::Matrix3d::Identity());
optimizer.addEdge(epg);
// Gravity and scale
VertexGDir* VGDir = new VertexGDir(Rwg);
VGDir->setId(maxKFid*2+4);
VGDir->setFixed(false);
optimizer.addVertex(VGDir);
VertexScale* VS = new VertexScale(scale);
VS->setId(maxKFid*2+5);
VS->setFixed(!bMono); // Fixed for stereo case
optimizer.addVertex(VS);
// Graph edges
// IMU links with gravity and scale
vector vpei;
vpei.reserve(vpKFs.size());
vector > vppUsedKF;
vppUsedKF.reserve(vpKFs.size());
std::cout << "build optimization graph" << std::endl;
for(size_t i=0;imPrevKF && pKFi->mnId<=maxKFid)
{
if(pKFi->isBad() || pKFi->mPrevKF->mnId>maxKFid)
continue;
if(!pKFi->mpImuPreintegrated)
std::cout << "Not preintegrated measurement" << std::endl;
pKFi->mpImuPreintegrated->SetNewBias(pKFi->mPrevKF->GetImuBias());
g2o::HyperGraph::Vertex* VP1 = optimizer.vertex(pKFi->mPrevKF->mnId);
g2o::HyperGraph::Vertex* VV1 = optimizer.vertex(maxKFid+(pKFi->mPrevKF->mnId)+1);
g2o::HyperGraph::Vertex* VP2 = optimizer.vertex(pKFi->mnId);
g2o::HyperGraph::Vertex* VV2 = optimizer.vertex(maxKFid+(pKFi->mnId)+1);
g2o::HyperGraph::Vertex* VG = optimizer.vertex(maxKFid*2+2);
g2o::HyperGraph::Vertex* VA = optimizer.vertex(maxKFid*2+3);
g2o::HyperGraph::Vertex* VGDir = optimizer.vertex(maxKFid*2+4);
g2o::HyperGraph::Vertex* VS = optimizer.vertex(maxKFid*2+5);
if(!VP1 || !VV1 || !VG || !VA || !VP2 || !VV2 || !VGDir || !VS)
{
cout << "Error" << VP1 << ", "<< VV1 << ", "<< VG << ", "<< VA << ", " << VP2 << ", " << VV2 << ", "<< VGDir << ", "<< VS <mpImuPreintegrated);
ei->setVertex(0,dynamic_cast(VP1));
ei->setVertex(1,dynamic_cast(VV1));
ei->setVertex(2,dynamic_cast(VG));
ei->setVertex(3,dynamic_cast(VA));
ei->setVertex(4,dynamic_cast(VP2));
ei->setVertex(5,dynamic_cast(VV2));
ei->setVertex(6,dynamic_cast(VGDir));
ei->setVertex(7,dynamic_cast(VS));
vpei.push_back(ei);
vppUsedKF.push_back(make_pair(pKFi->mPrevKF,pKFi));
optimizer.addEdge(ei);
}
}
// Compute error for different scales
std::set setEdges = optimizer.edges();
std::cout << "start optimization" << std::endl;
optimizer.setVerbose(false);
optimizer.initializeOptimization();
optimizer.optimize(its);
std::cout << "end optimization" << std::endl;
scale = VS->estimate();
// Recover optimized data
// Biases
VG = static_cast(optimizer.vertex(maxKFid*2+2));
VA = static_cast(optimizer.vertex(maxKFid*2+3));
Vector6d vb;
vb << VG->estimate(), VA->estimate();
bg << VG->estimate();
ba << VA->estimate();
scale = VS->estimate();
IMU::Bias b (vb[3],vb[4],vb[5],vb[0],vb[1],vb[2]);
Rwg = VGDir->estimate().Rwg;
cv::Mat cvbg = Converter::toCvMat(bg);
//Keyframes velocities and biases
std::cout << "update Keyframes velocities and biases" << std::endl;
const int N = vpKFs.size();
for(size_t i=0; imnId>maxKFid)
continue;
VertexVelocity* VV = static_cast(optimizer.vertex(maxKFid+(pKFi->mnId)+1));
Eigen::Vector3d Vw = VV->estimate(); // Velocity is scaled after
pKFi->SetVelocity(Converter::toCvMat(Vw));
if (cv::norm(pKFi->GetGyroBias()-cvbg)>0.01)
{
pKFi->SetNewBias(b);
if (pKFi->mpImuPreintegrated)
pKFi->mpImuPreintegrated->Reintegrate();
}
else
pKFi->SetNewBias(b);
}
}
InertialOptimization初始化过程中不涉及点, 只有imu预积分的边,各个关键帧时刻对应的IMU位姿固定不变, 但速度会被优化。而在论文中也说明了,初始化成功之后,还在5s和15s再次进行初始化,如果是单目的话,还会优化尺度和重力方向。得到最佳效果。这个在主函数中有解释。
我们来看看后面再次优化尺度和重力方向的函数
void LocalMapping::ScaleRefinement()
{
// Minimum number of keyframes to compute a solution
// Minimum time (seconds) between first and last keyframe to compute a solution. Make the difference between monocular and stereo
// unique_lock lock0(mMutexImuInit);
if (mbResetRequested)
return;
// Retrieve all keyframes in temporal order
list lpKF;
KeyFrame* pKF = mpCurrentKeyFrame;
while(pKF->mPrevKF)
{
lpKF.push_front(pKF);
pKF = pKF->mPrevKF;
}
lpKF.push_front(pKF);
vector vpKF(lpKF.begin(),lpKF.end());
while(CheckNewKeyFrames())
{
ProcessNewKeyFrame();
vpKF.push_back(mpCurrentKeyFrame);
lpKF.push_back(mpCurrentKeyFrame);
}
const int N = vpKF.size();
mRwg = Eigen::Matrix3d::Identity();
mScale=1.0;
std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
Optimizer::InertialOptimization(mpAtlas->GetCurrentMap(), mRwg, mScale);
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
if (mScale<1e-1) // 1e-1
{
cout << "scale too small" << endl;
bInitializing=false;
return;
}
// Before this line we are not changing the map
unique_lock lock(mpAtlas->GetCurrentMap()->mMutexMapUpdate);
std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
if ((fabs(mScale-1.f)>0.00001)||!mbMonocular)
{
mpAtlas->GetCurrentMap()->ApplyScaledRotation(Converter::toCvMat(mRwg).t(),mScale,true);
mpTracker->UpdateFrameIMU(mScale,mpCurrentKeyFrame->GetImuBias(),mpCurrentKeyFrame);
}
std::chrono::steady_clock::time_point t3 = std::chrono::steady_clock::now();
for(list::iterator lit = mlNewKeyFrames.begin(), lend=mlNewKeyFrames.end(); lit!=lend; lit++)
{
(*lit)->SetBadFlag();
delete *lit;
}
mlNewKeyFrames.clear();
double t_inertial_only = std::chrono::duration_cast >(t1 - t0).count();
// To perform pose-inertial opt w.r.t. last keyframe
mpCurrentKeyFrame->GetMap()->IncreaseChangeIndex();
return;
}
以上就是ORBSLAM3的VIO初始化。