(02)Cartographer源码无死角解析-(53) 2D后端优化→位姿图优化理论(SPA)讲解、核型函数调用流程

news2024/11/16 0:53:04

讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下:
(02)Cartographer源码无死角解析- (00)目录_最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/127350885
 
文末正下方中心提供了本人 联系方式, 点击本人照片即可显示 W X → 官方认证 {\color{blue}{文末正下方中心}提供了本人 \color{red} 联系方式,\color{blue}点击本人照片即可显示WX→官方认证} 文末正下方中心提供了本人联系方式,点击本人照片即可显示WX官方认证
 

一、前言

通过前面的一系列博客,已经完成了2D点云扫描匹配:含相关性暴力搜索匹配以及ceres扫描匹配的讲解。总得来说,关于Cartographer前端数据处理以及前端位姿优化的相关代码已经深入解析。那么接下来,就是对后端部分代码讲解,这里暂时还是以2D后端优化为例,后续再向3D扩展。后端优化的的代码主要集中在如下文件:

src/cartographer/cartographer/mapping/internal/2d/pose_graph_2d.cc

源码中的实现,主要参考SPA论文:Efficient Sparse Pose Adjustment for 2D Mapping
论文翻译:https://blog.csdn.net/u014527548/article/details/106238658
 

二、位姿图理论

Cartographer 后端优化是使用位姿图的方式,如果了解g2o的朋友应该知道,图的核心在于点(节点)与边的建立,边主要起到约束作用,Cartographer 位姿图中包含了两种约束: 子图内 \color{red}子图内 子图内 子图间 \color{red}子图间 子图间 约束,另外在回环检测中会计算子图间约束。

1、什么是图

由节点和边组成的一种数据结构, 节点之间的关系可以是任意的, 图中任意两节点之间都可能相关(存在边)。

2、什么是位姿图

SPA 论文中对位姿图的定义: 位姿图是一组通过非线性约束连接的机器人位姿, 这些非线性约束是从对附近位姿共有的特征的观察中获得的。位姿图是一种图, 节点代表位姿, 边代表 2 个位姿间的相对坐标变换(也叫约束),如下三角形表示机器人位姿, 三角形之间的连线表示约束(坐标变换):
在这里插入图片描述
如果对于图优化比较陌生的朋友可以参考本人博客:史上最简SLAM零基础解读(10.1) - g2o(图优化)→简介环境搭建(slam十四讲第二版为例)

3.什么是优化

由于前端里程计会有累计误差, 那有没有一种方法可以将这种累计误差减小甚至消除掉呢?这就是优化的目的与作用。
在这里插入图片描述
左边表示没有优化之前、中间表示优化之后、右边表示真实地图。可以明显看到优化之后的精度提升较大。
 

三、残差项构建

通过前面博客的了解,可以知道优化问题核心就是在于残差项的构建,这里粘贴一下源码中的图示:
在这里插入图片描述
总的来说,可以分为如下几个步骤

第一步: \color{blue} 第一步: 第一步: 确定 2 个节点在 global 坐标系下的相对位姿变换。
第二步: \color{blue} 第二步: 第二步: 通过其他方式再次获取这 2 个节点的相对位姿变换
第三步: \color{blue} 第三步: 第三步: 对这 2 个相对位姿变换的差 的最小二乘问题进行求解
第四步: \color{blue} 第四步: 第四步: 进行求解之后会得到一个增量 𝛥𝑥 , 将当前位姿加上这个增量后就得到了优
化后的位姿。
在这里插入图片描述
其上的存在疑问的是第二步,如何通过其他方式再次获取这 2 个节点的相对位姿变换,该部分内容再后续部分结合源码进行详细讲解。
 

四、约束(位姿变换)

cartographer 中是使用 ceres 进行位姿图优化,ceres求解的是残差和,至少有两个约束才能够进行 ceres 的位姿图优化。如果觉得约束这个词不好理解,那么直接认为是位姿变换即可(或许有些出入,但是问题不大)。很明显,如果只有一个约束(位姿变换)是没有办法进行优化的,至少需要两个位姿变换然后做残差,简单理解就是一个为待优化位姿,一个为目标位姿。

第一个约束就是节点 global 坐标系下的相对坐标变换,那么问题来了,第二个约束如何求得呢?源码中包含了如下类型约束,这里大致看一下即可,后续会进行详细分析。

