g2o入门

引子

g2o (General Graph Optimization) 是基于图优化的库,不同于 ceres 的“自动求导+残差块”不同,g2o从”图论“的视角来描述优化问题
在这里插入图片描述

核心架构

g2o的求解器初始化非常繁琐

![[g2o.png]]

先从整体上看,整个 g2o 框架可以分为上下两部分,以 SparseOpyimizer 为核心

配置一个 g2o 求解器,需要由内向外来进行

  1. LinearSolver (线性求解器)
    • 负责求解 H Δ x = − b H\Delta x=-b HΔx=b
    • 求解方法:LinearSolverCholmodLinearSolverCSparseLinearSolverEigenLinearSolverDense
    • 通常选用 CholmodCSparse 处理稀疏矩阵
  2. BlockSolver (块求解器)
    - 负责求解 H H H b b b 的矩阵块(雅可比乘法)
    - 定义:BlockSolver<BlockSolverTraits<PoseDim, LandmarkDim>>
    - 参数:位姿维度和路标维度
    - BA : <6, 3> (6 维位姿,3维点),也可以使用 BlockSolver_6_3
    - 位姿图优化:<6, 6> (只有位姿)
  3. OptimizationAlgorithm (迭代算法)
    - 确定下降策略(GN,LM,Dogleg)
    - 类型:OptimizationAlgorithmGaussNewtonOptimizationAlgorithmLevenbergOptimizationAlgorithmDogleg
  4. SparseOptimizer (稀疏优化器)
    - 最终的图模型

重要的类和函数

顶点 (Vertex)

继承自 BaseVertex<D, T>

  • D:变量维度(int
  • T:变量的数据类型(Eigen::Vector3dSophus::SE3d
    需要实现的方法:
  • setToOriginImpl():重置顶点为初始值
  • oplusImpl(const double* update)更新策略。定义优化过程中增量 Δ x \Delta x Δx 的计算(比如使用普通加法或者李代数乘法)

边(Edge)

继承自 BaseUnaryEdge (一元边)、BaseBinaryEdge (二元边)、BaseMultiEdge (多元边)
以最常见的二元边为例,其参数有:

  • D :观测值维度(int
  • E:观测值类型
  • VertexXiVertexXj :不同顶点的类型
    需要实现的方法:
  • compute :计算残差。 e = meas − f ( x ) e=\text{meas}-f(x) e=measf(x)
  • linearizeOplus :计算雅可比

代码实例

曲线拟合

#include <iostream>
#include <eigen3/Eigen/Core>
#include <vector>
#include <opencv4/opencv2/core.hpp>
#include <cmath>
#include <chrono>
#include <g2o/core/g2o_core_api.h>
#include <g2o/core/base_unary_edge.h>
#include <g2o/core/base_vertex.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/solvers/dense/linear_solver_dense.h>

// 1.定义顶点
class CurveFittingVertex : public g2o::BaseVertex<3, Eigen::Vector3d> {
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
    
    // 重置
    virtual void setToOriginImpl() override 
    {
        _estimate << 0, 0, 0;
    }

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

    // 读写
    virtual bool read(std::istream& in) override { return true; }
    virtual bool write(std::ostream& out) const override { return true; }
};

// 2.定义边
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) * _x * _x + abc(1) * _x + abc(2));
    }

    // 计算雅可比矩阵
    virtual void linearizeOplus() override
    {
        const CurveFittingVertex* v = static_cast<const CurveFittingVertex*>(_vertices[0]);
        const Eigen::Vector3d& abc = v->estimate();
        double y = std::exp(abc(0) * _x * _x + abc(1) * _x + abc(2));

        _jacobianOplusXi(0, 0) = -_x * _x * y; // 对a求导
        _jacobianOplusXi(0, 1) = -_x * y;      // 对b求导
        _jacobianOplusXi(0, 2) = -y;           // 对c求导
    }

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

public:
    double _x;
};

