🏗️ 第10课:Gazebo仿真基础

工具与仿真 ✅ Docker验证通过

📋 课程目标

🧠 Gazebo是什么?

Gazebo是一个强大的机器人仿真平台,提供物理引擎、传感器模拟和3D可视化。它允许在虚拟环境中测试机器人算法,无需真实硬件。

┌────────────────────────────────────────────────────┐ │ Gazebo 仿真架构 │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ 物理引擎 │ │ 渲染引擎 │ │ │ │ ODE/Bullet │ │ OGRE │ │ │ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ ┌──────┴─────────────────┴───────┐ │ │ │ Gazebo Server │ │ │ │ ┌─────────┐ ┌──────────────┐ │ │ │ │ │ World │ │ Sensors │ │ 传感器模拟 │ │ │ │ Model │ │ Plugin │ │ │ │ │ └─────────┘ └──────────────┘ │ │ │ └──────────────┬─────────────────┘ │ │ │ gz_bridge / ros_gz │ │ ┌──────────────┴─────────────────┐ │ │ │ ROS2 话题 │ │ │ │ /cmd_vel /scan /odom /tf │ │ │ └────────────────────────────────┘ │ └────────────────────────────────────────────────────┘

🎮 启动Gazebo

# 安装Gazebo与ROS2桥接
sudo apt install ros-humble-gazebo-ros-pkgs

# 启动空世界
ros2 launch gazebo_ros gazebo.launch.py

# 启动自定义世界
ros2 launch gazebo_ros gazebo.launch.py world:=/path/to/my_world.world

# Gazebo经典版(Gazebo 11)
gazebo --verbose

🌍 创建仿真世界

<?xml version="1.0" ?>
<!-- my_world.world - 自定义仿真世界 -->
<sdf version="1.6">
  <world name="my_robot_world">
    
    <physics type="ode">
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1.0</real_time_factor>
    </physics>

    
    <include>
      <uri>model://sun</uri>
    </include>

    
    <include>
      <uri>model://ground_plane</uri>
    </include>

    
    <model name="box_obstacle">
      <static>true</static>
      <pose>3 2 0.5 0 0 0</pose>
      <link name="link">
        <collision name="collision">
          <geometry>
            <box><size>1 1 1</size></box>
          </geometry>
        </collision>
        <visual name="visual">
          <geometry>
            <box><size>1 1 1</size></box>
          </geometry>
          <material>
            <ambient>0.8 0.2 0.2 1</ambient>
          </material>
        </visual>
      </link>
    </model>

    
    <model name="cylinder_obstacle">
      <static>true</static>
      <pose>-2 3 0.75 0 0 0</pose>
      <link name="link">
        <collision name="collision">
          <geometry>
            <cylinder><radius>0.5</radius><length>1.5</length></cylinder>
          </geometry>
        </collision>
        <visual name="visual">
          <geometry>
            <cylinder><radius>0.5</radius><length>1.5</length></cylinder>
          </geometry>
          <material>
            <ambient>0.2 0.2 0.8 1</ambient>
          </material>
        </visual>
      </link>
    </model>

    
    <model name="wall_1">
      <static>true</static>
      <pose>0 5 0.5 0 0 0</pose>
      <link name="link">
        <collision name="collision">
          <geometry><box><size>10 0.2 1</size></box></geometry>
        </collision>
        <visual name="visual">
          <geometry><box><size>10 0.2 1</size></box></geometry>
          <material><ambient>0.5 0.5 0.5 1</ambient></material>
        </visual>
      </link>
    </model>

  </world>
</sdf>

🤖 URDF中添加Gazebo插件

