Ceres的使用

Ceres官网

Ceres介绍

  • ceres 可以解约束非线性最小二乘问题:
    在这里插入图片描述
  1. f i f_ifi:cost function 代价函数,SLAM中叫做误差项
  2. x j x_jxj:优化变量
  3. ρ i \rho_{i}ρi: 核函数

在这里插入图片描述

在最简单的情况下:核函数ρ i = x    \rho_i=x\space\spaceρi=x  约束条件:l j = − ∞    u j = ∞ l_j=-\infty \space\space u_j=\inftylj=  uj= ,我们得到最熟悉的非线性最小二乘问题:
在这里插入图片描述



求解过程

一般利用Ceres求解非线性优化问题分为三个过程:

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

举例叙述

问题:求解 1 2 ( 10 − x ) 2 \frac{1}{2}(10-x)^221(10x)2的最小值时候的x xx

  1. 构建代价函数
struct CostFunctor
{
    template <typename T>
    bool operator()(const T* const x, T* residual) const
    {
        residual[0] = T(10.0) - x[0];
        return true;
    }
};

residual:残差(一维)
x:模型参数(一维)



  1. 构建优化问题

    double initial_x = 5.0;
    double x = initial_x; //变量初始值


    ceres::Problem  problem;
    CostFunction* cost_function = new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);
    problem.AddResidualBlock(cost_function, NULL, &x);

(1) 先定义一个求解问题:ceres::Problem problem
(2) 使用自动求导,将之前定义的cost function传入,模板参数:误差类型(自定义的仿函数cost function)、输出维度(残差)、输入维度(变量);顺序与cost function定义相反
(3) 添加误差项: (2)中的cost_function的指针,核函数(NULL不使用),x优化变量



  1. 配置求解器
    Solver::Options options;
    options.linear_solver_type=DENSE_QR;  //增量方程如何求解
    options.minimizer_progress_to_stdout=true; //迭代过程信息输出到stdout


    Solver::Summary summary; //优化信息
    Solve(options, &problem, &summary); //开始执行求解


求导方法

  • Automatic Derivatives(自动求导)

效率比数值求导高

  • Numeric Derivatives(数值求导)

  • Analytic Derivatives(解析求导)




曲线拟合

举例

  • 拟合y = e x p ( a x 2 + b x + c ) + w y=exp(ax^2+bx+c)+wy=exp(ax2+bx+c)+w
    a   b   c a \space b \space ca b c是曲线参数,w ww是高斯噪声,假设我们有N(100)个带噪声的数据点,拟合曲线,求曲线参数:
    min ⁡ a b c 1 2 ∑ i = 1 N ∣ ∣ y i − e x p ( a x i 2 + b x i + c ) ∣ ∣ 2 \min \limits_{abc} \frac{1}{2}\sum_{i=1}^{N}||y_i-exp(ax_i^2+bx_i+c)||^2abcmin21i=1Nyiexp(axi2+bxi+c)2

  1. 定义代价函数
#include <iostream>
#include <opencv4/opencv2/core.hpp>
#include <ceres/ceres.h>
#include <chrono>
#include <fstream>

using namespace std;

//构建代价函数
struct CURVE_FITTING_COST
{
    CURVE_FITTING_COST (double x, double y): _x(x), _y(y) {}

    template<typename T>
    bool operator()(const T* const abc, T* residual) const
    {
        residual[0]=T(_y) - ceres::exp(abc[0]*T(_x) * T(_x) + abc[1]*T(_x) +abc[2]);
        return true;
    }

    const double _x, _y;
};





int main()
{
    string fp("../res/data.txt");
    ofstream outfile;
    outfile.open(fp,ios::trunc);

    double a=1.0, b=2.0, c=1.0; //真实的参数
    int N=100 ;  //数据点
    double w_sigma = 1.0;
    cv::RNG rng;
    double abc[3] = {0,0,0};




    // 数据生成
    vector<double> x_data, y_data;
    cout << "gernerating data" << endl;

    for(int i=0; i<N; i++)
    {
        double x = i/100.0, y=0;
        x_data.push_back(x);
        y = exp(a*x*x+b*x+c)+rng.gaussian(w_sigma);
        y_data.push_back(y);

        outfile << x << "\t"<< y << endl;

        cout << x_data[i] << " " << y_data[i] << endl;
    }





    // 构建最小二乘问题
    ceres::Problem problem;
    for(int i=0; i<N; i++)
    {
        problem.AddResidualBlock(new ceres::AutoDiffCostFunction<CURVE_FITTING_COST, 1, 3>(new CURVE_FITTING_COST(x_data[i], y_data[i])), nullptr, abc);

    }


    //配置优化选项
    ceres::Solver::Options options;
    options.linear_solver_type = ceres::DENSE_QR;
    options.minimizer_progress_to_stdout = true;

    ceres::Solver::Summary summary;
    chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
    ceres::Solve(options, &problem, &summary);//进行优化
    chrono::steady_clock::time_point t2 = chrono::steady_clock::now();

    chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2-t1);
    cout << "solve time cost : " <<  time_used.count()  << "miliseconds" << endl;

    cout << summary.BriefReport() << endl;
    cout << "estimate a,b,c = " ;
    for(auto a:abc)cout << a<< " ";
    cout << endl;

    outfile << abc[0] << "\t"<< abc[1] <<  "\t" << abc[2] << endl;
    outfile.close();
    return 0;
}


在这里插入图片描述
估计的参数 a:0.891943 b:2.17039 c:0.944142
原始参数:a: 1 b: 2 b:1

outliers的处理

  • 假如有少数极端值(outliers),那么对参数的估计有很大影响,如:

在这里插入图片描述

这时候可以借助核函数(loss function)来解决,将

problem.AddResidualBlock(new ceres::AutoDiffCostFunction<CURVE_FITTING_COST, 1, 3>(new CURVE_FITTING_COST(x_data[i], y_data[i])), nullptr, abc);

nullptr 改为 new ceres::CauchyLoss(0.5)

problem.AddResidualBlock(new ceres::AutoDiffCostFunction<CURVE_FITTING_COST, 1, 3>(new CURVE_FITTING_COST(x_data[i], y_data[i])), new ceres::CauchyLoss(0.5), abc);

在这里插入图片描述


版权声明:本文为zzyczzyc原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。