☰
gmapping 2D激光SLAM实战:粒子滤波原理与Gazebo仿真调参指南
2026/10/3 5:46:03 网站建设 项目流程

1. 写在前面:为什么这次要拿 gmapping 开刀

做 ROS 机器人开发,绕过不去的坎就是 SLAM。前几篇实践我分别在 ROS 通信、URDF 建模和 tf 变换上花了些功夫,这篇终于到了真正有意思的环节——让机器人自己把房间结构画出来。gmapping 作为经典的 2D 激光 SLAM 算法,别看它年纪不小,实际工程里仍然有大量存量项目在用,尤其是在低成本差速底盘 + 单线激光雷达这种配置下,gmapping 的稳定性和易用性依然能打。

这次实践我选择在 Gazebo 仿真环境里跑,原因很直接:真机调试一次的成本太高,电池、场地、雷达标定、轮子打滑全是变量,而仿真环境可以让我把注意力精力全部放到算法本身和参数调优上。等仿真环境里的建图效果满意了,再迁移到真机,这中间节省的时间不是一点半点。

这篇文章的核心内容围绕三件事展开:gmapping 的原理和参数到底应该怎么理解、在 Gazebo 里如何搭建一个完整的建图流程、以及实际跑图时那些教科书上不会告诉你的坑。适合刚学完 ROS 基础、准备接触 SLAM 的朋友,也适合那些已经在真机上跑过但仍想搞清楚参数背后逻辑的开发者。

2. gmapping 的原理与方案选型

2.1 先说清楚 gmapping 在 SLAM 家族里是什么定位

SLAM 的全称是 Simultaneous Localization and Mapping,翻译过来就是同时定位与建图。gmapping 是 ROS 里最老的 2D SLAM 算法包之一,基于粒子滤波(Particle Filter)框架,核心思想是:用一堆带有权重的粒子来表示机器人位姿的后验概率分布,每个粒子都维护一张自己的地图,随着机器人移动,粒子不断传播、评估、重采样,最终收敛到真实轨迹附近。

听起来挺玄乎,打个比方你就懂了。想象你蒙着眼睛在一间屋子里走,手里抓着一把豆子,走一步撒一把,豆子落在哪里就代表你可能在哪里。走得越多,墙壁反馈给你的感觉(激光数据)就能帮你排除掉那些不合理的位置猜测,最后大多数豆子会聚到你真正所在的位置附近,这些豆子集体的“记忆”就是建出来的地图。

搞懂这个定位逻辑是调参的前提。很多人拿到 gmapping 就直接跑,地图糊了也不知道从哪个参数下手,就是因为不理解粒子滤波的工作机制,后面我会把参数和这个机制一一对应起来。

2.2 gmapping 和 Cartographer、hector_slam 怎么选

经常有新手在选 SLAM 算法时犯难,我在这里给一个比较朴素的经验判断。gmapping 最典型的诉求是:底盘有里程计、雷达是 2D 单线、场景是室内结构化环境。它的优势是包成熟、文档多、参数相对好调,对低配主机也很友好;劣势是退化场景(长走廊、空旷大厅)容易飘,且依赖于较准确的里程计输入。

hector_slam 不依赖里程计,主要靠激光匹配来估计位姿,适合无人机这类没有良好轮式里程计的载体,但对雷达频率和计算资源要求更高。Cartographer 是 Google 出的,图优化框架,理论精度上限最高,也能处理多传感器融合,但部署复杂度、学习成本都明显高一截,建图时的计算负载也大,跑在树莓派这类板子上会有点吃力。

我的建议是:第一次接触 SLAM,用 gmapping 打底是最务实的路线。把粒子滤波、扫描匹配这些概念吃透之后,再迁移到 Cartographer 会顺畅很多。

2.3 为什么先在 Gazebo 里跑通很重要

Gazebo 提供了接近真实的物理仿真,包括激光雷达的光线模拟、轮子的摩擦力和打滑特性、传感器的噪声模型。我这次用的机器人模型集成的是 360° 单线激光雷达,模拟频率设置成 10Hz,和市面上常见雷达一致。

仿真环境下最珍贵的是什么?是可重复性。真机建图时地图歪了,你不确定是雷达安装偏了、里程计标定不准,还是环境里有玻璃反射。但仿真环境里传感器误差是可控的,你若发现地图偏移,几乎可以确定是算法参数或者 TF 树配置的问题,可以快速定位到根因。

