【图解 cartographer】 之地图概率更新过程

2023-10-10 01:59

本文主要是介绍【图解 cartographer】 之地图概率更新过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


前言:
最近一直在研究建图,对google的开源SLAM框架 Cartographer 进行了源码梳理,发现很多巧妙的算法设计,结合原论文 《Real-time Loop Closure in 2D LIDAR SLAM》 的理论简介,才恍然理解其核心目的就是实现实时回环;为了达到实时的效果,其引入了一些其他方法来进行计算加速。理解其中的思想方法,整个过程有点漫长但很有意思。Cartographer 中工程设计中可参考的点很多,本篇先把其概率地图更新部分的流程进行说明,用图示意的方式对其思路进行展示,强化自我理解。

地图更新的目的: 根据最新采集到的雷达点,在与地图匹配后,把雷达点插入到当前地图中,其本质是对栅格地图概率的更新。

地图更新的方式:

  1. 逐一对地图中栅格进行概率更新,一般用对数 O d d ( x ) Odd(x) Odd(x) 进行相乘或者 L o g ( O d d ( x ) ) Log(Odd(x)) Log(Odd(x)) 相加减,因此当地图较大时,大量的乘法计算会影响地图更新的效率;所以为了提升计算效率,其他大部分SLAM实现中地图更新采用 L o g ( O d d ( x ) ) Log(Odd(x)) Log(Odd(x)) 相加减的方式。
  2. Cartographer针对地图更新过程,采用以空间换时间的策略,主要是建立地图更新对照表,然后更新过程就直接变成了查表操作,连加减法都省去了,牺牲内存空间,节约时间。

其具体操作流程如下,地图要更新的点主要是激光所扫到的范围:

地图查表更新全过程示意
如上图所示,当第1个栅格中的值是700时,通过查表可立即获得更新后的值892,然后直接填入第1个栅格中,即可完成栅格概率更新。那本质上说,更新表是存放的有序更新后的值,而表的序号就是更新前的值,因此该问题的重点是如何构建这张表。这里也许注意,Cartographer子图中存放的既不是概率也不是赔率odd,而是概率的映射[0~65536]。

注: 上图的值只做流程展示参考,不与真实计算挂钩。

在代码中构建这两张表的入口在代码在类LaserFanInserter的构造函数中:

// laser_fan_inserter.cc
LaserFanInserter::LaserFanInserter(const proto::LaserFanInserterOptions& options): options_(options),hit_table_(mapping::ComputeLookupTableToApplyOdds(mapping::Odds(options.hit_probability()))),miss_table_(mapping::ComputeLookupTableToApplyOdds(mapping::Odds(options.miss_probability()))) {}

其调用probability_values.cc中ComputeLookupTableToApplyOdds函数,可以看到,这里分别构造了两张表hit_table_和miss_table_,这就是前面提到的地图概率更新表。地图中点的更新状态有三种:hit、miss、free(不更新),options.hit_probability()和options.miss_probability()为激光雷达点击中(hit)和穿过(miss)的概率,是Lua配置数据输入:

options.hit_probability() = 0.55;
options.miss_probability() = 0.49;

我们可以看到在probability_values.h和probability_values.cc中实现了整个表的构建:

// probability_values.h
// 概率值转Odds
inline float Odds(float probability) {return probability / (1.f - probability);
}
// Odds转概率值
inline float ProbabilityFromOdds(const float odds) {return odds / (odds + 1.f);
}// 概率值的范围为 [0.1, 0.9]
constexpr float kMinProbability = 0.1f;
constexpr float kMaxProbability = 1.f - kMinProbability;// Clamps probability to be in the range [kMinProbability, kMaxProbability].
inline float ClampProbability(const float probability) {return common::Clamp(probability, kMinProbability, kMaxProbability);
}constexpr uint16 kUnknownProbabilityValue = 0;
constexpr uint16 kUpdateMarker = 1u << 15;  // = 32768// Converts a probability to a uint16 in the [1, 32767] range.
inline uint16 ProbabilityToValue(const float probability) {const int value =common::RoundToInt((ClampProbability(probability) - kMinProbability) *(32766.f / (kMaxProbability - kMinProbability))) +1;// DCHECK for performance.DCHECK_GE(value, 1);DCHECK_LE(value, 32767);return value;
}extern const std::vector<float>* const kValueToProbability;// Converts a uint16 (which may or may not have the update marker set) to a
// probability in the range [kMinProbability, kMaxProbability].
inline float ValueToProbability(const uint16 value) {return (*kValueToProbability)[value];
}

头文件中主要实现了基本概率、赔率、对应整数的转换函数,概率的范围为[0.1, 0.9],对应value的范围为[1, 32767],若value为0表示地图为unknow状态,也就是初始灰色状态。kValueToProbability为从Value对应的概率值的表,为计算地图更新hit和miss表做准备。