<?xml version="1.0"?>
<!-- 差速驱动机器人URDF (含Gazebo插件) -->
<robot name="diff_bot">
  
  <link name="base_link">
    <visual>
      <geometry><box size="0.5 0.3 0.15"/></geometry>
      <material name="blue"><color rgba="0 0 0.8 1"/></material>
    </visual>
    <collision>
      <geometry><box size="0.5 0.3 0.15"/></geometry>
    </collision>
    <inertial>
      <mass value="5.0"/>
      <inertia ixx="0.1" ixy="0" ixz="0" iyy="0.2" iyz="0" izz="0.2"/>
    </inertial>
  </link>

  
  <link name="left_wheel">
    <visual>
      <geometry><cylinder radius="0.075" length="0.05"/></geometry>
      <material name="black"/>
    </visual>
    <collision>
      <geometry><cylinder radius="0.075" length="0.05"/></geometry>
    </collision>
    <inertial>
      <mass value="0.5"/>
      <inertia ixx="0.001" ixy="0" ixz="0" iyy="0.001" iyz="0" izz="0.001"/>
    </inertial>
  </link>

  <joint name="left_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="left_wheel"/>
    <origin xyz="0 0.175 0" rpy="-1.5707 0 0"/>
    <axis xyz="0 0 1"/>
  </joint>

  
  <link name="right_wheel">
    <visual>
      <geometry><cylinder radius="0.075" length="0.05"/></geometry>
    </visual>
    <collision>
      <geometry><cylinder radius="0.075" length="0.05"/></geometry>
    </collision>
    <inertial>
      <mass value="0.5"/>
      <inertia ixx="0.001" ixy="0" ixz="0" iyy="0.001" iyz="0" izz="0.001"/>
    </inertial>
  </link>

  <joint name="right_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="right_wheel"/>
    <origin xyz="0 -0.175 0" rpy="-1.5707 0 0"/>
    <axis xyz="0 0 1"/>
  </joint>

  
  <link name="laser_link">
    <visual>
      <geometry><cylinder radius="0.05" length="0.04"/></geometry>
    </visual>
  </link>

  <joint name="laser_joint" type="fixed">
    <parent link="base_link"/>
    <child link="laser_link"/>
    <origin xyz="0.25 0 0.1"/>
  </joint>

  

  
  <gazebo>
    <plugin name="diff_drive" filename="libgazebo_ros_diff_drive.so">
      <alwaysOn>true</alwaysOn>
      <update_rate>50</update_rate>
      <left_joint>left_wheel_joint</left_joint>
      <right_joint>right_wheel_joint</right_joint>
      <wheel_separation>0.35</wheel_separation>
      <wheel_diameter>0.15</wheel_diameter>
      <command_topic>cmd_vel</command_topic>
      <odometry_topic>odom</odometry_topic>
      <odometry_frame>odom</odometry_frame>
      <base_frame_id>base_link</base_frame_id>
      <publish_odom>true</publish_odom>
      <publish_odom_tf>true</publish_odom_tf>
      <publish_wheel_tf>true</publish_wheel_tf>
      <max_wheel_torque>20</max_wheel_torque>
      <max_wheel_acceleration>1.0</max_wheel_acceleration>
    </plugin>
  </gazebo>

  
  <gazebo reference="laser_link">
    <sensor name="laser" type="ray">
      <pose>0 0 0 0 0 0</pose>
      <visualize>true</visualize>
      <update_rate>10</update_rate>
      <ray>
        <scan>
          <horizontal>
            <samples>360</samples>
            <resolution>1</resolution>
            <min_angle>-3.14159</min_angle>
            <max_angle>3.14159</max_angle>
          </horizontal>
        </scan>
        <range>
          <min>0.1</min>
          <max>10.0</max>
          <resolution>0.01</resolution>
        </range>
      </ray>
      <plugin name="laser_plugin" filename="libgazebo_ros_ray_sensor.so">
        <ros>
          <namespace>/</namespace>
          <remapping>~/out:=scan</remapping>
        </ros>
        <output_type>sensor_msgs/LaserScan</output_type>
      </plugin>
    </sensor>
  </gazebo>
</robot>

🐍 Spawn机器人Launch文件

#!/usr/bin/env python3
"""在Gazebo中Spawn机器人"""

import os
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription, ExecuteProcess
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory


def generate_launch_description():
    pkg_dir = get_package_share_directory('my_robot_pkg')
    urdf_file = os.path.join(pkg_dir, 'urdf', 'diff_bot.urdf')

    # 启动Gazebo
    gazebo = IncludeLaunchDescription(
        PythonLaunchDescriptionSource([
            os.path.join(get_package_share_directory('gazebo_ros'),
                        'launch', 'gazebo.launch.py')
        ]),
        launch_arguments={'world': os.path.join(
            pkg_dir, 'worlds', 'my_world.world')}.items()
    )

    # 发布机器人描述
    robot_state_publisher = Node(
        package='robot_state_publisher',
        executable='robot_state_publisher',
        parameters=[{'robot_description': open(urdf_file).read()}],
        output='screen'
    )

    # Spawn机器人到Gazebo
    spawn_entity = Node(
        package='gazebo_ros',
        executable='spawn_entity.py',
        arguments=[
            '-entity', 'diff_bot',
            '-file', urdf_file,
            '-x', '0', '-y', '0', '-z', '0.1',
        ],
        output='screen'
    )

    return LaunchDescription([
        gazebo,
        robot_state_publisher,
        spawn_entity,
    ])

🎮 控制仿真机器人

# 键盘控制
ros2 run teleop_twist_keyboard teleop_twist_keyboard

# 命令行发布速度
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.5}, angular: {z: 0.3}}"

# 查看里程计
ros2 topic echo /odom

# 查看激光扫描
ros2 topic echo /scan --once

🎯 练习题

📝 练习1:创建带障碍物的世界

创建一个包含墙壁、箱子和圆柱的Gazebo世界,在其中Spawn差速驱动机器人。

📝 练习2:添加传感器插件

在URDF中添加IMU传感器插件,查看 /imu 话题数据。

📝 练习3:键盘遥控

使用teleop_twist_keyboard在Gazebo中遥控机器人,观察里程计和激光数据变化。

🏆 成就解锁

🏅 仿真工程师

经验值:+200 XP