gtsam初探以及结合LIO-SAM算法的一些理解

概述

GTSAM(Georgia Tech Smoothing and Mapping)是基于因子图的C++库,本篇基于GTSAM对因子图优化做一个简单了解和梳理,并以LIO-SAM为例进一步分析因子图优化在SLAM中的应用。

参考链接:
[0]gtsam官方文档
[1]https://blog.csdn.net/QLeelq/article/details/111368277
[2]https://zhuanlan.zhihu.com/p/621999120

1.基本概念

  • 因子图:因子图是一种无向图,由变量和因子组成
  • 变量:待优化的状态,可以有多个变量,如机器人位姿、IMU偏置等,每个变量包含了多帧的信息,如位姿X={x1, x2, ... , xn}
  • 因子:对应图优化中的边, 即两个变量或者观测与变量之间的约束,如两帧之间的位姿变换、3D点到相机图像的投影关系
  • 先验因子:基于GPS或者其他模块得到的观测信息或者位姿初值
  • 值:指变量的具体数值

在这里插入图片描述

如下图所示,{x1,x2,x3}为变量
黑色方块为因子,其中连接两个变量的为二元因子,如o12, o23;
连接一个变量的为一元因子,如f1, f2, f3, p1,其中p1为先验因子,f1, f2, f3为观测因子

2.官方例子说明

2.1 机器人运动模型

下图为gtsam官方文档中最简单的一个例子,一个基于马尔可夫链的机器人运动模型,即当前时刻的机器人位姿只由机器人上一时刻位姿决定。
在这里插入图片描述

如上图所示:

  • x1,x2,x3为变量,表示机器人的位姿
  • f0为一元先验因子,可以是初始位姿
  • f1,f2为二元因子,表示机器人之间的运动,可以是IMU或激光SLAM的里程计信息

2.1.1建立因子图

对于某个问题,在建立关于该问题的数学模型后,就可以基于数学模型建立因子图。gtsam为不同的数学模型内置了现成的模板,在创建因子图时只需要根据不同的模型选择不同的模板即可。
上述机器人运动模型因子图创建过程如下所示:

// step1:构建一个空的非线性因子图
NonlinearFactorGraph graph;// step2:为因子图添加因子并连接到变量
// [1]构建先验因子,也就是图中的f_0, 这里使用二维姿态(x,y,theta)简化问题
Pose2 priorMean(0.0, 0.0, 0.0);
// [2]初始化高斯噪声,代表我们对该因子的不确定性
noiseModel::Diagonal::shared_ptr priorNoise = noiseModel::Diagonal::Sigmas(Vector3(0.3, 0.3, 0.1));
// [3]将先验因子加入因子图, 其中的1表示该因子连接到第1个变量
graph.add(PriorFactor<Pose2>(1, priorMean, priorNoise));// [1]构建里程计因子,也就是图中的f_1,f_2, 往前移动2米,y轴不便,theta不变
Pose2 odometry(2.0, 0.0, 0.0);
// [2]初始化高斯噪声
noiseModel::Diagonal::shared_ptr odometryNoise = noiseModel::Diagonal::Sigmas(Vector3(0.2, 0.2, 0.1));
// [3]将里程计因子加入因子图, (1,2)(2,3)分别代表该里程计约束是从变量1到变量2,从变量2到变量3
graph.add(BetweenFactor<Pose2>(1, 2, odometry, odometryNoise));
graph.add(BetweenFactor<Pose2>(2, 3, odometry, odometryNoise));// step3:设置各个变量的初始值
Values initial;
initial.insert(1, Pose2(0.5, 0.0, 0.2));
initial.insert(2, Pose2(2.3, 0.1, -0.2));
initial.insert(3, Pose2(4.1, 0.1, 0.1));// step4:调用优化器并使用设定好的初始值对因子图优化
Values result = LevenberMarquardtOptimizer(graph, initial).optimize();

2.3机器人运动建模

进一步的,为上述运动模型添加观测模型,这里以GPS的测量为例
在这里插入图片描述

如上图所示:

  • x1,x2,x3为变量,表示机器人的位姿
  • o12,o23为二元因子,表示机器人之间的运动
  • f1,f2,f3为一元观测因子,即GPS的观测值

2.3.1自定义观测因子

