g2o模块

1简介

g2o 是一个基于图优化的库,是为了更直观的体现出优化模型的形态,看出各个优化变量和误差项的关联;图优化由顶点和边构成,其中,顶点表示优化变量表示误差项,于是,对于任意一个非线性最小二乘问题,我们可以构建与之对应的的一个包含顶点和边的

2 安装

先安装以下库:

sudo apt install qt5-qmake qt5-default libqglviewer-dev-qt5 libsuitesparse-dev libcxsparse3 libcholmod3

然后下载源码(地址),用 cmake 安装,默认安装在 /usr/local/lib 下,

3 使用实例

3.1 案例1

  • 目标:

    拟合以下曲线:
    y = e x p ( a x 2 + b x + c ) + w y = exp(ax^2 + bx + c) + w y=exp(ax2+bx+c)+w
    在这个方程中, x x x 为一批输入值, y y y 为一批观测值, w w w 为随机噪声,而参数 a a a b b b c c c 是未知,我们的目标就是优化它从而拟合该曲线方程;

  • 分析

    我们可以将此问题转换为一个最小二乘问题:
    m i n 1 2 ∑ i = 1 N ∣ ∣ y i − e x p ( a x i 2 + b x i + c ) ∣ ∣ 2 min \frac{1}{2} \sum_{i=1}^{N} || y_i - exp(ax_i^2 + bx_i + c)||^2 min21i=1Nyiexp(axi2+bxi+c)2
    在这个最小二乘问题中,我们定义误差为:
    e i = y i − e x p ( a x i 2 + b x i + c ) e_i = y_i - exp(ax_i^2 + bx_i + c) ei=yiexp(axi2+bxi+c)
    然后求出误差对参数的偏导:
    ∂ e i ∂ a = − x i 2 e x p ( a x i 2 + b x i + c ) \frac{\partial{e_i}}{\partial{a}} = -x_i^2exp(ax_i^2 + bx_i + c) aei=xi2exp(axi2+bxi+c)
    ∂ e i ∂ b = − x i e x p ( a x i 2 + b x i + c ) \frac{\partial{e_i}}{\partial{b}} = -x_iexp(ax_i^2 + bx_i + c) bei=xiexp(axi2+bxi+c)
    ∂ e i ∂ c = − e x p ( a x i 2 + b x i + c ) \frac{\partial{e_i}}{\partial{c}} = -exp(ax_i^2 + bx_i + c) cei=exp(axi2+bxi+c)
    于是, J = [ ∂ e i ∂ a , ∂ e i ∂ a , ∂ e i ∂ a ] T J = \begin{bmatrix} \frac{\partial{e_i}}{\partial{a}}, \frac{\partial{e_i}}{\partial{a}}, \frac{\partial{e_i}}{\partial{a}} \end{bmatrix}^T J=[aei,aei,aei]T,高斯牛顿的增量方程为:
    ( ∑ i = 1 N J i J i T ) Δ a = ∑ i = 1 N − J i e i (\sum_{i=1}^{N} J_i J_i^T) \Delta a = \sum_{i=1}^{N} -J_i e_i (i=1NJiJiT)Δa=i=1NJiei
    其中, Δ a \Delta a Δa 就是需要迭代产生的参数更新量;

  • 模型

更新值
新顶点
输入值 x
预测值 y
初始化顶点 a
1. 更新误差 2. 更新雅可比矩阵
1. 更新顶点
  • 代码
    :::details
#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>

// 顶点模型
class CurveFittingVertex : public g2o::BaseVertex<3, Eigen::Vector3d> { // 采用顶点模板,并设置维度、类型
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    // 重置顶点
    virtual void setToOriginImpl() override {
        _estimate << 0, 0, 0;
    }

    // 更新顶点
    virtual void oplusImpl(const double *update) override {
        _estimate += Eigen::Vector3d(update);
    }

    virtual bool read(std::istream &in) {}
    virtual bool write(std::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]);
        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() override {
        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(std::istream &in) {}
    virtual bool write(std::ostream &out) const {}

public:
    double _x;
};


