(02)Cartographer源码无死角解析-(44) 2D栅格地图→ProbabilityGrid

news2024/9/21 4:34:53

讲解关于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官方认证
 

一、前言

上一篇博客中,首先介绍了 ValueConversionTables,其最终的目的是生成一个转换表,该转换的主要的功能是把 [ 0 ,   1 ∽ 32767 ] [0,~1\backsim 32767] [0, 132767] 的数值映射至 [ 0.9 ,   0.1 ∽ 0.9 ] [0.9,~0.1\backsim 0.9] [0.9, 0.10.9]

虽然后重点讲解 Grid2D::GrowLimits() 函数,该函数主要功能就是判断传入的 point 是否位目前的子图之中,如果不在,则会把地图扩大至原来的四倍,直到 point 处于子图之中。地图的扩增思路如下图所示:
在这里插入图片描述
最后,还留下了如下两个疑问:

疑问 1 \color{red} 疑问1 疑问1→为什么Grid2D::FinishUpdate()函数中,栅格值为什么需要减去 kUpdateMarker?

疑问 2 \color{red} 疑问2 疑问2→ known_cells_box_ 是什么时候更新,哪里会发生变化,其作用是什么?

那么该篇博客,看下在 ProbabilityGrid 这个类中是否能够找到相关的答案。ProbabilityGrid 是 Grid2D 的派生类,该类声明于 src/cartographer/cartographer/mapping/2d/probability_grid.h 文件中,可见其成员变量还是比较简单的,仅仅一个转换表 ValueConversionTables* conversion_tables_ 而已。下面就来看看 probability_grid.cc 文件中其成员函数的实现。
 

二、ProbabilityGrid

1、ProbabilityGrid::ProbabilityGrid()

其存在两个构造函数,一个是根据参数 const MapLimits& limits 与 ValueConversionTables* conversion_tables 实例化对象,另外一个是根据 proto::Grid2D 的配置参数构造对象。但是过程都比较简单,主要就是构建了一个 Grid2D 对象,然后把转换表赋值给了成员变量 conversion_tables_。

第一个构造函数在调用父类构造函数的时候,传入了参数 kMinCorrespondenceCost 与 kMaxCorrespondenceCost,着两个值都为常量,定义如下:

constexpr float kMinProbability = 0.1f;                         // 0.1
constexpr float kMaxProbability = 1.f - kMinProbability;        // 0.9
constexpr float kMinCorrespondenceCost = 1.f - kMaxProbability; // 0.1
constexpr float kMaxCorrespondenceCost = 1.f - kMinProbability; // 0.9

kMinCorrespondenceCost 与 kMaxCorrespondenceCost 分别记录 Grid2D::correspondence_cost_cells_ 的最小值与最大值,当然指的是通过转换表映射之后的结果。
 

2、ProbabilityGrid::ProbabilityGrid()

从函数名可以直到,该函数的作用是设置栅格地图的概率概率值,第一个形参 cell_index 为 cell 的索引,第二个形参 probability 表示该 cell 被占用的机率。

其首先是通过 cell_index 获得该索引对应的cell,其是通过 mutable_correspondence_cost_cells() 函数获得,所以可以对该cell 的栅格值进行修改。不过由于 probability 表示该 cell 被占用的机率,所以这里调用了 ProbabilityToCorrespondenceCost() 函数,实际上就是 1-probability,获得未被占用的概率。然后再调用 CorrespondenceCostToValue() 函数,该函数的作用是把 [ 0.9 ,   0.1 ∽ 0.9 ] [0.9,~0.1\backsim 0.9] [0.9, 0.10.9] 的数值映射至 [ 0 ,   1 ∽ 32767 ] [0,~1\backsim 32767] [0, 132767],可以看做是转换表的逆操作。

因为对该 cell 被重新设置,说明其已经被探索到,即其是已知的了,所以通过 mutable_known_cells_box 函数把 cell 的二维索引(也可以看作是像素坐标)添加至 known_cells_box_ 之中。代码注释如下:

