• 使用Ceres进行slam必须要弄清楚的几个类和函数


    Ceres solver 是谷歌开发的一款用于非线性优化的库,在谷歌的开源激光雷达slam项目cartographer中被大量使用。
    在之前的博客说了,图优化的本质就是一个非线性优化问题.所以ceres刚好适用图优化问题的解决.
    在进行特征点匹配后进行迭代的优化最优变换位姿时也可以使用ceres.

    ceres简介

    Ceres可以解决边界约束鲁棒非线性最小二乘法优化问题。这个概念可以用以下表达式表示:
    在这里插入图片描述
    这一表达式在工程和科学领域有非常广泛的应用。比如统计学中的曲线拟合,或者在计算机视觉中依据图像进行三维模型的构建等等。

    注意这个公式里面各模块有几个特殊的概念要熟知,涉及到具体的使用:

    残差块(ResidualBlock):
    在这里插入图片描述
    这一部分被称为残差块(ResidualBlock).

    代价函数(CostFunction):
    在这里插入图片描述
    这一部分被称为代价函数(CostFunction).

    参数块(ParameterBlock):
    在这里插入图片描述
    代价函数依赖于一系列参数,这一系列参数(均为标量)称为参数块(ParameterBlock).当然参数块中也可以只含有一个变量

    上下边界:
    在这里插入图片描述
    lj和uj是xj的上下边界.

    损失函数(LossFunction):
    在这里插入图片描述
    pi是损失函数(LossFunction).按照损失函数的是一个标量函数,其作用是减少异常值(Outliers)对优化结果的影响。其效果类似于对函数的过滤。

    ceres的使用流程

    1.构建代价函数(cost function)
    2.通过代价函数构建待求解的优化问题
    3.配置求解器参数并求解问题

    ceres必须要知道的类和函数

    class LossFunction

    class LossFunction 损失函数
    最小二乘问题的输入数据可能包含异常值(错误测量得到的),使用损失函数减少这部分数据的影响。

    比如说当一个移动相机的场景中,街道上有消防栓和汽车,当图像的处理算法把消防栓的尖和汽车的前灯匹配在了一起,那么如果不做任何处理,则会导致ceres为使这个错误的大的误差减小,而是优化结果偏离正确位置.

    LossFunction可以让大的残差的权重降低,从而对 最终的优化结果没有太大的影响.

    class LossFunction {
     public:
      virtual void Evaluate(double s, double out[3]) const = 0;
    };
    
    • 1
    • 2
    • 3
    • 4

    LossFunction的类,关键的函数就是 LossFunction::Evaluate()
    一个非负的参数s,计算输出
    在这里插入图片描述
    Ceres包含了几种定义好的损失函数,都是没有缩放的.具体效果如下图所示:
    在这里插入图片描述
    图中红色的就是没有经过损失函数的,y=x*x.蓝色的是HuberLoss,值低于正常值,并且x越大,效果越明显.
    正常的是:
    在这里插入图片描述
    HuberLoss是:
    在这里插入图片描述
    SoftLOneLoss是:
    在这里插入图片描述
    CauchyLoss是:
    在这里插入图片描述
    ArctanLoss是:
    在这里插入图片描述
    TolerantLoss是:
    在这里插入图片描述
    用定义好的损失函数使用也很简单.例如:

    ceres::LossFunction *loss_function = new ceres::HuberLoss(0.1);
    
    • 1

    定义ceres 的 损失函数 0.1代表 残差大于0.1的点 ,则权重降低,具体效果看上面的公式. 小于0.1 则认为正常,不做特殊的处理
    定义好后,在添加残差

    LocalParameterization

    LocalParameterization 本地参数
    在许多优化问题中,尤其是传感器融合问题中,必须对存在于称为流形的空间中的量进行建模,例如由四元数表示的传感器的旋转/方向。

    Ceres定义了一些特殊的参数,对于slam,用的更多的就是旋转的四元数
    QuaternionParameterization
    EigenQuaternionParameterization
    定义两个主要的原因就是Eigen的存储四元数的方式和一般的不同,Eigen是x,y,z,w,实数部分的w放在最后,一般的则是:w,x,y,z.

    使用

    double para_q[4] = {0, 0, 0, 1};
    ceres::LocalParameterization *q_parameterization =
    new ceres::EigenQuaternionParameterization();
    problem.AddParameterBlock(para_q, 4, q_parameterization);
    
    • 1
    • 2
    • 3
    • 4

    class problem

    problem类就是代表者具有双边约束的最小二乘问题
    为了创建一个最小二乘问题,需要使用
    Problem::AddResidalBlock() 添加残差模块
    Problem::AddParameterBlock() 添加参数模块
    这两个方法

    举个例子,一个问题包含三个参数模块,尺寸分别是3,4,5,两个残差模块尺寸分别是2和6

    double x1[] = { 1.0, 2.0, 3.0 };
    double x2[] = { 1.0, 2.0, 3.0, 5.0 };
    double x3[] = { 1.0, 2.0, 3.0, 6.0, 7.0 };
    
    Problem problem;
    problem.AddResidualBlock(new MyUnaryCostFunction(...), x1);
    problem.AddResidualBlock(new MyBinaryCostFunction(...), x2, x3);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    方法Problem::AddResidualBlock(),和名字一样,功能就是添加参数模块到problem中,这个方法必须有参数CostFunction和可选参数LossFunction,这个方法就连接了CostFunction参数模块.
    CostFunction 具有它希望的参数块尺寸的信息.
    这个函数检查这些是否与 parameter_blocks 中列出的参数块的大小相匹配 .如果检测到错误的匹配,程序会终止.
    LossFunction 可以有,也可以没有

    可以使用Problem::AddParameterBlock() 这个方法来添加参数模块,这个会添加一次参数尺寸的检测.将参数块显式添加到问题中。 还允许将 Manifold 对象与参数块相关联
    这个函数可以用带LocalParameterization的参数,也可以不带.

    problem.AddParameterBlock(para_q, 4, q_parameterization);// 添加四元数的参数块
    problem.AddParameterBlock(para_t, 3);//添加平移的参数块
    
    • 1
    • 2

    声明的时候一般这样:

    ceres::Problem::Options problem_options;
    ceres::Problem problem(problem_options);
    
    • 1
    • 2

    先声明一个ceres::Problem::Options,然后再用Options初始化problem

    class CostFunction

    在这里插入图片描述
    代价函数CostFunction 负责计算残差向量和雅克比矩阵.
    代价函数依赖参数块
    在这里插入图片描述
    其内部定义是这样的

    class CostFunction {
     public:
      virtual bool Evaluate(double const* const* parameters,
                            double* residuals,
                            double** jacobians) = 0;
      const vector<int32>& parameter_block_sizes();
      int num_residuals() const;
    
     protected:
      vector<int32>* mutable_parameter_block_sizes();
      void set_num_residuals(int num_residuals);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这部分不用太管,因为使用的时候用其它类定义的这个类的内部
    定义 CostFunction 或 SizedCostFunction 可能容易出错,尤其是在计算导数时。 为此,Ceres 提供了自动微分。

    class AutoDiffCostFunction

    定义 CostFunction 或 SizedCostFunction 可能容易出错,尤其是在计算导数时。 为此,Ceres 提供了自动微分。

    template <typename CostFunctor,
           int kNumResiduals,  // Number of residuals, or ceres::DYNAMIC.
           int... Ns>          // Size of each parameter block
    class AutoDiffCostFunction : public
    SizedCostFunction<kNumResiduals, Ns> {
     public:
      AutoDiffCostFunction(CostFunctor* functor, ownership = TAKE_OWNERSHIP);
      // Ignore the template parameter kNumResiduals and use
      // num_residuals instead.
      AutoDiffCostFunction(CostFunctor* functor,
                           int num_residuals,
                           ownership = TAKE_OWNERSHIP);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    得到一个可以自动求导的代价函数,必须定义一个类或者结构体在里面重载运算符,在里面实现用参数模板计算代价函数,重载的运算符必须在最后一个参数里存入计算结果,并且返回true.

    举个例子,要计算 一个 代价函数是 e= k - xTy.
    x和y是二维的向量,k是一个不变的参数.
    那么可以定义一个这样的类

    class MyScalarCostFunctor {
      MyScalarCostFunctor(double k): k_(k) {}
    
      template <typename T>
      bool operator()(const T* const x , const T* const y, T* e) const {
        e[0] = k_ - x[0] * y[0] - x[1] * y[1];
        return true;
      }
    
     private:
      double k_;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在重载运算符的定义里面.参数x和y在前面,如果有更多的输入参数,则继续可以排在y后面,输出也就是残差放在最后一个参数,

    给定这个类的定义之后,它的自动微分代价函数可以定义如下:

    CostFunction* cost_function
        = new AutoDiffCostFunction<MyScalarCostFunctor, 1, 2, 2>(
            new MyScalarCostFunctor(1.0));              ^  ^  ^
                                                        |  |  |
                            Dimension of residual ------+  |  |
                            Dimension of x ----------------+  |
                            Dimension of y -------------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面的1,2,2 就是注释的那样,计算1维的残差,2个2维的优化量

  • 相关阅读:
    安装Docker
    淘宝/天猫获取卖出的商品订单列表 API
    [Golang] GO 语言工作环境的基本概念
    ASEMI代理艾赛斯二极管DSA300I100NA,肖特基DSA300I100NA
    AI播客下载:The TWIML AI Podcast (机器学习与人工智能周刊)
    table通过伪类实现 另类自适应
    1.2 Portfolio Theroy
    ChatGPT Prompting开发实战(十三)
    宝塔面板+阿里云部署springboot+vue项目
    7-1归并排序还是插入排序
  • 原文地址:https://blog.csdn.net/qq_32761549/article/details/123850009