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. 掌握 PostgreSQL 的 psql 命令行工具

    title: 掌握 PostgreSQL 的 psql 命令行工具 date: 2024/12/30 updated: 2024/12/30 author: cmdragon excerpt: psq ...

  2. 数组 | 切片 | map | Go语言

    数组 1.数组的长度需要声明 2.存储的数据类型必须一致 3.可以通过下标来访问,超出长度问报访问越界的错误 4.不支持负数索引 5.数组是值类型,传递的都是拷贝,不会对原来的对象进行修改 6.Go中 ...

  3. 今天记录一下管理系统中预览pdf的方法

    在管理系统中,有很多需要预览文件的操作,既方便用户查看又可以不用打开新的页面,我发现一个不错的方法,记录一下 <el-dialog title="" :visible.syn ...

  4. C51--05---LCD1602调试工具

    一.LCD1602调试工具 单片机调试工具: 数码管 液晶屏 串口 数码管需要不断进行扫描,一旦扫描不及时就会不断闪烁,并且可显示的数据太过局限: 串口需要使用电脑进行发送指令,不易操作与携带: 所以 ...

  5. Solution -「LNOI 2022」「洛谷 P8367」盒

    \(\mathscr{Desription}\)   Link.   有 \(n\) 个盒子排成一排,第 \(i\) 个盒子内有 \(a_i\) 个球.球可以在相邻盒子间传递,\(i\) 与 \(i+ ...

  6. 远程连接Windows

    远程桌面连接 限制 1.同网段 (1)服务器关闭防火墙 (2)服务器端 右键点击'我的电脑'进入'属性'点击左侧菜单栏中的'远程设置': 把远程桌面选项设置成'允许运行任意版本远程桌面的计算机连接'. ...

  7. Doris端口列表

    实例名称 端口名称 默认端口 通讯方向 说明 BE be_port 9060 FE --> BE BE 上 thrift server 的端口,用于接收来自 FE 的请求 BE webserve ...

  8. Spark Sql调优

    一.任务调参 1.1 spark.executor.memory executor执行分配的内存大小 1.2 spark.executor.cores executor执行分配的核数 1.3 spar ...

  9. webgpu学习问题,遇到了create graphics pipeline state failed错误

    在学习webgpu并渲染图像时发生create graphics pipeline state failed with E_INVALIDARG (0x80070057)错误,图像无法成功渲染. ht ...

  10. Zookeeper - 本地模式部署

    本地模式部署 zoo.cfg 参数解析 本地模式部署 1.上传zookeeper的安装包并解压 tar -zxvf zookeeper-x.x.x.tar.gz -c /xxx/xxx/ 2.将 zo ...