广西庆海建设发展有限公司网站,昆山有做网站的公司吗,东莞seo网站推广建设,dw设计个人网页文章目录 Part.I IntroductionChap.I 预备知识Chap.II 概念理解 Part.II 简单使用Chap.I Ceres 中主要函数简介Chap.II 一个简单的实例 Reference Part.I Introduction
Ceres 1 是由 Google 开发的开源 C 通用非线性优化库#xff0c;与 g2o 并列为目前视觉 SLAM 中应用最广泛… 文章目录 Part.I IntroductionChap.I 预备知识Chap.II 概念理解 Part.II 简单使用Chap.I Ceres 中主要函数简介Chap.II 一个简单的实例 Reference Part.I Introduction
Ceres 1 是由 Google 开发的开源 C 通用非线性优化库与 g2o 并列为目前视觉 SLAM 中应用最广泛的优化算法库。本篇博文简要介绍笔者使用 Ceres 过程中所做笔记不当与不足之处还望批评指正 Chap.I 预备知识
在使用 Ceres 之前需要对图优化的概念有一定的理解。
Chap.II 概念理解
下面是使用 Ceres 过程中会涉及的一些概念
代价函数CostFunction 也就是寻优的目标式目标函数。它包含了参数模块的维度信息内部使用仿函数定义误差函数的计算方式。相当于最小二乘中的 V T P V m i n V^TPVmin VTPVmin损失函数LossFunction 用于处理参数中含有野值的情况避免错误量测对估计的影响常用参数包括 HuberLoss、CauchyLoss 等。边缘化marginalization 边缘化的对象是滑动窗口中的最老帧或者次新帧将滑窗内的某些较旧或者不满足要求的帧剔除所以边缘化也被描述为将联合概率分布分解为边缘概率分布和条件概率分布的过程(就是利用 Schur 补减少优化参数的过程)。边缘化是将丢掉的帧封装成先验信息加入到新的非线性优化问题中作为一部分误差。
Part.II 简单使用
Chap.I Ceres 中主要函数简介
这部分主要来源于网络中前辈们的总结 2笔者根据自己的理解进行了一些调整。Ceres 中的优化需要两步
构建优化问题包括构建优化的残差函数 CostFunction、在每次获取到数据后添加残差块用到 Problem 类 AddResidualBlock 方法等求解优化问题设置方程怎么求解求解过程是否输出等会调用 Solve 方法 CostFunction 类构建优化的残差函数
与其他非线性优化工具包一样ceres 的性能很大程度上依赖于导数计算的精度和效率。这部分工作在 ceres 中称为 CostFunctionceres 提供了许多种 CostFunction 模板较为常用的包括以下三种
自动导数AutoDiffCostFunction由 ceres 自行决定导数的计算方式最常用的求导方式。数值导数NumericDiffCostFunction由用户手动编写导数的数值求解形式通常在残差函数的计算使用无法直接调用的库函数导致调用AutoDiffCostFunction 类构建时使用但手动编写的精度和计算效率不如模板类因此不到不得已官方并不建议使用该方法。解析导数SizedCostFunction当导数存在闭合解析形式时使用用于可基于 SizedCostFunction 基类自行编写但由于需要自行管理残差和雅克比矩阵除非闭合解具有具有明显的精度和效率优势否则同样不建议使用。 Problem 类构建优化问题
构建最小二乘问题的相关方法均包含在 Ceres::Problem 类中涉及的成员函数主要包括
Problem::AddResidualBlock()向 Problem 类传递残差模块的信息传递的参数主要包括代价函数模块CostFunction *cost_function、损失函数模块LossFunction *loss_function和参数模块const vectordouble * parameter_blocks。Problem::AddParameterBlock()显式地向 Problem 传递参数模块其实用户在调用 AddParameterBlock()以前已经隐式地向 Problem 传递了参数模块但是在一些情况下需要用户显示地向 Problem 传递参数模块比如需要对优化参数进行重新参数化的情况因为这个时候优化的参数已经变了这个时候就需要使用 AddParameterBlock() 显示地向 Problem 传递参数模块。Problem::SetParameterBlockConstant()设定对应的参数模块在优化过程中保持不变。Problem::SetParameterBlockVariable()设定对应的参数模块在优化过程中可变Problem::SetParameterUpperBound()和Problem::SetParameterLowerBound()设定优化上下界Problem::Evaluate()该函数紧跟在参数赋值后在给定的参数位置求解Problem给出当前位置处的 cost、梯度以及 Jacobian 矩阵 Solver 类求解优化问题
ceres::Solve函数是 Ceres 求解最小二乘问题的核心函数函数原型如下
void Solve(const Solver::Options options, Problem* problem, Solver::Summary* summary)函数传入的参数包括
Solver::Options求解选项是 Ceres 求解的核心包括消元顺序、分解方法、收敛精度等在内的求解器所有行为均由 Solver::Options 控制。Solver::Summary求解报告只用于存储求解过程中的相关信息并不影响求解器性能。Problem求解问题即上面所述的优化问题。 Solver::Options
Solver::Options 中含有的参数种类繁多绝大多数参数都会使用 Ceres 的默认设置这里列出一些常用或较为重要的参数。
minimizer_type迭代求解方法可选线性搜索方法LINEAR_SEARCH、信赖域方法TRUST_REGION默认为TRUST_REGION 方法由于大多数情况我们都会选择 LM 或 DOGLEG 方法该选项一般直接采用默认值trust_region_strategy_type信赖域策略可选LEVENBERG_MARQUARDT或DOGLEG默认为LEVENBERG_MARQUARDT没有高斯牛顿选项linear_solver_type信赖域方法中求解线性方程组所使用的求解器类型默认为 DENSE_QRlinear_solver_ordering线性方程求解器的消元顺序默认为 NULL即由 Ceres 自行决定消元顺序在以 BA 为典型代表的对消元顺序有特殊要求的应用中可以通过成员函数 reset 设定消元顺序稍后将详细说明min_linear_solver_iteration / max_linear_solver_iteration线性求解器的最小/最大迭代次数默认为 0/500一般不需要更改max_num_iterations求解器的最大迭代次数max_solver_time_in_seconds求解器的最大运行秒数num_threadsCeres 求解时使用的线程数在老版本的 Ceres 中还有一个针对线性求解器的线程设置选项 num_linear_solver_threads最新版本的 Ceres 中该选项已被取消虽然为了保证程序的兼容性用户依旧可以设置该参数但 Ceres 会自动忽略该参数并没有实际意义minimizer_progress_to_stdout是否向终端输出优化过程信息该选型默认为 false即根据 vlog 设置等级的不同只会在向 STDERR 中输出错误信息若设置为 true 则会向程序的运行终端输出优化过程的所有信息根据所设置优化方法的不同输出的参数亦不同。
在实际应用中上述参数中对最终求解性能最大的就是线性方程求解器类型 linear_solver_type 和线程数 num_threads如果发现最后的求解精度或求解效率不能满足要求应首先尝试更换这两个参数。
Chap.II 一个简单的实例
基于上述先验知识我们一起来用 Ceres 求解一个简单的问题 3。
问题已知某二维曲线 y e a x 2 b x c ye^{ax^2bxc} yeax2bxc 上 1000 个点的坐标有噪声求参数 a , b , c a, b, c a,b,c 的值。思路首先构造代价函数结构体然后生成在[0,1] 之间均匀分布的 x把这些 x 代入待拟合曲线我们就令 a 3 , b 2 , c 1 a3, b2, c1 a3,b2,c1式子中得到 y_tmp在加上方差为1的随机噪声得到 y如此便得到 1000 个 (x,y) 这样的数据点用这些数据点来求参数 a , b , c a, b, c a,b,c 的值 a , b , c a, b, c a,b,c 与 3 , 2 , 1 3, 2, 1 3,2,1 越接近说明得到的结果越准确。ps上面我们得到数据点的过程说白了就是一个模拟观测的过程实际处理问题过程中数据点肯定是由观测得到的我们事先并不知道 a , b , c a, b, c a,b,c 的值。 首先构造代价函数
//构建代价函数结构体abc为待优化参数residual为残差。
struct CURVE_FITTING_COST
{CURVE_FITTING_COST(double x, double y) :_x(x), _y(y) {}template typename Tbool operator()(const T* const abc, T* residual)const{// y exp(ax^2 bx c)residual[0] _y - ceres::exp(abc[0] * _x * _x abc[1] * _x abc[2]);return true;}const double _x, _y;
};然后就是模拟观测生成数据点、构造优化问题、求解优化问题函数如下
int fitting_curve(double a, double b, double c)
{
#pragma region 生成数据模拟观测//参数初始化设置abc初始化为0白噪声方差为1使用 OpenCV 的随机数产生器。RNG rng;double w 1;double abc[3] { 0,0,0 };//生成待拟合曲线的数据散点储存在Vector里x_datay_data。vectordouble x_data, y_data;for (int i 0; i 1000; i){double x i / 1000.0;x_data.push_back(x);y_data.push_back(std::exp(a * x * x b * x c) rng.gaussian(w));}
#pragma endregion#pragma region 构建优化问题//反复使用 AddResidualBlock 方法逐个散点反复1000次//将每个点的残差累计求和构建最小二乘优化式//不使用核函数待优化参数是 abcceres::Problem problem;for (int i 0; i 1000; i){problem.AddResidualBlock(new ceres::AutoDiffCostFunctionCURVE_FITTING_COST, 1, 3(new CURVE_FITTING_COST(x_data[i], y_data[i])),nullptr,abc);}
#pragma endregion#pragma region 求解优化问题//配置求解器并求解输出结果ceres::Solver::Options options;options.linear_solver_type ceres::DENSE_QR;options.minimizer_progress_to_stdout true;ceres::Solver::Summary summary;ceres::Solve(options, problem, summary);cout a abc[0] endl;cout b abc[1] endl;cout c abc[2] endl;
#pragma endregionreturn 0;
}结果如下
iter cost cost_change |gradient| |step| tr_ratio tr_radius ls_iter iter_time total_time0 5.277388e06 0.00e00 5.58e04 0.00e00 0.00e00 1.00e04 0 2.05e-04 3.98e-041 4.287886e238 -4.29e238 0.00e00 7.39e02 -8.79e231 5.00e03 1 3.15e-04 7.20e-032 1.094203e238 -1.09e238 0.00e00 7.32e02 -2.24e231 1.25e03 1 1.98e-04 7.68e-033 5.129910e234 -5.13e234 0.00e00 6.96e02 -1.05e228 1.56e02 1 1.43e-04 8.13e-034 1.420558e215 -1.42e215 0.00e00 4.91e02 -2.97e208 9.77e00 1 1.03e-04 8.65e-035 9.607928e166 -9.61e166 0.00e00 1.85e02 -2.23e160 3.05e-01 1 9.73e-05 9.17e-036 7.192680e60 -7.19e60 0.00e00 4.59e01 -2.94e54 4.77e-03 1 1.16e-04 9.62e-037 5.061060e06 2.16e05 2.68e05 1.21e00 2.52e00 1.43e-02 1 2.55e-04 1.03e-028 4.342234e06 7.19e05 9.34e05 8.84e-01 2.08e00 4.29e-02 1 2.29e-04 1.09e-029 2.876001e06 1.47e06 2.06e06 6.42e-01 1.66e00 1.29e-01 1 2.41e-04 1.15e-0210 1.018645e06 1.86e06 2.58e06 4.76e-01 1.38e00 3.86e-01 1 2.55e-04 1.22e-0211 1.357731e05 8.83e05 1.30e06 2.56e-01 1.13e00 1.16e00 1 3.85e-04 1.27e-0212 2.142986e04 1.14e05 2.71e05 8.60e-02 1.03e00 3.48e00 1 2.86e-04 1.34e-0213 1.636436e04 5.07e03 5.94e04 3.01e-02 1.01e00 1.04e01 1 2.83e-04 1.40e-0214 1.270381e04 3.66e03 3.96e04 6.21e-02 9.96e-01 3.13e01 1 3.35e-04 1.46e-0215 6.723500e03 5.98e03 2.68e04 1.30e-01 9.89e-01 9.39e01 1 2.57e-04 1.50e-0216 1.900795e03 4.82e03 1.24e04 1.76e-01 9.90e-01 2.82e02 1 3.14e-04 1.55e-0217 5.933860e02 1.31e03 3.45e03 1.23e-01 9.96e-01 8.45e02 1 2.72e-04 1.59e-0218 5.089437e02 8.44e01 3.46e02 3.77e-02 1.00e00 2.53e03 1 2.34e-04 1.62e-0219 5.071157e02 1.83e00 4.47e01 1.63e-02 1.00e00 7.60e03 1 2.30e-04 1.68e-0220 5.056467e02 1.47e00 3.03e01 3.13e-02 1.00e00 2.28e04 1 2.50e-04 1.73e-0221 5.046313e02 1.02e00 1.23e01 3.82e-02 1.00e00 6.84e04 1 2.77e-04 1.78e-0222 5.044403e02 1.91e-01 2.23e00 2.11e-02 9.99e-01 2.05e05 1 2.33e-04 1.83e-0223 5.044338e02 6.48e-03 1.38e-01 4.35e-03 9.98e-01 6.16e05 1 2.32e-04 1.88e-02
a 3.01325
b 1.97599
c 1.01113麻雀虽小五脏俱全。可以看到笔者将 a 3 , b 2 , c 1 a3, b2, c1 a3,b2,c1 初值都赋为了 0共迭代了 23 次代价从 1 0 238 10^{238} 10238 量级降到了 1 0 2 10^{2} 102 量级最后求出 a 3.01325 , b 1.97599 , c 1.01113 a3.01325, b1.97599, c1.01113 a3.01325,b1.97599,c1.01113与 a 3 , b 2 , c 1 a3, b2, c1 a3,b2,c1 非常接近了。
值得注意的是这个算例中的代码用到了 opencv头文件引用可以参看 3笔者将其简单封装了一下。 结合上面的算例绘制 Ceres 解决优化问题的通用流程图如下所示 Reference Ceres Solver 官方文档 ↩︎ Ceres SolverGoogle高效的非线性优化库 ↩︎ Ceres 实战案例 ↩︎ ↩︎