Ceres solver 是谷歌开发的一款用于非线性优化的库,在谷歌的开源激光雷达slam项目cartographer中被大量使用。Ceres官网上的文档非常详细地介绍了其具体使用方法,相比于另外一个在slam中被广泛使用的图优化库G2O,ceres的文档可谓相当丰富详细(没有对比就没有伤害,主要是G2O资料太少了,对比起来就显得ceres的很多),下面我就介绍下如何使用ceres库进行简单的非线性优化,给各位ceres的初学者一个低门槛的入门教程,能比较直观地对使用ceres库求解优化问题的过程有一个清晰的了解。
Ceres可以解决有界约束的线性最小二乘问题的形式
min x 1 2 ∑ i ρ i ( ∣ ∣ f i ( x i 1 , . . . , x i k ) ∣ ∣ 2 ) s . t . l j ≤ x j ≤ u j { \min_x \frac{1}{2} \sum_i \rho_i(||f_i(x_{i_1},...,x_{i_k})||^2) \qquad\\ s.t. \quad l_j \leq x_j \leq u_j } xmin21i∑ρi(∣∣fi(xi1,...,xik)∣∣2)s.t.lj≤xj≤uj
这种形式的问题出现在科学和工程的广泛领域——从统计学中的拟合曲线,到计算机视觉中的照片构建三维模型。在本章中,我们将学习如何使用ceres求解器求解。所有源码可以在这里下载:https://github.com/ceres-solver/ceres-solver
表达式: ρ i ( ∣ ∣ f i ( x i 1 , . . . , x i k ) ∣ ∣ 2 ) {\rho_i(||f_i(x_{i_1},...,x_{i_k})||^2) } ρi(∣∣fi(xi1,...,xik)∣∣2) 为残差块(ResidualBlock),这里 f ( ⋅ ) {f(\cdot)} f(⋅)为依赖参数块 [ x i 1 , . . . , x i k ] {[x_{i_1},...,x_{i_k}]} [xi1,...,xik]的代价函数(CostFunction)。在大多数优化问题中,小群标量总是同时出现。例如,定义摄像机姿态的平移矢量的三个分量和四元数的四个分量。我们将这样一组小的标量称为参数块。当然,一个参数块可以只是一个参数。 l j {l_j} lj和 u j {u_j} uj是参数块 x j {x_j} xj上的界限。
ρ i {\rho_i} ρi是一个损失函数(LossFunction),损失函数是一个标量函数,用于减少异常值对非线性最小二乘问题解的影响。
特别的当 ρ i ( x ) = x {\rho_i(x)=x} ρi(x)=x,即为恒等函数,如果 l j = − ∞ {l_j=-\infty} lj=−∞和 l j = − ∞ {l_j=-\infty} lj=−∞我们得到了更熟悉的非线性最小二乘问题。
1 2 ∑ i ∣ ∣ f i ( x i 1 , . . . , x i k ) ∣ ∣ 2 { \frac{1}{2} \sum_i ||f_i(x_{i_1},...,x_{i_k})||^2 } 21i∑∣∣fi(xi1,...,xik)∣∣2
首先,考虑寻找函数的最小值的问题
1 2 ( 10 − x ) 2 {\frac{1}{2}(10-x)^2} 21(10−x)2
这是一个很小的问题。其最小值位于 x = 10 {x=10} x=10。但这是一个很好的说明Ceres问题基础的例子。
第一步是构造一个函数去估计函数: f ( x ) = 10 − x {f(x)=10-x} f(x)=10−x
struct CostFunctor {
template
bool operator()(const T* const x, T* residual) const {
residual[0] = T(10.0) - x[0];
return true;
}
};
值得注意的一件重要事情是, operator() 是一个模板化的方法,它假定所有的输入和输出是某种类型T的使用模板调用允许CeresCostFunctor::operator< T >()
,与T =double时剩余的价值是必要的,和一种特殊类型T =Jet,雅克比是必要的。在衍生品中,我们将更详细地讨论向谷神星提供衍生品的各种方法。
一旦我们有了计算残差的函数的方法,现在是时候构造一个非线性最小值了。平方问题,让Ceres来解决
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
// The variable to solve for with its initial value.
double initial_x = 5.0;
double x = initial_x;
// Build the problem.
Problem problem;
// Set up the only cost function (also known as residual). This uses
// auto-differentiation to obtain the derivative (jacobian).
CostFunction* cost_function =
new AutoDiffCostFunction(new CostFunctor);
problem.AddResidualBlock(cost_function, NULL, &x);
// Run the solver!
Solver::Options options;
options.linear_solver_type = ceres::DENSE_QR;
options.minimizer_progress_to_stdout = true;
Solver::Summary summary;
Solve(options, &problem, &summary);
std::cout << summary.BriefReport() << "\n";
std::cout << "x : " << initial_x
<< " -> " << x << "\n";
return 0;
}
自动求导函数将CostFunctor作为输入,自动微分它,并给它一个代价函数接口。
编译运行 examples/helloworld.cc 得到:
iter cost cost_change |gradient| |step| tr_ratio tr_radius ls_iter iter_time total_time
0 4.512500e+01 0.00e+00 9.50e+00 0.00e+00 0.00e+00 1.00e+04 0 5.33e-04 3.46e-03
1 4.511598e-07 4.51e+01 9.50e-04 9.50e+00 1.00e+00 3.00e+04 1 5.00e-04 4.05e-03
2 5.012552e-16 4.51e-07 3.17e-08 9.50e-04 1.00e+00 9.00e+04 1 1.60e-05 4.09e-03
Ceres Solver Report: Iterations: 2, Initial cost: 4.512500e+01, Final cost: 5.012552e-16, Termination: CONVERGENCE
x : 0.5 -> 10