通过NoiseModelFactor1<T>模板类定义新的一元因子,其中T为一元因子所对应变量的类型。

需要针对新因子定义一下几个信息:
(1)有关传感器观测值的私有成员变量。
(2)有关因子初始化的构造函数。
(3)用于计算评估误差的误差函数,功能包括:返回变量和观测值之间的残差信息、计算用于非线性优化的雅克比矩阵。

以定义一元GPS因子为例
(1)定义GPS的测量值mx_, my_
(2)定义GPS因子的构造函数,在使用时通过该函数实例化因子。用给定的x,y值初始化mx_, my_观测值,并给定与该因子连接的变量代号j以及观测的噪声协方差矩阵model
(3)重载用于计算评估误差的函数evaluateError:残差 = 变量值 - 观测值
公式定义如下:
在这里插入图片描述

自定义因子代码如下:

// 自定义因子是Pose2类型的,Pose2对应2D位姿的李群变换矩阵,即3x3的一个矩阵,包括2x2的旋转和2x1的平移
class UnaryFactor: public NoiseModelFactor1<Pose2> {// 观测的状态包括x和y两个值 double mx_, my_;public:// 成员函数, 在参数列表中初始化所关联变量的Id, 噪声模型, 测量值x和yUnaryFactor(Key j, double x, double y, const SharedNoiseModel& model):NoiseModelFactor1<Pose2>(model, j), mx_(x), my_(y){}// 残差函数模型,残差 = 变量值 - 观测值// 输入:当前时刻的位姿q,对误差求导的雅格比矩阵H// 位姿q=[qx,qy,angle]为三维,误差为两维,因此误差函数对位姿求导的雅格比矩阵为2x3Vector evaluateError(const Pose2& q, boost::optional<Matrix&> H = boost::none) const{// 如果存在雅格比矩阵,对其进行更新if(H){   // Jac是误差函数对位姿求导的雅格比矩阵,因为观测函数设置的比较简单,因此雅格比矩阵也很简单,具体的公式推导见下图gtsam::Matrix Jac = (Matrix(2,3)<< 1.0,0.0,0.0, 0.0,1.0,0.0).finished();;(*H) = Jac;}return (Vector(2) << q.x() - mx_, q.y() - my_).finished();}
};

创建因子图

// step1:构建一个空的非线性因子图
NonlinearFactorGraph graph;
// step2:为因子图添加因子并连接到变量
// 建立观测的噪声模型
noiseModel::Diagonal::shared_ptr unaryNoise = noiseModel::Diagonal::Sigmas(Vector2(0.1, 0.1));
// 加入自定义因子
graph.add(boost::make_shared<UnaryFactor>(1, 0.0, 0.0, unaryNoise));
graph.add(boost::make_shared<UnaryFactor>(2, 2.0, 0.0, unaryNoise));
graph.add(boost::make_shared<UnaryFactor>(3, 4.0, 0.0, unaryNoise));// step3:设置各个变量的初始值
Values initial;
initial.insert(1, 0.1, 0.0);
initial.insert(2, 2.0, -0.1);
initial.insert(3, 4.1, 0.1);// step4:调用优化器并使用设定好的初始值对因子图优化
Values result = LevenberMarquardtOptimizer(graph, initial).optimize();

3.LIO-SAM中的因子图应用

3.1IMU因子

在LIO-SAM的imageProjection模块主要对IMU数据进行积分,使用gtsam的IMU预积分模块。该模块订阅雷达里程计的位姿,将该位姿作为初始积分的先验,然后通过gtsam的IMU预积分模块得到IMU高频里程计,同时更新偏置信息,因子图如下:
在这里插入图片描述

在这个过程中:

  • 变量:位姿(旋转和平移)、速度、偏置
  • 先验因子:位姿的先验因子为雷达里程计的位姿,速度和偏置的先验因子为0
  • 观测因子:IMU因子,每个时刻IMU的角速度加速度,通过观测方程转化为观测值即位姿

1.创建因子图并添加先验因子

