简介:本资源是一个基于ROS2与Gazebo的室内机器人仿真完整项目,面向机器人方向本科生、研究生及初入ROS开发的工程师,聚焦自主导航、环境感知与路径规划等核心能力训练,特别适合作为毕业设计或课程设计实践平台。压缩包共195个文件,涵盖31个Python节点脚本(实现传感器数据处理与控制逻辑)、32个CMake构建文件(支撑多模块编译)、13个xacro模型定义(构建TurtleBot3机器人URDF结构)、7个YAML配置文件(含nav2导航参数与地图元数据)以及关键的Map.pgm/map.yaml室内地图、Lidar_dataset.csv激光雷达数据集和nav2_myworld_view.rviz可视化配置,整体大小为11.24MB。已有90人学习下载,资源结构清晰,包含完整工作空间(turtlebot3_ws)、可直接运行的launch启动脚本、README.md使用指南及环境配置说明,开箱即用,便于快速复现仿真流程、调试导航行为并开展机器学习驱动的避障算法验证。
1. 这不是“跑通一个 demo”:ROS2 + Gazebo 室内仿真项目,是毕业设计/课程设计里真正能写进简历的闭环能力验证
你花三天装完 ROS2 Humble、配好 Gazebo、启动 turtlebot3_gazebo 的 world 文件,看到小车在空房间里转圈——这不叫“完成仿真”,这只是启动了黑匣子。真正的室内仿真项目,必须能闭环:从传感器数据(LIDAR/IMU/Camera)实时采集 → SLAM 构建八叉树地图 → AMCL 定位修正 → 基于 Nav2 的路径规划与动态避障 → 最终让机器人自主导航到指定房间并停稳。这个.zip包不是教学视频的配套代码,而是一套可验证、可调试、可扩展的工程级骨架:它预置了 Ubuntu 22.04 + ROS2 Humble 环境下已验证的 launch 配置、带语义标签的室内 SDF 模型(含门、桌、墙、插座等可交互结构)、适配 Nav2 的 costmap 参数集、以及关键节点的 debug 日志开关。适合正在做机器人方向毕业设计或课程设计的同学——你不需要从零搭环境、不用手搓 SDF 模型、更不用在nav2_bt_navigator和bt_behavior_tree之间反复试错。它解决的是“我知道原理,但卡在参数调不对、topic 不对齐、Gazebo 模型不响应”的真实痛点。项目结构清晰,C++/Python 混合,所有 launch 文件支持--ros-args -p use_sim_time:=true,直接对接 real-time clock 同步逻辑。
2. 从解压到首帧可视化:环境准备、模型加载与基础通信验证
2.1 环境依赖确认与最小化安装策略
这个项目严格绑定Ubuntu 22.04 LTS + ROS2 Humble(非 Foxy/FortyFive),且不兼容 Ubuntu 24.04 或 ROS2 Iron。很多同学在apt install ros-humble-desktop后发现gazebo_ros_pkgs缺失,是因为默认源未启用 universe 仓库:
sudo apt update && sudo apt install software-properties-common sudo add-apt-repository universe sudo apt update然后安装核心组件(注意顺序):
# 先装基础 ROS2 sudo apt install ros-humble-desktop # 再装 Gazebo 相关(关键!必须用 ros-humble-gazebo-ros-pkgs,不是 gazebo11) sudo apt install ros-humble-gazebo-ros-pkgs ros-humble-gazebo-ros-control # 安装 Nav2(项目中 navigation_stack 使用 1.1.x 版本) sudo apt install ros-humble-nav2-bringup ros-humble-nav2-common ros-humble-nav2-controller # 安装 rviz2 插件(避免后续 rviz2 启动时报 missing plugin) sudo apt install ros-humble-rviz2 ros-humble-rviz-default-plugins提示:不要用
rosdep install --from-paths src --ignore-src -r -y自动装依赖——项目中config/下的 YAML 文件已固化参数,自动安装可能覆盖关键配置。所有依赖必须手动按上述列表执行。
2.2 解压后目录结构与关键文件定位
解压based_on_ros2_gazebo_indoor_simulation.zip后,你会看到标准 ROS2 工作空间结构:
ros2_indoor_ws/ ├── src/ │ ├── indoor_robot_description/ # URDF/SDF 模型包(含带 collision mesh 的 room.sdf) │ ├── indoor_navigation/ # Nav2 配置包(param/, launch/, behavior_trees/) │ ├── indoor_perception/ # LIDAR 数据预处理节点(pointcloud_to_laserscan) │ └── indoor_control/ # 底盘控制节点(diff_drive_controller 配置) ├── config/ # 全局参数(如 map_server.yaml, slam_toolbox.yaml) ├── launch/ # 主启动入口(indoor_sim.launch.py) └── setup.bash # 已预设 source /opt/ros/humble/setup.bash重点文件:
src/indoor_robot_description/models/indoor_office.world:Gazebo 加载的主 world 文件,含<include>引用room.sdf和turtlebot3_waffle_pi.modelsrc/indoor_navigation/param/nav2_params.yaml:已调优的costmap_common_params.yaml中obstacle_layer启用了track_unknown_space: true,这是解决“门口卡死”的关键launch/indoor_sim.launch.py:主入口,默认启用use_sim_time:=True和log_level:=INFO,便于排查时间同步问题
2.3 启动仿真并验证基础通信链路
进入工作空间,编译并 source:
cd ~/ros2_indoor_ws colcon build --packages-select indoor_robot_description indoor_navigation indoor_perception indoor_control source install/setup.bash启动仿真(后台运行 Gazebo GUI,前台输出日志):
ros2 launch indoor_navigation indoor_sim.launch.py此时应看到:
- Gazebo 窗口弹出,显示带走廊、办公室隔断、桌椅的室内场景
- 终端持续输出
[INFO] [xxx]: Loaded robot description from indoor_robot_description ros2 topic list应至少看到以下 topic:/clock # Gazebo 发布的 sim time,所有节点必须订阅 /tf # robot_state_publisher 发布的坐标变换 /scan # simulated LIDAR 数据(/front_laser) /robot_description # URDF 字符串(供 robot_state_publisher 使用)
验证关键链路是否打通:
# 检查 TF 树是否完整(必须有 map->odom->base_link->laser) ros2 run tf2_tools view_frames # 生成 frames.pdf,打开后确认无断裂(尤其检查 odom 到 base_link 是否存在) # 检查 scan 数据是否有效(非全零、有角度范围) ros2 topic echo /scan | head -n 20 # 正常输出应类似:angle_min: -1.5707963267948966, angle_max: 1.5707963267948966, ranges: [1.2, 1.3, ..., 0.8]如果/scan数据全为inf或0.0,说明 LIDAR 模型未正确挂载——需检查indoor_robot_description/urdf/turtlebot3_waffle_pi.urdf.xacro中<gazebo reference="front_castor">是否被误删(项目包中已修复此坑)。
3. SLAM 构建与八叉树地图生成:从 raw scan 到可导航语义地图
3.1 为什么选 SLAM Toolbox 而非 Cartographer?
项目采用slam_toolbox(而非 Cartographer)的核心原因有三:
- ROS2 Humble 原生支持:Cartographer 在 Humble 中需手动编译,且
cartographer_ros的ros2分支长期处于 beta;而slam_toolbox是官方维护包,apt install ros-humble-slam-toolbox即可开箱即用; - 八叉树地图兼容性:
slam_toolbox的occupancy_grid输出可直接被nav2_costmap_2d解析,其maptopic 的data字段为int8[],与nav2的OccupancyGrid消息完全匹配; - 实时重定位能力:当机器人被拖动后,
slam_toolbox的relocalizeservice 可触发快速重定位,这对课程设计中频繁 reset 场景至关重要。
启动 SLAM(在新终端):
ros2 launch slam_toolbox online_async_launch.py \ params_file:=/home/user/ros2_indoor_ws/src/indoor_navigation/config/slam_toolbox.yaml \ use_sim_time:=trueslam_toolbox.yaml关键参数说明:
| 参数 | 值 | 作用 |
|---|---|---|
odom_frame:"odom" | 必须与nav2的odomframe 一致,否则 costmap 偏移 | |
map_frame:"map" | 所有导航模块的全局参考系 | |
base_frame:"base_link" | 机器人底盘坐标系 | |
resolution:0.05 | 地图分辨率(米/像素),0.05 是室内精度与性能平衡点 | |
max_laser_range:10.0 | 过滤超出 10 米的无效 scan 数据,防噪 |
3.2 实时构建地图并保存为 PGM/YAML
SLAM 启动后,机器人会原地旋转建图。观察 RVIZ2(新终端):
rviz2 -d /home/user/ros2_indoor_ws/src/indoor_navigation/rviz/slam.rvizRVIZ2 中需添加:
Map显示类型,Topic 选/mapTF显示类型,确保map→odom→base_link链路绿色LaserScan显示类型,Topic 选/scan
当地图轮廓稳定(约 2 分钟),执行保存:
ros2 run nav2_map_server map_saver_cli -f /home/user/ros2_indoor_ws/maps/indoor_office该命令生成:
indoor_office.pgm:灰度地图(0=空闲,100=障碍,200=未知)indoor_office.yaml:元数据(含resolution: 0.05,origin: [-10.0, -10.0, 0.0])
注意:
map_saver_cli保存的是nav2_map_server的当前 map,不是slam_toolbox的内部地图。因此必须确保slam_toolbox的/maptopic 正在发布,且nav2_map_server已通过map_servernode 订阅该 topic(项目launch/indoor_sim.launch.py中已配置)。
3.3 验证八叉树地图的导航可用性
单纯 PGM 地图无法支持 3D 导航(如楼梯、多层)。本项目预置了octomap_server节点(位于indoor_perception包),用于生成 3D 占据栅格:
ros2 run octomap_server octomap_server_node \ _sensor_model:=laser \ _laser_topic:=/scan \ _frame_id:=base_link \ _base_frame_id:=base_link \ _world_frame_id:=map \ __params:=/home/user/ros2_indoor_ws/src/indoor_navigation/config/octomap.yaml关键验证点:
ros2 topic hz /octomap_full应输出average rate: 1.001 Hz(表示 octomap 正在更新)- 在 RVIZ2 中添加
OccupancyGrid显示,Topic 选/octomap_full,应看到 3D 点云堆叠效果(非平面) ros2 topic echo /octomap_full | head -n 5查看消息结构,确认data字段为uint8[],且info.height > 1(证明非 2D)
若/octomap_full无数据,检查octomap.yaml中sensor_model是否为laser(非depth),且laser_topic是否拼写为/scan(非/front/scan)。
4. Nav2 导航栈配置与动态避障:让机器人真正“理解”门和走廊
4.1 Nav2 参数分层与本项目的定制逻辑
Nav2 不是单个节点,而是由bt_navigator,controller_server,planner_server,recoveries_server,local_costmap/global_costmap等 7+ 个节点协同工作。本项目将参数分为三层:
| 层级 | 文件位置 | 作用 | 本项目关键修改 |
|---|---|---|---|
| 全局层 | config/nav2_params.yaml | 定义map_server,bt_navigator的基础行为 | use_sim_time: true,autostart: true |
| Costmap 层 | config/costmap_common_params.yaml | 定义障碍物来源、膨胀半径、坐标系 | inflation_radius: 0.55,track_unknown_space: true(解决门口未知区域误判) |
| 行为树层 | behavior_trees/navigate_to_pose_w_replanning.xml | 定义导航失败时的 recovery 行为 | 插入ClearGlobalCostmap+Spin+BackUp三连 recovery |
启动完整导航栈:
ros2 launch indoor_navigation bringup_launch.py \ map:=/home/user/ros2_indoor_ws/maps/indoor_office.yaml \ use_sim_time:=true4.2 发送目标点并观察行为树执行流
使用 RVIZ2 的2D Goal Pose工具(顶部工具栏第二个图标)点击地图任意位置。此时观察终端日志:
[INFO] [xxx]: Received goal pose, forwarding to BT navigator[INFO] [xxx]: Executing tree: NavigateToPose[INFO] [xxx]: Starting local planner...[WARN] [xxx]: No valid path found, triggering recovery behaviors(若路径被桌腿挡住)
关键日志解读:
No valid path found不代表失败,而是触发 recovery 的信号Recovery behavior: ClearGlobalCostmap会清空 global costmap 的障碍缓存Recovery behavior: Spin让机器人原地旋转 180°,重新扫描环境Recovery behavior: BackUp控制机器人后退 0.3 米,避开局部最小值
提示:若 recovery 后仍失败,检查
config/costmap_common_params.yaml中obstacle_layer的enabled: true和max_obstacle_height: 2.0(必须 ≥ 机器人高度,否则桌腿被忽略)。
4.3 动态避障实战:让机器人绕过移动的人体模型
项目预置了person_model.sdf(位于src/indoor_robot_description/models/),可在 Gazebo 中动态插入:
# 在 Gazebo GUI 中:Insert → 选择 person_model → 拖入走廊 # 或命令行插入(在仿真运行时) ros2 run gazebo_ros spawn_entity.py \ -file /home/user/ros2_indoor_ws/src/indoor_robot_description/models/person_model.sdf \ -entity person_01 \ -x 2.5 -y 1.0 -z 0.0此时/scan数据会突增障碍点。观察local_costmap是否实时更新:
ros2 topic echo /local_costmap/costmap | grep -A 5 "data:" | head -n 10 # 正常应看到 data 数组中出现非零值(如 100, 200),且随 person 移动而变化若 costmap 无反应,检查config/local_costmap_params.yaml中:
plugins: ["obstacle_layer", "inflation_layer"]是否包含obstacle_layerobstacle_layer的observation_sources是否包含scanscan的topic是否为/scan(非/front_laser/scan)
5. 避坑指南:ROS2 + Gazebo 室内仿真最常翻车的 5 个硬核问题
5.1 现象:Gazebo 启动后机器人模型悬浮在空中,不接触地面
原因:URDF 中<gazebo reference="base_link">缺少<mu1>1.0</mu1><mu2>1.0</mu2>摩擦系数定义,或<collision>几何体未对齐visual。Gazebo 默认物理引擎(ODE)对无摩擦模型施加浮力。
解决:打开src/indoor_robot_description/urdf/turtlebot3_waffle_pi.urdf.xacro,在<gazebo reference="base_link">块内添加:
<mu1>1.0</mu1> <mu2>1.0</mu2> <fdir1>1 0 0</fdir1> <kp>1000000.0</kp> <kd>100.0</kd>并确保<collision>的<origin>与<visual>完全一致(Z 偏移必须为 0)。
5.2 现象:ros2 topic list看不到/tf,但robot_state_publisher进程在运行
原因:robot_state_publisher启动时未传入robot_description参数,或 URDF 文件路径错误导致解析失败。进程虽存活,但未发布任何 TF。
解决:检查launch/indoor_sim.launch.py中robot_state_publishernode 的parameters:
Node( package='robot_state_publisher', executable='robot_state_publisher', name='robot_state_publisher', output='screen', parameters=[{ 'robot_description': Command(['xacro ', LaunchConfiguration('model')]) # 必须指向 .xacro 文件 }] )并在终端手动验证 URDF 解析:
xacro /home/user/ros2_indoor_ws/src/indoor_robot_description/urdf/turtlebot3_waffle_pi.urdf.xacro > /tmp/test.urdf # 若报错 "XML parsing error",说明 xacro 语法错误(如未闭合 <property>)5.3 现象:SLAM 生成的地图边缘严重畸变,走廊变弯曲
原因:Gazebo 的real_time_update_rate与 ROS2 的use_sim_time不同步,导致slam_toolbox接收的 scan 时间戳跳跃。
解决:在indoor_sim.launch.py中强制设置 Gazebo 参数:
IncludeLaunchDescription( PythonLaunchDescriptionSource([os.path.join(pkg_gazebo_ros, 'launch'), '/gazebo.launch.py']), launch_arguments={ 'use_sim_time': 'true', 'gui': 'true', 'verbose': 'false', 'world': world_path }.items(), ), # 添加以下参数节点 Node( package='gazebo_ros', executable='gazebo_ros', name='gazebo_params', output='screen', parameters=[{ 'use_sim_time': True, 'real_time_update_rate': 1000.0 # 必须 ≥ 1000,否则 scan 时间戳抖动 }] )5.4 现象:Nav2 规划路径时反复绕远路,不走直线走廊
原因:global_costmap的inflation_layer膨胀半径过大,且cost_scaling_factor设置不当,导致走廊两侧被过度膨胀,路径被迫绕行。
解决:调整config/costmap_common_params.yaml:
inflation_layer: enabled: true inflation_radius: 0.35 # 从 0.55 降至 0.35(机器人半宽 0.17m + 安全余量) cost_scaling_factor: 10.0 # 从 10.0 降至 5.0,降低膨胀代价权重并重启bringup_launch.py。
5.5 现象:RVIZ2 中Map显示为空白,但/maptopic 有数据
原因:RVIZ2 的Map显示插件未正确订阅/maptopic,或frame_id不匹配(RVIZ2 中 Fixed Frame 设为map,但/map消息的header.frame_id是map,却因 TF 断裂导致无法转换)。
解决:
- 在 RVIZ2 中
Panels → Displays → Map → Topic下拉框选择/map Fixed Frame设为map(非odom或base_link)- 运行
ros2 run tf2_tools view_frames,确认map→odom→base_link链路完整(绿色) - 若仍空白,检查
/map消息的info.resolution是否为正数(负值会导致 RVIZ2 渲染失败)。
6. 进阶技巧:用 rosbag2 录制仿真数据 + 复现强化学习训练闭环
6.1 录制全链路仿真数据:从 sensor 到 action
课程设计常需提交“算法效果对比视频”,但手动操作不可复现。本项目预置了record_all.launch.py,一键录制全部关键 topic:
ros2 launch indoor_navigation record_all.launch.py \ bag_name:=/home/user/ros2_indoor_ws/bags/indoor_nav_demo_20240601 \ use_sim_time:=true该 launch 文件录制以下 topic(共 12 个):
| Topic | 用途 | 是否压缩 |
|---|---|---|
/tf | 坐标变换真值 | 否(文本体积小) |
/scan | LIDAR 原始数据 | 是(--compression-mode=FILE) |
/map | SLAM 地图 | 是(PGM 图像) |
/local_costmap/costmap | 局部代价地图 | 是 |
/cmd_vel | 底盘控制指令 | 否 |
/robot_state | 机器人状态(电量、模式) | 否 |
录制 5 分钟后按Ctrl+C停止,生成:
indoor_nav_demo_20240601_0.db3:SQLite3 格式 bag 文件metadata.yaml:包含录制时间、topic 列表、压缩参数
验证录制完整性:
ros2 bag info /home/user/ros2_indoor_ws/bags/indoor_nav_demo_20240601_0.db3 # 输出应显示 12 个 topic,且 `/scan` 的 message_count ≥ 3000(5 分钟 @ 10Hz)6.2 回放 bag 并注入到强化学习训练环
很多同学想用仿真数据训练 DRL 算法(如 PPO),但卡在“如何把 bag 数据喂给 PyTorch”。核心是将 bag 转为 HDF5 或 NPZ 格式,而非直接读取 rosbag2:
# replay_bag_to_npz.py import rosbag2_py import numpy as np from rclpy.serialization import deserialize_message from sensor_msgs.msg import LaserScan from geometry_msgs.msg import Twist # 初始化 reader reader = rosbag2_py.SequentialReader() reader.open( rosbag2_py.StorageOptions(uri="/path/to/bag", storage_id="sqlite3"), rosbag2_py.ConverterOptions( input_serialization_format="cdr", output_serialization_format="cdr" ) ) scans = [] cmds = [] while reader.has_next(): (topic, data, t) = reader.read_next() if topic == "/scan": msg = deserialize_message(data, LaserScan) scans.append(np.array(msg.ranges, dtype=np.float32)) elif topic == "/cmd_vel": msg = deserialize_message(data, Twist) cmds.append(np.array([msg.linear.x, msg.angular.z], dtype=np.float32)) # 保存为压缩 NPZ np.savez_compressed( "/home/user/ros2_indoor_ws/data/indoor_rl_train.npz", scans=np.array(scans), cmds=np.array(cmds) )该脚本生成indoor_rl_train.npz,可直接被 PyTorch DataLoader 加载:
dataset = np.load("/path/to/indoor_rl_train.npz") scans_tensor = torch.from_numpy(dataset['scans']).float() # shape: [N, 360] cmds_tensor = torch.from_numpy(dataset['cmds']).float() # shape: [N, 2]6.3 用 bag 复现故障:快速定位“机器人突然停止”问题
某次演示中机器人走到走廊尽头突然 halt,日志只显示Controller server failed。传统 debug 需重演场景,耗时 20 分钟。用 bag 可秒级复现:
# 回放 bag(不启动 Gazebo,仅发布 topic) ros2 bag play /home/user/ros2_indoor_ws/bags/indoor_nav_demo_20240601_0.db3 \ --remap /scan:=/replay_scan \ --clock # 启动 nav2(但订阅 replay_scan) ros2 launch indoor_navigation bringup_launch.py \ map:=/home/user/ros2_indoor_ws/maps/indoor_office.yaml \ use_sim_time:=true \ params_file:=/home/user/ros2_indoor_ws/src/indoor_navigation/config/nav2_params_replay.yaml在nav2_params_replay.yaml中,将scantopic 改为/replay_scan:
local_costmap: plugins: ["obstacle_layer"] obstacle_layer: observation_sources: [scan] scan: topic: /replay_scan # 关键!指向回放 topic此时controller_server会复现相同 failure,但你可在 VSCode 中 attach 调试器,断点在dwb_core/src/dwb_controller.cpp的computeVelocityCommands(),查看costmap输入是否为全 0(证明 obstacle_layer 未生效)。
从那以后我每次遇到“偶发性 halt”,都强制走一遍 bag 回放 + topic remap 流程——它比重启 Gazebo 快 8 倍,且能精准锁定是 sensor 数据异常、costmap 配置错误,还是 controller 逻辑缺陷。希望帮到你。
本文还有配套的精品资源,点击获取