( 01 ) : \color{blue} (01): (01): 将节点(tracking 的位姿)与节点(子图原点位姿)在 global 坐标系下的相对位姿 与 约束(包含子图内约束与子图间约束) 的差值作为残差项。

( 02 ) : \color{blue} (02): (02): landmark 数据 与 通过 2 个节点位姿插值出来的相对位姿 的差值作为残差项

( 03 ) : \color{blue} (03): (03): 节点与节点间在 global 坐标系下的相对坐标变换 与 通过里程计数据插值出的相对坐标变换 的差值作为残差项

( 04 ) : \color{blue} (04): (04): 节点与节点间在 global 坐标系下的相对坐标变换 与 相邻 2 个节点在local 坐标系下的相对坐标变换 的差值作为残差项

( 05 ) : \color{blue} (05): (05): 节点与 gps 坐标系原点在 global 坐标系下的相对坐标变换 与 通过 gps 数据进行插值得到的相对坐标变换 的差值作为残差项

后续的讲解主要围绕以下函数或类型进行讲解(这里简单看一下即可):

ComputeConstraintsForNode() //计算节点的子图内约束与子图间约束(回环检测)
ConstraintBuilder2D() //回环检测(计算子图间约束)
PrecomputationGridStack2D() //多分辨率地图
FastCorrelativeScanMatcher2D() //基于分支定界算法的粗匹配
OptimizationProblem2D() //优化问题的构建与求解

 

五、函数调用→PoseGraph2D构建

在对上述函数进行具体分析之前,先来回顾以下之前的内容。首先就是关于2D后端优化的创建位于 src/cartographer/cartographer/mapping/map_builder.cc 文件中的 MapBuilder 构造函数中,可以看到如下代码:

  // 2d位姿图(后端)的初始化根据
  if (options.use_trajectory_builder_2d()) {//如果使用2d追踪
    pose_graph_ = absl::make_unique<PoseGraph2D>(
        options_.pose_graph_options(),
        absl::make_unique<optimization::OptimizationProblem2D>(
            options_.pose_graph_options().optimization_problem_options()),
        &thread_pool_);
  }

可以知道,其根据配置文件中的 src/cartographer/configuration_files/pose_graph.lua 的 optimization_problem_options 参数构建一个 optimization::OptimizationProblem2D 对象实例指针,然后利用还实例指针与 pose_graph.lua 文件中的 optimization_problem 以及 线程池 thread_pool_ 共同构建了一个 PoseGraph2D 实例对象,然后赋值给成员变量 MapBuilder::pose_graph_。

也就是说,MapBuilder::pose_graph_ 与 MapBuilder::pose_graph_::optimization_problem_ 都是在MapBuilder构造函数中完成的,同时在 MapBuilder::AddTrajectoryBuilder()函数中还可找到如下代码:

    // CollatedTrajectoryBuilder初始化
    trajectory_builders_.push_back(absl::make_unique<CollatedTrajectoryBuilder>(
        trajectory_options, sensor_collator_.get(), trajectory_id,
        expected_sensor_ids,
        // 将2D前端与2D位姿图打包在一起, 传入CollatedTrajectoryBuilder
        CreateGlobalTrajectoryBuilder2D(
            std::move(local_trajectory_builder), trajectory_id,
            static_cast<PoseGraph2D*>(pose_graph_.get()),
            local_slam_result_callback, pose_graph_odometry_motion_filter)));

也就是说,没创建一条轨迹都增加一个 CollatedTrajectoryBuilder 对象指针到 MapBuilder::trajectory_builders_ 之中,且 CollatedTrajectoryBuilder 中包含一个 std::unique_ptr<TrajectoryBuilderInterface> 类型的成员变量 wrapped_trajectory_builder_。其上的 CreateGlobalTrajectoryBuilder2D() 函数返回的就是一个 TrajectoryBuilderInterface 类型的智能指针对象。总而言之,上述代码创建了 CollatedTrajectoryBuilder 实例(该实例包含 GlobalTrajectoryBuilder 实例对象), 然后添加至 MapBuilder::trajectory_builders_ 之中。

最终可知另外 GlobalTrajectoryBuilder 中包含了如下两个成员变量:

  PoseGraph* const pose_graph_;     // 模板参数, 可以指向PoseGraph2D也可以指向PoseGraph3D
  std::unique_ptr<LocalTrajectoryBuilder> local_trajectory_builder_;  // 模板参数

 

