SLAM从入门到放弃:SLAM十四讲第十一章习题(1-2)

以下均为简单笔记,如有错误,请多多指教。

  1. 如果将位姿图中的误差定义为 Δ ξ i j = ξ i ∘ ξ j − 1 \Delta \xi_{ij}=\xi_i\circ\xi_j^{-1} Δξij=ξiξj1,推导按照此定义的左乘扰动雅克比矩阵。
    答:在进行证明之前,不妨先把 Δ ξ i j = ξ i − 1 ∘ ξ j \Delta \xi_{ij}=\xi_i^{-1}\circ\xi_j Δξij=ξi1ξj推导一次来巩固一下基础。
    首先定义残差 e i j = I n ( e x p ( − ξ i j ) ∧ e x p ( − ξ i ) ∧ e x p ( ξ j ) ∧ ) ∨ e_{ij}=In(exp(-\xi_{ij})^{\land}exp(-\xi_{i})^{\land}exp(\xi_{j})^{\land})^{\lor} eij=In(exp(ξij)exp(ξi)exp(ξj))
    因此增加两个左扰动后,上式变为 e ^ i j = I n ( e x p ( − ξ i j ) ∧ e x p ( − ξ i ) ∧ e x p ( − δ ξ i ) ∧ e x p ( δ ξ j ) ∧ e x p ( ξ j ) ∧ ) ∨ \hat{e}_{ij}=In(exp(-\xi_{ij})^{\land}exp(-\xi_{i})^{\land}exp(-\delta\xi_{i})^{\land}exp(\delta\xi_{j})^{\land}exp(\xi_{j})^{\land})^{\lor} e^ij=In(exp(ξij)exp(ξi)exp(δξi)exp(δξj)exp(ξj))
    这里需要说明的是,此处对 ξ i \xi_i ξi的左扰动,本应该写成 e x p ( δ ξ i ) ∧ e x p ( ξ i ) ∧ = δ T i T i exp(\delta \xi _i)^{\land}exp(\xi_i)^{\land}=\delta T_i T_i exp(δξi)exp(ξi)=δTiTi,然而由于在公式中需要对其求逆,从几何变换的角度来考虑,容易得到 ( δ T i T i ) − 1 = T i − 1 δ T i − 1 (\delta T_i T_i)^{-1}=T_i^{-1}\delta T_i^{-1} (δTiTi)1=Ti1δTi1; 至于 T − 1 = e x p ( − ξ ) ∧ T^{-1}=exp(-\xi)^{\land} T1=exp(ξ),只不过是一种表述而已。
    得到上式以后,首先对伴随公式 T e x p ( ξ ∧ ) T − 1 = e x p ( A d ( T ) ξ ) ∧ Texp(\xi^{\land})T^{-1}=exp(Ad(T)\xi)^{\land} Texp(ξ)T1=exp(Ad(T)ξ)进行变换 T − 1 e x p ( ξ ∧ ) T = e x p ( A d ( T − 1 ) ξ ) ∧ T^{-1}exp(\xi^{\land})T=exp(Ad(T^{-1})\xi)^{\land} T1exp(ξ)T=exp(Ad(T1)ξ),即 e x p ( ξ ∧ ) T = T e x p ( A d ( T − 1 ) ξ ) ∧ exp(\xi^{\land})T=Texp(Ad(T^{-1})\xi)^{\land} exp(ξ)T=Texp(Ad(T1)ξ)
    e ^ i j = I n ( e x p ( − ξ i j ) ∧ e x p ( − ξ i ) ∧ e x p ( − δ ξ i ) ∧ e x p ( δ ξ j ) ∧ e x p ( ξ j ) ∧ ) ∨ \hat{e}_{ij}=In(exp(-\xi_{ij})^{\land}exp(-\xi_{i})^{\land}exp(-\delta\xi_{i})^{\land}exp(\delta\xi_{j})^{\land}exp(\xi_{j})^{\land})^{\lor} e^ij=In(exp(ξij)exp(ξi)exp(δξi)exp(δξj)exp(ξj))
    e ^ i j = I n ( T i j − 1 T i − 1 e x p ( − δ ξ i ) ∧ e x p ( δ ξ j ) ∧ T j ) ∨ \hat{e}_{ij}=In(T_{ij}^{-1}T_i^{-1}exp(-\delta\xi_{i})^{\land}exp(\delta\xi_{j})^{\land}T_j)^{\lor} e^ij=In(Tij1Ti1exp(δξi)exp(δξj)Tj)
    e ^ i j = I n ( T i j − 1 T i − 1 e x p ( − δ ξ i ) ∧ T j e x p ( A d ( T j − 1 ξ j ) ∧ ) ) ∨ \hat{e}_{ij}=In(T_{ij}^{-1}T_i^{-1}exp(-\delta\xi_{i})^{\land}T_jexp(Ad(T_j^{-1}\xi_j)^{\land}))^{\lor} e^ij=In(Tij1Ti1exp(δξi)Tjexp(Ad(Tj1ξj)))
    e ^ i j = I n ( T i j − 1 T i − 1 T j e x p ( A d ( T j − 1 ) − ξ i ) ∧ e x p ( A d ( T j − 1 ) ξ j ) ∧ ) ∨ \hat{e}_{ij}=In(T_{ij}^{-1}T_i^{-1}T_jexp(Ad(T_j^{-1})-\xi_i)^{\land}exp(Ad(T_j^{-1})\xi_j)^{\land})^{\lor} e^ij=In(Tij1Ti1Tjexp(Ad(Tj1)ξi)exp(Ad(Tj1)ξj))
    到此之后,再利用BCH公式,对上式进行展开
    e ^ i j = I n ( T i j − 1 T i − 1 e x p ( ξ j ) ∧ T j e x p ( A d ( T j − 1 ) − ξ i ) ∧ e x p ( A d ( T j − 1 ) ξ j ) ∧ ) ∨ \hat{e}_{ij} = In(T_{ij}^{-1}T_i^{-1}exp(\xi_j)^{\land}T_jexp(Ad(T_j^{-1})-\xi_i)^{\land}exp(Ad(T_j^{-1})\xi_j)^{\land})^{\lor} e^ij=In(Tij1Ti1exp(ξj)Tjexp(Ad(Tj1)ξi)exp(Ad(Tj1)ξj))
    e ^ i j ≈ I n ( T i j − 1 T i − 1 e x p ( − ȷ r − 1 δ ξ i A d ( T j − 1 ) + ξ j ) ∧ e x p ( A d ( T j − 1 ) ξ j ) ∧ ) ) ∨ \hat{e}_{ij} \thickapprox In(T_{ij}^{-1}T_i^{-1}exp(-\jmath_r^{-1}\delta\xi_iAd(T_j^{-1})+\xi_j)^{\land}exp(Ad(T_j^{-1})\xi_j)^{\land}))^{\lor} e^ijIn(Tij1Ti1exp(ȷr1δξiAd(Tj1)+ξj)exp(Ad(Tj1)ξj)))
    e ^ i j ≈ I n ( T i j − 1 T i − 1 e x p ( − ȷ r − 1 δ ξ i A d ( T j − 1 ) + ȷ r − 1 δ ξ j A d ( T j − 1 ) + ξ j ) ∧ ) ∨ \hat{e}_{ij} \thickapprox In(T_{ij}^{-1}T_i^{-1}exp(-\jmath_r^{-1}\delta\xi_iAd(T_j^{-1})+\jmath_r^{-1}\delta\xi_jAd(T_j^{-1})+\xi_j)^{\land})^{\lor} e^ijIn(Tij1Ti1exp(ȷr1δξiAd(Tj1)+ȷr1δξjAd(Tj1)+ξj))
    e ^ i j ≈ I n ( e x p ( − ξ i j ) ∧ e x p ( − ξ i ) ∧ e x p ( − ȷ r − 1 δ ξ i A d ( T j − 1 ) + ȷ r − 1 δ ξ j A d ( T j − 1 ) + ξ j ) ∧ ) ∨ \hat{e}_{ij} \thickapprox In(exp(-\xi_{ij})^{\land}exp(-\xi_{i})^{\land}exp(-\jmath_r^{-1}\delta\xi_iAd(T_j^{-1})+\jmath_r^{-1}\delta\xi_jAd(T_j^{-1})+\xi_j)^{\land})^{\lor} e^ijIn(exp(ξij)exp(ξi)exp(ȷr1δξiAd(Tj1)+ȷr1δξjAd(Tj1)+ξj))
    考虑到 e x p ( A ) e x p ( B ) = e x p ( A + B ) exp(A)exp(B)=exp(A+B) exp(A)exp(B)=exp(A+B),且 a ∧ + b ∧ = ( a + b ) ∧ a^{\land}+b^{\land}=(a+b)^{\land} a+b=(a+b),故可以得到:
    e ^ i j ≈ I n ( e x p ( − ȷ r − 1 δ ξ i A d ( T j − 1 ) + ȷ r − 1 δ ξ j A d ( T j − 1 ) − ξ i j − ξ i + ξ j ) ∧ ) ∨ \hat{e}_{ij} \thickapprox In(exp(-\jmath_r^{-1}\delta\xi_iAd(T_j^{-1})+\jmath_r^{-1}\delta\xi_jAd(T_j^{-1})-\xi_{ij}-\xi_i+\xi_j)^{\land})^{\lor} e^ijIn(exp(ȷr1δξiAd(Tj1)+ȷr1δξjAd(Tj1)ξijξi+ξj))
    再考虑 I n In In e x p exp exp ∧ \land ∨ \lor 互相对应,上式可以成为
    e ^ i j ≈ − ȷ r − 1 δ ξ i A d ( T j − 1 ) + ȷ r − 1 δ ξ j A d ( T j − 1 ) − ξ i j − ξ i + ξ j \hat{e}_{ij} \thickapprox -\jmath_r^{-1}\delta\xi_iAd(T_j^{-1})+\jmath_r^{-1}\delta\xi_jAd(T_j^{-1})-\xi_{ij}-\xi_i+\xi_j e^ijȷr1δξiAd(Tj1)+ȷr1δξjAd(Tj1)ξijξi+ξj
    e ^ i j ≈ − ȷ r − 1 δ ξ i A d ( T j − 1 ) + ȷ r − 1 δ ξ j A d ( T j − 1 ) + e i j \hat{e}_{ij} \thickapprox -\jmath_r^{-1}\delta\xi_iAd(T_j^{-1})+\jmath_r^{-1}\delta\xi_jAd(T_j^{-1}) + e_{ij} e^ijȷr1δξiAd(Tj1)+ȷr1δξjAd(Tj1)+eij

    ∂ e i j ∂ δ ξ i = − ȷ r − 1 A d ( T j − 1 ) \tfrac{\partial e_{ij}}{\partial \delta \xi_i}=-\jmath_r^{-1}Ad(T_j^{-1}) δξieij=ȷr1Ad(Tj1)
    ∂ e i j ∂ δ ξ j = ȷ r − 1 A d ( T j − 1 ) \tfrac{\partial e_{ij}}{\partial \delta \xi_j}= \jmath_r^{-1}Ad(T_j^{-1}) δξjeij=ȷr1Ad(Tj1)
    反过来,再考虑 Δ ξ i j = ξ i ∘ ξ j − 1 \Delta \xi_{ij}=\xi_i\circ\xi_j^{-1} Δξij=ξiξj1,因此误差项为 e i j = I n ( Δ T i j − 1 T j T i − 1 ) ∨ e_{ij}=In(\Delta T_{ij}^{-1}T_jT_i^{-1})^{\lor} eij=In(ΔTij1TjTi1)。如果不推公式,直接映射可以得到(可能是错的.,实在不想推了):
    ∂ e i j ∂ δ ξ j = ȷ r − 1 A d ( T i ) \tfrac{\partial e_{ij}}{\partial \delta \xi_j}= \jmath_r^{-1}Ad(T_i) δξjeij=ȷr1Ad(Ti)
    ∂ e i j ∂ δ ξ i = − ȷ r − 1 A d ( T i ) \tfrac{\partial e_{ij}}{\partial \delta \xi_i}= -\jmath_r^{-1}Ad(T_i) δξieij=ȷr1Ad(Ti)
  1. 参照g2o的程序,在Ceres中实现对“球”位姿图的优化。
    答: 参考g2o的程序,本文的代码如下,其中也写了一部分注释。
