0. 基本数学:三个小工具
0. 基本数学:看懂 gmapping 只需要三个小工具 📚 本系列导航:1. SLAM是什么:盲人摸象与边走边画 → 2. 机器人眼中的世界:传感器与栅格地图 → 3. 粒子滤波:一千个分身猜位置 → 4. RBPF:把鸡蛋问题拆开 → 5. gmapping的两大改进 → 6. 源码走读:一帧激光的旅程 → 7. 实战:跑起来与调参 → 8. 从理解到实现:迷你gmapping → 9. 局限与进阶:gmapping之外的世界 别怕,这一篇只有三个概念:概率、条件概率、贝叶斯。全程用”出门要不要带伞”讲清楚,看完你就能读懂 gmapping 的所有公式。 一、概率 = 置信度(你有多相信一件事)机器人不是全知的。它不知道自己在哪,只能”猜”–猜的过程,数学上用概率描述。 $$P(\text{下雨}) = 0.7$$ 读法:”下雨这件事,我信七成。” 概率是 0~1 之间的数:0 = 完全不信,1 = 完全确定; 机器人对”我在哪”的答案从来不是一个点,而是一个分布(哪里都有可能,只是可能性不同)。 SLAM 的本质:...
1. SLAM是什么:盲人摸象与边走边画
1. SLAM 是什么:一个”鸡生蛋”的故事 上一篇:0. 基本数学 | 下一篇:2. 机器人眼中的世界:传感器与栅格地图 一、先讲个故事:蒙眼探索办公楼想象你被蒙上眼睛、坐轮椅推进一栋从没来过的办公楼,手里只有两样东西: 轮椅轮子上的计步器(= 里程计):”我刚才大概往前走了 3 米,左转了 90°”; 一根能旋转 360° 的盲杖(= 激光雷达):每挥一圈,告诉你”正前方 2.1 米是墙、右边 1.3 米是桌子腿、……” 现在要求你:一边搞清楚自己在楼里的位置,一边画出整层楼的平面图。 你很快会遇到死循环: 12345flowchart LR A["想画地图?"] -->|"得先知道自己在哪<br/>不然墙画在哪?"| B["想知道自己在哪?"] B -->|"得有地图参照<br/>'我在走廊三分之一处'"| A style A fill:#fce8e6,stroke:#d93025 s...
2. 机器人眼中的世界:传感器与栅格地图
2. 机器人眼中的世界:传感器与栅格地图 上一篇:1. SLAM是什么:盲人摸象与边走边画 | 下一篇:3. 粒子滤波:一千个分身猜位置 gmapping 只吃两种数据:激光(”我看到什么”)和里程计(”我动了多少”)。这一篇讲清楚这两种数据长什么样、各有什么脾气,以及最终要画出的栅格地图是什么。 一、激光雷达:机器人的一圈”声呐”1.1 数据长什么样2D 激光雷达(如 RPLIDAR、思岚 S2)水平旋转,每转一圈打出几百束激光,返回一个距离数组: 123angles: [-90°, -89.5°, ..., +89.5°] (均匀分布 360°/N 份)ranges: [2.13, 2.10, 1.05, inf, ..., 1.87] ↑前方墙 ↑桌腿 ↑没打到东西(超量程) 一帧激光 = N 个 (角度, 距离) 对,从雷达中心向外呈放射状的一圈点。 1.2 关键参数 参数 含义 对 gmapping 的影响 频率 每秒扫几圈(典型 5~12 Hz) gmapping 依赖里程计先验,5Hz 也够用(Hecto...
3. 粒子滤波:一千个分身猜位置
3. 粒子滤波:用一千个”分身”猜位置 上一篇:2. 机器人眼中的世界:传感器与栅格地图 | 下一篇:4. RBPF:把鸡蛋问题拆开 gmapping 的”滤波”两个字,指的就是粒子滤波。这一篇不出现任何 SLAM 公式,只讲清楚一件事:为什么撒一把随机点,就能算出一个概率分布。 一、核心思想:算不出来的分布,就”撒豆子”采样出来回到 0. 基本数学:机器人对”我在哪”的答案是一个概率分布。理想的分布长这样(真实位置附近可能性高): $$P(x) = \text{某个极其复杂、写不出解析式的函数}$$ 问题:这个函数在计算机里根本没法直接表示和计算(里程计误差+激光匹配,非高斯、非线性、多维)。 粒子滤波的暴力解法:管它什么函数,撒 N 个随机样本点(粒子),用这堆点的密度来近似分布: 12345678910flowchart LR subgraph hard["无法计算的真实分布"] A["🗺️ 复杂概率云<br/>(真值附近高, 其他地方低)"] end subgrap...
4. RBPF:把鸡蛋问题拆开
4. RBPF:把”鸡生蛋”拆成两个小问题 上一篇:3. 粒子滤波:一千个分身猜位置 | 下一篇:5. gmapping的两大改进 RBPF = Rao-Blackwellized Particle Filter(Rao-Blackwellized 粒子滤波),gmapping 的全称招牌。听起来唬人,其实思想在第 0 篇的链式法则里已经全部讲完了。这一篇把那行公式逐符号拆开,并补上”每个粒子怎么建图”。 一、那个 SLAM 公式,逐符号拆解gmapping 求解的目标(论文里的第一个公式): $$P(x_{1:t}, m \mid z_{1:t}, u_{1:t-1})$$ 先看懂每个符号(都是纸老虎): 符号 人话 $x_{1:t}$ 轨迹:从第 1 时刻到现在的全部位姿 $x_1, x_2, …, x_t$(每个位姿 = $x, y, \theta$) $m$ 地图(栅格地图) $z_{1:t}$ 观测:到目前所有激光帧(z = 激光 LaserScan) $u_{1:t-1}$ 控制量/运动:里程...
5. gmapping的两大改进
5. gmapping 的两大改进:粒子生得更准,死得更慢 上一篇:4. RBPF:把鸡蛋问题拆开 | 下一篇:6. 源码走读:一帧激光的旅程 朴素 RBPF 有两个坑(第 4 篇结尾):粒子撒不准 + 重采样太勤。gmapping 论文就干了两件事:改进提议分布(撒得准)+ 自适应重采样(死得慢)。这两件事让它用 30 个粒子干出了别人 3000 个粒子的活。 一、改进一:提议分布–先对齐,再撒粒子1.1 先搞懂”提议分布”是什么(60 秒版)回顾 3. 粒子滤波:一千个分身猜位置:预测阶段粒子从某个分布里采样,这个”从哪撒”的分布就叫提议分布(proposal distribution),记作 $q(x)$。 理想情况:直接从目标分布(真实的后验,”我到底在哪”的真实概率云)撒粒子–但算不出来(第 3 篇说过)。 所以只能找个好算又尽量接近目标的替身 $q(x)$,再靠权重修正两者差距: 权重的本质 = 目标分布与提议分布的不一致程度。提议分布越接近目标,需要的权重修正越小,粒子利用效率越高。 1.2 朴素做法烂在哪朴素 RBPF 的提议分布只信里程...
6. 源码走读:一帧激光的旅程
6. 源码走读:一帧激光数据的完整旅程 上一篇:5. gmapping的两大改进 | 下一篇:7. 实战:跑起来与调参 原理懂了,现在把 第 5 篇 的算法图对号入座到真实代码。gmapping 分两层:外层 ROS 壳(slam_gmapping 包)+ 内核算法库(openslam_gmapping)。 一、代码地图1234567891011121314slam_gmapping/ ← ROS 壳: 订阅话题、格式转换、发 TF 和地图├── main.cpp 入口├── slam_gmapping.cpp startLiveSlam / laserCallback / addScan / updateMap└── ...openslam_gmapping/ ← 算法内核: RBPF 全在这├── gridfastslam/│ ├── gridslamprocessor.{h,cpp} ⭐ GridSlamProcessor::processScan() 核心循环│ ...
7. 实战:跑起来与调参
7. 实战:把 gmapping 跑起来,参数怎么调 上一篇:6. 源码走读:一帧激光的旅程 | 下一篇:8. 从理解到实现:迷你gmapping 一、快速上手(ROS1)123456789101112# 终端1: 仿真环境(乌龟或自定义 world)roslaunch turtlebot3_gazebo turtlebot3_world.launch# 终端2: gmapping 建图roslaunch turtlebot3_slam turtlebot3_slam.launch slam_methods:=gmapping# 终端3: 键盘遥控走一圈roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch# RViz 中添加 Map/TF 显示, 看着地图慢慢长出来# 保存地图rosrun map_server map_saver -f my_map gmapping 的接口(对着 第 2 篇 的输入输出看): 方向 内容 订阅 /scan(sensor_msgs/LaserScan)、/...
8. 从理解到实现:迷你gmapping
8. 从理解到实现:亲手写一个迷你 gmapping 上一篇:7. 实战:跑起来与调参 | 下一篇:9. 局限与进阶:gmapping之外的世界 目标:用 Python 把 第 5 篇 的算法图变成 ~200 行可跑代码。不追求工程性能,追求每一行都能对应到原理。 一、总体框架12345678910111213141516import numpy as npclass MiniGmapping: def __init__(self, n_particles=30, res=0.05, map_size=400): self.particles = [Particle() for _ in range(n_particles)] self.res = res # 栅格分辨率 5cm self.grid = np.zeros((map_size, map_size)) # log-odds 占用栅格 def step(self, odom_delta, scan): ...
9. 局限与进阶:gmapping之外的世界
9. gmapping 的局限与激光 SLAM 进阶路线 上一篇:8. 从理解到实现:迷你gmapping | 系列完结篇 一、gmapping 的三条硬伤(原理决定的,非调参能救)1.1 内存:每个粒子都是”地图收藏家”算一笔账(沿用 原理分析 的例子): 1234场地 200m × 200m,分辨率 5cm,每格 1 字节 -> 一张地图 = (200/0.05)² = 4000² = 1600 万格 ≈ 16 MB -> 100 个粒子 = 1.6 GB -> 500m × 500m + 200 粒子 ≈ 直接爆内存 💥 即便有第 6 篇的地图树共享,地图差异随轨迹分歧增大,共享收益逐渐消失。 1.2 无回环检测:走过回头路就”翻旧账”12345flowchart LR A["走过一大圈<br/>回到起点"] --> B["里程计漂移累积<br/>(误差可能好几米)"] B --> C["gmapping: 没有回环检测<br/>...