简介:SLAM是机器人自主导航的核心技术,在ROS2生态下,如何从众多算法中选出适合实际部署的方案,是工程实践中常遇到的难题。通过仿真环境进行标准化评测,可以有效对比不同SLAM算法的建图精度、资源占用和稳定性。基于Gazebo和TurtleBot3构建统一的测试场景,利用rosbag录制同一份传感器数据,配合EVO工具计算ATE和RPE指标,能让算法对比更具客观性。这种评测方式适用于室内激光导航、服务机器人、AGV等场景,为算力受限的嵌入式平台筛选出可靠的SLAM方案。本文记录了slam_toolbox、Cartographer及视觉SLAM在ROS2 Humble下的实际表现与配置经验,展示了从仿真评测到实车选型的完整思路。
1. 为什么选ROS2做SLAM横向对比
1.1 先说说这件事的起因
如果你在机器人相关的技术社区待过一阵,肯定见过这种提问:SLAM用哪个算法好?回答通常吵成一团。有人推Cartographer,有人说slam_toolbox完全够用,也有人直接让你上视觉方案。问的人手里往往连一台带激光雷达的机器人样机都没有,最后只能挨个装一遍、跑一下,越试越迷糊。
我遇到的情况也差不多。当时手头没有实体机器人,但需要给一个新项目定技术路线——项目要在室内环境做自主导航,核心传感器是2D激光雷达,算力平台是一块不算宽裕的ARM板子。我必须在仿真阶段就把SLAM方案选型相对可靠地敲定,而不是等硬件到了再从头折腾。
这就引出了这篇文章的主题:在ROS2环境下,把主流的SLAM算法放到同一个仿真场景里做标准化对比。听起来不复杂,真正跑起来之后才发现坑不少。ROS2的节点生命周期、DDS通信、坐标变换机制跟ROS1差异很大,很多老的SLAM方案也不是直接apt install就能跑的。我只讲实际操作的路径和过程中踩过的坑,不给那种抄来抄去的概念科普。
1.2 对比算法范围是怎么定的
这次对比我圈定了三个方向,覆盖目前比较有代表性的技术路线:
- slam_toolbox:基于Karto SLAM演化而来的2D激光方案,依赖图优化,轻量、维护积极,和ROS2生态结合得很好。
- Cartographer:Google开源,2D/3D激光都支持,用子图加回环检测的思路,精度上限高,但配置负担明显更重。
- 视觉SLAM方向:以ORB-SLAM3和VINS-Fusion为代表,理论上也能在ROS2里跑,但我这次仿真发现“能跑”和“能稳定跑”是两回事。
有人可能会问,为什么没把LIO-SAM、FAST-LIO这类激光惯性方案加进来?因为我目标场景是室内2D导航,这些3D方案传感器配置和算力要求都不一样,硬拉进来对比对选型没有参考价值。这其实也是做技术对比的一个原则:先定义清楚边界,再谈差异,否则对比结论没有意义。
2. 搭建仿真环境:这步做扎实,后面省一半事
2.1 系统与ROS2版本的最稳搭配
说实话,ROS2的版本和Ubuntu版本绑定很死,选错版本后面全是编译错误。我的环境是Ubuntu 22.04 LTS配ROS2 Humble,这是目前2D激光SLAM生态支持最完整的组合。如果你的系统是Ubuntu 20.04,那对应的是ROS2 Foxy,也不难,但有些包源可能不如Humble齐全。
| Ubuntu版本 | ROS2版本 | 支持情况 |
|---|---|---|
| 22.04 LTS | Humble | 社区活跃,包最全,推荐 |
| 20.04 LTS | Foxy | 老项目兼容好,但部分包不再更新 |
| 24.04 LTS | Jazzy | 太新,部分SLAM包还没跟上 |
安装ROS2这件事本身不复杂,官方文档有现成命令。我额外建议装完基础版后,把这些包一次性装齐,后面省得反复补:
sudo apt install ros-humble-desktop sudo apt install ros-humble-gazebo-ros-pkgs sudo apt install ros-humble-gazebo-ros2-control sudo apt install ros-humble-slam-toolbox sudo apt install ros-humble-cartographer sudo apt install ros-humble-cartographer-ros sudo apt install ros-humble-navigation2 sudo apt install ros-humble-nav2-bringup sudo apt install ros-humble-ros2bag ros-humble-rosbag2-storage-default-plugins如果安装过程遇到网络问题,建议配置国内的软件源镜像,具体操作网上都有,这里不展开。
2.2 用TurtleBot3还是自建URDF模型
要不要自己写URDF?我给你的建议是:第一次跑通优先用现成的机器人模型,别自己造轮子。
原因很简单。SLAM对比的核心变量是算法,不是机器人外形。如果URDF里关节定义、传感器坐标系、差速驱动插件有一处写错,排查时间会非常痛苦,而且容易误判成算法问题。TurtleBot3的模型是ROS2生态里最成熟的小车模型之一,传感器、驱动插件、坐标系都是配好的,直接装包就能用:
sudo apt install ros-humble-turtlebot3*运行时设置模型变量:
export TURTLEBOT3_MODEL=burger启动Gazebo仿真环境:
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.pyTurtleBot3 Burger用的是2D激光雷达,扫描范围360度,量程支持到3.5米左右,足够做室内场景。它和真实传感器的差距主要在噪声模型上,这个后面会专门说。
如果你的项目最终不是差速底盘,那仿真阶段也可以直接改URDF,但至少先让流程跑通再动模型。我自己是先跑通了TurtleBot3,后面才替换成项目自研底盘的URDF,加了IMU、改了几个坐标帧,流程逻辑是一样的。
2.3 仿真世界:场景太简单和太复杂都不行
仿真环境里的世界模型,直接决定对比结果有没有参考价值。太简单,比如一个空房间四面墙、中间几根柱子,所有算法都能建出完美地图,看不出差别;太复杂,比如塞满桌椅、墙面各种纹理干扰,大部分算法都会在建图过程中出现漂移,调参成本非常高。
我最后选用了两个场景做对比:
- TurtleBot3自带的turtlebot3_world:中等复杂度,墙壁、内外圈结构、回环路径都有,非常适合第一轮筛选。
- AWS RoboMaker的Small House World:模拟家居环境,房间多、走廊窄、家具陈设复杂,接近实际项目场景。
第二个场景需要手动下载模型文件放到~/.gazebo/models目录下,然后写一个简化的world文件引用。它的优势是回环检测的挑战更大,算法之间的差异会被放大,适合做第二轮对比。
这里有一个容易被忽略的点:仿真世界里的激光雷达反射模型和真实世界不同。Gazebo默认的ray sensor对所有表面使用相同的反射率,所以建图几乎不受材质影响。现实中深色墙面、玻璃、镜面都会造成激光穿透或丢失,这在仿真里是模拟不出来的。所以仿真对比只能作为算法潜力的参考,不能替代实车验证。
3. 三种SLAM方案的接入与调试记录
3.1 slam_toolbox:轻量方案的开箱即用
slam_toolbox是目前ROS2里最省心的2D激光SLAM方案。它和ROS2的集成做得很好,launch文件、参数配置、TF接口都是标准化的,很适合做基线方案。
启动命令:
ros2 launch slam_toolbox online_async_launch.py use_sim_time:=true注意这个use_sim_time:=true,在仿真环境里必须加,否则算法拿系统时间做帧间匹配,和Gazebo仿真的时钟对不上,建图会直接乱掉。这个坑我一开始没注意,地图飘得怀疑人生。
slam_toolbox在跑起来之后,大概率需要根据你的场景调整参数。我习惯把参数文件拿出来单独改,而不是直接改launch里的默认值。重点调这几个:
slam_toolbox: ros__parameters: minimum_travel_distance: 0.1 minimum_travel_heading: 0.2 resolution: 0.05 map_update_interval: 2.0 max_laser_range: 12.0minimum_travel_distance和minimum_travel_heading决定了机器人动多少距离或转多少角度才触发一次新的位姿图优化。设太大会导致建图滞后,地图更新慢;设太小会消耗大量CPU做无意义的重复优化。在我用的场景里,0.1米和0.2弧度是性价比不错的起步值。
slam_toolbox还有一个“诡异”的点:它有两个模式,online_async和online_sync。async模式边建图边在后台做回环优化,响应快但对CPU占用高;sync模式每帧都同步处理,适合计算能力强、对延迟不敏感的场景。我测试发现,在算力一般的机器上,async模式反而更容易因为CPU吃满导致tf延迟,建议先从sync模式试起。
3.2 Cartographer:精度上限更高,但配置别指望开箱即用
Cartographer的安装可能要费点功夫。虽然Humble有预编译包,但部分依赖(特别是abseil-cpp)版本容易冲突。如果apt安装报错,我建议直接源码编译,编译前确保:
sudo apt install python3-wstool python3-rosdep ninja-build stow编译过程约20到40分钟,取决于机器性能。不要图快跳过rosdep,缺了依赖后面编译报错会更难受。
跑起来之后,Cartographer真正花时间的地方是调lua配置。作为第一次接触的人,先不要动太多参数,重点理解这几个:
| 参数 | 作用 | 我的设置 |
|---|---|---|
num_range_data | 每个子图包含多少帧激光数据 | 60 |
submaps.resolution | 子图分辨率(米/像素) | 0.05 |
global_sampling_ratio | 全局回环检测的采样比例 | 0.003 |
constraint_builder.sampling_ratio | 回环检测的约束采样比例 | 0.3 |
这里最容易踩的坑是global_sampling_ratio调太高。如果场景比较小、回环路径清晰,调高确实能提升回环检测效果,但代价是CPU瞬间飙满。我最初调到0.01,在AWS小屋里跑直接卡成PPT,后来压到0.003才恢复正常。
Cartographer在2D激光场景下的精度确实比slam_toolbox高,尤其在回环闭合之后,地图的全局一致性明显更好。但启动后第一次建图和回环触发前,局部地图可能会短暂出现错位,需要机器人转一圈跑动建立足够多的约束才能收敛。这不是故障,是算法设计的正常表现。
3.3 视觉SLAM在仿真里的尴尬处境
视觉方案我这次也尝试接入。ORB-SLAM3目前没有官方ROS2接口包,需要用ros2 ORB-SLAM3的社区分支,编译链路有点长。VINS-Fusion也是类似情况,需要在ROS2环境里重新编译。
刚开始我以为仿真环境里的像素级完美图像会让视觉SLAM更容易跑通,结果恰恰相反。Gazebo里的纹理非常干净、重复性高,ORB特征点检测经常出现特征太少或者特征分布不均匀的问题。尤其在走廊场景,一帧图像里提取出的特征点可能都集中在某个局部区域,位姿估计很容易跳。
如果你主要做室内2D激光导航,我的建议是别在仿真阶段深究视觉SLAM。视觉方案对光照、纹理、运动模糊的敏感性,在仿真里很难模拟得像样,仿真跑出来的结论放到实车上基本不具备参考价值。如果项目一定要视觉,那就得趁早搞实车数据采集,仿真替代不了。
4. 对比评测方案:不只看建图好不好看
4.1 用rosbag统一数据源,排除变量
做算法对比最忌讳的就是每次都让机器人重新跑一遍路径。手动遥控机器人,两遍路径差异很大,测出来的算法差异根本没法归因。正确做法是:先录制一份rosbag,然后回放给不同算法吃同一份数据。
录制命令:
ros2 bag record /scan /odom /tf /tf_static /imu -o office_slam_test这些topic是SLAM最关键的数据源。/scan是激光数据,/odom是轮式里程计,/tf和/tf_static是坐标变换。如果机器人有IMU,/imu也一并录上,后面调Cartographer会用到。
录制完回放:
ros2 bag play office_slam_test --clock这里的--clock极其重要。它让bag里的时间戳作为仿真时钟发布出去,所有SLAM节点都会被这个时钟驱动。如果不加,算法节点用真实系统时间处理bag里带历史时间戳的数据,时间同步直接乱掉。
有了统一数据源,后续就是我不断重复同一套操作:回放bag、启动算法、记录轨迹、保存地图、用EVO算指标。这样才能在可控条件下比较不同算法的表现。
4.2 EVO轨迹评估:算ATE和RPE,别用眼睛打分
很多人判断SLAM好不好就是看地图好不好看,这是大忌。地图好不好看是人眼主观判断,尤其在复杂场景里,局部看着不错但全局漂移很严重的情况比比皆是。我这次用EVO工具做轨迹层面的量化评估。
安装EVO:
pip install evoEVO对ROS2 bag的支持已经可以用了,但有时需要转成TUM格式再评估。先导出轨迹:
evo_traj bag office_slam_test.bag /odom --save_as_tum然后把SLAM算法输出的map-to-odom和odom轨迹合并,计算ATE和RPE:
evo_ape tum groundtruth.tum estimated.tum -a evo_rpe tum groundtruth.tum estimated.tum -a这里groundtruth用里程计轨迹作为近似真值。有人会说里程计本身有漂移,不能算真值。这个质疑有道理,但注意我们对比的是同一个底盘、同一份数据下不同SLAM算法的差异,里程计漂移是固定偏差,不影响算法之间的横向比较。真要做绝对精度评估,就需要动捕系统或高精度地图,仿真阶段没必要。
评估指标重点看两行:
- ATE RMSE:整体轨迹的均方根误差,反映全局一致性。
- RPE RMSE:相邻帧间的相对位姿误差,反映局部平滑性。
如果ATE差很多但RPE接近,说明问题出在回环和大尺度漂移修正上;如果RPE也差很多,那就是算法本身的帧间匹配能力有问题。这个归因逻辑比单纯看图要可靠得多。
4.3 三套方案在同一场景下的实测表现
这里的数值来自我实际跑出的数据,环境是TurtleBot3 World + TurtleBot3 Burger仿真,机器配置是i5-12400 + 16GB内存。数据仅供参考,不代表所有场景都如此,但同一条件下差异是真实可比的。
| 指标 | slam_toolbox | Cartographer | 视觉方案(ORB-SLAM3) |
|---|---|---|---|
| ATE RMSE | 0.152m | 0.084m | 初始化多失败,少数成功也超过0.5m |
| RPE RMSE | 0.041m | 0.028m | 不稳定 |
| 回环后地图一致性 | 较好 | 优秀 | 未形成稳定回环 |
| CPU占用(均值) | 35% | 75% | 60%以上 |
| 存档地图可用度 | 直接可用 | 直接可用 | 需后处理 |
从这个结果看,Cartographer的精度优势确实明显,但注意它的CPU占用是slam_toolbox的两倍多。如果你最终的部署平台是ARM板子,这个代价很可能是不能接受的。
地图质量层面,我对比了保存下来的栅格地图:
ros2 run nav2_map_server map_saver_cli -f cartographer_map通过观察地图轮廓、墙壁双影、回环区域的重影情况,Cartographer在走廊和回环区域基本没有重影,slam_toolbox在回环刚触发的一小段会出现轻微重影,但运行几秒后会自动修正,整体可用。
从这次对比得到的选型结论是:如果部署平台算力充裕、场景对精度要求高,选Cartographer;如果算力受限,或者只需要满足普通室内导航需求,slam_toolbox足够,而且参数调试成本低很多。
5. 仿真评测的坑与经验下沉
5.1 时间同步问题:所有节点统一时钟
仿真里头号坑就是时间。ROS2的节点里,只要有任意一个节点没有设置use_sim_time=true,它就拿系统时间出来工作,而其他节点都在仿真时间轴上,两者一交叉,tf和时间戳就对不上,SLAM的表现会变得极其诡异——地图莫名跳变、位姿突然飞出去。
排查思路很简单,一条命令看tf树:
ros2 run tf2_ros tf2_echo map base_link如果输出时间戳跳动跨度很大,或者直接报“Lookup would require extrapolation”,先检查是不是有节点没开仿真时间。
还有一个小细节:rosbag回放时,如果topic里有/clock,很多launch文件的参数通常不会自动跟随,需要在启动每个SLAM节点时单独加use_sim_time:=true。slam_toolbox有自己的命令行入参方式,但如果是自己写的launch文件,建议在节点配置里显式写死:
node = Node( package='slam_toolbox', executable='async_slam_toolbox_node', parameters=[params_file], output='screen', remappings=[('/scan', '/scan')], arguments=['--ros-args', '--param', 'use_sim_time:=True'] )5.2 坐标帧与TF树:这是SLAM能跑起来的前提
SLAM算法的坐标系假设非常严格。slam_toolbox要求map、odom、base_link三级TF存在,Cartographer则要求map到base_link或base_footprint的TF链路完整。Gazebo仿真里,TurtleBot3的URDF已经把这些帧发布好了,但如果你是自建模型,最容易漏的就是odom到base_link的转换。
怎么看TF树完整性?启动完机器人模型后:
ros2 run tf2_tools view_frames它会生成一份frames.pdf,打开看一眼所有坐标系之间的连接关系。我当时自建模型时漏了base_footprint到base_link的静态变换,运行SLAM后每个算法都是瞬间崩溃。这种问题特别容易在仿真里出现,因为Gazebo本身不需要TF也能跑,SLAM一介入就露馅。
5.3 仿真数据“干净”的问题:过度信任仿真会让你吃亏
这是我觉得仿真对比最大的局限性。Gazebo的激光sensor默认不模拟测距噪声,近距离障碍物也不会产生真实激光的拖尾、跳变、毛刺。真实的激光雷达在扫地机器人、AGV上会感受到地面反射干扰、透明玻璃穿透、动态物体遮挡,这些在仿真里通通不存在。
为了弥补,我建议在仿真中给激光数据主动加一点高斯噪声。可以在URDF里给ray sensor的noise参数加少量值:
<noise> <type>gaussian</type> <mean>0.0</mean> <stddev>0.01</stddev> </noise>这个0.01米的噪声不算大,但足够让算法不能“刷数据”。如果仿真里不加噪声,算法精度跑得再漂亮,上实车之后都会被打回原形。我在仿真对比结束后把激光噪声从0加到0.03米再跑了一遍,发现slam_toolbox和Cartographer都还稳得住,但视觉方案基本全崩,这个结论对实际选型很有价值。
5.4 仿真世界模型的文件路径坑
如果你从网上下载了别人的world文件,经常遇到Gazebo报错找不到模型。这个问题九成出在.gazebo/models路径没配对。
检查方法:
echo $GAZEBO_MODEL_PATH如果为空或者没有包含你的模型目录,Gazebo里的模型就加载不出来。在launch文件里可以手动加:
os.environ['GAZEBO_MODEL_PATH'] = os.path.expanduser('~/.gazebo/models')这种环境变量问题在仿真里非常普遍,但每次遇到都有人卡很久,所以单独拿出来说一下。
6. 从仿真对比到实车选型的一点思考
整套流程跑下来,我对这几个SLAM方案的理解比单纯看论文要深得多。最大的感触是:仿真对比的作用是帮你排除明显不合适的方案,而不是直接选出最终方案。
以我这边的项目为例,算力受限这个前提直接把Cartographer几乎排除掉了——它精度确实好,但在ARM平台上那种CPU占用率,留给导航和业务逻辑的资源就很少了。slam_toolbox的精度虽然差一点,但胜在轻量、稳定、容易调参,实车跑起来反而容易驾驭。
另外,如果你在ROS2里折腾这套流程,尽量保持环境一致。我强烈建议你用Docker或者至少把环境依赖版本记录下来,因为隔一个月再回头看,可能装了个新包就把老包的依赖顶掉了,整套对比流程又要重跑一遍。
最后分享一个提升效率的小技巧。把录制bag、回放、启动SLAM、保存地图、跑EVO这五步写成一组shell脚本,每次对比只需要更换启动的算法包,然后跑脚本就行。否则手动输入这些命令,一个算法三分钟,三个算法半小时,中间还可能输错某个参数,结果就废了。我自己就是吃了这个亏之后才把流程脚本化的。
回头再看,这套基于ROS2的SLAM算法对比仿真,核心价值不在于得出“谁最强”的结论,而在于建立了一套可重复、可量化、可复现的评测流程。以后项目传感器换了、场景变了,只要把bag重新录一遍,所有的评测脚本都能直接复用。这比任何一次性的对比结果都更值得投入时间。
本文还有配套的精品资源,点击获取