美文网首页
ceres-solver在李群上的使用

ceres-solver在李群上的使用

作者: 范帝楷 | 来源:发表于2018-07-03 12:55 被阅读0次

这篇文章就简单讲一个例子,希望大家动手做一个demo,这样来体会这个库的使用方式。

理论部分

不知道大家还记不记得g2o的经典demo,sphere.我们今天就来自己用ceres做一个图优化,从而学会ceres的使用。李群李代数基本功我不想在这里太多的讲解,就给大家简单的把公式推导一下,然后上代码(这个误差定义未必好,想做的更好的朋友可以考虑用更好的误差定义,从而有更好的线性化策略),废话不多说,直接上公式,为了简略起见,我就不去写那个hat和vee符号了,相信大多数朋友对这套玩意儿早已烂熟于心


sphere.jpg-87.8kBsphere.jpg-87.8kB

如上图,最后就这样得到了线性化结果,然后你就可以拿去定义g2o的边了,不过这不是今天的重点,接下来我们来用ceres写图优化

ceres-solver使用

这个程序我已经上传到我的github上,没怎么去考虑程序设计,这个程序附带显示端,私以为做个仿真应该还是可以了,记得给一个星星哦,网址为:https://github.com/Lancelot899/g2o_sphere
同时还有一个ceres写得pnp的仿真,程序和这个程序也类似,只有雅可比计算有一些不同,大家有兴趣也可以看看https://github.com/Lancelot899/ceres_pnp

废话不多说,进入程序编写部分,首先大家可以在我的github上把这个程序下载下来,外围的数据读取显示等都已完成,优化的核心代码在Sphere.cpp文件中,首先我先介绍一下需要打交道的数据结构,这部分代码在DataStruct.h文件中

    struct Vertex {
        int                         index;
        Eigen::Matrix<double, 6, 1> pose;

    };

    struct Edge {
        int                          i;
        int                          j;
        Sophus::SE3d                 pose;
        Eigen::Matrix<double, 6, 6>  infomation;
    };

点数据结构里面有点的索引,以及表示位姿的李代数的值,边数据结构存储了边连接的点的索引,以及边的约束(SE3),还有信息矩阵,这些数据的读取和显示已经在程序的其他部分做好了

然后是Sphere.cpp文件,在这个文件中,大家可以重新定义误差,然后实现一个自己的优化。使用ceres做优化,首先要做的是定义损失函数,在官方网站上有很多可以定义损失函数的方法,在这里我选择了一个相对比较灵活的方式,即继承"ceres::SizedCostFunction"
这是个可变参数的模板,第一个参数为误差的维度,由于误差是SE(3)的李代数se(3),所以是6维,后面的参数都是你要传入的参数的维度,这里要优化两个位姿,所以需要传入两个参数,每个位姿的维度为6,所以设置两个6,因此在程序中继承它的时候,传入了模板参数<6,6,6>,之后核心在于重载函数

virtual bool Evaluate(double const* const* parameters,
                      double* residuals,
                      double** jacobians) const;

注意这里是的const,一个不能少,我来介绍一下这个函数怎么写。
由于你有两个传入参数,所以parameters和jacobians这个二级指针里面,含有两个一级指针,就是说parameters[0]和parameters1,以及jacobians[0]和jacobians1这四个指针是有内存的,当然residuals也是有内存的
之后,由于param的维度都是6,所以parameters[0]和parameters1的空间分配量都为6,residuals自然也是6,注意,jacobians[0]和jacobians1两个雅可比指针的维度都是36,因为误差是6维,参数是6维,求导得到的矩阵为6*6,在这里把这个矩阵存成向量,行优先存储。你需要在这个函数里面把残差已经雅可比全部计算好,然后返回一个true