六、函数调用→PoseGraph2D使用

通过上面的分析,知道 GlobalTrajectoryBuilder 中包含成员变量 PoseGraph* const pose_graph_。GlobalTrajectoryBuilder 与 PoseGraph 的交互,即调用关系是在 src/cartographer/cartographer/mapping/internal/global_trajectory_builder.cc 文件中的 AddSensorData() 函数中体现的,基本每个AddSensorData() 重载函数都调用的类似 pose_graph_->Addxxxx() 的函数。首先来看一下关于雷达数据AddSensorData() 重载:

  void AddSensorData(
      const std::string& sensor_id, //订阅的话题
      const sensor::TimedPointCloudData& timed_point_cloud_data) override {

 
 
 

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

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

相关文章

Docker镜像部署至Rancher全局配置 以xxl-job-admin为例

流程以xxl-job-admin为例 1.基础环境 win/mac/linuxRancherDocker 2.下载源码 从Github上下载xxl-job xxl-jobGithub xxl-job官方地址 3.修改源码 打开 xxl-job 下的 xxl-job-admin 修改 application-properties 文件 修改数据库 修改为这种格式&#xff1a; 大括号包…

MPLS实验

目录实验要求mpls简介mpls工作过程实验的配置环回的配置R1和R5之间公网的ospf配置配置mpls-ldp配置R1和R5间的mplsvpn私网的rip及ospf的宣告配置公网mp-bgp的建立R2和R4上面的双向重发布R7和R8之间创建R7和R8间的mplsvpn配置静态路由及环回重发布实验要求 如图 要求&#xff1…

【C++修炼之路】15.C++继承

每一个不曾起舞的日子都是对生命的辜负 继承C继承一. 继承的概念及定义1.1 继承的引出1.2 继承的概念1.3 继承的定义二.基类和派生类对象赋值转换三.继承中的作用域3.1 作用域的概念3.2 举例说明同名冲突四.派生类的默认成员函数4.1 派生类的构造函数4.2 派生类的拷贝构造函数4…

【python学习笔记】:数据科学库操作(二)

接上一篇&#xff1a; 4、PIL Python Imaging Library(PIL) 已经成为 Python 事实上的图像处理标准库了&#xff0c;这是由于&#xff0c;PIL 功能非常强大&#xff0c;但API却非常简单易用。但是由于PIL仅支持到 Python 2.7&#xff0c;再加上年久失修&#xff0c;于是一群志…

如果写不好 SQL,有没有好用的报表软件?

业务和技术在做报表这件事情上&#xff0c;究竟有多大差别&#xff1f; 一家企业、一个组织&#xff0c;只要一直在经营和运作&#xff0c;因为税务和其他原因就需要通过数据报表来反映当期的经营管理状况。而“做报表”这个事情&#xff0c;在企业内部不管是业务人员还是技术人…

HTTP之Referrer和Referrer-policy

目录 HTTP之Referrer和Referrer-policy Referer Referrer-policy 如何设置referrer 盗链 防盗链的工作原理 绕过图片防盗链 利用https网站盗链http资源网站&#xff0c;refer不会发送 设置meta 设置referrerpolicy"no-referrer" 利用iframe伪造请求refe…

C语言指针变量的运算

指针变量保存的是地址&#xff0c;而地址本质上是一个整数&#xff0c;所以指针变量可以进行部分运算&#xff0c;例如加法、减法、比较等&#xff0c;请看下面的代码&#xff1a;#include<stdio.h>intmain(){ int a 10,*pa &a,*paa &a; double b 99.9,*pb &a…

JTAG和SWD调试器

文章目录一、调试器二、JTAG三、SWD三、各自优缺点一、调试器 当我们开发单片机程序时&#xff0c;通常是在Windows或Linux上进行代码编写和编译&#xff0c;但是单片机并不直接集成在电脑上&#xff0c;怎么验证我们的单片机程序是否正确并烧录到单片机中&#xff0c;此时就需…

某游戏平台检测加速辅助案例分析

加速类辅助会对游戏平衡造成极大的破坏&#xff0c;这类辅助会通过HOOK api的方式来达到修改游戏对时间判断的目的&#xff0c;一般情况下&#xff0c;在R3层&#xff0c;这类辅助会在 QueryPerformanceCounter TimeGetTime GettickCount这三个API上HOOK&#xff0c;修改他们的…

Java-黑马Java学习作业-day15面向对象进阶(抽象类接口内部类)

学习视频链接&#xff1a;https://www.bilibili.com/video/BV17F411T7Ao 文章目录第一题&#xff1a;&#xff08;抽象类求面积和周长&#xff09;第二题&#xff1a;&#xff08;接口实现新旧手机功能&#xff09;第三题&#xff1a;&#xff08;使用子类和匿名内部类调用接口…

大厂高薪测试在线讲解Jmeter全套性能测试

Jmeter进行性能测试基本包含如下基本过程&#xff1a;1&#xff09;新增线程组创建测试线程组&#xff0c;并设置线程数量及线程初始化启动方式。2&#xff09;新增 JMeter 元组创建各种默认元组及测试元组&#xff0c;填入目标测试静态资源请求和动态资源请求参数及数据。3&am…

Task9:Excel数据透视表

文章目录一 Excel数据透视表1 数据透视表2 切片器3 数据透视的注意事项4 透视表常用法二 Excel数据透视图一 Excel数据透视表 1 数据透视表 什么是透视表&#xff1a;把明细表分类汇总的过程&#xff0c;可以按照不同的组合方式进行数据计算使用场景&#xff1a; 1.大量数据&…

QT入门Buttons之QCheckBox

目录 一、界面布局介绍 1、布局器中的位置及使用 2、常用属性 二、属性功能介绍 1、常用信号 2、测试信号stateChanged(int) 3、组合框效果 三、Demo展示 此文为作者原创&#xff0c;转载标明出处&#xff01; 一、界面布局介绍 1、布局器中的位置及使用 QCheckBox复选…

除了console.log,你还用过console其它的属性么?

目录前言console.infoconsole.debugconsole.errorconsole.warnconsole.time 和 console.timeEndconsole.group 和 console.groupEndconsole.table前言 刚学习前端的时候&#xff0c;vue还没用vue-devtools&#xff0c;react还没用 React Developer Tools&#xff0c;我们经常通…

fastjson 1.2.47 RCE漏洞保姆级复现

1.漏洞概述 Fastjson提供了autotype功能&#xff0c;允许用户在反序列化数据中通过“type”指定反序列化的类型&#xff0c;Fastjson自定义的反序列化机制时会调用指定类中的setter方法及部分getter方法&#xff0c;那么当组件开启了autotype功能并且反序列化不可信数据时&…

Sklearn中的算法效果评估手段

我们曾在《算法效果评估&#xff1a;均方根误差&#xff08;RMSE&#xff09;/ 标准误差》一文中介绍过评估算法效果使用的主要方法&#xff1a;均方根误差&#xff08;RMSE&#xff09;&#xff0c;但在实际应用中&#xff0c;评估算法效果还有更多内容&#xff0c;本文我们以…

VsCode安装及修改插件存储位置

【官网】&#xff1a; https://code.visualstudio.com/ 【修改插件仓库】&#xff1a; 创建名为VSCODE_EXTENSIONS的环境变量&#xff0c;内容指向自定义的文件夹即可&#xff08;位置无需限制&#xff09;。 【需要安装的插件】&#xff1a;

SBOM的介绍与syft和grype的使用

文章目录SBOM介绍工具syftgrypeSBOM介绍 SBOM&#xff08;软件物料清单&#xff09;是给定产品的中所有软件组件&#xff08;专有和开源代码&#xff09;、开源许可证和依赖项的清单。它提供了对软件供应链以及可能存在的任何许可证合规性、安全性和质量风险的可见性。 SBOM可…

Hystrix如何达到高可用

小型电商网站的页面展示采用页面全量静态化的思想。数据库中存放了所有的商品信息,页面静态化系统,将数据填充进静态模板中,形成静态化页面,推入Nginx服务器。用户浏览网站页面时,取用一个已经静态化好的html页面,直接返回回去,不涉及任何的业务逻辑处理。 用户每次浏览…

python输出不重复的字符

项目场景&#xff1a; 输入一个字符串&#xff0c;把最左边的10个不重复的字符&#xff08;大小写算不同字符&#xff09;挑选出来。 如不重复的字符不到10个&#xff0c;则按实际数目输出。问题描述 输出一个字符串&#xff0c;包含字符串s最左边10个不重复的字符。不到10个…