Ceres库的入门介绍

一、背景知识简介

Ceres库主要由于求解优化问题,通过对测量获取的不十分可靠的数据和理想化的预测模型进行优化处理获取尽可能接近真实值的结果。本文通过俩个实例来详细说明ceres在优化问题和最小二乘法的求解步骤。

二、优化 1/2*(10−x)2

通过优化求解x值使得1/2(10−x)2最小。
Ceres问题求解主要分成以下三部分:

1. 构建cost fuction,即代价函数,也就是寻优的目标式(通过预测结果和测量值求误差的函数)。这个部分需要使用仿函数(functor)这一技巧来实现;
2. 通过上一步的代价函数构建待求解的优化问题;
3. 配置求解器参数并求解问题,这个步骤就是设置方程怎么求解、求解过程是否输出等,然后调用一下Solve方法。

1、代码

/*
使用Ceres求解非线性优化问题,一共分为三个部分:
1、 第一部分:构建cost fuction,即代价函数,也就是寻优的目标式。这个部分需要使用仿函数(functor)这一技巧来实现,
 做法是定义一个cost function的结构体,在结构体内重载()运算符,具体实现方法后续介绍。
 (这一部分会涉及到具体的函数形式,即通常的误惨差求解函数)
2、 第二部分:通过上一步的代价函数构建待求解的优化问题。
3、 第三部分:配置求解器参数并求解问题,这个步骤就是设置方程怎么求解、求解过程是否输出等,然后调用一下Solve方法。

好了,此时你应该对ceres的大概使用流程有了一个基本的认识。下面我就基于ceres官网上的教程中的一个例程来详细介绍一下ceres的用法。
Ceres官网教程给出的例程中,求解的问题是求x使得1/2(10−x)^2
取到最小值。(很容易心算出x的解应该是10)
好,来看代码:
*/
#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] = T(0.5)*(T(10.0) - x[0])*(T(10.0) - x[0]);//1/2(10−x)^2
        return true;
    }
};

//主函数
int main(int argc, char** argv)
{
    // 寻优参数x的初始值,为5
    double initial_x = 5.0;
    double x = initial_x;

    // 第二部分:构建寻优问题
    //实例化Problem
    ceres::Problem problem;
    //代价函数赋值
    //使用自动求导,将之前的代价函数结构体传入,第一个1是输出维度,即残差的维度,第二个1是输入维度,即待寻优参数x的维度。
//    ceres::CostFunction* cost_function = new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);
    //添加误差项,1、上一步实例化后的代价函数2、核函数3、待优化变量
    problem.AddResidualBlock(new ceres::AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor), nullptr, &x);

    //第三部分: 配置并运行求解器
    ceres::Solver::Options options;
    //配置增量方程的解法,此处为QR求解
    options.linear_solver_type = ceres::DENSE_QR;
    //是否输出到cout
    options.minimizer_progress_to_stdout = true;
    //优化信息
    ceres::Solver::Summary summary;
    //求解!!!1、求解器2、实例化problem 3、优化器
    Solve(options, &problem, &summary);
    //输出优化的简要信息,迭代次数和每次的cost
    std::cout << summary.BriefReport() << "\n";
    //最终结果
    std::cout << "初始值x : " << initial_x<< " 迭代到-> " << x << "\n";
    return 0;
}

