06 g2o 学习

文章目录

    • 06 g2o 学习
      • 6.1 概念
      • 6.2 框架简介
      • 6.3 代码示例

06 g2o 学习

6.1 概念

g2o(General Graphic Optimization)是基于图优化的库。图优化是把优化问题表现成图的一种方式。一个图由若干个顶点(Vertex),以及连接这这些顶点的边(Edge)组成。用顶点表示优化变量,用边表示误差项。

那么在 SLAM 中,不同时刻的位姿和路标点为待优化变量即顶点,将他们之间的观测作为边。数学表述为,传感器的观测方程

z k = h ( x k ) z_{k}=h\left(x_{k}\right) zk=h(xk)

实际上二者并不会相等,而是有误差存在

e k = z k − h ( x k ) e_k=z_k-h\left(x_k\right) ek=zkh(xk)

于是,位姿 x k x_k xk 和 路标 z k z_k zk 为待优化变量(图中节点),误差 e k e_k ek 为约束(红色虚线)。

在这里插入图片描述

6.2 框架简介

在这里插入图片描述

几个需要注意的点:

(1)迭代形式为 H Δ x = b H\Delta x=b HΔx=b,也就是求出每次迭代步长 Δ x \Delta x Δx,三个算法可选:高斯牛顿、LM 和 Dog-Leg;

(2)定义顶点:顶点也就是待优化变量,它继承自基础类 BaseVertex<D, T>,其中 D 为 int 类型,表示维度,T 为数据类型。例如

g2o::BaseVertex<3, Eigen::Vector3d>  // 三维点,Eigen::Vector3d 类型

g2o::BaseVertex<6, SE3Quat>  // SE3 变换矩阵,6个参数(平移+旋转)

(3)顶点更新:对于一般的函数,更新策略是 x k + 1 = x k + Δ x x_{k+1}=x_k+\Delta x xk+1=xk+Δx,也就是加上求出的 Δ x \Delta x Δx,而对于位姿 S E ( 3 ) SE(3) SE(3) 这样的数据类型类型来说,要用乘法。

// 顶点更新函数
void curveVetex::oplusImpl(const double* update)
{
    _estimate += Eigen::Vector3d(update);   // 加法更新
}

(4)添加顶点,有多少个顶点就添加多少个。

// 新建顶点
curveVetex* v = new curveVetex();          // 自定义顶点类型
v->setEstimate(Eigen::Vector3d(0,0,0));    // 初始化
v->setId(0);                               // 设置 Id
optimizer.addVertex(v);                    // 加入优化器中

(5)定义边:包括一元边、二元边和多元边。误差=测量值-估计值

在这里插入图片描述

以二元边为例

g2o::BaseBinaryEdge<errorDim, errorType, Vertex1Type, Vertex2Type>   // Vertex1Type 连接的顶点的类名

(6)添加边

EdgePointOnCurve* e = new EdgePointOnCurve;
e->setId(0);                               // 设置 Id
e->setInformation(Eigen::Matrix<double,1,1>::Identity());   // 信息矩阵
e->setVertex(0, v);            // 设置连接的顶点
e->setMeasurement(y_data[i]);    // 观测值
optimizer.addEdge(e); 

(7)主要步骤

  • 定义顶点和边的类型
  • 构建图(添加顶点和边)
  • 选择优化算法
  • 调用 g2o 进行优化,返回结果

通用模板

// 1. 定义顶点
class curveVetex: public g2o::BaseVertex<3,Eigen::Vector3d>
{
    public:
        EIGEN_MAKE_ALIGNED_OPERATOR_NEW
        virtual void setToOriginImpl();   // 顶点初始值,置零
        virtual void oplusImpl(const double* update);  // 更新
        virtual bool read(std::istream &is);          // 读盘、存盘,留空即可
        virtual bool write(std::ostream &os) const;
};

// 2. 添加顶点
curveVetex* v = new curveVetex();          // 自定义顶点类型
v->setEstimate(Eigen::Vector3d(0,0,0));    // 初始化
v->setId(0);                               // 设置 Id
optimizer.addVertex(v);                    // 加入优化器中

