• ceresCurveFitting代码笔记


    ceres使用基本流程
    1 构建代价函数
    2 通过代价函数构建待求解的问题
    3 配置求解器并求解问题

    从简单的开始,比如现在需要求解下面这个函数的最小值:
    \((10-x)^2 \over 2\)

    1 构建代价函数

    struct CostFunctor{
    
        template<typename T>
        bool operator()(const T* const x,T*residual)const{
            residual[0]=10.0-x[0];
            return true;
        }
    };  
    

    2 通过代价函数构建待求解问题

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

    3配置求解器求解问题

        // Run the solver!
        ceres::Solver::Options options;
        options.linear_solver_type = ceres::DENSE_QR;
        options.minimizer_progress_to_stdout = true;
        ceres::Solver::Summary summary;
        Solve(options, &problem, &summary);
    

    完整代码:

    #include <iostream>
    #include<ceres/ceres.h>
    
    using namespace std;
    
    struct CostFunctor{
        template<typename T>
        bool operator()(const T* const x,T*residual)const{
            residual[0]=10.0-x[0];
            return true;
        }
    };
    
    int main() {
        // The variable to solve for with its initial value.(初值)
        double initial_x = 5.0;
        double x = initial_x;
    
        //Build the problem
        ceres::Problem problem;
    
        ceres::CostFunction* cost_function =
                new ceres::AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);
        problem.AddResidualBlock(cost_function, nullptr, &x);
    
        // Run the solver!
        ceres::Solver::Options options;
        options.linear_solver_type = ceres::DENSE_QR;
        options.minimizer_progress_to_stdout = true;
        ceres::Solver::Summary summary;
        Solve(options, &problem, &summary);
    
        std::cout << summary.BriefReport() << "\n";
        std::cout << "x : " << initial_x
                  << " -> " << x << "\n";
        return 0;
    }
    
    

    一个比较复杂的例子:求解y=exp(ax^2+bx+c)
    其中abc为待优化参数,x为随机产生噪声数据

    1构建代价函数

    struct CURVE_FITTING_COST {
      //构造函数:要求传入x和y
      CURVE_FITTING_COST(double x, double y) : _x(x), _y(y) {}
    
      // 残差的计算
      //abc为待优化参数,residual为残差变量
      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]); // y-exp(ax^2+bx+c)
        return true;
      }
      const double _x, _y;    // x,y数据
    }; 
    
    • 构造函数(可选)
      误差函数中的参数包括: 已知参数待优化参数两部分,其中已知参数则在仿函数创建时通过构造函数传入,若优化问题没有已知参数,则不需要编写构造函数。而待优化参数由Problem::AddResidualBlock() 统一添加和管理

    • 重载操作符()(必有)
      操作符()是一个模板方法,返回值为bool型,接受参数为待优化变量残差变量。待优化变量的传入方式应和 Probelm::AddResidualBlock() 一致,即若Probelm::AddResidualBlock()中一次性传入变量数组指针,此处亦应该一次性传入变量数组指针;若Probelm::AddResidualBlock()变量是依次传入,此处亦应该依次传入,且保证变量传入顺序一致。由于在优化过程中,我们不希望因为程序的误操作导致操作符()重载的内容被修改,因此需要为函数体加上const关键字修饰。同理,在残差的计算过程中,为了避免除ceres优化之外的误操作引起待优化变量的改变,需要同时使用const关键字修饰参数类型和参数名保证类型和内容均不变;而residual只需要保证类型不变,参数每次都是可变的,因此只需要使用const修饰类型T即可。

    2 通过代价函数构建待求解的问题

    ceres::Problem problem;
      for (int i = 0; i < N; i++) {
    //写法一:
    //    problem.AddResidualBlock(     // 向问题中添加误差项
    //      // 使用自动求导,模板参数:误差类型,输出维度,输入维度,维数要与前面struct中一致
    //      new ceres::AutoDiffCostFunction<CURVE_FITTING_COST, 1, 3>(new CURVE_FITTING_COST(x_data[i], y_data[i])),
    //      nullptr,            // 核函数,这里不使用,为空
    //      abc                 // 待估计参数(传入的是指针)
    //    );
    
    //写法二:
    //模板参数依次为仿函数(functor)类型CostFunctor,残差维数residualDim和参数维数paramDim,接受参数类型为仿函数指针CostFunctor*
      ceres::CostFunction* costFunction=
                new ceres::AutoDiffCostFunction<CURVE_FITTING_COST,1,3>(new CURVE_FITTING_COST(x_data[i],y_data[i]));   //构造函数传入数据
        problem.AddResidualBlock(costFunction, nullptr,abc);//通过仿函数传入待优化参数
    
      }
    

    AutoDiffCostFunction()

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

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

    AddResidualBlock()

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

    ResidualBlockId Problem::AddResidualBlock(CostFunction *cost_function, 
                                              LossFunction *loss_function,  
                                              const vector<double *> parameter_blocks)
    										  
    ResidualBlockId Problem::AddResidualBlock(CostFunction *cost_function, 
                                              LossFunction *loss_function, 
                                              double *x0, double *x1, ...)
    
    • 代价函数:包含了参数模块的维度信息,内部使用仿函数定义误差函数的计算方式。AddResidualBlock() 函数会检测传入的参数模块是否和代价函数模块中定义的维数一致,维度不一致时程序会强制退出。

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

    • 参数模块:待优化的参数,可一次性传入所有参数的 指针容器 vector<double> 或 依次传入所有参数的指针double

    3 配置求解器求解问题

    ceres::Solver::Options options;     // 这里有很多配置项可以填
    options.linear_solver_type = ceres::DENSE_NORMAL_CHOLESKY;  // 增量方程如何求解
    options.minimizer_progress_to_stdout = true;   // 输出到cout
    ceres::Solver::Summary summary;                // 优化信息
    ceres::Solve(options, &problem, &summary);  // 开始优化
    

    4 输出结果

    cout << summary.BriefReport() << endl;
    cout << "estimated a,b,c = ";
      for (auto a:abc) cout << a << " ";
    
  • 相关阅读:
    BizTalk2010动手实验(二)第一个BizTalk应用
    基于NopCommerce的开源电商系统改造总结
    BizTalk动手实验(六)Orchestration开发
    BizTalk动手实验(三)BizTalk开发综合实验
    项目管理-自上而下还是自下而上的沟通?
    BizTalk 2013 Beta 新特性介绍
    自主开发与带兵打仗
    Word邮件合并IT男必备技能
    三年项目管理,三个阶段
    BizTalk动手实验(八)消息路由
  • 原文地址:https://www.cnblogs.com/hongweijiang/p/15955376.html
Copyright © 2020-2023  润新知