int main()
{
    double ar = 3.0, br = 4.0, cr = 1.0; // 真实参数
    double ae = 1.0, be = 3.0, ce = 2.0; // 预测参数
    int N = 100; // 数据个数
    double w_sigma = 1.0; // 数据的噪声
    double inv_sigma = 1.0 / w_sigma; // w_sigma^-1

    // 生成数据
    cv::RNG rng;
    std::vector<double> x_data, y_data;
    for (int i = 0; i < N; i++) {
        double x = i / 100.0;
        x_data.push_back(x);
        y_data.push_back(exp(ar*x*x + br*x + cr) 
                         + rng.gaussian(w_sigma*w_sigma));
    }


    // 设定g2o
    typedef g2o::BlockSolver<g2o::BlockSolverTraits<3, 1>> BlockSolverType; // 顶点类型
    typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType; // 边类型

    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->setEstimate(Eigen::Vector3d(ae, be, ce)); // 待优化的参数设为顶点
    v->setId(0);
    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() * inv_sigma);
        optimizer.addEdge(edge);
    }

    optimizer.initializeOptimization();
    optimizer.optimize(10);

    Eigen::Vector3d abc_estimate = v->estimate();
    std::cout << abc_estimate.transpose() << std::endl;

    return 0;
}

:::

3.2 案例2

  • 已知:

    两幅图像已经匹配的特征点若干,其中一幅图的点为 3d 点 P 1 P_1 P1,另一副图的点为 2d 点 u 1 u_1 u1

  • 目标:

    求这两幅图片之间的位姿变换 T T T

  • 分析:

    在此问题中, P 1 P_1 P1 点经过位姿变换会得到对应的 2d 点像素坐标 u 1 ′ u_1' u1,但是 u 1 ′ u_1' u1 u 1 u_1 u1 有偏差,我们需要最小化这个误差,从而找到最优值 T T T。所以可以得到最小二乘问题:
    T ∗ = arg ⁡ min ⁡ T 1 2 ∑ i = 1 n ∥ u i − 1 s i K T P i ∥ 2 2 T^* = \arg \min_{T}\frac{1}{2}\sum_{i=1}^{n}\|u_i - \frac{1}{s_i}KTP_i \|_2^2 T=argTmin21i=1nuisi1KTPi22
    在这个问题中,要计算的雅可比 J J J误差对位姿 $T$ 的导数
    J = − [ f x Z ′ 0 − f x X ′ Z ′ 2 − f x X ′ Y ′ Z ′ 2 f x + f x X ′ 2 Z ′ 2 − f x Y ′ Z ′ 0 f y Z ′ − f y Y ′ Z ′ 2 − f y − f y Y ′ 2 Z ′ 2 f y X ′ Y ′ Z ′ 2 f y X ′ Z ′ ] J = - \begin{bmatrix} \frac{f_x}{Z'} & 0 & -\frac{f_xX'}{{Z'}^2} & -\frac{f_xX'Y'}{{Z'}^2} & f_x+\frac{f_x{X'}^2}{{Z'}^2} & -\frac{f_xY'}{Z'} \\ 0 & \frac{f_y}{Z'} & -\frac{f_yY'}{{Z'}^2} & -f_y-\frac{f_y{Y'}^2}{{Z'}^2} & \frac{f_yX'Y'}{{Z'}^2} & \frac{f_yX'}{Z'} \end{bmatrix} J=[Zfx00ZfyZ2fxXZ2fyYZ2fxXYfyZ2fyY2fx+Z2fxX2Z2fyXYZfxYZfyX]
    然后用 J J J 计算梯度 Δ T \Delta T ΔT
    ( ∑ i = 1 N J i J i T ) Δ T = ∑ i = 1 N − J i e i (\sum_{i=1}^{N} J_i J_i^T) \Delta T = \sum_{i=1}^{N} -J_i e_i (i=1NJiJiT)ΔT=i=1NJiei
    在g2o中, Δ T \Delta T ΔT 用来更新顶点。顶点与边的关系如图:

  • 代码
    :::details

// 顶点
class VertexPose : public g2o::BaseVertex<6, Sophus::SE3d> {
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;

    // 初始化顶点
    virtual void setToOriginImpl() override {
        _estimate = Sophus::SE3d();
    }

    // 更新顶点:李群左乘小变量
    virtual void oplusImpl(const double *update) override {
        Eigen::Matrix<double, 6, 1> update_eigen;
        update_eigen << update[0], update[1], update[2], update[3], update[4], update[5];
        _estimate = Sophus::SE3d::exp(update_eigen) * _estimate;
    }

