g2o优化库实现曲线拟合

最近学习了一下g2o优化库的基本使用,尝试着自己写了一个曲线拟合的函数,也就是下面这个多项式函数:

\[y = ax^3 + bx^2 + cx + d
\]

我们以 \(a = 3, b = -2, c=5, b=7\)为例,拟合出的图像大概长这样。

下面简单记录一下思路:

目标函数:

\[\min _{a, b, c,d} \frac{1}{2} \sum_{i=1}^N\left\|y_i-(ax_i^3 + bx_i^2 + cx_i + d))\right\|^2 .
\]

目标就是求解出一组 \(a,b,c,d\)使得观测值和估计值的误差最小化。

  • 高斯牛顿法

    推导出每个误差项关于优化变量的导数:

    \[\begin{aligned}
    & \frac{\partial e_i}{\partial a}=-x_i^3\\
    & \frac{\partial e_i}{\partial b}=-x_i^2 \\
    & \frac{\partial e_i}{\partial c}=-x_i \\
    & \frac{\partial e_i}{\partial d}=-1
    \end{aligned}
    \]

    于是 \(\boldsymbol{J}_i=\left[\frac{\partial e_i}{\partial a}, \frac{\partial e_i}{\partial b}, \frac{\partial e_i}{\partial c} \frac{\partial e_i}{\partial d}, \right]^{\mathrm{T}}\), 高斯牛顿法的增量方程为:

    \[\left(\sum_{i=1}^{N} \boldsymbol{J}_i\left(\sigma^2\right)^{-1} \boldsymbol{J}_i^{\mathrm{T}}\right) \Delta \boldsymbol{x}_k=\sum_{i=1}^{N}-\boldsymbol{J}_i\left(\sigma^2\right)^{-1} e_i,
    \]

    代码在附录里,假设要估计的 \(a=3, b=-2,c=5,d=7\),大概只需要三次迭代就能得到估计的结果。

  • g2o

    优化变量构成一个顶点,每个误差项构成一条边。

    顶点类需要重写变量的重置,更新函数;

    边类需要设置关联的顶点,重写误差计算函数,必要时也可以给出解析导数。

​ 下面我们主要测试一下是否给出误差项关于优化变量的解析导数对g2o求解次数的影响(对应的就是g2o_curve.cpp中雅各比矩阵是否注释)

不提供解析导数 提供解析导数
0.01s 0.003s

我们可以看到速度差别还挺大的。

附录

gaussNewton.cpp

//
// Created by xin on 23-6-6.
// use g2o curve fitting y = a*x_3 + b*x_2 + c*x + d
//
#include <iostream>
#include <chrono>
#include <opencv2/opencv.hpp>
#include <Eigen/Core>
#include <Eigen/Dense> using namespace std; int main() { double a = 3.0, b = -2.0, c = 5.0, d = 7; // 真实参数值
double ae = 1, be = 1, ce = 1, de = 100; // 初始参数值 int N = 500; //数据点个数
double w_sigma = 1.0;
double inv_sigma = 1.0 / w_sigma;
cv::RNG rng; vector<double> x_data, y_data;
for (int i = 0; i < N; i++) {
double x = double(i);
x_data.push_back(x);
y_data.push_back(a * pow(x, 3) + b * pow(x, 2) + c*x + d + rng.gaussian(w_sigma * w_sigma));
//std::cout << x << "," << a * pow(x, 3) + b * pow(x, 2) + c * x + d + rng.gaussian(w_sigma * w_sigma) << endl;
} // 开始Gauss-Newton迭代
int iterations = 1000; // 迭代次数
double cost = 0, lastCost = 0; // 本次迭代的cost和上一次迭代的cost chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
for (int iter = 0; iter < iterations; iter++) { Eigen::Matrix4d H = Eigen::Matrix4d::Zero(); // Hessian = J^T W^{-1} J in Gauss-Newton
Eigen::Vector4d b = Eigen::Vector4d::Zero(); // bias
cost = 0; for (int i = 0; i < N; i++) { //求每一个数据点的 J 矩阵
double xi = x_data[i], yi = y_data[i]; // 第i个数据点
double error = yi - (ae*pow(xi, 3) + be*pow(xi, 2) + ce*xi + de);
Eigen::Vector4d J; // 雅可比矩阵
J[0] = -pow(xi, 3);
J[1] = -pow(xi, 2);
J[2] = -xi;
J[3] = -1; H += inv_sigma * inv_sigma * J * J.transpose();
b += -inv_sigma * inv_sigma * error * J; cost += error * error;
} // 求解线性方程 Hx=b
Eigen::Vector4d dx = H.ldlt().solve(b);
if (isnan(dx[0])) {
cout << "result is nan!" << endl;
break;
} if (iter > 0 && cost >= lastCost) {
cout << "cost: " << cost << ">= last cost: " << lastCost << ", break." << endl;
break;
} ae += dx[0];
be += dx[1];
ce += dx[2];
de += dx[3]; lastCost = cost; cout << "total cost: " << cost << ", \t\tupdate: " << dx.transpose() <<
"\t\testimated params: " << ae << "," << be << "," << ce << "," << de << endl;
} 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 << "estimated abcd = " << ae << ", " << be << ", " << ce << "," << de << endl;
return 0; return 0; }

