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

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

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用于计算迭代过程中最关键的一步,添加状态改正量;

                                          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=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()
目录
相关文章
|
算法 机器人 Linux
开源项目推荐:3D点云处理软件CloudCompare,基于Qt和OpenGL
开源项目推荐:3D点云处理软件CloudCompare,基于Qt和OpenGL
6250 0
开源项目推荐:3D点云处理软件CloudCompare,基于Qt和OpenGL
|
自然语言处理 机器人
ROS2教程 08 动作Action
本文是关于ROS2(机器人操作系统2)中动作(Action)机制的教程,详细介绍了动作的概念、ros2 action相关命令的使用,包括列出、发送目标、获取动作信息,并通过示例代码展示了如何创建动作服务端(Action Server)和客户端(Action Client),以及如何实现动作的执行、反馈和结果处理。
934 0
ROS2教程 08 动作Action
|
传感器 自动驾驶 机器人
大疆Livox Mid360 使用指南
本文是大疆Livox Mid-360激光雷达的使用指南,包括Livox Viewer 2的安装与使用、Livox SDK2的安装与演示、Livox ROS的配置与启动,以及一些使用时的注意事项。文章还提供了关于Livox Mid-360的详细特点、接线信息、尺寸信息、主控端IP设置、修改Livox Mid 360的IP方法、坐标系定义和IMU内参的介绍。此外,还提供了官方资料和软件下载的链接。
6536 2
|
算法 定位技术
最优化方法(最速下降、牛顿法、高斯牛顿法、LM算法)
最优化方法(最速下降、牛顿法、高斯牛顿法、LM算法)
1198 0
最优化方法(最速下降、牛顿法、高斯牛顿法、LM算法)
|
人工智能 自动驾驶 机器人
智能时代的桥梁:人工智能与人类协作的未来
随着人工智能技术的不断进步,其在各行各业的应用已经变得越来越广泛。本文将探讨AI技术如何成为连接人与人、人与机器、以及机器与机器的桥梁,促进更高效的协作模式,并分析这种协作对未来社会的影响。我们将通过具体实例来展示人工智能在提升工作效率、解决复杂问题以及创新服务方面的潜力,同时也会触及到这一过程中可能遇到的挑战和伦理问题。
|
存储 安全 JavaScript
Weaver E-Office v9.5 文件上传(CVE-2023-2648)
Weaver E-Office v9.5 文件上传(CVE-2023-2648)
|
机器学习/深度学习 传感器 安全
基于云的强化学习在汽车控制功能开发中的应用
摘要 汽车控制功能变得越来越复杂,其开发也越来越精细,这导致在开发过程中强烈需要自动化解决方案。在这里,强化学习为功能开发提供了巨大的潜力,可以以自动化的方式生成优化的控制功能。尽管它成功地部署在各种控制任务中,但在汽车行业中仍然缺乏基于强化学习的功能开发标准工具解决方案。为了解决这一差距,我们提出了一个灵活的框架,将传统的开发过程与开源的强化学习库相结合。它具有相关车辆部件的模块化物理模型、与微观交通模拟的协同模拟以生成真实场景,并实现分布式和并行训练。我们在一项可行性研究中证明了我们提出的方法的有效性,该研究旨在学习城市交通场景中电动汽车自动纵向控制的控制函数。进化的控制策略产生了一个平滑
419 0
|
人工智能 搜索推荐 开发工具
移动应用开发趋势与技术前景
随着移动互联网的快速发展,移动应用开发已成为当今科技领域的关键焦点之一。本文探讨了当前移动应用开发的最新趋势和技术前景,涵盖了移动操作系统、开发工具及框架的演变,以及未来可能的发展方向。通过分析市场需求和技术创新,展望了移动应用开发领域的潜力和挑战,为相关技术人员提供了重要的参考和启示。
|
算法 程序员 数据库连接
深入探索C++中的RAII原则:资源管理的艺术 (In-Depth Exploration of RAII in C++: The Art of Resource Management)...
深入探索C++中的RAII原则:资源管理的艺术 (In-Depth Exploration of RAII in C++: The Art of Resource Management)...
474 2