int main()
{
    double ar = 1.0, br = 2.0, cr = 1.0;  // 真实参数值
    double ae = 2.0, be = -1.0, ce = 5.0; // 估计参数初值
    int N = 100;                          // 数据点
    double w_sigma = 1.0;                 // 噪声Sigma值
    double inv_sigma = 1.0 / w_sigma;
    cv::RNG rng;                          // OpenCV随机数产生器

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

    // 构建图优化
    // 1. 构建 BlockSolver 块求解器
    typedef g2o::BlockSolver<g2o::BlockSolverTraits<3, 1>> BlockSolverType;

    // 2. 构建线性求解器
    typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType;

    // 3. 设定算法 Algorithm
    auto solver = std::make_unique<g2o::OptimizationAlgorithmLevenberg>(
        std::make_unique<BlockSolverType>(std::make_unique<LinearSolverType>())
    );

    // 4. 创建稀疏优化器
    g2o::SparseOptimizer optimizer;
    optimizer.setAlgorithm(solver.release());
    optimizer.setVerbose(true); // 打开调试输出

    // 5. 添加顶点
    CurveFittingVertex* v = std::make_unique<CurveFittingVertex>().release();
    v->setId(0);
    v->setEstimate(Eigen::Vector3d(ae, be, ce));   // 设置初始值
    optimizer.addVertex(v);

    // 6. 添加边
    for (int i = 0; i < N; i++) 
    {
        CurveFittingEdge* edge = std::make_unique<CurveFittingEdge>(x_data[i]).release();
        edge->setId(i);
        edge->setVertex(0, optimizer.vertex(0));    // 连接顶点
        edge->setMeasurement(y_data[i]);            // 观测值
        edge->setInformation(Eigen::Matrix<double, 1 ,1>::Identity() * inv_sigma * inv_sigma); // 信息矩阵
        optimizer.addEdge(edge);
    }

    // 7. 执行优化
    std::cout << "Start optimization" << std::endl;
    std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
    optimizer.initializeOptimization();
    optimizer.optimize(100);
    std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
    std::chrono::duration<double> time_used = std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
    std::cout << "Optimization costs time: " << time_used.count() << " seconds." << std::endl;
        
    std::cout << "Estimated model: " << v->estimate().transpose() << std::endl;
}

PnP

#include <iostream>
#include <eigen3/Eigen/Core>
#include <vector>
#include <opencv4/opencv2/core.hpp>
#include <sophus/se3.hpp>
#include <g2o/core/g2o_core_api.h>
#include <g2o/core/base_unary_edge.h>
#include <g2o/core/base_vertex.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/solvers/dense/linear_solver_dense.h>

// 相机内参
double fx = 520.9, fy = 521.0, cx = 325.1, cy = 249.7;

// 1. 定义顶点:相机位姿
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::Map<const Eigen::Matrix<double, 6, 1>> update_eigen(update);
        // T_new = exp(update) * T_old
        _estimate = Sophus::SE3d::exp(update_eigen) * _estimate;
    }

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

// 2. 定义边:重投影误差
class EdgeProjection : public g2o::BaseUnaryEdge<2, Eigen::Vector2d, VertexPose> {
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    EdgeProjection(const Eigen::Vector3d& pos) : _pos3d(pos) {}

    virtual void computeError() override
    {
        const VertexPose* v = static_cast<const VertexPose*>(_vertices[0]);
        Sophus::SE3d T = v->estimate();

        // 世界坐标系转换到相机坐标系
        Eigen::Vector3d pos_cam = T * _pos3d;

        // 投影到像素平面
        pos_cam /= pos_cam[2];
        double u = fx * pos_cam[0] + cx;
        double v_pix = fy * pos_cam[1] + cy;

        // 误差 = 观测 - 预测
        _error = _measurement - Eigen::Vector2d(u, v_pix);
    }

    // 计算雅可比
    virtual void linearizeOplus() override
    {
        const VertexPose* pose_vertex = static_cast<const VertexPose*>(_vertices[0]);
        Sophus::SE3d T = pose_vertex->estimate();
        Eigen::Vector3d pos_cam = T * _pos3d;

        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 / Z2, fy + fy * Y * Y / Z2, -fy * X * Y / Z2, -fy * X / Z;
    }

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

private:
    Eigen::Vector3d _pos3d; // 3D点在世界坐标系中的位置
};

