做ROS导航小车最常见的误区就是底盘能转、雷达能出点就急着跑导航。实话讲没有一张像样的地图导航就是空中楼阁。这一篇是“从0制作自己的ROS导航小车”系列的上位机篇第4篇核心任务只有一个用gmapping把真实环境的二维栅格地图建出来。内容适合已经把底盘驱动、激光雷达数据跑通正准备进入slam建图环节的朋友。看完照着做基本能避开我第一次建图时那些地图鬼影、漂移、错层的坑。需要先说明一下这里说的“上位机”指哪台机器。在ROS小车这个圈子里不同人的叫法不太一样。我这套系列里的上位机指的是小车上运行UbuntuROS的计算平台比如树莓派、Jetson Nano这类嵌入式主机或者你开发调试用的笔记本电脑。它负责接收下位机通常是STM32之类的单片机发来的里程计数据以及激光雷达的点云数据然后跑gmapping这类slam算法最终把地图和导航任务跑起来。下位机只管电机控制、码盘读取这些脏活累活。这篇所有操作默认你已经有这么一台能跑ROS的上位机并且雷达和底盘的数据都能正常出来。1. 建图不是拍照先想清楚gmapping解决什么问题1.1 为什么导航前必须先有地图导航的目的是让机器人自己决定“怎么从A点走到B点”。但它天生不知道A点在哪、B点在哪、中间有没有墙、哪里有路。这些信息怎么来就是靠建图阶段先扫一遍环境生成一张坐标一致的栅格地图后续的定位和路径规划才能在这张地图上进行。有人会问我不能让小车边跑边建图边导航吗这就是所谓的SLAM同步定位与地图构建问题。理论上是可行的ROS里也确实有这种“在线建图导航”的做法但工程上为了稳定可靠大家都习惯分成两个阶段先认真建一张干净的地图保存下来然后重启小车在纯定位模式下加载这张地图再导航。这样做的好处是建图过程中可以人为干预把容易出错的角落补扫干净最终拿到的地图是经过校验的。1.2 gmapping的技术方案拆解gmapping是ROS里最经典、也是上手门槛最低的2D激光 slam 方案。它基于Rao-Blackwellized粒子滤波RBPF用一堆粒子去描述机器人位姿的不确定性每个粒子维护着一份力所能及的子地图随着机器人移动不断打分、重采样最后收敛出一个最优的轨迹和地图。听起来很绕我用生活化类比解释一下你在一栋没图纸的大楼里被蒙着眼睛推着走手里拿着激光测距笔每走一步就朝四面扫一圈。gmapping相当于让一千个“虚拟的你”同时模拟这个走动过程每个虚拟你记录的数据都略有差异。走到一个环形走廊回来后大部分“虚拟你”的数据对不上了被淘汰少数对得上的留下来它们画出来的地图就是可信的。这个方案的优点是实现简单、对小场景室内环境友好、调参相对少、计算量不大。缺点是极度依赖里程计质量不太适合没有轮式里程计的无人机或腿足机器人在大走廊、长直墙这类特征稀少的场景里容易漂移。所以它特别符合我这类“自制轮式底盘2D雷达”小车的需求。1.3 为什么不直接上cartographer或fast-lio现在大家经常听说cartographer、karto、fast-lio这些更“高级”的建图方案特别是看到别人用3D雷达加fast-lio出图很炫酷就容易心痒。但我的建议是自制ROS导航小车的第一步不要直接上这些重型方案。原因有三点。第一gmapping对新手的调试负担最小核心参数就那么几个出现问题容易定位。而cartographer引入子图、回环检测、多传感器融合一旦建图质量不好新手根本不知道是前端匹配问题、后端优化问题还是传感器标定问题。第二gmapping在室内小场景下效果并不差我家里那间大概60平米的房子用它建出来的地图基本够用。第三后续要跑move_base导航最终需要的都是一张2D栅格地图gmapping的输出格式直接就能被AMCL和move_base使用。等哪天这辆车换上了固态雷达甚至3D雷达再考虑迁移也不迟。2. 建图前的四件事硬件、驱动、TF和时间2.1 确认雷达和里程计话题正常先别急着写launch文件把基础数据检查一遍。打开一个终端运行雷达驱动节点和底盘驱动节点后用rostopic工具确认消息有没有正常发布。rostopic list rostopic hz /scan rostopic hz /odom rostopic echo /scan -n1正常情况下/scan频率应该在5Hz到10Hz以上/odom在20Hz以上。如果/scan频率过低比如只有1Hzgmapping扫出来的地图会非常稀疏墙壁轮廓扭曲。这时候优先检查雷达的串口波特率、供电电流以及USB转串口是否被降频。雷达供电不稳是新手最常见的问题之一。/odom消息里必须包含位置和速度信息而且坐标值不要跳跃得离谱。如果底盘还没有发布odom建议先返回去补底盘驱动。gmapping里里程计是预测机器人位姿的核心输入没有它粒子滤波基本就是在瞎猜。2.2 TF树这个最容易翻车建图前必须确认坐标变换树是完整的。gmapping内部要求map、odom、base_link、laser_link这几个坐标系之间有明确的TF关系。你可以使用工具查看rosrun rqt_tf_tree rqt_tf_tree正常情况下你能够看到map - odom - base_link - laser_link这样一条完整的链路。这里最关键也最容易漏的是odom到base_link的变换通常由底盘驱动节点发布从编码器里程计计算出来laser到base_link的变换一般写在雷达的launch文件或单独的一个tf静态变换节点里需要确认laser相对于base_link的安装偏移是否真实准确。我见过有朋友把雷达装在车尾却按车头偏移写结果建出来的地图方向是反的而且雷达数据平移了一截。如果你雷达安装位置不是正中心一定要注意包含x、y坐标和yaw角偏移。yaw、pitch、roll也要定义清楚尤其是雷达装歪的时候那建出来的地图会是斜的。2.3 上下位机角色分工与驱动安装按这个系列前面的铺垫这时候你的上位机里应该装好了ROS并且能收到下位机通过串口或USB发来的消息。如果你还没装ROS很多人是在Ubuntu 20.04上装ROS Noetic。安装方式不外乎官方源手动装或者用鱼香ROS的一键安装脚本这个大家按自己网络情况选择就好。需要启动的东西分三类雷达驱动节点发布/scan话题发布laser到base_link的TF。底盘驱动节点读取下位机里程计发布/odom话题以及odom到base_link的TF。后续的gmapping节点订阅/scan和/odom以及TF发布/map话题。在真实小车上的时候这些节点都在上位机上跑下位机只做底层电机闭环和码盘数据的原始测量。如果你用的是虚拟机跑ROS串口和USB设备要先映射进虚拟机里这个细节别忽略虚拟机usb识别不到雷达建图自然无从谈起。2.4 安装gmapping功能包如果你的ROS是完整版desktop安装的一般自带gmapping。可以用下面命令确认rospack find gmapping如果提示找不到就需要安装。Noetic版本sudo apt install ros-noetic-gmappingMelodic版本就把noetic换成melodic。有些精简版ROS桌面安装不带gmapping装上之后依然要确保所有节点都能找到功能包路径。装完建议运行一次rosrun gmapping slam_gmapping看看有没有报缺少依赖的错误。3. 建图实操手把手把第一张地图跑出来3.1 写一个够用的gmapping launch文件网上关于gmapping的launch文件版本很多但不少都是老古董参数和当前功能包对应不上。我自己用的是下面的模板适配Noetic没问题改一下话题名就能用launch arg namescan_topic default/scan / node pkggmapping typeslam_gmapping nameslam_gmapping outputscreen param namebase_frame valuebase_link/ param nameodom_frame valueodom/ param namemap_frame valuemap/ param namemap_update_interval value5.0/ param namemaxUrange value6.0/ param namemaxRange value8.0/ param nameparticles value30/ param nameminimumScore value50/ param namelinearUpdate value0.1/ param nameangularUpdate value0.1/ param nametemporalUpdate value0.5/ param namedelta value0.05/ remap fromscan to$(arg scan_topic)/ /node /launch这个文件里我们显式指定了三个关键坐标系base_frame、odom_frame、map_frame。它们必须和你的底盘驱动、雷达驱动里发布TF时使用的坐标系名字严格一致。很多人在这一步翻车底盘驱动里把base叫base_footprintlaunch文件里却写base_link结果tf树断开建图出来乱成一团。统一命名非常重要。3.2 关键参数我以前乱填的代价这里选几个直接影响建图效果的参数展开说。maxUrange和maxRangemaxRange是雷达硬件能测到的极限maxUrange是gmapping实际用来匹配的雷达距离。一般把maxUrange设置为比maxRange小一截比如8米雷达就设6米这样可以屏蔽掉远处不可靠的杂点。如果设得太小会丢掉远处特征拐角处容易匹配失败设得太大又容易出现散乱的噪点。particles粒子数量默认30。越多精度越高但CPU占用成倍增加。树莓派4B这种级别建议30左右就够了航模图上想要更细可以调到60再高性价比就很低了。实测在室内小场景30和60的差别主要在重影边缘的处理上肉眼几乎看不出来。minimumScore扫描匹配的最低得分默认是0.0很多教程不会强调这个参数。实际上把它调到50到100能明显减少在某些特征重复环境里的误匹配。比如走廊、玻璃墙如果分数太低机器人转过一个弯后容易把自己“锁”到错误的位置上地图会出现重影。但注意不要调太高否则匹配失败会导致地图更新卡住。可以先从50开始感受。linearUpdate和angularUpdate表示机器人每平移多少米或旋转多少弧度才触发一次新的扫描匹配和地图更新。数值越小更新越频繁地图越精细但对里程计和雷达频率要求也越高。室内人工遥控建图我建议都设0.1平衡性和精度都够用。temporalUpdate单位是秒表示即使机器人没怎么动隔多久也要强制更新一次。这个参数在转角处、原地旋转时很有用保证机器人转了一圈后地图能及时闭合。delta栅格地图的分辨率默认0.05也就是每格5厘米。如果要更精细可以改0.02但建图划存储占用都会增加而且对雷达精度要求更高。便携式雷达就不建议小于0.05。3.3 启动流程与rviz可视化确认前面所有驱动都在跑话题和TF正常后启动gmappingroslaunch your_robot gmapping.launch然后在新的终端里打开rvizrosrun rviz rviz在rviz左侧面板里做三件事把Fixed Frame设为map点Add添加Map displayTopic选择/map建议再添加一个LaserScan显示Topic设置为/scan这样你能实时看到当前雷达扫描线和已建地图的重合情况。正常情况你会在rviz里看到地图从一片空白开始随着小车移动画出一条条不断延伸的黑色“墙线”。这时候不要着急先把车停在原地看到地图稳定了再动。如果你希望建图脱离真实环境、先在仿真里练习可以改用gazebo加turtlebot3的仿真环境跑命令roslaunch turtlebot3_gazebo turtlebot3_world.launch和roslaunch turtlebot3_slam turtlebot3_slam.launch用同样思路刷刷经验。仿真里练熟了再上真车能省很多冤枉时间。3.4 手动控制小车的建图节奏建图不是拿着遥控器乱转就行这里有讲究。控制小车手动建图节奏比速度重要得多。下面是我实际操作中总结的几条建图操作纪律速度要慢。线速度控制在0.15m/s以内角速度也别超过0.4rad/s。太快会导致激光雷达数据形变粒子滤波来不及收敛地图上的墙会拉出拖尾。优先环游外轮廓。先贴着房间四周走一圈让算法把环境边界闭合起来这相当于给地图打了“外框”之后内部补充就稳很多。遇到走廊、门口要二次确认。小车直行穿过长走廊时雷达容易丢失横向特征产生漂移。最好的办法是走完一遍再回头走一遍或者通过进入另一个房间形成闭环让算法有机会修正此前的误差。原地旋转要慢并且平稳。转太快时激光帧之间的对应关系容易断建出的墙角会变成圆弧。需要扫视某个区域时最好停下车再缓慢原地打转。关注rviz中的“鬼影”。地图上如果出现了多层重叠的墙壁尤其是已经走过的闭合区域说明当前位置匹配可能出现错误。先停一下让算法重算几秒看能不能“拉回”不行就原路返回一小段再缓慢重复一次通常就能修好。3.5 保存地图当整个环境轮廓完整、墙体清晰、没有明显重影时就可以保存地图了。保存使用map_server提供的map_saver工具mkdir -p ~/maps rosrun map_server map_saver -f ~/maps/room1执行完会生成两个文件room1.pgm是图像格式的栅格地图room1.yaml是地图的描述文件记录分辨率、原点坐标、占用阈值等参数。两个文件都要保留后续加载地图时缺一不可。yaml文件里有两个关键参数值得检查resolution对应栅格分辨率一般是0.05origin包含了地图原点在世界坐标系的x、y和yaw偏转。如果你建图时的起点恰好在房间角落原点值是负的这很正常。只要图像本身端正就不用管它。要验证保存的地图是否可用可以单独启动map_server加载一下rosrun map_server map_server ~/maps/room1.yaml然后在rviz里添加Map显示Fixed Frame选map看看能否正确显示。这一步验证通过地图才算真正保存成功。4. 建图常见问题排查我踩过的坑4.1 地图重影和错层表现同一面墙在地图上被画成两条甚至多条平行线或者机器人绕了一圈回到起点地图的走廊却错开了半米。这个问题的根源多数是里程计漂移或扫描匹配误判。排查顺序先看TF树是否完整odom到base_link的坐标是否连续变化再看里程计是否标定准确尤其是轮子直径、编码器线数这类参数不准的话跑得越远误差越大。再考虑降低速度重新扫描给算法更多匹配机会。如果用了minimumScore50之后还有重影可以尝试调高到100。4.2 地图无边界或墙壁断裂表现地图边缘出现大片黑色区域或者雷达明明扫到了墙地图上却无法形成连续轮廓。大多和雷达有效测距参数设置有关。检查maxUrange和maxRange是否匹配如果雷达最大只能测6米你却在launch里写12米gmapping就会收到大量无效数据地图边缘全是噪点。也要检查雷达是否对着空旷区域时返回的是有序数据还是一堆0或inf。部分低成本雷达对深色物体、倾斜表面反射不好建图时尽量让雷达的照射角度垂直墙面。4.3 树莓派建图卡顿、CPU爆满自制小车如果上位机是树莓派4Bgmapping跑起来确实有点吃力。我实测粒子数30、地图更新间隔5秒时CPU能占到70%以上但还是能跑无非是rviz显示有点掉帧。这时候可以分几步优化把particles降到20地图精度影响可控。把map_update_interval从5.0改到8.0降低地图更新频率。在rviz里关掉点云显示只保留Map和LaserScan。有条件就升级到Jetson Nano或NUC性能差距真的很大。如果你手头有台式机也可以做“远程建图”把雷达和底盘数据通过局域网实时传到PC端在PC上运行gmapping和rviz小车只负责采数据。这是很多项目团队实际采用的流程。4.4 地图内容是对的但图像是颠倒的这通常不是建图问题而是坐标系定义不一致。比如你的雷达安装时x轴朝后或者底盘odom的yaw方向定义和gmapping默认不一致。先检查雷达launch文件里laser相对base_link的yaw偏移再检查底盘驱动里odom的四元数有没有转错方向。这两个坑我在调试时都踩过方向不对时不是把launch里的yaw改成3.14159就行要回到imu或编码器计算姿态的那段代码里找原因。4.5 建图调试时的速查表我把建图过程中最常见的问题整理成了一张速查表适合贴在工位旁边省得每次翻历史记录现象可能原因排查方向地图重影/错层里程计漂移、扫描匹配失败检查TF、降低速度、提高minimumScore地图边缘混乱maxUrange设置过大拉近扫描范围忽略远距离噪点墙体断裂缺口雷达数据频率低、反射丢失检查雷达串口和供电补扫缺失区域地图整体旋转雷达yaw偏移不准检查laser-base_link的TF偏转长时间不出图话题名不匹配确认/scan以及odom话题名与launch一致地图隔一段才更新linearUpdate/angularUpdate过大调小更新阈值CPU过高、卡顿粒子数过大降低particles增大map_update_interval局部出现随机噪点玻璃、镜面、深色物体反射问题靠近细腻扫描手动补充信息排查问题的大原则是先查基础话题和TF再怀疑算法参数。很多时候不是gmapping不行而是odom本身已经烂了gmapping再怎么调也救不回来。5. 地图质量评估与导航衔接5.1 什么样的地图算合格很多朋友看到地图能出来就很兴奋但其实那种不能用的地图也不少。我判断一张地图合格不合格有这几个标准墙体线条清晰连贯。栅格图上的“墙”应该是连续的黑线厚度稳定在几格以内不能断成虚线也不能粗得像毛笔。回环闭合。机器人绕一圈回到起点后地图上的起点和终点应该能对齐误差不超过5厘米级别。如果错开了一二十厘米导航时定位精度会很差。障碍物边界分明。桌子腿、箱子这些障碍物边缘干净没有一大团糊状黑点。室内狭小区域能分辨。比如门框、墙角这种细节地图上应该能看出明显转角而不是糊成一片。如果发现局部有少量噪点不用太紧张可以在导航定位阶段用代价地图把它们过滤掉。但如果大面积漂移、墙体断裂建议重新建不要拿烂地图硬上导航。5.2 从地图到AMCL导航恭喜地图保存成功的这一刻你手里已经有了导航系统的“地基”。后续要做的是把这张地图交给AMCL定位节点再配合move_base跑路径规划。启动AMCL时只需要在launch文件里加载之前保存的room1.yamlnode pkgmap_server typemap_server namemap_server args$(find your_pkg)/maps/room1.yaml/导航时rviz里会显示机器人在地图中的位姿估计。如果发现定位不准可以手动给定初始位姿2D Pose Estimate后续AMCL会通过激光雷达数据不断修正。这一步和gmapping建图用的是同一种思想靠激光和里程计数据来猜测机器人在地图中的位置。只不过建图时map是未知的导航时map是已知的我们只需要定位机器人自身。所以建图质量直接决定导航体验。地图错层越少、轮廓越清晰AMCL收敛就越快路径规划的代价地图也能更好地识别障碍物。5.3 gmapping的局限与后续升级方向我自己在小车上用gmapping一段时间后也明显感受到它的天花板室内场景如果超过两三间房或者遇到长走廊、玻璃墙这些环境建图质量就会下滑。后续如果要继续升级可以考虑三条路换用cartographer。谷歌这套方案支持2D和3D自带回环检测对里程计依赖小建图效果在小场景和中等场景都明显好于gmapping代价是配置复杂、学习曲线陡。换用karto或slam_toolbox。如果树莓派资源有限可以试试slam_toolbox它适合2D建图而且是基于图优化的对CPU占用比cartographer友好。3D雷达用户考虑fast-lio或mid360建图。近年用固态雷达跑fast-lio方案的越来越多但产出的是3D点云地图要接到nav2或move_base的2D导航里还需要额外做投影处理不建议新手上手就直接玩这套。我的建议很简单gmapping是你理解slam原理的最短路径。等这套流程彻底跑通再根据不同场景选更合适的建图方案思路会清晰很多。最后再分享一个小技巧。建图的时候如果手边没有手柄或键盘遥控可以写一个简单的按键控制节点用w/a/s/d发送cmd_vel速度指令。但记得把最大线速度限制在0.2以下最大角速度限制在0.4以下。我自己第一次遥控建图时就是因为把速度调到1.0结果小车转个弯地图直接糊了一片后来才意识到建图阶段“慢就是快”这个经验比任何参数调优都实在。愿你第一张地图就能干净闭合。