有不足或者不正确不清晰的地方可以随时在评论区或者私我,敬请斧正!
class Optimizer
{
public:
void static BundleAdjustment(const std::vector<KeyFrame*> &vpKF, const std::vector<MapPoint*> &vpMP,
int nIterations = 5, bool *pbStopFlag=NULL, const unsigned long nLoopKF=0,
const bool bRobust = true);
void static GlobalBundleAdjustemnt(Map* pMap, int nIterations=5, bool *pbStopFlag=NULL,
const unsigned long nLoopKF=0, const bool bRobust = true);
void static LocalBundleAdjustment(KeyFrame* pKF, bool *pbStopFlag, Map *pMap);
int static PoseOptimization(Frame* pFrame);
// if bFixScale is true, 6DoF optimization (stereo,rgbd), 7DoF otherwise (mono)
void static OptimizeEssentialGraph(Map* pMap, KeyFrame* pLoopKF, KeyFrame* pCurKF,
const LoopClosing::KeyFrameAndPose &NonCorrectedSim3,
const LoopClosing::KeyFrameAndPose &CorrectedSim3,
const map<KeyFrame *, set<KeyFrame *> > &LoopConnections,
const bool &bFixScale);
// if bFixScale is true, optimize SE3 (stereo,rgbd), Sim3 otherwise (mono)
static int OptimizeSim3(KeyFrame* pKF1, KeyFrame* pKF2, std::vector<MapPoint *> &vpMatches1,
g2o::Sim3 &g2oS12, const float th2, const bool bFixScale);
};
主要在slam/ORB_SLAM2/Thirdparty/g2o/g2o/types
EdgeSE3ProjectXYZOnlyPose:一元边以仅优化相机姿势
详见slam/ORB_SLAM2/Thirdparty/g2o/g2o/types/types_six_dof_expmap.cpp
以下来自slam/ORB_SLAM2/Thirdparty/g2o/g2o/types/types_six_dof_expmap.h
class EdgeSE3ProjectXYZOnlyPose: public BaseUnaryEdge<2, Vector2d, VertexSE3Expmap>{
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
EdgeSE3ProjectXYZOnlyPose(){}
bool read(std::istream& is);
bool write(std::ostream& os) const;
void computeError() {
const VertexSE3Expmap* v1 = static_cast<const VertexSE3Expmap*>(_vertices[0]);
Vector2d obs(_measurement);
_error = obs-cam_project(v1->estimate().map(Xw));
}
bool isDepthPositive() {
const VertexSE3Expmap* v1 = static_cast<const VertexSE3Expmap*>(_vertices[0]);
return (v1->estimate().map(Xw))(2)>0.0;
}
virtual void linearizeOplus();
Vector2d cam_project(const Vector3d & trans_xyz) const;
Vector3d Xw;
double fx, fy, cx, cy;
};
使用到的g2o顶点包括:
VertexSE3Expmap():SE(3)位姿
VertexSim3Expmap():Sim(3)位姿
VertexSBAPointXYZ():地图点坐标
使用到的g2o边包括:
EdgeSE3ProjectXYZ():BA中的重投影误差(3D-2D(u,v)误差),将地图点投影到相机坐标系下的相机平面。
EdgeSE3ProjectXYZOnlyPose():PoseEstimation中的重投影误差,将地图点投影到相机坐标系下的相机平面。优化变量只有pose,地图点位置固定,是一边元,双目中使用的是EdgeStereoSE3ProjectXYZOnlyPoze()。
EdgeSim3():Sim3之间的相对误差。优化变量只有Sim3表示的pose,用于OptimizeEssentialGraph。
EdgeSim3ProjectXYZ():重投影误差。优化变量Sim3位姿与地图点,用于闭环检测中的OptimizeSim3。
【参考文档】:ORB-SLAM(十二)优化
《slam14讲》p255
【参考文档】非线性优化库g2o使用教程,探索一些常见的用法,以及信息矩阵、鲁棒核函数对于优化的结果的影响

