
简介基于机器人操作系统ROS2的Fishbot机器人仿真与导航系统是一套融合仿真环境搭建、三维可视化、实时建图与自主导航的完整资源。它面向机器人专业学生、科研人员及ROS2初学者帮助使用者在没有实体机器人的情况下完成从模型加载、环境模拟、传感器数据获取到SLAM建图与路径规划的全链路验证。压缩包内共有48个文件主要包含功能脚本、参数配置、模型描述、仿真场景、地图数据等不同类型其中Python脚本用于控制逻辑XML与URDF定义机器人结构YAML保存导航参数RVIZ与World负责可视化与仿真PGM和Lua配合Cartographer完成地图构建。整套资源仅379KB轻巧便携目前已有241人学习下载。通过这套资源读者可以直观理解Fishbot的URDF建模过程掌握Cartographer在室内环境中的建图效果并运用Nav2框架实现自动避障与目标导航是快速切入机器人仿真与导航开发的实用资料。1. 为什么是Fishbot从URDF到自主导航的ROS2完整链路三年前我折腾ROS2 humble的时候最烦的不是写代码而是把URDF模型、Gazebo仿真、Cartographer建图、Nav2导航串成一条能跑的链路。不同教程用的机器人型号不一样包版本又对不上launch文件互相打架。这份基于ROS2的Fishbot机器人仿真与导航系统基本就是把这四件事打包好的样板Gazebo里有一个能受控的差速小车RVIZ里能看到激光和TFCartographer建出二维栅格地图Nav2再拿这张地图做路径规划。适合两类人一类是刚学ROS2想用完整项目理解仿真、SLAM、导航三次数据流怎么贯通另一类是已经在实际机器人上跑过但想先在仿真里验证算法参数避免一上来就改实车。我拆这份资源时反复对比过不同版本的launch和参数这两节里写的踩坑点基本都是你照着重跑一遍就会撞上的。2. 把Fishbot拉进GazeboURDF建模、仿真插件与话题核对Gazebo里看不到模型是最挫败的起步状态。这部分我拆包时先看的是模型文件和launch启动链而不是一上来就点运行。Fishbot这类差速小车URDF里真正决定行为的是轮距、轮径、惯性矩阵和Gazebo插件配置哪个少了轻则模型乱飞重则整个仿真直接崩掉。2.1 资源包里除了代码还有哪些家底我解压完这个zip第一件事是看目录结构。除了src和launch真正决定仿真能不能起来的文件集中在description和config里。description里放的是URDF/xacro模型里面定义了base_link、wheel和雷达linkconfig里则是Cartographer和Nav2的yaml参数。很多新手直接ros2 launch结果Gazebo起来后空无一物十有八九是模型文件路径没写对或者插件没加载。资源包里的模型文件一般分几块一个描述几何和关节的xacro一个写Gazebo插件和物理属性的gazebo文件还有一个robot_state_publisher的配置文件。fishbot/ ├── description/ │ ├── fishbot.urdf.xacro │ └── gazebo/ │ └── fishbot.gazebo.xacro ├── launch/ │ ├── gazebo.launch.py │ ├── cartographer.launch.py │ └── nav2.launch.py └── config/ ├── cartographer/ │ └── fishbot.lua └── nav2/ ├── nav2_params.yaml └── map.yaml这段结构不是标准答案但绝大多数ROS2机器人资源都长这样。你拿到其他项目时先找有没有gazebo这个子目录再确认launch文件里spawn_entity的参数是不是引用了正确路径。路径错了Gazebo不会报错只会静默地不生成机器人。2.2 URDF里藏着什么link、joint和gazebo插件Fishbot的底盘模型通常只有几个linkbase_link、两个驱动轮、一个万向轮以及雷达。URDF里最容易被忽略的是inertial尤其是虚设的惯性参数。有些教程直接把质量设为1kg惯性矩阵却全是0Gazebo的物理引擎一跑就报警轮子会无限打滑小车根本走不直。robot namefishbot xmlns:xacrohttp://www.ros.org/wiki/xacro link namebase_link visual geometrybox size0.3 0.2 0.1//geometry /visual collision geometrybox size0.3 0.2 0.1//geometry /collision inertial mass value2.0/ inertia ixx0.01 ixy0.0 ixz0.0 iyy0.02 iyz0.0 izz0.01/ /inertial /link /robot这段里collision和visual都用了同一个box不是偷懒而是为了减少碰撞计算的复杂度。你如果要做避障场景visual和collision可以不一样但至少要保证collision存在。inertia里的ixx、iyy、izz是绕三个轴的转动惯量数值别全写成0Gazebo会直接拒绝加载这个link报“inertia is zero”之类的错。移动机器人的ixx和iyy可以设成略小于izz因为底盘绕z轴旋转比绕x、y轴容易。接下来是diff_drive插件它决定了小车能不能响应/cmd_vel以及里程计/odom怎么产生。gazebo plugin namediff_drive filenamelibgazebo_ros_diff_drive.so ros namespace/fishbot/namespace remappingcmd_vel:/fishbot/cmd_vel/remapping /ros left_jointleft_wheel_joint/left_joint right_jointright_wheel_joint/right_joint wheel_separation0.16/wheel_separation wheel_diameter0.065/wheel_diameter command_topiccmd_vel/command_topic odometry_topicodom/odometry_topic /plugin /gazebowheel_separation是两个驱动轮与地面接触点之间的距离不是轮子中线之间的车宽差一两厘米在低速下无所谓但导航时转弯半径会明显偏。wheel_diameter在Gazebo里填的是包含轮胎的直径不能用轮毂半径。这里command_topic写到cmd_vel实际又被remap到了/fishbot/cmd_vel所以后边teleop和Nav2发速度时话题必须带/fishbot前缀否则小车纹丝不动。很多新手在这一步翻车不是没给速度而是话题名对不上。2.3 启动仿真launch文件与常用参数资源里的gazebo.launch.py一般会做三件事启动Gazebo服务器加载模型到参数服务器再把模型spawn到世界中。如果直接用xacro文件喂给spawn_entity会报无法解析。所以要先用xacro预处理成URDF再交给启动脚本。xacro fishbot.urdf.xacro fishbot.urdf ros2 launch fishbot_gazebo gazebo.launch.py如果launch文件里没有自动做预处理你可以手动生成后修改launch让它读取fishbot.urdf。另一个稳妥做法是用-topic robot_description把模型内容作为字符串传入ros2 run gazebo_ros spawn_entity.py \ -entity fishbot \ -param robot_description \ -x 0.0 -y 0.0 -z 0.0-param robot_description表示从参数服务器读模型这个参数通常由robot_state_publisher已经发布好了。这种方式比-file更不容易出路径问题因为文件路径一旦带了空格或中文Gazebo会静默报错。-x -y -z是初始位置在调试Cartographer时我会把y设为0x设成1或2方便地图从一开始就见得到机器人。2.4 怎么确认仿真真的跑起来了Gazebo界面出现小车不等于系统就是好的。我从拆这个资源起就固定跑三个检查命令缺一个都不继续往下走。ros2 topic list | grep -E odom|scan|cmd_vel ros2 run tf2_tools view_frames ros2 topic echo /odom --once | head -30topic list确认三个关键话题都在/odom、/scan、以及命名空间下的/cmd_vel。view_frames会生成一张TF树PDF你需要看到从base_link到odom到map的完整链路而不是只有两个孤立的坐标系。echo /odom则是验证里程计数值正常如果看到大量NaN说明惯性参数或插件配置可能错了。这三个检查都过了才说明仿真环境立住了可以进RVIZ做可视化。3. 让RVIZ“看见”机器人TF树、激光与可视化配置RVIZ本身不负责仿真它只负责可视化。很多人在Gazebo里看到机器人了RVIZ却空白原因集中在固定坐标系设置、话题名带命名空间、Display列表没加对三件事上。这一章我会把坐标系和Display配置拆开讲。3.1 把固定坐标系想清楚map、odom、base_link 三者的关系RVIZ里有个固定坐标系Fixed Frame默认通常是map。但刚启动仿真、还没开始建图时map坐标系可能根本不存在所以RVIZ里会报错机器人和点云全部隐藏。正确做法是先看TF树的根在哪里。ros2 run tf2_tools view_frames生成的PDF会展示坐标系之间的父子关系。建图前机器人的TF链一般是base_link - odom由robot_state_publisher和Gazebo的diff_drive插件共同发布。建图后Cartographer会额外发布map - odom。所以RVIZ里Fixed Frame填base_link或odom最保险填map则必须在Cartographer跑起来之后。如果你在RVIZ里把Fixed Frame设置为base_link机器人模型会始终显示在屏幕中央激光数据相对机器人固定看起来好像“整个世界都在动”这是正常的。等建图完成后再改成map就能看到机器人在固定地图里移动了。这里很多教程没说清导致我一直误以为TF断开了。3.2 添加RobotModel和LaserScan从空画面到完整状态RVIZ2默认画面只有网格和坐标轴要看到机器人需要手动加Display。这一步不复杂但每次启动都手动加很烦所以我会把配置保存成.rviz文件。资源里如果带了现成的rviz配置直接加载即可没有的话按下面的字段自己加。Displays: - Class: rviz_common/Displays/RobotModel Name: RobotModel Robot Description: robot_description Visual Enabled: true - Class: rviz_default_plugins/Displays/LaserScan Name: LaserScan Topic: Value: /scan Size: 0.05 Color: 255, 0, 0Robot Description对应robot_state_publisher发布的robot_description参数不要改成别的名字。LaserScan的Topic要填实际存在的激光话题如果你的雷达数据被包装成/fishbot/scan这里就要填完整路径否则RVIZ会一直等待数据。Size是激光点的像素大小太小了看不清我一般设0.05。Color用红色或彩色避免和地图撞色。添加完Display后画面里应该出现一个红色的小车轮廓周围点缀着一圈激光点。如果小车横七竖八乱飞说明TF没有完好衔接回到3.1去查TF树。3.3 话题被遮挡时怎么排查仿真里最容易遇到的现象是Gazebo有雷达显示RVIZ里却什么都没有。这通常是命名空间造成的。比如雷达在Gazebo里发布的是/scanrobot_state_publisher却把它放到了/fishbot/scanRVIZ默认订阅/scan自然收不到。ros2 topic list | grep scan ros2 topic echo /fishbot/scan --once | head -20第一行命令查看实际存在的scan话题。第二行直接订阅并打印一帧激光数据确认有内容而不是空数组。看到数据的报文之后再回到RVIZ的LaserScan Topic栏里填同样的路径。如果topic list里连/scan都没有说明雷达插件没启动或者命名空间remap失败去查launch文件里有没有对应remapping。另一个后知后觉的检查是rqt_graph它能很直观显示谁在发布、谁在订阅。ros2 run rqt_graph rqt_graph启动后如果看到话题之间没有连线问题基本就出在包名或命名空间上。这种方式在调试Nav2时尤其有用因为Nav2多个costmap会各自订阅不同的scan话题看一两眼就知道哪个节点把数据断开了。4. Cartographer建图实战参数调优与地图输出Cartographer在ROS2里的地位有点特殊它比gmapping更擅长处理回环但对参数敏感。用Fishbot在Gazebo里建图我一般先跑通默认参数再根据地图效果调一两个关键权重。4.1 为什么选Cartographer而不是gmappinggmapping的定位是轻量适合小房间和计算资源有限的场景。Cartographer引入了一个子图submap和回环闭合的概念在走廊、回环、重复纹理多的室内场景下地图漂移明显更小。Fishbot这类两轮差速小车的转向半径小车轮打滑时里程计漂得很快Cartographer的scan-to-submap匹配能把漂移拉回来。代价是参数多默认配置不一定适合你的底盘需要动几个量。如果你只想快速看到地图gmapping也许更快但想要一张能长时间重复使用、后续还能用于Nav2的地图Cartographer是更稳的选择。4.2 建图launch文件与Cartographer参数怎么配资源里的cartographer.launch.py一般会启动两个节点cartographer_node负责建图cartographer_occupancy_grid_node负责发布二维栅格占用地图。核心参数在lua文件里。include map_builder.lua include trajectory_builder.lua options { map_builder MAP_BUILDER, trajectory_builder TRAJECTORY_BUILDER, map_frame map, tracking_frame base_footprint, odom_frame odom, provide_odom_frame true, use_odometry true, num_laser_scans 1, num_subdivisions_per_laser_scan 1, } MAP_BUILDER.use_trajectory_builder_2d true TRAJECTORY_BUILDER_2D.submaps.num_accumulated_range_data 1map_frame一般固定是maptracking_frame是机器人身上用于跟踪的坐标系Fishbot通常是base_footprint如果你模型里没有footprint就填base_link。provide_odom_frame设为true表示Cartographer会维护map - odom的发布为false的话需要外部提供。use_odometry在这份资源里建议开启因为Gazebo的里程计噪声可控开启后能明显减少纯激光匹配的漂移。num_accumulated_range_data是每次累积几帧激光数据再匹配设成1能降低延迟设成2或3会提高稳定性但增加计算量。Gazebo仿真中我一般设成1因为数据来源干净不需要过度滤波。4.3 用键盘遥控走一圈并保存地图启动建图之前需要先把激光和TF链路查一遍。然后启动teleop节点控制小车。ros2 run teleop_twist_keyboard teleop_twist_keyboard \ --ros-args -r cmd_vel:/cmd_vel如果你的小车话题是/fishbot/cmd_vel把这个重映射改成对应名称。这里有个细节teleop_twist_keyboard的按键表默认是英文键盘如果输入法开着中文键盘响应会错乱按i车不走反倒触发其他字符。我一般会让终端聚焦并切成英文输入法再操作。走完一圈后执行存图命令ros2 service call /finish_trajectory cartographer_ros_msgs/srv/FinishTrajectory {trajectory_id: 0} ros2 service call /write_state cartographer_ros_msgs/srv/WriteState {filename: map.pbstream} ros2 run cartographer_ros cartographer_pbstream_to_ros_map \ -pbstream map.pbstream -map_filestem map第一行结束当前轨迹第二行把Cartographer的内部状态写到pbstream文件第三行把pbstream转成ROS通用的map.pgm和map.yaml。注意/write_state返回成功后再做第三步转换不要抢时间。生成的地图会出现在当前工作目录后续Nav2要用到这两个文件。如果地图有黑边或者范围不对八成是走线过程中机器人的初始位置没对准回头重走一遍即可。4.4 建图质量变差的三个参数方向建图质量差通常表现为地图有重影、墙角圆弧化、直线变波浪。第一个调参方向是use_odometry确认是否开启。第二个是TRAJECTORY_BUILDER_2D.ceres_scan_matcher.rotation_weight和translation_weight这两个权重控制scan matcher对旋转和位移的信任程度。我调过的地方是TRAJECTORY_BUILDER_2D.ceres_scan_matcher.rotation_weight 8.0 TRAJECTORY_BUILDER_2D.ceres_scan_matcher.translation_weight 1.0在Gazebo仿真中旋转往往比平移更难判断提高rotation_weight可以抑制转角处的地图漂移。如果发现虚影出现在直线段则降低translation_weight试试。第三个方向是TRAJECTORY_BUILDER_2D.submaps.resolution改成0.05能得到更精细的地图但建图速度会慢不少Fishbot这种低速小车还好。改完这些参数后重启建图节点再走一遍同样的路线观察效果。不要贪多一次只改一个参数否则你根本不知道是哪个改动起了作用。5. 避坑指南从RVIZ打不开到Nav2导航翻车这部分每一条都是我自己在虚拟机、远程桌面和实体环境下跑Fishbot时遇到过的。现象、原因、解决分开写方便你直接对照。5.1 RVIZ打不开或闪退现象执行ros2 run rviz2 rviz2后界面一闪而过终端提示OpenGL相关错误或干脆白屏卡死。原因RVIZ2依赖OpenGL在虚拟机或远程桌面环境里没有可用的GPU加速libGL加载失败。解决先用软件渲染启动export LIBGL_ALWAYS_SOFTWARE1 ros2 run rviz2 rviz2这行命令只影响当前shell不需要改系统配置。如果仍然闪退再检查是不是缺少mesa依赖安装后重试。软件渲染下RVIZ的操作会略显卡顿但数据流不会丢可以正常完成建图和导航过程。5.2 Gazebo在VMware里屏幕闪烁现象Gazebo窗口画面不断闪烁小车模型和地面交替消失像幻灯片一样。原因VMware的3D加速与Gazebo的渲染器冲突GPU加速反而导致画面撕裂。解决在启动Gazebo前同样设置软件渲染export LIBGL_ALWAYS_SOFTWARE1 gazebo --verbose--verbose能打印详细日志方便同时观察有没有其他插件加载错误。如果闪烁问题依旧去VMware的虚拟机设置里把3D加速关闭再启动一次。仿真物理计算不受影响只是画面由CPU渲染。5.3 Cartographer建图时地图跟随焦点乱动现象建图过程中RVIZ的画面不停自动移动地图没有固定住机器人模型反而在屏幕中央不动。原因RVIZ的固定坐标系设置成了base_link。当机器人移动坐标系本身也跟着移动地图看起来就乱晃。另一个原因是Cartographer的tracking_frame与模型坐标系不一致导致TF跳变。解决在RVIZ的Global Options里把Fixed Frame改为map或odom并检查launch中的tracking_frame如果模型是用base_link此处必须用base_link而不是base_footprint。改完后重启rviz2画面应保持稳定机器人在移动的地图上前进。5.4 Nav2导航时机器人原地转圈现象给Nav2发送目标点后机器人开始原地转圈速度一直很大但无法前进出有效路径。原因最常见的两个原因局部costmap的膨胀半径设置过大把可行通道全堵掉了或者tf的map - odom没有正常发布Nav2内部的定位跳来跳去。解决先看Nav2启动日志中有没有“Transform failure”之类的警告。再降低inflation_radiusFishbot这类小底盘我一般设0.4到0.8。同时检查全局costmap的robot_radius不要超过小车实际宽度的三分之一。如果还是转圈执行ros2 topic echo /cmd_vel观察Nav2发送的速度值看方向是否与目标点相反很多时候只是地图原点设错了。5.5 3D雷达适配Nav2时什么参数必须改现象把Gazebo里的雷达换成mid360这类3D雷达后Nav2完全不响应costmap上一直空白。原因3D雷达发布的是PointCloud2Nav2的传感器costmap默认订阅LaserScan两者话题类型对不上。解决加一个点云转激光的节点ros2 run pointcloud_to_laserscan pointcloud_to_laserscan --ros-args \ -p target_frame:base_link \ -p transform_tolerance:0.1 \ -p min_height:0.1 -p max_height:0.5target_frame填雷达frame或base_linktransform_tolerance是TF延时容差如果雷达数据时间戳和TF有偏差把它调大一点。min_height和max_height决定截取哪段高度范围通常设0.1到0.5高于0.5会扫到桌面或者人腿。改完后再让Nav2的costmap订阅转换后的/scan话题导航就能正常工作了。6. 把仿真当“预演”Nav2导航与重定位的验证技巧6.1 用Nav2把建好的图变成能导航的图建完图后Nav2要吃的是map.yaml和map.pgm两个文件。启动导航时直接把地图传进去ros2 launch fishbot_nav2 nav2.launch.py map:map.yamlNav2启动后RVIZ会加载栅格地图。此时机器人在地图上的位置是未知的需要你手动给定初始位姿点击上方“2D Pose Estimate”在地图上拖出一个带箭头的点这个操作会初始化AMCL粒子。在仿真里可以偷懒因为你知道Gazebo里小车的真实初始位置按这个位置给就行。给完初始位姿后观察一圈绿色粒子是否快速收敛到小车附近粒子若散开说明给定位置误差太大重新点一次比反复微调更有效。6.2 我固定的最终验证顺序折腾了一段时间后我现在每次跑Fishbot都会强制走一遍这个流程不跳步。顺序是先看TF树确认map - odom - base_link链路完整再停在一个位置静止两秒看/scan数据是否稳定没有激烈跳变接着手动发一个前进速度确认小车动起来后/odom方向和速度正负号都对然后才启动Cartographer跑完建图后保存地图最后才用Nav2导航。整套走完才敢说这个仿真环境是“合格”的。这套顺序帮我筛掉了至少一半的莫名故障尤其是把“机器人不走路”归因成模型坏了结果只是cmd_vel话题名或者invcision参数的问题。从那时起我不再依赖Gazebo界面来判断系统健康而是直接看话题数据和TF树。希望帮到你。本文还有配套的精品资源点击获取