g2o_curve.cpp

//
// Created by xin on 23-6-6.
// use g2o curve fitting y = a*x_3 + b*x_2 + c*x + d
//
#include <iostream>
#include <g2o/core/g2o_core_api.h>
#include <g2o/core/base_vertex.h>
#include <g2o/core/base_unary_edge.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/core/optimization_algorithm_gauss_newton.h>
#include <g2o/core/optimization_algorithm_dogleg.h>
#include <g2o/solvers/dense/linear_solver_dense.h>
#include <Eigen/Core>
#include <opencv2/core/core.hpp>
#include <cmath>
#include <chrono> using namespace std; /// 自定义图优化的顶点 以及 边
class CurveFittingVertex : public g2o::BaseVertex<4, Eigen::Vector4d> {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW; // 重置
virtual void setToOriginImpl() override {
_estimate << 0, 0, 0, 0;
} // 更新
virtual void oplusImpl(const double *update) override {
_estimate += Eigen::Vector4d(update);
} virtual bool read(istream &in) {} virtual bool write(ostream &out) const {}
}; class CurveFittingEdge : public g2o::BaseUnaryEdge<1, double, CurveFittingVertex> { // 误差项的维度, 类型, 关联的顶点
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW; CurveFittingEdge(double x) : BaseUnaryEdge(), _x(x) {} // 误差函数
virtual void computeError() override {
const CurveFittingVertex *v = static_cast<const CurveFittingVertex *> (_vertices[0]); //此处拿到关联的顶点
Eigen::Vector4d abcd = v->estimate();
_error(0, 0) =
_measurement - (abcd[0] * pow(_x, 3) + abcd[1] * pow(_x, 2) + abcd[2] * _x + abcd[3]);
} // 雅各比矩阵,可以注释掉对比一下求解速度
virtual void linearizeOplus() override {
_jacobianOplusXi[0] = -_x * _x * _x;
_jacobianOplusXi[1] = -_x * _x;
_jacobianOplusXi[2] = -_x;
_jacobianOplusXi[3] = -1;
} virtual bool read(istream &in) {} virtual bool write(ostream &out) const {} public:
double _x; // 这个误差项对应的自变量x
}; int main() { double a = 3.0, b = -2.0, c = 5.0, d = 7; // 真实参数值
double ae = 1, be = 1, ce = 1, de = 100; // 初始参数值 int N = 500; //数据点个数
double w_sigma = 1.0;
double inv_sigma = 1.0 / w_sigma;
cv::RNG rng; vector<double> x_data, y_data;
for (int i = 0; i < N; i++) {
double x = double(i);
x_data.push_back(x);
y_data.push_back(a * pow(x, 3) + b * pow(x, 2) + c*x + d + rng.gaussian(w_sigma * w_sigma)); std::cout << x << "," << a * pow(x, 3) + b * pow(x, 2) + c * x + d + rng.gaussian(w_sigma * w_sigma) << endl;
} // 构建图优化
typedef g2o::BlockSolver<g2o::BlockSolverTraits<4, 1>> BlockSolverType;
typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType; // 线性求解器类型 dense CSparse //梯度下降方法
auto solver = new g2o::OptimizationAlgorithmGaussNewton(
g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>())
);
g2o::SparseOptimizer optimizer;
optimizer.setAlgorithm(solver);
optimizer.setVerbose(true); //往图中加入顶点
CurveFittingVertex *v = new CurveFittingVertex();
v->setId(0);
v->setEstimate(Eigen::Vector4d(ae, be, ce, de));
optimizer.addVertex(v); //往图中加入边(误差项)
for(int i = 0; i < N; i++){
CurveFittingEdge *edge = new CurveFittingEdge(x_data[i]);
edge->setId(i);
edge->setVertex(0, v);
edge->setMeasurement(y_data[i]);
edge->setInformation(Eigen::Matrix<double, 1, 1>::Identity() * 1 / (w_sigma * w_sigma));
optimizer.addEdge(edge);
} cout << "starting optimization" << endl;
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
optimizer.initializeOptimization();
optimizer.optimize(100);
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; // output 结果
Eigen::Vector4d abcd_estimate = v->estimate();
cout << "estimated model" << abcd_estimate.transpose() << endl;
return 0; }

