
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_MODELburger 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% 左右。比如你的雷达量程是 12mmaxRange设 11.5mmaxUrange设 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 namescan_topic defaultscan / arg namebase_frame defaultbase_footprint / node pkggmapping typeslam_gmapping nameslam_gmapping outputscreen param namebase_frame value$(arg base_frame)/ param nameodom_frame valueodom/ param namemap_frame valuemap/ param namemaxUrange value10.0/ param namemaxRange value11.5/ param nameparticles value30/ param nameminimumScore value50/ param namelinearUpdate value0.3/ param nameangularUpdate value0.2/ param namemap_update_interval value1.0/ param namesrr value0.05/ param namesrt value0.05/ param namesnm value0.01/ param namesml value0.02/ remap fromscan 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_MODELburger 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 防止匹配跑飞我在一个转角较多的场景里试过默认minimumScore0.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.0CPU 会明显下降。如果是 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 节的排查表逐项检查大部分问题都逃不过那几个方向。