• orb-slam2 从单目开始的简单学习(7):Optimizer


    前言

    有不足或者不正确不清晰的地方可以随时在评论区或者私我,敬请斧正!

    1. 整体介绍

    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);
    };
    

    1.1 数据类型

    主要在slam/ORB_SLAM2/Thirdparty/g2o/g2o/types在这里插入图片描述

    1.3 浅浅了解一下G2O

    1.3.1 常见的数据结构

    1.3.1.1 EdgeStereoSE3ProjectXYZOnlyPose

    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;
    };
    
    
    1.3.1.2 VertexSBAPointXYZ

    使用到的g2o顶点包括:

    1. VertexSE3Expmap():SE(3)位姿

    2. VertexSim3Expmap():Sim(3)位姿

    3. VertexSBAPointXYZ():地图点坐标

    使用到的g2o边包括:

    1. EdgeSE3ProjectXYZ():BA中的重投影误差(3D-2D(u,v)误差),将地图点投影到相机坐标系下的相机平面。

    2. EdgeSE3ProjectXYZOnlyPose():PoseEstimation中的重投影误差,将地图点投影到相机坐标系下的相机平面。优化变量只有pose,地图点位置固定,是一边元,双目中使用的是EdgeStereoSE3ProjectXYZOnlyPoze()。

    3. EdgeSim3():Sim3之间的相对误差。优化变量只有Sim3表示的pose,用于OptimizeEssentialGraph。

    4. EdgeSim3ProjectXYZ():重投影误差。优化变量Sim3位姿与地图点,用于闭环检测中的OptimizeSim3。

    【参考文档】:ORB-SLAM(十二)优化

    1.3.2 常见的边的函数

    1.3.2.1 设置鲁棒核函数setRobustKernel

    《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中提供了多种鲁棒核函数,你可以根据自己的需要进行选择。
    加入鲁棒核函数之后,结果明显好转。

    1.3.2.2 其他鲁棒核函数

    //todo!!

    1.3.2.3 设置信息矩阵setInformation

    【参考文档】非线性优化库g2o使用教程,探索一些常见的用法,以及信息矩阵、鲁棒核函数对于优化的结果的影响

    在一次优化中,对于某一次测量,我们有十足的把握,它非常的准确,所以优化时我们希望对于这次测量给予更高的权重。
    假设我们认为右上角那个异常点是一个比较正确的点(只是假设),我们希望拟合的曲线尽量往这个点偏移。那么我们就这可以设置这次测量边的权重更大。

    代码如下:

    e->setInformation(Eigen::Matrix<double, 1, 1>::Identity() * 10);
    

    因为测量值的维度为1,所以信息矩阵也为1。如果我们把每一条边的信息矩阵都设置为一样,那么在优化时将认为所有边的优化权重是一样的,将不会对某一条边执行过多的优化!

    对于那个异常点设置权重为别的点的10倍,则曲线会往右上角那个点靠。最终的结果如下图:
    在这里插入图片描述

    一般情况下,信息矩阵和鲁棒核函数都会一起使用!

    1.3.2.4 设置优化边setLevel

    一般g2o默认只处理level为0的边,可以将,不进行优化的边的level设为1

    1.3.3.4 设置边缘化 setMarginalized

    利用H稀疏性加速计算的方法:schur消元,在SLAM研究中也被称为Marginalizeation(边缘化)
    本质:求解位姿部分的增量方程,然后把解得的delta xc 代入到原方程,然后求解delta xp
    【参考文档】《slam14讲》p253

    1.3.3.5 设置顶点setVertex

    (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)));
    

    1.3.3 常见的顶点的函数

    1.3.3.1setEstimate

    设置测量量

    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)
    从而最终转化为四元数
    
    1.3.3.2 设置顶点ID setId

    使用示例:

    vSE3->setId(pKFi->mnId);
    
    1.3.3.3 固定顶点setFixed
    vSE3->setFixed(pKFi->mnId==0);//只固定第一帧
    

    2. PoseOptimization

    只优化当前帧pose,地图点固定

    2.1 PoseOptimization完整代码及注释

    int Optimizer::PoseOptimization(Frame *pFrame)//只优化当前帧pose,地图点固定
    {
        g2o::SparseOptimizer optimizer;
        g2o::BlockSolver_6_3::LinearSolverType * linearSolver;
        		//pose维度为63个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;
    }
    
    

    3. BundleAdjustment

    3.1 BundleAdjustment完整代码及注释

    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;
            }
        }
    
    }
    

    4.LocalBundleAdjustment

    用于LocalMapping线程中剔除关键帧之前的局部地图优化

    4.1 LocalBundleAdjustment中数据关系

    LocalBundleAdjustment数据关系有点乱,因此特别说明一下

    数据结构来源条件
    vNeighKFspKF->GetVectorCovisibleKeyFrames()
    lLocalKeyFramesvNeighKFs[i](当前帧的共视关键帧)不是坏点
    lLocalMapPoints与正常的的共视关键帧匹配的地图点(1)不是坏点(2)mnBALocalForKF未被当前帧固定(后面就被固定了)
    lFixedCameras地图点的所有共视关系中的关键帧mnBALocalForKF,mnBAFixedForKF均不属于当前帧(后面就属于了)

    4.2 LocalBundleAdjustment完整代码及注释

    
    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);//优化5bool 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();
        }
    }
    
    

    5. GlobalBundleAdjustemnt

    全局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

  • 相关阅读:
    读书笔记:《浪潮之巅:下》
    JAVA集合01_Collection接口概述、常用方法、集合和数组互转、3种遍历方式
    pytorch_autograd v1.backward()+variable.grad
    Open3D FPS最远点下采样
    ACM第三周总结
    深度学习之 11 卷积神经网络实现
    文字弹性跳动CSS3代码
    Android音视频开发:MediaRecorder录制音频
    Docker安装redis
    论文解读(AGC)《Attributed Graph Clustering via Adaptive Graph Convolution》
  • 原文地址:https://blog.csdn.net/weixin_50862344/article/details/126912325