由于右上角那个离谱的点,导致优化时将整个函数被拉偏了。
那么怎么解决这种问题呢?g2o中提供了鲁棒核函数来抑制某些误差特别大的点,拉偏整个优化结果。
使用方法如下:
//构造一个Huber鲁棒核函数
g2o::RobustKernelHuber* robust_kernel_huber = new g2o::RobustKernelHuber;
robust_kernel_huber->setDelta(0.3);
//设置delta的大小。注意这个要根据实际的应用场景去尝试,然后选择合适的大小
e->setRobustKernel(robust_kernel_huber);//向边中添加鲁棒核函数
g2o中提供了多种鲁棒核函数,你可以根据自己的需要进行选择。
加入鲁棒核函数之后,结果明显好转。
//todo!!
【参考文档】非线性优化库g2o使用教程,探索一些常见的用法,以及信息矩阵、鲁棒核函数对于优化的结果的影响
在一次优化中,对于某一次测量,我们有十足的把握,它非常的准确,所以优化时我们希望对于这次测量给予更高的权重。
假设我们认为右上角那个异常点是一个比较正确的点(只是假设),我们希望拟合的曲线尽量往这个点偏移。那么我们就这可以设置这次测量边的权重更大。
代码如下:
e->setInformation(Eigen::Matrix<double, 1, 1>::Identity() * 10);
因为测量值的维度为1,所以信息矩阵也为1。如果我们把每一条边的信息矩阵都设置为一样,那么在优化时将认为所有边的优化权重是一样的,将不会对某一条边执行过多的优化!
对于那个异常点设置权重为别的点的10倍,则曲线会往右上角那个点靠。最终的结果如下图:

