简介:面向机器人开发者与ROS2学习者的完整仿真导航资料包。以Fishbot机器人为载体,整合Gazebo仿真环境、RVIZ可视化、Cartographer建图算法、Nav2导航框架及URDF模型,覆盖从环境搭建、SLAM定位到自主路径规划的典型闭环流程,适合算法验证、课程设计与入门实操。压缩包共48个文件,大小379KB,包含Python脚本、XML/YAML配置、RVIZ界面、URDF模型、World场景、Lua建图参数及PGM地图等类型,目录按功能模块清晰划分,便于按需调用和二次开发。已有241人学习下载。可直接加载Gazebo虚拟环境,结合RVIZ实时观察机器人位姿、传感器数据与增量地图,借助Cartographer完成未知环境建图,并通过Nav2实现点到点导航与避障,快速复现完整机器人仿真导航系统,显著减少环境搭建与算法调试成本。
1. 把Fishbot仿真导航系统当ROS2入门第一站:为什么这条链路值得复现
很多人在接触ROS2导航时,第一反应是去买一台带激光雷达的实机底盘。但SLAM和导航真正烧钱的地方不是硬件本身,是反复试错的调试时间——实车跑一圈要清场地、充电池、等传感器慢慢收敛,半小时过去了地图还没建干净。这套基于ROS2实现的Fishbot仿真与导航方案,把整个链路搬进Gazebo里:URDF定义机器人结构,Cartographer用仿真激光雷达建出二维栅格地图,RVIZ把建图过程可视化,Nav2在拿到地图后接管路径规划与自主避障。你在终端敲几条命令,就能看到一台虚拟机器人从建图到导航的完整表现。
这套系统适合两类人:一类是刚装好ROS2、想在不烧硬件的环境里把导航参数完整过一遍的初学者;另一类是打算把方案迁移到自研低成本轮式底盘上的工程师。仿真不会替你回答实车的所有问题,但能帮你把坐标系配置、传感器帧对齐、代价地图膨胀这些共性难点先磨平。下面的内容按我实际跑通这个项目的顺序展开,从建环境到迁移实车,每一步都带可复现的命令和参数。
2. 搭建ROS2与Gazebo仿真环境:版本选型、Launch文件与URDF坐标系
先把结论放前面:如果没有特殊硬件依赖,直接用Ubuntu 22.04加ROS2 Humble加经典Gazebo 11这套组合,不要在版本上继续纠结。网上确实有人把ROS2 Humble塞进Docker容器里跑仿真,但GPU透传和窗口显示会多出一堆额外麻烦,我在第五章节末尾会专门说这个坑。先把本机环境装干净,比什么都重要。
2.1 版本选型:为什么是Humble配Gazebo 11
ROS2发行版和仿真器之间有兼容性矩阵。Humble对应的Gazebo有两个选择:经典Gazebo 11(通过ros-humble-gazebo-ros-pkgs提供)和Ignition Fortress(后来的gz-sim)。Fishbot这种两轮差速加2D激光雷达的仿真场景,用经典Gazebo 11就足够,而且是ROS2里资料最全、踩坑记录最多的一条路。Ignition Fortress在物理引擎和多机器人支持上更强,但插件命名和SDF文件写法跟经典版差别不小,初学者容易在最开始的启动步骤就被劝退。
安装命令分三步走,先把ROS2本体装好,再用apt补齐仿真和可视化相关包:
# ROS2 Humble 桌面版,含 RVIZ2、TF2、常用工具 sudo apt install ros-humble-desktop # Gazebo 11 与 ROS2 桥接插件,会连带拉入 gazebo11 本体 sudo apt install ros-humble-gazebo-ros-pkgs # 机器人模型发布与关节控制 sudo apt install ros-humble-robot-state-publisher \ ros-humble-joint-state-publisher \ ros-humble-xacro三个安装包各管一件事。ros-humble-desktop是基础,没有它后面所有节点都起不来;ros-humble-gazebo-ros-pkgs里带gazebo_ros插件和spawn_entity.py脚本,负责把URDF模型生成到仿真世界里;robot_state_publisher把URDF里定义的关节和link转换成TF变换,RVIZ里看到的机器人模型全靠它。我一般还会额外加装ros-humble-teleop-twist-keyboard,建图时用键盘手动控制机器人走位,比在RVIZ里发目标点更灵活。
教程里的Fishbot模型本身不挑显卡,但Gazebo 11的渲染对OpenGL有要求。如果在虚拟机里跑,先确认3D加速打开,否则后面建图时地图刷新会非常卡。
2.2 一个Launch文件把Gazebo、URDF和RVIZ串起来
Fishbot项目的启动思路通常是一个总launch文件:拉起Gazebo世界,发布URDF模型,把模型生成到仿真世界里,最后打开RVIZ。下面是按这个思路组织的最小launch文件,保存为simulate.launch.py:
# fishbot_simulation/launch/simulate.launch.py from launch import LaunchDescription from launch.actions import ExecuteProcess from launch_ros.actions import Node from launch_ros.substitutions import FindPackageShare import os def generate_launch_description(): pkg_share = FindPackageShare('fishbot_description').find('fishbot_description') urdf_path = os.path.join(pkg_share, 'urdf', 'fishbot.urdf') world_path = os.path.join(pkg_share, 'worlds', 'room.world') with open(urdf_path, 'r') as f: robot_desc = f.read() return LaunchDescription([ ExecuteProcess( cmd=['gazebo', '--verbose', world_path], output='screen' ), Node( package='robot_state_publisher', executable='robot_state_publisher', parameters=[{'robot_description': robot_desc}], output='screen' ), Node( package='gazebo_ros', executable='spawn_entity.py', arguments=['-entity', 'fishbot', '-topic', 'robot_description'], output='screen' ), ])这个launch文件做三件事。ExecuteProcess直接调用Gazebo可执行文件并加载room.world世界文件,这比用gazebo_ros的launch插件更直观,也方便后面替换自定义world;robot_state_publisher节点把读进来的robot_description参数发布成URDF和TF树;spawn_entity.py以robot_description话题里的模型为模板,把Fishbot生成到Gazebo世界坐标原点。
注意-topic robot_description这个参数,它和前面robot_state_publisher的参数是配套的。少了这一句,launch文件不会报错,但Gazebo里就是空的,新手经常在这卡住。启动后再加一个终端跑rviz2,在Displays面板里添加RobotModel和TF两项,就能看到Fishbot的模型了。
如果RVIZ里看到的模型位置和Gazebo不一致,优先检查use_sim_time参数。仿真模式下所有节点必须使用仿真时钟,否则TF和时间戳对不上,RVIZ里会出现模型瞬移或干脆黑屏。在launch文件里给每个Node加parameters=[{'use_sim_time': True}],或者在启动命令后面加--ros-args -p use_sim_time:=true,选一种方式即可。
2.3 URDF模型里最容易错的坐标系与惯性参数
Fishbot的URDF不会太复杂,核心结构是base_link、两个驱动轮、一个万向支撑轮和一个激光雷达link。坐标系设计上,base_link在底盘中心,激光雷达固定在base_link正上方一段高度。仿真里最关键的是laser到base_link的静态坐标变换,URDF里用固定joint表达。典型片段如下:
<link name="base_link"> <inertial> <mass value="3.0"/> <inertia ixx="0.04" ixy="0.0" ixz="0.0" iyy="0.04" iyz="0.0" izz="0.08"/> </inertial> <visual> <geometry> <cylinder radius="0.15" length="0.08"/> </geometry> <material name="blue"/> </visual> <collision> <geometry> <cylinder radius="0.15" length="0.08"/> </geometry> </collision> </link> <joint name="laser_joint" type="fixed"> <parent link="base_link"/> <child link="laser"/> <origin xyz="0 0 0.15" rpy="0 0 0"/> </joint>仿真里最容易翻车的是惯性参数。URDF的link如果没有写<inertial>块,Gazebo物理引擎会直接跳过该link的动力学计算,机器人要么纹丝不动,要么在重力的作用下掉出地图。上面给出的mass和inertia数值是常见的经验值,两轮差速底盘在平地上跑没问题,但如果后面加机械臂这类附加结构,需要重新计算整体重心。
激光雷达的安装高度建议不低于0.1米,否则2D扫描平面会穿过底盘本体,Cartographer建图时地图边缘会出现一圈固定的假轮廓。如果雷达自带IMU,laser和imu_link之间用固定joint连接,保持坐标系静止。建图和导航阶段会反复用到base_link、laser、odom、map这四个坐标系,URDF里定义好前两个,后两个由Cartographer和Nav2在运行时发布。
把world文件放worlds目录、URDF放urdf目录,运行launch后Gazebo里应该能看到一个蓝色圆柱底盘在房间里待命。RVIZ里的TF树从map到odom到base_link再到laser,四层结构齐全,这套仿真系统的基础就跑通了。
3. Cartographer建图:把激光扫描变成可导航的二维栅格地图
建图是整个流程里最容易被低估的一环。Nav2的导航质量直接取决于地图质量,而地图质量取决于Cartographer的参数配置和建图时的操控习惯。很多人在Gazebo里建图时发现地图边缘是毛刺、走廊越建越宽,原因不在算法,而在传感器帧和时间戳没对齐。这一章先把Cartographer在ROS2里的正确启动方式写清楚,再展开三组关键参数。
3.1 安装并打通建图数据流
ROS2 Humble的官方软件源里直接带Cartographer,不需要从源码编译,除非你要改算法内部逻辑。
sudo apt install ros-humble-cartographer ros-humble-cartographer-ros装完之后的建图数据流是这样的:/scan话题从Gazebo拿到2D激光点云,Cartographer节点同时接收/scan和TF树上的odom -> base_link -> laser变换,内部维护一个由多帧激光拼接成的子地图,最终在/map话题上发布栅格地图。RVIZ里看到的地图是不断更新的占据栅格,而不是实时点云。
要确认数据流是否打通,先检查三个话题是否存在:ros2 topic list | grep scan、ros2 topic list | grep map、ros2 topic list | grep tf。缺任何一个都说明上游节点没启动,不要急着调Cartographer参数。Gazebo仿真里/scan频率一般设10Hz就够了,太高浪费CPU,太低会让Cartographer的帧间匹配变得困难。
3.2 建图启动配置:Lua文件与launch命令
Cartographer的配置分成两部分:Lua配置文件和launch启动文件。Lua里定义传感器来源、坐标系和算法参数,launch负责把Lua文件传给节点。一个Fishbot可用的最小Lua配置如下:
-- fishbot_2d.lua include "cartographer_ros.lua" options = { map_builder = MAP_BUILDER, tracking_frame = "base_link", odom_frame = "odom", provide_odom_frame = true, publish_frame_projected_to_2d = false, use_odometry = true, use_nav_sat = false, use_landmarks = false, num_laser_scans = 1, num_subdivisions_per_laser_scan = 1, -- 2D 激光有效范围 min_range = 0.05, max_range = 10.0, min_z = -0.2, max_z = 2.0, } MAP_BUILDER.use_trajectory_builder_2d = true TRAJECTORY_BUILDER_2D.use_imu_data = false TRAJECTORY_BUILDER_2D.num_accumulated_range_data = 1 TRAJECTORY_BUILDER_2D.ceres_scan_matcher.translation_weight = 10.0这里几个参数很容易改错。tracking_frame写base_link是最稳的选择,如果写laser,Cartographer会把雷达本身当作跟踪目标,在后续添加里程计数据时会产生额外的坐标变换误差。use_odometry在Gazebo仿真里开true没问题,因为仿真里程计没有轮子打滑和累计漂移;实车环境要根据底盘编码器的质量决定,编码器噪声大会有反效果。use_imu_data设false是刻意为之——Fishbot的IMU在仿真里虽然是理想值,但坐标轴方向和激光雷达未必对齐,一旦方向标定有误,IMU参与优化反而会让地图产生旋转漂移。
启动命令用标准做法:
ros2 launch cartographer_ros cartographer.launch.py \ configuration_basename=fishbot_2d.lua另一个终端用键盘遥控Fishbot移动:
ros2 run teleop_twist_keyboard teleop_twist_keyboard建图时的走位节奏很重要:慢速直行、转弯时原地旋转、同一区域重复扫一遍。这不是玄学——Cartographer的局部子地图在转弯时最容易积累误差,重复扫描能让后端优化拿到更多约束,把漂移拉回来。建完图后保存地图用nav2_map_server提供的工具:
ros2 run nav2_map_server map_saver_cli -f ~/fishbot_map这条命令生成fishbot_map.pgm和fishbot_map.yaml两个文件。pgm是灰度图,yaml里记录了分辨率、原点和占据阈值。如果打算继续做Nav2导航,map.yaml里的frame_id必须是map,不能省略。保存路径不要带中文或空格,Nav2读取时处理起来很麻烦。
3.3 建图质量的三个关键参数
第一组是ceres_scan_matcher.translation_weight和rotation_weight。默认值下,Cartographer对旋转约束的信任度比平移更高。如果Fishbot在Gazebo里频繁原地旋转,地图中心区域会发虚,把translation_weight从默认值往上调到10以上能明显缓解,但别超过50,否则后端优化会过拟合子地图的局部误差。
第二组是min_range和max_range。仿真雷达标称量程一般是20米以上,但Gazebo房间通常不超过10米,把max_range设在10米可以忽略墙壁之外的无效点,减少子地图匹配时的干扰。min_range设为0.05米,把底盘附近的盲区排除掉,避免雷达打到自己的轮子产生噪点。
第三组是子地图相关参数。num_accumulated_range_data对应每次生成子地图时累积多少帧激光,数值越大子地图覆盖范围越大,但计算开销也越高。Gazebo仿真里设60到90比较合适,太大之后Cartographer的发布频率会掉下来,RVIZ里会看到地图更新一顿一顿。还有submaps相关的num_range_data——老版本Cartographer里叫这个名字,Humble版本统一为num_accumulated_range_data,配置前先看一眼你装的版本里实际字段名。
关于3D雷达多说一句。如果将来换mid360这类固态激光雷达,Cartographer也能做3D建图,但配置会多出num_echoes、num_subdivisions_per_laser_scan、use_imu_data强制开启等条件,tracking_frame通常要指向IMU坐标系。Fishbot这种2D底盘加2D雷达的方案不需要碰这些,先把2D链路跑通再谈扩展。
4. Nav2导航框架:让Fishbot在仿真地图里自主移动
建图只是第一步,Nav2才是真正考验参数理解力的部分。Nav2不是一个单节点,而是一组互相配合的ROS2节点:map_server加载静态地图,AMCL负责定位,planner_server规划全局路径,controller_server跟踪局部路径并输出速度指令。任何一环的坐标系对不上,机器人都只会原地转圈。
4.1 Nav2核心组件和它们各自管的活
先搞清楚组件,再谈配置。map_server在启动时读入.pgm和.yaml文件,把静态地图发布到/map话题;AMCL接收/scan、/tf和/map,通过粒子滤波估计Fishbot在map坐标系下的位姿,输出到/amcl_pose;planner_server基于全局代价地图搜索可行路径;controller_server负责沿路径跟踪,并把速度指令发到/cmd_vel。这四个节点缺一个,行为树就跑不到终点。
Nav2的默认行为树是navigate_to_pose,定义了标准导航流程:先全局规划,再局部跟踪,途中遇到动态障碍物会触发局部代价地图的重新规划。Fishbot仿真里直接使用官方默认行为树即可,不需要自己写XML。如果后面想扩展成多点巡航,再换navigate_through_poses,那是另一个话题。
4.2 Nav2参数模板:代价地图与AMCL的调参重点
Nav2启动会带一组yaml参数文件。下面是从Fishbot场景整理出的最小可行配置,保存为nav2_params.yaml:
amcl: ros__parameters: use_sim_time: true base_frame_id: "base_link" odom_frame_id: "odom" scan_topic: "/scan" min_particles: 500 max_particles: 2000 update_min_d: 0.2 update_min_a: 0.2 local_costmap: local_costmap: ros__parameters: robot_radius: 0.15 inflation_radius: 0.25 width: 3.0 height: 3.0 resolution: 0.05 obstacle_layer: observation_sources: scan scan: topic: /scan marking: true clearing: true global_costmap: global_costmap: ros__parameters: robot_radius: 0.15 inflation_radius: 0.30 obstacle_layer: observation_sources: scan scan: topic: /scan marking: true clearing: true bt_navigator: ros__parameters: use_sim_time: true default_bt_xml_filename: "navigate_to_pose.xml"两个参数组值得单列出来。robot_radius是Fishbot的圆柱体半径,URDF里已经定义过,这里必须保持一致,否则代价地图上的膨胀跟底盘实际体积不匹配,导航路径会贴墙太近。inflation_radius是障碍物外圈的膨胀范围,0.25米表示路径规划会把机器人中心保持在据障碍物至少0.25米外。具体数值取决于你的通道宽度和底盘宽度:房间宽敞就设0.3,走廊狭窄就缩到0.2。数值太大,小尺寸可行路径会被吞掉;数值太小,过窄通道时容易刮墙。
AMCL里最常见的错误是odom_frame_id和base_frame_id写反。Gazebo仿真里,odom话题的坐标系是odom,机器人本体是base_link。如果设成map或者漏设,AMCL粒子会在一开始就散成一片,定位收敛时间翻倍。仿真里粒子数设500到2000足够,如果建图质量好,200个粒子也能稳住,只是转角时收敛慢。粒子数设太多不会让定位更准,反而拖慢CPU——RVIZ里看到地图在抖动,先查粒子数是不是被调到了5000以上。
启动Nav2的命令就是标准bringup加地图路径:
ros2 launch nav2_bringup bringup_launch.py \ map:=~/fishbot_map.yaml \ use_sim_time:=true启动后在RVIZ2里用2D Pose Estimate设置初始位姿。如果建图时的起点和导航时的起点不一样,这一步不能省。设置完观察AMCL粒子收敛,粒子聚集到机器人实际位置附近再发导航目标。这时可以再了解一个更现代的重定位方案:fast_lio_localization的定位模式现在经常被拿来替代AMCL做3D重定位,思路类似但依赖点云配准,Fishbot这种2D雷达场景用不上,但值得了解。
4.3 避障行为与3D雷达扩展
Fishbot只有一个2D激光雷达,Nav2的代价地图会自动以/scan作为障碍物来源。但仿真里有一种典型情况:Gazebo中的门框或柱子高度低于激光雷达安装平面,扫描线从门框上方穿过,地图上没有这个障碍物,机器人就直接撞上去。做仿真时,尽量把world文件里的障碍物高度做得比激光雷达平面高出一截,或者让雷达的max_z限制在2.0米以内,把低于雷达平面的物体保留下来。
如果想扩展3D雷达,Nav2本身不直接消费pointcloud2做2D代价地图,常见做法是用pointcloud_to_laserscan节点把点云投影成scan,再用laser_filters做降采样,然后把投影后的scan接到代价地图的observation source。这个扩展在Fishbot仿真里不是必须的,但理解这条链路,后面换实车雷达时会少走弯路。直接给costmap挂一个海量点云话题会让规划器每帧做全局重规划,CPU很快拉满,这是刚接触的人最容易犯的错误。
5. Fishbot仿真里的高频避坑:五条能直接抄的排查记录
到了踩坑环节。我从建环境到跑导航这一路遇到的高频问题里挑了五条,按现象、原因、解决的顺序写。每条都是可以直接照着操作的经验。
5.1 Gazebo加载URDF时模型掉进地面
现象:Gazebo启动后界面正常,但URDF生成的模型一出现就往下陷,或者在地面上剧烈抖动后直接掉出地图边界。
原因:URDF里的link缺少<inertial>块,或者惯性矩阵数值太小,Gazebo物理引擎在碰撞检测时算出的接触力不足以支撑机器人重量。另一个常见原因是碰撞体的几何尺寸远大于视觉模型,导致模型被自己的碰撞体顶起来,物理状态不稳定。
解决:给每个link补上完整惯性参数,质量不要设成0.001这种夸张数字,按照底盘实际重量从1到5千克开始调。碰撞体几何最好和视觉模型完全一致,仿真里不需要为所谓安全冗余单独做大的碰撞框。改完URDF后重启整个launch,不要只重启Gazebo——Gazebo对URDF变更的响应经常不生效,重启一次最快。
5.2 RVIZ2启动后黑屏且没有TF显示
现象:rviz2窗口是黑色的,Displays面板里添加RobotModel和TF后没有内容,终端也不报错。
原因:按概率排序有三种。robot_state_publisher没有输出/tf话题;use_sim_time和系统时间不匹配,RVIZ缓存里全是过期数据;Fixed Frame设成了base_link但TF树里还没有这个坐标系,显示自然为空。
解决:先看话题列表ros2 topic list | grep tf,确认/tf存在。再用ros2 run tf2_tools view_frames生成TF树PDF,检查map -> odom -> base_link -> laser链路是否完整。最后把RVIZ的Global Options里Fixed Frame改成map。很多黑屏其实只是显示参考系没配对。
5.3 Cartographer建图出现重影和地图偏移
现象:建图过程中地图轮廓看起来清晰,但同一面墙在图中出现两条平行线,或者机器人转一圈后地图整体旋转了一个角度。
原因:最常见的是IMU坐标方向和激光雷达不一致,use_imu_data开true但IMU的roll/pitch/yaw没有标定过;另一种情况是tracking_frame设成了laser,Cartographer内部把雷达当作定位基准,和odom的变换对不上。
解决:仿真里直接把use_imu_data设false,能规避90%的重影问题;再把tracking_frame改成base_link。如果还有重影,检查robot_state_publisher发布的静态变换频率,低于10Hz时Cartographer的位姿约束会稀疏,地图更新跟不上运动。用ros2 topic hz /tf查看发布频率,仿真正常应该在50Hz左右。
5.4 Nav2规划路径时总是顶到墙角
现象:RVIZ里用2D Goal Pose点目标点后,全局路径看起来合理,但Fishbot走到墙角附近就反复重规划,甚至卡在原地转圈。
原因:inflation_radius和robot_radius的关系不对,规划器认为墙角通道足够宽,但实际代价地图膨胀后已经没有可行路径;或者代价地图obstacle_layer里的clearing没开,激光扫过的空间无法被标记为自由,障碍物只增不减,路径越规划越窄。
解决:把robot_radius调回和URDF一致的0.15,inflation_radius从0.3往下缩到0.2,给墙角留出转弯裕量。确认observation_sources下的scan同时设置了marking: true和clearing: true。还有一个容易被忽略的细节:global_costmap的resolution要和保存的地图分辨率一致——默认是0.05,如果map_saver存出来的yaml是0.01,Nav2会把地图放大五倍,路径规划全乱。这类问题终端看不到报错,只能在RVIZ里对比栅格尺寸来判断。
5.5 VNC或Docker里RVIZ2打不开
现象:在Docker容器或远程VNC桌面里跑rviz2,窗口要么起不来,要么起来后一片黑;Gazebo在VMware里也出现屏幕闪烁的问题。
原因:RVIZ2依赖OpenGL硬件加速。VNC默认的虚拟显示没有GLX扩展,容器里如果没有把宿主GPU透传进去,渲染会走软件模式或者直接失败。Gazebo在VMware里的闪烁通常是3D加速没开启,VMware设置里显卡的3D图形加速默认关闭。
解决:先在容器里装libgl1-mesa-dri和libgl1-mesa-glx,然后用LIBGL_ALWAYS_SOFTWARE=1 rviz2强制软件渲染,能解决绝大多数黑屏问题。如果这样还是不响,把整个launch挪回宿主机的真实X11会话里跑。仿真调试阶段别在VNC里较劲——软件渲染的RVIZ2帧率不到10Hz,看TF树都卡,更别说判断导航路径。Docker跑ROS2 Humble本身没问题,但可视化调试请把X11 forward出来,VNC适合挂后台进程,不适合做渲染型工具的前端。
6. 从仿真到实车:用rosbag和TF检查验证你的导航配置
仿真跑通了不代表实车能直接复制。最后这章是三个我一直在用的验证手段,能在换底盘之前把配置层面的隐患清掉。
6.1 先录一段rosbag再验证定位
ros2 bag record -a -o nav_validate录完之后用ros2 bag play nav_validate回放,相当于把建图和导航过程完整重跑一遍。回放时重点看/scan、/odom、/tf三个话题的时间戳是否对齐——时间戳错开的典型现象是回放时地图叠加位置和实际路径漂移。这个话题在实车上更严重,因为真实传感器的时钟源不同,Gazebo里时间戳默认是统一的,拉到实车反而会暴露问题。
6.2 三个坐标系的最终确认
实车迁移前必须检查三个坐标变换:map -> odom由AMCL维护,odom -> base_link由底盘里程计发布,base_link -> laser是静态变换。用ros2 run tf2_tools view_frames生成当前TF树,确认这三条链路完整且变换关系正确。
odom话题的漂移情况用这条命令看:
ros2 topic echo /odom --once重点看pose.pose.position数值在建图过程中是否持续增长,如果静止时odom位置也在变化,说明底盘的轮式里程计标定有问题。Fishbot如果换成ESP32这类低成本小车,串口桥接通常要单独写一个serial节点,odom话题的坐标系和频率都要重新对齐,这步检查会帮你提前发现误差来源。
6.3 一个我沿用至今的习惯
我接手任何一套导航系统,第一件事永远是保存一份启动前十分钟的rosbag和view_frames PDF,作为后续排查比对的基线。出问题的时候,先和基线对比是TF变了还是话题频率变了,比在远传日志里翻快得多。这个习惯救过我很多次——有一次实车导航莫名偏移,最后发现是换了一块雷达后base_link -> laser的静态变换漏更新了,而RVIZ里看起来一切正常,只有TF树对比暴露了问题。
仿真阶段养成的检查习惯,到实车上全部能复用。Gazebo里可以容忍的配置错误,实车会以撞墙或原地转圈的方式还给你。希望这份从建环境到导航验证的完整记录能帮你少走几步弯路,顺利把这个项目跑通。
本文还有配套的精品资源,点击获取