// 声明非线性因子图
gtsam::NonlinearFactorGraph graphFactors;// [1]加入雷达里程计先验因子
// 将雷达里程计位姿转换到IMU坐标系作为先验
prevPose_ = lidarPose.compose(lidar2Imu);
// 创建因子,X(0)表示先验因子连接到X第一个变量,prevPose_为先验因子具体的值,priorPoseNoise为之前定义的噪声
gtsam::PriorFactor<gtsam::Pose3> priorPose(X(0), prevPose_, priorPoseNoise);
// 添加因子
graphFactors.add(priorPose);// [2]加入速度先验因子
prevVel_ = gtsam::Vector3(0, 0, 0);// 初始化为0
gtsam::PriorFactor<gtsam::Vector3> priorVel(V(0), prevVel_, priorVelNoise);// 创建
graphFactors.add(priorVel);// 加入// [3]加入Bias先验因子
prevBias_ = gtsam::imuBias::ConstantBias();// 初始化为0
gtsam::PriorFactor<gtsam::imuBias::ConstantBias> priorBias(B(0), prevBias_, priorBiasNoise);// 创建
graphFactors.add(priorBias);// 添加// 将初始状态设置为因子图变量的初始值
graphValues.insert(X(0), prevPose_);
graphValues.insert(V(0), prevVel_);
graphValues.insert(B(0), prevBias_);// 使用优化器对变量进行更新
optimizer.update(graphFactors, graphValues);

2.添加IMU因子
通过gtsam自带的积分器,已经将一段时间内(两帧激光之间)的IMU进行了连续积分,积分角速度得到旋转,积分加速度得到线速度和位姿

// 使用IMU预积分的结果构建IMU因子,并加入因子图中
// 将IMU的连续积分结果封装为gtsam::PreintegratedImuMeasurements格式
const gtsam::PreintegratedImuMeasurements& preint_imu = dynamic_cast<const gtsam::PreintegratedImuMeasurements&>(*imuIntegratorOpt_);
// 从preint_imu中拿出一次积分结果即IMU因子,如上图这个因子连接了相邻两个时刻的位姿、速度和上一时刻的Bias
gtsam::ImuFactor imu_factor(X(key - 1), V(key - 1), X(key), V(key), B(key - 1), preint_imu);
graphFactors.add(imu_factor);// imuIntegratorOpt_->predict输入之前时刻的状态和偏差,预测当前时刻的状态
gtsam::NavState propState_ = imuIntegratorOpt_->predict(prevState_, prevBias_);
// 将IMU预积分的结果作为当前时刻因子图变量的初值
graphValues.insert(X(key), propState_.pose());
graphValues.insert(V(key), propState_.v());
graphValues.insert(B(key), prevBias_);// 更新一次优化器
optimizer.update(graphFactors, graphValues);
optimizer.update();// 从优化器中获取当前经过优化后估计值
gtsam::Values result = optimizer.calculateEstimate();
prevPose_  = result.at<gtsam::Pose3>(X(key));
prevVel_   = result.at<gtsam::Vector3>(V(key));
prevState_ = gtsam::NavState(prevPose_, prevVel_);
prevBias_  = result.at<gtsam::imuBias::ConstantBias>(B(key));

2.地图优化

在LIO-SAM的地图优化模块,只在添加关键帧时进行了因子图优化。因子图如下所示:
在这里插入图片描述

  • 变量:关键帧位姿
  • 因子:激光里程计因子、GPS因子、回环检测因子

下面一段代码是关于添加激光里程计因子的过程,对于第一帧添加的是PriorFactor类型的先验因子,对于后续帧添加的是BetweenFactor类型的二元因子,过程和上述一样。