int main()
{
    std::vector<Eigen::Vector3d> points_3d;
    std::vector<Eigen::Vector2d> points_2d;
    Eigen::Matrix3d K;
    K << fx, 0, cx,
         0, fy, cy,
         0, 0, 1;

    // 真实位姿
    Sophus::SE3d pose_true(Eigen::Quaterniond(0.92388, 0, 0, 0.38268), Eigen::Vector3d(0.5, 0, 1.0));

    // 生成观测点
    for (int x = -2; x <= 2; x++)
    {
        for (int y = -2; y <= 2; y++)
        {
            for (int z = 2; z <= 4; z++)
            {
                Eigen::Vector3d p_w(x, y, z);
                Eigen::Vector3d p_c = pose_true * p_w;

                // 投影
                Eigen::Vector2d p_uv( fx * p_c[0] / p_c[2] + cx,
                                      fy * p_c[1] / p_c[2] + cy );

                points_3d.push_back(p_w);
                points_2d.push_back(p_uv);
            }
        }
    }

    // 构建g2o优化器
    // 1. BlockSolver
    // PoseDim = 6, LandMarkDim = 1(没有优化点,所以是一元边)
    typedef g2o::BlockSolver<g2o::BlockSolverTraits<6, 1>> BlockSolverType;

    // 2. LinearSolver
    typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType;

    // 3. 算法配置
    auto solver = std::make_unique<g2o::OptimizationAlgorithmLevenberg>(
        std::make_unique<BlockSolverType>(std::make_unique<LinearSolverType>())
    );

    // 4. 优化器
    g2o::SparseOptimizer optimizer;
    optimizer.setAlgorithm(solver.release());
    optimizer.setVerbose(true);

    // 5. 添加位姿顶点
    VertexPose* vertex_pose = std::make_unique<VertexPose>().release();
    vertex_pose->setId(0);
    vertex_pose->setEstimate(Sophus::SE3d(Eigen::Quaterniond(1,0,0,0), Eigen::Vector3d(0,0,0))); // 初始值设为单位元
    optimizer.addVertex(vertex_pose);

    // 6. 添加边
    for (int i = 0; i < points_3d.size(); i++)
    {
        EdgeProjection* edge = std::make_unique<EdgeProjection>(points_3d[i]).release();
        edge->setId(i);
        edge->setVertex(0, vertex_pose);
        edge->setMeasurement(points_2d[i]);
        edge->setInformation(Eigen::Matrix2d::Identity());
        optimizer.addEdge(edge);
    }

    // 7. 优化
    std::cout << "Start optimization" << std::endl;
    optimizer.initializeOptimization();
    optimizer.optimize(100);

    // 输出结果
    Sophus::SE3d pose_estimated = vertex_pose->estimate();
    std::cout << "Estimated pose: \n" << pose_estimated.matrix() << std::endl;
    std::cout << "True pose: \n" << pose_true.matrix() << std::endl;
}

BA

#include <iostream>
#include <eigen3/Eigen/Core>
#include <vector>
#include <opencv4/opencv2/core.hpp>
#include <sophus/se3.hpp>
#include <g2o/core/g2o_core_api.h>
#include <g2o/core/base_binary_edge.h>
#include <g2o/core/base_vertex.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/solvers/dense/linear_solver_dense.h>

double fx = 520.9, fy = 521.0, cx = 325.1, cy = 249.7;

// 1. 定义顶点:相机位姿
class VertexPose : public g2o::BaseVertex<6, Sophus::SE3d> {
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    virtual void setToOriginImpl() override
    {
        _estimate = Sophus::SE3d();
    }

    // T_new = exp(update) * T_old
    virtual void oplusImpl(const double* update) override
    {
        Eigen::Map<const Eigen::Matrix<double, 6, 1>> update_eigen(update);
        _estimate = Sophus::SE3d::exp(update_eigen) * _estimate;
    }

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

// 2. 定义路标点顶点 
class VertexPoint : public g2o::BaseVertex<3, Eigen::Vector3d> {
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    virtual void setToOriginImpl() override
    {
        _estimate = Eigen::Vector3d::Zero();
    }

    // P_new = P_old + update
    virtual void oplusImpl(const double* update) override
    {
        Eigen::Map<const Eigen::Vector3d> update_eigen(update);
        _estimate += update_eigen;
    }

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

class EdgeProjection : public g2o::BaseBinaryEdge<2, Eigen::Vector2d, VertexPose, VertexPoint> {
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    virtual void computeError() override
    {
        const VertexPose* v0 = static_cast<const VertexPose*>(_vertices[0]);
        const VertexPoint* v1 = static_cast<const VertexPoint*>(_vertices[1]);

        Sophus::SE3d T = v0->estimate();
        Eigen::Vector3d pos_3d = v1->estimate();

        // 1. 转换到相机坐标系
        Eigen::Vector3d pos_cam = T * pos_3d;

        // 2. 投影到像素平面
        double inv_z = 1.0 / pos_cam[2];
        double u = fx * pos_cam[0] * inv_z + cx;
        double v = fy * pos_cam[1] * inv_z + cy;

        // 3. 计算误差
        _error = _measurement - Eigen::Vector2d(u, v);
    }

