G2O (General Graph Optimization)入门及简单使用

news2024/9/21 14:41:14

g2o全称是General Graph Optimization,也就是图优化,我们在做SLAM后端或者更加常见的任何优化问题(曲线拟合)都可以使用G2O进行处理。

先放出本文的几个参考链接:

半闲居士(高翔博士)

非线性优化库g2o使用教程,探索一些常见的用法,以及信息矩阵、鲁棒核函数对于优化的结果的影响

g2o库简单入门

G2O结构介绍

在这里插入图片描述
从SparseOptimizer开始看起,我们最终要使用的优化器就是它。它是一个Optimizable Graph,从而也是一个Hyper Graph。一个 SparseOptimizer 含有很多个顶点 (都继承自 Base Vertex)和很多个边(继承自 BaseUnaryEdge, BaseBinaryEdge或BaseMultiEdge)。这些 Base Vertex 和 Base Edge 都是抽象的基类,而实际用的顶点和边,都是它们的派生类。我们用 SparseOptimizer.addVertex 和 SparseOptimizer.addEdge 向一个图中添加顶点和边,最后调用 SparseOptimizer.optimize 完成优化。

在进行优化之前,需要指定我们用的求解器和迭代算法。从图中下半部分可以看到,一个 SparseOptimizer 拥有一个 Optimization Algorithm,继承自Gauss-Newton, Levernberg-Marquardt, Powell’s dogleg 三者之一(我们常用的是GN或LM、DL)。同时,这个 Optimization Algorithm 拥有一个Solver,它含有两个部分:

  • 一个是 SparseBlockMatrix ,用于计算稀疏的雅可比和海塞矩阵;
  • 一个是LinearSolver用于计算迭代过程中最关键的一步,添加状态改正量;

H △ x = − b . H△x = -b. Hx=b.

准备数据

准备要进行拟合的数据,加上噪声:

    int numPoints = 200;
    double a = 1.;
    double b = 2;
    double c = 3;
    Eigen::Vector2d *points = new Eigen::Vector2d[numPoints];
    ofstream points_file("../points.txt", ios::out);

    //准备用于拟合的数据  加上噪声
    for (int i = 0; i < numPoints; ++i) {
        double x = g2o::Sampler::uniformRand(0, 10);
        double y = sin(a*x) + cos(b*x) + c;
        y += g2o::Sampler::gaussRand(0, 0.1);

        points[i].x() = x;
        points[i].y() = y;
        points_file << x << " " << y << endl;
    }
    points_file.close();

定义顶点与边

G2O已经给我们内置定义好了很多类型的顶点与边,但是我们在使用过程中可能要根据自己的需要重新定义。例如,我们想要求解一个曲线拟合优化的问题,曲线的真实方程为:
y = s i n ( a x ) + c o s ( b x ) + c . 其中 a = 1 , b = 2 , c = 3 y= sin(ax)+cos(bx)+c. 其中a = 1,b = 2,c=3 y=sin(ax)+cos(bx)+c.其中a=1,b=2,c=3
则针对这一问题,顶点为我们所需要求解的a,b,c,边就为预测值与观测值之间的差值。

  • 构造顶点:
class VertexParams : public g2o::BaseVertex<3, Eigen::Vector3d> {
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;

    VertexParams() = default;

    bool read(std::istream & /*is*/) override {
        cerr << __PRETTY_FUNCTION__ << " not implemented yet" << endl;
        return false;
    }

    bool write(std::ostream & /*os*/) const override {
        cerr << __PRETTY_FUNCTION__ << " not implemented yet" << endl;
        return false;
    }

    //该函数作用是更新顶点的估计值
    void setToOriginImpl() override {
        cerr << __PRETTY_FUNCTION__ << " not implemented yet" << endl;
    }

    //更新优化之后的顶点
    void oplusImpl(const double *update) override {
        Eigen::Vector3d::ConstMapType v(update);
        _estimate += v;
    }
};
  • 构造边:
/*!
 * 从BaseUnaryEdge继承得到一元边
 */
class EdgePointOnCurve : public g2o::BaseUnaryEdge<1, Eigen::Vector2d, VertexParams> {
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    EdgePointOnCurve() = default;

    bool read(std::istream & /*is*/) override {
        cerr << __PRETTY_FUNCTION__ << " not implemented yet" << endl;
        return false;
    }