一般情况下,信息矩阵和鲁棒核函数都会一起使用!
一般g2o默认只处理level为0的边,可以将,不进行优化的边的level设为1
利用H稀疏性加速计算的方法:schur消元,在SLAM研究中也被称为Marginalizeation(边缘化)
本质:求解位姿部分的增量方程,然后把解得的delta xc 代入到原方程,然后求解delta xp
【参考文档】《slam14讲》p253
(1)单元边
e->setVertex(0, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(0)));
(2)双元边
如果是双元边,则0对应三维点顶点,1对应位姿顶点
e->setVertex(0, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(id)));
e->setVertex(1, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(pKFi->mnId)));
设置测量量
vSE3->setEstimate(Converter::toSE3Quat(pKFi->GetPose()));
需要先将当前的坐标转换为四元数
orb-slam2中转换函数toSE3Quat
g2o::SE3Quat Converter::toSE3Quat(const cv::Mat &cvT)
{
Eigen::Matrix<double,3,3> R;
R << cvT.at<float>(0,0), cvT.at<float>(0,1), cvT.at<float>(0,2),
cvT.at<float>(1,0), cvT.at<float>(1,1), cvT.at<float>(1,2),
cvT.at<float>(2,0), cvT.at<float>(2,1), cvT.at<float>(2,2);
Eigen::Matrix<double,3,1> t(cvT.at<float>(0,3), cvT.at<float>(1,3), cvT.at<float>(2,3));
return g2o::SE3Quat(R,t);
}
至于SE3Quat有这两个重载方式:
SE3Quat(const Matrix3d& R, const Vector3d& t)
SE3Quat(const Quaterniond& q, const Vector3d& t)
从而最终转化为四元数
使用示例:
vSE3->setId(pKFi->mnId);
vSE3->setFixed(pKFi->mnId==0);//只固定第一帧
只优化当前帧pose,地图点固定
int Optimizer::PoseOptimization(Frame *pFrame)//只优化当前帧pose,地图点固定
{
g2o::SparseOptimizer optimizer;
g2o::BlockSolver_6_3::LinearSolverType * linearSolver;
//pose维度为6(3个R,3个t),landmark维度为3
linearSolver = new g2o::LinearSolverDense<g2o::BlockSolver_6_3::PoseMatrixType>();
g2o::BlockSolver_6_3 * solver_ptr = new g2o::BlockSolver_6_3(linearSolver);
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg(solver_ptr);
optimizer.setAlgorithm(solver);
int nInitialCorrespondences=0;//当前帧对应到的地图点的数量
// 顶点:李代数位姿 Set Frame vertex
g2o::VertexSE3Expmap * vSE3 = new g2o::VertexSE3Expmap();
vSE3->setEstimate(Converter::toSE3Quat(pFrame->mTcw));
vSE3->setId(0);
vSE3->setFixed(false);
optimizer.addVertex(vSE3);
// Set MapPoint vertices
const int N = pFrame->N;
//单目
vector<g2o::EdgeSE3ProjectXYZOnlyPose*> vpEdgesMono;
vector<size_t> vnIndexEdgeMono;
vpEdgesMono.reserve(N);//传入边
vnIndexEdgeMono.reserve(N);//传入边对应的索引
//双目
vector<g2o::EdgeStereoSE3ProjectXYZOnlyPose*> vpEdgesStereo;
vector<size_t> vnIndexEdgeStereo;
vpEdgesStereo.reserve(N);
vnIndexEdgeStereo.reserve(N);
//定义自由度为2的卡方分布
const float deltaMono = sqrt(5.991);
const float deltaStereo = sqrt(7.815);
{
unique_lock<mutex> lock(MapPoint::mGlobalMutex);
for(int i=0; i<N; i++)
{
MapPoint* pMP = pFrame->mvpMapPoints[i];//对于传入的帧中的地图点
if(pMP)//物理判断
{
// Monocular observation
if(pFrame->mvuRight[i]<0)//单目相机没有右眼
{
nInitialCorrespondences++;
pFrame->mvbOutlier[i] = false;
Eigen::Matrix<double,2,1> obs;//定义一个观测量 (u,v)
const cv::KeyPoint &kpUn = pFrame->mvKeysUn[i];
obs << kpUn.pt.x, kpUn.pt.y;//将未矫正的点传入obs
g2o::EdgeSE3ProjectXYZOnlyPose* e = new g2o::EdgeSE3ProjectXYZOnlyPose();//定义一条边
e->setVertex(0, dynamic_cast<g2o::OptimizableGraph::Vertex*>(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->fx = pFrame->fx;
e->fy = pFrame->fy;
e->cx = pFrame->cx;nInitialCorrespondences
e->cy = pFrame->cy;
cv::Mat Xw = pMP->GetWorldPos();//地图点的世界坐标
e->Xw[0] = Xw.at<float>(0);
e->Xw[1] = Xw.at<float>(1);
e->Xw[2] = Xw.at<float>(2);
optimizer.addEdge(e);
vpEdgesMono.push_back(e);
vnIndexEdgeMono.push_back(i);
}
else //双目 Stereo observation
{
nInitialCorrespondences++;
pFrame->mvbOutlier[i] = false;
//SET EDGE
Eigen::Matrix<double,3,1> 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<g2o::OptimizableGraph::Vertex*>(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<float>(0);
e->Xw[1] = Xw.at<float>(1);
e->Xw[2] = Xw.at<float>(2);
optimizer.addEdge(e);
vpEdgesStereo.push_back(e);
vnIndexEdgeStereo.push_back(i);
}
}
}
}
if(nInitialCorrespondences<3)
return 0;
delta
// 我们执行了4次优化,每次优化后,我们将观察结果分类为内值/离群值
//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.
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};
int nBad=0;
for(size_t it=0; it<4; it++)
{
vSE3->setEstimate(Converter::toSE3Quat(pFrame->mTcw));//更新估计值
optimizer.initializeOptimization(0);
optimizer.optimize(its[it]);//优化10次
nBad=0;
//单目
for(size_t i=0, iend=vpEdgesMono.size(); i<iend; i++)
{
g2o::EdgeSE3ProjectXYZOnlyPose* e = vpEdgesMono[i];//经过外循环后获得优化后的边
const size_t idx = vnIndexEdgeMono[i];//获得优化后的边的索引
if(pFrame->mvbOutlier[idx])
{
e->computeError();
}
const float chi2 = e->chi2();//卡方统计量
if(chi2>chi2Mono[it])
{
pFrame->mvbOutlier[idx]=true;//置为离群点
e->setLevel(1);//将离群点的level设为1,不进行优化
nBad++;
}
else
{
pFrame->mvbOutlier[idx]=false;
e->setLevel(0);// 一般g2o默认只处理level为0的边
}
if(it==2)//优化过一次之后离群点不再优化,不再需要鲁棒核
e->setRobustKernel(0);
}
//双目
for(size_t i=0, iend=vpEdgesStereo.size(); i<iend; i++)
{
g2o::EdgeStereoSE3ProjectXYZOnlyPose* e = vpEdgesStereo[i];
const size_t idx = vnIndexEdgeStereo[i];
if(pFrame->mvbOutlier[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
g2o::VertexSE3Expmap* vSE3_recov = static_cast<g2o::VertexSE3Expmap*>(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::BundleAdjustment(const vector<KeyFrame *> &vpKFs, const vector<MapPoint *> &vpMP,
int nIterations, bool* pbStopFlag, const unsigned long nLoopKF, const bool bRobust)
{
vector<bool> vbNotIncludedMP;
vbNotIncludedMP.resize(vpMP.size());
//-----------经典套路--------------------------
g2o::SparseOptimizer optimizer;
g2o::BlockSolver_6_3::LinearSolverType * linearSolver;
linearSolver = new g2o::LinearSolverEigen<g2o::BlockSolver_6_3::PoseMatrixType>();
g2o::BlockSolver_6_3 * solver_ptr = new g2o::BlockSolver_6_3(linearSolver);
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg(solver_ptr);
optimizer.setAlgorithm(solver);
//-----------经典套路--------------------------
//setForceStopFlag:设置在每次迭代时检查的变量,以强制用户停止。当变量为true时,迭代退出
if(pbStopFlag)
optimizer.setForceStopFlag(pbStopFlag);//停止优化标志位
long unsigned int maxKFid = 0;
// Set KeyFrame vertices
for(size_t i=0; i<vpKFs.size(); i++)
{
KeyFrame* pKF = vpKFs[i];
if(pKF->isBad())
continue;
//---------为每一个关键帧的位姿设置顶点----------------------
g2o::VertexSE3Expmap * vSE3 = new g2o::VertexSE3Expmap();
vSE3->setEstimate(Converter::toSE3Quat(pKF->GetPose()));//顶点设置为位姿
vSE3->setId(pKF->mnId);
vSE3->setFixed(pKF->mnId==0);
optimizer.addVertex(vSE3);
//---------为每一个关键帧的位姿设置顶点----------------------
if(pKF->mnId>maxKFid)
maxKFid=pKF->mnId;
}
//自由度为2(x,y)的卡方分布
const float thHuber2D = sqrt(5.99);//95%
//自由度为3(x,y,deep)的卡方分布
const float thHuber3D = sqrt(7.815);//95%
// Set MapPoint vertices
for(size_t i=0; i<vpMP.size(); i++)
{
MapPoint* pMP = vpMP[i];
if(pMP->isBad())
continue;
g2o::VertexSBAPointXYZ* vPoint = new g2o::VertexSBAPointXYZ();//landmark点
vPoint->setEstimate(Converter::toVector3d(pMP->GetWorldPos()));
//mnId给了位姿顶点
const int id = pMP->mnId+maxKFid+1;
vPoint->setId(id);
vPoint->setMarginalized(true);
optimizer.addVertex(vPoint);
const map<KeyFrame*,size_t> observations = pMP->GetObservations();
int nEdges = 0;
//SET EDGES
for(map<KeyFrame*,size_t>::const_iterator mit=observations.begin(); mit!=observations.end(); mit++)
{
KeyFrame* pKF = mit->first;
if(pKF->isBad() || pKF->mnId>maxKFid)
continue;
nEdges++;
const cv::KeyPoint &kpUn = pKF->mvKeysUn[mit->second];
if(pKF->mvuRight[mit->second]<0)//单目
{
Eigen::Matrix<double,2,1> obs;
obs << kpUn.pt.x, kpUn.pt.y;
//---------为每一个共视关系设置边----------------------
g2o::EdgeSE3ProjectXYZ* e = new g2o::EdgeSE3ProjectXYZ();
e->setVertex(0, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(id)));
e->setVertex(1, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(pKF->mnId)));
e->setMeasurement(obs);
const float &invSigma2 = pKF->mvInvLevelSigma2[kpUn.octave];
e->setInformation(Eigen::Matrix2d::Identity()*invSigma2);
//---------为每一个共视关系设置边----------------------
if(bRobust)
{
g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;
e->setRobustKernel(rk);
rk->setDelta(thHuber2D);
}
e->fx = pKF->fx;
e->fy = pKF->fy;
e->cx = pKF->cx;
e->cy = pKF->cy;
optimizer.addEdge(e);
}
else//双目
{
Eigen::Matrix<double,3,1> obs;
const float kp_ur = pKF->mvuRight[mit->second];
obs << kpUn.pt.x, kpUn.pt.y, kp_ur;
g2o::EdgeStereoSE3ProjectXYZ* e = new g2o::EdgeStereoSE3ProjectXYZ();
e->setVertex(0, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(id)));
e->setVertex(1, dynamic_cast<g2o::OptimizableGraph::Vertex*>(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);
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);
}
}
//如果图中的边数为0,则删除图中的结点
if(nEdges==0)
{
optimizer.removeVertex(vPoint);
vbNotIncludedMP[i]=true;
}
else
{
vbNotIncludedMP[i]=false;
}
}
// Optimize!
optimizer.initializeOptimization();
optimizer.optimize(nIterations);
// Recover optimized data
//Keyframes
for(size_t i=0; i<vpKFs.size(); i++)
{
KeyFrame* pKF = vpKFs[i];
if(pKF->isBad())
continue;
g2o::VertexSE3Expmap* vSE3 = static_cast<g2o::VertexSE3Expmap*>(optimizer.vertex(pKF->mnId));
g2o::SE3Quat SE3quat = vSE3->estimate();
if(nLoopKF==0)//不发生回环,nLoopKF:回环帧数量
{
//无回环就默认当前位姿
pKF->SetPose(Converter::toCvMat(SE3quat));
}
else
{//若出现回环要提取估计值进行回环检测在更新位姿
pKF->mTcwGBA.create(4,4,CV_32F);
Converter::toCvMat(SE3quat).copyTo(pKF->mTcwGBA);
pKF->mnBAGlobalForKF = nLoopKF;
}
}
//Points
for(size_t i=0; i<vpMP.size(); i++)
{
if(vbNotIncludedMP[i])
continue;
MapPoint* pMP = vpMP[i];
if(pMP->isBad())
continue;
g2o::VertexSBAPointXYZ* vPoint = static_cast<g2o::VertexSBAPointXYZ*>(optimizer.vertex(pMP->mnId+maxKFid+1));
if(nLoopKF==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 = nLoopKF;
}
}
}
用于LocalMapping线程中剔除关键帧之前的局部地图优化
LocalBundleAdjustment数据关系有点乱,因此特别说明一下
| 数据结构 | 来源 | 条件 |
|---|---|---|
| vNeighKFs | pKF->GetVectorCovisibleKeyFrames() | |
| lLocalKeyFrames | vNeighKFs[i](当前帧的共视关键帧) | 不是坏点 |
| lLocalMapPoints | 与正常的的共视关键帧匹配的地图点 | (1)不是坏点(2)mnBALocalForKF未被当前帧固定(后面就被固定了) |
| lFixedCameras | 地图点的所有共视关系中的关键帧 | mnBALocalForKF,mnBAFixedForKF均不属于当前帧(后面就属于了) |
void Optimizer::LocalBundleAdjustment(KeyFrame *pKF, bool* pbStopFlag, Map* pMap)
{
// Local KeyFrames: First Breath Search from Current Keyframe
list<KeyFrame*> lLocalKeyFrames;
lLocalKeyFrames.push_back(pKF);
pKF->mnBALocalForKF = pKF->mnId;//对于每一个正常的KF或地图点都需要维护一个mnBALocalForKF
//mnBALocalForKF:用于局部建图
const vector<KeyFrame*> vNeighKFs = pKF->GetVectorCovisibleKeyFrames();//获得当前帧的共视关键帧
//遍历当前帧的共视关键帧 lLocalKeyFrames存放不是坏点的pKFi
for(int i=0, iend=vNeighKFs.size(); i<iend; i++)
{
KeyFrame* pKFi = vNeighKFs[i];
pKFi->mnBALocalForKF = pKF->mnId;
if(!pKFi->isBad())
lLocalKeyFrames.push_back(pKFi);
}
// 在局部关键帧中看到的局部地图点 Local MapPoints seen in Local KeyFrames
list<MapPoint*> lLocalMapPoints;//lLocalMapPoints存放不是坏点的地图点
//遍历当前帧正常的的共视关键帧寻找地图点
for(list<KeyFrame*>::iterator lit=lLocalKeyFrames.begin() , lend=lLocalKeyFrames.end(); lit!=lend; lit++)
{
vector<MapPoint*> vpMPs = (*lit)->GetMapPointMatches();//与正常的的共视关键帧匹配的地图点
for(vector<MapPoint*>::iterator vit=vpMPs.begin(), vend=vpMPs.end(); vit!=vend; vit++)
{
MapPoint* pMP = *vit;
if(pMP)
if(!pMP->isBad())
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
// 固定关键帧。可以看到局部地图点但不是局部关键帧的关键帧
list<KeyFrame*> lFixedCameras; //存放了正常的地图点
for(list<MapPoint*>::iterator lit=lLocalMapPoints.begin(), lend=lLocalMapPoints.end(); lit!=lend; lit++)
{
map<KeyFrame*,size_t> observations = (*lit)->GetObservations();//对 当前地图点 找它的共视关系
//遍历当前地图点的所有共视关系 寻找不是当前帧的共视关键帧
for(map<KeyFrame*,size_t>::iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++)
{
KeyFrame* pKFi = mit->first;
//暂时不是当前帧的共视关键帧 :在遍历当前帧的共视关键帧时已经将pKFi->mnBALocalForKF = pKF->mnId;
if(pKFi->mnBALocalForKF!=pKF->mnId && pKFi->mnBAFixedForKF!=pKF->mnId)
{ //该关键帧尚未被固定为当前关键帧的
pKFi->mnBAFixedForKF=pKF->mnId;
if(!pKFi->isBad())
lFixedCameras.push_back(pKFi);
}
}
}
//-----------经典套路--------------------------
// Setup optimizer
g2o::SparseOptimizer optimizer;
g2o::BlockSolver_6_3::LinearSolverType * linearSolver;
linearSolver = new g2o::LinearSolverEigen<g2o::BlockSolver_6_3::PoseMatrixType>();
g2o::BlockSolver_6_3 * solver_ptr = new g2o::BlockSolver_6_3(linearSolver);
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg(solver_ptr);
optimizer.setAlgorithm(solver);
//-----------经典套路--------------------------
if(pbStopFlag)//setForceStopFlag:设置在每次迭代时检查的变量,以强制用户停止。当变量为true时,迭代退出
optimizer.setForceStopFlag(pbStopFlag);//停止优化标志位
unsigned long maxKFid = 0;
//------------为lLocalKeyFrames(当前关键帧的正常的共视关键帧)建立位姿顶点--------------------------------------------------
// Set Local KeyFrame vertices
for(list<KeyFrame*>::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==0);//只固定第一帧
optimizer.addVertex(vSE3);
if(pKFi->mnId>maxKFid)
maxKFid=pKFi->mnId;
}
//------------为Fixed KeyFrame建立位姿顶点--------------------------------------------------
// Set Fixed KeyFrame vertices
for(list<KeyFrame*>::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
const int nExpectedSize = (lLocalKeyFrames.size()+lFixedCameras.size())*lLocalMapPoints.size();
vector<g2o::EdgeSE3ProjectXYZ*> vpEdgesMono;
vpEdgesMono.reserve(nExpectedSize);
vector<KeyFrame*> vpEdgeKFMono;
vpEdgeKFMono.reserve(nExpectedSize);
vector<MapPoint*> vpMapPointEdgeMono;
vpMapPointEdgeMono.reserve(nExpectedSize);
vector<g2o::EdgeStereoSE3ProjectXYZ*> vpEdgesStereo;
vpEdgesStereo.reserve(nExpectedSize);
vector<KeyFrame*> vpEdgeKFStereo;
vpEdgeKFStereo.reserve(nExpectedSize);
vector<MapPoint*> vpMapPointEdgeStereo;
vpMapPointEdgeStereo.reserve(nExpectedSize);
//自由度为2(x,y)的卡方分布
const float thHuberMono = sqrt(5.991);
//自由度为3(x,y,deep)的卡方分布
const float thHuberStereo = sqrt(7.815);
for(list<MapPoint*>::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()));
int id = pMP->mnId+maxKFid+1;
vPoint->setId(id);
vPoint->setMarginalized(true);
optimizer.addVertex(vPoint);
const map<KeyFrame*,size_t> observations = pMP->GetObservations();
//Set edges
for(map<KeyFrame*,size_t>::const_iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++)
{
KeyFrame* pKFi = mit->first;
if(!pKFi->isBad())
{
const cv::KeyPoint &kpUn = pKFi->mvKeysUn[mit->second];
// 单目 Monocular observation
if(pKFi->mvuRight[mit->second]<0)
{
Eigen::Matrix<double,2,1> obs;
obs << kpUn.pt.x, kpUn.pt.y;
g2o::EdgeSE3ProjectXYZ* e = new g2o::EdgeSE3ProjectXYZ();
//此时顶点应该代表MapPoint
e->setVertex(0, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(id)));
//此时顶点应该代表KF
e->setVertex(1, dynamic_cast<g2o::OptimizableGraph::Vertex*>(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->fx = pKFi->fx;
e->fy = pKFi->fy;
e->cx = pKFi->cx;
e->cy = pKFi->cy;
optimizer.addEdge(e);
vpEdgesMono.push_back(e);
vpEdgeKFMono.push_back(pKFi);
vpMapPointEdgeMono.push_back(pMP);
}
else // Stereo observation
{
Eigen::Matrix<double,3,1> obs;
const float kp_ur = pKFi->mvuRight[mit->second];
obs << kpUn.pt.x, kpUn.pt.y, kp_ur;
g2o::EdgeStereoSE3ProjectXYZ* e = new g2o::EdgeStereoSE3ProjectXYZ();
e->setVertex(0, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(id)));
e->setVertex(1, dynamic_cast<g2o::OptimizableGraph::Vertex*>(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);
}
}
}
}
if(pbStopFlag)
if(*pbStopFlag)
return;
optimizer.initializeOptimization();
optimizer.optimize(5);//优化5次
bool bDoMore= true;
if(pbStopFlag)
if(*pbStopFlag)
bDoMore = false;
if(bDoMore)//进一步进行优化
{
// Check inlier observations
for(size_t i=0, iend=vpEdgesMono.size(); i<iend;i++)
{
g2o::EdgeSE3ProjectXYZ* e = vpEdgesMono[i];
MapPoint* pMP = vpMapPointEdgeMono[i];
if(pMP->isBad())
continue;
if(e->chi2()>5.991 || !e->isDepthPositive())
{
e->setLevel(1);//不进行优化
}
e->setRobustKernel(0);
}
for(size_t i=0, iend=vpEdgesStereo.size(); i<iend;i++)
{
g2o::EdgeStereoSE3ProjectXYZ* e = vpEdgesStereo[i];
MapPoint* pMP = vpMapPointEdgeStereo[i];
if(pMP->isBad())
continue;
if(e->chi2()>7.815 || !e->isDepthPositive())
{
e->setLevel(1);
}
e->setRobustKernel(0);
}
// Optimize again without the outliers
optimizer.initializeOptimization(0);
optimizer.optimize(10);
}//if(bDoMore)
vector<pair<KeyFrame*,MapPoint*> > vToErase;
vToErase.reserve(vpEdgesMono.size()+vpEdgesStereo.size());
// Check inlier observations
for(size_t i=0, iend=vpEdgesMono.size(); i<iend;i++)
{
g2o::EdgeSE3ProjectXYZ* e = vpEdgesMono[i];
MapPoint* pMP = vpMapPointEdgeMono[i];
if(pMP->isBad())
continue;
if(e->chi2()>5.991 || !e->isDepthPositive())
{
KeyFrame* pKFi = vpEdgeKFMono[i];
//不符合条件的先放入vToErase,之后再进一步消除
vToErase.push_back(make_pair(pKFi,pMP));
}
}
for(size_t i=0, iend=vpEdgesStereo.size(); i<iend;i++)
{
g2o::EdgeStereoSE3ProjectXYZ* e = vpEdgesStereo[i];
MapPoint* pMP = vpMapPointEdgeStereo[i];
if(pMP->isBad())
continue;
if(e->chi2()>7.815 || !e->isDepthPositive())
{
//为什么不先置为坏(尽管没有这个操作)
KeyFrame* pKFi = vpEdgeKFStereo[i];
vToErase.push_back(make_pair(pKFi,pMP));
}
}
// 得到锁才能正式消除掉Get Map Mutex
unique_lock<mutex> lock(pMap->mMutexMapUpdate);
if(!vToErase.empty())
{
for(size_t i=0;i<vToErase.size();i++)
{
KeyFrame* pKFi = vToErase[i].first;
MapPoint* pMPi = vToErase[i].second;
pKFi->EraseMapPointMatch(pMPi);
pMPi->EraseObservation(pKFi);
}
}
// 恢复优化后的Recover optimized data
//Keyframes
for(list<KeyFrame*>::iterator lit=lLocalKeyFrames.begin(), lend=lLocalKeyFrames.end(); lit!=lend; lit++)
{
KeyFrame* pKF = *lit;
g2o::VertexSE3Expmap* vSE3 = static_cast<g2o::VertexSE3Expmap*>(optimizer.vertex(pKF->mnId));
g2o::SE3Quat SE3quat = vSE3->estimate();
pKF->SetPose(Converter::toCvMat(SE3quat));
}
//Points
for(list<MapPoint*>::iterator lit=lLocalMapPoints.begin(), lend=lLocalMapPoints.end(); lit!=lend; lit++)
{
MapPoint* pMP = *lit;
g2o::VertexSBAPointXYZ* vPoint = static_cast<g2o::VertexSBAPointXYZ*>(optimizer.vertex(pMP->mnId+maxKFid+1));
pMP->SetWorldPos(Converter::toCvMat(vPoint->estimate()));
pMP->UpdateNormalAndDepth();
}
}
全局BA优化的代码是最简单的,调用了BundleAdjustment()进行优化
void Optimizer::GlobalBundleAdjustemnt(Map* pMap, int nIterations, bool* pbStopFlag, const unsigned long nLoopKF, const bool bRobust)
{
vector<KeyFrame*> vpKFs = pMap->GetAllKeyFrames();
vector<MapPoint*> vpMP = pMap->GetAllMapPoints();
BundleAdjustment(vpKFs,vpMP,nIterations,pbStopFlag, nLoopKF, bRobust);
}
【参考文档】https://www.cnblogs.com/shang-slam/p/6483725.html