// 3. 定义边
class myEdge: public g2o::BaseBinaryEdge<errorDim, errorType, Vertex1Type, Vertex2Type>
{
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
    myEdge(){}
    
    // 计算曲线模型误差=测量值-估计值
    void computeError();
    
    //存取
    bool read(std::istream& is);
    bool write(std::ostream& os) const;
    
    // 增量计算函数:误差对优化变量的偏导数
    void linearizeOplus();
}

// 4. 添加边
EdgePointOnCurve* e = new EdgePointOnCurve;
e->setId(0);                               // 设置 Id
e->setInformation(Eigen::Matrix<double,1,1>::Identity());   // 信息矩阵
e->setVertex(0, v);            // 设置连接的顶点
e->setMeasurement(y_data[i]);    // 观测值
optimizer.addEdge(e); 

6.3 代码示例

拟合函数 y = exp ⁡ ( a x 2 + b x + c ) y=\exp(ax^2+bx+c) y=exp(ax2+bx+c)

显然待优化变量为 a b c abc abc,只有一个顶点;误差值=观测值(实际值)-估计值(理论值),一元边。

在这里插入图片描述

需要注意的是,这里用的三种优化算法,都是误差 e i e_i ei 对代优化变量的偏导数,而不是 F ( x ) \boldsymbol{F}(x) F(x),即

e i = y i − e x p ( a x i 2 + b i x + c ) e_i=y_i-exp(ax_i^2+b_ix+c) ei=yiexp(axi2+bix+c)
∂ e i ∂ a = − x i 2 exp ⁡ ( a x i 2 + b i x + c ) ∂ e i ∂ b = − x i exp ⁡ ( a x i 2 + b i x + c ) ∂ e i ∂ c = − exp ⁡ ( a x i 2 + b i x + c ) \frac{ \partial e_i }{ \partial a}=-x_i^2\exp(ax_i^2+b_ix+c) \\ \frac{ \partial e_i }{ \partial b}=-x_i\exp(ax_i^2+b_ix+c) \\ \frac{ \partial e_i }{ \partial c}=-\exp(ax_i^2+b_ix+c) aei=xi2exp(axi2+bix+c)bei=xiexp(axi2+bix+c)cei=exp(axi2+bix+c)

关于 g2o 使用的几个问题

(1)安装:

安装依赖项:

sudo apt-get install libqt4-dev qt4-qmake libqglviewer-dev libsuitesparse-dev libcxsparse3.1.2 libcholmod-dev

安装下列命令依次执行安装:

git clone https://github.com/RainerKuemmerle/g2o.git
cd g2o
mkdir build
cd build
cmake ..
make
sudo make install

安装完成后在目录/usr/local/includ 下能找到 g2o 目录,在 /usr/local/lib 下能找到libg2o_**.so的文件。

(2)cmakelists.txt

include_directories( ${G2O_INCLUDE_DIRS})
SET(G2O_LIBS g2o_cli g2o_ext_freeglut_minimal g2o_simulator g2o_solver_slam2d_linear g2o_types_icp g2o_types_slam2d g2o_core g2o_interface g2o_solver_csparse g2o_solver_structure_only g2o_types_sba g2o_types_slam3d g2o_csparse_extension g2o_opengl_helper g2o_solver_dense g2o_stuff g2o_types_sclam2d g2o_parser g2o_solver_pcg g2o_types_data g2o_types_sim3 cxsparse )

include_directories("/usr/include/eigen3")

add_executable(g2oCurveFitting ./src/g2oCurveFitting.cpp)
target_link_libraries(g2oCurveFitting ${G2O_LIBS})

(3)编译过程中遇到如下错误

error while loading shared libraries: libg2o_core.so: cannot open shared object file: No such file or directory

出现这个问题的主要原因是,新安装的 g2o 没能生效。执行

sudo ldconfig

代码

/***********************************************************                                          *
* Time: 2023/8/27
* Author: xiaocong
* Function: g2o
* 注意这里用的是 ae * xi * xi + be * xi + sin(ce)
***********************************************************/