运行结果:
iter cost cost_change |gradient| |step| tr_ratio tr_radius ls_iter iter_time total_time
0 7.812500e+01 0.00e+00 6.25e+01 0.00e+00 0.00e+00 1.00e+04 0 3.29e-05 1.29e-04
1 4.884766e+00 7.32e+01 7.81e+00 2.50e+00 9.37e-01 3.00e+04 1 9.92e-05 7.13e-03
2 3.053386e-01 4.58e+00 9.77e-01 1.25e+00 9.37e-01 9.00e+04 1 1.38e-05 7.17e-03
3 1.908451e-02 2.86e-01 1.22e-01 6.25e-01 9.37e-01 2.70e+05 1 1.00e-05 7.19e-03
4 1.192799e-03 1.79e-02 1.53e-02 3.13e-01 9.37e-01 8.10e+05 1 9.06e-06 7.21e-03
5 7.455033e-05 1.12e-03 1.91e-03 1.56e-01 9.37e-01 2.43e+06 1 7.87e-06 7.23e-03
6 4.659404e-06 6.99e-05 2.39e-04 7.81e-02 9.37e-01 7.29e+06 1 1.00e-05 7.25e-03
7 2.912129e-07 4.37e-06 2.98e-05 3.91e-02 9.37e-01 2.19e+07 1 8.82e-06 7.27e-03
8 1.820081e-08 2.73e-07 3.73e-06 1.95e-02 9.37e-01 6.56e+07 1 8.82e-06 7.29e-03
9 1.137551e-09 1.71e-08 4.66e-07 9.77e-03 9.37e-01 1.97e+08 1 1.10e-05 7.32e-03
10 7.109691e-11 1.07e-09 5.82e-08 4.88e-03 9.37e-01 5.90e+08 1 1.12e-05 7.34e-03
11 4.443557e-12 6.67e-11 7.28e-09 2.44e-03 9.37e-01 1.77e+09 1 1.00e-05 7.36e-03
12 2.777223e-13 4.17e-12 9.10e-10 1.22e-03 9.37e-01 5.31e+09 1 1.00e-05 7.38e-03
13 1.735765e-14 2.60e-13 1.14e-10 6.10e-04 9.37e-01 1.59e+10 1 1.00e-05 7.41e-03
14 1.084853e-15 1.63e-14 1.42e-11 3.05e-04 9.37e-01 4.78e+10 1 1.00e-05 7.43e-03
Ceres Solver Report: Iterations: 15, Initial cost: 7.812500e+01, Final cost: 1.084853e-15, Termination: CONVERGENCE
初始值x : 5 迭代到-> 9.99969

Process finished with exit code 0

2、分析

接下来将代码分为三部分进行分析:

(1)构建代价函数CostFunctor结构体
struct CostFunctor {
    //模板函数,泛化函数类型
    template <typename T>
    //重载符号(),仿函数;传入待优化变量列表和承接惨差的变量列表。
    bool operator()(const T* const x, T* residual) const {
        //惨差计算步骤
        residual[0] = T(0.5)*(T(10.0) - x[0])*(T(10.0) - x[0]);//1/2(10−x)^2
        return true;
    }
};

这一步最主要的也是唯一需要根据实际情况需要修改的步骤就是惨差residual的计算,这里通过operator关键字对"()"进行符号重载操作。

(2)构建待优化问题
 //实例化Problem
    ceres::Problem problem;
    //代价函数赋值
    //使用自动求导,将之前的代价函数结构体传入,第一个1是输出维度,即残差的维度,第二个1是输入维度,即待寻优参数x的维度。
//    ceres::CostFunction* cost_function = new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);
    //添加误差项,1、上一步实例化后的代价函数2、核函数3、待优化变量
    problem.AddResidualBlock(new ceres::AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor), nullptr, &x);

这一步最主要的部分是参差块添加函数,AddResidualBlock的使用,涉及到的三个参数分别是(1)自动求导代价函数;(2)是否添加核函数;(3)待优化变量。其中自动求导函数AutoDiffCostFunction可以单独进行,它内部的三个参数分别为(1)上述定义的代价函数结构体;(2)参差维度;(3)待优化变量的维度。

(3)配置优化器执行优化
    ceres::Solver::Options options;
    //配置增量方程的解法,此处为QR求解
    options.linear_solver_type = ceres::DENSE_QR;
    //是否输出到cout
    options.minimizer_progress_to_stdout = true;
    //优化信息
    ceres::Solver::Summary summary;
    //求解!!!1、求解器2、实例化problem 3、优化器
    Solve(options, &problem, &summary);
    //输出优化的简要信息,迭代次数和每次的cost
    std::cout << summary.BriefReport() << "\n";
    //最终结果
    std::cout << "初始值x : " << initial_x<< " 迭代到-> " << x << "\n";

这一部分实现求解器实例化;选择求解方式(这里用QR分解);是否输出运行信息;优化器实例化;调用Slove函数进行问题求解;简要输出执行信息。
其中Slove函数很重要,它负责最后的问题求解,涉及到的三个参数分别是(1)求解器实例化;(2)优化问题实例化;(3)优化器实例化。