    bool write(std::ostream & /*os*/) const override {
        cerr << __PRETTY_FUNCTION__ << " not implemented yet" << endl;
        return false;
    }

    //边的误差计算
    void computeError() override {
        const VertexParams *params = dynamic_cast<const VertexParams *>(vertex(0));//顶点
        const double &a = params->estimate()(0);
        const double &b = params->estimate()(1);
        const double &c = params->estimate()(2);
        // double fval = a * exp(-lambda * measurement()(0)) + b;
        double fval = sin(a * measurement()(0)) + cos(b * measurement()(0)) + c;
        _error(0) = std::abs(fval - measurement()(1));
    }
};

构建G2O优化器

g2o在使用过程中主要包括三种数据:

  • 顶点:待优化的变量(状态)
  • 边:顶点之间的约束关系,常用误差表示
  • 求解器:线性方程求解器,从 PCG, CSparse, Choldmod中选,实际则来自 g2o/solvers 文件夹

因此,在g2o优化器定义过程中可以通过下述步骤实现:

    g2o::SparseOptimizer optimizer;
    // 优化器类型为LM
    string solver_type = "lm_var";
    // 优化器生成器
    g2o::OptimizationAlgorithmFactory *solver_factory = g2o::OptimizationAlgorithmFactory::instance();
    // 存储优化器性质
    g2o::OptimizationAlgorithmProperty solver_property;
    // 生成优化器
    g2o::OptimizationAlgorithm *solver = solver_factory->construct(solver_type, solver_property);
    
    optimizer.setAlgorithm(solver);
    // 判断是否构建成功
    if (!optimizer.solver()) {
       std::cout << "G2O 优化器创建失败!" << std::endl;
    }

设置初值,添加顶点与边

针对我们目前要求解的这一问题,顶点为我们所需要拟合的曲线系数a,b,c,边就为预测值(拟合出a,b,c之后代入公式计算的预测值)与观测值(生成的带噪声数据)之间的差值。

	VertexParams *params = new VertexParams();
    params->setId(0);
    params->setEstimate(Eigen::Vector3d(0.7, 2.4, 2));//初始化顶点的估计值
    // 添加顶点(待求解的a b c)
    optimizer.addVertex(params);
    
    for (int i = 0; i < numPoints; ++i) {
        EdgePointOnCurve *e = new EdgePointOnCurve;
        e->setInformation(Eigen::Matrix<double, 1, 1>::Identity());
        e->setVertex(0, params);
        e->setMeasurement(points[i]);
        // 添加边
        optimizer.addEdge(e);
    }

添加的顶点只有一个,边有很多条,其中,我们所使用的边为一元边,其链接的顶点只是一个。所构成的图如下所示:
在这里插入图片描述

优化

使用设置好的优化器进行优化:

    optimizer.initializeOptimization();
    optimizer.computeInitialGuess();
    optimizer.computeActiveErrors();

    optimizer.setVerbose(false);
    
    optimizer.optimize(maxIterations);

优化后的结果如下图所示,散点代表观测量,红色曲线为拟合的结果:
在这里插入图片描述

G2O优化源代码

#include <Eigen/Core>
#include <iostream>

#include "g2o/stuff/sampler.h"
#include "g2o/core/sparse_optimizer.h"
#include "g2o/core/block_solver.h"
#include <g2o/core/optimization_algorithm_factory.h>
#include "g2o/core/optimization_algorithm_levenberg.h"
#include "g2o/core/base_vertex.h"
#include "g2o/core/base_unary_edge.h"
#include "g2o/solvers/dense/linear_solver_dense.h"
#include "g2o/core/robust_kernel_impl.h"


using namespace std;

// linerSolver三种求解器,用于计算迭代过程中最关键的一步HΔx=−b
G2O_USE_OPTIMIZATION_LIBRARY(pcg)
G2O_USE_OPTIMIZATION_LIBRARY(cholmod)
G2O_USE_OPTIMIZATION_LIBRARY(csparse)

/*!
 * 继承BaseVertex类,构造顶点
 */
class VertexParams : public g2o::BaseVertex<3, Eigen::Vector3d> {
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;

    VertexParams() = default;

    bool read(std::istream & /*is*/) override {
        cerr << __PRETTY_FUNCTION__ << " not implemented yet" << endl;
        return false;
    }