此外,仿真环境下我可以随时“瞬移”到场景任意位置观察机器人状态,可以在不停止建图的情况下直接把物体挪进场景测试地图的动态更新能力,这些事情在真机环境里做起来成本非常高。所以如果你是刚开始接触 SLAM,强烈建议先在 Gazebo 里把整个流程吃透再考虑真机。

3. 环境准备与工具链

3.1 ROS 环境安装的省心方案

这一节我默认你用的是 Ubuntu 20.04 + ROS Noetic 或者 Ubuntu 22.04 + ROS 2 Humble。如果你想快速把环境跑起来,推荐使用鱼香 ROS 一键安装脚本,这是我实测下来对新手最友好的安装方式,它会帮你把 ROS 本体、依赖包和一些常用工具一次性装好,省去手动处理源和依赖冲突的麻烦。

安装完成后务必验证一下环境是否正常,打开终端输入:

source /opt/ros/noetic/setup.bash roscore

能正常启动 roscore 说明 ROS 主环境没问题。如果你的发行版是 Humble,对应的命令是source /opt/ros/humble/setup.bash,注意不要混用不同发行版的环境变量。

3.2 安装 Gazebo 与仿真依赖包

Gazebo 通常随 ROS 一起安装,但我们需要额外安装一些机器人仿真相关的功能包。这里以 Noetic 为例,执行以下命令完成基础依赖的安装:

sudo apt install ros-noetic-gazebo-ros ros-noetic-gazebo-plugins sudo apt install ros-noetic-gmapping ros-noetic-map-server sudo apt install ros-noetic-teleop-twist-keyboard

其中gazebo-plugins里包含了激光雷达的传感器插件,是仿真建图必需的一环;gmapping是我们本篇的主角;map_server用来保存和加载建好的地图;teleop-twist-keyboard用于通过键盘给机器人发运动指令。

有一条经验很多人不提醒,我先说了:安装完成之后,强烈建议逐个rospack find检查一下这些包是否能被找到,比如rospack find gmapping,如果返回路径则正常,如果提示找不到,说明安装源的索引没有更新,需要重新source一下或者执行sudo apt update后再装一次,否则后面启动 launch 文件时会看到奇怪的报错。

3.3 准备机器人模型与仿真场景

这次我用的是一台两轮差速的移动机器人,URDF 模型里包含底盘、两个驱动轮、一个万向支撑轮,以及安装在底盘顶部的激光雷达。为了方便复现,你可以直接使用 ROS 官方仓库里的 TurtleBot3 模型,也可以参考我之前几篇文章自己搭建 URDF。

TurtleBot3 的启动方式很标准:

export TURTLEBOT3_MODEL=burger roslaunch turtlebot3_gazebo turtlebot3_world.launch

这会启动一个带墙体和障碍物的标准仿真房间,非常适合用来验证建图效果。我自己搭的机器人模型也是照着这个思路来的,激光雷达安装高度 0.2m,扫描范围 360°,分辨率 0.5° 每步,最大测距 12m。这里有一个细节需要注意:雷达的安装位姿必须正确配置,z轴朝上,否则建出来的图是颠倒的。

4. gmapping 核心参数逐一拆解

4.1 参数这么多,哪些动不得

gmapping 的 ROS 封装提供了大量可调参数,但对我们实际建图真正关键的其实是下面这几个,其他的保持默认即可。

maxUrange和maxRange这两个参数决定了激光数据参与建图的有效范围。maxRange表示雷达的最大探测距离,一般设置成略小于雷达标称量程;maxUrange表示建图时要使用的有效测距,通常设置为maxRange的 80% 左右。比如你的雷达量程是 12m,maxRange设 11.5m,maxUrange设 10m 左右比较合理。

particles是粒子数,直接影响建图精度和 CPU 负载的平衡。默认是 30,如果你的主机性能不错,可以尝试提高到 60,地图的细节和抗噪声能力会有可感知的提升。但如果你的场景很大,粒子数翻倍带来的计算量增长也相当明显,实际跑下来 CPU 占用可能是默认值的两倍以上,这一点要有心理预期。

4.2 影响位姿估计的关键参数

linearUpdate和angularUpdate分别表示机器人平移多少米或旋转多少度才触发一次新的扫描匹配。默认值分别是 1.0 米和 0.5 弧度。这个参数设小了,算法频繁重算,CPU 压力大增;设大了,两次匹配之间机器人走得太远,容易丢特征导致匹配失败。

实际操作中,室内小场景建议把linearUpdate设成 0.3~0.5 米,angularUpdate设成 0.2~0.4 弧度。这样既不会让 CPU 爆掉,又能保证地图细节的实时更新。

