“SLAM 工具箱”是一个基于“SLAM”(Simultaneous Localization and Mapping)技术实现的库。

SLAM是指移动机器人或无人机等在未知环境中进行“自我定位”的同时,“生成地图”的技术。通过传感器(如激光雷达、视觉)同步构建环境地图并确定机器人自身位置,为后续导航和定位提供核心数据。

例如:SLAM最早用于军事核潜艇的定位与地图构建 。在未知环境中,SLAM技术使机器人能够自主导航,成为移动扫描和自动驾驶的基础。

SLAM、Navigation 、Odom、TF的关系如下:

SLAM 是核心,提供地图和定位;Navigation 依赖SLAM的地图和定位结果;Odom 提供快速但有漂移的局部定位;TF 负责坐标系间的转换,确保系统一致性。

“SLAM ToolBox” 包含以下两种模式:

在线模式:机器人行驶的同时实时生成地图。这种模式下可以进行终身建图,环路闭合和地图修正会自动进行。

离线模式:使用预先记录的传感器数据进行离线地图生成和修正。

这次我们使用“在线模式”。基于以下信息,我们将生成的地图发布到 /map,将估计的自身位置发布到 /tf (map→odom) 和 /pose。 

/lidar : LiDAR 信息
/tf:odom 与 base_link 的关系
/tf_static:base_link 与 lidar 的关系
/odom : odometry 信息

这次我们使用的是第三章中的simple_robot_lidar.sdf

gz sim simple_robot_lidar.sdf

然后另外打开终端将topic /cmd_vel、/lidar、/odom、/tf桥接到ROS2

$ ros2 run ros_gz_bridge parameter_bridge /cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist

$ ros2 run ros_gz_bridge parameter_bridge /lidar@sensor_msgs/msg/LaserScan@gz.msgs.LaserScan