#include <iostream>
#include <fstream>

#include <ceres/ceres.h>
#include <ceres/rotation.h>


#include <Eigen/Core>
#include <Eigen/Dense>

#include "sophus/so3.h"
#include "sophus/se3.h"

// 需要设置新的参数域,因此需要继承LocalParameterization 
// 具体可以参考http://www.ceres-solver.org/nnls_modeling.html?highlight=localparameterization#localparameterization
class SE3Parameterization : public ceres::LocalParameterization 
{
public:
    SE3Parameterization() {}
    virtual ~SE3Parameterization() {}
    
    // 如果更新,此处使用李代数更新,而不是简单的加减
    virtual bool Plus(const double* x,
                      const double* delta,
                      double* x_plus_delta) const
    {
        Eigen::Map<const Eigen::Matrix<double, 6, 1>> lie(x);
        Eigen::Map<const Eigen::Matrix<double, 6, 1>> delta_lie(delta);

        Sophus::SE3 T = Sophus::SE3::exp(lie);
        Sophus::SE3 delta_T = Sophus::SE3::exp(delta_lie);

		// 李代数左乘更新
        Eigen::Matrix<double, 6, 1> x_plus_delta_lie = (delta_T * T).log();

        for(int i = 0; i < 6; ++i) 
            x_plus_delta[i] = x_plus_delta_lie(i, 0);  

        return true;
    }