minimumScore是扫描匹配的最低得分阈值。这是一个非常关键的参数,但很多人不知道怎么设。默认值是 0.0,意味着不管匹配质量多差,算法都认为它勉强可用。在复杂环境里,这会导致地图出现重影或者错位。结合经验,室内环境设成 30~80 是比较安全的范围。我第一次调参时设成了 100,结果在特征较少的走廊里频繁出现匹配失败警告,后来降到 50 就好了。

4.3 地图更新的节奏怎么控制

map_update_interval表示地图更新的时间间隔,单位是秒,默认 5.0。这意味着地图不是实时刷新的,而是每隔一段时间才把最新统计的粒子地图发布出去。如果你在 Rviz 里看到地图迟迟不动,先别急着怀疑程序卡死,很可能就是这个参数在起作用。

想看到更流畅的建图过程,可以把map_update_interval调成 1.0 甚至 0.5,代价是map_server和 Rviz 之间的地图传输频率变高,对网络和渲染有一点额外负担。我在真机调试时习惯保持默认值,因为 5 秒刷新一次对最终地图质量没有任何影响,我只在需要观察算法中间状态时才调低它。

还有一个容易被忽略的参数是srr、srt、snm、sml这一组,它们分别表示里程计平移误差、旋转误差、以及传感器测量误差。官方文档给的建议值分别是 0.01、0.02、0.01、0.02,实际使用中如果你的底盘里程计质量一般(比如便宜的直流电机 + 霍尔编码器),建议把srr和srt调大到 0.05 左右,让算法对里程计的信任程度降低,多依赖激光匹配来修正位姿,这样能在一定程度上降低成本底盘的建图漂移。

4.4 手把手带你理解 launch 文件写法

参数不能光看,得写进 launch 文件里才有意义。下面是我这次实践用的gmapping.launch文件,直接可用的版本:

<launch> <arg name="scan_topic" default="scan" /> <arg name="base_frame" default="base_footprint" /> <node pkg="gmapping" type="slam_gmapping" name="slam_gmapping" output="screen"> <param name="base_frame" value="$(arg base_frame)"/> <param name="odom_frame" value="odom"/> <param name="map_frame" value="map"/> <param name="maxUrange" value="10.0"/> <param name="maxRange" value="11.5"/> <param name="particles" value="30"/> <param name="minimumScore" value="50"/> <param name="linearUpdate" value="0.3"/> <param name="angularUpdate" value="0.2"/> <param name="map_update_interval" value="1.0"/> <param name="srr" value="0.05"/> <param name="srt" value="0.05"/> <param name="snm" value="0.01"/> <param name="sml" value="0.02"/> <remap from="scan" to="$(arg scan_topic)"/> </node> </launch>

这里最关键的是base_frame、odom_frame、map_frame这三个 frame 的设定,它们必须和你的 URDF 里的 TF 树完全匹配。一个小技巧:正常启动后执行rosrun tf view_frames可以生成 TF 树图,检查 frame 之间是否连续、方向是否正确。如果 TF 树断裂,gmapping 会直接报错无法工作。

5. 实操流程:从启动到出图完整跑一遍

5.1 启动 Gazebo 仿真环境

在终端一执行:

export TURTLEBOT3_MODEL=burger roslaunch turtlebot3_gazebo turtlebot3_world.launch

如果一切正常,你会看到 Gazebo 窗口弹出,一辆小小的 TurtleBot 出现在一个带墙和柱子的房间里。这个房间面积不大,但墙体和柱子的特征足够丰富,非常适合验证建图算法的基本功能。

注意观察终端输出,如果出现[WARN]级别但又马上恢复的消息(比如 waitForService 相关),通常是正常的启动时序问题,不用理会。但如果出现持续刷屏的[ERROR],特别是关于 model spawn 失败的,就要优先排查 URDF 模型文件和 Gazebo 模型的路径问题。

5.2 启动键盘控制节点

开一个新终端,执行:

roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch

这个节点会监听键盘按键,把按键转换为cmd_vel话题上的速度指令。控制方式看终端里的提示就行:w前进、s后退、a左转、d右转,x停止。我个人的建议是刚开始速度放慢一点,后续如果觉得走得太慢,可以调大cmd_vel话题上的线速度和角速度消息值。

这里有个小细节:在真机上跑时,底盘驱动节点通常已经将速度指令转换成了电机 PWM,但在 Gazebo 里,需要 Gazebo 的差速驱动插件来订阅cmd_vel并作用到仿真模型上,这一点在搭 URDF 时就要配置好,否则键盘控制完全无效。

