/**
* This file is part of ORB-SLAM3
*
* Copyright (C) 2017-2020 Carlos Campos, Richard Elvira, Juan J. Gómez Rodríguez, José M.M. Montiel and Juan D. Tardós, University of Zaragoza.
* Copyright (C) 2014-2016 Raúl Mur-Artal, José M.M. Montiel and Juan D. Tardós, University of Zaragoza.
*
* ORB-SLAM3 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ORB-SLAM3 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with ORB-SLAM3.
* If not, see .
*/
#include "Optimizer.h"
#include
#include
#include
#include
#include
#include "Thirdparty/g2o/g2o/core/sparse_block_matrix.h"
#include "Thirdparty/g2o/g2o/core/block_solver.h"
#include "Thirdparty/g2o/g2o/core/optimization_algorithm_levenberg.h"
#include "Thirdparty/g2o/g2o/core/optimization_algorithm_gauss_newton.h"
#include "Thirdparty/g2o/g2o/solvers/linear_solver_eigen.h"
#include "Thirdparty/g2o/g2o/types/types_six_dof_expmap.h"
#include "Thirdparty/g2o/g2o/core/robust_kernel_impl.h"
#include "Thirdparty/g2o/g2o/solvers/linear_solver_dense.h"
#include "G2oTypes.h"
#include "Converter.h"
#include
#include "OptimizableTypes.h"
namespace ORB_SLAM3
{
bool sortByVal(const pair &a, const pair &b)
{
return (a.second < b.second);
}
/**
* @brief 全局BA: pMap中所有的MapPoints和关键帧做bundle adjustment优化
* 这个全局BA优化在本程序中有两个地方使用:
* 1、单目初始化:CreateInitialMapMonocular函数
* 2、闭环优化:RunGlobalBundleAdjustment函数
* @param[in] pMap 地图点
* @param[in] nIterations 迭代次数
* @param[in] pbStopFlag 外部控制BA结束标志
* @param[in] nLoopKF 形成了闭环的当前关键帧的id
* @param[in] bRobust 是否使用鲁棒核函数
*/
void Optimizer::GlobalBundleAdjustemnt(Map* pMap, int nIterations, bool* pbStopFlag, const unsigned long nLoopKF, const bool bRobust)
{
// 获取地图中的所有关键帧
vector vpKFs = pMap->GetAllKeyFrames();
// 获取地图中的所有地图点
vector vpMP = pMap->GetAllMapPoints();
// 调用GBA
BundleAdjustment(vpKFs,vpMP,nIterations,pbStopFlag, nLoopKF, bRobust);
}
/**
* @brief bundle adjustment 优化过程
* 1. Vertex: g2o::VertexSE3Expmap(),即当前帧的Tcw
* g2o::VertexSBAPointXYZ(),MapPoint的mWorldPos
* 2. Edge:
* - g2o::EdgeSE3ProjectXYZ(),BaseBinaryEdge
* + Vertex:待优化当前帧的Tcw
* + Vertex:待优化MapPoint的mWorldPos
* + measurement:MapPoint在当前帧中的二维位置(u,v)
* + InfoMatrix: invSigma2(与特征点所在的尺度有关)
*
* @param[in] vpKFs 参与BA的所有关键帧
* @param[in] vpMP 参与BA的所有地图点
* @param[in] nIterations 优化迭代次数
* @param[in] pbStopFlag 外部控制BA结束标志
* @param[in] nLoopKF 形成了闭环的当前关键帧的id
* @param[in] bRobust 是否使用核函数
*/
void Optimizer::BundleAdjustment(const vector &vpKFs, const vector &vpMP,
int nIterations, bool* pbStopFlag, const unsigned long nLoopKF, const bool bRobust)
{
// 不参与优化的地图点
vector vbNotIncludedMP;
vbNotIncludedMP.resize(vpMP.size());
Map* pMap = vpKFs[0]->GetMap();
// Step 1 初始化g2o优化器
g2o::SparseOptimizer optimizer;
g2o::BlockSolver_6_3::LinearSolverType * linearSolver;
linearSolver = new g2o::LinearSolverEigen();
g2o::BlockSolver_6_3 * solver_ptr = new g2o::BlockSolver_6_3(linearSolver);
// 使用LM算法优化
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg(solver_ptr);
optimizer.setAlgorithm(solver);
optimizer.setVerbose(false);
// 如果这个时候外部请求终止,那就结束
// 注意这句执行之后,外部再请求结束BA,就结束不了了
if(pbStopFlag)
optimizer.setForceStopFlag(pbStopFlag);
// 记录添加到优化器中的顶点的最大关键帧id
long unsigned int maxKFid = 0;
const int nExpectedSize = (vpKFs.size())*vpMP.size();
vector vpEdgesMono;
vpEdgesMono.reserve(nExpectedSize);
vector vpEdgesBody;
vpEdgesBody.reserve(nExpectedSize);
vector vpEdgeKFMono;
vpEdgeKFMono.reserve(nExpectedSize);
vector vpEdgeKFBody;
vpEdgeKFBody.reserve(nExpectedSize);
vector vpMapPointEdgeMono;
vpMapPointEdgeMono.reserve(nExpectedSize);
vector vpMapPointEdgeBody;
vpMapPointEdgeBody.reserve(nExpectedSize);
vector vpEdgesStereo;
vpEdgesStereo.reserve(nExpectedSize);
vector vpEdgeKFStereo;
vpEdgeKFStereo.reserve(nExpectedSize);
vector vpMapPointEdgeStereo;
vpMapPointEdgeStereo.reserve(nExpectedSize);
// Step 2 向优化器添加顶点
// Set KeyFrame vertices
// Step 2.1 :向优化器添加关键帧位姿顶点
// 对于当前地图中的所有关键帧
for(size_t i=0; iisBad())
continue;
// 对于每一个能用的关键帧构造SE3顶点,其实就是当前关键帧的位姿
g2o::VertexSE3Expmap * vSE3 = new g2o::VertexSE3Expmap();
vSE3->setEstimate(Converter::toSE3Quat(pKF->GetPose()));
vSE3->setId(pKF->mnId);
// 只有第0帧关键帧不优化(参考基准)
vSE3->setFixed(pKF->mnId==pMap->GetInitKFid());
// 向优化器中添加顶点,并且更新maxKFid
optimizer.addVertex(vSE3);
if(pKF->mnId>maxKFid)
maxKFid=pKF->mnId;
}
// 卡方分布 95% 以上可信度的时候的阈值
const float thHuber2D = sqrt(5.99); // 自由度为2
const float thHuber3D = sqrt(7.815); // 自由度为3
// Set MapPoint vertices
// Step 2.2:向优化器添加MapPoints顶点
// 遍历地图中的所有地图点
for(size_t i=0; iisBad())
continue;
// 创建顶点
g2o::VertexSBAPointXYZ* vPoint = new g2o::VertexSBAPointXYZ();
// 注意由于地图点的位置是使用cv::Mat数据类型表示的,这里需要转换成为Eigen::Vector3d类型
vPoint->setEstimate(Converter::toVector3d(pMP->GetWorldPos()));
// 前面记录maxKFid 是在这里使用的
const int id = pMP->mnId+maxKFid+1;
vPoint->setId(id);
// g2o在做BA的优化时必须将其所有地图点全部schur掉,否则会出错。
// 原因是使用了g2o::LinearSolver这个类型来指定linearsolver,
// 其中模板参数当中的位姿矩阵类型在程序中为相机姿态参数的维度,于是BA当中schur消元后解得线性方程组必须是只含有相机姿态变量。
// Ceres库则没有这样的限制
vPoint->setMarginalized(true);
optimizer.addVertex(vPoint);
// 边的关系,其实就是点和关键帧之间观测的关系
const map> observations = pMP->GetObservations();
// 边计数
int nEdges = 0;
//SET EDGES
// Step 3:向优化器添加投影边(是在遍历地图点、添加地图点的顶点的时候顺便添加的)
// 遍历观察到当前地图点的所有关键帧
for(map>::const_iterator mit=observations.begin(); mit!=observations.end(); mit++)
{
KeyFrame* pKF = mit->first;
// 滤出不合法的关键帧
if(pKF->isBad() || pKF->mnId>maxKFid)
continue;
if(optimizer.vertex(id) == NULL || optimizer.vertex(pKF->mnId) == NULL)
continue;
nEdges++;
const int leftIndex = get<0>(mit->second);
if(leftIndex != -1 && pKF->mvuRight[get<0>(mit->second)]<0)
{
// 如果是单目相机按照下面操作
const cv::KeyPoint &kpUn = pKF->mvKeysUn[leftIndex];
Eigen::Matrix obs;
obs << kpUn.pt.x, kpUn.pt.y;
// 创建边
ORB_SLAM3::EdgeSE3ProjectXYZ* e = new ORB_SLAM3::EdgeSE3ProjectXYZ();
// 填充数据,构造约束关系
e->setVertex(0, dynamic_cast(optimizer.vertex(id)));
e->setVertex(1, dynamic_cast(optimizer.vertex(pKF->mnId)));
e->setMeasurement(obs);
// 信息矩阵,也是协方差,表明了这个约束的观测在各个维度(x,y)上的可信程度,在我们这里对于具体的一个点,两个坐标的可信程度都是相同的,
// 其可信程度受到特征点在图像金字塔中的图层有关,图层越高,可信度越差
// 为了避免出现信息矩阵中元素为负数的情况,这里使用的是sigma^(-2)
const float &invSigma2 = pKF->mvInvLevelSigma2[kpUn.octave];
e->setInformation(Eigen::Matrix2d::Identity()*invSigma2);
// 如果要使用鲁棒核函数
if(bRobust)
{
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
// 这里的重投影误差,自由度为2,所以这里设置为卡方分布中自由度为2的阈值,如果重投影的误差大约大于1个像素的时候,就认为不太靠谱的点了,
// 核函数是为了避免其误差的平方项出现数值上过大的增长
rk->setDelta(thHuber2D);
}
// 设置相机内参
e->pCamera = pKF->mpCamera;
optimizer.addEdge(e);
vpEdgesMono.push_back(e);
vpEdgeKFMono.push_back(pKF);
vpMapPointEdgeMono.push_back(pMP);
}
else if(leftIndex != -1 && pKF->mvuRight[leftIndex] >= 0) //Stereo observation
{
// 双目或RGBD相机按照下面操作
// 双目相机的观测数据则是由三个部分组成:投影点的x坐标,投影点的y坐标,以及投影点在右目中的x坐标(默认y方向上已经对齐了)
const cv::KeyPoint &kpUn = pKF->mvKeysUn[leftIndex];
Eigen::Matrix obs;
const float kp_ur = pKF->mvuRight[get<0>(mit->second)];
obs << kpUn.pt.x, kpUn.pt.y, kp_ur;
// 对于双目输入,g2o也有专门的误差边
g2o::EdgeStereoSE3ProjectXYZ* e = new g2o::EdgeStereoSE3ProjectXYZ();
// 填充
e->setVertex(0, dynamic_cast(optimizer.vertex(id)));
e->setVertex(1, dynamic_cast(optimizer.vertex(pKF->mnId)));
e->setMeasurement(obs);
// 信息矩阵这里是相同的,考虑的是左目特征点的所在图层
const float &invSigma2 = pKF->mvInvLevelSigma2[kpUn.octave];
Eigen::Matrix3d Info = Eigen::Matrix3d::Identity()*invSigma2;
e->setInformation(Info);
// 如果要使用鲁棒核函数
if(bRobust)
{
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
// 由于现在的观测有三个值,重投影误差会有三个平方项的和组成,因此对应的卡方分布的自由度为3,所以这里设置的也是自由度为3的时候的阈值
rk->setDelta(thHuber3D);
}
// 填充相机的基本参数
e->fx = pKF->fx;
e->fy = pKF->fy;
e->cx = pKF->cx;
e->cy = pKF->cy;
e->bf = pKF->mbf;
optimizer.addEdge(e);
vpEdgesStereo.push_back(e);
vpEdgeKFStereo.push_back(pKF);
vpMapPointEdgeStereo.push_back(pMP);
}
if(pKF->mpCamera2){
int rightIndex = get<1>(mit->second);
if(rightIndex != -1 && rightIndex < pKF->mvKeysRight.size()){
rightIndex -= pKF->NLeft;
Eigen::Matrix obs;
cv::KeyPoint kp = pKF->mvKeysRight[rightIndex];
obs << kp.pt.x, kp.pt.y;
ORB_SLAM3::EdgeSE3ProjectXYZToBody *e = new ORB_SLAM3::EdgeSE3ProjectXYZToBody();
e->setVertex(0, dynamic_cast(optimizer.vertex(id)));
e->setVertex(1, dynamic_cast(optimizer.vertex(pKF->mnId)));
e->setMeasurement(obs);
const float &invSigma2 = pKF->mvInvLevelSigma2[kp.octave];
e->setInformation(Eigen::Matrix2d::Identity()*invSigma2);
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(thHuber2D);
e->mTrl = Converter::toSE3Quat(pKF->mTrl);
e->pCamera = pKF->mpCamera2;
optimizer.addEdge(e);
vpEdgesBody.push_back(e);
vpEdgeKFBody.push_back(pKF);
vpMapPointEdgeBody.push_back(pMP);
}
}
}
// 如果因为一些特殊原因,实际上并没有任何关键帧观测到当前的这个地图点,那么就删除掉这个顶点,并且这个地图点也就不参与优化
if(nEdges==0)
{
optimizer.removeVertex(vPoint);
vbNotIncludedMP[i]=true;
}
else
{
vbNotIncludedMP[i]=false;
}
}
// Optimize!
// Step 4:开始优化
optimizer.setVerbose(false);
optimizer.initializeOptimization();
optimizer.optimize(nIterations);
Verbose::PrintMess("BA: End of the optimization", Verbose::VERBOSITY_NORMAL);
// Recover optimized data
// Step 5:得到优化的结果
// Step 5.1 Keyframes
// 遍历所有的关键帧
for(size_t i=0; iisBad())
continue;
// 获取到优化结果
g2o::VertexSE3Expmap* vSE3 = static_cast(optimizer.vertex(pKF->mnId));
g2o::SE3Quat SE3quat = vSE3->estimate();
if(nLoopKF==pMap->GetOriginKF()->mnId)
{
// 原则上来讲不会出现"当前闭环关键帧是第0帧"的情况,如果这种情况出现,只能够说明是在创建初始地图点的时候调用的这个全局BA函数.
// 这个时候,地图中就只有两个关键帧,其中优化后的位姿数据可以直接写入到帧的成员变量中
pKF->SetPose(Converter::toCvMat(SE3quat));
}
else
{
// 正常的操作,先把优化后的位姿写入到帧的一个专门的成员变量mTcwGBA中备用
pKF->mTcwGBA.create(4,4,CV_32F);
Converter::toCvMat(SE3quat).copyTo(pKF->mTcwGBA);
pKF->mnBAGlobalForKF = nLoopKF;
cv::Mat mTwc = pKF->GetPoseInverse();
cv::Mat mTcGBA_c = pKF->mTcwGBA * mTwc;
cv::Vec3d vector_dist = mTcGBA_c.rowRange(0, 3).col(3);
double dist = cv::norm(vector_dist);
if(dist > 1)
{
int numMonoBadPoints = 0, numMonoOptPoints = 0;
int numStereoBadPoints = 0, numStereoOptPoints = 0;
vector vpMonoMPsOpt, vpStereoMPsOpt;
for(size_t i=0, iend=vpEdgesMono.size(); iisBad())
continue;
if(e->chi2()>5.991 || !e->isDepthPositive())
{
numMonoBadPoints++;
}
else
{
numMonoOptPoints++;
vpMonoMPsOpt.push_back(pMP);
}
}
for(size_t i=0, iend=vpEdgesStereo.size(); iisBad())
continue;
if(e->chi2()>7.815 || !e->isDepthPositive())
{
numStereoBadPoints++;
}
else
{
numStereoOptPoints++;
vpStereoMPsOpt.push_back(pMP);
}
}
}
}
}
// Step 5.2 遍历所有地图点,去除其中没有参与优化过程的地图点
for(size_t i=0; iisBad())
continue;
// 获取优化之后的地图点的位置
g2o::VertexSBAPointXYZ* vPoint = static_cast(optimizer.vertex(pMP->mnId+maxKFid+1));
if(nLoopKF==pMap->GetOriginKF()->mnId)
{
// 如果这个GBA是在创建初始地图的时候调用的话,那么地图点的位姿也可以直接写入
pMP->SetWorldPos(Converter::toCvMat(vPoint->estimate()));
pMP->UpdateNormalAndDepth();
}
else
{
// 反之,如果是正常的闭环过程调用,就先临时保存一下
pMP->mPosGBA.create(3,1,CV_32F);
Converter::toCvMat(vPoint->estimate()).copyTo(pMP->mPosGBA);
pMP->mnBAGlobalForKF = nLoopKF;
}
}
}
/**
* @brief imu初始化优化,LocalMapping::InitializeIMU中使用,地图全部做BA。也就是imu版的GlobalBundleAdjustemnt
* 误差包含三个残差与两个偏置,优化目标:mp,位姿,偏置,速度
* 更新: 关键帧位姿,速度,偏置(预积分里的与关键帧里的),mp
* @param pMap 地图
* @param its 迭代次数
* @param bFixLocal 是否固定局部,localmapping中为false
* @param nLoopId 回环id
* @param pbStopFlag 是否停止的标志
* @param bInit 提供priorG时为true,此时偏置只优化最后一帧的至0,然后所有关键帧的偏置都赋值为优化后的值
* 若为false,则建立每两帧之间的偏置边,优化使其相差为0
* @param priorG 陀螺仪偏置的信息矩阵系数,主动设置时一般bInit为true,也就是只优化最后一帧的偏置,这个数会作为计算信息矩阵时使用
* @param priorA 加速度计偏置的信息矩阵系数
* @param vSingVal 没用,估计调试用的
* @param bHess 没用,估计调试用的
*/
void Optimizer::FullInertialBA(Map *pMap, int its, const bool bFixLocal, const long unsigned int nLoopId, bool *pbStopFlag, bool bInit, float priorG, float priorA, Eigen::VectorXd *vSingVal, bool *bHess)
{
// 获取地图里所有mp与kf,以及最大kf的id
long unsigned int maxKFid = pMap->GetMaxKFid();
const vector vpKFs = pMap->GetAllKeyFrames();
const vector vpMPs = pMap->GetAllMapPoints();
// Setup optimizer
// 1. 很经典的一套设置优化器流程
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);
solver->setUserLambdaInit(1e-5);
optimizer.setAlgorithm(solver);
optimizer.setVerbose(false);
if(pbStopFlag)
optimizer.setForceStopFlag(pbStopFlag);
int nNonFixed = 0;
// 2. 设置关键帧与偏置节点
// Set KeyFrame vertices
KeyFrame* pIncKF; // vpKFs中最后一个id符合要求的关键帧
for(size_t i=0; imnId>maxKFid)
continue;
VertexPose * VP = new VertexPose(pKFi);
VP->setId(pKFi->mnId);
pIncKF=pKFi;
bool bFixed = false;
if(bFixLocal)
{
bFixed = (pKFi->mnBALocalForKF>=(maxKFid-1)) || (pKFi->mnBAFixedForKF>=(maxKFid-1));
if(!bFixed)
nNonFixed++;
VP->setFixed(bFixed); // 固定,这里可能跟回环有关
}
optimizer.addVertex(VP);
// 如果是初始化的那几个及后面的关键帧,加入速度节点
if(pKFi->bImu)
{
VertexVelocity* VV = new VertexVelocity(pKFi);
VV->setId(maxKFid+3*(pKFi->mnId)+1);
VV->setFixed(bFixed);
optimizer.addVertex(VV);
// priorA==0.f 时 bInit为false,也就是又加入了偏置节点
if (!bInit)
{
VertexGyroBias* VG = new VertexGyroBias(pKFi);
VG->setId(maxKFid+3*(pKFi->mnId)+2);
VG->setFixed(bFixed);
optimizer.addVertex(VG);
VertexAccBias* VA = new VertexAccBias(pKFi);
VA->setId(maxKFid+3*(pKFi->mnId)+3);
VA->setFixed(bFixed);
optimizer.addVertex(VA);
}
}
}
// priorA!=0.f 时 bInit为true,加入了偏置节点
if (bInit)
{
VertexGyroBias* VG = new VertexGyroBias(pIncKF);
VG->setId(4*maxKFid+2);
VG->setFixed(false);
optimizer.addVertex(VG);
VertexAccBias* VA = new VertexAccBias(pIncKF);
VA->setId(4*maxKFid+3);
VA->setFixed(false);
optimizer.addVertex(VA);
}
// TODO 暂时不理解,看到回环后再回看这里
if(bFixLocal)
{
if(nNonFixed<3)
return;
}
// 3. 添加关于imu的边
// IMU links
for(size_t i=0;imPrevKF)
{
Verbose::PrintMess("NOT INERTIAL LINK TO PREVIOUS FRAME!", Verbose::VERBOSITY_NORMAL);
continue;
}
if(pKFi->mPrevKF && pKFi->mnId<=maxKFid)
{
if(pKFi->isBad() || pKFi->mPrevKF->mnId>maxKFid)
continue;
// 这两个都必须为初始化后的关键帧
if(pKFi->bImu && pKFi->mPrevKF->bImu)
{
// 3.1 根据上一帧的偏置设定当前帧的新偏置
pKFi->mpImuPreintegrated->SetNewBias(pKFi->mPrevKF->GetImuBias());
// 3.2 提取节点
g2o::HyperGraph::Vertex* VP1 = optimizer.vertex(pKFi->mPrevKF->mnId);
g2o::HyperGraph::Vertex* VV1 = optimizer.vertex(maxKFid+3*(pKFi->mPrevKF->mnId)+1);
g2o::HyperGraph::Vertex* VG1;
g2o::HyperGraph::Vertex* VA1;
g2o::HyperGraph::Vertex* VG2;
g2o::HyperGraph::Vertex* VA2;
// 根据不同输入配置相应的偏置节点
if (!bInit)
{
VG1 = optimizer.vertex(maxKFid+3*(pKFi->mPrevKF->mnId)+2);
VA1 = optimizer.vertex(maxKFid+3*(pKFi->mPrevKF->mnId)+3);
VG2 = optimizer.vertex(maxKFid+3*(pKFi->mnId)+2);
VA2 = optimizer.vertex(maxKFid+3*(pKFi->mnId)+3);
}
else
{
VG1 = optimizer.vertex(4*maxKFid+2);
VA1 = optimizer.vertex(4*maxKFid+3);
}
g2o::HyperGraph::Vertex* VP2 = optimizer.vertex(pKFi->mnId);
g2o::HyperGraph::Vertex* VV2 = optimizer.vertex(maxKFid+3*(pKFi->mnId)+1);
if (!bInit)
{
if(!VP1 || !VV1 || !VG1 || !VA1 || !VP2 || !VV2 || !VG2 || !VA2)
{
cout << "Error" << VP1 << ", "<< VV1 << ", "<< VG1 << ", "<< VA1 << ", " << VP2 << ", " << VV2 << ", "<< VG2 << ", "<< VA2 <mpImuPreintegrated);
ei->setVertex(0,dynamic_cast(VP1));
ei->setVertex(1,dynamic_cast(VV1));
ei->setVertex(2,dynamic_cast(VG1));
ei->setVertex(3,dynamic_cast(VA1));
ei->setVertex(4,dynamic_cast(VP2));
ei->setVertex(5,dynamic_cast(VV2));
g2o::RobustKernelHuber* rki = new g2o::RobustKernelHuber;
ei->setRobustKernel(rki);
// 9个自由度的卡方检验(0.05)
rki->setDelta(sqrt(16.92));
optimizer.addEdge(ei);
// 加了每一个关键帧的偏置时,还要优化两帧之间偏置的误差
if (!bInit)
{
EdgeGyroRW* egr= new EdgeGyroRW();
egr->setVertex(0,VG1);
egr->setVertex(1,VG2);
cv::Mat cvInfoG = pKFi->mpImuPreintegrated->C.rowRange(9,12).colRange(9,12).inv(cv::DECOMP_SVD);
Eigen::Matrix3d InfoG;
for(int r=0;r<3;r++)
for(int c=0;c<3;c++)
InfoG(r,c)=cvInfoG.at(r,c);
egr->setInformation(InfoG);
egr->computeError();
optimizer.addEdge(egr);
EdgeAccRW* ear = new EdgeAccRW();
ear->setVertex(0,VA1);
ear->setVertex(1,VA2);
cv::Mat cvInfoA = pKFi->mpImuPreintegrated->C.rowRange(12,15).colRange(12,15).inv(cv::DECOMP_SVD);
Eigen::Matrix3d InfoA;
for(int r=0;r<3;r++)
for(int c=0;c<3;c++)
InfoA(r,c)=cvInfoA.at(r,c);
ear->setInformation(InfoA);
ear->computeError();
optimizer.addEdge(ear);
}
}
else
{
cout << pKFi->mnId << " or " << pKFi->mPrevKF->mnId << " no imu" << endl;
}
}
}
// 只加入pIncKF帧的偏置,优化偏置到0
if (bInit)
{
g2o::HyperGraph::Vertex* VG = optimizer.vertex(4*maxKFid+2);
g2o::HyperGraph::Vertex* VA = optimizer.vertex(4*maxKFid+3);
// Add prior to comon biases
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);
}
const float thHuberMono = sqrt(5.991);
const float thHuberStereo = sqrt(7.815);
const unsigned long iniMPid = maxKFid*5;
vector vbNotIncludedMP(vpMPs.size(),false);
// 5. 添加关于mp的节点与边,这段比较好理解,很传统的视觉上的重投影误差
for(size_t i=0; isetEstimate(Converter::toVector3d(pMP->GetWorldPos()));
unsigned long id = pMP->mnId+iniMPid+1;
vPoint->setId(id);
vPoint->setMarginalized(true);
optimizer.addVertex(vPoint);
const map> observations = pMP->GetObservations();
bool bAllFixed = true;
//Set edges
// 遍历所有能观测到这个点的关键帧
for(map>::const_iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++)
{
KeyFrame* pKFi = mit->first;
if(pKFi->mnId>maxKFid)
continue;
if(!pKFi->isBad())
{
const int leftIndex = get<0>(mit->second);
cv::KeyPoint kpUn;
// 添加边
if(leftIndex != -1 && pKFi->mvuRight[get<0>(mit->second)]<0) // Monocular observation
{
kpUn = pKFi->mvKeysUn[leftIndex];
Eigen::Matrix obs;
obs << kpUn.pt.x, kpUn.pt.y;
EdgeMono* e = new EdgeMono(0);
g2o::OptimizableGraph::Vertex* VP = dynamic_cast(optimizer.vertex(pKFi->mnId));
if(bAllFixed)
if(!VP->fixed())
bAllFixed=false;
e->setVertex(0, dynamic_cast(optimizer.vertex(id)));
e->setVertex(1, VP);
e->setMeasurement(obs);
const float invSigma2 = pKFi->mvInvLevelSigma2[kpUn.octave];
e->setInformation(Eigen::Matrix2d::Identity()*invSigma2);
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(thHuberMono);
optimizer.addEdge(e);
}
else if(leftIndex != -1 && pKFi->mvuRight[leftIndex] >= 0) // stereo observation
{
kpUn = pKFi->mvKeysUn[leftIndex];
const float kp_ur = pKFi->mvuRight[leftIndex];
Eigen::Matrix obs;
obs << kpUn.pt.x, kpUn.pt.y, kp_ur;
EdgeStereo* e = new EdgeStereo(0);
g2o::OptimizableGraph::Vertex* VP = dynamic_cast(optimizer.vertex(pKFi->mnId));
if(bAllFixed)
if(!VP->fixed())
bAllFixed=false;
e->setVertex(0, dynamic_cast(optimizer.vertex(id)));
e->setVertex(1, VP);
e->setMeasurement(obs);
const float invSigma2 = pKFi->mvInvLevelSigma2[kpUn.octave];
e->setInformation(Eigen::Matrix3d::Identity()*invSigma2);
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(thHuberStereo);
optimizer.addEdge(e);
}
if(pKFi->mpCamera2){ // Monocular right observation
int rightIndex = get<1>(mit->second);
if(rightIndex != -1 && rightIndex < pKFi->mvKeysRight.size()){
rightIndex -= pKFi->NLeft;
Eigen::Matrix obs;
kpUn = pKFi->mvKeysRight[rightIndex];
obs << kpUn.pt.x, kpUn.pt.y;
EdgeMono *e = new EdgeMono(1);
g2o::OptimizableGraph::Vertex* VP = dynamic_cast(optimizer.vertex(pKFi->mnId));
if(bAllFixed)
if(!VP->fixed())
bAllFixed=false;
e->setVertex(0, dynamic_cast(optimizer.vertex(id)));
e->setVertex(1, VP);
e->setMeasurement(obs);
const float invSigma2 = pKFi->mvInvLevelSigma2[kpUn.octave];
e->setInformation(Eigen::Matrix2d::Identity()*invSigma2);
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(thHuberMono);
optimizer.addEdge(e);
}
}
}
}
if(bAllFixed)
{
optimizer.removeVertex(vPoint);
vbNotIncludedMP[i]=true;
}
}
if(pbStopFlag)
if(*pbStopFlag)
return;
optimizer.initializeOptimization();
optimizer.optimize(its);
// 5. 取出优化结果,对应的值赋值
// Recover optimized data
//Keyframes
for(size_t i=0; imnId>maxKFid)
continue;
VertexPose* VP = static_cast(optimizer.vertex(pKFi->mnId));
if(nLoopId==0)
{
cv::Mat Tcw = Converter::toCvSE3(VP->estimate().Rcw[0], VP->estimate().tcw[0]);
pKFi->SetPose(Tcw);
}
else
{
pKFi->mTcwGBA = cv::Mat::eye(4,4,CV_32F);
Converter::toCvMat(VP->estimate().Rcw[0]).copyTo(pKFi->mTcwGBA.rowRange(0,3).colRange(0,3));
Converter::toCvMat(VP->estimate().tcw[0]).copyTo(pKFi->mTcwGBA.rowRange(0,3).col(3));
pKFi->mnBAGlobalForKF = nLoopId;
}
if(pKFi->bImu)
{
VertexVelocity* VV = static_cast(optimizer.vertex(maxKFid+3*(pKFi->mnId)+1));
if(nLoopId==0)
{
pKFi->SetVelocity(Converter::toCvMat(VV->estimate()));
}
else
{
pKFi->mVwbGBA = Converter::toCvMat(VV->estimate());
}
VertexGyroBias* VG;
VertexAccBias* VA;
if (!bInit)
{
VG = static_cast(optimizer.vertex(maxKFid+3*(pKFi->mnId)+2));
VA = static_cast(optimizer.vertex(maxKFid+3*(pKFi->mnId)+3));
}
else
{
VG = static_cast(optimizer.vertex(4*maxKFid+2));
VA = static_cast(optimizer.vertex(4*maxKFid+3));
}
Vector6d vb;
vb << VG->estimate(), VA->estimate();
IMU::Bias b (vb[3],vb[4],vb[5],vb[0],vb[1],vb[2]);
if(nLoopId==0)
{
pKFi->SetNewBias(b);
}
else
{
pKFi->mBiasGBA = b;
}
}
}
//Points
for(size_t i=0; i(optimizer.vertex(pMP->mnId+iniMPid+1));
if(nLoopId==0)
{
pMP->SetWorldPos(Converter::toCvMat(vPoint->estimate()));
pMP->UpdateNormalAndDepth();
}
else
{
pMP->mPosGBA.create(3,1,CV_32F);
Converter::toCvMat(vPoint->estimate()).copyTo(pMP->mPosGBA);
pMP->mnBAGlobalForKF = nLoopId;
}
}
pMap->IncreaseChangeIndex();
}
/**
* @brief 位姿优化,纯视觉时使用。优化目标:单帧的位姿
* @param pFrame 待优化的帧
*/
int Optimizer::PoseOptimization(Frame *pFrame)
{
// 该优化函数主要用于Tracking线程中:运动跟踪、参考帧跟踪、地图跟踪、重定位
// Step 1:构造g2o优化器, BlockSolver_6_3表示:位姿 _PoseDim 为6维,路标点 _LandmarkDim 是3维
g2o::SparseOptimizer optimizer;
g2o::BlockSolver_6_3::LinearSolverType * linearSolver;
linearSolver = new g2o::LinearSolverDense();
g2o::BlockSolver_6_3 * solver_ptr = new g2o::BlockSolver_6_3(linearSolver);
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg(solver_ptr);
optimizer.setAlgorithm(solver);
// 输入的帧中,有效的,参与优化过程的2D-3D点对
int nInitialCorrespondences=0;
// Set Frame vertex
// Step 2:添加顶点:待优化当前帧的Tcw
g2o::VertexSE3Expmap * vSE3 = new g2o::VertexSE3Expmap();
vSE3->setEstimate(Converter::toSE3Quat(pFrame->mTcw));
// 设置id
vSE3->setId(0);
// 要优化的变量,所以不能固定
vSE3->setFixed(false);
optimizer.addVertex(vSE3);
// Set MapPoint vertices
const int N = pFrame->N;
vector vpEdgesMono;
vector vpEdgesMono_FHR;
vector vnIndexEdgeMono, vnIndexEdgeRight;
vpEdgesMono.reserve(N);
vpEdgesMono_FHR.reserve(N);
vnIndexEdgeMono.reserve(N);
vnIndexEdgeRight.reserve(N);
vector vpEdgesStereo;
vector vnIndexEdgeStereo;
vpEdgesStereo.reserve(N);
vnIndexEdgeStereo.reserve(N);
// 自由度为2的卡方分布,显著性水平为0.05,对应的临界阈值5.991
const float deltaMono = sqrt(5.991);
// 自由度为3的卡方分布,显著性水平为0.05,对应的临界阈值7.815
const float deltaStereo = sqrt(7.815);
// Step 3:添加一元边
{
// 锁定地图点。由于需要使用地图点来构造顶点和边,因此不希望在构造的过程中部分地图点被改写造成不一致甚至是段错误
unique_lock lock(MapPoint::mGlobalMutex);
// 遍历当前地图中的所有地图点
for(int i=0; imvpMapPoints[i];
// 如果这个地图点还存在没有被剔除掉
if(pMP)
{
//Conventional SLAM
if(!pFrame->mpCamera2){
// Monocular observation
// 单目情况
if(pFrame->mvuRight[i]<0)
{
nInitialCorrespondences++;
pFrame->mvbOutlier[i] = false;
// 对这个地图点的观测
Eigen::Matrix obs;
const cv::KeyPoint &kpUn = pFrame->mvKeysUn[i];
obs << kpUn.pt.x, kpUn.pt.y;
// 新建节点,注意这个节点的只是优化位姿Pose
ORB_SLAM3::EdgeSE3ProjectXYZOnlyPose* e = new ORB_SLAM3::EdgeSE3ProjectXYZOnlyPose();
e->setVertex(0, dynamic_cast(optimizer.vertex(0)));
e->setMeasurement(obs);
// 这个点的可信程度和特征点所在的图层有关
const float invSigma2 = pFrame->mvInvLevelSigma2[kpUn.octave];
e->setInformation(Eigen::Matrix2d::Identity()*invSigma2);
// 在这里使用了鲁棒核函数
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(deltaMono);
// 设置相机内参
e->pCamera = pFrame->mpCamera;
// 地图点的空间位置,作为迭代的初始值
cv::Mat Xw = pMP->GetWorldPos();
e->Xw[0] = Xw.at(0);
e->Xw[1] = Xw.at(1);
e->Xw[2] = Xw.at(2);
optimizer.addEdge(e);
vpEdgesMono.push_back(e);
vnIndexEdgeMono.push_back(i);
}
else // Stereo observation
{
nInitialCorrespondences++;
pFrame->mvbOutlier[i] = false;
//SET EDGE
// 观测多了一项右目的坐标
Eigen::Matrix obs;
const cv::KeyPoint &kpUn = pFrame->mvKeysUn[i];
const float &kp_ur = pFrame->mvuRight[i];
obs << kpUn.pt.x, kpUn.pt.y, kp_ur;
// 新建节点,注意这里也是只优化位姿
g2o::EdgeStereoSE3ProjectXYZOnlyPose* e = new g2o::EdgeStereoSE3ProjectXYZOnlyPose();
e->setVertex(0, dynamic_cast(optimizer.vertex(0)));
e->setMeasurement(obs);
// 置信程度主要是看左目特征点所在的图层
const float invSigma2 = pFrame->mvInvLevelSigma2[kpUn.octave];
Eigen::Matrix3d Info = Eigen::Matrix3d::Identity()*invSigma2;
e->setInformation(Info);
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(deltaStereo);
e->fx = pFrame->fx;
e->fy = pFrame->fy;
e->cx = pFrame->cx;
e->cy = pFrame->cy;
e->bf = pFrame->mbf;
cv::Mat Xw = pMP->GetWorldPos();
e->Xw[0] = Xw.at(0);
e->Xw[1] = Xw.at(1);
e->Xw[2] = Xw.at(2);
optimizer.addEdge(e);
vpEdgesStereo.push_back(e);
vnIndexEdgeStereo.push_back(i);
}
}
//SLAM with respect a rigid body
else{
nInitialCorrespondences++;
cv::KeyPoint kpUn;
if (i < pFrame->Nleft) { //Left camera observation
kpUn = pFrame->mvKeys[i];
pFrame->mvbOutlier[i] = false;
Eigen::Matrix obs;
obs << kpUn.pt.x, kpUn.pt.y;
ORB_SLAM3::EdgeSE3ProjectXYZOnlyPose *e = new ORB_SLAM3::EdgeSE3ProjectXYZOnlyPose();
e->setVertex(0, dynamic_cast(optimizer.vertex(0)));
e->setMeasurement(obs);
const float invSigma2 = pFrame->mvInvLevelSigma2[kpUn.octave];
e->setInformation(Eigen::Matrix2d::Identity() * invSigma2);
g2o::RobustKernelHuber *rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(deltaMono);
e->pCamera = pFrame->mpCamera;
cv::Mat Xw = pMP->GetWorldPos();
e->Xw[0] = Xw.at(0);
e->Xw[1] = Xw.at(1);
e->Xw[2] = Xw.at(2);
optimizer.addEdge(e);
vpEdgesMono.push_back(e);
vnIndexEdgeMono.push_back(i);
}
else { //Right camera observation
kpUn = pFrame->mvKeysRight[i - pFrame->Nleft];
Eigen::Matrix obs;
obs << kpUn.pt.x, kpUn.pt.y;
pFrame->mvbOutlier[i] = false;
ORB_SLAM3::EdgeSE3ProjectXYZOnlyPoseToBody *e = new ORB_SLAM3::EdgeSE3ProjectXYZOnlyPoseToBody();
e->setVertex(0, dynamic_cast(optimizer.vertex(0)));
e->setMeasurement(obs);
const float invSigma2 = pFrame->mvInvLevelSigma2[kpUn.octave];
e->setInformation(Eigen::Matrix2d::Identity() * invSigma2);
g2o::RobustKernelHuber *rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(deltaMono);
e->pCamera = pFrame->mpCamera2;
cv::Mat Xw = pMP->GetWorldPos();
e->Xw[0] = Xw.at(0);
e->Xw[1] = Xw.at(1);
e->Xw[2] = Xw.at(2);
e->mTrl = Converter::toSE3Quat(pFrame->mTrl);
optimizer.addEdge(e);
vpEdgesMono_FHR.push_back(e);
vnIndexEdgeRight.push_back(i);
}
}
}
}
}
// 如果没有足够的匹配点,那么就只好放弃了
//cout << "PO: vnIndexEdgeMono.size() = " << vnIndexEdgeMono.size() << " vnIndexEdgeRight.size() = " << vnIndexEdgeRight.size() << endl;
if(nInitialCorrespondences<3)
return 0;
// We perform 4 optimizations, after each optimization we classify observation as inlier/outlier
// At the next optimization, outliers are not included, but at the end they can be classified as inliers again.
// Step 4:开始优化,总共优化四次,每次优化迭代10次,每次优化后,将观测分为outlier和inlier,outlier不参与下次优化
// 由于每次优化后是对所有的观测进行outlier和inlier判别,因此之前被判别为outlier有可能变成inlier,反之亦然
// 基于卡方检验计算出的阈值(假设测量有一个像素的偏差)
const float chi2Mono[4]={5.991,5.991,5.991,5.991}; // 单目
const float chi2Stereo[4]={7.815,7.815,7.815, 7.815}; // 双目
const int its[4]={10,10,10,10};// 四次迭代,每次迭代的次数
// bad 的地图点个数
int nBad=0;
// 一共进行四次优化
for(size_t it=0; it<4; it++)
{
vSE3->setEstimate(Converter::toSE3Quat(pFrame->mTcw));
// 其实就是初始化优化器,这里的参数0就算是不填写,默认也是0,也就是只对level为0的边进行优化
optimizer.initializeOptimization(0);
// 开始优化,优化10次
optimizer.optimize(its[it]);
nBad=0;
// 优化结束,开始遍历参与优化的每一条误差边(单目)
for(size_t i=0, iend=vpEdgesMono.size(); imvbOutlier[idx])
{
e->computeError();
}
// 就是error*\Omega*error,表征了这个点的误差大小(考虑置信度以后)
const float chi2 = e->chi2();
if(chi2>chi2Mono[it])
{
pFrame->mvbOutlier[idx]=true;
e->setLevel(1); // 设置为outlier , level 1 对应为外点,上面的过程中我们设置其为不优化
nBad++;
}
else
{
pFrame->mvbOutlier[idx]=false;
e->setLevel(0); // 设置为inlier, level 0 对应为内点,上面的过程中我们就是要优化这些关系
}
if(it==2)
e->setRobustKernel(0); // 除了前两次优化需要RobustKernel以外, 其余的优化都不需要 -- 因为重投影的误差已经有明显的下降了
}
for(size_t i=0, iend=vpEdgesMono_FHR.size(); imvbOutlier[idx])
{
e->computeError();
}
const float chi2 = e->chi2();
if(chi2>chi2Mono[it])
{
pFrame->mvbOutlier[idx]=true;
e->setLevel(1);
nBad++;
}
else
{
pFrame->mvbOutlier[idx]=false;
e->setLevel(0);
}
if(it==2)
e->setRobustKernel(0);
}
for(size_t i=0, iend=vpEdgesStereo.size(); imvbOutlier[idx])
{
e->computeError();
}
const float chi2 = e->chi2();
if(chi2>chi2Stereo[it])
{
pFrame->mvbOutlier[idx]=true;
e->setLevel(1);
nBad++;
}
else
{
e->setLevel(0);
pFrame->mvbOutlier[idx]=false;
}
if(it==2)
e->setRobustKernel(0);
}
if(optimizer.edges().size()<10)
break;
}
// Recover optimized pose and return number of inliers
// Step 5 得到优化后的当前帧的位姿
g2o::VertexSE3Expmap* vSE3_recov = static_cast(optimizer.vertex(0));
g2o::SE3Quat SE3quat_recov = vSE3_recov->estimate();
cv::Mat pose = Converter::toCvMat(SE3quat_recov);
pFrame->SetPose(pose);
// 并且返回内点数目
return nInitialCorrespondences-nBad;
}
void Optimizer::LocalBundleAdjustment(KeyFrame *pKF, bool* pbStopFlag, vector &vpNonEnoughOptKFs)
{
// 该优化函数用于LocalMapping线程的局部BA优化
// Local KeyFrames: First Breath Search from Current Keyframe
list lLocalKeyFrames;
// Step 1 将当前关键帧及其共视关键帧加入lLocalKeyFrames
lLocalKeyFrames.push_back(pKF);
pKF->mnBALocalForKF = pKF->mnId;
Map* pCurrentMap = pKF->GetMap();
// 找到关键帧连接的共视关键帧(一级相连),加入lLocalKeyFrames中
const vector vNeighKFs = pKF->GetVectorCovisibleKeyFrames();
for(int i=0, iend=vNeighKFs.size(); imnBALocalForKF = pKF->mnId;
if(!pKFi->isBad() && pKFi->GetMap() == pCurrentMap)
lLocalKeyFrames.push_back(pKFi);
}
for(KeyFrame* pKFi : vpNonEnoughOptKFs)
{
if(!pKFi->isBad() && pKFi->GetMap() == pCurrentMap && pKFi->mnBALocalForKF != pKF->mnId)
{
pKFi->mnBALocalForKF = pKF->mnId;
lLocalKeyFrames.push_back(pKFi);
}
}
// Local MapPoints seen in Local KeyFrames
// Step 2 遍历 lLocalKeyFrames 中(一级)关键帧,将它们观测的MapPoints加入到lLocalMapPoints
list lLocalMapPoints;
set sNumObsMP;
int num_fixedKF;
// 遍历 lLocalKeyFrames 中的每一个关键帧
for(list::iterator lit=lLocalKeyFrames.begin() , lend=lLocalKeyFrames.end(); lit!=lend; lit++)
{
KeyFrame* pKFi = *lit;
if(pKFi->mnId==pCurrentMap->GetInitKFid())
{
num_fixedKF = 1;
}
vector vpMPs = pKFi->GetMapPointMatches();
// 遍历这个关键帧观测到的每一个地图点,加入到lLocalMapPoints
for(vector::iterator vit=vpMPs.begin(), vend=vpMPs.end(); vit!=vend; vit++)
{
MapPoint* pMP = *vit;
if(pMP)
if(!pMP->isBad() && pMP->GetMap() == pCurrentMap)
{
// 把参与局部BA的每一个地图点的 mnBALocalForKF设置为当前关键帧的mnId
// mnBALocalForKF 是为了防止重复添加
if(pMP->mnBALocalForKF!=pKF->mnId)
{
lLocalMapPoints.push_back(pMP);
pMP->mnBALocalForKF=pKF->mnId;
}
}
}
}
// Fixed Keyframes. Keyframes that see Local MapPoints but that are not Local Keyframes
// Step 3 得到能被局部MapPoints观测到,但不属于局部关键帧的(二级)关键帧,这些关键帧在局部BA优化时不优化
list lFixedCameras;
// 遍历局部地图中的每个地图点
for(list::iterator lit=lLocalMapPoints.begin(), lend=lLocalMapPoints.end(); lit!=lend; lit++)
{
// 观测到该MapPoint的KF和该MapPoint在KF中的索引
map> observations = (*lit)->GetObservations();
// 遍历所有观测到该地图点的关键帧
for(map>::iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++)
{
KeyFrame* pKFi = mit->first;
// pKFi->mnBALocalForKF!=pKF->mnId 表示不属于局部关键帧,
// pKFi->mnBAFixedForKF!=pKF->mnId 表示还未标记为fixed(固定的)关键帧
if(pKFi->mnBALocalForKF!=pKF->mnId && pKFi->mnBAFixedForKF!=pKF->mnId )
{
// 将局部地图点能观测到的、但是不属于局部BA范围的关键帧的mnBAFixedForKF标记为pKF(触发局部BA的当前关键帧)的mnId
pKFi->mnBAFixedForKF=pKF->mnId;
if(!pKFi->isBad() && pKFi->GetMap() == pCurrentMap)
lFixedCameras.push_back(pKFi);
}
}
}
num_fixedKF = lFixedCameras.size() + num_fixedKF;
if(num_fixedKF < 2)
{
list::iterator lit=lLocalKeyFrames.begin();
int lowerId = pKF->mnId;
KeyFrame* pLowerKf;
int secondLowerId = pKF->mnId;
KeyFrame* pSecondLowerKF;
for(; lit != lLocalKeyFrames.end(); lit++)
{
KeyFrame* pKFi = *lit;
if(pKFi == pKF || pKFi->mnId == pCurrentMap->GetInitKFid())
{
continue;
}
if(pKFi->mnId < lowerId)
{
lowerId = pKFi->mnId;
pLowerKf = pKFi;
}
else if(pKFi->mnId < secondLowerId)
{
secondLowerId = pKFi->mnId;
pSecondLowerKF = pKFi;
}
}
lFixedCameras.push_back(pLowerKf);
lLocalKeyFrames.remove(pLowerKf);
num_fixedKF++;
if(num_fixedKF < 2)
{
lFixedCameras.push_back(pSecondLowerKF);
lLocalKeyFrames.remove(pSecondLowerKF);
num_fixedKF++;
}
}
if(num_fixedKF == 0)
{
Verbose::PrintMess("LM-LBA: There are 0 fixed KF in the optimizations, LBA aborted", Verbose::VERBOSITY_QUIET);
return;
}
// Setup optimizer
// Step 4 构造g2o优化器,按照步骤来就行了
g2o::SparseOptimizer optimizer;
g2o::BlockSolver_6_3::LinearSolverType * linearSolver;
linearSolver = new g2o::LinearSolverEigen();
g2o::BlockSolver_6_3 * solver_ptr = new g2o::BlockSolver_6_3(linearSolver);
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg(solver_ptr);
if (pCurrentMap->IsInertial())
solver->setUserLambdaInit(100.0); // TODO uncomment
optimizer.setAlgorithm(solver);
optimizer.setVerbose(false);
// 外界设置的停止优化标志
// 可能在 Tracking::NeedNewKeyFrame() 里置位
if(pbStopFlag)
optimizer.setForceStopFlag(pbStopFlag);
// 记录参与局部BA的最大关键帧mnId
unsigned long maxKFid = 0;
// Set Local KeyFrame vertices
// Step 5 添加待优化的位姿顶点:Pose of Local KeyFrame
for(list::iterator lit=lLocalKeyFrames.begin(), lend=lLocalKeyFrames.end(); lit!=lend; lit++)
{
KeyFrame* pKFi = *lit;
g2o::VertexSE3Expmap * vSE3 = new g2o::VertexSE3Expmap();
// 设置初始优化位姿
vSE3->setEstimate(Converter::toSE3Quat(pKFi->GetPose()));
vSE3->setId(pKFi->mnId);
// 如果是初始关键帧,要锁住位姿不优化
vSE3->setFixed(pKFi->mnId==pCurrentMap->GetInitKFid());
optimizer.addVertex(vSE3);
if(pKFi->mnId>maxKFid)
maxKFid=pKFi->mnId;
}
// Set Fixed KeyFrame vertices
// Step 6 添加不优化的位姿顶点:Pose of Fixed KeyFrame,注意这里调用了vSE3->setFixed(true)
// 不优化为啥也要添加?回答:为了增加约束信息
for(list::iterator lit=lFixedCameras.begin(), lend=lFixedCameras.end(); lit!=lend; lit++)
{
KeyFrame* pKFi = *lit;
g2o::VertexSE3Expmap * vSE3 = new g2o::VertexSE3Expmap();
vSE3->setEstimate(Converter::toSE3Quat(pKFi->GetPose()));
vSE3->setId(pKFi->mnId);
// 所有的这些顶点的位姿都不优化,只是为了增加约束项
vSE3->setFixed(true);
optimizer.addVertex(vSE3);
if(pKFi->mnId>maxKFid)
maxKFid=pKFi->mnId;
}
// Set MapPoint vertices
// Step 7 添加待优化的3D地图点顶点
// 边的数目 = pose数目 * 地图点数目
const int nExpectedSize = (lLocalKeyFrames.size()+lFixedCameras.size())*lLocalMapPoints.size();
vector vpEdgesMono;
vpEdgesMono.reserve(nExpectedSize);
vector vpEdgesBody;
vpEdgesBody.reserve(nExpectedSize);
vector vpEdgeKFMono;
vpEdgeKFMono.reserve(nExpectedSize);
vector vpEdgeKFBody;
vpEdgeKFBody.reserve(nExpectedSize);
vector vpMapPointEdgeMono;
vpMapPointEdgeMono.reserve(nExpectedSize);
vector vpMapPointEdgeBody;
vpMapPointEdgeBody.reserve(nExpectedSize);
vector vpEdgesStereo;
vpEdgesStereo.reserve(nExpectedSize);
vector vpEdgeKFStereo;
vpEdgeKFStereo.reserve(nExpectedSize);
vector vpMapPointEdgeStereo;
vpMapPointEdgeStereo.reserve(nExpectedSize);
// 自由度为2的卡方分布,显著性水平为0.05,对应的临界阈值5.991
const float thHuberMono = sqrt(5.991);
// 自由度为3的卡方分布,显著性水平为0.05,对应的临界阈值7.815
const float thHuberStereo = sqrt(7.815);
int nPoints = 0;
int nKFs = lLocalKeyFrames.size()+lFixedCameras.size(), nEdges = 0;
// 遍历所有的局部地图中的地图点
for(list::iterator lit=lLocalMapPoints.begin(), lend=lLocalMapPoints.end(); lit!=lend; lit++)
{
// 添加顶点:MapPoint
MapPoint* pMP = *lit;
g2o::VertexSBAPointXYZ* vPoint = new g2o::VertexSBAPointXYZ();
vPoint->setEstimate(Converter::toVector3d(pMP->GetWorldPos()));
// 前面记录maxKFid的作用在这里体现
int id = pMP->mnId+maxKFid+1;
vPoint->setId(id);
// 因为使用了LinearSolverType,所以需要将所有的三维点边缘化掉
vPoint->setMarginalized(true);
optimizer.addVertex(vPoint);
nPoints++;
// 观测到该地图点的KF和该地图点在KF中的索引
const map> observations = pMP->GetObservations();
//Set edges
// Step 8 在添加完了一个地图点之后, 对每一对关联的MapPoint和KeyFrame构建边
// 遍历所有观测到当前地图点的关键帧
for(map>::const_iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++)
{
KeyFrame* pKFi = mit->first;
if(!pKFi->isBad() && pKFi->GetMap() == pCurrentMap)
{
const int cam0Index = get<0>(mit->second);
// 根据单目/双目两种不同的输入构造不同的误差边
// Monocular observation of Camera 0
// 单目模式下
if(cam0Index != -1 && pKFi->mvuRight[cam0Index]<0)
{
const cv::KeyPoint &kpUn = pKFi->mvKeysUn[cam0Index];
Eigen::Matrix obs;
obs << kpUn.pt.x, kpUn.pt.y;
ORB_SLAM3::EdgeSE3ProjectXYZ* e = new ORB_SLAM3::EdgeSE3ProjectXYZ();
e->setVertex(0, dynamic_cast(optimizer.vertex(id)));
e->setVertex(1, dynamic_cast(optimizer.vertex(pKFi->mnId)));
e->setMeasurement(obs);
// 权重为特征点所在图像金字塔的层数的倒数
const float &invSigma2 = pKFi->mvInvLevelSigma2[kpUn.octave];
e->setInformation(Eigen::Matrix2d::Identity()*invSigma2);
// 使用鲁棒核函数抑制外点
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(thHuberMono);
e->pCamera = pKFi->mpCamera;
optimizer.addEdge(e);
vpEdgesMono.push_back(e);
vpEdgeKFMono.push_back(pKFi);
vpMapPointEdgeMono.push_back(pMP);
nEdges++;
}
else if(cam0Index != -1 && pKFi->mvuRight[cam0Index]>=0)// Stereo observation (with rectified images)
{
const cv::KeyPoint &kpUn = pKFi->mvKeysUn[cam0Index];
Eigen::Matrix obs;
const float kp_ur = pKFi->mvuRight[cam0Index];
obs << kpUn.pt.x, kpUn.pt.y, kp_ur;
g2o::EdgeStereoSE3ProjectXYZ* e = new g2o::EdgeStereoSE3ProjectXYZ();
e->setVertex(0, dynamic_cast(optimizer.vertex(id)));
e->setVertex(1, dynamic_cast(optimizer.vertex(pKFi->mnId)));
e->setMeasurement(obs);
const float &invSigma2 = pKFi->mvInvLevelSigma2[kpUn.octave];
Eigen::Matrix3d Info = Eigen::Matrix3d::Identity()*invSigma2;
e->setInformation(Info);
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(thHuberStereo);
e->fx = pKFi->fx;
e->fy = pKFi->fy;
e->cx = pKFi->cx;
e->cy = pKFi->cy;
e->bf = pKFi->mbf;
optimizer.addEdge(e);
vpEdgesStereo.push_back(e);
vpEdgeKFStereo.push_back(pKFi);
vpMapPointEdgeStereo.push_back(pMP);
nEdges++;
}
// Monocular observation of Camera 0
if(pKFi->mpCamera2){
int rightIndex = get<1>(mit->second);
if(rightIndex != -1 ){
rightIndex -= pKFi->NLeft;
Eigen::Matrix obs;
cv::KeyPoint kp = pKFi->mvKeysRight[rightIndex];
obs << kp.pt.x, kp.pt.y;
ORB_SLAM3::EdgeSE3ProjectXYZToBody *e = new ORB_SLAM3::EdgeSE3ProjectXYZToBody();
e->setVertex(0, dynamic_cast(optimizer.vertex(id)));
e->setVertex(1, dynamic_cast(optimizer.vertex(pKFi->mnId)));
e->setMeasurement(obs);
const float &invSigma2 = pKFi->mvInvLevelSigma2[kp.octave];
e->setInformation(Eigen::Matrix2d::Identity()*invSigma2);
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(thHuberMono);
e->mTrl = Converter::toSE3Quat(pKFi->mTrl);
e->pCamera = pKFi->mpCamera2;
optimizer.addEdge(e);
vpEdgesBody.push_back(e);
vpEdgeKFBody.push_back(pKFi);
vpMapPointEdgeBody.push_back(pMP);
nEdges++;
}
}
}
}
}
// 开始BA前再次确认是否有外部请求停止优化,因为这个变量是引用传递,会随外部变化
// 可能在 Tracking::NeedNewKeyFrame(), mpLocalMapper->InsertKeyFrame 里置位
if(pbStopFlag)
if(*pbStopFlag)
{
return;
}
// Step 9 开始优化。分成两个阶段
// 第一阶段优化
optimizer.initializeOptimization();
// 迭代5次
int numPerform_it = optimizer.optimize(5);
bool bDoMore= true;
// 检查是否外部请求停止
if(pbStopFlag)
if(*pbStopFlag)
bDoMore = false;
// 如果有外部请求停止,那么就不在进行第二阶段的优化
if(bDoMore)
{
// Check inlier observations
int nMonoBadObs = 0;
// Step 10 检测outlier,并设置下次不优化
// 遍历所有的单目误差边
for(size_t i=0, iend=vpEdgesMono.size(); iisBad())
continue;
if(e->chi2()>5.991 || !e->isDepthPositive())
{
nMonoBadObs++;
}
}
int nBodyBadObs = 0;
for(size_t i=0, iend=vpEdgesBody.size(); iisBad())
continue;
if(e->chi2()>5.991 || !e->isDepthPositive())
{
nBodyBadObs++;
}
}
// 对于所有的双目的误差边也都进行类似的操作
int nStereoBadObs = 0;
for(size_t i=0, iend=vpEdgesStereo.size(); iisBad())
continue;
// 自由度为3的卡方分布,显著性水平为0.05,对应的临界阈值7.815
if(e->chi2()>7.815 || !e->isDepthPositive())
{
nStereoBadObs++;
}
}
// Optimize again
// Step 11:排除误差较大的outlier后再次优化 -- 第二阶段优化
numPerform_it += optimizer.optimize(5);
}
vector > vToErase;
vToErase.reserve(vpEdgesMono.size()+vpEdgesBody.size()+vpEdgesStereo.size());
// Check inlier observations
// Step 12:在优化后重新计算误差,剔除连接误差比较大的关键帧和MapPoint
// 对于单目误差边
for(size_t i=0, iend=vpEdgesMono.size(); iisBad())
continue;
// 基于卡方检验计算出的阈值(假设测量有一个像素的偏差)
// 自由度为2的卡方分布,显著性水平为0.05,对应的临界阈值5.991
// 如果 当前边误差超出阈值,或者边链接的地图点深度值为负,说明这个边有问题,要删掉了
if(e->chi2()>5.991 || !e->isDepthPositive())
{
KeyFrame* pKFi = vpEdgeKFMono[i];
vToErase.push_back(make_pair(pKFi,pMP));
}
}
for(size_t i=0, iend=vpEdgesBody.size(); iisBad())
continue;
if(e->chi2()>5.991 || !e->isDepthPositive())
{
KeyFrame* pKFi = vpEdgeKFBody[i];
vToErase.push_back(make_pair(pKFi,pMP));
}
}
// 双目误差边
for(size_t i=0, iend=vpEdgesStereo.size(); iisBad())
continue;
if(e->chi2()>7.815 || !e->isDepthPositive())
{
KeyFrame* pKFi = vpEdgeKFStereo[i];
vToErase.push_back(make_pair(pKFi,pMP));
}
}
bool bRedrawError = false;
bool bWriteStats = false;
// Get Map Mutex
unique_lock lock(pCurrentMap->mMutexMapUpdate);
// 删除点
// 连接偏差比较大,在关键帧中剔除对该地图点的观测
// 连接偏差比较大,在地图点中剔除对该关键帧的观测
if(!vToErase.empty())
{
for(size_t i=0;iEraseMapPointMatch(pMPi);
pMPi->EraseObservation(pKFi);
}
}
// Recover optimized data
// Step 13:优化后更新关键帧位姿以及地图点的位置、平均观测方向等属性
//Keyframes
vpNonEnoughOptKFs.clear();
for(list::iterator lit=lLocalKeyFrames.begin(), lend=lLocalKeyFrames.end(); lit!=lend; lit++)
{
KeyFrame* pKFi = *lit;
g2o::VertexSE3Expmap* vSE3 = static_cast(optimizer.vertex(pKFi->mnId));
g2o::SE3Quat SE3quat = vSE3->estimate();
cv::Mat Tiw = Converter::toCvMat(SE3quat);
cv::Mat Tco_cn = pKFi->GetPose() * Tiw.inv();
cv::Vec3d trasl = Tco_cn.rowRange(0,3).col(3);
double dist = cv::norm(trasl);
pKFi->SetPose(Converter::toCvMat(SE3quat));
pKFi->mnNumberOfOpt += numPerform_it;
if(pKFi->mnNumberOfOpt < 10)
{
vpNonEnoughOptKFs.push_back(pKFi);
}
}
//Points
for(list::iterator lit=lLocalMapPoints.begin(), lend=lLocalMapPoints.end(); lit!=lend; lit++)
{
MapPoint* pMP = *lit;
g2o::VertexSBAPointXYZ* vPoint = static_cast(optimizer.vertex(pMP->mnId+maxKFid+1));
pMP->SetWorldPos(Converter::toCvMat(vPoint->estimate()));
pMP->UpdateNormalAndDepth();
}
pCurrentMap->IncreaseChangeIndex();
}
/**
* @brief Local Bundle Adjustment LocalMapping::Run() 使用,纯视觉
*
* 1. Vertex:
* - g2o::VertexSE3Expmap(),LocalKeyFrames,即当前关键帧的位姿、与当前关键帧相连的关键帧的位姿
* - g2o::VertexSE3Expmap(),FixedCameras,即能观测到LocalMapPoints的关键帧(并且不属于LocalKeyFrames)的位姿,在优化中这些关键帧的位姿不变
* - g2o::VertexSBAPointXYZ(),LocalMapPoints,即LocalKeyFrames能观测到的所有MapPoints的位置
* 2. Edge:
* - g2o::EdgeSE3ProjectXYZ(),BaseBinaryEdge
* + Vertex:关键帧的Tcw,MapPoint的Pw
* + measurement:MapPoint在关键帧中的二维位置(u,v)
* + InfoMatrix: invSigma2(与特征点所在的尺度有关)
* - g2o::EdgeStereoSE3ProjectXYZ(),BaseBinaryEdge
* + Vertex:关键帧的Tcw,MapPoint的Pw
* + measurement:MapPoint在关键帧中的二维位置(ul,v,ur)
* + InfoMatrix: invSigma2(与特征点所在的尺度有关)
*
* @param pKF KeyFrame
* @param pbStopFlag 是否停止优化的标志
* @param pMap 在优化后,更新状态时需要用到Map的互斥量mMutexMapUpdate
*
* 总结下与ORBSLAM2的不同
* 前面操作基本一样,但优化时2代去掉了误差大的点又进行优化了,3代只是统计但没有去掉继续优化,而后都是将误差大的点干掉
*/
void Optimizer::LocalBundleAdjustment(KeyFrame *pKF, bool* pbStopFlag, Map* pMap, int& num_fixedKF, int& num_OptKF, int& num_MPs, int& num_edges)
{
// 该优化函数用于LocalMapping线程的局部BA优化
// Local KeyFrames: First Breath Search from Current Keyframe
list lLocalKeyFrames;
// 步骤1:将当前关键帧加入lLocalKeyFrames
lLocalKeyFrames.push_back(pKF);
pKF->mnBALocalForKF = pKF->mnId;
Map* pCurrentMap = pKF->GetMap();
// 步骤2:找到关键帧连接的关键帧(一级相连),加入lLocalKeyFrames中
const vector vNeighKFs = pKF->GetVectorCovisibleKeyFrames();
for(int i=0, iend=vNeighKFs.size(); imnBALocalForKF = pKF->mnId;
if(!pKFi->isBad() && pKFi->GetMap() == pCurrentMap)
lLocalKeyFrames.push_back(pKFi);
}
// Local MapPoints seen in Local KeyFrames
num_fixedKF = 0;
// 步骤3:遍历lLocalKeyFrames中关键帧,将它们观测的MapPoints加入到lLocalMapPoints
list lLocalMapPoints;
set sNumObsMP;
for(list::iterator lit=lLocalKeyFrames.begin() , lend=lLocalKeyFrames.end(); lit!=lend; lit++)
{
KeyFrame* pKFi = *lit;
if(pKFi->mnId==pMap->GetInitKFid())
{
num_fixedKF = 1;
}
vector vpMPs = pKFi->GetMapPointMatches();
for(vector::iterator vit=vpMPs.begin(), vend=vpMPs.end(); vit!=vend; vit++)
{
MapPoint* pMP = *vit;
if(pMP)
if(!pMP->isBad() && pMP->GetMap() == pCurrentMap)
{
if(pMP->mnBALocalForKF!=pKF->mnId)
{
lLocalMapPoints.push_back(pMP);
pMP->mnBALocalForKF=pKF->mnId;
}
}
}
}
num_MPs = lLocalMapPoints.size();
// Fixed Keyframes. Keyframes that see Local MapPoints but that are not Local Keyframes
// 步骤4:得到能被局部MapPoints观测到,但不属于局部关键帧的关键帧,这些关键帧在局部BA优化时不优化
list lFixedCameras;
for(list::iterator lit=lLocalMapPoints.begin(), lend=lLocalMapPoints.end(); lit!=lend; lit++)
{
map> observations = (*lit)->GetObservations();
for(map>::iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++)
{
KeyFrame* pKFi = mit->first;
if(pKFi->mnBALocalForKF!=pKF->mnId && pKFi->mnBAFixedForKF!=pKF->mnId )
{
pKFi->mnBAFixedForKF=pKF->mnId;
if(!pKFi->isBad() && pKFi->GetMap() == pCurrentMap)
lFixedCameras.push_back(pKFi);
}
}
}
// 步骤4.1:相比ORBSLAM2多出了判断固定关键帧的个数,最起码要两个固定的,如果实在没有就把lLocalKeyFrames中最早的KF固定,还是不够再加上第二早的KF固定
num_fixedKF = lFixedCameras.size() + num_fixedKF;
if(num_fixedKF < 2)
{
list::iterator lit=lLocalKeyFrames.begin();
int lowerId = pKF->mnId;
KeyFrame* pLowerKf;
int secondLowerId = pKF->mnId;
KeyFrame* pSecondLowerKF;
for(; lit != lLocalKeyFrames.end(); lit++)
{
KeyFrame* pKFi = *lit;
if(pKFi == pKF || pKFi->mnId == pMap->GetInitKFid())
{
continue;
}
if(pKFi->mnId < lowerId)
{
lowerId = pKFi->mnId;
pLowerKf = pKFi;
}
else if(pKFi->mnId < secondLowerId)
{
secondLowerId = pKFi->mnId;
pSecondLowerKF = pKFi;
}
}
lFixedCameras.push_back(pLowerKf);
lLocalKeyFrames.remove(pLowerKf);
num_fixedKF++;
if(num_fixedKF < 2)
{
lFixedCameras.push_back(pSecondLowerKF);
lLocalKeyFrames.remove(pSecondLowerKF);
num_fixedKF++;
}
}
if(num_fixedKF == 0)
{
Verbose::PrintMess("LM-LBA: There are 0 fixed KF in the optimizations, LBA aborted", Verbose::VERBOSITY_QUIET);
return;
}
// Setup optimizer
// 步骤5:构造g2o优化器
g2o::SparseOptimizer optimizer;
g2o::BlockSolver_6_3::LinearSolverType * linearSolver;
linearSolver = new g2o::LinearSolverEigen();
g2o::BlockSolver_6_3 * solver_ptr = new g2o::BlockSolver_6_3(linearSolver);
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg(solver_ptr);
if (pMap->IsInertial())
solver->setUserLambdaInit(100.0);
optimizer.setAlgorithm(solver);
optimizer.setVerbose(false);
if(pbStopFlag)
optimizer.setForceStopFlag(pbStopFlag);
unsigned long maxKFid = 0;
// Set Local KeyFrame vertices
// 步骤6:添加顶点:Pose of Local KeyFrame
for(list::iterator lit=lLocalKeyFrames.begin(), lend=lLocalKeyFrames.end(); lit!=lend; lit++)
{
KeyFrame* pKFi = *lit;
g2o::VertexSE3Expmap * vSE3 = new g2o::VertexSE3Expmap();
vSE3->setEstimate(Converter::toSE3Quat(pKFi->GetPose()));
vSE3->setId(pKFi->mnId);
vSE3->setFixed(pKFi->mnId==pMap->GetInitKFid());
optimizer.addVertex(vSE3);
if(pKFi->mnId>maxKFid)
maxKFid=pKFi->mnId;
}
num_OptKF = lLocalKeyFrames.size();
// Set Fixed KeyFrame vertices
// 步骤7:添加顶点:Pose of Fixed KeyFrame,注意这里调用了vSE3->setFixed(true)。
for(list::iterator lit=lFixedCameras.begin(), lend=lFixedCameras.end(); lit!=lend; lit++)
{
KeyFrame* pKFi = *lit;
g2o::VertexSE3Expmap * vSE3 = new g2o::VertexSE3Expmap();
vSE3->setEstimate(Converter::toSE3Quat(pKFi->GetPose()));
vSE3->setId(pKFi->mnId);
vSE3->setFixed(true);
optimizer.addVertex(vSE3);
if(pKFi->mnId>maxKFid)
maxKFid=pKFi->mnId;
}
// Set MapPoint vertices
// 步骤7:添加3D顶点
// 存放的方式(举例)
// 边id: 1 2 3 4 5 6 7 8 9
// KFid: 1 2 3 4 1 2 3 2 3
// MPid: 1 1 1 1 2 2 2 3 3
// 所以这个个数大约是点数×帧数,实际肯定比这个要少
const int nExpectedSize = (lLocalKeyFrames.size()+lFixedCameras.size())*lLocalMapPoints.size();
// 存放单目时的边
vector vpEdgesMono;
vpEdgesMono.reserve(nExpectedSize);
// 存放单目时的KF
vector vpEdgesBody;
vpEdgesBody.reserve(nExpectedSize);
vector vpEdgeKFMono;
vpEdgeKFMono.reserve(nExpectedSize);
// 存放单目时的MP
// 存放双目鱼眼时另一个相机的KF
vector vpEdgeKFBody;
vpEdgeKFBody.reserve(nExpectedSize);
// 存放双目鱼眼时另一个相机的边
vector vpMapPointEdgeMono;
vpMapPointEdgeMono.reserve(nExpectedSize);
// 存放双目鱼眼时另一个相机的MP
vector vpMapPointEdgeBody;
vpMapPointEdgeBody.reserve(nExpectedSize);
// 存放双目时的边
vector vpEdgesStereo;
vpEdgesStereo.reserve(nExpectedSize);
// 存放双目时的KF
vector vpEdgeKFStereo;
vpEdgeKFStereo.reserve(nExpectedSize);
// 存放双目时的MP
vector vpMapPointEdgeStereo;
vpMapPointEdgeStereo.reserve(nExpectedSize);
const float thHuberMono = sqrt(5.991);
const float thHuberStereo = sqrt(7.815);
int nPoints = 0;
int nKFs = lLocalKeyFrames.size()+lFixedCameras.size(), nEdges = 0;
// 添加顶点:MapPoint
for(list::iterator lit=lLocalMapPoints.begin(), lend=lLocalMapPoints.end(); lit!=lend; lit++)
{
MapPoint* pMP = *lit;
g2o::VertexSBAPointXYZ* vPoint = new g2o::VertexSBAPointXYZ();
vPoint->setEstimate(Converter::toVector3d(pMP->GetWorldPos()));
int id = pMP->mnId+maxKFid+1;
vPoint->setId(id);
// 这里的边缘化与滑动窗口不同,而是为了加速稀疏矩阵的计算BlockSolver_6_3默认了6维度的不边缘化,3自由度的三维点被边缘化,所以所有三维点都设置边缘化
vPoint->setMarginalized(true);
optimizer.addVertex(vPoint);
nPoints++;
const map> observations = pMP->GetObservations();
//Set edges
// 步骤8:对每一对关联的MapPoint和KeyFrame构建边
for(map>::const_iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++)
{
KeyFrame* pKFi = mit->first;
if(!pKFi->isBad() && pKFi->GetMap() == pCurrentMap)
{
const int leftIndex = get<0>(mit->second);
// Monocular observation
// 单目
if(leftIndex != -1 && pKFi->mvuRight[get<0>(mit->second)]<0)
{
const cv::KeyPoint &kpUn = pKFi->mvKeysUn[leftIndex];
Eigen::Matrix obs;
obs << kpUn.pt.x, kpUn.pt.y;
ORB_SLAM3::EdgeSE3ProjectXYZ* e = new ORB_SLAM3::EdgeSE3ProjectXYZ();
e->setVertex(0, dynamic_cast(optimizer.vertex(id)));
e->setVertex(1, dynamic_cast(optimizer.vertex(pKFi->mnId)));
e->setMeasurement(obs);
const float &invSigma2 = pKFi->mvInvLevelSigma2[kpUn.octave];
e->setInformation(Eigen::Matrix2d::Identity()*invSigma2);
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(thHuberMono);
e->pCamera = pKFi->mpCamera;
optimizer.addEdge(e);
vpEdgesMono.push_back(e);
vpEdgeKFMono.push_back(pKFi);
vpMapPointEdgeMono.push_back(pMP);
nEdges++;
}
else if(leftIndex != -1 && pKFi->mvuRight[get<0>(mit->second)]>=0)// Stereo observation
{
const cv::KeyPoint &kpUn = pKFi->mvKeysUn[leftIndex];
Eigen::Matrix obs;
const float kp_ur = pKFi->mvuRight[get<0>(mit->second)];
obs << kpUn.pt.x, kpUn.pt.y, kp_ur;
g2o::EdgeStereoSE3ProjectXYZ* e = new g2o::EdgeStereoSE3ProjectXYZ();
e->setVertex(0, dynamic_cast(optimizer.vertex(id)));
e->setVertex(1, dynamic_cast(optimizer.vertex(pKFi->mnId)));
e->setMeasurement(obs);
const float &invSigma2 = pKFi->mvInvLevelSigma2[kpUn.octave];
Eigen::Matrix3d Info = Eigen::Matrix3d::Identity()*invSigma2;
e->setInformation(Info);
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(thHuberStereo);
e->fx = pKFi->fx;
e->fy = pKFi->fy;
e->cx = pKFi->cx;
e->cy = pKFi->cy;
e->bf = pKFi->mbf;
optimizer.addEdge(e);
vpEdgesStereo.push_back(e);
vpEdgeKFStereo.push_back(pKFi);
vpMapPointEdgeStereo.push_back(pMP);
nEdges++;
}
if(pKFi->mpCamera2){
int rightIndex = get<1>(mit->second);
if(rightIndex != -1 ){
rightIndex -= pKFi->NLeft;
Eigen::Matrix obs;
cv::KeyPoint kp = pKFi->mvKeysRight[rightIndex];
obs << kp.pt.x, kp.pt.y;
ORB_SLAM3::EdgeSE3ProjectXYZToBody *e = new ORB_SLAM3::EdgeSE3ProjectXYZToBody();
e->setVertex(0, dynamic_cast(optimizer.vertex(id)));
e->setVertex(1, dynamic_cast(optimizer.vertex(pKFi->mnId)));
e->setMeasurement(obs);
const float &invSigma2 = pKFi->mvInvLevelSigma2[kp.octave];
e->setInformation(Eigen::Matrix2d::Identity()*invSigma2);
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(thHuberMono);
e->mTrl = Converter::toSE3Quat(pKFi->mTrl);
e->pCamera = pKFi->mpCamera2;
optimizer.addEdge(e);
vpEdgesBody.push_back(e);
vpEdgeKFBody.push_back(pKFi);
vpMapPointEdgeBody.push_back(pMP);
nEdges++;
}
}
}
}
}
num_edges = nEdges;
if(pbStopFlag)
if(*pbStopFlag)
return;
// 步骤9:开始优化
optimizer.initializeOptimization();
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
optimizer.optimize(5);
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
bool bDoMore= true;
if(pbStopFlag)
if(*pbStopFlag)
bDoMore = false;
if(bDoMore)
{
// Check inlier observations
int nMonoBadObs = 0;
// 步骤10:检测outlier,并设置下次不优化,上面展示了怎么存储的,i是共享的,第i个边是由第i个MP与第i个KF组成的
for(size_t i=0, iend=vpEdgesMono.size(); iisBad())
continue;
if(e->chi2()>5.991 || !e->isDepthPositive())
{
nMonoBadObs++;
}
}
int nBodyBadObs = 0;
for(size_t i=0, iend=vpEdgesBody.size(); iisBad())
continue;
if(e->chi2()>5.991 || !e->isDepthPositive())
{
nBodyBadObs++;
}
}
int nStereoBadObs = 0;
for(size_t i=0, iend=vpEdgesStereo.size(); iisBad())
continue;
if(e->chi2()>7.815 || !e->isDepthPositive())
{
nStereoBadObs++;
}
}
// Optimize again
// 步骤11:排除误差较大的outlier后再次优化,但这里没有去掉,相当于接着优化了10次,如果上面不去掉应该注释掉,浪费了计算时间
optimizer.initializeOptimization(0);
optimizer.optimize(10);
}
vector > vToErase;
vToErase.reserve(vpEdgesMono.size()+vpEdgesBody.size()+vpEdgesStereo.size());
// Check inlier observations
// 步骤12:在优化后重新计算误差,剔除连接误差比较大的关键帧和MapPoint
for(size_t i=0, iend=vpEdgesMono.size(); iisBad())
continue;
if(e->chi2()>5.991 || !e->isDepthPositive())
{
KeyFrame* pKFi = vpEdgeKFMono[i];
vToErase.push_back(make_pair(pKFi,pMP));
}
}
for(size_t i=0, iend=vpEdgesBody.size(); iisBad())
continue;
if(e->chi2()>5.991 || !e->isDepthPositive())
{
KeyFrame* pKFi = vpEdgeKFBody[i];
vToErase.push_back(make_pair(pKFi,pMP));
}
}
for(size_t i=0, iend=vpEdgesStereo.size(); iisBad())
continue;
if(e->chi2()>7.815 || !e->isDepthPositive())
{
KeyFrame* pKFi = vpEdgeKFStereo[i];
vToErase.push_back(make_pair(pKFi,pMP));
}
}
// Get Map Mutex
unique_lock lock(pMap->mMutexMapUpdate);
if(!vToErase.empty())
{
for(size_t i=0;iEraseMapPointMatch(pMPi);
pMPi->EraseObservation(pKFi);
}
}
// Recover optimized data
//Keyframes
bool bShowStats = false;
for(list::iterator lit=lLocalKeyFrames.begin(), lend=lLocalKeyFrames.end(); lit!=lend; lit++)
{
KeyFrame* pKFi = *lit;
g2o::VertexSE3Expmap* vSE3 = static_cast(optimizer.vertex(pKFi->mnId));
g2o::SE3Quat SE3quat = vSE3->estimate();
pKFi->SetPose(Converter::toCvMat(SE3quat));
}
//Points
for(list::iterator lit=lLocalMapPoints.begin(), lend=lLocalMapPoints.end(); lit!=lend; lit++)
{
MapPoint* pMP = *lit;
g2o::VertexSBAPointXYZ* vPoint = static_cast(optimizer.vertex(pMP->mnId+maxKFid+1));
pMP->SetWorldPos(Converter::toCvMat(vPoint->estimate()));
pMP->UpdateNormalAndDepth();
}
// TODO Check this changeindex
pMap->IncreaseChangeIndex();
}
void Optimizer::OptimizeEssentialGraph(Map* pMap, KeyFrame* pLoopKF, KeyFrame* pCurKF,
const LoopClosing::KeyFrameAndPose &NonCorrectedSim3,
const LoopClosing::KeyFrameAndPose &CorrectedSim3,
const map > &LoopConnections, const bool &bFixScale)
{
// Setup optimizer
// Step 1:构造优化器
g2o::SparseOptimizer optimizer;
optimizer.setVerbose(false);
// 指定线性方程求解器使用Eigen的块求解器
g2o::BlockSolver_7_3::LinearSolverType * linearSolver =
new g2o::LinearSolverEigen();
// 构造线性求解器
g2o::BlockSolver_7_3 * solver_ptr= new g2o::BlockSolver_7_3(linearSolver);
// 使用LM算法进行非线性迭代
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg(solver_ptr);
// 第一次迭代的初始lambda值,如未指定会自动计算一个合适的值
solver->setUserLambdaInit(1e-16);
optimizer.setAlgorithm(solver);
// 获取当前地图中的所有关键帧 和地图点
const vector vpKFs = pMap->GetAllKeyFrames();
const vector vpMPs = pMap->GetAllMapPoints();
// 最大关键帧id,用于添加顶点时使用
const unsigned int nMaxKFid = pMap->GetMaxKFid();
// 记录所有优化前关键帧的位姿,优先使用在闭环时通过Sim3传播调整过的Sim3位姿
vector > vScw(nMaxKFid+1);
// 记录所有关键帧经过本次本质图优化过的位姿
vector > vCorrectedSwc(nMaxKFid+1);
// 这个变量没有用
vector vpVertices(nMaxKFid+1);
vector vZvectors(nMaxKFid+1); // For debugging
Eigen::Vector3d z_vec;
z_vec << 0.0, 0.0, 1.0;
// 两个关键帧之间共视关系的权重(也就是共视点的数目,单目x1,双目/rgbd x2)的最小值
const int minFeat = 100; // MODIFICATION originally was set to 100
// Set KeyFrame vertices
// Step 2:将地图中所有关键帧的pose作为顶点添加到优化器
// 尽可能使用经过Sim3调整的位姿
// 遍历全局地图中的所有的关键帧
for(size_t i=0, iend=vpKFs.size(); iisBad())
continue;
g2o::VertexSim3Expmap* VSim3 = new g2o::VertexSim3Expmap();
// 关键帧在所有关键帧中的id,用来设置为顶点的id
const int nIDi = pKF->mnId;
LoopClosing::KeyFrameAndPose::const_iterator it = CorrectedSim3.find(pKF);
if(it!=CorrectedSim3.end())
{
// 如果该关键帧在闭环时通过Sim3传播调整过,优先用调整后的Sim3位姿
vScw[nIDi] = it->second;
VSim3->setEstimate(it->second);
}
else
{
// 如果该关键帧在闭环时没有通过Sim3传播调整过,用跟踪时的位姿
Eigen::Matrix Rcw = Converter::toMatrix3d(pKF->GetRotation());
Eigen::Matrix tcw = Converter::toVector3d(pKF->GetTranslation());
g2o::Sim3 Siw(Rcw,tcw,1.0); //尺度为1
vScw[nIDi] = Siw;
VSim3->setEstimate(Siw);
}
// 闭环匹配上的帧不进行位姿优化(认为是准确的,作为基准)
if(pKF->mnId==pMap->GetInitKFid())
VSim3->setFixed(true);
VSim3->setId(nIDi);
VSim3->setMarginalized(false);
// 和当前系统的传感器有关,如果是RGBD或者是双目,那么就不需要优化sim3的缩放系数,保持为1即可
VSim3->_fix_scale = bFixScale;
optimizer.addVertex(VSim3);
vZvectors[nIDi]=vScw[nIDi].rotation().toRotationMatrix()*z_vec; // For debugging
// 优化前的pose顶点,后面代码中没有使用
vpVertices[nIDi]=VSim3;
}
// 保存由于闭环后优化sim3而出现的新的关键帧和关键帧之间的连接关系,其中id比较小的关键帧在前,id比较大的关键帧在后
set > sInsertedEdges;
const Eigen::Matrix matLambda = Eigen::Matrix::Identity();
// Set Loop edges
// Step 3:添加第1种边:LoopConnections是闭环时因为地图点调整而出现的新关键帧连接关系
int count_loop = 0;
for(map >::const_iterator mit = LoopConnections.begin(), mend=LoopConnections.end(); mit!=mend; mit++)
{
KeyFrame* pKF = mit->first;
const long unsigned int nIDi = pKF->mnId;
// 和pKF 有连接关系的关键帧
const set &spConnections = mit->second;
const g2o::Sim3 Siw = vScw[nIDi];
const g2o::Sim3 Swi = Siw.inverse();
// 对于当前关键帧nIDi而言,遍历每一个新添加的关键帧nIDj链接关系
for(set::const_iterator sit=spConnections.begin(), send=spConnections.end(); sit!=send; sit++)
{
const long unsigned int nIDj = (*sit)->mnId;
// 条件1:至少有一个不是pCurKF或pLoopKF
// 条件2:共视程度太少(<100),不足以构成约束的边
if((nIDi!=pCurKF->mnId || nIDj!=pLoopKF->mnId) && pKF->GetWeight(*sit)setVertex(1, dynamic_cast(optimizer.vertex(nIDj)));
e->setVertex(0, dynamic_cast(optimizer.vertex(nIDi)));
// Sji内部是经过了Sim调整的观测
e->setMeasurement(Sji);
// 信息矩阵是单位阵,说明这类新增加的边对总误差的贡献也都是一样大的
e->information() = matLambda;
optimizer.addEdge(e);
count_loop++;
// 保证id小的在前,大的在后
sInsertedEdges.insert(make_pair(min(nIDi,nIDj),max(nIDi,nIDj)));
}
}
int count_spa_tree = 0;
int count_cov = 0;
int count_imu = 0;
int count_kf = 0;
// Set normal edges
for(size_t i=0, iend=vpKFs.size(); imnId;
g2o::Sim3 Swi;
LoopClosing::KeyFrameAndPose::const_iterator iti = NonCorrectedSim3.find(pKF);
if(iti!=NonCorrectedSim3.end())
Swi = (iti->second).inverse();
else
Swi = vScw[nIDi].inverse();
KeyFrame* pParentKF = pKF->GetParent();
// Spanning tree edge
if(pParentKF)
{
int nIDj = pParentKF->mnId;
g2o::Sim3 Sjw;
LoopClosing::KeyFrameAndPose::const_iterator itj = NonCorrectedSim3.find(pParentKF);
if(itj!=NonCorrectedSim3.end())
Sjw = itj->second;
else
Sjw = vScw[nIDj];
g2o::Sim3 Sji = Sjw * Swi;
g2o::EdgeSim3* e = new g2o::EdgeSim3();
e->setVertex(1, dynamic_cast(optimizer.vertex(nIDj)));
e->setVertex(0, dynamic_cast(optimizer.vertex(nIDi)));
e->setMeasurement(Sji);
count_kf++;
count_spa_tree++;
e->information() = matLambda;
optimizer.addEdge(e);
}
// Loop edges
const set sLoopEdges = pKF->GetLoopEdges();
for(set::const_iterator sit=sLoopEdges.begin(), send=sLoopEdges.end(); sit!=send; sit++)
{
KeyFrame* pLKF = *sit;
if(pLKF->mnIdmnId)
{
g2o::Sim3 Slw;
LoopClosing::KeyFrameAndPose::const_iterator itl = NonCorrectedSim3.find(pLKF);
if(itl!=NonCorrectedSim3.end())
Slw = itl->second;
else
Slw = vScw[pLKF->mnId];
g2o::Sim3 Sli = Slw * Swi;
g2o::EdgeSim3* el = new g2o::EdgeSim3();
el->setVertex(1, dynamic_cast(optimizer.vertex(pLKF->mnId)));
el->setVertex(0, dynamic_cast(optimizer.vertex(nIDi)));
el->setMeasurement(Sli);
el->information() = matLambda;
optimizer.addEdge(el);
count_kf++;
count_loop++;
}
}
// Covisibility graph edges
const vector vpConnectedKFs = pKF->GetCovisiblesByWeight(minFeat);
for(vector::const_iterator vit=vpConnectedKFs.begin(); vit!=vpConnectedKFs.end(); vit++)
{
KeyFrame* pKFn = *vit;
if(pKFn && pKFn!=pParentKF && !pKF->hasChild(pKFn) && !sLoopEdges.count(pKFn))
{
if(!pKFn->isBad() && pKFn->mnIdmnId)
{
if(sInsertedEdges.count(make_pair(min(pKF->mnId,pKFn->mnId),max(pKF->mnId,pKFn->mnId))))
continue;
g2o::Sim3 Snw;
LoopClosing::KeyFrameAndPose::const_iterator itn = NonCorrectedSim3.find(pKFn);
if(itn!=NonCorrectedSim3.end())
Snw = itn->second;
else
Snw = vScw[pKFn->mnId];
g2o::Sim3 Sni = Snw * Swi;
g2o::EdgeSim3* en = new g2o::EdgeSim3();
en->setVertex(1, dynamic_cast(optimizer.vertex(pKFn->mnId)));
en->setVertex(0, dynamic_cast(optimizer.vertex(nIDi)));
en->setMeasurement(Sni);
en->information() = matLambda;
optimizer.addEdge(en);
count_kf++;
count_cov++;
}
}
}
// Inertial edges if inertial
if(pKF->bImu && pKF->mPrevKF)
{
g2o::Sim3 Spw;
LoopClosing::KeyFrameAndPose::const_iterator itp = NonCorrectedSim3.find(pKF->mPrevKF);
if(itp!=NonCorrectedSim3.end())
Spw = itp->second;
else
Spw = vScw[pKF->mPrevKF->mnId];
g2o::Sim3 Spi = Spw * Swi;
g2o::EdgeSim3* ep = new g2o::EdgeSim3();
ep->setVertex(1, dynamic_cast(optimizer.vertex(pKF->mPrevKF->mnId)));
ep->setVertex(0, dynamic_cast(optimizer.vertex(nIDi)));
ep->setMeasurement(Spi);
ep->information() = matLambda;
optimizer.addEdge(ep);
count_kf++;
count_imu++;
}
}
// Optimize!
optimizer.initializeOptimization();
optimizer.computeActiveErrors();
float err0 = optimizer.activeRobustChi2();
optimizer.optimize(20);
optimizer.computeActiveErrors();
float errEnd = optimizer.activeRobustChi2();
unique_lock lock(pMap->mMutexMapUpdate);
// SE3 Pose Recovering. Sim3:[sR t;0 1] -> SE3:[R t/s;0 1]
for(size_t i=0;imnId;
g2o::VertexSim3Expmap* VSim3 = static_cast(optimizer.vertex(nIDi));
g2o::Sim3 CorrectedSiw = VSim3->estimate();
vCorrectedSwc[nIDi]=CorrectedSiw.inverse();
Eigen::Matrix3d eigR = CorrectedSiw.rotation().toRotationMatrix();
Eigen::Vector3d eigt = CorrectedSiw.translation();
double s = CorrectedSiw.scale();
eigt *=(1./s); //[R t/s;0 1]
cv::Mat Tiw = Converter::toCvSE3(eigR,eigt);
pKFi->SetPose(Tiw);
}
// Correct points. Transform to "non-optimized" reference keyframe pose and transform back with optimized pose
for(size_t i=0, iend=vpMPs.size(); iisBad())
continue;
int nIDr;
if(pMP->mnCorrectedByKF==pCurKF->mnId)
{
nIDr = pMP->mnCorrectedReference;
}
else
{
KeyFrame* pRefKF = pMP->GetReferenceKeyFrame();
nIDr = pRefKF->mnId;
}
g2o::Sim3 Srw = vScw[nIDr];
g2o::Sim3 correctedSwr = vCorrectedSwc[nIDr];
cv::Mat P3Dw = pMP->GetWorldPos();
Eigen::Matrix eigP3Dw = Converter::toVector3d(P3Dw);
Eigen::Matrix