    // 如果计算Jacobian,此处大概是局部求导;不过由于我已经在残差函数中仔细计算了导数,所以这里单位矩阵就好
    virtual bool ComputeJacobian(const double* x,
                                 double* jacobian) const 
    {
        ceres::MatrixRef(jacobian, 6, 6) = ceres::Matrix::Identity(6, 6);
        return true;
    }
    virtual int GlobalSize() const { return 6; }
    virtual int LocalSize() const { return 6; }
};

// 自己求导数的类,需要重构SizedCostFunction
class PosegraphBA: public ceres::SizedCostFunction<6,6,6>
{
public:
    Sophus::SE3 deltaSE3Inv;

    PosegraphBA(double x,double y,double z,
                double s,double vx,double vy,double vz)
    {
        Eigen::Quaterniond q( s,vx, vy, vz );
        q.normalize();
        deltaSE3Inv = Sophus::SE3( q,Eigen::Vector3d( x, y, z ) ).inverse();
    }

    //  Evaluate是很重要的一个函数
    virtual bool Evaluate(double const* const* pose,
                          double *residual,
                          double **jacobians) const
    {
        // Get Pose A
        Eigen::Map<const Eigen::Matrix<double,6,1>> poseAVec6d(pose[0]);
        Sophus::SE3 poseASE3 = Sophus::SE3::exp(poseAVec6d);

        // Get Pose B
        Eigen::Map<const Eigen::Matrix<double,6,1>> poseBVec6d(pose[1]);
        Sophus::SE3 poseBSE3 = Sophus::SE3::exp(poseBVec6d);

        // Compute Error
        Sophus::SE3 errorSE3 = deltaSE3Inv*poseASE3.inverse()*poseBSE3;
        Eigen::Matrix<double,6,1> errorVec6d = errorSE3.log();

        // 残差项
        residual[0] = errorVec6d(0);
        residual[1] = errorVec6d(1);
        residual[2] = errorVec6d(2);
        residual[3] = errorVec6d(3);
        residual[4] = errorVec6d(4);
        residual[5] = errorVec6d(5);

        if( !jacobians )
            return true;

        if( !jacobians[0] && !jacobians[1] )
            return true;

	    // 以下都是jacobian的公式,具体也对应了公式
	    // 可以参考http://www.ceres-solver.org/nnls_modeling.html?highlight=evaluate#_CPPv2N5ceres12CostFunction8EvaluateEPPCdPdPPd
        {
            // 公式11.10 J_r^{-1}
            Eigen::Matrix<double,6,6> J;
            J.block(0,0,3,3) = Sophus::SO3::hat(errorSE3.so3().log());
            J.block(0,3,3,3) = Sophus::SO3::hat(errorSE3.translation());
            J.block(3,0,3,3) = Eigen::Matrix3d::Zero(3,3);
            J.block(3,3,3,3) = Sophus::SO3::hat(errorSE3.so3().log());
            J = J*0.5 + Eigen::Matrix<double,6,6>::Identity();

            // 公式11.8
            // row correspond with error
            // col correspond with parameterA
            Eigen::Matrix<double,6,6> jacA = - J * poseBSE3.inverse().Adj();

            jacobians[0][ 0] = jacA(0,0); jacobians[0][ 1] = jacA(1,0); jacobians[0][ 2] = jacA(2,0); jacobians[0][ 3] = jacA(3,0); jacobians[0][ 4] = jacA(4,0); jacobians[0][ 5] = jacA(5,0);
            jacobians[0][ 6] = jacA(0,1); jacobians[0][ 7] = jacA(1,1); jacobians[0][ 8] = jacA(2,1); jacobians[0][ 9] = jacA(3,1); jacobians[0][10] = jacA(4,1); jacobians[0][11] = jacA(5,1);
            jacobians[0][12] = jacA(0,2); jacobians[0][13] = jacA(1,2); jacobians[0][14] = jacA(2,2); jacobians[0][15] = jacA(3,2); jacobians[0][16] = jacA(4,2); jacobians[0][17] = jacA(5,2);
            jacobians[0][18] = jacA(0,3); jacobians[0][19] = jacA(1,3); jacobians[0][20] = jacA(2,3); jacobians[0][21] = jacA(3,3); jacobians[0][22] = jacA(4,3); jacobians[0][23] = jacA(5,3);
            jacobians[0][24] = jacA(0,4); jacobians[0][25] = jacA(1,4); jacobians[0][26] = jacA(2,4); jacobians[0][27] = jacA(3,4); jacobians[0][28] = jacA(4,4); jacobians[0][29] = jacA(5,4);
            jacobians[0][30] = jacA(0,5); jacobians[0][31] = jacA(1,5); jacobians[0][32] = jacA(2,5); jacobians[0][33] = jacA(3,5); jacobians[0][34] = jacA(4,5); jacobians[0][35] = jacA(5,5);

            // 公式11.9
            Eigen::Matrix<double,6,6> jacB =   J * poseBSE3.inverse().Adj();

            jacobians[1][ 0] = jacB(0,0); jacobians[1][ 1] = jacB(1,0); jacobians[1][ 2] = jacB(2,0); jacobians[1][ 3] = jacB(3,0); jacobians[1][ 4] = jacB(4,0); jacobians[1][ 5] = jacB(5,0);
            jacobians[1][ 6] = jacB(0,1); jacobians[1][ 7] = jacB(1,1); jacobians[1][ 8] = jacB(2,1); jacobians[1][ 9] = jacB(3,1); jacobians[1][10] = jacB(4,1); jacobians[1][11] = jacB(5,1);
            jacobians[1][12] = jacB(0,2); jacobians[1][13] = jacB(1,2); jacobians[1][14] = jacB(2,2); jacobians[1][15] = jacB(3,2); jacobians[1][16] = jacB(4,2); jacobians[1][17] = jacB(5,2);
            jacobians[1][18] = jacB(0,3); jacobians[1][19] = jacB(1,3); jacobians[1][20] = jacB(2,3); jacobians[1][21] = jacB(3,3); jacobians[1][22] = jacB(4,3); jacobians[1][23] = jacB(5,3);
            jacobians[1][24] = jacB(0,4); jacobians[1][25] = jacB(1,4); jacobians[1][26] = jacB(2,4); jacobians[1][27] = jacB(3,4); jacobians[1][28] = jacB(4,4); jacobians[1][29] = jacB(5,4);
            jacobians[1][30] = jacB(0,5); jacobians[1][31] = jacB(1,5); jacobians[1][32] = jacB(2,5); jacobians[1][33] = jacB(3,5); jacobians[1][34] = jacB(4,5); jacobians[1][35] = jacB(5,5);
 
        }

        return true;
    }
};