三、最小二乘优化expax2+bx+c

通过建立最小二乘法优化求解expax2+bx+c,估算abc参数值。

1、代码

//最小二乘法的Ceres库求解。
#include<iostream>
#include<ceres/ceres.h>
#include<opencv2/core/core.hpp>
using namespace std;

//第一部分:构建代价函数,重载()符号,仿函数的小技巧
struct CostFunctor {
    //成员构造函数赋值
    CostFunctor(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]); // y-exp(ax^2+bx+c)
        return true;
    }
    const double x, y;
};

//主函数
int main(int argc, char** argv)
{
    double areal = 1.0, breal = 2.0, creal = 4.0;// 真实参数值
    double aest = 2.0, best = -1.0, cest = 5.0;// 估计参数值
    int PointNum = 100;
    double w_sigma = 1.0;// 噪声Sigma值
    double inv_sigma = 1.0 / w_sigma;
    cv::RNG rng;// OpenCV随机数产生器
    // 迭代数据存储
    vector<double > x_data, y_data;
    //存储多个范围内的x值和带有噪声的y值,用于后面建立最小二乘法
    for (int i=0; i < PointNum; i++)
    {
        double x = i/100.0;
        x_data.push_back(x);
        y_data.push_back(exp(areal * x * x + breal * x + creal) + rng.gaussian(w_sigma * w_sigma));
    }
    //用于后续求解的承接变量列表
    double abc[3] = {aest, best, cest};

    // 第二部分:构建寻优问题
    //实例化Problem
    ceres::Problem problem;
    //代价函数赋值
    //使用自动求导,将之前的代价函数结构体传入,第一个1是输出维度,即残差的维度,第二个1是输入维度,即待寻优参数x的维度。
//    ceres::CostFunction* cost_function = new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);
    //建立最小二乘法,将所有惨差块加入。
    for (int i=0; i < PointNum; i++)
    {
        //添加误差项,1、上一步实例化后的代价函数2、核函数3、待优化变量
        problem.AddResidualBlock(new ceres::AutoDiffCostFunction<CostFunctor, 1, 3>(new CostFunctor(x_data[i],y_data[i])), nullptr, abc);
    }

    //第三部分: 配置并运行求解器
    ceres::Solver::Options options;
    //配置增量方程的解法,此处为QR求解
    options.linear_solver_type = ceres::DENSE_QR;
    //是否输出到cout
    options.minimizer_progress_to_stdout = true;
    //优化信息
    ceres::Solver::Summary summary;
    //求解!!!1、求解器2、实例化problem 3、优化器
    Solve(options, &problem, &summary);
    //输出优化的简要信息,迭代次数和每次的cost
    cout << summary.BriefReport() << "\n";
    //最终结果
    cout << "初始值 a b c : " << aest << " " << best << " " << cest << endl;
    cout << "真实值 a b c : " << areal << " " << breal << " " << creal<< endl;
    cout << "迭代值 a b c : " << abc[0] << " " << abc[1] << " " << abc[2]<< endl;
    return 0;
}