    // 手写雅可比
    // _jacobianOplusXi: 位姿顶点的雅可比(2*6)
    // _jacobianOplusXj: 路标点顶点的雅可比(2*3)
    virtual void linearizeOplus() override
    {
        const VertexPose* v0 = static_cast<const VertexPose*>(_vertices[0]);
        const VertexPoint* v1 = static_cast<const VertexPoint*>(_vertices[1]);
        Sophus::SE3d T = v0->estimate();
        Eigen::Vector3d pos_3d = v1->estimate();
        Eigen::Vector3d pos_cam = T * pos_3d;

        double X = pos_cam[0];
        double Y = pos_cam[1];
        double Z = pos_cam[2];
        double Z2 = Z * Z;
        double inv_Z = 1.0 / Z;
        double inv_Z2 = inv_Z * inv_Z;

        // 位姿顶点雅可比(平移在前,旋转在后)
        _jacobianOplusXi <<
            -fx * inv_Z, 0, fx * X * inv_Z2, fx * X * Y * inv_Z2, -fx - fx * X * X * inv_Z2, fx * Y * inv_Z,
            0, -fy * inv_Z, fy * Y * inv_Z2, fy + fy * Y * Y * inv_Z2, -fy * X * Y * inv_Z2, -fy * X * inv_Z;   

        // 路标点顶点雅可比
        Eigen::Matrix<double, 2, 3> j_cam_point;
        j_cam_point <<
           -fx * inv_Z,  0,  fx * X * inv_Z2,
            0, -fy * inv_Z, fy * Y * inv_Z2;

        Eigen::Matrix3d R = T.rotationMatrix();

        _jacobianOplusXj = j_cam_point * R;
    }

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

int main()
{
    // 1. 数据生成 (模拟 3 个相机观测 20 个点) 
    std::vector<Eigen::Vector3d> true_points;
    std::vector<Sophus::SE3d> true_poses; // g2o 内部使用的位姿类型
    
    // 生成 3 个相机位姿 (围绕原点半圆分布)
    for (int i = 0; i < 3; i++) 
    {
        double theta = i * M_PI / 4; // 0, 45, 90 度
        Eigen::Matrix3d R;
        // 相机朝向原点:简单的 LookAt 逻辑 (这里简化为绕Y轴旋转)
        R = Eigen::AngleAxisd(theta - M_PI/2, Eigen::Vector3d(0,1,0)).toRotationMatrix(); 
        Eigen::Vector3d t(5 * cos(theta), 0, 5 * sin(theta));
        true_poses.push_back(Sophus::SE3d(R, t));
    }

    // 生成 20 个随机 3D 点 (在原点附近)
    cv::RNG rng;
    for (int i = 0; i < 20; ++i)
    {
        double x = rng.uniform(-1.0, 1.0);
        double y = rng.uniform(-1.0, 1.0);
        double z = rng.uniform(-1.0, 1.0);
        true_points.push_back(Eigen::Vector3d(x, y, z));
    }

    // 2. 构建 g2o 优化器
    typedef g2o::BlockSolver< g2o::BlockSolverTraits<6, 3> > BlockSolverType; // pose:6, landmark:3
    typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType;

    auto solver = std::make_unique<g2o::OptimizationAlgorithmLevenberg>(
        std::make_unique<BlockSolverType>(std::make_unique<LinearSolverType>())
    );

    g2o::SparseOptimizer optimizer;
    optimizer.setAlgorithm(solver.release());
    optimizer.setVerbose(true);

    // 3. 添加顶点
    // 添加相机位姿顶点
    for (size_t i = 0; i < true_poses.size(); ++i)
    {
        VertexPose* v = std::make_unique<VertexPose>().release();
        v->setId(i);
        v->setEstimate(true_poses[i]);
        if (i == 0) // 固定第一个相机位姿作为参考
            v->setFixed(true);

        optimizer.addVertex(v);
    }

    // 添加路标点顶点
    for (size_t i = 0; i < true_points.size(); ++i)
    {
        VertexPoint* v = std::make_unique<VertexPoint>().release();
        v->setId(static_cast<int>(i + true_poses.size())); // 路标点 ID 从相机数量开始编号
        v->setEstimate(true_points[i]);
        v->setMarginalized(true); // 路标点边缘化
        optimizer.addVertex(v);
    }

    // 4. 添加边
    for (size_t i = 0; i < true_poses.size(); ++i)
    {
        for (size_t j = 0; j < true_points.size(); ++j)
        {
            EdgeProjection* edge = std::make_unique<EdgeProjection>().release();
            edge->setVertex(0, optimizer.vertex(static_cast<int>(i))); // 相机位姿顶点
            edge->setVertex(1, optimizer.vertex(static_cast<int>(j + true_poses.size()))); // 路标点顶点
            edge->setMeasurement(Eigen::Vector2d(100, 100));
            edge->setInformation(Eigen::Matrix2d::Identity());
            optimizer.addEdge(edge);
        }
    }

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

也可以直接#include <g2o/types/sba/types_six_dof_expmap.h> 调用官方预制的 BA 库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值