#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_gauss_newton.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/core/optimization_algorithm_dogleg.h>
#include <g2o/solvers/dense/linear_solver_dense.h>
#include <eigen3/Eigen/Core>
#include <cmath>

const int N = 100;                 // 数据点个数

using namespace std;

// 定义顶点即待优化变量 abc
class CurveFittingVertex : public g2o::BaseVertex<3, Eigen::Vector3d>
{
public:

    EIGEN_MAKE_ALIGNED_OPERATOR_NEW

        // 设置初始值
        virtual void setToOriginImpl()
    {
        _estimate << 0, 0, 0;
    }

    // 更新,直接加上 delta_x
    virtual void oplusImpl(const double* update)
    {
        _estimate += Eigen::Vector3d(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) : _x(x) {}

    // 计算曲线模型误差=测量值-估计值
    virtual void computeError()
    {
        // 取出 _vertices 中的第一个顶点,强制转换为 CurveFittingVertex* 类型
        const CurveFittingVertex* v = static_cast<const CurveFittingVertex*> (_vertices[0]);

        const Eigen::Vector3d abc = v->estimate();
        _error(0, 0) = _measurement - std::exp(abc(0, 0) * _x * _x + abc(1, 0) * _x + abc(2, 0));
    }

    // 计算误差对优化变量的雅可比矩阵
    virtual void linearizeOplus()
    {
        const CurveFittingVertex* v = static_cast<const CurveFittingVertex*> (_vertices[0]);
        const Eigen::Vector3d abc = v->estimate();

        double y = exp(abc[0] * _x * _x + abc[1] * _x + abc[2]);
        _jacobianOplusXi[0] = -_x * _x * y;
        _jacobianOplusXi[1] = -_x * y;
        _jacobianOplusXi[2] = -y;
    }

    // 存盘和读盘:留空
    virtual bool read(istream& in) {}

    virtual bool write(ostream& out) const {}

public:
    double _x;   //x 值, y 值为 _measurement
};


int main()
{
    double ar = 1.0, br = 2.0, cr = 1.0;         // 真实参数值

    // 生成数据
    vector<double> x_data, y_data;

    for (int i = 0; i < N; i++)
    {
        double xi = i / 100.0;                                     // [0~1]
        double sigma = 0.02 * (rand() % 1000) / 1000.0 - 0.01;     // 随机噪声,[-0.01, 0.01]
        double yi = exp(ar * xi * xi + br * xi + cr) + sigma;

        x_data.push_back(xi);
        y_data.push_back(yi);
    }


    // 构建图优化
    typedef g2o::BlockSolver< g2o::BlockSolverTraits<3, 1> > Block;  // 每个误差项优化变量维度为3,误差值维度为1
    Block::LinearSolverType* linearSolver = new g2o::LinearSolverDense<Block::PoseMatrixType>(); // 线性方程求解器
    Block* solver_ptr = new Block(linearSolver);      // 矩阵块求解器

    // 高斯牛顿法优化   
    g2o::OptimizationAlgorithmGaussNewton* solver = new g2o::OptimizationAlgorithmGaussNewton(solver_ptr);

    g2o::SparseOptimizer optimizer;     // 图模型
    optimizer.setAlgorithm(solver);     // 设置求解器
    optimizer.setVerbose(true);         // 打开调试输出


    // 向图中添加顶点(只有一个顶点)
    CurveFittingVertex* v = new CurveFittingVertex();
    v->setEstimate(Eigen::Vector3d(0, 0, 0));   // 初始值
    v->setId(0);                                   // 顶点序号
    optimizer.addVertex(v);                        // 加入优化器

    // 向图中添加边
    for (int i = 0; i < N; i++)
    {
        CurveFittingEdge* e = new CurveFittingEdge(x_data[i]);
        e->setId(i);                 // 设置 Id
        e->setVertex(0, v);          // 连接的顶点
        e->setMeasurement(y_data[i]);      // 观测值
        e->setInformation(Eigen::Matrix<double, 1, 1>::Identity());    // 信息矩阵
        optimizer.addEdge(e);       // 加入优化器
    }

    // 执行优化
    cout << "start optimization" << endl;
    optimizer.initializeOptimization();
    optimizer.optimize(100);               // 最大迭代次数

    // 输出优化值
    Eigen::Vector3d abc_estimate = v->estimate();
    cout << "result: " << abc_estimate.transpose() << endl;

    return 0;
}

结果

result: 0.999313  2.00098 0.999658

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/215739.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024品牌营销为何需要提供“情绪价值”和“感官滋养”?徐礼昭

什么是情绪价值&#xff1f; 品牌营销在当今市场中&#xff0c;已经超越了单纯的产品推广和销售&#xff0c;更多地涉及到提供“情绪价值”和“感官滋养”。 情绪价值是指产品或服务能够引发的消费者情感反应和共鸣&#xff0c;从而满足消费者情感需求的一种价值。它与产品的…

蓝桥杯 动态规划

01 数字三角形 #include<bits/stdc.h> using namespace std; const int N105; using lllong long; ll a[N][N],dp[N][N]; int main(){int n;cin>>n;for(int i1;i<n;i){for(int j1;j<i;j){cin>>a[i][j];}}for(int i5;i>1;i--){for(int j1;j<i;j){…

解决SecureFX的中文乱码问题

SecureFX的乱码截图 一般出现乱码问题&#xff0c;看起来会很烦&#xff0c;所以&#xff0c;我们要干掉它。 解决步骤&#xff1a; 1&#xff0c;在SecureFX中&#xff0c;选择“选项”-“全局选项”&#xff0c;打开对话框&#xff0c;不同的版本可能会显示略有不同&#x…

【Openstack Train】十五、glance命令合集

本文介绍了glance组件的常用命令。关于openstack的安装&#xff0c;可以参考以下内容&#xff1a; 【Openstack Train安装】一、虚拟机创建 【Openstack Train安装】二、NTP安装 【Openstack Train安装】三、openstack安装 【Openstack Train安装】四、MariaDB/RabbitMQ 安…

【java+vue+微信小程序项目】从零开始搭建——健身房管理平台(1)spring boot项目搭建、vue项目搭建、微信小程序项目搭建

项目笔记为项目总结笔记,若有错误欢迎指出哟~ 【项目专栏】 【java+vue+微信小程序项目】从零开始搭建——健身房管理平台(1)项目搭建 持续更新中… java+vue+微信小程序项目】从零开始搭建——健身房管理平台 项目简介Java项目搭建(IDEA)1.新建项目2.项目类型3.项目设置4…

Springboot养老院信息管理系统的开发-计算机毕设 附源码 27500

Springboot养老院信息管理系统的开发 摘 要 随着互联网趋势的到来&#xff0c;各行各业都在考虑利用互联网将自己推广出去&#xff0c;最好方式就是建立自己的互联网系统&#xff0c;并对其进行维护和管理。在现实运用中&#xff0c;应用软件的工作规则和开发步骤&#xff0c;…

「Swift」类淘宝商品瀑布流展示

前言&#xff1a;需要做一个类似于淘宝商品页面的瀑布流展示 结构分析&#xff1a; ps&#xff1a;图片来源 思路分析&#xff1a; 该瀑布流主要还是基于UICollectionView进行展示&#xff0c;只是在cell展示的UICollectionViewFlowLayout需要进行相应调整和自定义&#xff…

医药行业:轻松学会超低温冰箱技能

超低温冰箱在医疗、科研和生物领域中扮演着至关重要的角色&#xff0c;用于存储和保护对温度极为敏感的样品和药品。 然而&#xff0c;由于这些冰箱内的温度波动可能导致样品的损坏&#xff0c;因此对超低温冰箱的监控变得至关重要。 客户案例 医疗研究机构 上海某医疗研究机…

【Seata源码学习 】篇六 全局事务提交与回滚

【Seata源码学习 】篇六 全局事务提交与回滚 全局事务提交 TM在RPC远程调用RM后,如果没有出现异常&#xff0c;将向TC发送提交全局事务请求io.seata.tm.api.TransactionalTemplate#execute public Object execute(TransactionalExecutor business) throws Throwable {// 1. …

大文件分片上传、分片进度以及整体进度、断点续传【前端原生、后端 Koa、Node 原生】(一)

分片进度 效果展示&#xff0c;一个分片是 500MB 的 这个分片大小是 10MB 的 大文件分片上传 效果展示 前端 思路 前端的思路&#xff1a;将大文件切分成多个小文件&#xff0c;然后并发给后端。 页面构建 先在页面上写几个组件用来获取文件。 <body><input ty…

网工学习7-配置 GVRP 协议

7.1GARP概述 GARP(Generic Attribute Registration Protocol)是通用属性注册协议的应用&#xff0c;提供 802.1Q 兼容的 VLAN 裁剪 VLAN pruning 功能和在 802.1Q 干线端口 trunk port 上建立动态 VLAN 的功能。 GARP 作为一个属性注册协议的载体&#xff0c;可以用来传播属性…

(1w字一篇理解透Unsafe类)Java魔法类:Unsafe详解

Java魔法类 Unsafe 文章导读&#xff1a;(约12015字&#xff0c;阅读时间大约1小时)1. Unsafe介绍2. Unsafe创建3. Unsafe功能3.1内存操作3.2 内存屏障3.3 对象操作3.4 数组操作3.5 CAS操作3.6 线程调度3.7 Class操作3.8 系统信息 4. 总结 JUC源码中的并发工具类出现过很多次 …

外包干了4年,技术退步太明显了。。。。。

先说一下自己的情况&#xff0c;本科生生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年国庆&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测…

netcore swagger 错误 Failed to load API definition

后端接口报错如下&#xff1a; 前端nswag报错如下&#xff1a; 根据网上查询到的资料说明&#xff0c;说一般swagger这种错误都是控制器里有接口代码异常造成的&#xff0c;通常是接口没有加属性Attribute&#xff0c; 比如[HttpPost("Delete")]、[HttpGet("Del…

基于ssm的疫苗预约系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于ssm的疫苗预约系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring Spri…

从这7点重构品牌企业的业务中台系统|徐礼昭

文&#xff5c;徐礼昭 &#xff08;商派市场负责人&#xff0c;RRL重构零售实验室负责人&#xff09; 重构或者升级企业的数字化系统究竟有多重要&#xff1f; 笔者列举两个简单的数字化项目案例&#xff0c;数据仅供大家参考—— &#xff08;1&#xff09;某连锁企业线上云店…

【OpenCV】计算机视觉图像处理基础知识

目录 前言 推荐 1、OpenCV礼帽操作和黑帽操作 2、Sobel算子理论基础及实际操作 3、Scharr算子简介及相关操作 4、Sobel算子和Scharr算子的比较 5、laplacian算子简介及相关操作 6、Canny边缘检测的原理 6.1 去噪 6.2 梯度运算 6.3 非极大值抑制 6.4 滞后阈值 7、Ca…

mockito加junit实现单元测试笔记

目录 一、简介1.1 单元测试的特点1.2 mock类框架使用场景1.3 常用mock类框架1.3.1 mockito1.3.2 easymock1.3.3 powermock1.3.4 JMockit 二、mockito的单独使用2.1 mock对象与spy对象2.2 初始化mock/spy对象的方式初始化mock/spy对象第1种方式初始化mock/spy对象第2种方式初始化…

数据“表”的增删改查

创建数据表 删除数据表 修改数据表 查看数据表 喜欢点赞收藏&#xff0c;如有疑问&#xff0c;点击链接加入群聊【信创技术交流群】&#xff1a;http://qm.qq.com/cgi-bin/qm/qr?_wv1027&kEjDhISXNgJlMMemn85viUFgIqzkDY3OC&authKey2SKLwlmvTpbqlaQtJ%2FtFXJgHVgl…

全球与中国HDPE管道市场:增长趋势、竞争格局与前景展望

快速成长的人口、快速的经济成长和工业发展增加了对可靠供水系统的需求。工业需要为制造流程、冷却系统和卫生目的提供可靠的水供应。随着国家的发展&#xff0c;它们更加重视基础设施&#xff0c;包括供水系统&#xff0c;以支持工业成长。HDPE管道广泛应用于饮用水和灌溉的配…