3 · 第一个导航地图:使用SLAM Toolbox构建静态地图

说实话,很多新手一上来就急着调路径规划,却忽略了最基础的东西——地图。没有一张靠谱的静态地图,导航就像蒙着眼走路。我个人习惯先把地图建好、存好,再谈其他。这一章我们就用 SLAM Toolbox 把第一张地图做出来,顺便聊聊地图格式和保存的那些坑。

SLAM Toolbox 在线激光SLAM 地图保存(map_saver) 占用栅格 (OccupancyGrid) YAML + pgm / png 核心流程:实时建图 → 栅格地图 → 序列化保存

3.1 为什么选SLAM Toolbox?

ROS2里建图工具有好几个,但SLAM Toolbox是我用得最顺手的。它不像gmapping那样只做2D激光SLAM,也不像cartographer那样配置复杂。说白了,它就是为Nav2量身定做的——开箱即用,还支持生命周期节点。我在项目里换过三四种建图方案,最后都回到了它身上。

核心优势: 支持异步/同步模式、地图序列化、重定位、以及保存/加载地图的完整管线。你不需要额外装一堆包。

3.2 启动建图:一条命令就够了

假设你已经有了激光雷达数据(或者仿真环境),启动SLAM Toolbox只需要一个launch文件。我个人习惯把参数单独放一个yaml,这样调起来方便。

# slam_toolbox_launch.py (片段)
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='slam_toolbox',
            executable='async_slam_toolbox_node',
            name='slam_toolbox',
            output='screen',
            parameters=['./slam_params.yaml']
        )
    ])

然后运行:

ros2 launch your_package slam_toolbox_launch.py

嗯,就这么简单。你会看到终端里不断输出图优化信息,同时RViz2里逐渐出现地图轮廓。我记得第一次跑通的时候,看着地图一点点展开,还是挺有成就感的。

如果你用仿真,记得先启动机器人模型和激光驱动。SLAM Toolbox默认订阅 /scan 话题,如果没数据它就会干等。

3.3 地图格式:占用栅格地图

建图过程中,SLAM Toolbox内部维护的是一张 占用栅格地图 (OccupancyGrid)。每个格子有三种状态:占用(100)、空闲(0)、未知(-1)。你想想看,这其实就是一张灰度图——黑色是障碍,白色是自由空间,灰色是未探索区域。

栅格值含义可视化颜色
0空闲 (free)白色
100占用 (occupied)黑色
-1未知 (unknown)灰色

为什么用这种格式?因为导航时代价地图需要快速查询某个位置是否可通行。栅格结构简单,计算快。我在实际部署时发现,地图分辨率一般设 0.05米/像素,太粗了导航不精细,太细了文件大且容易受噪声影响。

3.4 保存地图:map_saver_cli 与 map_saver_server

地图建好了,不保存等于白干。SLAM Toolbox 提供了两种保存方式:

  • 命令行工具 map_saver_cli —— 适合手动保存
  • 服务端 map_saver_server —— 适合自动化流程

我最常用的是命令行,简单直接:

ros2 run nav2_map_server map_saver_cli -f my_map

这条命令会生成两个文件:my_map.yaml 和 my_map.pgm(或 .png)。YAML文件里记录了地图的元信息,比如分辨率、原点、占用阈值等。pgm就是那张灰度图。

我曾经犯过一个错:保存地图时忘了设置 -f 参数,结果文件被覆盖了。后来我每次保存都会加上时间戳,比如 my_map_20250321。养成好习惯,避免悲剧。

3.5 YAML 参数详解

打开生成的 my_map.yaml,内容大概长这样:

image: my_map.pgm
resolution: 0.050000
origin: [-10.0, -10.0, 0.000000]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.196
参数说明建议值
image地图图片文件名(相对路径)与yaml同目录
resolution每个像素代表的实际距离(米)0.05
origin地图左下角在世界坐标系中的位姿 (x, y, yaw)根据建图范围定
negate是否反转黑白(0不反转,1反转)0
occupied_thresh像素灰度值高于此阈值视为占用0.65
free_thresh像素灰度值低于此阈值视为空闲0.196

这里有个小细节:occupied_thresh 和 free_thresh 之间的区域会被当作未知区域。如果你发现地图里出现大量灰色噪点,可以适当调高 free_thresh 或降低 occupied_thresh。我一般保持默认,除非传感器噪声特别大。

3.6 避坑指南:地图原点与对齐

很多新手保存地图后,加载到Nav2里发现机器人位置对不上。原因往往是 origin 设置不合理。SLAM Toolbox 建图时默认把地图原点设在机器人起始位置附近,但如果你手动修改了 origin,一定要保证和实际建图范围匹配。

一个小技巧:保存地图前,在RViz里用「2D Pose Estimate」大致看一下地图边界,然后估算 origin。或者直接用 map_saver_cli 的默认值,它自动从SLAM节点获取当前地图原点。

另外,如果你在仿真中建图,记得把地图保存到 ~/map 这样的独立目录,别跟代码混在一起。我习惯在项目里建一个 maps/ 文件夹,所有地图都放里面,方便管理。

3.7 验证地图:加载到Nav2

地图保存好了,怎么确认它没问题?最简单的方法是用 map_server 加载看看:

ros2 run nav2_map_server map_server --ros-args -p yaml_filename:=my_map.yaml

然后在另一个终端启动 RViz2,添加 Map 显示,选择话题 /map。如果地图显示正常,而且栅格黑白分明,那就说明成功了。我每次都会做这个验证,避免后期导航时才发现地图损坏。

总结一下本章核心: SLAM Toolbox 建图 → 占用栅格地图 → map_saver_cli 保存为 yaml+pgm → 检查地图原点与阈值。这是后续所有导航调参的基础,务必亲手走一遍。