5.3 启动 gmapping 并连接激光数据

再开一个新终端,首先确认scan话题上有数据:

rostopic echo /scan | head -50

如果看到一列数字输出,说明激光数据已经发布。接着启动我们刚才写好的 gmapping launch 文件:

roslaunch gmapping gmapping.launch

启动成功后,在 Rviz 里添加Map显示,Topic 选择/map,你应该能立刻看到地图开始从零生成。这时用键盘控制机器人慢慢移动,观察地图的变化。

第一次跑的时候请务必慢,这是我反复强调的点。开始时可以先原地旋转一圈,让雷达先扫到周围环境,后面移动的时候地图的初始参考就准确了。如果一开始就往一个方向冲,激光还没建立足够的环境特征,容易出现地图漂移。

5.4 保存地图

建图结束之后,把地图保存下来是必须的步骤。新开终端执行:

mkdir -p ~/map_data rosrun map_server map_saver -f ~/map_data/test_map

执行后会在~/map_data下生成两个文件:test_map.pgm是图像格式的地图,test_map.yaml是地图描述文件,包含分辨率、原点、占据阈值等信息。这两个文件之后可以用于map_server加载,配合move_base做导航使用。

有一个细节:map_saver保存的是当前 Rviz 里显示的地图,如果 gmapping 节点因为某些原因崩溃了,地图数据就取不到了。所以保存前建议确认 gmapping 节点状态正常,rqt_graph里能看到完整的节点连接图。

5.5 复现实验:地图刷新与动态障碍物

仿真环境还有一个特别好用的点是可以在建图过程中往场景里放物体。比如用 Gazebo 的模型插入工具,在建图过程中往房间里放一个箱子,随后地图更新时,箱子所在位置会从“未知”变成“占据”,移动箱子后,地图上的对应区域又会变回“空”。

这个实验在真机上做会非常麻烦,但在仿真里只需要点几下鼠标。它能够很好地展示 gmapping 对地图的持续更新和校正能力,像我这样的视觉党,第一次看到地图上物体出现又消失的时候,对 SLAM 的理解确实深了一层。

6. 调参实战与效果对比

6.1 把粒子数翻倍,地图发生了什么变化

为了让大家有直观感受,我做了一组对照实验。第一轮用默认的 30 个粒子跑完整个房间,第二轮把particles改成 60 重新跑。第二轮地图墙体边缘更平滑,柱子轮廓也更接近真实尺寸,但代价是另外开了一个终端看到 CPU 占用从 120% 左右升到了 220% 左右(在四核八线程的 i7 上)。

这个对比说明一个道理:粒子数不是越大越好,它必须在你的硬件能承受的范围内。如果你的 CPU 核算力有限,维持 30 个粒子、通过手动控制机器人慢速移动也能得到不错的结果,而不是盲目加粒子数导致系统卡顿反而建图失败。

6.2 调高 minimumScore 防止匹配跑飞

我在一个转角较多的场景里试过,默认minimumScore=0.0时,机器人转弯后地图偶尔会出现一块墙体错位,这是因为快速旋转导致前后两帧激光数据重叠区域太小,匹配质量下降,但算法仍然没有拒绝这次匹配。

把minimumScore调到 50 之后,同样的转弯过程会看到终端输出类似Scan matching failed的警告,同时 gmapping 短时间内不更新位姿,等机器人转过弯重新获得足够特征后再恢复跟踪。表面上看好像是算法“卡了一下”,实际上这降低了地图错误累积的风险。这里要提醒一下:minimumScore设得过高会导致建图频繁中断,每次中断都会让地图生成卡顿,所以建议从 30 开始试验,逐步调大。

6.3 里程计可信度参数调整的体会

我在第一次建图时用的底盘模拟得非常理想,里程计几乎没有漂移。这种情况下srr、srt都保持默认值 0.01 就能得到很好的结果。

但如果你实际使用的底盘轮径小、编码器精度低,或者地面是瓷砖加地毯这种容易打滑的组合,就建议像我前面说的那样把srr和srt调大。我的一个朋友在真机上调试时,把这两个参数调到 0.1,建图质量反而比默认值更稳定,因为算法不再轻信里程计累计出来的位姿,而是更多依赖当前激光数据来做匹配校正。这算是 gmapping 参数调优里一个很典型的“反直觉”例子。

7. 常见问题与排查技巧实录

7.1 地图始终空白或者一动不动

