美文网首页
Ceres曲线拟合

Ceres曲线拟合

作者: 金戈大王 | 来源:发表于2017-05-31 20:37 被阅读1323次

    本文介绍如何使用Ceres库实现曲线拟合。

    一、曲线拟合

    所谓曲线拟合,就是给定一组x和y的值,它们大体上满足一条曲线方程,但受噪声影响,并不精确满足方程。在这种情况下求取曲线方程的参数。例如,给定100对x和y的值,把它们画在坐标系上如图所示:

    假设我们事先知道(或者猜测)该曲线方程的形式为

    那么就可以构造一个最小二乘问题以估计其中的未知参数a、b和c。该最小二乘问题的代价函数为:

    这是一个非线性优化问题,目标是求使上式最小的a、b和c。

    二、Ceres库

    Ceres是一个C++库,用于求解通用的最小二乘问题,因此非常适合上面介绍的曲线拟合。而且Ceres的使用也非常简单,大体上分为如下三步:

    1. 定义代价函数;
    2. 构建最小二乘问题,向问题中添加误差项,此时会用到第一步的代价函数;
    3. 配置求解器,开始求解。

    下面用一个实际的例子来说明。

    三、使用Ceres库实现曲线拟合

    代码下载地址:https://github.com/jingedawang/CeresCurveFitting

    鉴于代码很简短,不妨先全部贴出来,再仔细讲解。

    #include <iostream>
    #include <opencv2/core/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,     // 模型参数,有3维
                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数据
    };
    
    int main ( int argc, char** argv )
    {
        double a=1.0, b=2.0, c=1.0;         // 真实参数值
        int N=100;                          // 数据点
        double w_sigma=1.0;                 // 噪声Sigma值
        cv::RNG rng;                        // OpenCV随机数产生器
        double abc[3] = {0,0,0};            // abc参数的估计值
    
        stringstream ss;
    
        vector<double> x_data, y_data;      // 数据
    
        cout<<"generating data: "<<endl;
        for ( int i=0; i<N; i++ )
        {
            double x = i/100.0;
            x_data.push_back ( x );
            y_data.push_back (
                    exp ( a*x*x + b*x + c ) + rng.gaussian ( w_sigma )
            );
            cout<<x_data[i]<<" "<<y_data[i]<<endl;
            ss << x_data[i] << " " << y_data[i] << endl;
        }
    
        ofstream file("points.txt");
        file << ss.str();
    
        // 构建最小二乘问题
        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                 // 待估计参数
            );
        }
    
        // 配置求解器
        ceres::Solver::Options options;     // 这里有很多配置项可以填
        options.linear_solver_type = ceres::DENSE_QR;  // 增量方程如何求解
        options.minimizer_progress_to_stdout = true;   // 输出到cout
    
        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()<<" seconds. "<<endl;
    
        // 输出结果
        cout<<summary.BriefReport() <<endl;
        cout<<"estimated a,b,c = ";
        for ( auto a:abc ) cout<<a<<" ";
        cout<<endl;
    
        return 0;
    }
    

    代码中需要关注的有这么几点:

    1. 定义代价函数的方式是自定义一个结构体。只需要在结构体中实现operator()方法,就算是给Ceres提供了一个调用接口,由Ceres内部在合适的时候调用该方法计算代价函数。
    2. 构建最小二乘问题时,需要将所有误差项依次添加进去。而每个误差项又是由前面定义的结构体构成的。需要注意的是,每个误差项需要指定代价函数求导的方法,有三种方法可供选择:自动求导、数值求导和自行推导。一般采用自动求导,既方便,又节约运行时时间。
    3. 以自动求导为例,ceres::AutoDiffCostFunction是个模板类,后两个模板参数为输出维度和输入维度,必须与前面定义的结构体中的residualabc的维度一样。
    4. 使用ceres::Solver::Options配置求解器。这个类有许多字段,每个字段都提供了一些枚举值供用户选择。所以需要时只要查一查文档就知道怎么设置了。

    程序的编译就不赘述了。该程序在0.00104641s内运行完毕,迭代22次,最终将误差从18249降到了50.97。优化结果为:

    estimated a,b,c = 0.891943 2.17039 0.944142

    最终拟合出的曲线如下图所示,还算比较完美了。

    四、参考资料

    《视觉SLAM十四讲》第6讲 非线性优化 高翔
    Ceres Solver Ceres官网
    Ceres优化 徐尚
    自动求导的二三事 Dark_Scope
    Introduction to AUTOMATIC DIFFERENTIATION Alexey Radul

    相关文章

      网友评论

          本文标题:Ceres曲线拟合

          本文链接:https://www.haomeiwen.com/subject/ajmjfxtx.html