virtual bool Evaluate(double const* const* parameters,

                      double* residuals,

                      double** jacobians) const {
        Eigen::Map<const Eigen::Matrix<double, 6, 1>> lie_i(*parameters);
        Eigen::Map<const Eigen::Matrix<double, 6, 1>> lie_j(*(parameters + 1));
        Eigen::Matrix<double, 6, 6> Jac_i;
        Eigen::Matrix<double, 6, 6> Jac_j;
        Sophus::SE3d T_i = Sophus::SE3d::exp(lie_i);
        Sophus::SE3d T_j = Sophus::SE3d::exp(lie_j);
        Sophus::SE3d Tij_estimate = T_i * T_j.inverse();

        Sophus::SE3d err = Tij_estimate * T_ij_.inverse();

        Eigen::Matrix<double, 6, 6> Jl;

        Jl.block(3, 3, 3, 3) = Jl.block(0, 0, 3, 3) = Sophus::SO3d::hat(err.so3().log());
        Jl.block(0, 3, 3, 3) = Sophus::SO3d::hat(err.translation());
        Jl.block(3, 0, 3, 3) = Eigen::Matrix3d::Zero();
        Eigen::Matrix<double, 6, 6> I = Eigen::Matrix<double, 6, 6>::Identity();
        Jl.noalias() = sqrt_information_ * (I - 0.5 * Jl);
        Jac_i = Jl;
        
        Eigen::Matrix<double, 6, 1> err_ = sqrt_information_ * err.log();
        
        const Eigen::Matrix<double, 3, 3>& R = Tij_estimate.rotationMatrix();
        Eigen::Matrix<double, 6, 6> adj;
        adj.block<3, 3>(3, 3) = adj.block<3, 3>(0, 0) = R;
        adj.block<3, 3>(0, 3) = Sophus::SO3d::hat(Tij_estimate.translation()) * R;
        adj.block<3, 3>(3, 0) = Eigen::Matrix<double, 3, 3>::Zero(3, 3);

        Jac_j = -Jac_i * adj;
        
        int k = 0;
        for(int i = 0; i < 6; i++) {
            residuals[i] = err_(i);
            if(jacobians) {
                for (int j = 0; j < 6; ++j) {
                    if (jacobians[0])
                        jacobians[0][k] = Jac_i(i, j);
                    if (jacobians[1])
                        jacobians[1][k] = Jac_j(i, j);
                    k++;
                }
            }
        }
        return true;
    }

这段代码应该能和上面的公式都对上,应该说明的两个地方为sqrt_information_,由于整个误差计算是有信息矩阵的,你需要把信息矩阵分解一下,然后乘到雅可比上,最后得到不带权值的最小二乘法,简单用公式说明就是:


sphereLL.jpg-42.3kBsphereLL.jpg-42.3kB

所以大家可以看到我在构造函数里面把这个事做了,然后后面直接乘一下雅可比得到新的最小二乘法。

Eigen::LLT<Eigen::Matrix<double, 6, 6>> llt(information);
sqrt_information_ = llt.matrixL();

之后,由于大家都知道,点里面的位姿是李代数,所以更新的时候是不能够线性相加的,而要用这样那样的公式:


lie.jpg-10.6kBlie.jpg-10.6kB

因此你需要自己设置更新方法,这个就是使用本地化参数了,需要继承" ceres::LocalParameterization"这个类,实现四个接口,分别是:

virtual bool Plus(const double* x,
                  const double* delta,
                  double* x_plus_delta) const;

virtual bool ComputeJacobian(const double* x,
                             double* jacobian) const;

virtual int GlobalSize() const;
virtual int LocalSize() const ;

在我的程序里面,雅可比都在前面全部搞定了,也没有本地参数(你实际上要用的)和全局参数(最小二乘法算的)参数的变化,所以你会看到ComputeJacobian这个函数,我就设置了个单位矩阵上去,然后实现了Plus这个函数

做好这些事,你的准备工作就算全部写好了,最后就是设置求解器,都是套路活儿,代码是死的,大家看看我写的应该就会了,就写这么多,有什么问题,可以私下与我联系

相关文章

  • ceres-solver在李群上的使用

    这篇文章就简单讲一个例子,希望大家动手做一个demo,这样来体会这个库的使用方式。 理论部分 不知道大家还记不记得...

  • ubuntu安装ceres

    github:https://github.com/ceres-solver/ceres-solver官方教程 :...

  • cmake_parse_arguments用法

    实例代码在ceres-solver的cmake的FindSuiteSparse.cmake中 set(OPTION...

  • 李群再看

    李群再看 大多数情况下,在物理的世界里,李群基本就等于简单或是半简单李群。所以可以说,我是不懂李群的,用Sussk...

  • 李群李代数在SLAM系统中的使用

    这篇文章针对有一定SLAM基础的同学或者对李群李代数的应用感兴趣的数学专业同学,已经很小众了,但对于真正干这行并想...

  • 吃饺子

    我上完练字班后,就和妈妈去了二姨家吃饺子了。那是因为李群姐也回来了。李群姐姐上河北金融大学,他的作业比我...

  • ceres-solver

    ceres库是算法优化库 由于平时会经常用到这些库,每次找网址都觉得麻烦,特此整理记录一下官方教程:http://...

  • week64 ubuntu18.04安装Ceres

    软件包下载 下载地址: https://github.com/ceres-solver/ceres-sol...

  • 李群李代数

    李群 在SLAM中,我们经常会遇到李群李代数的概念。我只知道对于3维空间的旋转矩阵 \bf R 其是个特殊的正交群...

  • Git在AS上的使用

    我的项目上有一个以前的仓库,首先去掉以前仓库,然后在Bitbucket创建仓库。(GitLab类似) 一。Bitb...

网友评论

      本文标题:ceres-solver在李群上的使用

      本文链接:https://www.haomeiwen.com/subject/xtbduftx.html