如果你在 Rviz 里看到了 Map 显示,但地图全黑(未知区域),或者地图只在很小范围内变化,优先检查三个方面:第一,scan话题是否有数据,用rostopic hz /scan看发布频率,正常应该在 8~15Hz;第二,TF 树是否完整,rosrun tf view_frames生成 TF 树图,确认map -> odom -> base_footprint -> laser这条链路没有断裂;第三,cmd_vel速度指令是否真的让机器人移动了,在 Gazebo 里看机器人位置有没有变化。

这三步走完能解决大部分“没反应”的问题。需要特别注意的是,map到odom之间的 TF 是由 gmapping 节点自己发布的,不需要我们额外静态发布,否则会与 gmapping 的发布产生冲突,这是刚接触时容易搞混的地方。

7.2 地图有重影或墙体错位

重影一般是粒子滤波在某个区域无法收敛的表现。常见原因有两个:一是机器人移动太快,相邻两帧激光数据的重叠度太低,匹配退化;二是环境中特征太少,比如走到一面空白的墙前,雷达扫到连续大段等距离点,算法难以判断当前的真实位置。

解决办法:机器人建图时走“S”型路线,避免在无特征区域长时间直行;控制最大速度不要超过 0.5m/s;如果场景实在太空,考虑增加一些临时标记物(比如纸箱、三角锥桶)来提供特征。仿真环境下没有临时物料,就搬几个 Gazebo 内置的模型进去。

7.3 CPU 占用过高导致建图卡顿

如果你的电脑跑 gmapping 时风扇狂转,先看rostop(安装ros-noetic-rostop)里哪个节点消耗的 CPU 最高。如果是slam_gmapping本身高,通常是粒子数设置过大或者linearUpdate太小导致的重算频率过高。把粒子数从 60 降到 30,同时把linearUpdate调回到 1.0,CPU 会明显下降。

如果是 Rviz 渲染导致的高占用,那就把 Map 显示的分辨率降低一点,或者暂时关闭不用的显示组件。这个坑我踩过,有一次以为算法卡死了,结果发现是调试时开了 6 个显示面板,Rviz 自己把 CPU 吃满了。

7.4 保存地图后 PGM 文件损坏或全黑

保存地图后如果打开 PGM 发现全黑,可能是保存时 gmapping 的地图还没真正生成。检查一下 Rviz 里是否能看到完整地图,如果看不到,用rostopic echo /map查看消息里的data字段是否为全零。另外,map_saver会直接读取当前时刻的/map话题数据,如果 gmapping 因为 TF 丢失已经停止更新的地图,保存下来的就是旧的或者空的内容。

解决思路:先终止建图过程,确认map话题持续发布有效数据(可以用rostopic hz /map检查),再执行保存命令。保存的yaml文件里resolution字段如果小于 0.01,说明地图范围非常大但分辨率被压缩了,需要检查建图过程中是否发生了长时间漂移。

7.5 建图过程中机器人撞墙了

仿真环境下撞墙不会造成物理损坏,但会极大地影响建图质量,因为机器人被卡住后,轮子还在转,里程计在累积位移,但激光数据几乎没变化,粒子滤波的预测模型和观测模型严重不一致,可能导致粒子分布发散。

防止建图撞墙的经验是:在 Rviz 里打开LaserScan显示,根据激光点的距离判断前方障碍物距离,降低行使速度,多预留一点安全距离。也可以给机器人增加碰撞检测节点,但那是另一个话题了。

8. 建图完成之后还能做什么

地图保存下来只是第一步。之后你可以用map_server加载地图,配合amcl(自适应蒙特卡洛定位)实现机器人在已知地图中的定位,再结合move_base做路径规划与自主导航。这一整套流程是 ROS 机器人最经典的“感知-规划-控制”链路。

如果你对建图质量不满意,除了调整 gmapping 参数,还可以考虑在雷达数据上游做处理,比如用laser_filters功能包过滤掉一些不稳定的杂点,或者使用pointcloud_to_laserscan把三维点云投影成二维激光数据,从而在低成本雷达上获得更丰富的环境信息。

在我个人的实际经验中,gmapping 到现在依然是最适合入门 SLAM 的算法,因为它的原理足够清晰,参数和算法机制之间有着明确的对应关系,调参的过程就是深入理解 SLAM 的过程。等你在 gmapping 上积累足够经验,再切换到 Cartographer 或者进行视觉 SLAM 研究时,很多概念是相通的。这篇实践笔记基于我自己的调试过程整理,参数推荐值也来自实际跑图的结果,希望能帮你少踩几个坑。如果你在复现过程中遇到什么问题,建议先按第 7 节的排查表逐项检查,大部分问题都逃不过那几个方向。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询