这里kUpdateMarker 为已更新后的标志量,是为了防止miss和hit重复对地图更新,其值为32768,为uint16 的最高位,其操作的方式是,更新时加上,更新完了后减去。

// probability_values.cc
// 0 is unknown, [1, 32767] maps to [kMinProbability, kMaxProbability].
float SlowValueToProbability(const uint16 value) {CHECK_GE(value, 0);CHECK_LE(value, 32767);if (value == kUnknownProbabilityValue) {// Unknown cells have kMinProbability.return kMinProbability;}const float kScale = (kMaxProbability - kMinProbability) / 32766.f;return value * kScale + (kMinProbability - kScale);
}// 直接巧妙计算Value转概率对照表,重复2次,区别了带marker和不带marker
const std::vector<float>* PrecomputeValueToProbability() {std::vector<float>* result = new std::vector<float>;// Repeat two times, so that both values with and without the update marker// can be converted to a probability.for (int repeat = 0; repeat != 2; ++repeat) {for (int value = 0; value != 32768; ++value) {result->push_back(SlowValueToProbability(value));}}return result;
}}  // namespaceconst std::vector<float>* const kValueToProbability = PrecomputeValueToProbability();

在计算ComputeLookupTableToApplyOdds更新表之前,这里先利用PrecomputeValueToProbability计算了一个kValueToProbability 表,也是为了加速计算更新表:
在这里插入图片描述

注意:

  1. 这里直接巧妙计算Value转概率对照表,重复2次,区别了带marker和不带marker,但都可以同时获得相同的概率值。
std::vector<uint16> ComputeLookupTableToApplyOdds(const float odds) {std::vector<uint16> result;// 计算Value=0时更新后的Value,可直接更新,因为更新前=0为Unknownresult.push_back(ProbabilityToValue(ProbabilityFromOdds(odds)) + kUpdateMarker);// 从第2个开始,到32767for (int cell = 1; cell != 32768; ++cell) {result.push_back(ProbabilityToValue(ProbabilityFromOdds(odds * Odds((*kValueToProbability)[cell]))) +kUpdateMarker);}return result;
}

在这里插入图片描述

M n e w ( x ) = c l a m p [ o d d s − 1 ( o d d s ( M o l d ( x ) ) ⋅ o d d s ( p h i t ) ) ] M_{new}(x) =clamp[odds^{-1}(odds(M_{old}(x))\cdot odds(p_{hit}))] Mnew(x)=clamp[odds1(odds(Mold(x))odds(phit))]

注意:

  1. 这里的更新表第一个数是直接存入的,直接用传入的赔率值计算,这其实是在计算未知区域的更新Value,也是地图中当前值为0的区域;
  2. 更新后的Value都加上了kUpdateMarker标志,该标志将在下次地图更新之前被清除。清除操作在probability_grid.h文件中的StartUpdate()函数中。

到此,我们已经成功建立了更新查询的hit和miss表,如下开始介绍其地图更新的核心操作,如下laser_fan_inserter.cc文件中:

// laser_fan_inserter.cc
void LaserFanInserter::Insert(const sensor::LaserFan& laser_fan,ProbabilityGrid* const probability_grid) const {// 去除上一次更新后留下的kUpdateMarker标志CHECK_NOTNULL(probability_grid)->StartUpdate();// By not starting a new update after hits are inserted, we give hits priority// (i.e. no hits will be ignored because of a miss in the same cell).// 利用激光的CastRays模型获得his和miss栅格在grid_map上的索引表,然后根据前面构建的更新表进行更新// hit和miss一次调用ApplyLookupTable()进行更新CastRays(laser_fan, probability_grid->limits(),[this, &probability_grid](const Eigen::Array2i& hit) {probability_grid->ApplyLookupTable(hit, hit_table_);},[this, &probability_grid](const Eigen::Array2i& miss) {// 可根据外部参数关闭miss区域更新if (options_.insert_free_space()) {probability_grid->ApplyLookupTable(miss, miss_table_);}});
}

注意:

  1. 这里的CastRays()传入的是一个函数指针;
  2. 这块后面可以单独拎出来讲解。
// probability_grid.h
bool ApplyLookupTable(const Eigen::Array2i& xy_index,const std::vector<uint16>& table) {DCHECK_EQ(table.size(), mapping::kUpdateMarker);const int cell_index = GetIndexOfCell(xy_index);uint16& cell = cells_[cell_index];if (cell >= mapping::kUpdateMarker) {return false;}update_indices_.push_back(cell_index);cell = table[cell];  // 真正的更新操作DCHECK_GE(cell, mapping::kUpdateMarker);UpdateBounds(xy_index);return true;
}