int main( int argc, char *argv[] )
{
    google::InitGoogleLogging(argv[0]);

    if(argc<2)
    {
        std::cerr<<"./pose_graph_ceres_SE3 sphere.g2o"<<std::endl;
        return -1;
    }

    std::cout<<"Input g2o file: "<<argv[1]<<std::endl;


    std::ifstream g2oFile( argv[1] );
    if ( !g2oFile )
    {
        std::cout<<"file "<<argv[1]<<" does not exist."<<std::endl;
        return -1;
    }
    // Count Pose and Edge
    int poseCount = 0;
    int edgeCount = 0;
    std::string fileLine;
    while( std::getline(g2oFile,fileLine) )
    {
        if(fileLine[0]=='V')
        {
            poseCount++;
        }
        if(fileLine[0]=='E')
        {
            edgeCount++;
        }
    }
    g2oFile.clear();
    g2oFile.seekg(std::ios::beg);    
    

    std::cout<<poseCount<<std::endl;
    std::cout<<edgeCount<<std::endl;


    // Init Ceres
    ceres::Problem problem;

    // Load Data
    double *poseData = new double[poseCount*6];
    for( int i=0; i<poseCount; i++ )
    {
        std::string flag;
        int id;
        double x,y,z,s,vx,vy,vz;
        g2oFile>>flag>>id>>x>>y>>z>>vx>>vy>>vz>>s;
        Eigen::Quaterniond q( s,vx, vy, vz );
        q.normalize();
        Eigen::Matrix<double,6,1> poseVec6d = Sophus::SE3( q,Eigen::Vector3d( x, y, z ) ).log();

        poseData[6*i+0] = poseVec6d(0);
        poseData[6*i+1] = poseVec6d(1);
        poseData[6*i+2] = poseVec6d(2);
        poseData[6*i+3] = poseVec6d(3);
        poseData[6*i+4] = poseVec6d(4);
        poseData[6*i+5] = poseVec6d(5);
    }

    ceres::LocalParameterization *local_parameterization = new SE3Parameterization();

    // Add Residual
    for( int i=0; i<edgeCount; i++ )
    {
        std::string flag;
        int idA,idB;
        double x,y,z,s,vx,vy,vz;
        double temp;
        g2oFile>>flag>>idA>>idB>>x>>y>>z>>vx>>vy>>vz>>s; 
        // I dont't know how to use info
        for( int j=0; j<21; j++ )
        {
            g2oFile>>temp;
        } 

        ceres::CostFunction *costFunction = new PosegraphBA(x,y,z,s,vx,vy,vz);

        problem.AddResidualBlock (  costFunction,
                                    nullptr,            
                                    poseData+6*idA,
                                    poseData+6*idB
        );

        problem.SetParameterization(poseData+6*idA,local_parameterization);
        problem.SetParameterization(poseData+6*idB,local_parameterization);

    }
    g2oFile.close();


    // Set Ceres
    ceres::Solver::Options options;    
    // options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY;  
    options.minimizer_progress_to_stdout = true;  

    // Solve
    ceres::Solver::Summary summary;             
    ceres::Solve ( options, &problem, &summary ); 
    
    // Report
    std::cout<<summary.FullReport() <<std::endl;
    std::ofstream txt("1.txt");
    for( int i=0; i<poseCount; i++ )
    {
        Eigen::Map<const Eigen::Matrix<double,6,1>> poseAVec6d( poseData+6*i );
        Sophus::SE3 poseSE3 = Sophus::SE3::exp(poseAVec6d);

        txt<<poseSE3.translation().transpose()<<std::endl;
    }

    txt.close();


    // Release Data
    delete []poseData;

    return 0;
}


优化前结果

在这里插入图片描述
优化后结果

在这里插入图片描述

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值