$ ros2 run ros_gz_bridge parameter_bridge /model/simple_robot/tf@tf2_msgs/msg/TFMessage[gz.msgs.Pose_V --ros-args -r /model/simple_robot/tf:=/tf

~$ ros2 run ros_gz_bridge parameter_bridge /model/simple_robot/odometry@nav_msgs/msg/Odometry@gz.msgs.Odometry --ros-args -r /model/simple_robot/odometry:=/odom

然后另开一个终端,发布robot和lidar之间的静态相对关系

ros2 run tf2_ros static_transform_publisher 0.2 0 0.15 0 0 0 simple_robot/base_link simple_robot/base_link/lidar

然后开一个终端,启动teleop_twist_keyboard

$ ros2 run teleop_twist_keyboard teleop_twist_keyboard

安装slam_toolbox

$ sudo apt update$ sudo apt install ros-jazzy-slam-toolbox

创建slam配置文件

use_sim_time:这里配置ROS2节点使用的clock是仿真时间,仿真环境使用的是自己的时间/clock,仿真时间与实时时间不同,可以改变时间或者暂停,当希望避免仿真与 ROS 节点之间出现时间不一致时使用。

这次我们使用toolbox的online_async_launch.py,然后启动

ros2 launch slam_toolbox online_async_launch.py slam_params_file:=slam_toolbox_params.yaml

然后分别确认topic /map /pose的输出

$ ros2 topic echo /map

$ ros2 topic echo /pose

最后启动数据可视化工具rviz2

$ rviz2

在前面章节设置的基础上添加map,点击左下角Add,然后选择By topic,选择/map

然后选择By display type,选择PoseWithCovariance

最后我们回到操作终端,移动小车,会看到小车移动在RVIZ2的实时数据仿真的情况。

红色箭头表示通过 /odom 发布的位置里程计的自定位估计,蓝色箭头表示通过 /pose 发布 SLAM 的自定位估计。

然后移动小车,让小车雷达扫描整张地图

最后我们保存地图

ros2 service call /slam_toolbox/save_map slam_toolbox/srv/SaveMap "{name: {data: 'demo_map'}}"

地图最终保存在我们启动online_async_launch.py的目录,地图元数据demo_map.yaml

demo_map.pgm使用灰度图表示每个像素的占据状态。

如果碰到返回消息:[async_slam_toolbox_node-1] Package 'nav2_map_server' not founda str

这个时候需要安装nav2-map-server

$ sudo apt install ros-jazzy-nav2-map-server

同样的我们也可以保存posegraph

demo_map.posegraph:一种用图结构表示机器人移动过的每个位置(节点)以及这些位置之间的约束(边)的结构。

demo_map.data:各个节点获取的激光扫描等传感器数据以及用于机器人自定位估计的里程计数据。

同样的,我们也可以将上述的操作转换成一个launch.py文件

from launch import LaunchDescriptionfrom launch.actions import ExecuteProcessdef generate_launch_description():    return LaunchDescription([        # Gazebo        ExecuteProcess(            cmd=['gz', 'sim', 'simple_robot_lidar.sdf'],            output='log',            log_cmd=True        ),        # rviz2        ExecuteProcess(            cmd=['rviz2'],            output='log',            log_cmd=True        ),        ExecuteProcess(            cmd=[                'ros2', 'run', 'ros_gz_bridge', 'parameter_bridge',                '/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist'            ],            output='log',            log_cmd=True        ),        ExecuteProcess(            cmd=[                'ros2', 'run', 'ros_gz_bridge', 'parameter_bridge',                '/lidar@sensor_msgs/msg/LaserScan@gz.msgs.LaserScan'            ],            output='log',            log_cmd=True        ),        ExecuteProcess(            cmd=[                'ros2', 'run', 'ros_gz_bridge', 'parameter_bridge',                '/model/simple_robot/tf@tf2_msgs/msg/TFMessage[gz.msgs.Pose_V',                '--ros-args', '-r', '/model/simple_robot/tf:=/tf'            ],            output='log',            log_cmd=True        ),        ExecuteProcess(            cmd=[                'ros2', 'run', 'ros_gz_bridge', 'parameter_bridge',                '/model/simple_robot/odometry@nav_msgs/msg/Odometry@gz.msgs.Odometry',                '--ros-args', '-r', '/model/simple_robot/odometry:=/odom'            ],            output='log',            log_cmd=True        ),        # teleop_twist_keyboard        ExecuteProcess(            cmd=['ros2', 'run', 'teleop_twist_keyboard', 'teleop_twist_keyboard'],            output='screen',            prefix='xterm -e'        ),        # static_transform_publisher        ExecuteProcess(            cmd=[                'ros2', 'run', 'tf2_ros', 'static_transform_publisher',                '0.2', '0', '0.15', '0', '0', '0',                'simple_robot/base_link', 'simple_robot/base_link/lidar'            ],            output='log',            log_cmd=True        ),        # slam_toolbox        ExecuteProcess(            cmd=[                'ros2', 'launch', 'slam_toolbox', 'online_async_launch.py',                'slam_params_file:=slam_toolbox_params.yaml'            ],            output='log',            log_cmd=True        ),    ])

最后补充说明下online_async_launch.py


$ cat /opt/ros/jazzy/share/slam_toolbox/config/mapper_params_online_async.yamlslam_toolbox:  ros__parameters:
    # Plugin params (配置优化求解器插件)    solver_plugin: solver_plugins::CeresSolver  # 使用 Ceres 求解器 [[7]]    ceres_linear_solver: SPARSE_NORMAL_CHOLESKY  # 线性方程求解方法 [[7]]    ceres_preconditioner: SCHUR_JACOBI  # 预处理方法 [[7]]    ceres_trust_strategy: LEVENBERG_MARQUARDT  # 信赖域策略 [[7]]    ceres_dogleg_type: TRADITIONAL_DOGLEG  # Dogleg 算法类型 [[7]]    ceres_loss_function: None  # 损失函数类型(None 表示不使用) [[7]]
    # ROS Parameters (ROS 相关参数)    odom_frame: odom  # 里程计坐标系名称 [[7]]    map_frame: map  # 地图坐标系名称 [[7]]    base_frame: base_footprint  # 机器人基坐标系名称 [[7]]    scan_topic: /scan  # 激光雷达数据主题 [[7]]    use_map_saver: true  # 启用地图保存功能 [[3]][[4]]    mode: mapping #localization  # 模式选择:mapping(建图) 或 localization(定位) [[5]]
    # 地图加载相关参数(互斥选项)    # map_file_name: test_steve  # 预加载地图文件名(需与 map_start_at_dock 互斥) [[7]]    # map_start_pose: [0.0, 0.0, 0.0]  # 初始位姿(x, y, theta) [[7]]    # map_start_at_dock: true  # 从充电桩位置开始建图 [[7]]
    debug_logging: false  # 是否启用调试日志 [[7]]    throttle_scans: 1  # 扫描数据处理频率(1 表示全部处理) [[7]]    transform_publish_period: 0.02  # TF 发布周期(s),为 0 时不发布里程计 [[7]]    map_update_interval: 5.0  # 地图更新间隔(s) [[7]]    resolution: 0.05  # 地图分辨率(m/pixel) [[5]]    max_laser_range: 20.0  # 激光最大范围(m,用于栅格地图生成) [[7]]    minimum_time_interval: 0.5  # 最小时间间隔(s) [[7]]    transform_timeout: 0.2  # TF 超时时间(s) [[7]]    tf_buffer_duration: 30.0  # TF 缓存持续时间(s) [[7]]    stack_size_to_use: 40000000  # 序列化大地图所需的栈大小 [[7]]    enable_interactive_mode: true  # 启用交互模式(可视化调试) [[7]]
    # General Parameters (通用参数)    use_scan_matching: true  # 启用扫描匹配 [[7]]    use_scan_barycenter: true  # 使用扫描重心对齐 [[7]]    minimum_travel_distance: 0.5  # 最小移动距离(m)触发更新 [[7]]    minimum_travel_heading: 0.5  # 最小转向角度(rad)触发更新 [[7]]    scan_buffer_size: 10  # 扫描缓冲区大小 [[7]]    scan_buffer_maximum_scan_distance: 10.0  # 缓冲区最大扫描距离(m) [[7]]    link_match_minimum_response_fine: 0.1  # 精细匹配最小响应值 [[7]]    link_scan_maximum_distance: 1.5  # 扫描链接最大距离(m) [[7]]    loop_search_maximum_distance: 3.0  # 回环检测最大距离(m) [[7]]    do_loop_closing: true  # 启用回环闭合 [[7]]    loop_match_minimum_chain_size: 10  # 回环匹配最小链长度 [[7]]    loop_match_maximum_variance_coarse: 3.0  # 粗匹配最大方差 [[7]]    loop_match_minimum_response_coarse: 0.35  # 粗匹配最小响应值 [[7]]    loop_match_minimum_response_fine: 0.45  # 精细匹配最小响应值 [[7]]
    # Correlation Parameters - Correlation Parameters (相关性参数)    correlation_search_space_dimension: 0.5  # 相关性搜索空间维度(m) [[7]]    correlation_search_space_resolution: 0.01  # 搜索空间分辨率(m) [[7]]    correlation_search_space_smear_deviation: 0.1  # 搜索空间模糊偏差 [[7]]
    # Correlation Parameters - Loop Closure Parameters (回环闭合相关性参数)    loop_search_space_dimension: 8.0  # 回环搜索空间维度(m) [[7]]    loop_search_space_resolution: 0.05  # 回环搜索空间分辨率(m) [[7]]    loop_search_space_smear_deviation: 0.03  # 回环搜索空间模糊偏差 [[7]]
    # Scan Matcher Parameters (扫描匹配器参数)    distance_variance_penalty: 0.5  # 距离方差惩罚系数 [[7]]    angle_variance_penalty: 1.0  # 角度方差惩罚系数 [[7]]    fine_search_angle_offset: 0.00349  # 精细搜索角度偏移(rad) [[7]]    coarse_search_angle_offset: 0.349  # 粗搜索角度偏移(rad) [[7]]    coarse_angle_resolution: 0.0349  # 粗角度分辨率(rad) [[7]]    minimum_angle_penalty: 0.9  # 最小角度惩罚 [[7]]    minimum_distance_penalty: 0.5  # 最小距离惩罚 [[7]]    use_response_expansion: true  # 启用响应扩展 [[7]]    min_pass_through: 2  # 最小通过次数 [[7]]    occupancy_threshold: 0.1  # 占据阈值(0-1) [[7]]
插件参数 :定义了 SLAM 优化算法的底层实现(如 Ceres 求解器),直接影响计算效率和精度 

ROS 参数:配置坐标系、话题名称及核心功能开关(如 use_map_saver控制地图保存功能) 

地图初始化 :支持从指定文件或初始位姿启动,但 map_file_name和map_start_at_dock互斥 

性能调优 :通过 map_update_interval、resolution等参数平衡实时性和地图质量 

回环检测 :通过距离、方差、响应值等阈值控制回环闭合的灵敏度和鲁棒性。

通过以下命令确认正在发布的话题的可视化结构图

ros2 run tf2_tools view_frames

最后总结一下:

里程计与 SLAM 自定位估计的比较

基于里程计的自定位估计

优势:

1、高速・实时性 :通过简单的算法即可实现快速定位,计算资源需求低。

2、计算资源较少 :适用于嵌入式设备或低功耗场景。

缺点:

1、累积误差(漂移)问题 :随着时间推移,误差会逐渐累积,导致定位精度下降(例如轮式里程计因打滑或IMU的噪声干扰)。

2、无法生成地图或利用环境信息进行误差校正 :仅依赖传感器数据(如车轮编码器、IMU、相机),缺乏外部环境特征的反馈修正机制。

常用传感器:

轮式里程计 (车轮编码器)

IMU (加速度传感器、陀螺仪传感器)

视觉里程计 (基于相机的估计,如单目/立体视觉)

基于SLAM的自定位估计

优势:

1、利用环境地图大幅抑制漂移 :通过同步构建地图与定位,结合环境特征(如地标、激光雷达点云)修正累积误差。

2、未知环境中自主建图与定位 :即使在没有先验地图的情况下,也能实时构建环境地图并估计机器人位姿。

3、多传感器融合 :结合相机、LiDAR、IMU等传感器,获取更丰富的环境信息(例如视觉SLAM利用语义特征优化位姿估计)。

缺点:

1、计算资源需求高 :尤其在实时SLAM中需要高性能CPU/GPU支持(如3D LiDAR数据处理)。

2、实现复杂 :涉及非线性优化、数据关联、回环检测等算法,开发难度较大。

常用传感器:

相机 (单色、立体视觉)

LiDAR (2D/3D扫描仪)

IMU (可选集成,用于提升动态场景下的鲁棒性)

总结:

里程计适合对实时性要求高、计算资源有限的场景,但存在累积误差问题;而SLAM通过融合环境地图与传感器数据,能够实现高精度的长期定位,但需付出更高的计算成本和算法复杂度。

Logo

立足具身智能前沿赛道,致力于搭建全球化、开源化、全栈式技术交流与实践共创平台。

更多推荐