Ceres Solver介绍

一、Ceres解决了什么问题

ceres是一款非线性优化问题的数值求解器。
Ceres解决的是非线性优化问题,给定初值,得出最优解。

具体求解就是构建一个误差函数,认为误差函数最小的时候解是最优的,即求解:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、如何使用

CERES_EXPORT void Solve(const Solver::Options& options, Problem* problem, Solver::Summary* summary);

先来看一下ceres::Solve的形参,根据参数来看要怎么构建这个优化问题。

具体可以参考官方文档http://ceres-solver.org/nnls_solving.html#_CPPv2N5ceres6Solver7Options22linear_solver_orderingE,也可以参考一下这篇博客https://blog.csdn.net/DumpDoctorWang/article/details/84890792

1、参数一 Solver::Options

Solver::Options 用来设置优化的参数。

常用的就是调一些迭代的次数,步长,线性求解器的类型之类的。
常用通用参数如下:

  • options.max_solver_time_in_seconds
    默认值:1e6
    最长运行时间,单位为秒。
  • options.linear_solver_type
    线性求解器的类型,用于计算Levenberg-Marquardt算法每次迭代中线性最小二乘问题的解。 如果编译Ceres时加入了SuiteSparse或CXSparse或Eigen的稀疏Cholesky分解选项,则默认为SPARSE_NORMAL_CHOLESKY,否则为DENSE_QR。
  • int Solver::Options::max_num_iterations
    默认值:50
    最大迭代次数。
  • bool Solver::Options::minimizer_progress_to_stdout
    默认值:false
    默认情况下,Minimizer(优化器)进度会记录到stderr,具体取决于vlog级别。 如果此标志设置为true,并且Solver::Option:: logging_type不是SILENT,则日志记录输出将发送到stdout(在终端打印出信息)。

其他更多可见博客:https://blog.csdn.net/DumpDoctorWang/article/details/84890792

2、参数二 Problem

Problem 用来设置误差函数
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Ceres的求解过程包括构建最小二乘和求解最小二乘问题两部分,其中构建最小二乘问题的相关方法均包含在Ceres::Problem类中,涉及的成员函数主要包括Problem::AddResidualBlock()和Problem::AddParameterBlock()。

1.1 Problem::AddResidualBlock( )

AddResidualBlock()顾名思义主要用于向Problem类传递残差模块的信息,函数原型如下,传递的参数主要包括代价函数模块、损失函数模块和参数模块。

ResidualBlockId Problem::AddResidualBlock(CostFunction *cost_function, 
										  LossFunction *loss_function,
										  double *x0, double *x1, ...)

注:x0、x1为估计参数

1.1.1 CostFunction

代价函数:包含了参数模块的维度信息,内部使用仿函数定义误差函数的计算方式。AddResidualBlock( )函数会检测传入的参数模块是否和代价函数模块中定义的维数一致,维度不一致时程序会强制退出。

ceres提供了许多种CostFunction模板,较为常用的包括以下三种:

  • 自动导数(AutoDiffCostFunction):由ceres自行决定导数的计算方式,最常用的求导方式。
  • 数值导数(NumericDiffCostFunction):由用户手动编写导数的数值求解形式,通常在残差函数的计算使用无法直接调用的库函数,导致调用
    AutoDiffCostFunction类构建时使用;但手动编写的精度和计算效率不如模板类,因此不到不得已,官方并不建议使用该方法。
  • 解析导数(Analytic Derivatives):当导数存在闭合解析形式时使用,用于可基于CostFunciton基类自行编写;但由于需要自行管理残差和雅克比矩阵,除非闭合解具有具有明显的精度和效率优势,否则同样不建议使用。

AutoDiffCostFunction为模板类,构造函数如下:

ceres::AutoDiffCostFunction<CostFunctor, int residualDim, int paramDim>(CostFunctor* functor);

模板参数依次为仿函数(functor)类型CostFunctor,残差维数residualDim和待优化变量维数paramDim,接受参数类型为仿函数指针CostFunctor*。

ceres::CostFunction* cost_function=new ceres::AutoDiffCostFunction<
											ReprojectionError3D, 2, 4, 3, 3>(new ReprojectionError3D(observed_x,observed_y));
problem.AddResidualBlock(cost_function, NULL, Q, T, Position); 		

1.1.2 LossFunction

损失函数:用于处理参数中含有野值的情况,避免错误量测对估计的影响,常用参数包括HuberLoss、CauchyLoss等;该参数可以取NULL或nullptr,此时损失函数为单位函数。

1.2 Problem::AddParameterBlock( )
用户在调用AddResidualBlock( )时其实已经隐式地向Problem传递了参数模块,但在一些情况下,需要用户显示地向Problem传入参数模块(通常出现在需要对优化参数进行重新参数化的情况)。Ceres提供了Problem::AddParameterBlock( )函数用于用户显式传递参数模块:

