基于 GTSAM 的因子图简单实例

news2025/1/10 11:06:37

Title: 基于 GTSAM 的因子图简单实例


文章目录

  • I. 引言
  • II. GTSAM 的安装与配置
  • III. 基于 GTSAM 的因子图实例的 C++ 实现
    • 1. C++ 源码
    • 2. CMakeLists.txt 脚本
    • 3. 数值结果
  • IV. 基于 GTSAM 的因子图实例的 Python 实现
    • 1. Python 源码
    • 2. 数值结果
    • 3. 可视化结果
  • V. 总结


关联博文:

因子图、边缘化与消元算法的抽丝剥茧 —— Notes for “Factor Graphs for Robot Perception”
基于 GTSAM 的因子图简单实例


I. 引言

在上一篇博文中, 围绕因子图的构建和运行原理, 分析了因子图的定义、消元与边缘化算法等.

此处基于开源库 GTSAM, 构建和应用一下因子图.

就从一个简单的实例展开 —— 上一博文中的 “一个简单的 SLAM 中的因子图例子”.

SLAM 例子:

机器人先验的初始起点为 x 1 x_1 x1, 在该点上进行了一元测量 (Unary Measurement, 如 GPS、UWB 等绝对位姿测量) z 1 z_1 z1; 同时又探测到了路标 l 1 l_1 l1 并进行了二元测量 (Binary Measurement, 如激光扫描测量) z 2 z_2 z2.

然后机器人前进到了新的地点 x 2 x_2 x2, 并记录了从 x 1 x_1 x1 x 2 x_2 x2 的里程计信息; 在该点上也侦测到了路标点 l 1 l_1 l1 并进行了二元测量 z 3 z_3 z3.

机器人继续前进到 x 3 x_3 x3 并记录了这一段路程的里程计信息; 同时, 机器人在该点观察到了另一个路标点 l 2 l_2 l2 并记录了测量结果 z 4 z_4 z4.

factor_graph_2_slam
Fig. 1 一个简单的 SLAM 中的因子图例子

II. GTSAM 的安装与配置

先安装和配置环境.

序号处理
1下载源码
https://github.com/borglab/gtsam, 注意 checkout 最新的稳定版本.
2cmake-gui 查看编译参数
Picture1
3命令行运行
  mkdir build
  cd build
  cmake … -DCMAKE_INSTALL_PREFIX=指定路径/gtsam_4.2 -DGTSAM_BUILD_PYTHON=ON -DGTSAM_BUILD_DOCS=ON
  make -j4
  make install
  make python-install
注意: 替换"指定路径"为自己实际安装路径
4配置环境参数
Picture2_环境
5卸载
[不完全卸载] 编译时建立的build文件夹下, 运行命令
  make uninstall
[完全卸载] 编译时建立的build文件夹下, 运行命令
  xargs rm -rf < install_manifest.txt

III. 基于 GTSAM 的因子图实例的 C++ 实现

1. C++ 源码

一共 9 个因子, 涉及先验因子、里程计因子、二元测量因子、自定义一元测量因子等.

难点在于自定义一元测量因子的构建.

一元测量因子用于建模 UWB、GPS等, 此处以二维平面上的起始位姿状态 x 1 = ( x , y , θ ) \mathbf{x}_1=(x, y, \theta) x1=(x,y,θ) 为例, 对应的测量值为 m = ( m x , m y ) \mathbf{m}=(m_x, m_y) m=(mx,my). 则位置误差/残差为
r ( x 1 ) = [ x − m x y − m y ] \mathbf{r}(\mathbf{x}_1) = \begin{bmatrix} x - m_x \\ y - m_y \end{bmatrix} r(x1)=[xmxymy]
r \mathbf{r} r 相对于 x 1 \mathbf{x}_1 x1 在位置测量值/测量点 m \mathbf{m} m 上的 Jacobian 矩阵 (雅可比矩阵) 为
∂ r ( x 1 ) ∂ x 1 ∣ m = [ ∂ ( x − m x ) ∂ x ∂ ( x − m x ) ∂ y ∂ ( x − m x ) ∂ θ ∂ ( y − m y ) ∂ x ∂ ( y − m y ) ∂ y ∂ ( y − m y ) ∂ θ ] ∣ x = m x , y = m y = [ 1 0 0 0 1 0 ] \left. \frac{\partial \mathbf{r}(\mathbf{x}_1)}{\partial \mathbf{x}_1} \right|_{\mathbf{m}} = \left. \begin{bmatrix} \frac{\partial (x - m_x) }{\partial x } & \frac{\partial (x - m_x) }{\partial y } & \frac{\partial (x - m_x) }{\partial \theta }\\ \frac{\partial (y - m_y) }{\partial x } & \frac{\partial (y - m_y) }{\partial y } & \frac{\partial (y - m_y) }{\partial \theta } \end{bmatrix}\right|_{x=m_x, y=m_y} = \begin{bmatrix}1 &0 &0\\ 0 & 1 &0\end{bmatrix} x1r(x1) m=[x(xmx)x(ymy)y(xmx)y(ymy)θ(xmx)θ(ymy)] x=mx,y=my=[100100]
在该一元测量中 θ \theta θ 分量并不起作用, 只是为了兼容其他因子 (如 Odometry 因子) 才引入的.