void addOdomFactor()
{if (cloudKeyPoses3D->points.empty()){noiseModel::Diagonal::shared_ptr priorNoise = noiseModel::Diagonal::Variances((Vector(6) << 1e-2, 1e-2, M_PI*M_PI, 1e8, 1e8, 1e8).finished()); // rad*rad, meter*metergtSAMgraph.add(PriorFactor<Pose3>(0, trans2gtsamPose(transformTobeMapped), priorNoise));initialEstimate.insert(0, trans2gtsamPose(transformTobeMapped));}else{noiseModel::Diagonal::shared_ptr odometryNoise = noiseModel::Diagonal::Variances((Vector(6) << 1e-6, 1e-6, 1e-6, 1e-4, 1e-4, 1e-4).finished());gtsam::Pose3 poseFrom = pclPointTogtsamPose3(cloudKeyPoses6D->points.back());gtsam::Pose3 poseTo   = trans2gtsamPose(transformTobeMapped);gtSAMgraph.add(BetweenFactor<Pose3>(cloudKeyPoses3D->size()-1, cloudKeyPoses3D->size(), poseFrom.between(poseTo), odometryNoise));initialEstimate.insert(cloudKeyPoses3D->size(), poseTo);}
}

总结

综上来看,基于gtsam的因子图优化代码结构是比较清晰的,只不过在使用之前需要根据自己的误差模型选择合适的模板,针对里程计类型、GPS类型等gtsam都提供了相应的模板,对于gtsam库中没有的模板可以自行定义。

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

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

相关文章

最小化安装移动云大云操作系统--BCLinux-R8-U2-Server-x86_64-231017版

有个业务系统因为兼容性问题&#xff0c;需要安装el8.2的系统&#xff0c;因此对应安装国产环境下的BCLinuxR8U2系统来满足用户需求。BCLinux-R8-U2-Server是中国移动基于AnolisOS8.2深度定制的企业级X86服务器通用版操作系统。本文记录在DELL PowerEdge R720xd服务器上最小化安…

Redis-分布式锁

Redis-setnx实现分布式锁 Redis实现分布式锁主要利用Redis的setnx命令。setnx是SET if not exists(如果不存在&#xff0c;则SET)的简写。(NX是互斥、EX是设置超时时间) 但是如上会面临一个问题&#xff0c;当业务执行时间太长&#xff0c;导致业务还没执行完锁已到期&#xf…

学习笔记二十九:K8S配置管理中心Configmap实现微服务配置管理

Configmap概述 Configmap概述Configmap能解决哪些问题&#xff1f;Configmap应用场景局限性 Configmap创建方法命令行直接创建通过文件创建指定目录创建configmap 编写configmap资源清单YAML文件使用Configmap通过环境变量引入&#xff1a;使用configMapKeyRef通过环境变量引入…

Azure机器学习 - 在 Azure 机器学习中上传、访问和浏览数据

目录 一、环境准备二、设置内核三、下载使用的数据四、创建工作区的句柄五、将数据上传到云存储空间六、访问笔记本中的数据七、创建新版本的数据资产八、清理资源 机器学习项目的开始阶段通常涉及到探索性数据分析 (EDA)、数据预处理&#xff08;清理、特征工程&#xff09;以…

Java语法 - 01

Java基础 Java 是一种广泛使用的高级编程语言&#xff0c;最初由Sun Microsystems于1995年发布。它被设计为具有简单、可移植和面向对象的特性&#xff0c;以满足跨平台应用程序开发的需求。以下是一些关于 Java 的简介&#xff1a; 跨平台性&#xff1a;Java 程序可以在不同…

基于QT的简易计算器(一)

目录 0 简介1.设计原理1.1界面设计1.1.1界面基本布局1.1.2 界面调整和美化1.1.2 控件重命名 1.2 连接信号和槽1.3 软件逻辑1.3.1四则运算1.3.2 连续运算&#xff08;不完全&#xff09;的原理1.3.3 清屏1.3.4 退格1.3.5 等于1.3.6 小数点 2.总结与拓展 0 简介 最近在学QT&…

Django实战项目-学习任务系统-自定义URL拦截器

接着上期代码框架&#xff0c;6个主要功能基本实现&#xff0c;剩下的就是细节点的完善优化了。 首先增加URL拦截器&#xff0c;你不会希望没有登录用户就可以进入用户主页各种功能的&#xff0c;所以增加URL拦截器可以解决这个问题。 Django框架本身也有URL拦截器&#xff0…

【Python入门二】安装第三方库(包)

安装第三方库/包 1 使用pip安装2 使用PyCharm软件安装3 离线安装&#xff0c;使用whl文件安装参考 在Python中&#xff0c;有多种安装第三方库的方法&#xff0c;下面是一些常用的方法&#xff1a; 1 使用pip安装 pip是Python中最常用的包管理工具&#xff0c;也是最常用的在线…

代码随想录 Day35 动态规划04 01背包问题和完全背包问题 LeetCode T416 分割等和子集

背包问题 说到背包问题大家都会想到使用动规的方式来求解,那么为什么用动规呢,dp数组代表什么呢?初始化是什么,遍历方式又是什么,这篇文章笔者将详细讲解背包问题的经典例题0-1背包问题和完全背包问题的解题方式,希望能帮助到大家 1.暴力方式 有人一提到背包问题就只会使用动态…

OpenGL ES入门教程(一)编写第一个OpenGL程序

OpenGL ES入门教程&#xff08;一&#xff09;编写第一个OpenGL程序 前言 从本文开始我将参考学习OpenGL ES应用开发实践指南 Android卷 [&#xff08;美&#xff09;KevinBrothaler著]&#xff08;提取码: 394m&#xff09;&#xff0c;并基于自己的理解以更加通俗易懂的方式…

近独立粒子的最概然分布

近独立粒子&#xff1a;粒子之间相互作用微弱基本粒子中&#xff0c;自旋量子数为半整数的有 电子 、 质子 、中子、中微子自旋量子数为整数的有 光子、pi介子 经典力学描述系统的微观运动状态 经典力学中&#xff0c;全同粒子可以分辨量子力学&#xff0c;全同粒子不可以分辨微…

2023-11-02 LeetCode每日一题(环和杆)

2023-11-02每日一题 一、题目编号 2103. 环和杆二、题目链接 点击跳转到题目位置 三、题目描述 总计有 n 个环&#xff0c;环的颜色可以是红、绿、蓝中的一种。这些环分别穿在 10 根编号为 0 到 9 的杆上。 给你一个长度为 2n 的字符串 rings &#xff0c;表示这 n 个环在…

BetterDisplay Pro v1.4.15(显示器管理管理软件)

BetterDisplay Pro是一款屏幕显示优化工具&#xff0c;可用于Windows和Mac操作系统。它可以帮助用户调整屏幕的亮度、对比度、色彩等参数&#xff0c;以获得更好的视觉体验。此外&#xff0c;BetterDisplay Pro还提供了一些额外的功能&#xff0c;如屏幕分割、窗口管理、快捷键…

Django3框架-(3)-[使用websocket]:使用channels实现websocket功能;简化的配置和实际使用方式

概述&#xff1a; 对于Django使用channels实现websocket的功能&#xff0c;之前就写了几篇博文了。随着在项目的使用和实际维护来说&#xff0c;重新设置了相关处理方法。 一般来说&#xff0c;前后端都只维护一个全局的连接&#xff0c;通过携带数据来判断具体的操作&#x…

Flink1.18新特性生产环境应用的重点解读!

大家好&#xff0c;我是你们的群主王知无呀。 Flink 1.18已经于近期发布了。在这个新版本中新增了很多新的功能和特性。在这些特性中&#xff0c;有一些是生产环境非常重要的能力&#xff0c;大家在使用过程中可以重点参考和了解其中的原理。 算子级别状态保留时间TTL设置 首先…

GitHub经常打不开或者访问解决办法

访问慢或无法访问的原因&#xff1a;DNS解析是最为基础的一个环节。由于Github的服务器在全球各地&#xff0c;域名解析所需的时间也会不同&#xff0c;这就导致了在特定地区可能会出现Github无法正常访问的情况。 解决&#xff1a;查询到github对应的IP&#xff0c;然后在host…

精准测试:提高软件质量和用户满意度的利器

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

腾讯云域名备案后,如何解析到华为云服务器Linux宝塔面板

一、购买域名并且进行备案和解析&#xff0c;正常情况下&#xff0c;购买完域名&#xff0c;如果找不到去哪备案&#xff0c;可以在腾讯云上搜索“备案”关键词就会出现了&#xff0c;所以这里不做详细介绍&#xff0c;直接进行步骤提示&#xff1a; 二、申请ssl证书&#xff0…

uniapp使用抖音微信自定义组件

tt.vue中使用video-player组件 用到的目录如下&#xff1a; pages.json {"path": "pages/Tabbar/tt/tt","style": {"navigationBarTitleText": "","enablePullDownRefresh": false,// 使用自定义组件"using…

Nginx服务器安装证书并启用SSL(acme.sh)

前提 您已购置vps服务器&#xff0c;例如阿里云全球站ecs、AWS EC2、Azure VM、GCP Compute等安全组已开启80、443端口&#xff0c;且访问源设置为0.0.0.0/0域名已设置A记录指向当前操作服务器&#xff0c;若您使用aws ec2&#xff0c;有公有 IPv4 DNS&#xff0c;可供使用 安…