void Problem::AddParameterBlock(double *values, int size)

void Problem::AddParameterBlock(double *values, int size, LocalParameterization *local_parameterization)

注:values表示优化变量,size表示优化变量的维度。
其中,第一种函数原型除了会增加一些额外的参数检查之外,功能上和隐式传递参数并没有太大区别。第二种函数原型则会额外传入LocalParameterization参数,用于重构优化参数的维数,这里我们着重讲解一下LocalParameterization类。

1.2.1 LocalParameterization

LocalParameterization是在优化Manifold(流形)上的变量时需要考虑的,Manifold上变量是过参数的,即Manifold上变量的维度大于其自由度。这会导致Manifold上变量各个量之间存在约束,如果直接对这些量求导、优化,那么这就是一个有约束的优化,实现困难。为了解决这个问题,在数学上对Manifold在当前变量值处形成的切空间求导,在切空间上优化,最后投影回Manifold。

对于SLAM问题,广泛遇到的Manifold是旋转,旋转仅需要3个量,但实际运用中涉及到万向锁问题,在更高维空间表达旋转,四元数就是在维度4表达3个自由度的三维空间的旋转。

bool ComputeJacobian()计算得到一个4*3的矩阵(global_to_local),含义是Manifold上变量对Tangent Space上变量的导数,在ceres::CostFunction处提供residuals对Manifold上变量的倒数,乘以这个矩阵,之后变就变成了对Tangent Space上变量的导数。

problem.AddParameterBlock(quaternion, 4);// 直接传递4维参数

ceres::LocalParameterization* local_param = new ceres::QuaternionParameterization();
problem.AddParameterBlock(quaternion, 4, local_param)//重构参数,优化时实际使用的是3维的等效旋转矢量

除了上面提到的QuaternionParameterization外,ceres还提供下述预定义LocalParameterization子类,具体可查手册。

1.2.2 自定义LocalParameterization
LocalParaneterization本身是一个虚基类,详细定义如下。用户可以自行定义自己需要使用的子类,或使用Ceres预先定义好的子类。

1.3 其他成员函数

Probelm还提供了其他关于ResidualBlock和ParameterBlock的函数,例如获取模块维数、判断是否存在模块、存在的模块数目等,这里只列出几个比较重要的函数,完整的列表参见ceres API:

// 设定对应的参数模块在优化过程中保持不变
void Problem::SetParameterBlockConstant(double *values)
// 设定对应的参数模块在优化过程中可变
void Problem::SetParameterBlockVariable(double *values)
// 设定优化下界
void Problem::SetParameterLowerBound(double *values, int index, double lower_bound)
// 设定优化上界
void Problem::SetParameterUpperBound(double *values, int index, double upper_bound)
// 该函数紧跟在参数赋值后,在给定的参数位置求解Problem,给出当前位置处的cost、梯度以及Jacobian矩阵;
bool Problem::Evaluate(const Problem::EvaluateOptions &options, 
					   double *cost, vector<double>* residuals, 
					   vector<double> *gradient, CRSMatrix *jacobian)

3、参数三 Solver::Summary

输出优化过程及结果

  • termination_type
    求解器终止的原因:
enum TerminationType {
  CONVERGENCE,
  NO_CONVERGENCE,
  FAILURE,
  USER_SUCCESS,
  USER_FAILURE
};
  • final_cost
    优化后的最终代价

三、使用

ceres::Solve(options, &problem, &summary);

例子:

// cost fuction
// 需要重载operator(),具体参数为优化的变量和根据变量计算出的残差
struct F4 {
      template <typename T>
      bool operator()(const T* const x1, const T* const x4, T* residual) const {
        residual[0] = T(sqrt(10.0)) * (x1[0] - x4[0]) * (x1[0] - x4[0]);
        return true;
}

int main(int argc, char** argv)
{
    double x1 = 0.2;
    double x4 = 0.2;
    
    //设置优化参数
    Solver::Options options;
    options.minimizer_progress_to_stdout = false;

    //存log的
    Solver::Summary summary;
    
    //构建残差
    Problem problem;
    problem.AddResidualBlock(
      new AutoDiffCostFunction<F4, 1, 1, 1>(new F4), NULL, &x1, &x4);
    Solve(options, &problem, &summary);
    std::cout << summary.FullReport() << std::endl;
    return 0;
};

所以需要关注的就两个部分,一个是你怎么调优化的参数,即你如何设置option,再一个就是如何构建cost funtion,把cost funtion重载在operator()里,其他的就是固定流程的套路,想看的更详细看一下源代码的参数。

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值