构建因子时需要明确误差和 Jacobian, 是由于因子图的构建是为了求解非线性二乘问题.

#include "gtsam/geometry/Pose2.h"
#include "gtsam/geometry/Point2.h"
#include "gtsam/slam/BetweenFactor.h"
#include "gtsam/slam/BearingRangeFactor.h"
#include "gtsam/nonlinear/NonlinearFactorGraph.h"
#include "gtsam/nonlinear/LevenbergMarquardtOptimizer.h"
#include "gtsam/nonlinear/Marginals.h"
#include "gtsam/inference/Symbol.h"
#include "Eigen/Dense"
#include "iostream"

// 自定义因子 UnaryFactor 继承自 NoiseModelFactorN
class UnaryFactor: public gtsam::NoiseModelFactorN<gtsam::Pose2> {
    double mx_, my_; // mx_, my_ 代表测量到的数值

 public:
    typedef boost::shared_ptr<UnaryFactor> shared_ptr;
    // The constructor requires the variable key, the (X, Y) measurement value, and the noise model
    UnaryFactor(gtsam::Key k, double x, double y, const gtsam::SharedNoiseModel& model):
    gtsam::NoiseModelFactorN<gtsam::Pose2>(model, k), mx_(x), my_(y) {}  // 利用 key 和 noise model 初始化基类 NoiseModelFactorN
    ~UnaryFactor() override {}

    // evaluateError 中建立 (1)误差向量相对于状态变量的Jacobian 和 (2) 误差向量
    gtsam::Vector evaluateError(const gtsam::Pose2& q, 
    boost::optional<gtsam::Matrix&> H = boost::none) const override {
        if (H) (*H) = (gtsam::Matrix(2, 3) <<
                1.0, 0.0, 0.0,
                0.0, 1.0, 0.0).finished();
        return (gtsam::Vector(2) << q.x() - mx_, q.y() - my_).finished();
    }
};