    virtual bool read(istream &in) override {}
    virtual bool write(ostream &out) const override {}
};

// 边
class EdgeProjection : public g2o::BaseUnaryEdge<2, Eigen::Vector2d, VertexPose> {
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;

    EdgeProjection(const Eigen::Vector3d &pos, const Eigen::Matrix3d &K) : _pos3d(pos), _K(K) {} // 传入参数:3d点坐标、K

    // 计算误差
    virtual void computeError() override {
        const VertexPose *v = static_cast<VertexPose *> (_vertices[0]);
        Sophus::SE3d T = v->estimate();
        Eigen::Vector3d pos_pixel = _K * (T * _pos3d);
        pos_pixel /= pos_pixel[2];
        _error = _measurement - pos_pixel.head<2>();
    }

    // 计算雅可比
    virtual void linearizeOplus() override {
        const VertexPose *v = static_cast<VertexPose *> (_vertices[0]);
        Sophus::SE3d T = v->estimate();
        Eigen::Vector3d pos_cam = T * _pos3d;
        double fx = _K(0, 0);
        double fy = _K(1, 1);
        double cx = _K(0, 2);
        double cy = _K(1, 2);
        double X = pos_cam[0];
        double Y = pos_cam[1];
        double Z = pos_cam[2];
        double Z2 = Z * Z;
        _jacobianOplusXi
          << -fx / Z, 0, fx * X / Z2, fx * X * Y / Z2, -fx - fx * X * X / Z2, fx * Y / Z,
          0, -fy / Z, fy * Y / (Z * Z), fy + fy * Y * Y / Z2, -fy * X * Y / Z2, -fy * X / Z;
    }

    virtual bool read(istream &in) override {}
    virtual bool write(ostream &out) const override {}

private:
    Eigen::Vector3d _pos3d;
    Eigen::Matrix3d _K;
};


// 建图
void bundleAdjustmentG2O(
    const VecVector3d &points_3d, // 3d点坐标
    const VecVector2d &points_2d, // 2d点坐标
    const Mat &K,                 // 内参
    Sophus::SE3d &pose) {         // 位姿(优化对象)

    // 构建图优化,先设定g2o
    typedef g2o::BlockSolver<g2o::BlockSolverTraits<6, 3>> BlockSolverType;  // pose is 6, landmark is 3
    typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType; // 线性求解器类型
    // 梯度下降方法,可以从GN, LM, DogLeg 中选
    auto solver = new g2o::OptimizationAlgorithmGaussNewton(
    g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));
    g2o::SparseOptimizer optimizer;     // 图模型
    optimizer.setAlgorithm(solver);   // 设置求解器
    optimizer.setVerbose(true);       // 打开调试输出

    // 设置顶点
    VertexPose *vertex_pose = new VertexPose(); // camera vertex_pose
    vertex_pose->setId(0);
    vertex_pose->setEstimate(Sophus::SE3d());
    optimizer.addVertex(vertex_pose);

    // 创建内参K
    Eigen::Matrix3d K_eigen;
    K_eigen <<
            K.at<double>(0, 0), K.at<double>(0, 1), K.at<double>(0, 2),
        K.at<double>(1, 0), K.at<double>(1, 1), K.at<double>(1, 2),
        K.at<double>(2, 0), K.at<double>(2, 1), K.at<double>(2, 2);

    // 设置边
    int index = 1;
    for (size_t i = 0; i < points_2d.size(); ++i) {
        auto p2d = points_2d[i];
        auto p3d = points_3d[i];
        EdgeProjection *edge = new EdgeProjection(p3d, K_eigen);
        edge->setId(index);
        edge->setVertex(0, vertex_pose);
        edge->setMeasurement(p2d);
        edge->setInformation(Eigen::Matrix2d::Identity());
        optimizer.addEdge(edge);
        index++;
    }

    chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
    optimizer.setVerbose(true);
    optimizer.initializeOptimization();
    optimizer.optimize(10);
    chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
    chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
    cout << "optimization costs time: " << time_used.count() << " seconds." << endl;
    cout << "pose estimated by g2o =\n" << vertex_pose->estimate().matrix() << endl;
    pose = vertex_pose->estimate();
}

:::

  • 图优化

3.3 案例3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值