// Sets the probability of the cell at 'cell_index' to the given
// 'probability'. Only allowed if the cell was unknown before.
// 将 索引 处单元格的概率设置为给定的概率, 仅当单元格之前处于未知状态时才允许
void ProbabilityGrid::SetProbability(const Eigen::Array2i& cell_index,
                                     const float probability) {
  // 获取对应栅格的引用
  uint16& cell =
      (*mutable_correspondence_cost_cells())[ToFlatIndex(cell_index)];
  CHECK_EQ(cell, kUnknownProbabilityValue);
  // 为栅格赋值 value
  cell =
      CorrespondenceCostToValue(ProbabilityToCorrespondenceCost(probability));
  // 更新bounding_box
  mutable_known_cells_box()->extend(cell_index.matrix());
}

 

3、ProbabilityGrid::ApplyLookupTable()

从该函数的命名来看,叫做应用查询表,那么其是如何应用的,且作用是什么呢?该函数的主要逻辑如下:

( 1 ) \color{blue}(1) (1) 其传入的参数 cell_index 与上一函数相同,通过该索引可以获得 correspondence_cost_cells_ 中对应的 cell。;另一参数 table 就是查询表,通过该查询表可以可以把 [ 0 ,   1 ∽ 32767 ] [0,~1\backsim 32767] [0, 132767] 的数值映射到 [ 0.9 ,   0.1 ∽ 0.9 ] [0.9,~0.1\backsim 0.9] [0.9, 0.10.9]

( 2 ) \color{blue}(2) (2) 该函数首先判断一下查询表 table 的大小,需要保证其为 kUpdateMarker=32768,否则报错。然后把二维 cell_index 变换成一维索引 flat_index。然后调用 mutable_correspondence_cost_cells() 函数,再借助 flat_index 获得其对应的 cell。判断一下 *cell >= kUpdateMarker 是否成立,如果成立表示该 cell 已经更新过了,无需再次更新,同时返回 false。

( 3 ) \color{blue}(3) (3) 该 cell 没有更新过,则通过 mutable_update_indices() 函数把 flat_index 添加到 update_indices_ 之中,表示该 cell 已经更新过了,然后再对其进行更新。更新的操作为 : *cell = table[*cell],其更新的操作比较怪→ 疑问 1 \color{red} 疑问1 疑问1

( 4 ) \color{blue}(4) (4) 如果当前的 cell 更新了,说明该 cell 已经探索到,属于已知的 cell,通过 mutable_known_cells_box() 函数把其像素坐标添加到 known_cells_box_ 之中。

该函数的代码注释如下:

// Applies the 'odds' specified when calling ComputeLookupTableToApplyOdds()
// to the probability of the cell at 'cell_index' if the cell has not already
// been updated. Multiple updates of the same cell will be ignored until
// FinishUpdate() is called. Returns true if the cell was updated.
// 如果单元格尚未更新,则将调用 ComputeLookupTableToApplyOdds() 时指定的 'odds' 应用于单元格在 'cell_index' 处的概率
// 在调用 FinishUpdate() 之前,将忽略同一单元格的多次更新。如果单元格已更新,则返回 true
//
// If this is the first call to ApplyOdds() for the specified cell, its value
// will be set to probability corresponding to 'odds'.
// 如果这是对指定单元格第一次调用 ApplyOdds(),则其值将设置为与 'odds' 对应的概率

// 使用查找表对指定栅格进行栅格值的更新
bool ProbabilityGrid::ApplyLookupTable(const Eigen::Array2i& cell_index,
                                       const std::vector<uint16>& table) {
  DCHECK_EQ(table.size(), kUpdateMarker);
  const int flat_index = ToFlatIndex(cell_index);
  // 获取对应栅格的指针
  uint16* cell = &(*mutable_correspondence_cost_cells())[flat_index];
  // 对处于更新状态的栅格, 不再进行更新了
  if (*cell >= kUpdateMarker) {
    return false;
  }
  // 标记这个索引的栅格已经被更新过
  mutable_update_indices()->push_back(flat_index);
  // 更新栅格值
  *cell = table[*cell];
  DCHECK_GE(*cell, kUpdateMarker);
  // 更新bounding_box
  mutable_known_cells_box()->extend(cell_index.matrix());
  return true;
}

该函数中,比较难理解的点 *cell = table[*cell]; 这句代码了,为了方便理解,打印了变量 flat_index、以及更新之前与更新之后的 *cell。如下:

flat_index           250643    ......    267468     249052  ......  250643   251439
*cell(更新之前)         0       ......    16794      16794   ......  14336    16794
*cell(更新之后)       47104     ......    47511      47511   ......  45097    47511