int main(int argc, char** argv)
{
    gtsam::NonlinearFactorGraph graph;

    auto x_1 = gtsam::Symbol('x', 1);
    auto x_2 = gtsam::Symbol('x', 2);
    auto x_3 = gtsam::Symbol('x', 3);
    auto l_1 = gtsam::Symbol('l', 1);
    auto l_2 = gtsam::Symbol('l', 2);

    // std::freopen("results.txt", "w", stdout);  // 输出重定位

    // 第一部分 因子图的构建
    // factor 1 —— prior factor for the starting point x_1 起始点先验因子
    // x_1 变量的类型为 gtsam::Pose2
    gtsam::Pose2 startingPriorMean(0.0, 0.0, 0.0);
    auto startingPriorNoise = gtsam::noiseModel::Diagonal::Sigmas(Eigen::Vector3d(0.2, 0.2, 0.1));
    graph.addPrior(x_1, startingPriorMean, startingPriorNoise);

    // factor 2 and 3 —— odometry factors for x_1->x_2 and x_2->x_3 里程计因子
    // x_2 和 x_3 变量的类型都为 gtsam::Pose2
    gtsam::Pose2 odometry1(2.0, 0.0, 0.0);
    auto odometryNoise = gtsam::noiseModel::Diagonal::Sigmas(Eigen::Vector3d(0.25, 0.25, 0.1));
    graph.add(gtsam::BetweenFactor<gtsam::Pose2>(x_1, x_2, odometry1, odometryNoise));
    graph.add(gtsam::BetweenFactor<gtsam::Pose2>(x_2, x_3, odometry1, odometryNoise));

    // factor 4 and 5 —— prior factors for landmark l_1 and l_2 路标先验因子
    // l_1 和 l_2 变量的类型都为 gtsam::Point2
    gtsam::Point2 landmarkPriorMean1(2.0, 2.0);
    gtsam::Point2 landmarkPriorMean2(4.0, 2.0);
    auto landmarkPriorNoise = gtsam::noiseModel::Diagonal::Sigmas(Eigen::Vector2d(0.2, 0.2));
    graph.add(gtsam::PriorFactor<gtsam::Point2>(l_1, landmarkPriorMean1, landmarkPriorNoise));
    graph.add(gtsam::PriorFactor<gtsam::Point2>(l_2, landmarkPriorMean2, landmarkPriorNoise));

    // factor 6 —— unary measurement factor for x_1 起始点的一元测量, 与先验因子的区别是信息维度 (少了方向数据)
    auto startingUnaryNoise = gtsam::noiseModel::Diagonal::Sigmas(Eigen::Vector2d(0.1, 0.1));
    // 自定义因子的 noise model 的维度须与自定义因子中的误差向量维度一致
    // graph.add(boost::make_shared<UnaryFactor>(x_1, 0.0, 0.0, startingPriorNoise));
    graph.emplace_shared<UnaryFactor>(x_1, 0.01, 0.01, startingUnaryNoise);

    // factor 7 - binary measurement factor for (x_1, l_1) 二元测量
    // l_1 在 x_1 局部坐标系中的测量值
    auto measurementNoise = gtsam::noiseModel::Diagonal::Sigmas(Eigen::Vector2d(0.1, 0.2));
    gtsam::Rot2 bearing_x1_l1 = gtsam::Rot2::fromAngle(M_PI/4);
    double range_x1_l1 = sqrt(8.0);
    graph.add(gtsam::BearingRangeFactor<gtsam::Pose2, gtsam::Point2>(x_1, l_1, bearing_x1_l1, range_x1_l1, measurementNoise));

    // factor 8 - binary measurement factor for (x_2, l_1) 二元测量
    // l_1 在 x_2 局部坐标系中的测量值
    gtsam::Rot2 bearing_x2_l1 = gtsam::Rot2::fromAngle(M_PI/2);
    double range_x2_l1 = 2.0;
    graph.add(gtsam::BearingRangeFactor<gtsam::Pose2, gtsam::Point2>(x_2, l_1, bearing_x2_l1, range_x2_l1, measurementNoise));

    // factor 9 - binary measurement factor for (x_3, l_2) 二元测量
    // l_2 在 x_3 局部坐标系中的测量值
    gtsam::Rot2 bearing_x3_l2 = gtsam::Rot2::fromAngle(M_PI/2);
    double range_x3_l2 = 2.0;
    graph.add(gtsam::BearingRangeFactor<gtsam::Pose2, gtsam::Point2>(x_3, l_2, bearing_x3_l2, range_x3_l2, measurementNoise));

    graph.print("\nFactor Graph\n");  // 因子图构建的输出

    // 第二部分 非线性优化计算
    printf("Nonlinear optimization:\n");
    gtsam::Values initial;  // 一些随机值作为非线性二乘问题迭代计算的初始值, 是对真值的初步猜测, 与真值之间有偏差
    initial.insert(x_1, gtsam::Pose2(0.0, 0.1, 0.1));
    initial.insert(x_2, gtsam::Pose2(0.17, -0.1, -0.05));
    initial.insert(x_3, gtsam::Pose2(0.45, 0.1, 0.1));
    initial.insert(l_1, gtsam::Point2(0.19, 0.21));
    initial.insert(l_2, gtsam::Point2(42.0, 19.0));
    initial.print("Initial estimation:"); // 初始值的输出

    // gtsam::Values result = gtsam::LevenbergMarquardtOptimizer(graph, initial).optimize();
    // result.print("Final results:"); // 优化值的输出
    gtsam::LevenbergMarquardtParams parameters;
    parameters.setVerbosity("ERROR");
    // parameters.setVerbosityLM("SUMMARY");
    gtsam::LevenbergMarquardtOptimizer optimizer(graph, initial, parameters);
    gtsam::Values result = optimizer.optimize();
    result.print("\nFinal Result:\n");

    // 第三部分 边缘概率计算
    std::cout.precision(3);
    printf("\nMarginal Covariance:\n");
    gtsam::Marginals marginals(graph, result);
    std::cout << "\nx1 covariance:\n" << marginals.marginalCovariance(x_1) << std::endl;
    std::cout << "\nx2 covariance:\n" << marginals.marginalCovariance(x_2) << std::endl;
    std::cout << "\nx3 covariance:\n" << marginals.marginalCovariance(x_3) << std::endl;
    std::cout << "\nl1 covariance:\n" << marginals.marginalCovariance(l_1) << std::endl;
    std::cout << "\nl2 covariance:\n" << marginals.marginalCovariance(l_2) << std::endl;

}  

2. CMakeLists.txt 脚本

cmake_minimum_required(VERSION 3.0)

project(factor_graph_example)
find_package(GTSAM 4.2 REQUIRED COMPONENTS)
include_directories(${GTSAM_INCLUDE_DIR})