    bool write(std::ostream & /*os*/) const override {
        cerr << __PRETTY_FUNCTION__ << " not implemented yet" << endl;
        return false;
    }

    //该函数作用是更新顶点的估计值
    void setToOriginImpl() override {
        cerr << __PRETTY_FUNCTION__ << " not implemented yet" << endl;
    }

    //更新优化之后的顶点
    void oplusImpl(const double *update) override {
        Eigen::Vector3d::ConstMapType v(update);
        _estimate += v;
    }
};

/*!
 * 从BaseUnaryEdge继承得到一元边
 */
class EdgePointOnCurve : public g2o::BaseUnaryEdge<1, Eigen::Vector2d, VertexParams> {
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    EdgePointOnCurve() = default;

    bool read(std::istream & /*is*/) override {
        cerr << __PRETTY_FUNCTION__ << " not implemented yet" << endl;
        return false;
    }

    bool write(std::ostream & /*os*/) const override {
        cerr << __PRETTY_FUNCTION__ << " not implemented yet" << endl;
        return false;
    }

    //边的误差计算
    void computeError() override {
        const VertexParams *params = dynamic_cast<const VertexParams *>(vertex(0));//顶点
        const double &a = params->estimate()(0);
        const double &b = params->estimate()(1);
        const double &c = params->estimate()(2);
        // double fval = a * exp(-lambda * measurement()(0)) + b;
        double fval = sin(a * measurement()(0)) + cos(b * measurement()(0)) + c;
        _error(0) = std::abs(fval - measurement()(1));
    }
};

int main(int argc, char **argv) {
    int numPoints = 200;
    int maxIterations = 50;
    bool verbose = true;

    double a = 1.;
    double b = 2;
    double c = 3;
    Eigen::Vector2d *points = new Eigen::Vector2d[numPoints];
    ofstream points_file("../points.txt", ios::out);

    //准备用于拟合的数据  加上噪声
    for (int i = 0; i < numPoints; ++i) {
        double x = g2o::Sampler::uniformRand(0, 10);
        double y = sin(a*x) + cos(b*x) + c;
        y += g2o::Sampler::gaussRand(0, 0.1);

        // if (i == 20) {
        //     x = 8;
        //     y = 2.5;
        // }

        points[i].x() = x;
        points[i].y() = y;
        points_file << x << " " << y << endl;
    }
    points_file.close();

    g2o::SparseOptimizer optimizer;
    // 优化器类型
    string solver_type = "lm_var";
    // 优化器生成器
    g2o::OptimizationAlgorithmFactory *solver_factory = g2o::OptimizationAlgorithmFactory::instance();
    // 存储优化器性质
    g2o::OptimizationAlgorithmProperty solver_property;
    // 生成优化器
    g2o::OptimizationAlgorithm *solver = solver_factory->construct(solver_type, solver_property);
    
    optimizer.setAlgorithm(solver);
    
    if (!optimizer.solver()) {
       std::cout << "G2O 优化器创建失败!" << std::endl;
    }

    VertexParams *params = new VertexParams();
    params->setId(0);
    params->setEstimate(Eigen::Vector3d(0.7, 2.4, 2));//初始化顶点的估计值
    optimizer.addVertex(params);
    
    for (int i = 0; i < numPoints; ++i) {
        EdgePointOnCurve *e = new EdgePointOnCurve;
        e->setInformation(Eigen::Matrix<double, 1, 1>::Identity());

        // if (i == 20) {
        //     e->setInformation(Eigen::Matrix<double, 1, 1>::Identity() * 10);
        // }

        e->setVertex(0, params);
        e->setMeasurement(points[i]);

        // g2o::RobustKernelHuber *robust_kernel_huber = new g2o::RobustKernelHuber;
        // robust_kernel_huber->setDelta(0.1);
        // e->setRobustKernel(robust_kernel_huber);

        optimizer.addEdge(e);
    }
    
    
    optimizer.initializeOptimization();
    optimizer.computeInitialGuess();
    optimizer.computeActiveErrors();

    optimizer.setVerbose(false);
    
    optimizer.optimize(maxIterations);

    ofstream result_file("../result.txt");
    result_file << params->estimate()[0] << " "
                << params->estimate()[1] << " "
                << params->estimate()[2];
    result_file.close();

    cout << endl << "a, b, c: "
         << params->estimate()[0] << ", "
         << params->estimate()[1] << ", "
         << params->estimate()[2] << endl;

    delete[] points;

    return 0;
}

