简介:本资源是一套面向高校机器人方向课程设计与毕业设计的ROS2实战项目,聚焦自主导航与SLAM建图核心能力训练,适用于具备Linux基础与ROS入门知识的学习者。项目完整实现未知环境下的实时建图、定位、路径规划与动态避障,集成Teleop远程操控、ros_arduino_bridge硬件通信、URDF机器人建模、diffdrive差速驱动控制及serial_motor_demo底层电机调试等六大功能模块,覆盖从仿真到实物部署的关键链路。压缩包共61个文件,含12个Python节点脚本(导航逻辑与接口)、7个XML/XACRO模型文件(URDF结构定义)、5个C++/INO固件代码(Arduino底层驱动)、4个YAML配置(参数调优)及2个RVIZ可视化配置,总大小仅49KB,轻量但结构清晰、模块解耦。已有60人学习下载,提供可直接编译运行的工程骨架、标准化launch启动流程与典型world仿真场景,是理解ROS2中间件通信机制与机器人系统集成的优质实践载体。 去年年中我接手了一个比较有意思的项目:用ROS2从零搭一台能自己建图、自己导航的差速底盘机器人。当时手头有一台现成的麦轮底盘,上面装了一颗16线激光雷达、一个IMU,还有一块 Jetson Orin NX。硬件不差,但软件栈一塌糊涂——驱动是ROS1的,导航用的老版move_base,建图靠gmapping,稍微跑远一点地图就飘。索性推倒重来,全部换成ROS2 Humble + Nav2 + Cartographer这套组合。整个过程踩了不少坑,也积累了很多经验,今天写出来,给准备入坑ROS2自主导航的朋友一个参考。
这篇文章不是教程堆砌,而是从项目整体设计的角度,把我选型、环境搭建、建图、导航、仿真、实车调试的完整链路拆开讲清楚。内容偏工程实践,涉及到的配置、参数、命令都是我在真机上跑通的,可以直接参考。
1. 项目整体设计与技术选型思路
1.1 为什么把整套软件栈迁移到ROS2
先说结论:如果你现在要新起一个机器人项目,直接用ROS2,别犹豫。
我在这个项目里选择ROS2,核心原因有三点。第一是通信架构。ROS1的roscore中心化节点设计,在长时间运行的自主导航场景下就是单点故障源,roscore一挂全部节点瘫痪,实车调试的时候很难受。ROS2改用DDS分布式通信,节点之间点对点直连,没有中心节点,稳定性好了一个量级。第二是时间同步机制。ROS1里激光雷达和IMU的时间戳经常对不齐,建图时点云容易产生畸变。ROS2内置了完整的time synchronization机制,配合message_filters做时间同步,数据质量明显提升。第三是工程化能力。ROS2的launch文件支持Python和XML两种写法,参数可以通过yaml文件动态加载,加上tf2树更加规范,多人协作开发时维护成本低很多。
当然ROS2也有让人头大的地方,比如DDS的发现机制在Wi-Fi环境下会丢节点,topic的QoS策略配置不对会一直收不到数据。这些坑后面我会专门讲。
1.2 建图与导航框架的选型对比
建图方案我对比过三个主流框架:gmapping、slam_toolbox、Cartographer。gmapping是ROS1时代的老将,基于粒子滤波,2D建图效果还行,但极其依赖里程计质量,激光频率低一点就崩,而且不支持闭环检测,跑大场景地图会累积漂移。slam_toolbox是Karto的开源版,支持2D闭环,做小场景够用,但它的闭环优化是纯2D的,遇到坡道或者不平整路面就抓瞎。最后选了Cartographer,理由很直接:它支持2D和3D建图,利用子图(submap)和闭环检测做图优化,对里程计质量的要求没那么苛刻,配合IMU的话,即使底盘打滑,建出来的地图也能保持一致性。
自主导航框架没有悬念,直接用Nav2。Nav2是ROS2官方主推的导航框架,内部集成了行为树(Behavior Tree)流程控制、AMCL定位、代价地图(costmap2d)、路径规划(planner)和轨迹跟踪(controller)等完整模块。它比ROS1时代的move_base设计得更好,所有模块都是独立插件,可以按需替换。比如我想换一个全局规划算法,只需要改配置里的插件名,不用动任何源代码。
1.3 激光雷达与传感器配置方案
传感器选型直接决定了建图的上限。我这边用的是一颗16线机械式激光雷达,360度扫描范围,测距30米,对于室内和园区场景完全够用。如果用单线雷达,也不是不行,但Cartographer的2D建图对单线雷达的扫描畸变特别敏感,最好加IMU做运动补偿。
IMU我用了底盘自带的九轴惯性单元,输出三轴加速度和三轴角速度。在建图时,IMU的作用不只是补充姿态信息,更重要的是在Cartographer的位姿推测器(PoseExtrapolator)里做运动预测。机械雷达的扫描频率一般是10Hz,而IMU的更新频率可以到200Hz以上,两者融合之后,即便激光扫描之间底盘状态有变化,也能准确补偿。
还有一个经常被忽略的配置——轮式里程计。如果你用的是现成底盘,通常会有编码器,编码器数据直接发布odom话题。但要注意,ROS2里odom的话题类型是nav_msgs/msg/Odometry,而且必须正确填充pose和twist两个子字段。我在初期调试时就是twist里没有填角速度,导致Cartographer的位姿推断完全混乱,地图旋转了90度。
2. 开发环境搭建与项目初始化
2.1 ROS2 Humble环境和一键安装脚本
系统我选的是Ubuntu 22.04,ROS2版本对应Humble Hawksbill。Humble是长期支持版本,支持周期到2027年,整个ROS2生态里的主流包基本都是优先兼容它。比它新的Iron版本虽然功能更多,但很多第三方驱动包还没来得及适配,实车项目求稳,不要追新。
安装ROS2的方式,官方文档推荐用apt源逐包安装。但如果网络条件不稳定,或者不想折腾源配置,可以直接用鱼香ROS的一键安装脚本:
wget http://fishros.com/install -O fishros && . fishros这个脚本会交互式询问你要装什么,选择ROS2 Humble桌面版即可。它会自动配置软件源、安装全部基础包、初始化rosdep,整个过程大概十五分钟。我实测下来比手动配置靠谱,尤其是在网络环境复杂的情况下,它能自动切换镜像源。装完之后记得验证一下:
ros2 --version source /opt/ros/humble/setup.bash ros2 run demo_nodes_cpp talker能正常跑起来talker和listener,说明环境没问题。
2.2 工作空间与功能包目录规划
项目的工作空间结构,建议按照功能分包,不要把所有代码塞进一个包。我这个项目的目录结构是这样的:
src/ ├── robot_base/ # 底盘驱动、odom发布、IMU驱动 ├── robot_description/ # URDF模型、tf树配置 ├── robot_sensors/ # 激光雷达驱动、点云处理 ├── robot_navigation/ # Nav2相关配置和launch文件 ├── robot_slam/ # Cartographer建图配置和launch文件 ├── robot_bringup/ # 总启动入口 └── robot_simulation/ # Gazebo仿真相关创建功能包可以用ros2 pkg create命令。注意,C++包需要加--build-type ament_cmake,Python包用ament_python:
cd ~/ros2_ws/src ros2 pkg create robot_base --build-type ament_cmake功能包的命名规范要提前定好,我给这个系列的包统一用robot_前缀,这样在ros2 pkg list里一眼就能过滤出自己项目的包。
2.3 底盘驱动与TF树配置的关键点
底盘驱动是整个机器人软件栈的地基,它负责接收速度指令,发布里程计。驱动里最核心的就是TF变换的发布——odom到base_footprint的变换关系必须准确连续。
TF树的设计我建议这样组织:
map → odom → base_footprint → base_link → laser_linkmap到odom是由定位模块发布的,odom到base_footprint由里程计发布,base_footprint到laser_link由URDF模型静态发布。这个层级关系不能乱,特别是base_footprint和base_link的区分——base_footprint是底盘在地面的投影点,z轴高度为0,这对2D导航非常重要。
发布odom→base_footprint的代码,核心就是根据轮式编码器数据做航位推算。差速底盘的航位推算公式如下:
v = (left_speed + right_speed) / 2 # 线速度 w = (right_speed - left_speed) / track_width # 角速度 x += v * cos(yaw) * dt y += v * sin(yaw) * dt yaw += w * dt这里的track_width是左右轮的轮距,单位要跟轮速统一。我在调试时遇到一个很隐蔽的问题:轮速单位是m/s,但发布到Odometry消息里的twist.linear.x却写成了cm/s,导致Cartographer推算的位姿比实际快100倍。这个bug查了我整整一个下午。
3. SLAM建图核心实操
3.1 Cartographer的配置参数详解
Cartographer的配置是典型的yaml风格,初次接触会很懵,但核心参数其实就几个。先看一个我调通的2D建图配置主文件:
map_frame: map tracking_frame: base_link published_frame: odom odom_frame: odom provide_odom_frame: true use_odometry: true num_laser_scans: 1 num_subdivisions_per_laser_scan: 1这几个参数要重点理解。tracking_frame是Cartographer位姿估计的参考坐标系,用base_link没问题。published_frame和odom_frame都设为odom,配合provide_odom_frame: true,意思是由Cartographer来发布odom→base_link的坐标变换。如果使用自己的里程计,就是use_odometry: true,让Cartographer把轮式里程计数据作为位姿推断的一个输入源。
建图质量最关键的一个参数组是位姿推测器(pose_extrapolator),它负责预测当前帧点云的初始位姿:
pose_extrapolator: use_imu_based: true constant_velocity: use_imu: true use_odometry: trueIMU数据的质量直接影响这个模块的效果。如果IMU发布频率低于50Hz,Cartographer会直接忽略它,退化成纯速度模型。所以我在驱动层把IMU的发布频率锁死在100Hz。
3.2 子图、关键帧与回环检测的原理
Cartographer能把建图做得比gmapping稳定的根本原因,在于它的图优化结构。简单解释一下:机器人每移动一段距离,就会生成一个子图(submap)。子图是由连续若干帧激光扫描数据拼成的局部地图。同时,系统会抽取关键帧,保存机器人在该位姿下的激光扫描、IMU状态、里程计数据。
回环检测做的事情是:当机器人再次经过某个已经建过的区域时,系统算法会把当前扫描与之前所有子图进行匹配,如果匹配到相似度足够高的子图,就认定检测到了回环。此时会把当前关键帧与历史子图之间的约束加入图中,通过非线性优化(ceres solver求解)来调整所有关键帧和子图的位姿,从而消除累积漂移。
这个机制带来的直观效果是:绕一圈回到起点,地图的起点和终点能完美重合。而gmapping这类纯滤波框架做不到这一点,它只是把当前帧匹配到地图上,历史位姿的误差无法修正,绕大圈必然漂移。
关键参数是submaps里怎么决定何时生成新的子图:
submaps: num_range_data: 35 range_data_inserter: range_data_inserter_type: PROBABILITY_GRID_2D probability_grid_range_data_inserter: insert_free_space: true hit_probability: 0.55 miss_probability: 0.49num_range_data是每个子图包含多少帧扫描数据,数值越小,子图越频繁生成,回环检测的机会越多,但计算量和内存占用也越大。我这里取35,对16线雷达和10Hz扫描,大约3秒一个子图,比较均衡。
3.3 建图过程中的运动控制与数据采集
建图前还有一项重要工作——移动机器人。很多人拿到Cartographer配置后直接用手柄遥控机器人,旋转的时候手一抖,速度忽快忽慢,地图就容易糊。
建图时的运动控制,我总结几条实操经验:
- 建图时移动速度控制在0.3m/s以下,旋转速度控制在0.3rad/s以下。速度太快会导致激光扫描畸变严重,特别是低帧率的机械雷达。
- 旋转要匀速,不要急起急停。Cartographer虽然有IMU补偿但补偿不了极大的角加速度。
- 扫描环境需要有明显的几何特征。在空旷的大厅里建图,激光打不到墙,回环检测基本失效。这种情况下需要贴着墙走一圈,让雷达能扫到结构化特征。
- 先建小区域,确认地图无重影后,再逐步扩展。不要一上来就拉大图,否则地图废了返工成本很高。
建图的启动命令如下:
ros2 launch robot_slam cartographer.launch.py ros2 run teleop_twist_keyboard teleop_twist_keyboard边遥控边实时看rviz2里的地图,如果出现重影,停下来检查IMU和里程计。地图结束时执行finish_trajectory,然后序列化保存地图:
rosservice call /finish_trajectory 0 rosservice call /write_state "{filename: '/home/ws/maps/map.pbstream'}"后续用map_server把.pbstream转成pgm和yaml格式。
3.4 点云处理与畸变补偿
用了机械式16线雷达,建图前最好做一次点云预处理。原因在于,Cartographer的2D建图接收的应该是2D laser scan,但很多机械雷达原生输出3D PointCloud2,直接把3D点云降维成2D,不做处理的话,坡度、地面杂物都会变成2D地图上的噪声。
我这边用pointcloud_to_laserscan包做转换,核心参数如下:
laserscan: target_frame: laser_link transform_tolerance: 0.01 min_height: 0.05 max_height: 0.5min_height和max_height限定了生成2D scan时保留点云的高度范围。我取0.05到0.5米,意思是只保留雷达上方5厘米到50厘米之间的点。这个高度范围能滤掉地面和大部分桌腿以下的小障碍,同时保留墙面和主要障碍物。
畸变补偿这块,机械式雷达自身在扫描过程中雷达会旋转,如果底盘同时运动,一帧扫描内每个点对应的时间戳不同,直接拼起来会出现畸变。Cartographer针对这个问题有内置的传感器数据时间同步机制,但前提是pointcloud_to_laserscan在发布laser scan时必须正确填充时间戳。我踩过的坑是,某些驱动发布PointCloud2时时间戳是0,导致Cartographer无法做畸变补偿,地图出现“甩尾”现象。排查方法很简单,在rviz2里查看PointCloud2的rostopic hz和延迟,如果时间戳到处乱跳,就得修驱动。
4. 自主导航框架与核心配置
4.1 Nav2框架解析与行为树流程
建好地图之后,下一步就是自主导航。Nav2的导航流程依赖行为树(Behavior Tree)来组织,而不是ROS1里那种硬编码的状态机。行为树的好处是灵活性极高,导航过程中的每个环节——计算路径、平滑速度、旋转朝向、避障——都是一个独立节点,可以自由编排和复用。
默认的导航行为树大概是这样的逻辑:备份当前位置 → 计算到达目标点的全局路径 → 如果全局路径失败尝试重新规划 → 控制底盘沿路径移动 → 途中持续通过局部代价地图避障 → 到达目标后停下。
实际工程中最常用的是navigate_to_pose这个行为树。如果需要连续走过多个目标点,可以用Waypoint Follower插件,配置一组目标点,机器人会依次导航。我这个项目还扩展了一个巡逻功能:在室内场景设置几个巡检点,机器人按需循环导航。
Nav2的启动launch文件,关键在于加载正确的参数文件:
from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): return LaunchDescription([ Node( package='nav2_bringup', executable='bringup_launch.py', output='screen', parameters=['config/nav2_params.yaml'], arguments=['--ros-args', '--remap', 'odom:=odom'] ) ])注意arguments里的remap,如果你的底盘odom话题名不是默认的odom,比如叫odom_raw,就必须在这里remap,否则Nav2的机器人状态节点找不到话题,导航时会一直报“Robot is off grid”。
4.2 自适应蒙特卡洛定位配置
Nav2的定位功能由AMCL(自适应蒙特卡洛定位)模块实现。AMCL的原理是:在地图上随机撒几万个粒子,代表机器人可能的位置假设。机器人每移动一步,粒子根据里程计预测传播;每收到一帧激光,就计算每个粒子位置对应的激光扫描与真实地图的匹配程度,粒子权重按匹配度重新分配,然后重采样,逐步收敛到真实位置。
AMCL用得好不好,全看两个参数:odom的噪声模型和激光的噪声模型。
amcl: ros__parameters: odom_model_type: "diff" # 差速底盘模型 update_min_d: 0.1 # 累计位移超过0.1m才更新 update_min_a: 0.2 # 累计旋转超过0.2rad才更新 resample_interval: 1 laser_min_range: 0.1 laser_max_range: 20.0 laser_max_beams: 60 sigma_hit: 0.3 z_hit: 0.95odom_model_type用diff是差速底盘的标准选项。如果你是全向底盘(麦轮/全向轮),需要改成omni,否则定位漂移会非常明显。update_min_d和update_min_a决定了AMCL的更新频率,越小定位响应越快,但计算量也越大,对Jetson这类边缘设备,保持0.1m / 0.2rad比较合适。
4.3 全局与局部代价地图参数调优
代价地图(costmap)是Nav2实现避障的核心。它把真实环境栅格化,每个栅格代价值从0到255,表示该区域被占据的概率。Nav2里有两个代价地图:全局代价地图(global_costmap)用于全局路径规划,覆盖范围大、更新频率低;局部代价地图(local_costmap)用于实时避障,覆盖范围小、更新频率高。
我调出来的参数配置如下:
global_costmap: global_costmap: ros__parameters: robot_radius: 0.25 obstacle_layer: enabled: true combination_method: 1 obstacle_range: 5.0 raytrace_range: 7.0 observation_sources: laser_scan_sensor inflation_layer: enabled: true inflation_radius: 0.35 cost_scaling_factor: 5.0 local_costmap: local_costmap: ros__parameters: robot_radius: 0.25 width: 3.0 height: 3.0 resolution: 0.05 obstacle_range: 3.0 raytrace_range: 4.0robot_radius要按机器人真实轮廓设置,设小了会出现碰撞,设大了会在狭窄通道里找不到路径。inflation_radius和cost_scaling_factor是成对调整的,inflation_radius越大机器人离障碍物越远,cost_scaling_factor越大代价衰减得越快。如果你希望机器人尽量贴墙走,把cost_scaling_factor提到8.0,inflation_radius降到0.2;如果求稳,保持我这组参数就行。
4.4 全局规划器与局部控制器选型
Nav2的全局规划器我选择NavfnPlanner插件,它基于Dijkstra算法,实现简单、运行稳定,对小中型底盘足够。如果地图很大、计算资源紧张,可以换成SmacPlannerHybrid,它实现的Hybrid A*算法可以生成符合机器人运动学约束的路径,拐弯更平滑,就是需要耗费一定的计算资源。
局部控制器我用的是DWB(DWA改进版),它相比默认的DWAPlanner,有更多可调节的评分项,比如路径跟随得分、目标点距离得分、旋转角度得分。核心参数:
controller_server: ros__parameters: controller_plugins: ["FollowPath"] FollowPath: plugin: "dwb_core::DWBLocalPlanner" min_vel_x: 0.0 max_vel_x: 0.5 min_vel_theta: 0.0 max_vel_theta: 1.0 min_speed_xy: 0.0 max_speed_xy: 0.5max_vel_x是导航过程中的最大线速度。我设的0.5m/s,室内场景比较安全,也能保证AMCL定位稳定的速度范围内。如果你的场景是园区走廊,可以适当提高到1.0m/s,但代价是定位可能跟不上,需要AMCL的粒子数也相应增大。
5. 仿真验证与真机部署
5.1 Gazebo仿真环境搭建
在动真机之前,我强烈建议先在Gazebo仿真里跑通整套流程。平时调试一个导航算法,反复开关真机电源、搬动底盘,半小时就过去了,Gazebo里一个ros2 launch的事情,效率完全不同。
我的仿真环境是基于robot_description包里的URDF模型,加上一个简单的室内地图:
world_name = 'house.world' gazebo = Node( package='gazebo_ros', executable='gazebo', arguments=['-s', 'libgazebo_ros_factory.so', world_name], output='screen' )URDF模型里要给机器人加上差速驱动插件和激光雷达插件,这些是Gazebo仿真的关键。差速驱动插件读取cmd_vel话题,施加力让模型移动;激光雷达插件读取3D场景,实时生成点云数据。仿真里能直接验证建图和导航算法,不需要任何硬件。
仿真环境下运行Cartographer建图,和真机几乎一样的命令:
ros2 launch robot_simulation gazebo_simulation.launch.py ros2 launch robot_slam cartographer.launch.py ros2 run teleop_twist_keyboard teleop_twist_keyboard唯一的区别是仿真里IMU数据非常干净,没有零漂,所以仿真跑通不代表真机也能跑通。
5.2 真机部署时的时间同步与话题对齐
真机部署是考验工程能力的时候。第一个坑就是时间同步。Jetson和雷达、底盘之间通常通过USB或网口连接,如果系统时间不准确,激光和里程计的时间戳就会错位,Cartographer建图会飘。
我的做法是在Jetson上启用PTP硬件时间同步,或者至少用chrony做NTP同步。没有硬件PTP的情况下,用软件方案:
sudo apt install chrony sudo systemctl enable chrony同时激光雷达驱动如果支持PPS时间同步,优先启用。
第二个坑是话题名称统一。真机驱动、仿真插件、导航配置、SLAM配置里,所有话题名必须完全一致。我建议所有launch文件里都用变量定义话题名,不要在每个yaml里硬编码。
5.3 计算资源受限场景下的性能优化
我在项目里跑的是Jetson Orin NX,8GB内存版本。Cartographer建图加Nav2导航同时跑,CPU占用率经常冲到80%以上。如果遇到计算资源更紧张的设备,比如树莓派4,需要做几项优化。
降低雷达扫描频率是个有效手段。16线雷达默认10Hz,如果场景不大,可以降到5Hz,数据量减半,Cartographer性能压力小很多。代价是建图精度略有下降,但在小场景里完全能用。
调整AMCL粒子数。默认500个粒子,如果定位环境稳定(比如室内平地上),减到300个,CPU占用能降10%左右。
Nav2里还可以把局部代价地图的分辨率调低,从0.05降到0.1,计算量直接降为原来的四分之一,代价是避障的精细程度下降。
如果这些都不够,最后的方案是把Cartographer建图和Nav2导航分时运行——建图时关掉导航,导航时加载已经保存好的地图和pbstream,不跑实时SLAM。这种方式在实用项目中其实很常见,因为导航和建图同时跑本来就依赖更多计算资源。
6. 常见问题与排查技巧实录
6.1 故障速查表
这半年调试下来,我把遇到的典型问题整理成了一张速查表,写在这里,碰到类似情况可以直接对照排查。
| 故障现象 | 可能原因 | 排查方法与解决 |
|---|---|---|
| 建图时地图发生“甩尾”或重影 | 激光雷达时间戳异常、IMU方向错误、里程计单位错误 | 检查雷达驱动的时间戳有效性,在rviz2查看TF树,打印IMU数据的朝向与底盘真实朝向是否一致 |
| 导航时机器人原地转圈 | 局部代价地图没有障碍物信息、激光topic QoS不匹配 | 在rviz2查看local_costmap是否显示障碍点,检查激光话题的QoS深度是否与costmap一致 |
| AMCL定位丢粒子,机器人位置跳变 | 地图分辨率过低、AMCL激光噪声参数不对 | 重新以较高分辨率建图,调大z_hit、调小z_rand |
| Nav2无法规划路径 | 机器人在代价地图上的位置被标记为“致命障碍” | 检查机器人半径是否大于通道宽度,检查全局代价地图的robot_radius参数 |
| Cartographer启动即崩溃 | 配置yaml参数缺失、坐标系名称不匹配 | 逐项检查map_frame、tracking_frame、odom_frame的TF树是否存在,缺失参数会导致ceres优化崩溃 |
6.2 雷达与里程计坐标系的避坑经验
坐标系问题属于“一日踩坑,终身难忘”的类型。我调试时出现过一次非常离谱的现象:建出的地图整个上下颠倒,后来发现是laser_link在URDF里的方向写反了。雷达的z轴应该朝上,如果写成了朝下,激光扫描的点云高度范围全部变成负值,Cartographer拿到错误的传感器外参,地图自然就反了。
检查坐标系最直接的方式是rviz2里的TF显示。启动机器人,打开rviz2,添加TF显示插件,设置fixed frame为odom,然后看laser_link的坐标系箭头是否与底盘坐标系一致。如果激光扫描点云在rviz2里显示的方向和现场物理布局明显对不上,优先怀疑URDF外参。
再有一个非常隐蔽的坑:某些激光雷达驱动的默认坐标系是laser,而不是laser_link。如果Cartographer配置里用的是laser_link,但驱动发布的消息里frame_id写的是laser,TF树里找不到对应的变换,整个建图直接卡死。解决方法是统一修改驱动的frame_id参数,或者在launch里用static_transform_publisher补一个静态变换。
6.3 QoS策略不一致导致的“收不到数据”
ROS2相比ROS1又一个让人头疼的地方是QoS策略。ROS1的topic是发布订阅完全解耦的,不存在消息匹配问题。ROS2里如果发布端的QoS设置和订阅端匹配不上,ros2 topic echo直接没有任何输出,订阅端也收不到任何数据。
我在Nav2里遇到的典型情况是:雷达驱动发布点云时用的QoS depth是1,而Nav2的obstacle_layer订阅时要求的depth必须是rclcpp::SensorDataQoS()(depth=5),两者匹配不上,导致代价地图一直看不到障碍物,导航时直接撞墙。排查方法容易忽略:用ros2 topic info /scan -v,查看Publisher的QoS属性和Subscriber的QoS属性,逐项对比reliability(RELIABLE还是BEST_EFFORT)和durability(VOLATILE还是TRANSIENT_LOCAL)。
遇到这种情况,解决办法是改雷达驱动的QoS设置。激光和点云这类传感器数据,推荐用BEST_EFFORT,因为丢几帧无关紧要,但实时性优先。而地图数据(map)必须用TRANSIENT_LOCAL,这样即使切换地图的消费者也能收到最新的地图数据。
6.4 长距离导航中的定位漂移问题
这个项目的最后阶段,我做了一次两百米长距离的自主巡检测试。问题很快暴露:机器人跑了大概一百米后,AMCL的粒子开始发散,机器人位置在地图上的显示与实际偏差越来越大。
定位漂移的根源在于:机器人在长走廊里,激光扫描的几何特征高度重复——两边的墙看起来一模一样,激光雷达无法提供有效的纵向约束。这种环境下,粒子在横向会收敛,纵向却会产生明显的扩散。
我的解决办法是融合IMU航向。Nav2的AMCL支持imu话题订阅,在配置里添加:
amcl: ros__parameters: use_imu: true imu_topic: /imu/data让AMCL直接利用IMU的航向角数据来辅助粒子更新,抗纵向漂移的能力会明显提升。同时,机器人经过走廊里有门、柱子等显著特征的区域时,定位会重新收敛。所以长距离导航的路线规划上,尽量让路径途经有特征的环境,不要总是贴着无边无际的大白墙走。
另外值得一提的点是,Nav2里的全局路径规划本身也有问题——两百米的路程,全局规划器一次性生成的路径粒子在地图上的位置必须精确,否则规划器会认为路径穿越障碍物,拒绝给底盘发速度指令。遇到这种情况,把全局规划器的路径规划超时时间适当调大,比如从2秒调大到5秒,让规划器有足够时间搜索一条可行的平滑路径。
7. 写在最后的几点经验
整个项目做下来,我觉得最有价值的经验不是某个算法调通了某个参数,而是建立了一套“先仿真、后实机、分模块调试”的工程流程。每次改动都从仿真验证开始,确认无误后再上真机,大幅减少了实车调试的时间成本和安全隐患,这个方法论在后续项目中一直沿用。
Cartographer建图能建出稳定的地图,Nav2能把机器人安全地导航到目标点,这只是一个开始。后续你可以在这个基础上扩展更多功能:把建图模块换成支持3D语义地图的方案,让机器人能识别特定物体并执行抓取;把导航模块接上多楼层切换逻辑,实现跨楼层自主移动;或者加入实时动态避障,让机器人在人群密集的场景也能灵活穿行。这些扩展都建立在前面扎扎实实的地基之上,希望这篇文章能帮你少走一些弯路,让你的ROS2机器人项目尽早跑起来。
本文还有配套的精品资源,点击获取