
1. 写在前面的整体思路为什么这一篇要聊gmapping这篇是ROS实践系列的第四篇。前面几篇咱们一般是从环境搭建、节点通信、tf变换这些基础概念一路走过来的到了这一篇终于进入了一个让很多人一开始听着很头大、跑起来却很有成就感的话题机器人SLAM建图。这里我默认你手里已经有一套能跑起来的ROS环境最好还接触过turtlebot这类仿真平台因为gmapping再怎么说也不是一个纯理论的东西它需要真实的激光数据、里程计数据、tf树关系这些东西如果全靠自己去搭新手很容易被劝退。gmapping这个包是基于粒子滤波的2D激光SLAM算法实现属于ROS里非常成熟的建图方案。它做的事情通俗地讲就是让机器人在一个未知环境里走一圈利用激光雷达扫描到的障碍物信息结合底盘里程计推算出来的运动轨迹最终拼出一张二维栅格地图。这张地图可以直接用于后续的导航、路径规划、避障是机器人自主移动的一个重要前提。这篇内容适合谁看如果你是刚学完ROS基础、正准备让机器人“睁开眼睛看世界”的初学者这篇可以当作第一份完整的gmapping实操笔记如果你已经在跑gmapping但地图总是歪、有重影、或者保存出来有问题这篇里关于参数调优和问题排查的部分会比你重新翻一堆官方文档更直接。我会从原理取舍讲到具体命令再讲到参数调优和踩坑记录尽量把整个流程讲透。2. 先摸清gmapping的底细为什么选它不选hector或cartographer2.1 gmapping到底解决什么问题先给完全没接触过的朋友补一个背景。SLAM全称是Simultaneous Localization and Mapping也就是同步定位与建图。这件事最麻烦的地方在于机器人要建地图就得知道自己在哪里可机器人要定位又得先有一张地图。两边互相依赖像一个先有鸡还是先有蛋的问题。gmapping就是用来打破这个循环的一种方法它把激光扫描数据和里程计数据放到概率框架里一边估计机器人的位姿一边更新地图。机器人位姿指的是它在平面上的坐标x、y和朝向角theta三个量加起来就是一个位姿。在未知环境里这个位姿是带着不确定性的激光传感器、轮子码盘都有噪声gmapping要做的就是用大量的粒子去模拟可能的位置分布。每个粒子代表机器人轨迹的一种假设也持有一张自己的地图最后根据观测数据的好坏筛选出更靠谱的粒子。这个过程就是粒子滤波专业一点叫Rao-Blackwellized Particle FilterRBPF这也是gmapping的核心理论基础。对比起来hector_slam是纯激光匹配方案强依赖高频率激光雷达对里程计要求很低但很容易在特征不明显的环境里飘cartographer是谷歌的方案精度高、支持回环检测但依赖重、上手难度大资源占用也偏高。gmapping在普通差速底盘、单线激光雷达的家用/教学级机器人上是综合成本最低、参数最好调的方案。2.2 粒子滤波的思想为什么gmapping能搞定鸡生蛋问题这里我需要多说一点粒子滤波因为很多人越用越糊涂。你想象机器人一开始在地图上的位置是未知的gmapping就撒一把粒子比如30个每个粒子相当于一份“猜测”猜机器人可能在哪个位置。机器人往前走了几步激光扫了一帧每个粒子就会根据“如果我真的在这个位置那我应该看到什么障碍物”这一逻辑打个分。和实际激光数据越像的粒子得分越高下一轮迭代时就越可能保留下来得分低的逐渐淘汰。这个过程中里程计模型负责预测运动趋势激光匹配负责修正位置错误。默认情况gmapping直接用里程计和激光做扫描匹配来获取提议分布再生成粒子这样粒子会集中在概率更高的区域少数粒子也能有不错的精度。这也是gmapping相比早期RBPF算法的关键改进之一。gmapping的另一个改进是自适应重采样。粒子会随着时间退化也就是少量粒子权重越来越高多样性下降重采样能解决退化问题但频繁重采样也会让粒子过于单一导致定位失败。gmapping会根据粒子权重的分散程度决定是否重采样也就是持续性变差时才重采样其他时候保持粒子的多样性。2.3 gmapping的短板你得提前知道选了gmapping不等于万事大吉。它的短板非常明显没有回环检测也就是说机器人回到已经走过的位置时它不会主动去修正之前漂移的地图。拿一个小房间做测试还好一旦环境大一点走了几分钟里程计误差累积起来地图就容易出现错位或者重影这也就是很多人常说的“地图拼接不上”。另外gmapping对里程计的质量非常敏感码盘标定不准、轮子打滑、底盘差速参数不对建出来的图大概率是歪的。所以这篇里我会花挺多篇幅讲怎么先用好里程计再去调gmapping参数。顺序反了你会觉得调什么都像没调。3. 准备阶段环境、依赖、实验平台这些东西一个都不能少3.1 ROS版本选择和安装建议开始实操之前先确认一下ROS环境。我这边用的是Ubuntu 20.04配ROS Noetic这也是目前ROS 1里最稳妥的组合。如果你用的是Ubuntu 22.04ROS 1只有兼容包能用所以更建议直接上ROS 2 Humble可gmapping本身是ROS 1时代的包ROS 2里虽然有slam_toolbox这类更现代的工具但很多人初学就冲着gmapping来的那还是老老实实装Noetic最省心。如果你还没装ROS我个人推荐用一个叫“鱼香ROS一键安装”的社区脚本工具。很多新手在装ROS的时候死在ROS keys更新、软件源配置、apt依赖缺失这些环节用这个脚本基本是一条命令搞定。wget http://fishros.com/install -O fishros . fishros脚本运行之后按提示选择ROS版本它会帮你配置源、装基础组件、设置环境变量比起自己一步步折腾省很多时间。不过我也建议你至少弄清楚它替你干了什么添加ROS软件源、安装ros-noetic-desktop-full、初始化rosdep、配置环境变量。这些东西后面出了问题你能更快定位。装完验证一下source /opt/ros/noetic/setup.bash roscore能看到node started就说明基础环境没问题。3.2 我是用仿真平台还是真实机器人做实验如果手头没有真实机器人完全可以用Gazebo仿真来跑gmapping这对学习来讲完全足够因为你关心的数据流、launch文件、参数配置、tf关系仿真和真机是一样的。唯一要注意的是仿真的里程计数据非常理想几乎没有打滑和标定误差所以在仿真里调很好的参数搬到真机上不一定能直接复现需要重新微调。我建议初学者先在Gazebo里把整套流程跑通再去碰真实机器人。仿真里你能反复测试各种环境跑崩了也不心疼而且能非常直观地看到gmapping不同参数对地图的影响。如果直接用真机一开始可能连雷达数据都没回声排查问题的时间会多好几倍。仿真平台我常用turtlebot3_gazebo。这个包的好处是雷达、底盘、里程计、URDF模型全部配好了launch起来就能跑。sudo apt install ros-noetic-turtlebot3-gazebo ros-noetic-turtlebot3-teleop ros-noetic-map-server echo export TURTLEBOT3_MODELburger ~/.bashrc source ~/.bashrc roslaunch turtlebot3_gazebo turtlebot3_world.launch这里我把TURTLEBOT3_MODEL设成burger这是turtlebot3的最小号型号雷达只有一个2D激光计算量低训练完全够用。如果要用真实机器人你至少需要确保三样东西有ROS发布接口激光雷达数据话题一般叫/scan消息类型sensor_msgs/LaserScan、里程计数据话题一般叫/odom消息类型nav_msgs/Odometry、机器人本体坐标转换base_footprint到laser的tf关系。这三样缺了gmapping根本跑不起来。3.3 理解gmapping需要哪些数据才能快速定位报错gmapping订阅两个输入话题一个是scan激光数据一个是tf坐标变换。tf里必须包含map到odom、odom到base_footprint、base_footprint到laser的变换关系。很多朋友一启动gmapping就报“Could not get transform from base_link to laser”本质就是少了一段tf。我用一个生活化的例子解释tf地图是整个世界地图机器人坐标是“我现在在哪个十字路口”雷达坐标是“我的眼睛长在头顶偏左三厘米的位置”。没有眼睛和身体的相对位置信息大脑就算收到眼睛看到的画面也不知道画面应该摆在自己前后左右哪个方向。激光数据是一组距离值它只告诉你“这个方向有障碍物”但如果不知道“这个方向对应机器人身体的哪个方向”数据就没法画到地图上。turtlebot3仿真里tf由robot_state_publisher和Gazebo插件自动发布真机就得自己写节点发布或者用robot_state_publisher加载URDF模型发布。检查tf的命令是rosrun tf view_frames会生成一个frames.pdf里面能直观看到所有坐标帧的树状关系。这是排查gmapping启动报错时最高效的手段。4. 实操全流程launch文件编写、手动控制、保存地图4.1 手写一份gmapping launch文件把参数说到位仿真环境启动起来之后正式建图前要写一个gmapping launch文件。这一步我强烈建议不要直接复制别人的一大段配置而是自己一行行看懂再写否则后面调参数你会非常痛苦。下面是我在项目中身边同事用得最多的一份基础launch我在关键位置补了注释。launch !-- 启动 gmapping 节点 -- node pkggmapping typeslam_gmapping nameslam_gmapping outputscreen !-- 激光话题名 -- param namescan_topic value/scan/ !-- 粒子数默认30仿真环境可以调少室内20够用环境复杂再提高 -- param nameparticles value20/ !-- 地图更新间隔单位秒。越大越省CPU但地图刷新越慢 -- param namemap_update_interval value5.0/ !-- 当机器人位移超过linearUpdate米或角度变化超过angularUpdate度时触发一次扫描匹配 -- param namelinearUpdate value0.10/ param nameangularUpdate value0.10/ !-- 激光的最大可用距离。超过这个距离的测距点直接丢弃 -- param namemaxUrange value4.0/ param namemaxRange value5.0/ !-- 激光数据预处理每两帧才处理一帧能降低CPU负载 -- param namethrottle_scans value1/ !-- 地图分辨率单位米/像素。越小越精细但计算量越大 -- param namedelta value0.05/ /node /launch这份launch的核心思路是先确保gmapping能启动参数都偏向保守宁可地图精度一般也不能频繁崩。你先跑通再慢慢调优参数。第一次就把particles调到100运行一下地图还没扫完CPU先跑满了。保存为新文件放到你自己的ROS包里mkdir -p ~/catkin_ws/src/my_slam/launch cd ~/catkin_ws/src/my_slam catkin_create_pkg my_slam roscpp rospy gmapping cp ~/你的launch文件路径/gmapping.launch ~/catkin_ws/src/my_slam/launch/ cd ~/catkin_ws catkin_make source devel/setup.bash这样启动gmappingroslaunch my_slam gmapping.launch同时开一个RViz可视化窗口看建图过程rosrun rviz rviz -d $(rospack find gmapping)/gmapping.rviz在RViz里添加Map话题选择Map数据图像显示类型设为Map你就能看到地图一点点被画出来。4.2 如何控制机器人移动才能让地图质量有保证gmapping不会自己开车。你需要手动遥控机器人在环境里移动这一步看着简单实际上特别影响成图质量。我见过太多人上来就一通乱开地图花了再来问为什么其实多半是控制手法的问题。控制方式一般用键盘roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch按键和移动方向之间一般通过速度指令话题/cmd_vel通信。这里我强调几个控制要点第一速度要慢。尤其转弯的时候要给gmapping足够的激光匹配时间我一般会把最大线速度设成0.2m/s左右角速度0.5rad/s以下。太快了激光匹配跟不上地图边缘会出现扇形的“拖影”。第二尽量走闭环。让机器人从一个位置出发沿着环境绕一圈然后回到起点附近这样最终地图的闭合误差会小很多。虽然gmapping没有回环检测但“人为闭环”可以让激光匹配反复修正已经建好的区域。第三遇到走廊和空旷区域要“回扫”。激光在长直走廊里特征很少容易在走廊方向产生漂移走到头之后稍微停下来转个方向让雷达扫到两侧墙壁再继续走能明显减少走廊方向的累积误差。4.3 地图保存从map_server到真实可用的pgm文件建图完成后赶紧保存地图文件。这一步用map_server包里带的地图保存工具rosrun map_server map_saver -f ~/maps/my_map执行完会生成两个文件my_map.pgm和my_map.yaml。pgm是灰度图像素值从0到255越黑代表障碍物概率越高越白代表空闲区域yaml是元数据文件里面记录分辨率、原点坐标、占用阈值等参数。我踩过一个坑保存地图时RViz里看着觉得挺好打开pgm之后发现车周围一片黑。这是因为地图栅格值默认是0到255但程序里通常把0到100当障碍物概率100到255当空闲map_saver保存时会对占用概率做逆变换。如果打开图片觉得明暗反直觉先在yaml文件里确认occupied_thresh和free_thresh这两个值控制占用的判据。这里提醒大家保存地图之前一定要先确认当前显示的Map话题是gmapping发布的动态地图而不是其他话题下的一张旧图。我在team里就有人存了半天最后发现存的是别的节点发布的地图。5. 参数调优一个参数一个坑说说我的实际调整经验5.1 粒子数、扫描匹配频率、地图更新频率分别影响什么gmapping参数非常多但实际调的时候优先级最高的是这几个particles数量直接决定定位精度和CPU负载。粒子越多机器人位置假设越密集地图越不容易跟丢但计算量成倍上涨。我自己的经验是仿真里20到30粒足够真实环境室内40到50粒足够超过60粒的收益就很不明显了。除非你的机器人跑在回环特别多、特征特别乱的工厂环境否则不需要盲目加粒子。linearUpdate和angularUpdate触发扫描匹配的位移阈值。这个参数和你的移动速度强相关跑得快就要调小否则两次匹配之间机器人已经走了很远激光数据重叠不够匹配容易失败。我一般设0.1到0.2米新手如果觉得地图偏移先看看这两个值是不是设大了。throttle_scans激光数据降频处理设为2就是每两帧激光才进入一次匹配。雷达频率越高这个参数越有用但降太多会导致建图反应慢。我用10Hz雷达时保持默认1用40Hz雷达时设成3甚至4。map_update_interval地图可视化的更新频率。这个只影响RViz里地图的刷新快慢不影响最终建图精度。我喜欢调成5地图每5秒刷新一次能看清建图进程CPU也不吃力。5.2 激光雷达匹配相关的参数怎么调能减少地图重影gmapping里有很多带Urange和Range的参数maxUrange和maxRange最容易混淆。maxRange是激光雷达本身测距上限由传感器硬件决定maxUrange是gmapping做匹配时考虑的有效距离超过这个距离的激光点误差大、还可能打到动态物体上不值得信任干脆忽略。我建议maxUrange设成4到8米具体看你环境大小。如果环境小设大了也没关系但在空旷大环境里设小了gmapping会因为没有远处参照物而频繁定位失败。还有两个参数容易被忽略minimumScore和srr/srt/sst/stt这些噪声模型参数。minimumScore控制扫描匹配的最低得分得分太低就相信里程计。如果机器人一直报“Scan Matching failed”适当往下调minimumScore让gmapping别因为一帧激光匹配差就彻底摆烂。srr/srt这些是里程计噪声模型参数默认值0.1这个量级能适应大多数底盘但如果你发现地图沿着运动方向越来越模糊可以把这些值调小到0.02到0.05试试。5.3 用一张表总结我常用的参数组合表格里这份参数是我在室内20平米左右小房间、单线激光雷达、差速底盘上反复验证过的组合仿真和真机都能跑。我见过有人问“为什么地图边界一直闪”或者“地图不连续”很大概率是先搬了一套参数乱调没理解每个参数解决什么问题。参数名仿真默认小房间真机大房间真机备注particles204050环境越大粒子适当增加linearUpdate0.100.050.05真机速度慢阈值调小提升匹配频率angularUpdate0.100.050.05转弯时匹配更密集maxUrange4.05.08.0根据环境尺度调整map_update_interval5.02.03.0大环境建议2便于观察delta0.050.050.05基本不用动throttle_scans112雷达频率高时再降频这个表不是标准答案但至少能帮你建立参数调整的起点思维。调参之前先想清楚一个参数要解决什么问题再动它否则就是玄学调参。6. 常见问题与排查技巧建图翻车合集看看你踩过几个6.1 启动gmapping就报tf错误找不到坐标帧这应该是gmapping系列里最经典的问题了。报错大致是[ERROR] Could not compute odom pose, skipping scan. Transform error: Lookup would require extrapolation into the future这个问题本质是tf的时序问题。gmapping在收到一帧激光后需要拿到这一帧时刻对应的odom到base_footprint变换。如果tf发布太慢、或者时间戳不匹配就报这个错。排查手段首先检查有没有tf断链rosrun tf view_frames打开生成的frames.pdf看map、odom、base_footprint、laser这些帧之间是否完整。如果缺了base_footprint到laser多半是URDF模型没加载全或者robot_state_publisher没起来。另一种高频原因是时间戳同步问题。多传感器之间时钟不同步尤其是机器人上有多个独立工控机、或者用了不同的ROS master时间戳差异会导致tf查不到对应时刻。我建议先统一设备时间比如用chrony做时间同步仿真里如果出现这个问题大多数情况是Steam游戏手柄或者VNC操作导致系统调度卡顿可以先降低雷达频率再测试。6.2 节点起来了但地图一直是空的或者黑的这个情况我见过太多次。节点不报错但RViz里就是没有地图。先检查RViz的Fixed Frame是不是map这一项设置错了地图当然放不对位置。然后在终端里看gmapping有没有真的在接收scanrostopic hz /scan如果hz一直是0说明激光数据根本没进来。再检查scan话题名是不是/scan有些机器人雷达话题叫/scan_raw或者/laser/scan要在launch文件里把scan_topic改成对应话题。如果话题有数据但地图还是黑的很可能是地图概率值设置问题。检查Map的话题类型RViz里要选Map而不是OccupancyGrid虽然消息类型差不多但展示逻辑不一样选错了会产生显示偏差。6.3 地图出现重影、错位、不闭合先怀疑这三个地方地图重影是我在建图过程中遇到最多的质量问题。重影通常不是gmapping一个环节的问题而是多个因素叠加。首先检查底盘里程计。你可以让机器人沿直线走一米看里程计反馈是不是一米。如果差很多先把车轮直径、轮距、码盘线数这些参数标定好再回来调gmapping。里程计标定的方法比较常见的是让机器人走一个固定距离对比真实距离和odom发布距离算出修正比例然后写进底盘驱动或使用robot_pose_ekf之类的节点做融合。其次是激光安装位置。如果雷达的坐标系相对base_footprint有旋转偏差或者雷达在机器人高速转弯时发生抖动建图就会歪斜。检查URDF里laser的xyz和yaw设置是否和真实机械结构一致。我见过一个案例雷达装歪了1度建出来的地图整个旋转了十几度因为角度误差是随移动距离累积的。还有雷达的运动畸变。单线雷达是旋转扫描的一帧数据里的点不是同一时刻获取的机器人一边动一边扫描就会导致“扇叶状畸变”。尤其在快速旋转时特别明显。解决方法是控制移动速度或者用带运动畸变校正的驱动节点。gmapping本身不处理这个问题全靠外部配合。6.4 建图完成保存地图后地图不完整或者边缘被裁剪地图保存出来之后边缘被裁掉一般都是因为map_saver保存的范围只覆盖了当前地图已知区域。gmapping的地图会随着机器人移动不断扩展只要机器人没走过的区域自然没有地图数据。解决办法是建图时确保机器人把所有需要标记的边界区域都扫过一遍尤其是墙根、墙角这些地方。激光雷达扫描到墙壁时如果雷达安装高度较高或者有倾角贴近墙根的位置有盲区地图上会表现为墙壁像“缩水”了一圈。这种情况可以在建图时沿着墙走一遍同时让雷达稍微低下头或者调整laser的安装角度让扫描平面尽量贴近地面。另外如果你后期想在地图上直接做路径规划保存后先打开看pgm边缘部分有多余的黑色噪点可以用图像处理工具简单清理一下。但不要修太多因为你修的是图不是gmapping生成的地图数据过度修图会让真实障碍物信息丢失影响导航安全性。6.5 CPU占用过高建图卡顿地图越来越慢gmapping本身不算特别吃资源但如果你同时开了Gazebo仿真、RViz可视化、键盘遥控节点、还有一堆传感器驱动老电脑确实会卡。这个时候优先降RViz显示负载比如减少地图的显示频率再考虑调gmapping参数。如果CPU还是扛不住先把particles从默认30降到15再把throttle_scans设为2甚至3。注意这会让定位精度下降但至少能保证系统跑得动。等你换到性能好一点的电脑再把参数调回来。另一个常见问题是CPU一致很高但地图不动可能是gmapping把大量时间花在等待tf同步上。检查一下/odom话题有没有持续发布odom的发布频率一般在20Hz以上如果底盘的odom只有2Hzgmapping会一直等待变换根本进不了建图主循环。6.6 动态物体干扰建图时有人或者门突然开关这个问题仿真里少见真机实验中特别普遍。激光雷达扫描到行人、经过的推车、突然打开的门这些“动态物体”会被当成障碍物画进地图。gmapping没有动态物体剔除机制所以建图时要严格控制环境中的人和物体。我常用的办法是选择非工作时间建图比如早上人少的时候。如果条件不允许只能在有人走动的环境里建图可以把雷达的识别人群范围直接用参数屏蔽掉但这也会牺牲一部分真实障碍物的识别能力看你的具体场景权衡。6.7 想要在ROS 2环境里建图怎么迁移这些经验如果你用的是ROS 2 Humblegmapping也有对应的ros2端口包叫slam_gmapping但功能和维护状态都不如ROS 1版本成熟。ROS 2里更推荐直接学slam_toolbox它支持2D SLAM、回环检测、地图序列化而且能直接读取之前gmapping保存的pgm地图做匹配定位。不过这篇里讲的所有关于激光数据、tf、里程计、地图质量、动态物体干扰的经验在ROS 2里百分之九十仍然适用。因为SLAM的核心从来不是工具链而是对传感器数据特征和机器人运动模型的理解。你只要能手动把topic和tf这些概念迁移过去上手slam_toolbox也就是一两小时的事。7. 最后再分享一点我自己的实操感受按照gmapping的整个流程做下来你会发现最难的部分其实不是算法本身而是Debug。我最初自己照着网上教程跑gmapping的时候地图歪了整整一个星期后来查出来是雷达装歪了0.5度URDF里没体现走到走廊里偏移被放大地图就整体扭了。所以如果你也遇到类似问题先检查机械和tf再怀疑参数顺序不要反。另外建议做一张“标定房间”的地图找一个小仓库或者一间放满纸箱的空房间先跑一个测试环境把gmapping的各个参数都试一遍并记录地图效果形成一个自己的经验表。这样以后换到更大环境你的调试速度会比别人快很多。这个系列接下来可能会聊amcl定位和navigation导航框架到时候这张建好的地图就要派上大用场了。先把gmapping这关过掉地图质量过关了后面的事真的会顺很多。