这里并没有看出什么,但是比较奇怪的是查询表并非从0开始的,如下图所示,确实比较出乎意料,不过没有关系,后续看下该函数是如何被调用的,然后再来分析出现该现象的原因。
在这里插入图片描述
 

5、ProbabilityGrid::GetProbability()

该函数较为简单,其就是传入一个cell索引值,

 
 
 

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

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

相关文章

win10系统如何找回删除文件?教你五种恢复方法

在使用win10系统过程中&#xff0c;意外删除数据是经常发生的事情&#xff0c;那么win10系统如何找回删除文件呢&#xff1f;下面为大家总结了这五种win10文件误删除恢复方法&#xff0c;希望能够帮助到您。 ▍方法一、 ctrlz撤销法 ctrlz撤销法十分适用于恢复刚刚删除的文件…

绘图神器draw.io(写文必备)

绘图神器draw.io&#xff08;写文必备&#xff09; 前言 大家早上好&#xff0c;我是毛小悠&#xff0c;一个前端开发工程师。 最近发现一个绘图神器&#xff0c;没错&#xff0c;就是标题中的draw.io。免费、开源&#xff0c;功能强大&#xff0c;真的非常适合程序员。 我…

NSGA-II:快速精英多目标遗传算法(论文+代码解读)

目录 1.介绍 2. NSGA-II 2.1 快速非支配排序 2.1.1 NSGA的传统非支配排序 2.1.2 NSGA-II的快速非支配排序 2.2 多样性保护(Diversity Preservation) 2.2.1 NSGA的共享函数方法(sharing function) 2.2.2 NSGA-II的拥挤距离方法(crowded-comparison) 2.3 NSGA-II主循环…

4类时钟分频【附源码】1.偶数分频;2.奇数分频(占空比50%);3.奇数分频(任意无占空比);4.小数分频;

题目来源于牛客网&#xff0c;完整工程源码&#xff1a;https://github.com/ningbo99128/verilog 目录 VL37 偶数分频 VL40 奇数分频&#xff08;占空比50%&#xff09; VL42 奇数分频&#xff08;任意无占空比&#xff09; VL41 任意小数分频 VL37 偶数分频 题目介绍 请…

【阶段二】Python数据分析NumPy工具使用04篇:数组常用分析函数、数组广播与数组和矩阵的点积运算

本篇的思维导图: 数组常用分析函数 基本数学函数 函数 描述 abs 求取每个元素的绝对值

1-计算机系统概述

文章目录一.操作系统的基本概念&#xff08;一&#xff09;操作系统的特征&#xff08;二&#xff09;操作系统的目标和功能二.操作系统的发展与分类&#xff08;一&#xff09;手工操作阶段&#xff08;二&#xff09;批处理阶段&#xff08;三&#xff09;分时操作系统&#…

verilog学习笔记- 1)Quartus软件的使用

目录 新建工程&#xff1a; 设计输入&#xff1a; 配置工程&#xff1a; 编译 &#xff1a; 分配引脚&#xff1a; 编译工程&#xff1a; 下载程序&#xff1a; 固化程序 &#xff1a; 在开始使用 Quartus 软件之前&#xff0c;我们先来了解一下 Quartus 软件的使用流程…

Windows10 下的docker和Kubernetes安装

Docker官网Docker Desktop 安装 Docker官网 进入docker官网&#xff0c;下载windows版 docker desktop建议使用win10专业版在任务管理器查看是否开启了windows虚拟化如果没有开启&#xff0c;则在控制面板&#xff0c;启用或关闭windows功能&#xff0c;勾选Hyper-v&#xff…

面向对象1(类的成员变量的默认值规则、private、this、构造方法、基本数据类型和引用数据类型)

1、定义类的注意事项 类的成员变量的默认值规则 2、private 3、this关键字 语法是this. 4、构造方法 先Constructor生成构造方法&#xff0c;select无参构造&#xff0c;Ctrla全参构造 再Getter and Setter生成每个成员变量的getter 和 setter方法 也可以使用插件右键Ptg To …

java开发的美食菜谱网

简介 Java开发美食菜谱分享网站&#xff0c;健康专题分享&#xff0c;菜谱网源码。用户可以发布菜谱&#xff0c;可以评论&#xff0c;可以发布关于健康饮食的文章&#xff0c;还可以查看自己的主页。管理员可以管理所有的数据&#xff0c;审核数据。 演示视频 https://www.b…