画图源代码

import numpy as np
import matplotlib.pyplot as plt

filename = './points.txt'
X, Y = [], []
with open(filename, 'r') as f:
    lines = f.readlines()
    for line in lines:
        value = [float(s) for s in line.split()]
        X.append(float(value[0]))
        Y.append(float(value[1]))

result_name = './result.txt'
with open(result_name, 'r') as r:
    lines = r.readlines()
    for line in lines:
        value = [float(s) for s in line.split()]
        a = float(value[0])
        b = float(value[1])
        c = float(value[2])

x = np.linspace(0, 10, 100)
y = np.sin(a*x) + np.cos(b*x) + c

plt.plot(x, y, 'r')
plt.scatter(X, Y)
plt.show()

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能终端信息安全概念(一):开篇

最近屁事以及自己对于时间的把控太差了&#xff0c;还有就是一个师妹最近让辅导作业&#xff0c;很烦。 回归正轨&#xff0c;好好学习Linux驱动的方面。 在实际的Linux驱动中&#xff0c;Linux内核尽量做得更多&#xff0c;以便于底层的驱动可以做得更少。 而且&#xff0c…

【C++笔试强训】第十七天

&#x1f387;C笔试强训 博客主页&#xff1a;一起去看日落吗分享博主的C刷题日常&#xff0c;大家一起学习博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a;夜色难免微凉&#xff0c;前方必有曙光 &#x1f31e;。 &#x1f4a6;&a…

【微服务容器化】第四章-Docker应用部署

&#x1f334;第四章 Docker应用部署&#x1f343;4.1 Mysql部署&#x1f343;4.2 Tomcat部署&#x1f343;4.3 Nginx 部署&#x1f343;4.4 redis 部署&#x1f343;4.1 Mysql部署 分析: 容器内的网络服务和外部机器不能直接通信 外部机器和宿主机可以直接通信 宿主机和容器…

因为有了它,我用舍友玩王者的时间拿到了华为offer

随时随地刷Leetcode题的方法&#xff0c;大学因为这个&#xff0c;我入职大厂! 下面有免费试用网址 目录我如何拿到大厂offer秘密武器使用教程VSCODE云IDE介绍完整功能&#xff0c;兼容VS Code安装的插件没有效果&#xff1f;通过终端启动项目后无法预览&#xff1f;CIDE收费…

sklearn笔记:调参

1 介绍 超参数是不直接在估计器中学习的参数。 在 scikit-learn 中&#xff0c;它们作为参数传递给估计器类的构造函数。 需要搜索超参数空间以获得最佳交叉验证分数。scikit-learn 中提供了两种通用的参数搜索方法&#xff1a; 对于给定的值&#xff0c;GridSearchCV 会详尽地…

客快物流大数据项目(八十四):Impala优化

文章目录 Impala优化 一、​​​​​​​Impala关键配置 二、Impala查询分析

C++ Reference: Standard C++ Library reference: C Library: cstring: strspn

C官网参考链接&#xff1a;strspnhttps://cplusplus.com/reference/cstring/strspn/strspn 函数 <cstring> strspn size_t strspn ( const char * str1, const char * str2 ); 获取字符串中字符集的跨度 返回str1的初始部分的长度&#xff0c;它只包含str2的一部分字符…

FPGA双线性插值图像缩放详细讲解,上板验证稳定通过,提供两套工程源码

开局直接放大招&#xff1a;提供源码及工程&#xff1b; 重点讲解双线性插值图像缩放&#xff1b; 此功能模块使用HLS实现&#xff0c;并已封装导出IP&#xff0c;可在工程中添加并使用&#xff0c;可提供HLS工程源码&#xff1b; 若是用verilog实现双线性插值图像缩放&#xf…

【Java中23种面试常考的设计模式之组合模式(Composite)---结构型模式】

【Java中23种面试常考的设计模式之组合模式(Composite)—结构型模式】 知识回顾: 之前我们讲过的设计模式在这里呦: 【面试最常见的设计模式之单例模式】 【面试最常见的设计模式之工厂模式】 【Java中23种面试常考的设计模式之备忘录模式(Memento)—行为型模式】 【Java中23种…