FILE(GLOB_RECURSE HPP_INCLUDE include/*.hpp include/*.h)
FILE(GLOB_RECURSE CPP_SOURCE source/*.cpp)
add_executable(${CMAKE_PROJECT_NAME} ${CPP_SOURCE} ${HPP_INCLUDE})
target_link_libraries(${CMAKE_PROJECT_NAME} gtsam)

3. 数值结果

Factor Graph
size: 9

Factor 0: PriorFactor on x1
  prior mean:  (0, 0, 0)
  noise model: diagonal sigmas [0.2; 0.2; 0.1];

Factor 1: BetweenFactor(x1,x2)
  measured:  (2, 0, 0)
  noise model: diagonal sigmas [0.25; 0.25; 0.1];

Factor 2: BetweenFactor(x2,x3)
  measured:  (2, 0, 0)
  noise model: diagonal sigmas [0.25; 0.25; 0.1];

Factor 3: PriorFactor on l1
  prior mean: [
        2;
        2
]
isotropic dim=2 sigma=0.2

Factor 4: PriorFactor on l2
  prior mean: [
        4;
        2
]
isotropic dim=2 sigma=0.2

Factor 5:   keys = { x1 }
isotropic dim=2 sigma=0.1

Factor 6: BearingRangeFactor
Factor 6:   keys = { x1 l1 }
  noise model: diagonal sigmas [0.1; 0.2];
ExpressionFactor with measurement: bearing : 0.785398163
range  2.82842712

Factor 7: BearingRangeFactor
Factor 7:   keys = { x2 l1 }
  noise model: diagonal sigmas [0.1; 0.2];
ExpressionFactor with measurement: bearing : 1.57079633
range  2

Factor 8: BearingRangeFactor
Factor 8:   keys = { x3 l2 }
  noise model: diagonal sigmas [0.1; 0.2];
ExpressionFactor with measurement: bearing : 1.57079633
range  2

Nonlinear optimization:
Initial estimation:
Values with 5 values:
Value l1: (Eigen::Matrix<double, 2, 1, 0, 2, 1>)
[
        0.19;
        0.21
]

Value l2: (Eigen::Matrix<double, 2, 1, 0, 2, 1>)
[
        42;
        19
]

Value x1: (gtsam::Pose2)
(0, 0.1, 0.1)

Value x2: (gtsam::Pose2)
(0.17, -0.1, -0.05)

Value x3: (gtsam::Pose2)
(0.45, 0.1, 0.1)

Initial error: 45816.3375
newError: 60.0746132
errorThreshold: 60.0746132 > 0
absoluteDecrease: 45756.2629219 >= 1e-05
relativeDecrease: 0.99868879495 >= 1e-05
newError: 1.24368959022
errorThreshold: 1.24368959022 > 0
absoluteDecrease: 58.8309235801 >= 1e-05
relativeDecrease: 0.97929758471 >= 1e-05
newError: 0.00598271925373
errorThreshold: 0.00598271925373 > 0
absoluteDecrease: 1.23770687096 >= 1e-05
relativeDecrease: 0.995189539817 >= 1e-05
newError: 0.00296591263207
errorThreshold: 0.00296591263207 > 0
absoluteDecrease: 0.00301680662167 >= 1e-05
relativeDecrease: 0.504253416167 >= 1e-05
newError: 0.00296590504323
errorThreshold: 0.00296590504323 > 0
absoluteDecrease: 7.58883373286e-09 < 1e-05
relativeDecrease: 2.55868418065e-06 < 1e-05
converged
errorThreshold: 0.00296590504323 <? 0
absoluteDecrease: 7.58883373286e-09 <? 1e-05
relativeDecrease: 2.55868418065e-06 <? 1e-05
iterations: 5 >? 100

Final Result:

Values with 5 values:
Value l1: (Eigen::Matrix<double, 2, 1, 0, 2, 1>)
[
        2.00402979248;
        2.00353272869
]

Value l2: (Eigen::Matrix<double, 2, 1, 0, 2, 1>)
[
        4.00101280265;
        2.00108370699
]

Value x1: (gtsam::Pose2)
(0.00699225259677, 0.00707591845494, -0.000329623653647)

Value x2: (gtsam::Pose2)
(2.00448395483, 0.00399757524079, -6.886908946e-05)

Value x3: (gtsam::Pose2)
(4.00290058662, 0.00216735620844, 0.000437767142559)


Marginal Covariance:

x1 covariance:
  0.00728 -0.000291  0.000719
-0.000291   0.00737  -0.00105
 0.000719  -0.00105   0.00438

x2 covariance:
   0.0397  -0.00156   0.00895
 -0.00156    0.0315 -0.000391
  0.00895 -0.000391   0.00798

x3 covariance:
 0.0701   0.012  0.0193
  0.012  0.0546 0.00783
 0.0193 0.00783  0.0147

l1 covariance:
  0.0224 -0.00333
-0.00333   0.0217

l2 covariance:
   0.0329 -0.000904
-0.000904    0.0336

IV. 基于 GTSAM 的因子图实例的 Python 实现

1. Python 源码

同样地, 难点在于一元测量因子的自定义构建.

Python 上运行画图比较方便.

"""
GTSAM Copyright 2010-2018, Georgia Tech Research Corporation,
Atlanta, Georgia 30332-0415
All Rights Reserved
Authors: Frank Dellaert, et al. (see THANKS for the full author list)

See LICENSE for the license information

Simple robotics example using odometry measurements and bearing-range (laser) measurements
Author: Alex Cunningham (C++), Kevin Deng & Frank Dellaert (Python)

++ + custom factor of the unary measurement 

"""
# pylint: disable=invalid-name, E1101

from __future__ import print_function
from functools import partial
from typing import List, Optional

import gtsam
import numpy as np
from gtsam.symbol_shorthand import L, X
import gtsam.utils.plot as gtsam_plot
import matplotlib.pyplot as plt

# Create noise models
PRIOR_NOISE = gtsam.noiseModel.Diagonal.Sigmas(np.array([0.2, 0.2, 0.1]))
ODOMETRY_NOISE = gtsam.noiseModel.Diagonal.Sigmas(np.array([0.25, 0.25, 0.1]))
MEASUREMENT_NOISE = gtsam.noiseModel.Diagonal.Sigmas(np.array([0.1, 0.2]))
UNARY_MEASUREMENT_NOISE = gtsam.noiseModel.Diagonal.Sigmas(np.array([0.1, 0.1]))
LANDMARK_PRIOR_NOISE = gtsam.noiseModel.Diagonal.Sigmas(np.array([0.2, 0.2]))


def error_unary(measurement: np.ndarray, this: gtsam.CustomFactor,
              values: gtsam.Values,
              jacobians: Optional[List[np.ndarray]]) -> float:
    """Unary Factor error function
    :param measurement: Unary measurement, to be filled with `partial`
    :param this: gtsam.CustomFactor handle
    :param values: gtsam.Values
    :param jacobians: Optional list of Jacobians
    :return: the unwhitened error
    """
    key = this.keys()[0]
    estimate = values.atPose2(key)
    # error = [estimate[0], estimate[1]] - measurement
    error = [(estimate.x() - measurement[0]), (estimate.y() - measurement[1])]
    
    print(error)

    if jacobians is not None:
        jacobians[0] = np.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
    # jacobians[0] = np.eye(2)

    return error
            


def main():
    """Main runner"""

    # Create an empty nonlinear factor graph
    graph = gtsam.NonlinearFactorGraph()

    # Create the keys corresponding to unknown variables in the factor graph
    X1 = X(1)
    X2 = X(2)
    X3 = X(3)
    L1 = L(1)
    L2 = L(2)



    # Add a prior on pose X1 at the origin. A prior factor consists of a mean and a noise model
    graph.add(
        gtsam.PriorFactorPose2(X1, gtsam.Pose2(0.0, 0.0, 0.0), PRIOR_NOISE))

    # Add odometry factors between X1,X2 and X2,X3, respectively
    graph.add(
        gtsam.BetweenFactorPose2(X1, X2, gtsam.Pose2(2.0, 0.0, 0.0),
                                 ODOMETRY_NOISE))
    graph.add(
        gtsam.BetweenFactorPose2(X2, X3, gtsam.Pose2(2.0, 0.0, 0.0),
                                 ODOMETRY_NOISE))
    


    # Add a prior on pose L1 at the origin. A prior factor consists of a mean and a noise model
    landmarkPriorMean1 = gtsam.Point2(2.0, 2.0)
    graph.add(
        gtsam.PriorFactorPoint2(L1, landmarkPriorMean1, LANDMARK_PRIOR_NOISE))


    # Add a prior on pose L2 at the origin. A prior factor consists of a mean and a noise model
    landmarkPriorMean1 = gtsam.Point2(4.0, 2.0)
    graph.add(
        gtsam.PriorFactorPoint2(L2, landmarkPriorMean1, LANDMARK_PRIOR_NOISE))


    # Add the Unary-Measurement factors on X1
    measurement = [0.01 , 0.01]
    graph.add(
        gtsam.CustomFactor(UNARY_MEASUREMENT_NOISE, [X1], partial(error_unary, measurement)))
    # 注意 此处 functools.partial 基于error_unary 函数创建可调用对象, 把原函数中的第一个参数 measurement 固定了
    # 直白一点就是 传递了 measurement 参数值
    # 不要误解为偏微分计算

    # Add Range-Bearing measurements to two different landmarks L1 and L2
    graph.add(
        gtsam.BearingRangeFactor2D(X1, L1, gtsam.Rot2.fromDegrees(45),
                                   np.sqrt(4.0 + 4.0), MEASUREMENT_NOISE))
    graph.add(
        gtsam.BearingRangeFactor2D(X2, L1, gtsam.Rot2.fromDegrees(90), 2.0,
                                   MEASUREMENT_NOISE))
    graph.add(
        gtsam.BearingRangeFactor2D(X3, L2, gtsam.Rot2.fromDegrees(90), 2.0,
                                   MEASUREMENT_NOISE))

    # Print graph
    print("Factor Graph:\n{}".format(graph))

    # Create (deliberately inaccurate) initial estimate
    initial_estimate = gtsam.Values()
    initial_estimate.insert(X1, gtsam.Pose2(0.0, 0.1, 0.1))
    initial_estimate.insert(X2, gtsam.Pose2(0.17, -0.1, -0.05))
    initial_estimate.insert(X3, gtsam.Pose2(0.45, 0.10, 0.10))
    initial_estimate.insert(L1, gtsam.Point2(0.19, 0.21))
    initial_estimate.insert(L2, gtsam.Point2(42.0, 19.0))

    # Print
    print("Initial Estimate:\n{}".format(initial_estimate))

    # Optimize using Levenberg-Marquardt optimization. The optimizer
    # accepts an optional set of configuration parameters, controlling
    # things like convergence criteria, the type of linear system solver
    # to use, and the amount of information displayed during optimization.
    # Here we will use the default set of parameters.  See the
    # documentation for the full set of parameters.
    params = gtsam.LevenbergMarquardtParams()
    params.setVerbosity("ERROR")
    optimizer = gtsam.LevenbergMarquardtOptimizer(graph, initial_estimate,
                                                  params)
    result = optimizer.optimize()
    print("\nFinal Result:\n{}".format(result))

    # Calculate and print marginal covariances for all variables
    marginals = gtsam.Marginals(graph, result)
    for (key, s) in [(X1, "X1"), (X2, "X2"), (X3, "X3"), (L1, "L1"),
                     (L2, "L2")]:
        print("{} covariance:\n{}\n".format(s,
                                            marginals.marginalCovariance(key)))
        
    for (key, s) in [(X1, "X1"), (X2, "X2"), (X3, "X3") ]:
        gtsam_plot.plot_pose2(0, result.atPose2(key), 0.5, marginals.marginalCovariance(key))
    for (key, s) in [ (L1, "L1"),(L2, "L2") ]:
        gtsam_plot.plot_point2(0, result.atPoint2(key), 0.5, marginals.marginalCovariance(key))
    plt.axis('equal')
    plt.show()


if __name__ == "__main__":
    main()

2. 数值结果

可以看出不管是 C++ 版本的结果还是 Python 版本的结果是一致的.

Factor Graph:
NonlinearFactorGraph: size: 9

Factor 0: PriorFactor on x1
  prior mean:  (0, 0, 0)
  noise model: diagonal sigmas [0.2; 0.2; 0.1];

Factor 1: BetweenFactor(x1,x2)
  measured:  (2, 0, 0)
  noise model: diagonal sigmas [0.25; 0.25; 0.1];

Factor 2: BetweenFactor(x2,x3)
  measured:  (2, 0, 0)
  noise model: diagonal sigmas [0.25; 0.25; 0.1];

Factor 3: PriorFactor on l1
  prior mean: [
	2;
	2
]
isotropic dim=2 sigma=0.2

Factor 4: PriorFactor on l2
  prior mean: [
	4;
	2
]
isotropic dim=2 sigma=0.2

Factor 5: CustomFactor on x1
isotropic dim=2 sigma=0.1

Factor 6: BearingRangeFactor
Factor 6:   keys = { x1 l1 }
  noise model: diagonal sigmas [0.1; 0.2];
ExpressionFactor with measurement: bearing : 0.785398163
range  2.82842712

Factor 7: BearingRangeFactor
Factor 7:   keys = { x2 l1 }
  noise model: diagonal sigmas [0.1; 0.2];
ExpressionFactor with measurement: bearing : 1.57079633
range  2

Factor 8: BearingRangeFactor
Factor 8:   keys = { x3 l2 }
  noise model: diagonal sigmas [0.1; 0.2];
ExpressionFactor with measurement: bearing : 1.57079633
range  2


Initial Estimate:
Values with 5 values:
Value l1: (Eigen::Matrix<double, -1, 1, 0, -1, 1>)
[
	0.19;
	0.21
]

Value l2: (Eigen::Matrix<double, -1, 1, 0, -1, 1>)
[
	42;
	19
]

Value x1: (gtsam::Pose2)
(0, 0.1, 0.1)

Value x2: (gtsam::Pose2)
(0.17, -0.1, -0.05)

Value x3: (gtsam::Pose2)
(0.45, 0.1, 0.1)


[-0.01, 0.09000000000000001]
Initial error: 45816.3375
[-0.01, 0.09000000000000001]
[-0.09431931375291222, 0.08073185880549684]
newError: 60.0746132
errorThreshold: 60.0746132 > 0
absoluteDecrease: 45756.2629219 >= 1e-05
relativeDecrease: 0.99868879495 >= 1e-05
[-0.09431931375291222, 0.08073185880549684]
[-0.021338603719452405, -0.0190455547054151]
newError: 1.24368959022
errorThreshold: 1.24368959022 > 0
absoluteDecrease: 58.8309235801 >= 1e-05
relativeDecrease: 0.97929758471 >= 1e-05
[-0.021338603719452405, -0.0190455547054151]
[-0.0026978140729723103, -0.0029251685355443906]
newError: 0.00598271925373
errorThreshold: 0.00598271925373 > 0
absoluteDecrease: 1.23770687096 >= 1e-05
relativeDecrease: 0.995189539817 >= 1e-05
[-0.0026978140729723103, -0.0029251685355443906]
[-0.0030085387600294784, -0.002923210421981185]
newError: 0.00296591263207
errorThreshold: 0.00296591263207 > 0
absoluteDecrease: 0.00301680662167 >= 1e-05
relativeDecrease: 0.504253416167 >= 1e-05
[-0.0030085387600294784, -0.002923210421981185]
[-0.0030077474032272995, -0.0029240815450598734]
newError: 0.00296590504323
errorThreshold: 0.00296590504323 > 0
absoluteDecrease: 7.58883373286e-09 < 1e-05
relativeDecrease: 2.55868418065e-06 < 1e-05
converged
errorThreshold: 0.00296590504323 <? 0
absoluteDecrease: 7.58883373286e-09 <? 1e-05
relativeDecrease: 2.55868418065e-06 <? 1e-05
iterations: 5 >? 100

Final Result:
Values with 5 values:
Value l1: (Eigen::Matrix<double, -1, 1, 0, -1, 1>)
[
	2.00402979248;
	2.00353272869
]

Value l2: (Eigen::Matrix<double, -1, 1, 0, -1, 1>)
[
	4.00101280265;
	2.00108370699
]

Value x1: (gtsam::Pose2)
(0.00699225259677, 0.00707591845494, -0.000329623653647)

Value x2: (gtsam::Pose2)
(2.00448395483, 0.00399757524079, -6.886908946e-05)

Value x3: (gtsam::Pose2)
(4.00290058662, 0.00216735620844, 0.000437767142559)


[-0.0030077474032272995, -0.0029240815450598734]
X1 covariance:
[[ 0.00728256 -0.00029085  0.0007187 ]
 [-0.00029085  0.0073673  -0.00104787]
 [ 0.0007187  -0.00104787  0.0043819 ]]

X2 covariance:
[[ 0.03966749 -0.00155955  0.00895399]
 [-0.00155955  0.03147331 -0.00039051]
 [ 0.00895399 -0.00039051  0.00797534]]

X3 covariance:
[[0.07007273 0.01202848 0.01925868]
 [0.01202848 0.05455565 0.00783016]
 [0.01925868 0.00783016 0.01468828]]

L1 covariance:
[[ 0.02237142 -0.00332628]
 [-0.00332628  0.02172322]]

L2 covariance:
[[ 0.03294629 -0.00090407]
 [-0.00090407  0.03363416]]

3. 可视化结果

python_result
Fig. 1 一个简单的 SLAM 中的因子图例子的计算结果

V. 总结

针对上一篇博文中提到的 “一个简单的 SLAM 中的因子图例子”, 基于 GTSAM 进行了简单应用测试.

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

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

相关文章

day2:信号与槽

思维导图 使用手动连接&#xff0c;将登录框中的取消按钮使用t4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断u界面上输入的账号是否为"123",密码是否为"…

springboot集成JWT实现token权限认证

vuespringboot登录与注册功能的实现 注&#xff1a;对于JWT的学习&#xff0c;首先要完成注册和登录的功能&#xff0c;本篇博客是基于上述博客的进阶学习&#xff0c;代码页也是在原有的基础上进行扩展 ①在pom.xml添加依赖 <!-- JWT --> <dependency><grou…

【Linux】git操作 - gitee

1.使用 git 命令行 安装 git yum install git 2.使用gitee 注册账户 工作台 - Gitee.com 进入gitee&#xff0c;根据提示注册并登录 新建仓库 仓库名称仓库简介初始换仓库 3.Linux-git操作 进入仓库&#xff0c;选择“克隆/下载” 复制下面的两行命令进行git配置 然后将仓库clo…

Vue3引用第三方模块报错Could not find a declaration file for module ***.

在引用第三方的组件时候报错如下 原因是&#xff1a;该组件可能不是.ts文件而是.js文件 解决方案&#xff1a; 1.在Src的目录下面新建一个文件为shims-vue.d.ts的文件 2.文件内容为 declare module xxx&#xff0c;xxx就是你报错的模块 例如我这样 declare module vue3-pu…

【C语言】中的位操作符和移位操作符,原码反码补码以及进制之间的转换

欢迎大家来到c语言知识小课堂&#xff0c;今天的知识点是操作符和进制 目录 一、进制之间的转化1、什么是二进制&#xff0c;八进制&#xff0c;十进制&#xff0c;十六进制2、进制之间的转化其他进制转化为十进制十进制转化为二进制二进制转化为八进制八进制转化为二进制二进…

2023年最新阿里云服务器价格表(配置价格+磁盘+带宽)

2024年阿里云服务器租用价格表更新&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、ECS u1实例2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元、2核4G4M带宽轻量服务器一年165元12个月、2核4G服…

P2957 [USACO09OCT] Barn Echoes G

P2957 [USACO09OCT] Barn Echoes G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P2957 题目分析 对于求单个字符串的哈希值相当于求前缀和&#xff0c;而求单个字符串的子串的哈希值则相当于求其区间和&#xff1b; 那么只需求两个…

fail-safe机制与fail-fast机制分别有什么作用

fail-safe和fail-fast&#xff0c;是多线程并发操作集合时的一种失败处理机制。 Fail-fast&#xff1a;表示快速失败&#xff0c;在集合遍历过程中&#xff0c;一旦发现容器中的数据被修改了&#xff0c;会立刻抛出ConcurrentModificationException异常&#xff0c;从而导致遍…

Selenium基于Python web自动化测试框架 -- PO

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

代码随想录第二十一天 701.二叉搜索树中的插入操作 108.将有序数组转换为二叉搜索树

701.二叉搜索树中的插入操作 题目描述 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和要插入树中的值 value &#xff0c;将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 &#xff0c;新值和原始二叉搜索树中的任意节点值都不同。 注意&a…

CF1468J Road Reform 题解

CF1468J Road Reform 题解 link CF1468J Road Reform 题面翻译 给定一个有 n n n 个节点&#xff0c; m m m 条无向带权边的图&#xff0c;和一个参数 k k k&#xff0c;第 i i i 条边权值为 s i s_i si​。 现在你要保留这个图中的 n − 1 n-1 n−1 条边使得这个图变…

306_C++_QT_创建多个tag页面,使用QMdiArea容器控件,每个页面都是一个新的表格[或者其他]页面

程序目的是可以打开多个styles文件(int后缀文件),且是tag样式的(就是可以切多个页面出来,并且能够单独关闭);其中读取ini文件,将其插入到表格中的操作,也是比较复杂的,因为需要保持RGB字符串和前面的说明字符串对齐 ini文件举例: [MainMenu] Foreground\Selected=&…

63-JQuery语法,选择器,事件,方法,遍历循环each

1.一个JS库,用js封装很多的方法放到一个文件里面,直接拿了用就可以 文件名带min是压缩过的不带min是没压缩过的 2.JQuery语法 通过选取HTML元素,并对选取的元素执行某些操作 基础语法:$(selector).action() <!-- 需要把JQuery文件先引入才能用 --><script src…

Project_Euler-06 题解

Project_Euler-06 题解 题目描述 两个公式 等差数列求和公式 i i i项&#xff1a; a i a_{i} ai​ 项数&#xff1a; n n n 公差&#xff1a; d d d 和&#xff1a; S n S_{n} Sn​ a n a 1 ( n − 1 ) d S n n ( a 1 a n ) 2 a_{n} a_{1} (n - 1)d\\ S_{n} \frac{n(a_…

L1-047 装睡-java

输入格式&#xff1a; 输入在第一行给出一个正整数N&#xff08;≤10&#xff09;。随后N行&#xff0c;每行给出一个人的名字&#xff08;仅由英文字母组成的、长度不超过3个字符的串&#xff09;、其呼吸频率和脉搏&#xff08;均为不超过100的正整数&#xff09;。 输出格…

百度地图海量点方案趟坑记录(百度地图GL版 + MapVGL + vue3 + ts)

核心需求描述 不同层级有不同的海量图标展示底层海量图标需要展示文字拖动、放大缩小都需要重新请求数据并展示固定地图中心点&#xff08;拖动、放大缩小&#xff0c;中心点始终在地图中心&#xff09; 示例图片&#xff1a;&#xff08;某些图片涉及公司数据&#xff0c;就未…

2万字带你看懂什么是智能座舱

现在市面上只要在卖的车&#xff0c;在推销出售的时候&#xff0c;如果不说这车有智能座舱&#xff0c;你都不好意思给别人推销&#xff0c;哪怕仅仅只有一个纯液晶显示的中控大屏&#xff0c;到底什么样的座舱才算是智能座舱&#xff1f; 座舱一词由飞机和船舶行业引进而来&am…

初识AXI总线

AXI是一种总线类型&#xff0c;具有高传输速率&#xff0c;高带宽&#xff0c;低时延等特性 AXI具有三种类型&#xff1a; 1.AXI_FULL:满足高性能内存映射&#xff08;memory-mapped&#xff09;需求 2.AXI_lite:不可突发传输 3.AXI_stream:面向数据流的传输 AXI的工作方式&a…

基于Java在线蛋糕店商城系统设计与实现(源码+部署文档)

博主介绍&#xff1a; ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅 &#x1f447;&#x1f3fb; 不然下次找不到 Java项目精品实…

【八股文】面向对象基础

【八股文】面向对象基础 面向对象和面向过程的区别 面向过程把解决问题的过程拆成一个个方法&#xff0c;通过一个个方法的执行解决问题。面向对象会先抽象出对象&#xff0c;然后用对象执行方法的方式解决问题。 创建一个对象用什么运算符?对象实体与对象引用有何不同? …