ROS机器人底盘坐标像素变换

对于ROS小车底盘地图数据需要知道的点 1.整幅地图处于第三象限 2.坐标值代表距离&#xff0c;单位米。 3.分辨率单位&#xff08;米/像素&#xff09; 因此通过地图坐标得到像素坐标的办法&#xff1a; 像素坐标(实时坐标-初始坐标)/分辨率 实时坐标的获取&#xff1a; 触发条…

20230102单独编译Toybrick的TB-RK3588X开发板的Android12的内核【失败】

20230102单独编译Toybrick的TB-RK3588X开发板的Android12的内核【失败】 现阶段已经验证通过的编译指令&#xff1a; 直接使用build.sh编译了。 rootrootrootroot-adol-ADOLBOOK-I421UAY-ADOL14UA:~/toybrick3588_discrete$ source build/envsetup.sh rootrootrootroot-adol-A…

windows下编译opencv_contrib

文章目录基本环境二. 准备1. 下载所需各软件的安装文件2. 安装所需软件三.编译opencv步骤基本环境 cmaker&#xff1a;3.22.0vs2019&#xff1a;16.11.10CUDA&#xff1a;11.4.1cudnn&#xff1a;8.2.4opencv_source&#xff1a;4.5.4opencv_contrib&#xff1a;4.5.4python&a…

给程序员友人,2022投资总结的6000字评论

程序员友人的2022投资总结 以下是我的评论、回复。 友情提示&#xff1a;不好意思&#xff0c;长篇大论了&#xff0c;先长篇铺垫&#xff0c;再到正题。 下午说道&#xff1a;以我的最新投资原则方法&#xff0c;对你的持股情况&#xff0c;略显忧虑。 “一直放心不下”&#…

Ubuntu 18.04使用CMake编译可执行文件、动态链接库

目录准备新建文件编辑源文件编译生成可执行文件编译生成动态链接库准备 新建文件 在开始之前&#xff0c;我们先准备一个项目所需要的基本结构。 新建一个项目的文件夹test_so在项目文件夹中新建一个src文件夹存放源代码&#xff08;并新建一个test.cpp文件&#xff09;&…

基于 Prometheus 的监控平台简介

1. Prometheus 体系架构简介 Prometheus 是 CNCF 基金会的一款开源产品&#xff0c;主要用做监控系统&#xff0c;通常用来和 Zabbix 等监控系统做比较&#xff0c;以其简单易用、架构灵活著称。整个 Prometheus 的架构如上&#xff0c;分为几个重要的部分&#xff1a; 指标收…

Kubernetes:Ingress

文章目录1、安装 Ingress 控制器2、Ingress 扇出3、基于名称的虚拟托管4、Ingress TLSIngress 是一组路由规则&#xff0c;公开从集群外部到集群内服务的 HTTP 和 HTTPS 路由。 Ingress 控制器是一组 pod&#xff0c;负责通过负载均衡器来解析 Ingress 路由规则&#xff0c;将请…

序列到序列学习(seq2seq,BLEU)

根据“编码器-解码器”架构的设计&#xff0c; 我们可以使用两个循环神经网络来设计一个序列到序列学习的模型。 在实现编码器和解码器时&#xff0c;我们可以使用多层循环神经网络。 我们可以使用遮蔽来过滤不相关的计算&#xff0c;例如在计算损失时。 在“编码器&#xf…

crackme01——Acid_burn

最近对逆向稍微有点兴趣&#xff0c;自学了一点。写这个文章主要用于记录学习的过程。 首先看一下这个程序&#xff0c;其实就是个简单的比对程序&#xff0c;主界面按左侧按钮进入Name Serial界面&#xff0c;点击【Check it Baby】&#xff0c;则弹出窗口【Sorry, The seria…

[Java]JDBC学习笔记(尚硅谷康师傅JDBC)

文章目录&#x1f97d; 视频链接及资源下载&#x1f97d; JDBC简介&#x1f97d; JDBC程序访问数据库步骤&#x1f97d; 获取数据库连接&#x1f30a; 导入jar包&#x1f30a; Driver 接口&#x1f30a; JDBC URL&#x1f30a; 方式1&#xff1a;&#x1f30a; 方式2&#xff1…