Ceres学习-3.Solver
使用Ceres求解非线性优化问题,一共分为三个部分:
- 第一部分:构建cost fuction,即代价函数,也就是寻优的目标式。参见《Ceres学习-1.CostFunction》https://www.cnblogs.com/vivian187/p/15393995.html
- 第二部分:通过代价函数构建待求解的优化问题。参见《Ceres学习-2.Problem》https://www.cnblogs.com/vivian187/p/15394000.html
- 第三部分:配置求解器参数并求解问题,这个步骤就是设置方程怎么求解、求解过程是否输出等,然后调用一下Solve方法。
这节就讲述第三个部分:
一个简单的应用例子
// 来自于ceres-solver-1.14.0/examples/helloworld.cc
// 第三部分: 配置并运行求解器
// Run the solver!
Solver::Options options;
options.minimizer_progress_to_stdout = true;
options.linear_solver_type = ceres::DENSE_QR;
Solver::Summary summary; // 优化信息
Solve(options, &problem, &summary);// 求解!!!
std::cout << summary.BriefReport() << "\n"; // 输出优化的简要信息
求解最小二乘问题
ceres::Solve函数是Ceres求解最小二乘问题的核心函数,函数原型如下:
// 来自于ceres-solver-1.14.0/include/ceres/solver.h
void Solve(const Solver::Options& options, Problem* problem, Solver::Summary* summary);
参数:
Solver::Options 求解选项。是Ceres求解的核心,包括消元顺序、分解方法、收敛精度等在内的求解器所有行为均由Solver::Options控制。
Problem 求解问题。参考《Ceres学习-2.Problem》
Solver::Summary 求解报告。用于存储求解过程中的相关信息,并不影响求解器性能
参数详解
Solver::Options
Solver::Options含有的参数种类繁多,API文档中对于每个参数的作用和意义都给出了详细的说明。由于在大多数情况下,绝大多数参数我们都会使用Ceres的默认设置。列举了一些可能会改变的参数:
- linear_solver_type:信赖域方法中求解线性方程组所使用的求解器类型,默认为DENSE_QR,其他可选项如下:
DENSE_QR:QR分解,用于小规模最小二乘问题求解;
DENSE_NORMAL_CHOLESKY&SPARSE_NORMAL_CHOLESKY:Cholesky分解,用于具有稀疏性的大规模非线性最小二乘问题求解;
CGNR:使用共轭梯度法求解稀疏方程;
DENSE_SCHUR&SPARSE_SCHUR:SCHUR分解,用于BA问题求解;
ITERATIVE_SCHUR:使用共轭梯度SCHUR求解BA问题;
-
min_linear_solver_iteration/max_linear_solver_iteration:线性求解器的最小/最大迭代次数,默认为0/500,一般不需要更改;
-
max_num_iterations:求解器的最大迭代次数;
-
num_threads:Ceres求解时使用的线程数
-
linear_solver_ordering:线性方程求解器的消元顺序,默认为NULL,即由Ceres自行决定消元顺序;在以BA为典型代表的,对消元顺序有特殊要求的应用中,可以通过成员函数reset设定消元顺序,稍后将详细说明;
linear_solver_ordering
Ceres消元顺序的设置由linear_solver_ordering的reset函数完成,该函数接受参数为ParameterBlockOrdering对象。该对象将所有待优化参数存储为带标记(ID)的组(Group),
ID小的Group在求解线性方程的过程中会被首先消去。因此,我们需要做的第一个工作是调用其成员函数AddElementToGroup将参数添加到对应ID的Group中,函数原型为:
bool ParameterBlockOrdering::AddElementToGroup(const double *element, const int group)
接收的元素为变量数组的指针;组ID为非负整数,最小为0,如果该Id对应的Group不存在,则Ceres会自动创建。下面我们来看一个BA中的例子:
ceres::ParameterBlockOrdering* ordering = new ceres::ParameterBlockOrdering();
// set all points in ordering to 0
for(int i = 0; i < num_points; i++){
ordering->AddElementToGroup(points + i * point_block_size, 0);
}
// set all cameras in ordering to 1
for(int i = 0; i < num_cameras; i++){
ordering->AddElementToGroup(cameras + i * camera_block_size, 1);
}
该例子中,所有路标点被分到了ID = 0组,而所有相机位姿被分到了ID = 1组,因此在线性方程组的求解中,所有路标点会变首先SCHUR消元。
接下来,我们就可以使用reset函数制定线性求解器的消元顺序了:
// set ordering in options
options->linear_solver_ordering.reset(ordering);
在实际应用中,对最终求解性能最大的就是线性方程求解器类型linear_solver_type和线程数,如果发现最后的求解精度或求解效率不能满足要求,应首先尝试更换这两个参数。
Solver::Summary
Solver::Summary包含了求解器本身和求解中各变量的信息,许多成员函数与Solver::Options一致,详细列表同样请参阅API文档,这里只给出另外两个常用的成员函数:
- BriefReport():输出单行的简单总结;
- FullReport():输出多行的完整总结。
实例Bundle Adjustment,参照下面两个网站
https://www.cnblogs.com/vivian187/p/15331483.html 《5.Ceres官方教程-非线性最小二乘~Bundle Adjustment》