
做导航小车做到“上位机篇”前面几篇已经把ROS环境、下位机通信、键盘控制这些都跑通了那接下来最让人兴奋的一步就是让小车自己“认路”——也就是SLAM建图。这一篇我打算把gmapping这套流程完整拆开揉碎讲清楚。这不是一篇单纯的命令行教程我会把选型理由、参数含义、踩坑记录、地图文件格式这些东西全部聊透。无论你是刚装好ROS还分不清话题和服务的新手还是已经能让小车动起来但建图总是漂的老哥这篇文章应该都能给你一些有价值的参考。这个系列的名字叫“从0制作自己的ros导航小车”那咱们就坚持从0的视角来讲。上一阶段我们做了底盘的上下位机联调现在要在上位机里跑起SLAM算法让机器人一边移动一边生成环境地图。这一步的结果将直接决定后面导航AMCL、move_base等能不能正常工作所以建图这个环节千万别抱着“能用就行”的心态糊弄过去。1. 为什么建图环节选了gmapping1.1 从导航小车的真实需求出发很多第一次接触机器人建图的朋友会有个疑问SLAM算法那么多为什么教程上来都用gmapping是不是因为老是不是已经过时了我可以负责任地说gmapping虽然“年龄”不小但在2D激光雷达建图这个细分场景下它依然是最稳定、最省资源、最容易排查问题的那一个。咱们的导航小车用的是2D单线激光雷达底盘是差速轮上位机是一台普通PC或者性能不算强的迷你主机。在这种配置下gmapping的粒子滤波方案能很好地工作。它不像cartographer那样需要精心调试后端优化参数也不像hector那样对雷达频率和精度要求苛刻。gmapping的维护成本很低你只需要提供两样东西可靠的激光数据和基本准确的里程计数据它就能给你一张不错的地图。回到系列的主题小车后面还要接amcl定位和move_base路径规划gmapping生成的标准栅格地图occupancy grid map可以直接喂给导航栈使用格式天然兼容不需要额外转换。这一点非常关键因为有些SLAM算法输出的地图格式比如点云图、子图你还得再写转换工具对于从0做小车的阶段来说这是额外负担。1.2 gmapping和hector、cartographer的对比这里顺便把另外两个常见2D方案也聊一下方便大家理解我为什么最终锁定了gmapping。hector_slam它的优势是不依赖轮式里程计纯靠激光雷达的扫描匹配来推算位姿。听起来很香对吧但它有个致命前提激光雷达的更新频率必须非常高一般要求40Hz以上而且雷达自身的测量噪声要足够小。日常用的像RPLIDAR A1这类入门雷达只有5~10Hz跑hector的话地图很容易飘或者干脆建出一堆重影。除非你做的是无人机或者没有轮式里程计的特殊平台否则不建议优先尝试。cartographer谷歌出品的图优化方案精度高、支持回环检测能处理大场景也支持3D雷达。但代价是配置繁琐、参数多、吃算力新手照着官方文档折腾几天可能是常态。而且cartographer对硬件的要求偏高入门小车主控配置一般跑起来CPU占用会很难看。当然不是说不能用等基础建图跑顺了后期想追求更高精度再去换也不迟。gmapping粒子滤波方案计算量适中参数不复杂默认参数在多数室内场景就能有不错表现。它非常依赖里程计但恰好咱们的底盘有编码器能输出odom话题这就形成了完美配合。一句话总结选型逻辑任何算法选型都是先看平台约束再看场景需求最后才是个人偏好。对于从0起步的室内轮式小车gmapping就是最短路径。2. 建图前别急着跑先把这些环境指标捋清楚2.1 硬件条件与会话检查先说硬件底线。gmapping需要的是激光雷达话题/scan和TF树理论上只要有这两个来源就行。但你实际运行的效果很大程度取决于以下指标雷达扫描频率建议至少要有5Hz以上10Hz更好。帧率太低会导致机器人动得快一点时激光点云稀疏匹配不准。里程计精度差速轮底盘的里程计最好有编码器反馈纯开环控制的话建图质量会明显下降。上位机性能双核以上CPU基本能跑粒子数调到适中就行单核老电脑也可以跑只是建图过程要更慢更小心。在终端里启动小车底盘和雷达之后先用rostopic list确认关键话题存在rostopic list正常情况下你应该看到至少这三个关键话题/scan激光雷达数据/odom底盘里程计/tf坐标变换关系然后用下面的命令检查数据是否真的在流动rostopic echo /scan -n1 rostopic echo /odom -n1如果话题没输出先别往下走把驱动和底盘代码修好再来否则后面全是白忙活。2.2 TF树所有坐标系的“家谱”TF是ROS里最容易让新手懵圈的东西但它在SLAM里又极其重要。简单说TF就是机器人身上各个坐标系之间的相对位置关系。gmapping建图时需要维护这样一棵TF树map - odom - base_footprint - base_link - laser这里面的每一层关系都有明确出处map - odom由gmapping节点在运行时发布表示机器人在地图中的位姿这个不需要你管。odom - base_footprint通常由底盘驱动节点或robot_pose_ekf发布来自轮式里程计对位姿的推算。base_link - laser激光雷达安装在车体上的哪个位置一般用静态坐标变换发布。你可以用下面命令查看当前TF树是否完整rosrun tf view_frames这会生成一个frames.pdf文件打开看一下里面是否有报错。常见的问题要么是缺少base_link到laser的静态变换要么是odom到base_footprint没人发布。这两种情况gmapping都会因为TF不完整而拒绝工作很多“为什么gmapping没输出地图”的求助帖最后查来查去都是TF树的锅。2.3 上位机环境的准备与一次性安装关于环境我强烈推荐新手直接使用鱼香ROS提供的一键安装脚本来准备ROS环境。它能在Ubuntu上帮你搞定ROS本体、依赖库、常见工具链省去一个个apt安装的开销。这个脚本在社区里口碑很好尤其适合刚接触Linux和ROS的人能够把从零到能跑通roslaunch的时间压缩到半小时以内。安装完成后务必确认roscore能正常启动并用rqt_graph看看节点连接是否正常。到这一步你就具备了跑gmapping的前提条件。3. 核心实操手把手跑通gmapping建图3.1 准备一个干净的launch文件我不会让你去git clone一堆别人的工程而是建议自己写一个gmapping.launch因为这样你才能理解每一行在做什么。先给大家一个可以直接用的模板我通常放在功能包的launch目录下launch !-- 启动gmapping节点 -- node pkggmapping typeslam_gmapping nameslam_gmapping outputscreen !-- 激光话题与坐标系 -- param namescan_topic value/scan/ param namebase_frame valuebase_footprint/ param nameodom_frame valueodom/ param namemap_frame valuemap/ !-- 里程计噪声模型 -- param namesrr value0.01/ param namesrt value0.02/ param namestr value0.01/ param namestt value0.02/ !-- 建图更新策略 -- param namelinearUpdate value1.0/ param nameangularUpdate value0.5/ param nametemporalUpdate value-1.0/ !-- 粒子数量 -- param nameparticles value30/ !-- 地图参数 -- param namedelta value0.05/ param namexmin value-10.0/ param nameymin value-10.0/ param namexmax value10.0/ param nameymax value10.0/ param namemaxUrange value5.0/ param namemaxRange value6.0/ /node /launch启动方式roslaunch your_package gmapping.launch如果一切配置正确终端里会刷一些gmapping的运行日志并且看到它已经订阅了/scan和/tf。没有报错的话打开rviz添加Map显示并选择话题/map就能看到地图在慢慢绘制了。3.2 关键参数含义详解给一坨参数列表却不解释等于没给。下面挑几个我用下来最影响建图质量的参数展开聊。particles粒子数gmapping本质是维护一堆粒子每个粒子都携带一份“机器人轨迹和地图”的假设。粒子数越多对真实状态估计越准但CPU开销也越大。30这个数值在入门小车上是比较均衡的选择想精细建图调高到60~80但要注意内存占用和帧率下降。linearUpdate和angularUpdate机器人平移多少米或旋转多少弧度才触发一次扫描匹配。默认1.0米和0.5弧度适合普通小车。如果你的机器人移动速度很慢可以适当调小让地图更新更频繁如果建图资源紧张也可以调大来降低计算频率。srr/srt/str/stt这是里程计噪声模型参数。简单理解就是“你对里程计的信任程度”。数值越小算法越相信里程计数据地图线条会更干净但如果里程计质量差数值太小反而会让建图结果“固执己见”无法通过激光匹配来修正错误。目前这组0.01/0.02的参数对大部分差速底盘来说比较稳。maxUrange和maxRange雷达的有效测量距离。maxUrange如果设得太大远处噪声扫出来的点是无效信息反而干扰匹配设得太小又会让可观测环境的范围受限。我是按实际雷达量程来设置的比如小车雷达标称6米我就设maxRange6.0maxUrange5.0。delta地图分辨率单位是米每像素。0.05表示每像素对应5厘米这是室内小车的经典参数。想要更精细就改0.025代价是地图尺寸变大、内存翻倍。3.3 用键盘控制小车移动建图gmapping本身只负责算地图它不指挥小车行动。你需要一个方式控制小车移动最常见的方案是键盘遥控teleop_twist_keyboard。rosrun teleop_twist_keyboard teleop_twist_keyboard.py这个节点会把键盘按键映射成/cmd_vel速度指令你打开的是控制窗口按u/i/o/j/k/l这些字母分别控制前后左右转。别小看这个东西移动策略直接影响建图质量。建图移动有几个非常重要的原则速度要慢。速度快会导致里程计误差增大、激光帧间重合度低地图就会变糊甚至出现重影。平移和旋转交替进行。直线走一段停下原地旋转扫清四周再继续走。这样保证每个区域的激光扫描范围足够全面。尽量按“Z字形”或者“回字形”遍历区域减少重复扫过的面积浪费提高效率。遇到走廊尽头掉头时务必留出空间别怼着墙硬转否则雷达可能扫不清楚墙角结构。键盘控制的体感比较“莽”但控制起来直接有效做原型验证完全没问题。如果你手里有手柄或遥控器原理一样只要能发布/cmd_vel就行。实操时有个小细节rviz里把机器人模型和Map同时显示出来建图过程中不断观察地图轮廓是否和真实环境吻合。如果发现地图边缘突然偏移或者出现大面积黑色错误区域马上停一下等地图稳定了再继续。别指望建完再修修图远远没有重新建便宜。3.4 保存地图别把劳动成果弄丢当你把家里客厅、走廊这类区域都扫完rviz里的地图看起来已经成型了这时候就要把地图保存下来。工具是map_server包里的map_saver命令如下rosrun map_server map_saver -f ~/maps/home_map-f参数指定保存路径和文件名。执行完成后会生成两个文件home_map.pgm黑白栅格图像就是地图本身home_map.yaml地图的描述文件记录分辨率、原点、阈值等元信息保存时有个容易忽略的坑别在gmapping还没建完时就急着保存否则可能保出一张半成品。还有一点map_saver保存的是当前ROS系统中最新发布的一张/map所以确保保存前地图话题是活的且已经更新到了你满意的状态。保存完之后可以用图片查看器打开PGM文件快速看看效果。如果地图歪歪扭扭区域边缘错位那就说明建图过程中移动速度太快、回环没闭合好建议重跑一次而不是硬着头皮拿半张坏图去导航。4. 地图文件到底怎么解读搞清楚这层才算真懂4.1 yaml文件和pgm图像的含义很多时候我们只关心“地图能不能用”但导航系统设计上地图不是一张图片那么简单它是一组数据。来看一个典型的yaml文件内容image: home_map.pgm resolution: 0.050000 origin: [-10.000000, -10.000000, 0.000000] negate: 0 occupied_thresh: 0.65 free_thresh: 0.196逐行说image保存的pgm图像文件名路径是相对于yaml文件所在位置。resolution地图分辨率0.05米/像素。origin地图左下角在世界坐标系通常是map系中的坐标和偏航角这里表示地图左下角位于map坐标系(-10, -10)。occupied_thresh和free_thresh像素灰度值转为占用概率的阈值高于occupied_thresh判定为占用障碍物低于free_thresh判定为空闲。中间值是未知区域。PGM图像本身是8位灰度图像素值从0黑到255白。在ROS的栅格地图约定中黑色是墙占用灰色是未知区域白色是自由空间。注意这和一般人的直觉相反第一次做导航时很多人在地图上标注障碍物结果把路标成了墙就是这个约定没搞清。4.2 地图的简单后处理建好的地图偶尔会有一些孤立噪点或者坑坑洼洼的边角这很正常。你可以在GIMP或者OpenCV里做一下简单的处理比如把孤立的黑点清掉或者把明显是反射噪点的散点抹掉。但这里有一条红线不要为了“美观”大改地图。因为栅格地图不仅仅是给人看的它最终会被导航算法用来做代价地图costmap。如果你用图像编辑软件贸然把一大片灰色未知区域涂成白色空闲会导致机器人规划出一条穿越未知空间的路径后面撞墙了都不知道怎么回事。只清理那些明显是传感器噪声产生的微小孤立块别动大块结构。其实我个人的经验是如果是地图质量还行就不碰图像处理这个环节。建图阶段多花十分钟把每个角落扫干净比在图片编辑器里修半天更可靠。5. 建图过程中最常见的坑和排查方法5.1 典型问题速查表这里把这几年在读者群和开源社区里最常见的gmapping问题整理成速查表方便大家对照排查现象可能原因排查思路地图一直发散或全是黑色粒子数太少或里程计噪声参数过小提高particles到50~80适当调大srr/srt等参数地图出现重影、双墙移动速度太快或雷达帧率低放慢速度检查雷达驱动是否掉帧gmapping启动后无地图输出TF树不完整scan话题不匹配用rqt_tf_tree查看TF是否完整rostopic info /scan确认发布者建图过程中地图突然错位激光雷达被碰撞、位置松动检查雷达固定支架是否紧固必要时重新标定static transform回环闭合后地图断层里程计误差积累过大减小移动速度回环路径尽量平滑必要时调大粒子数地图保存后打开是纯黑或纯白保存前gmapping可能退出或还未输出有效地图确认/map话题有数据重新保存5.2 几个独家避坑技巧再补充几个我在实际项目中吃过亏后总结的经验。首先建图期间禁止在底盘上加载额外重物。很多小车底盘是差速驱动负载一变轮径有效半径就变了里程计瞬间不准建图结果就是灾难。想要建图效果稳定请保持车辆状态前后一致不要在车架上加装不必要的设备。其次激光雷达的固定位置非常关键。雷达与车身之间的静态变换base_link - laser必须和物理安装位置严格一致。很多车的建图没问题但导航时总觉得地图偏了45度结果发现是雷达安装朝向写错了或者安装支架歪了。遇到这类问题先用尺子和角度尺量准偏移再更新launch里的静态变换参数。还有一个容易被忽视的点建图过程中尽量避开透明玻璃、镜子、高反光物体。激光打在玻璃或镜面上会发生透射或镜面反射这是物理限制任何2D算法都救不了。如果你家具体环境里有大块玻璃要么在标签纸上做好遮挡要么干脆暂时绕开这些区域否则地图里会留下一条条“幽灵墙”。最后一个很实用的技巧是启动gmapping前先在家里走一圈心里规划好路径。建图不是乱逛你要让小车以最少的路径覆盖所有要扫的区域。提前规划能大幅减少无效运行里程也减少里程计误差累积的时间地图质量自然会上去。6. 建图之外你还需要知道的一些心得到了这个阶段地图已经保存成功不少朋友就以为gmapping篇结束了马上往导航篇冲。但以我的经验建图完成之后先别急着切换项目有几个收尾动作会让你后续少踩很多坑。第一个建好的地图一定要保留原始版本。后续做导航参数调参时代价地图参数随时可能改坏到时候你还能回退到原始地图重新折腾。我习惯按日期归档地图文件maps/ ├── 20250101_home_v1.pgm ├── 20250101_home_v1.yaml ├── 20250103_home_v2.pgm └── 20250103_home_v2.yaml版本管理的习惯虽简单但真能救命。第二个善于利用rosbag。如果你不想每次建图都手动推着小车跑一圈可以在建图过程中同时录制bag包这样以后回放数据就能重新建图不必同一块场地反复跑。录制命令很常见我简单给个记录scan、odom和tf三者的命令rosbag record /scan /odom /tf -o bag/home_build_map回放时用rosbag play把话题重新发出来再启动gmapping即可。这样调参数非常方便不用把人累废。第三个如果想在上位机上做一个可视化控制页面将来就可以把gmapping的启动和地图查看集成到一个界面里。常见思路是用rqt或者PyQt写一个Python控制面板通过subprocess调用roslaunch再用rqt_image_view或自定义的rviz嵌入来实时看地图。这个方向以后可以当“上位机篇”的进阶章节来专门做。还有一点想特别提的是现在网上很多教程会拿fast-lio、mid360这类3D方案来做“炫技”但3D方案对雷达成本和计算资源的要求根本不是入门小车阶段该碰的。如果你以后想往更高阶的方向走等到你的地盘基础扎实、2D建图和导航都吃透了再考虑升级激光雷达和算法也不迟。基础不牢上3D方案只会让你在参数海洋里越陷越深。最后再分享一个我个人的习惯每次建图前我都会先手动转动一下雷达的电机看看雷达数据在rviz里是否均匀且连续。如果数据断断续续多半是供电不足或者接触不良。雷达的稳定性和供电质量往往比算法参数对建图效果的影响还要大。这个小检查动作帮我避过不少建图白跑的坑。gmapping建图这件事说穿了也不复杂数据进来算法匹配地图输出。但真正做好需要你对坐标系、话题、参数和移动策略都有足够的耐心和感知力。希望这篇文字能帮你少走点弯路把地图建得稳稳当当为后面的导航攒下好底子。