多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

ROS2机器人竞赛实战:从SLAM建图到Nav2导航全流程解析

ROS2机器人竞赛实战:从SLAM建图到Nav2导航全流程解析 每年机器人竞赛的备赛季团队里最常问的问题几乎都是同一类导航跑不稳、传感器参数不会调、机械臂抓取总是偏移、仿真能跑实机却动不了。加上 ROS2 版本在快速迭代、不同竞赛平台的规则差异又大网上资料虽然不少但大多只讲某一个环节很难直接组成一套能从头跑到尾的方案。 这篇文章我准备用一套相对完整的技术路线把机器人竞赛里最常用的 ROS2 导航、SLAM 建图、机器人仿真、以及工业机器人现场调试问题串起来。当中会包含可以直接复制的配置和代码思路也会整理备赛过程中最容易踩的坑。无论你是刚接触机器人竞赛的新手还是已经在做实机联调的老队员都可以拿这篇当备赛手册来用。1. 背景与核心概念1.1 机器人竞赛到底在比什么国内常见的机器人竞赛种类很多从全国大学生机器人大赛 ROBOCO N、RoboMaster到中国机器人大赛、中国机器人及人工智能大赛、以及各类企业举办的专项赛任务形式差异很大。但把任务拆到技术层面核心能力基本集中在以下几类移动能力底盘运动控制、循迹、避障、自主导航。感知能力摄像头视觉识别、激光雷达建图、里程计融合、目标检测。操作能力机械臂正逆解、路径规划、抓取与放置。智能决策任务调度、状态机、行为树、多机协同。竞赛拿高分的关键在于系统集成而不是单一技术点。很多团队的问题是单个模块都能跑但放到一台实物机器人上传感器时间戳对不上、话题频率不一致、底盘响应延迟整个系统就崩了。1.2 ROS/ROS2 在竞赛中的位置ROSRobot Operating System和 ROS2 是当前机器人竞赛中使用最广泛的软件框架。它提供的核心能力包括节点间通信话题Topic、服务Service、动作Action。硬件抽象统一驱动程序接口降低传感器和底盘适配成本。工具链可视化工具 RViz、仿真环境 Gazebo/Webots、日志工具。生态导航栈 Nav2、SLAM 工具库、机械臂 MoveIt、视觉处理 OpenCV 等。在 ROS2 出现后越来越多的竞赛平台开始切换到 ROS2主要原因是 ROS1 在多机通信、实时性、安全性方面有明显短板。如果你所在的队伍刚从 ROS1 迁移过来建议先把以下对应关系理清楚概念ROS1ROS2通信中间件TCPROS/UDPROSDDS节点管理roscoreDiscovery 自动发现启动方式roslaunchros2 launch参数服务rosparamParameters节点内消息记录rosbagros2 bag编译工具catkincolcon1.3 竞赛机器人的常见技术栈我整理了一个相对通用的竞赛机器人技术栈大多数比赛项目都可以在这个结构上做裁剪传感器层激光雷达、深度相机、IMU、编码器、摄像头 驱动层底盘的 ROS2 驱动节点、机械臂驱动 感知层SLAM 建图、定位、目标检测、颜色识别 决策层状态机、行为树、路径规划 执行层速度指令下发、机械臂抓取指令、舵机控制这套结构的好处是每一层职责清楚出问题时可以快速定位。比如导航飘了问题可能在传感器层激光雷达数据异常也可能在执行层底盘速度响应不一致而不需要把整个系统都翻一遍。2. 环境准备与版本说明2.1 操作系统与 ROS2 版本选择版本需要根据你的项目实际情况调整我以目前竞赛中使用最广泛的组合为例重点演示配置思路。当前 ROS2 较稳定的版本有ROS2 Humble对应 Ubuntu 22.04LTS 版本社区资料最多推荐优先选择。ROS2 IronUbuntu 22.04中间版本不建议长期项目使用。ROS2 Jazzy对应 Ubuntu 24.04新项目可以尝试但部分第三方包可能没有适配。如果你所在的竞赛平台官方提供的是 ROS1 接口也不要慌通过 ros1_bridge 可以做过渡但长期维护成本较高。2.2 安装 ROS2示例为 Humblesudo apt update sudo apt install locales sudo locale-gen en_US en_US.UTF-8 sudo apt install software-properties-common sudo add-apt-repository universe sudo apt update sudo apt install curl -y sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo deb [arch$(dpkg --print-architecture) signed-by/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release echo $UBUNTU_CODENAME) main | sudo tee /etc/apt/sources.list.d/ros2.list /dev/null sudo apt update sudo apt upgrade -y sudo apt install ros-humble-desktop -y安装完成后在.bashrc中加入source /opt/ros/humble/setup.bash如果你使用 zsh则对应修改为source /opt/ros/humble/setup.zsh2.3 安装仿真与建图导航组件竞赛备赛阶段大部分算法验证都建议先在仿真中完成。常用组件如下sudo apt install ros-humble-gazebo-ros-pkgs -y sudo apt install ros-humble-cartographer ros-humble-cartographer-ros -y sudo apt install ros-humble-slam-toolbox -y sudo apt install ros-humble-navigation2 ros-humble-nav2-bringup -y sudo apt install ros-humble-turtlebot3-gazebo -y为什么要同时安装 Cartographer 和 slam_toolboxCartographer 在复杂环境下的建图效果比较好但占用资源更高slam_toolbox 轻量适合中小场地后续还要支持地图重定位。竞赛中场地相对固定两套方案都值得提前测试选择一个更适合现场环境的。2.4 示例项目结构本文后面的实战案例建议使用下面这个工作区结构ros2_ws/ ├── src/ │ ├── contest_bot/ │ │ ├── urdf/ │ │ ├── launch/ │ │ ├── config/ │ │ ├── src/ │ │ └── package.xml │ └── contest_bot_nav/ │ ├── config/ │ ├── launch/ │ ├── maps/ │ └── package.xml └── build/ install/ log/这样的结构简洁清晰便于多人在同一台机器上协作。3. 核心技术与原理拆解3.1 ROS2 的基础通信机制竞赛机器人系统往往有多个节点各节点之间通过以下三种方式通信话题单向数据流比如传感器数据、速度指令。服务请求-响应模式适合调用一次性操作比如打开/关闭某个设备。动作适合耗时任务能持续反馈进度并支持取消比如导航到目标点。下面是一个简单的话题发布订阅示例用于理解 ROS2 节点关系。订阅端#!/usr/bin/env python3 import rclpy from rclpy.node import Node from std_msgs.msg import Float32 class LidarSubscriber(Node): def __init__(self): super().__init__(lidar_distance_subscriber) self.subscription self.create_subscription( Float32, front_distance, self.listener_callback, 10) def listener_callback(self, msg): self.get_logger().info(f距离: {msg.data:.2f} m) def main(argsNone): rclpy.init(argsargs) node LidarSubscriber() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ __main__: main()发布端#!/usr/bin/env python3 import rclpy from rclpy.node import Node from std_msgs.msg import Float32 import random class LidarPublisher(Node): def __init__(self): super().__init__(lidar_distance_publisher) self.publisher self.create_publisher(Float32, front_distance, 10) self.timer self.create_timer(0.1, self.publish_distance) def publish_distance(self): msg Float32() msg.data random.uniform(0.2, 3.0) self.publisher.publish(msg) def main(argsNone): rclpy.init(argsargs) node LidarPublisher() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ __main__: main()这个话题名front_distance只是示意真实项目中需要根据机器人硬件定义统一的接口规范不要让不同传感器各自起名否则后续做感知融合会很痛苦。3.2 SLAM 建图的实现思路SLAMSimultaneous Localization and Mapping解决的问题是让机器人在未知环境中一边构建地图一边确定自身位置。竞赛中常用的是 2D 激光 SLAM因为激光雷达受光照影响小建图稳定。3.2.1 slam_toolbox 建图先启动底盘与激光雷达节点再启动 slam_toolboxros2 launch contest_bot robot.launch.py ros2 launch slam_toolbox online_async_launch.py slam_params_file:src/contest_bot_nav/config/slam_toolbox_config.yamlslam_toolbox_config.yaml的关键参数slam_toolbox: ros__parameters: odom_frame: odom map_frame: map base_frame: base_link scan_topic: /scan mode: mapping resolution: 0.05 max_laser_range: 10.0这里的odom、map、base_link三个坐标系关系要清楚map全局地图坐标系固定不变。odom里程计坐标系以机器人的起点为原点通常由底盘里程计持续累积。base_link机器人本体坐标系通常位于底盘中心。在 TF 树坐标变换树中map到odom的变换由 SLAM 系统维护odom到base_link的变换由里程计节点维护。如果 TF 树缺失或跳变导航系统会直接报错。3.2.2 保存地图建图完成后保存地图ros2 run nav2_map_server map_saver_cli -f src/contest_bot_nav/maps/contest_map保存后会生成contest_map.pgm和contest_map.yaml两个文件。.yaml文件内容如下image: contest_map.pgm resolution: 0.050000 origin: [-10.000000, -10.000000, 0.000000] negate: 0 occupied_thresh: 0.65 free_thresh: 0.1963.3 Nav2 自主导航的参数与调优导航是竞赛中最重要的技能之一。Nav2 的核心是“全局规划 局部规划 代价地图”三层结构。3.3.1 导航启动配置一个典型的导航启动 launch 文件思路如下# 文件路径src/contest_bot_nav/launch/navigation.launch.py from launch import LaunchDescription from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription from launch_ros.actions import Node from launch.launch_description_sources import PythonLaunchDescriptionSource from ament_index_python.packages import get_package_share_directory import os def generate_launch_description(): nav2_bringup_dir get_package_share_directory(nav2_bringup) nav2_params os.path.join( get_package_share_directory(contest_bot_nav), config, nav2_params.yaml) map_file os.path.join( get_package_share_directory(contest_bot_nav), maps, contest_map.yaml) return LaunchDescription([ DeclareLaunchArgument(map, default_valuemap_file), IncludeLaunchDescription( PythonLaunchDescriptionSource( os.path.join(nav2_bringup_dir, launch, bringup_launch.py)), launch_arguments{map: map_file, use_sim_time: True}.items(), ), Node( packagerviz2, executablerviz2, arguments[-d, os.path.join(nav2_bringup_dir, rviz, nav2_default_view.rviz)] ), ])3.3.2 代价地图参数说明在nav2_params.yaml中最重要的参数是膨胀半径和代价衰减方式costmap: global_costmap: robot_radius: 0.18 inflation_radius: 0.30 update_frequency: 2.0 publish_frequency: 2.0 local_costmap: robot_radius: 0.18 inflation_radius: 0.25 width: 3.0 height: 3.0 resolution: 0.05 update_frequency: 5.0 publish_frequency: 5.0这里的robot_radius是根据机器人底盘外形设置的如果是方形底盘可用footprint替代footprint: [[-0.20, -0.20], [-0.20, 0.20], [0.20, 0.20], [0.20, -0.20]]注意inflation_radius设得太大会导致狭窄通道无法规划出路径设得太小又会让机器人离障碍物太近。竞赛场地空间有限建议在仿真中测试多种参数组合。3.4 机械臂与模拟仿真平台选择很多竞赛任务包含机械臂抓取。如果你选择的机械臂基于 MoveIt可以通过配置文件快速生成运动规划包。仿真方面主流选择有 Gazebo、Webots、CoppeliaSimV-REP。平台选择建议Gazebo与 ROS2 集成最成熟适合多传感器仿真但对 GPU 要求高。Webots物理引擎稳定适合快速验证运动学算法。CoppeliaSim内置机器人模型多适合复杂机构仿真。如果队伍还处于算法验证阶段优先选择 Gazebo 或 Webots它们与 ROS2 的接口资料最多。3.5 工业机器人竞赛中的调试切入点部分竞赛也会使用工业机器人平台比如 ABB、KUKA、发那科。这些平台的调试思路与 ROS2 机器人差异较大更依赖示教器和 PLC 逻辑。常见的调试问题包括ABB 机器人条件等待卡顿通常是等待条件逻辑过于复杂或信号冲突导致循环等待。建议简化等待条件增加超时保护。ABB 机器人触发中断后回到原断点需要在中断程序中恢复现场并明确跳转指令的目标位置否则可能出现位置偏移。KUKA 备份还原KUKA 的备份文件包含机器人系统、程序、配置文件还原时要注意软件版本一致性混用版本会导致系统启动异常。发那科机器人干涉区信号触发干涉区 DI 信号主要是为了避免两个设备在同一空间中碰撞信号触发后需要确认是逻辑冲突还是机械未到位不要直接屏蔽安全信号。这些工业机器人的调试经验往往需要在实际设备上反复踩坑建议备赛时优先用官方离线编程软件做逻辑验证再上实机调试。4. 完整实战案例基于 ROS2 的竞赛机器人导航项目下面用一个简单的差速机器人导航案例把前面讲到的建图、定位、导航串起来。这个案例可以在仿真环境里直接运行方便没有实机的队伍验证。4.1 创建项目结构mkdir -p ~/ros2_ws/src/contest_bot cd ~/ros2_ws/src/contest_bot mkdir -p urdf launch config src maps创建 Python 包cd ~/ros2_ws ros2 pkg create --build-type ament_python contest_bot_nav --dependencies rclpy launch launch_ros4.2 创建机器人描述文件先用一个简单的 URDF 描述机器人底盘和激光雷达!-- 文件路径src/contest_bot/urdf/contest_bot.urdf -- robot namecontest_bot xmlns:xacrohttp://www.ros.org/wiki/xacro link namebase_link visual geometry box size0.36 0.28 0.10/ /geometry /visual collision geometry box size0.36 0.28 0.10/ /geometry /collision inertial mass value2.0/ inertia ixx0.03 iyy0.04 izz0.05 ixy0 ixz0 iyz0/ /inertial /link link namelaser_link visual geometry cylinder radius0.04 length0.04/ /geometry /visual collision geometry cylinder radius0.04 length0.04/ /geometry /collision inertial mass value0.05/ inertia ixx0.0001 iyy0.0001 izz0.0001 ixy0 ixz0 iyz0/ /inertial /link joint namelaser_joint typefixed parent linkbase_link/ child linklaser_link/ origin xyz0.0 0.0 0.12 rpy0 0 0/ /joint link nameleft_wheel visual geometry cylinder radius0.04 length0.03/ /geometry /visual inertial mass value0.1/ inertia ixx0.0001 iyy0.0001 izz0.0001 ixy0 ixz0 iyz0/ /inertial /link joint nameleft_wheel_joint typecontinuous parent linkbase_link/ child linkleft_wheel/ origin xyz0.0 0.14 0.0 rpy0 0 0/ axis xyz0 0 1/ /joint link nameright_wheel visual geometry cylinder radius0.04 length0.03/ /geometry /visual inertial mass value0.1/ inertia ixx0.0001 iyy0.0001 izz0.0001 ixy0 ixz0 iyz0/ /inertial /link joint nameright_wheel_joint typecontinuous parent linkbase_link/ child linkright_wheel/ origin xyz0.0 -0.14 0.0 rpy0 0 0/ axis xyz0 0 1/ /joint /robot这里把左右轮子的旋转轴设置成xyz0 0 1实际应用时要根据你的底盘结构改成正确的轮轴方向。4.3 编写底盘驱动节点为了让机器人能够在 ROS2 中运动需要一个底盘驱动节点把/cmd_vel话题中的速度指令转换为轮子转动。下面是一个简化版本# 文件路径src/contest_bot/contest_bot/chassis_controller.py import rclpy from rclpy.node import Node from geometry_msgs.msg import Twist from std_msgs.msg import Float32 import math class SimpleChassisController(Node): def __init__(self): super().__init__(simple_chassis_controller) self.subscription self.create_subscription( Twist, cmd_vel, self.cmd_vel_callback, 10) self.left_wheel_pub self.create_publisher(Float32, left_wheel_speed, 10) self.right_wheel_pub self.create_publisher(Float32, right_wheel_speed, 10) self.wheel_base 0.28 # 轮距 self.wheel_radius 0.04 def cmd_vel_callback(self, msg): linear msg.linear.x angular msg.angular.z left_speed (linear - angular * self.wheel_base / 2.0) / self.wheel_radius right_speed (linear angular * self.wheel_base / 2.0) / self.wheel_radius left_msg Float32() left_msg.data left_speed right_msg Float32() right_msg.data right_speed self.left_wheel_pub.publish(left_msg) self.right_wheel_pub.publish(right_msg) def main(argsNone): rclpy.init(argsargs) node SimpleChassisController() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ __main__: main()这是一个“差速驱动逆解”的例子核心公式就是左右轮速 线速度 ± 角速度 × 轮距 / 2再除以轮子半径。4.4 建图流程在仿真环境中启动机器人模型和底盘节点然后启动激光雷达接着启动 slam_toolbox。# 终端 1启动仿真与机器人 ros2 launch contest_bot gazebo.launch.py # 终端 2启动建图 ros2 launch slam_toolbox online_async_launch.py slam_params_file:src/contest_bot_nav/config/slam_toolbox_config.yaml # 终端 3启动 RViz 查看建图过程 rviz2在 RViz 中添加 Map 显示选择话题/map然后使用键盘控制机器人缓慢扫图ros2 run teleop_twist_keyboard teleop_twist_keyboard4.5 自主导航验证建图保存后启动 Nav2 进行导航。ros2 launch contest_bot_nav navigation.launch.py在 RViz 中点击“Nav2 Goal”并设置目标点观察机器人是否能规划路径并运动到目标位置。如果无法规划优先检查地图是否完整。机器人当前位姿是否出现在地图中。代价地图是否显示了膨胀区域。底盘是否正常发布 odom 坐标变换。5. 常见问题与排查思路机器人竞赛备赛调试阶段遇到的问题大多数不是算法难而是环境配置、通信时序和设备传感器异常。下面整理一份高频问题排查表。问题现象常见原因解决思路启动 launch 报包找不到环境变量未 source或包未编译成功重新执行colcon build并source install/setup.bashRViz 中机器人变形或位置错乱TF 树缺失或跳变运行ros2 run tf2_tools view_frames检查 TF 树激光雷达在 RViz 中无数据驱动节点未启动或话题名不对ros2 topic list查看话题确认/scan是否存在建图过程中地图漂移底盘里程计不准轮子打滑降低速度扫图检查码盘编码器标定系数导航规划不出路径膨胀半径过大或地图不完整调小inflation_radius重新扫图机器人撞到障碍物激光雷达安装位置偏移或代价地图更新太慢校准雷达外参提高 local costmap 更新频率仿真中机器人不动底盘驱动节点未启动或者 cmd_vel 话题无人订阅用ros2 topic hz /cmd_vel检查发布频率和内容多机通信时节点找不到ROS2 自动发现机制问题检查 /etc/hosts设置ROS_DOMAIN_ID为相同值实机运行与仿真结果差异大物理参数、传感器噪声不同必须做传感器标定并逐一对比话题数据除了表格中的问题这里再补充一个非常典型的排查场景就是“导航目标点一直显示 invalid”。这个问题的根本原因通常是全局代价地图里机器人的初始位姿没有正确设置或者机器人所在位置在地图中被判定为障碍物。解决方法是先用 RViz 的“2D Pose Estimate”手动调整初始位姿确保激光数据与地图墙体对齐再发布目标点。6. 最佳实践与工程建议6.1 仿真先行实机后置备赛时间有限不要把大量时间消耗在实机反复跑图上这既浪费时间又容易损坏设备。建议每个算法都先在仿真环境用统一场景验证确认逻辑正确后再搬到实机。实机上第一次跑时速度上限先设低一点确认安全后再逐步提高。6.2 统一消息接口和话题规范竞赛团队往往多人并行开发如果每个人给自己的传感器换一个话题名集成时必然混乱。建议在团队内规定统一命名规范激光雷达话题统一/scan。底盘速度指令统一/cmd_vel。里程计统一/odom。视觉话题按camera_xxx命名。所有传感器数据帧都加上时间戳。6.3 参数配置与代码分离不要把传感器参数、底盘 PID、代价地图参数硬编码在代码里。ROS2 的参数服务器完全可以支持动态参数配置。ros2 param set /local_costmap inflation_radius 0.30在调试阶段用命令行动态调整参数比修改代码后重新编译高效得多。确定最佳参数组合后再写回配置 YAML 文件。6.4 日志与频谱分析当机器人运行异常时第一件事不是改代码而是看数据。常用命令有ros2 topic list ros2 topic info /scan ros2 topic hz /scan ros2 topic echo /odom --once ros2 bag record /scan /odom /cmd_vel /tf用ros2 bag record录制现场数据比赛出现问题时可以回放分析这是排查偶发问题最有效的手段。6.5 安全边界优先电子竞赛和工程实践中安全永远排在第一位。无论是仿真还是实机必须设置最大线速度和角速度限制。激光雷达防碰撞距离阈值。机械臂运动范围软限位。急停按钮物理接线检查。生产环境或正式比赛中不要为追求速度而关闭安全限制。任何安全逻辑的调整都应该在测试环境验证、经过团队评审后再执行。7. 总结与下一步规划这篇内容从一个机器人竞赛团队的视角把 ROS2 环境搭建、SLAM 建图、Nav2 导航、仿真平台选择、工业机器人调试这几个关键环节串成了一条比较完整的学习路线。重点在于理解系统集成的思路而不是死记某一个命令。接下来建议按下面的顺序继续深入先用 TurtleBot3 或自定义 URDF 在 Gazebo 中完整跑通“建图 - 保存地图 - 导航”全流程。再把传感器从仿真换成真实硬件做激光雷达和里程计标定。加入视觉识别用 OpenCV 或深度学习模型完成目标检测。接入机械臂用 MoveIt2 实现抓取。最后整合成竞赛系统的状态机控制机器人在不同任务之间切换。如果你正在准备具体某一场比赛可以把比赛规则中的任务清单拆成技术点逐个对照本教程内容查漏补缺。遇到具体报错或异常时最好先记录完整日志、截图和上下文信息再动手改代码很多问题其实靠日志就能定位。机器人竞赛的备赛过程虽然辛苦但也是工程师快速成长最有效的方式之一。希望你也能在这个过程中把理论变成真正跑得起来的机器人。
返回列表