到这里,地图概率更新的过程已经梳理完成,这其中还有很多具体的细分点,比如:MapLimit的计算、CastRays模型的介绍、边界的更新,等等。


好了,今天就简单写到这里。

这篇关于【图解 cartographer】 之地图概率更新过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/177141

相关文章

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

hdu4865(概率DP)

题意:已知前一天和今天的天气概率,某天的天气概率和叶子的潮湿程度的概率,n天叶子的湿度,求n天最有可能的天气情况。 思路:概率DP,dp[i][j]表示第i天天气为j的概率,状态转移如下:dp[i][j] = max(dp[i][j, dp[i-1][k]*table2[k][j]*table1[j][col] )  代码如下: #include <stdio.h>#include

图解TCP三次握手|深度解析|为什么是三次

写在前面 这篇文章我们来讲解析 TCP三次握手。 TCP 报文段 传输控制块TCB:存储了每一个连接中的一些重要信息。比如TCP连接表,指向发送和接收缓冲的指针,指向重传队列的指针,当前的发送和接收序列等等。 我们再来看一下TCP报文段的组成结构 TCP 三次握手 过程 假设有一台客户端,B有一台服务器。最初两端的TCP进程都是处于CLOSED关闭状态,客户端A打开链接,服务器端

图解可观测Metrics, tracing, and logging

最近在看Gophercon大会PPT的时候无意中看到了关于Metrics,Tracing和Logging相关的一篇文章,凑巧这些我基本都接触过,也是去年后半年到现在一直在做和研究的东西。从去年的关于Metrics的goappmonitor,到今年在排查问题时脑洞的基于log全链路(Tracing)追踪系统的设计,正好是对这三个话题的实践。这不禁让我对它们的关系进行思考:Metrics和Loggi

全英文地图/天地图和谷歌瓦片地图杂交/设备分布和轨迹回放/无需翻墙离线使用

一、前言说明 随着风云局势的剧烈变化,对我们搞软件开发的人员来说,影响也是越发明显,比如之前对美对欧的软件居多,现在慢慢的变成了对大鹅和中东以及非洲的居多,这两年明显问有没有俄语或者阿拉伯语的输入法的增多,这要是放在2019年以前,一年也遇不到一个人问这种需求场景的。 地图应用这块也是,之前的应用主要在国内,现在慢慢的多了一些外国的应用场景,这就遇到一个大问题,我们平时主要开发用的都是国内的地

cartographer+turtlebot+hokuyo|安装配置

cartographer+turtlebot+hokuyo|安装配置 系统:ubuntu 14.04+ros indigo 或 ubuntu 16.04+ros kinetic 平台:turtlebot2 传感器:hokuyo UTM-30LX laser 本文假设已经成功安装ubuntu,ros,以及各种与turtlebot相关的ros package等。本文主要介绍,怎么安装配car

Imageview在百度地图中实现点击事件

1.首先第一步,需要声明的全局有关类的引用 private BMapManager mBMapMan; private MapView mMapView; private MapController mMapController; private RadioGroup radiogroup; private RadioButton normalview; private RadioBu

MMO地图传送

本篇由以下四个点讲解: 创建传送点 传送点配置 编辑器扩展:传送点数据生成 传送协议与实现 创建传送点 建碰撞器触发 //位置归零 建一个传送门cube放到要传送的位置(这个teleporter1是传出的区域 这是从另一张地图传入时的传送门 创建一个脚本TeleporterObject给每个传送cube都绑上脚本 通过脚本,让传送门在编辑器下面还能绘制出来

概率DP (由一道绿题引起的若干问题。目前为一些老题,蒟蒻的尝试学习1.0)

概率DP: 利用动态规划去解决 概率 期望 的题目。 概率DP 求概率(采用顺推) 从 初始状态推向结果,同一般的DP类似,只是经历了概率论知识的包装。 老题: 添加链接描述 题意: 袋子里有w只白鼠,b只黑鼠,A和B轮流从袋子里抓,谁先抓到白色谁就赢。A每次随机抓一只,B每次随机 抓完一只后 会有另外一只随机老鼠跑出来。如果两个人都没有抓到白色,那么B赢。A先抓,问A赢得概率。 w b 均在

2024国赛论文拿奖快对照这几点及评阅要点,勿踩雷区!(国赛最后冲刺,提高获奖概率)

↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 2024“高教社杯”全国大学生数学建模竞赛已过去第三个夜晚,小伙伴们都累了没有,如果感到思维滞涩,别忘了稍作休息,放松一下自己,准备迎接国赛非常重要的收尾阶段——论文。 国赛这几天的努力最后都