运行结果:
iter cost cost_change |gradient| |step| tr_ratio tr_radius ls_iter iter_time total_time
0 2.484359e+06 0.00e+00 3.37e+06 0.00e+00 0.00e+00 1.00e+04 0 8.92e-05 2.04e-04
1 4.116716e+06 -1.63e+06 0.00e+00 1.88e+00 -6.57e-01 5.00e+03 1 7.01e-05 3.65e-04
2 4.120729e+06 -1.64e+06 0.00e+00 1.87e+00 -6.59e-01 1.25e+03 1 3.48e-05 4.18e-04
3 4.138801e+06 -1.65e+06 0.00e+00 1.84e+00 -6.66e-01 1.56e+02 1 2.91e-05 4.62e-04
4 4.095474e+06 -1.61e+06 0.00e+00 1.74e+00 -6.49e-01 9.77e+00 1 3.19e-05 5.08e-04
5 2.279406e+06 2.05e+05 1.25e+07 1.30e+00 8.47e-02 6.21e+00 1 8.08e-05 6.03e-04
6 1.388422e+05 2.14e+06 1.69e+06 2.21e-01 9.62e-01 1.86e+01 1 7.61e-05 6.97e-04
7 5.147954e+04 8.74e+04 1.11e+05 1.05e-01 9.88e-01 5.59e+01 1 7.61e-05 7.90e-04
8 4.432203e+04 7.16e+03 3.00e+04 1.78e-01 9.97e-01 1.68e+02 1 6.70e-05 8.74e-04
9 3.132129e+04 1.30e+04 2.79e+04 4.25e-01 9.90e-01 5.03e+02 1 7.20e-05 9.76e-04
10 1.370158e+04 1.76e+04 1.89e+04 8.01e-01 9.77e-01 1.51e+03 1 3.15e-04 1.32e-03
11 2.687570e+03 1.10e+04 6.63e+03 9.45e-01 9.77e-01 4.53e+03 1 1.30e-04 1.48e-03
12 2.068338e+02 2.48e+03 1.13e+03 6.04e-01 9.89e-01 1.36e+04 1 2.25e-04 1.73e-03
13 5.266660e+01 1.54e+02 1.52e+02 1.81e-01 9.98e-01 4.07e+04 1 8.80e-05 1.85e-03
14 5.096990e+01 1.70e+00 4.18e+00 2.05e-02 1.00e+00 1.22e+05 1 8.70e-05 1.97e-03
15 5.096766e+01 2.23e-03 5.99e-02 7.64e-04 1.00e+00 3.67e+05 1 6.79e-05 2.06e-03
Ceres Solver Report: Iterations: 16, Initial cost: 2.484359e+06, Final cost: 5.096766e+01, Termination: CONVERGENCE
初始值 a b c : 2 -1 5
真实值 a b c : 1 2 4
迭代值 a b c : 0.994596 2.00851 3.99722

Process finished with exit code 0

2、分析

和第一个实例步骤不同之处主要有:

  1. 代价函数的构建;
  2. 用于构建最小二乘的数据准备,需要多个点的测量数据;
    for (int i=0; i < PointNum; i++)
    {
        double x = i/100.0;
        x_data.push_back(x);
        y_data.push_back(exp(areal * x * x + breal * x + creal) + rng.gaussian(w_sigma * w_sigma));
    }
  1. 参差块的多次添加(最小二乘问题需要多组数据)和自动求导函数的数据量大小。
    for (int i=0; i < PointNum; i++)
    {
        //添加误差项,1、上一步实例化后的代价函数2、核函数3、待优化变量
        problem.AddResidualBlock(new ceres::AutoDiffCostFunction<CostFunctor, 1, 3>(new CostFunctor(x_data[i],y_data[i])), nullptr, abc);
    }
  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Ceres是一个开源的C++,用于解决非线性最小二乘问题。它提供了一套先进的优化算法和工具,可用于求解各种各样的优化问题,比如相机标定、图像配准、立体视觉、SLAM等。 首先,为了开始使用Ceres,我们需要在计算机上安装它。对于Windows用户,可以从Ceres的官方网站上下载预编译好的二进制文件,并将其添加到系统环境变量中。对于Linux或Mac用户,可以通过命令行安装,并使用包管理器(如apt-get或brew)来安装Ceres。 安装完成后,我们可以在代码中包含Ceres的头文件,并链接相应的文件,以便在程序中使用Ceres的功能。接下来,我们需要定义一个优化问题,并添加待优化的参数、残差函数和约束条件。 在Ceres中,我们可以通过定义一个继承自ceres::CostFunction的类来表示残差函数。同时,在优化问题中可以使用ceres::Problem类来添加和管理这些残差函数。通过构建、配置和解决这个问题,Ceres可以自动寻找最优的参数值,使得所有残差函数的总和最小。 值得一提的是,在使用Ceres时,我们需要定义自己的残差函数,并提供优化问题的初始参数。同时,也可以选择合适的优化算法和迭代次数,以及监控优化过程的输出信息。 总之,Ceres是一个功能强大的开源优化,使用它可以很方便地解决非线性最小二乘问题。通过正确安装和使用Ceres,我们可以有效地求解各种优化问题,并获得最佳的优化结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值