【每日渗透笔记】后台弱口令+未授权尝试

目录 一、特点&#xff1a; 1.1、特征&#xff1a; 1.2、分析&#xff1a; 1.3、所处情景&#xff1a; 目前&#xff1a; 问题&#xff1a; 二、渗透 一、特点&#xff1a; 1.1、特征&#xff1a; 用户登陆页面 1.2、分析&#xff1a; 毋庸置疑&#xff0c;既然有用户登…

SpringBoot系列之动态生成cron表达式执行定时程序

业务场景 最近需要实现一个功能&#xff0c;根据页面选择的星期&#xff0c;默认是凌晨执行&#xff0c;生成cron表达式&#xff0c;然后定时执行定时程序 环境准备 开发环境 JDK 1.8SpringBoot2.2.1Maven 3.2 开发工具 IntelliJ IDEAsmartGitNavicat15 在IDEA里集成阿里的…

Pycharm 如何自动调整 Python 代码符合 pep8 编码规范

前言 学生时代&#xff0c;写的一手漂亮的好字&#xff0c;能给人留下好的印象。作为 IT 人&#xff0c;写的一手漂亮的代码也会给人留下美好的印象。 代码就是自己的脸面&#xff0c;不管写质量怎样&#xff0c;首先要写的漂亮。Python 有一套 pep8 编码规范标准。 什么是 p…

电感重要参数的理解

电感作为一种储能元件&#xff0c;广泛运用在硬件电路的各个模块。较为常见的有DCDC电路&#xff0c;滤波电路以及振荡电路等。对于电感的选取&#xff0c;大多数人往往只关心感值&#xff0c;感值越大&#xff0c;储能越强&#xff0c;纹波也就越小。然而除了感值以外&#xf…

用Python剪辑视频?太简单了

人生苦短&#xff0c;快学Python&#xff01; 最近我在网上下载一个视频&#xff0c;结果下载到本地是近百个视频片段&#xff0c;为了方便观看只能将这些片段合并为一个视频整体。 不过我并没有搜到能够处理类似情况的小工具&#xff0c;只是发现剪映等软件可以实现视频合并功…

Allegro DFM Ravel Rule丝印文字到测试点pad间距检查

Allegro DFM Ravel Rule丝印文字到测试点pad间距检查 下面介绍丝印到测试点pad间距检查 设置top层丝印文字到孔属性测试点间距,默认值是1mil,可以自己修改这个值 设置bottom层丝印文字到孔属性测试点间距 设置top层丝印文字到通孔pin属性测试点间距 设置bottom层丝印文字…

VAEGAN:理解 VAE 与 GAN【图像生成】

标准VAE(Variational Autoencoder)的原理&#xff1a; 在autoencoder模型中&#xff0c;我们加入一个编码器&#xff0c;它能帮我们把图片编码成向量。然后解码器能够把这些向量恢复成图片。 标准自编码器我们现在获得了一个有点实际用处的网络了。而且我们现在能训练任意多的…

mulesoft Module 4 quiz解析

mulesoft Module 4 quiz1. What is NOT part of a Mule 4 event?2. A Database connector is configured to select rows from a Mysql database.3. What is the minimue required configuration in a flow for a Mule application to compile?4. What is the purpose of the…

Qt读写Excel文件与QXlsx的使用

最近项目比较忙&#xff0c;许久没写博客了&#xff0c;想着还是需要定期整理下学到的和用到的新东西&#xff0c;才有沉淀。刚好最近使用Qt时需要读取excel文件的数据&#xff0c;于是在github找了一个开源库QXlsx&#xff0c;Star数还比较多&#xff0c;应该靠谱&#xff0c;…

【CV】第 2 章:使用本地二进制模式的内容识别

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

MPLS隧道——PE与CE之间运行不同路由协议的情况分析

目录 PE和CE之间运行OSPF路由协议 Ospf的Dmian ID&#xff08;还原属性&#xff09; Ospf的Sham Link属性 OSPF的DN置位与VPN Router Tag PE和CE之间运行BGP路由协议 AS号替换功能 BGP的SoO属性 PE和CE之间可以不同部署不同的路由协议&#xff0c;部署不同的路由协议会存…