g2o优化库实现曲线拟合的更多相关文章

  1. [g2o]C++图优化库

    g2o以图模型表达上述最小二乘问题:比较适合解决SLAM问题 http://openslam.org http://wiki.ros.org/g2o

  2. 嵌入式开发之davinci--- 8148/8168/8127 中的图像处理算法优化库vlib

    The Texas Instruments VLIB is an optimizedImage/Video Processing Functions Library for C programmers ...

  3. [Ceres]C++优化库

    官网教程: http://ceres-solver.org/nnls_tutorial.html 定义了一个最小二乘法求解器 自动求导的功能

  4. 视觉SLAM漫淡(二):图优化理论与g2o的使用

    视觉SLAM漫谈(二):图优化理论与g2o的使用 1    前言以及回顾 各位朋友,自从上一篇<视觉SLAM漫谈>写成以来已经有一段时间了.我收到几位热心读者的邮件.有的希望我介绍一下当前 ...

  5. 深入理解图优化与g2o:图优化篇

    前言 本节我们将深入介绍视觉slam中的主流优化方法——图优化(graph-based optimization).下一节中,介绍一下非常流行的图优化库:g2o. 关于g2o,我13年写过一个文档,然 ...

  6. 视觉slam十四讲开源库安装教程

    目录 前言 1.Eigen线性代数库的安装 2.Sophus李代数库的安装 3.OpenCV计算机视觉库的安装 4.PCL点云库的安装 5.Ceres非线性优化库的安装 6.G2O图优化库的安装 7. ...

  7. Ubuntu16.04安装视觉SLAM环境(g2o)

    1.首先在github上下载g2o图优化库 git clone https://github.com/RainerKuemmerle/g2o.git 2.运行安装以下依赖库 sudo apt-get ...

  8. 第六篇 视觉slam中的优化问题梳理及雅克比推导

    优化问题定义以及求解 通用定义 解决问题的开始一定是定义清楚问题.这里引用g2o的定义. \[ \begin{aligned} \mathbf{F}(\mathbf{x})&=\sum_{k\ ...

  9. 姿态角(RPY)的优化目标函数

    在Pose-Graph的过程中,如果使用G2O优化函数库,那么似乎是不用自己编写代价函数(也就是优化目标函数)的,因为G2O有封装好的SE3等格式,使得Pose-Graph的过程变得简单了,即只需要设 ...

  10. rpc优化

    1.刷文章列表的时候,发现调用总时间100ms ,其中调策略是花了60ms,一个开源的map方法dozer,组装bean要花40ms 2.redis的zounct方法,传 1和-1的时候有时候会返回0 ...

随机推荐

  1. Commit规范

    Commit规范 遵循commit规范是一件很重要的事,不仅更规范,而且也方便其他人查看git记录,所以这篇博客就用来浅记一下commit需要遵循的基本规范. commit格式 在 Angular 规 ...

  2. DVWA靶场JavaScript Attacks漏洞low(低),medium(中等),high(高),impossible(不可能的)所有级别通关教程

    JavaScript Attacks (前端攻击) JavaScript Attacks(前端攻击)漏洞通常涉及利用Web应用程序中的安全漏洞,特别是在JavaScript代码的使用和实现上,从而对用 ...

  3. API接口请求小结

    API接口请求小结 一.python: API接口请求 1.1 multipart/form-data类型请求 参数类型:数组 1.2 multipart/form-data类型请求 参数类型:文件流 ...

  4. Solution -「CF 590E」Birthday

    \(\mathscr{Description}\)   Link.   给定 \(n\) 个字符串 \(S_{1..n}\),选出其一个最大子集 \(T\),使得 \(T\) 中的字符串两两不存在包含 ...

  5. CDS标准视图:催款代码 I_DunningKey

    视图名称:催款代码 I_DunningKey 视图类型:基础 视图代码: 点击查看代码 @AccessControl.authorizationCheck: #NOT_REQUIRED @EndUse ...

  6. Java方法引用、lambda如何序列化&方法引用与lambda底层原理

    系列文章目录和关于我 0.引入 最近笔者使用flink实现一些实时数据清洗(从kafka清洗数据写入到clickhouse)的功能,在编写flink作业后进行上传,发现运行的时候抛出:java.io. ...

  7. ASP 代码示例,可以生成一个8位随机字符串由字母和数字组成

    ChatGP回答的: 下面是一个 ASP 代码示例,可以生成一个8位随机字符串由字母和数字组成: ```Function generateRandomString(length) dim chars, ...

  8. Graph DataBase介绍-图数据库

    前言分析社会关系这类复杂图壮结构的海量数据,使用图形数据库(Graph DataBase)是最好的选择.– 作者:李祎 <程序员>介绍各种NoSQL 数据库的文章已经很多,不过大部分都是基 ...

  9. 字符流:FileReader/FileWriter的使用

    读取文件 1.建立一个流对象,将已存在的一个文件加载进流. FileReader fr = new FileReader(new File("Test.txt"));2.创建一个 ...

  10. Django和FastAPI的比较

    在 Python 的 Web 开发领域,Django 和 FastAPI 是两款备受瞩目的框架. 通过对二者的实践与比较,本文总结了它们的特点与适用场景,希望能给开发者在选择时提供参考. 1. 设计理 ...