程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

ROS2 - 智能车走马观碑组Gazebo仿真环境模型搭建

一、概述

1.1 为什么要仿真

搭建Gazebo仿真环境对于智能车比赛(特别是涉及视觉巡线、强化学习等算法开发)来说,不是可选项,而是最优解。以下是需要搭建仿真环境的核心理由,以及它能解决的实际问题。

1.1.1 硬件不足

问题:你现在没有久久派、摄像头、电机等硬件,但需要写程序、验证算法。

Gazebo的作用:

  • 提供虚拟的小车模型(带摄像头、激光雷达等传感器);
  • 提供虚拟的赛道环境(可以自定义颜色、形状、材质);
  • 程序写完后,直接在Gazebo里运行,效果等同于在真车上测试;

结果:硬件还没到,你的巡线算法已经跑通了。硬件一到,只需换底层驱动即可。

1.1.2 缩短调试周期
调试场景 真车调试 Gazebo仿真
修改PID参数 烧录→上电→跑一圈→观察→再烧录(5-10分钟/次) 改代码→保存→重启仿真(10秒/次)
小车撞墙 可能损坏硬件(电机、舵机、车架) 重置位置,继续调试
跑完一整圈 需要清场、充电、防止撞人 无限制运行,无人值守
测试极端情况 可能翻车、失控 完全安全

结论:仿真环境让的调试效率提升30-50倍。

1.1.3 提供可复现的测试环境

问题:真车测试时,光线变化、地面摩擦力、电池电量都会影响结果,今天跑通的代码明天可能就失效。

Gazebo的优势:

  • 每次启动都是完全相同的环境(相同的光照、相同的摩擦力、相同的传感器噪声);
  • 可以精确控制变量:比如只改变线条颜色,其他不变;
  • 算法性能变化只由代码改动引起,排除了环境干扰;

这对于调参、对比算法优劣至关重要。

1.1.4 支持强化学习训练

强化学习需要数百万次试错,这在真车上完全不可能:

训练需求 真车 Gazebo仿真
试错次数 几百次就报废 无限次
训练速度 1倍速 可以加速到10-100倍
并行训练 需要多台真车 开多个Gazebo实例
复位成本 手动搬回起点 代码一键复位

实例:训练一个简单的巡线RL模型,真车可能需要3个月+损坏5台车,仿真只需要1周+电费。

1.1.5 提前发现算法缺陷

仿真中可以轻松制造"事故场景":

  • 突然的强光照射(模拟阳光直射摄像头);
  • 赛道上有污渍(模拟线条部分缺失);
  • 传感器故障(模拟某个像素坏点);

这些在真车上很难刻意制造,但在仿真中可以随时开启。提前让你的算法适应这些情况,比赛时就不会翻车。

1.2 环境搭建步骤

走马观碑组Gazebo仿真环境搭建主要包含以下几个步骤:

① 环境准备(ubuntu + ROS2):这个可以参考《ROS2概述和基于RK3588的环境搭建》;

② 小车建模(URDF):有关URFD介绍可以参考《ROS2URDF建模》;

③ 赛道建模(World);

④ 视觉巡线算法开发。

1.2.1 工程目录简介

我们可以先创建工程目录,创建目录car_ws

zhengyang@ubuntu:~$ cd /opt/2k0300/loongson_2k300_lib
zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib$ mkdir -p car_ws/src
zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib$ cd car_ws/src

完整的目录结构大致如下,这个我们后续内容会依次创建:

car_ws/
├── src/
│   ├── car_description/      # 功能包1:机器人URDF模型
│   ├── car_gazebo/           # 功能包2:Gazebo仿真
│   └── car_vision/           # 功能包3:视觉算法

ROS的开发规范中,src/ 下的每个子目录叫功能包(Package),而不是独立项目。它们共同组成一个完整的机器人项目。

功能包 职责 修改频率
car_description 小车的物理模型(尺寸、颜色、传感器位置) 低(硬件确定后很少改)
car_gazebo 仿真环境、赛道、启动脚本、生成小车 中(换赛道时需要改)
car_vision 图像处理、巡线算法 高(天天调参)
1.2.2 功能包关系

功能包之间的协作关系如下:

启动顺序:
1. car_gazebo 启动仿真世界,生成小车模型
   └── 调用 car_description 中的 car.urdf 描述小车长什么样

2. car_vision 订阅摄像头图像
   └── 处理图像 → 发布速度指令

3. car_gazebo 中的 Gazebo 接收速度指令
   └── 驱动仿真小车运动

二、小车建模

car_description用于定义小车的物理模型(尺寸、颜色、传感器位置)。创建car_descriptionPython版本的功能包;

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src$ ros2 pkg create --build-type ament_python car_description

运行成功后,终端会显示创建的文件和目录信息。此时, car_description 功能包目录结构将如下所示:

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src$ tree ./car_description/
./car_description/
├── car_description  # 核心Python模块目录,用于存放Python代码
│   └── __init__.py
├── package.xml      # 功能包的描述文件(含依赖信息)
├── resource         # 资源文件夹
│   └── car_description
├── setup.cfg        # setuptools 的配置文件 
├── setup.py         # Python 包的安装脚本
└── test             # 测试文件夹

2.1 子目录

功能包创建好了,但按照规划我们需要在 car_description 中存放小车的URDF模型文件。这些不是.py文件,放在自动生成的 car_description 子目录下并不合适。

我们可以手动创建多个目录来更好地组织文件:

  • urdf: 专门存放 .urdf.xacro 模型文件;
  • launch:保存相关启动文件;
  • rviz:保存rviz的配置文件;
  • meshes:放置URDF中引用的模型渲染文件;
  • config: 存放Gazebo控制器的配置文件。

在终端中执行:

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src$ cd car_description/
zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src/car_description$ mkdir urdf config launch rviz meshes

我们需要修改setup.py文件,添加配置文件:

import os
from glob import glob

    ...

    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*.launch.py'))),
        (os.path.join('share', package_name, 'urdf'), glob(os.path.join('urdf', '*.*'))),
        (os.path.join('share', package_name, 'urdf/sensors'), glob(os.path.join('urdf/sensors', '*.*'))),
        (os.path.join('share', package_name, 'meshes'), glob(os.path.join('meshes', '*.*'))),
        (os.path.join('share', package_name, 'rviz'), glob(os.path.join('rviz', '*.rviz'))),
        (os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.*'))),
    ],

    ...

2.2 模型文件

接下来就是编写一个完整的 car.xacro 文件,这个模型包含车身、三个轮子、摄像头传感器,并且配置了Gazebo仿真所需的插件;

首先进入 urdf 目录并创建文件:

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src/car_description$ cd urdf/
2.2.1 camera_gazebo.xacro

urdf下创建子目录sensors,并创建camera_gazebo.xacro文件;

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="camera">

    <xacro:macro name="usb_camera" params="prefix:=camera">
        <!-- Create laser reference frame -->
        <link name="${prefix}_link">
            <inertial>
                <mass value="0.1" />
                <origin xyz="0 0 0" />
                <inertia ixx="0.01" ixy="0.0" ixz="0.0"
                         iyy="0.01" iyz="0.0"
                         izz="0.01" />
            </inertial>

            <visual>
                <origin xyz=" 0 0 0 " rpy="0 0 0" />
                <geometry>
                    <box size="0.01 0.04 0.04" />
                </geometry>
                <material name="black"/>
            </visual>

            <collision>
                <origin xyz="0.0 0.0 0.0" rpy="0 0 0" />
                <geometry>
                    <box size="0.01 0.04 0.04" />
                </geometry>
            </collision>
        </link>
        <gazebo reference="${prefix}_link">
            <material>Gazebo/Black</material>
        </gazebo>

        <gazebo reference="${prefix}_link">
            <sensor type="camera" name="camera">
                <update_rate>60.0</update_rate>
                <camera name="head">
                    <horizontal_fov>2.04</horizontal_fov>
                    <image>
                        <width>160</width>
                        <height>120</height>
                        <format>R8G8B8</format>
                    </image>
                    <clip>
                        <near>0.02</near>
                        <far>300</far>
                    </clip>
                    <noise>
                        <type>gaussian</type>
                        <mean>0.0</mean>
                        <stddev>0.007</stddev>
                    </noise>
                </camera>
                <plugin name="gazebo_camera" filename="libgazebo_ros_camera.so">
                    <alwaysOn>true</alwaysOn>
                    <updateRate>0.0</updateRate>
                    <cameraName>camera</cameraName>
                    <imageTopicName>image_raw</imageTopicName>
                    <cameraInfoTopicName>camera_info</cameraInfoTopicName>
                    <frameName>camera_link</frameName>
                    <hackBaseline>0.07</hackBaseline>
                    <distortionK1>0.0</distortionK1>
                    <distortionK2>0.0</distortionK2>
                    <distortionK3>0.0</distortionK3>
                    <distortionT1>0.0</distortionT1>
                    <distortionT2>0.0</distortionT2>
                </plugin>
            </sensor>
        </gazebo>

    </xacro:macro>
</robot>

其作用是给小车装上了“眼睛”。定义了一个针孔相机模型,并配置了ROS驱动,让Gazebo能把渲染出的图像发布成ROS话题(实时渲染图像并发布到ROS话题 /camera/image_raw)。

传感器(sensor)基础配置:

  • type="camera": 指定这是一个普通的光学摄像头(区别于深度相机 depth 或 激光雷达 ray);
  • name="camera": 传感器在Gazebo内部的名称,方便在Gazebo界面里调试;
  • update_rate: 刷新率 (Hz),表示每秒生成多少帧图像;

相机光学参数:

  • horizontal_fov: 水平视场角(单位:弧度),智能车竞赛通常使用广角镜头。如果觉得视野太窄,车还没看到弯道就已经撞墙了,可以把这个值改大,例如2.09 (120度);
  • width/height: 图像分辨率;
  • format:像素格式;
  • near: 最近裁剪距离(米),小于0.02米(2厘米)的物体不会被渲染。如果摄像头装得太低,地面可能会直接变黑或消失;
  • far: 最远裁剪距离(米),超过300米 的物体看不见。对于室内赛道,这个值无所谓;如果是室外大场景,要调大。

ROS插件配置:

  • filename:加载ROS的摄像头驱动库;
  • alwaysOn: 仿真一开始就启动摄像头;
  • updateRate: 注意!这里设为 0.0 的意思是“跟随 <sensor> 标签里的 update_rate”。如果这里设为非零值,它会覆盖上面的update_rate。建议保持0.0
  • cameraName: 话题的前缀;
  • imageTopicName: 图像话题名,最终发布的Topic/camera/image_raw
  • cameraInfoTopicName: 相机内参话题名最终发布的Topic/camera/camera_info。里面包含焦距、光心等标定参数;
  • frameNameTF坐标系名称;
    • 发布的图像消息里会包含 header.frame_id = "camera_link"
    • 必须和URDF中摄像头的 <link name="..."> 名字一致,否则TF变换会报错;
  • hackBaseline: 基线距离,这通常用于双目相机(两个镜头之间的距离)。单目相机设为0即可,这里保留默认值不影响单目使用;
  • 畸变参数:这里全是 0.0,意味着图像是完美的,没有畸变;
    • 现实对比:真实的广角摄像头会有“桶形畸变”(直线变弯);
    • 仿真优势:在仿真里关闭畸变是好事!这样图像处理算法(比如霍夫变换找直线)会更容易写,不用先做去畸变处理。
2.2.2 car_gazebo.xacro

urdf下创建car_gazebo.xacro文件;

点击查看详情
<?xml version="1.0"?>
<robot name="f_car_model" xmlns:xacro="http://www.ros.org/wiki/xacro">


  <!-- ==================== 1. 核心参数定义 (基于实测数据) ==================== -->

  <!-- 车身尺寸 (单位:米) -->
  <!-- 总长22.3cm = 后底盘13.5cm + 前支架8.8cm, 不算后轮超出部分 -->
  <xacro:property name="main_body_len" value="0.135"/>   <!-- 后部底盘  -->
  <xacro:property name="main_body_width" value="0.135"/> <!-- 后部宽  -->
  <xacro:property name="main_body_height" value="0.064"/> <!-- 车身总高 -->

  <xacro:property name="nose_len" value="0.088"/>      <!-- 前部支架 -->
  <xacro:property name="nose_width" value="0.077"/>    <!-- 前部宽 -->
  <xacro:property name="nose_height" value="0.064"/>   <!-- 与车身同高 -->

  <!-- 轮子参数 (直径6.4cm) -->
  <xacro:property name="wheel_diameter" value="0.064"/>
  <xacro:property name="wheel_radius" value="0.032"/>  <!-- 半径 -->
  <xacro:property name="wheel_width" value="0.027"/>   <!-- 宽度 -->
  <xacro:property name="wheel_track" value="0.155"/>   <!-- 轮距 -->

  <!-- 万向轮参数 -->
  <xacro:property name="caster_total_height" value="0.058"/>  <!-- 总高5.8cm -->
  <xacro:property name="caster_total_width" value="0.028"/>  <!-- 总宽2.8cm -->
  <xacro:property name="caster_small_wheel_radius" value="0.006"/> <!-- 小轮半径1.2cm -->
  <xacro:property name="caster_offset_x" value="0.0975"/>  <!-- 万向轮位于前部支架下方 -->
  
  <!-- 万向轮支架偏移(使小轮接触地面) -->
  <xacro:property name="caster_mount_height" value="${caster_total_height - caster_small_wheel_radius*2}"/>

  <!-- 质量参数(根据实际尺寸调整) -->
  <xacro:property name="mass_body" value="2.5"/>
  <xacro:property name="mass_wheel" value="0.2"/>
  <xacro:property name="mass_caster" value="0.2"/>
  <xacro:property name="mass_caster_mount" value="0.02"/>

  <!-- ==================== 2. 材质定义 ==================== -->
  <material name="black">
    <color rgba="0.1 0.1 0.1 1.0"/>
  </material>
  <material name="blue">
    <color rgba="0.0 0.0 0.6 1.0"/>
  </material>
  <material name="white">
    <color rgba="0.9 0.9 0.9 1.0"/>
  </material>
  <material name="silver">
    <color rgba="0.7 0.7 0.7 1.0"/>
  </material>
  <material name="gray">
    <color rgba="0.4 0.4 0.4 1.0"/>
  </material>

  <!-- ==================== 3. 宏定义:轮子 ==================== -->
  <xacro:macro name="wheel" params="prefix parent *origin">
    <link name="${prefix}_wheel">
      <visual>
        <geometry>
          <cylinder radius="${wheel_radius}" length="${wheel_width}"/>
        </geometry>
        <material name="black"/>
        <origin xyz="0 0 0" rpy="1.5707 0 0"/> <!-- 旋转90度让轮子立起来 -->
      </visual>
      <collision>
        <geometry>
          <cylinder radius="${wheel_radius}" length="${wheel_width}"/>
        </geometry>
        <origin xyz="0 0 0" rpy="1.5707 0 0"/>
      </collision>
      <inertial>
        <mass value="${mass_wheel}"/>
        <inertia ixx="0.0005" ixy="0.0" ixz="0.0" iyy="0.0005" iyz="0.0" izz="0.0005"/>
      </inertial>
    </link>

    <joint name="${prefix}_wheel_joint" type="continuous">
      <parent link="${parent}"/>
      <child link="${prefix}_wheel"/>
      <axis xyz="0 1 0"/> <!-- 绕Y轴旋转 -->
      <xacro:insert_block name="origin"/>
    </joint>

    <!-- Gazebo 轮胎摩擦属性 -->
    <gazebo reference="${prefix}_wheel">
      <material>Gazebo/Black</material>
      <mu1 value="1.6"/> <!-- 摩擦系数 -->
      <mu2 value="2.0"/>
      <kp value="10000000.0" />
      <kd value="10.0" />
    </gazebo>
  </xacro:macro>

  <!-- ==================== 4. 宏定义:万向轮 ==================== -->
  <xacro:macro name="caster" params="prefix">
    <!-- 万向轮支架 -->
    <link name="${prefix}_caster_mount">
      <visual>
        <geometry>
          <box size="0.04 0.028 0.058"/>
        </geometry>
        <material name="gray"/>
        <origin xyz="0 0 0" rpy="0 0 0"/>
      </visual>
      <collision>
        <geometry>
          <box size="0.04 0.028 0.058"/>
        </geometry>
      </collision>
      <inertial>
        <mass value="${mass_caster_mount}"/>
        <inertia ixx="0.0001" ixy="0.0" ixz="0.0" iyy="0.0001" iyz="0.0" izz="0.0001"/>
      </inertial>
    </link>

    <joint name="${prefix}_caster_mount_joint" type="fixed">
      <parent link="base_link"/>
      <child link="${prefix}_caster_mount"/>
      <origin xyz="${caster_offset_x} 0 ${-main_body_height/2}" rpy="0 0 0"/>
    </joint>

    <!-- 万向轮小轮 -->
    <link name="${prefix}_caster_small_wheel">
      <visual>
        <geometry>
          <cylinder radius="${caster_small_wheel_radius}" length="0.02"/>
        </geometry>
        <material name="black"/>
        <origin xyz="0 0 0" rpy="1.5707 0 0"/>
      </visual>
      <collision>
        <geometry>
          <cylinder radius="${caster_small_wheel_radius}" length="0.02"/>
        </geometry>
        <origin xyz="0 0 0" rpy="1.5707 0 0"/>
      </collision>
      <inertial>
        <mass value="${mass_caster}"/>
        <inertia ixx="0.00005" ixy="0.0" ixz="0.0" iyy="0.00005" iyz="0.0" izz="0.00005"/>
      </inertial>
    </link>

    <joint name="${prefix}_caster_small_wheel_joint" type="continuous">
      <parent link="${prefix}_caster_mount"/>
      <child link="${prefix}_caster_small_wheel"/>
      <axis xyz="1 0 0"/>  <!-- 绕X轴旋转,模拟万向轮小轮滚动 -->
      <origin xyz="0 0 ${-caster_total_height/2 + caster_small_wheel_radius}" rpy="0 0 0"/>
    </joint>

    <!-- Gazebo 万向轮属性 -->
    <gazebo reference="${prefix}_caster_mount">
      <material>Gazebo/Gray</material>
      <mu1 value="0.3"/>
      <mu2 value="0.3"/>
    </gazebo>

    <gazebo reference="${prefix}_caster_small_wheel">
      <material>Gazebo/Black</material>
      <mu1 value="0.8"/>
      <mu2 value="0.8"/>
    </gazebo>
  </xacro:macro>

   <!-- ==================== 5. 宏定义:车身主体 ==================== -->
   <xacro:macro name="car_gazebo">
      <link name="base_link">
        <!-- 视觉部分:后部主体 其中心作为原点 -->
        <visual>
          <origin xyz="0 0 0" rpy="0 0 0"/>
          <geometry>
            <box size="${main_body_len} ${main_body_width} ${main_body_height}"/>
          </geometry>
          <material name="blue"/>
        </visual>

        <!-- 视觉部分:前部窄支架 -->
        <visual>
          <origin xyz="${main_body_len/2 + nose_len/2} 0 0" rpy="0 0 0"/>
          <geometry>
            <box size="${nose_len} ${nose_width} ${nose_height}"/>
          </geometry>
          <material name="silver"/>
        </visual>

        <!-- 碰撞属性 (简化为两个盒子,提高仿真稳定性) -->
        <collision>
          <origin xyz="0 0 0" rpy="0 0 0"/>
          <geometry>
            <box size="${main_body_len} ${main_body_width} ${main_body_height}"/>
          </geometry>
        </collision>
        <collision>
          <origin xyz="${main_body_len/2 + nose_len/2} 0 0" rpy="0 0 0"/>
          <geometry>
            <box size="${nose_len} ${nose_width} ${nose_height}"/>
          </geometry>
        </collision>

        <!-- 惯性参数 -->
        <inertial>
		<origin xyz="0 0 -0.015" rpy="0 0 0"/>
          <mass value="${mass_body}"/>
          <inertia ixx="0.0035" ixy="0.0" ixz="0.0" iyy="0.012" iyz="0.0" izz="0.0015"/>
        </inertial>
      </link>

      <!-- ==================== 轮子实例化 ==================== -->
      <!-- 左后轮 -->
      <xacro:wheel prefix="left" parent="base_link">
        <origin xyz="${-main_body_len/2 + 0.0075} ${wheel_track/2} ${-wheel_radius}" rpy="0 0 0"/>
      </xacro:wheel>

      <!-- 右后轮 -->
      <xacro:wheel prefix="right" parent="base_link">
        <origin xyz="${-main_body_len/2 + 0.0075} ${-wheel_track/2} ${-wheel_radius}" rpy="0 0 0"/>
      </xacro:wheel>

      <!-- 万向轮 -->
      <xacro:caster prefix="front" />

      <!-- ==================== Gazebo 插件 ==================== -->
      <gazebo>
        <plugin name="differential_drive_controller" filename="libgazebo_ros_diff_drive.so">
          <rosDebugLevel>Debug</rosDebugLevel>
          <publishWheelTF>false</publishWheelTF>
          <robotNamespace>/</robotNamespace>
          <publishTf>1</publishTf>
          <publishWheelJointState>true</publishWheelJointState>
          <alwaysOn>true</alwaysOn>
          <updateRate>50.0</updateRate>
          <left_joint>left_wheel_joint</left_joint>
          <right_joint>right_wheel_joint</right_joint>
          <wheel_separation>${wheel_track}</wheel_separation>
          <wheel_diameter>${wheel_diameter}</wheel_diameter>
          <broadcastTF>1</broadcastTF>
          <wheelTorque>5.0</wheelTorque>
          <wheelAcceleration>1.5</wheelAcceleration>
          <commandTopic>cmd_vel</commandTopic>
          <odometryFrame>odom</odometryFrame>
          <odometryTopic>odom</odometryTopic>
          <robotBaseFrame>base_link</robotBaseFrame>
        </plugin>
      </gazebo>
  </xacro:macro>

</robot>

这里我们通过通过 nosemain_body 的组合来模拟车模前窄后宽的形状,这更符合F车模的真实外观;

  • 长:22.3cm、宽13.5cm、高6.4cm,注意这里不算后轮超出部分;
  • 车轮:直径6.4cm、宽度2.7cm、轮距15.5cm
  • 万向轮:直径5.8cm、宽度2.8cm、小轮直径1.2cm

此外还要注意小车的质量设置,如果质量太轻,仿真的时候如果速度过快小车可能会翻车。

2.2.2.1 车身几何结构

使用了两个 <box> 来拼接车身;

  • 后部 (main_body):尺寸0.135m x 0.135m,这是电机和后轮所在的位置,比较宽;
  • 前部 (nose):尺寸0.088m x 0.077m,这是向前延伸的部分,模拟真实的窄支架;

总长:0.135m + 0.088m = 0.223m

当然如果熟悉SolidWorksFusion 360可以按照图片画一个精确的3D模型,将其导出为 .stl.dae 文件。在URDF中,把 <box> 标签换成:

<mesh filename="package://your_package/meshes/f_car_body.stl"/>

这样在rvizGazebo中看到的就完全是一模一样的车了。

2.2.2.2 轮距与轮子

轮距wheel_track 设为0.155m。轮子坐标通过 ${-main_body_len/2 + 0.0075} 计算,确保轮子安装在后底盘的边缘,而不是车身中心。

2.2.2.3 万向轮位置

万向轮安装在 caster_offset_x处,这大约在前部支架的中间位置,既保证了支撑稳定性,又不会因为太靠前而在转弯时产生过大的阻力矩。

2.2.5.4 差速驱动插件

我们在模型最后添加了差速驱动插件:可以让小车动起来,对应真车:电机驱动器 + 编码器。

Gazebo仿真支持 = 给虚拟小车装上“电机、摄像头、物理轮胎”,让它能在虚拟世界里被控制、感知环境,像真车一样跑起来。

libgazebo_ros_diff_drive.soROSGazebo通信的桥梁,它的作用是:接收ROS的速度指令,转化为车轮的物理转动;同时将车轮的转动转化为里程计数据发回给ROS。即订阅 /cmd_vel 指令,计算左右轮速度,驱动小车运动;同时发布里程计 /odom

  <gazebo>
    <plugin name="differential_drive_controller" filename="libgazebo_ros_diff_drive.so">
      <rosDebugLevel>Debug</rosDebugLevel>
      <publishWheelTF>false</publishWheelTF>
      <robotNamespace>/</robotNamespace>
      <publishTf>1</publishTf>
      <publishWheelJointState>true</publishWheelJointState>
      <alwaysOn>true</alwaysOn>
      <updateRate>50.0</updateRate>
      <left_joint>left_wheel_joint</left_joint>
      <right_joint>right_wheel_joint</right_joint>
      <wheel_separation>${wheel_track}</wheel_separation>
      <wheel_diameter>${wheel_diameter}</wheel_diameter>
      <broadcastTF>1</broadcastTF>
      <wheelTorque>5.0</wheelTorque>
      <wheelAcceleration>1.5</wheelAcceleration>
      <commandTopic>cmd_vel</commandTopic>
      <odometryFrame>odom</odometryFrame>
      <odometryTopic>odom</odometryTopic>
      <robotBaseFrame>base_link</robotBaseFrame>
    </plugin>
  </gazebo>

其中:

  • name: 插件在Gazebo内部的名称,随便起,只要不重复就行;

  • filename:核心文件。这是ROS官方提供的差速驱动库。

  • rosDebugLevel: 调试等级。设为 Debug 会在终端打印大量信息(比如“我收到速度指令了”、“我发布里程计了”)。调试时很有用,正式运行可以改为 InfoWarn

  • robotNamespace: 命名空间。设为 / 表示使用全局命名空间。如果你有多台小车(比如小车A、小车B),这里可以设为 /robot_A,这样指令就不会串台;

  • left_joint/right_joint: 这里必须填在URDF中定义的关节名称;

  • wheel_separation: 左右轮之间的距离(即轮距),插件利用这个数据计算角速度:差速越大,转得越快;

  • wheel_diameter:轮子直径,插件利用这个数据计算线速度:轮子转一圈走多远;

  • wheelTorque: 最大输出扭矩(单位Nm),决定了车有多“大力”;

  • wheelAcceleration: 角加速度决定了车速变化的快慢。设为1.5表示速度增加比较平滑,不会瞬间从0变到100

  • commandTopic: 输入话题。插件会订阅这个话题(通常是 geometry_msgs/Twist 类型)。给这个topic发速度指令,车就会动;

  • odometryTopic: 输出话题。插件会把里程计数据发布到这个topic(通常是 nav_msgs/Odometry 类型);

  • odometryFrame: 里程计的父坐标系,通常叫 odom

  • robotBaseFrame: 机器人的基坐标系,通常叫base_link,插件会自动发布 odom -> base_linkTF变换;

  • alwaysOn: 只要仿真开始,插件就一直运行;

  • updateRate: 控制频率(Hz)。100Hz表示每秒计算100次物理响应,足够流畅;

  • publishTf: 是否发布TF变换树(必须设为1,否则导航包无法定位);

  • publishWheelTF: 是否发布轮子相对于车身的TF。一般不需要,设为 false 节省资源;

  • publishWheelJointState: 是否发布轮子的关节状态(位置、速度)。设为 true 后,可以在ROS里看到 /joint_states 话题,看到轮子转得多快。

2.2.3 car.xacro

urdf下创建car.xacro文件;

<?xml version="1.0"?>
<robot name="f_car_model" xmlns:xacro="http://www.ros.org/wiki/xacro">


  <xacro:include filename="$(find car_description)/urdf/car_gazebo.xacro" />
  <xacro:include filename="$(find car_description)/urdf/sensors/camera_gazebo.xacro" />


  <!-- 摄像头安装位置 -->
  <xacro:property name="camera_x" value="0.1"/>         <!-- 摄像头在车的位置,约10cm -->
  <xacro:property name="camera_y" value="0.0"/>
  <xacro:property name="camera_z" value="0.33"/>        <!-- 立杆 -->
  <xacro:property name="camera_pitch" value="0.524"/>   <!-- 俯视角度约30度 -->
  <xacro:property name="camera_roll" value="0.0"/>
  <xacro:property name="camera_yaw" value="0.0"/>

  <xacro:usb_camera prefix="camera"/>
  <xacro:car_gazebo/>

  <joint name="camera_joint" type="fixed">
    <parent link="base_link"/>
    <child link="camera_link"/>
    <!-- 摄像头安装在车头立杆顶端 -->
    <origin xyz="${camera_x} ${camera_y} ${camera_z}" rpy="${camera_roll} ${camera_pitch} ${camera_yaw}"/>
  </joint>
</robot>

为了适应“走马观碑”组别,摄像头高度 h = 33cmcamera_z),镜头视场角FOV = 140°(对角线),安装位置距车头10 cm

对于33cm高度、摄像头距车头10cm的情况,推荐pitch角度为30°35°

① 光轴指向距离

tan(pitch) = 高度 / 光轴指向距离 = 33cm / 0.577 ≈ 57cm

摄像头的正中心指向地面上距离车头投影点57cm的位置;

       摄像头 (33cm高)
          │
          │ 光轴 (30°向下)
          │
          ▼
    ───────────────────── 地面
       ← 57cm →   

② 图像近端距离

摄像头有一个垂直视场角(VFOV),140°镜头的垂直视场角约80°。这意味着光轴上下各覆盖40°

底部方向与水平面的夹角 = pitch + VFOV/2 = 30° + 40° = 70°
tan(70°) = 高度 / 近端距离

tan(70°) ≈ 2.75

近端距离 = 33cm / 2.75 ≈ 12cm

所以图像底部看到的地面距离摄像头投影点约12cm

       摄像头 (33cm高)
          │
          │ 光轴 (30°)
          │    ↘
          │      ↘ 40° (视野下边缘)
          │        ↘
    ──────┼──────────┼─────地面
         12cm       57cm
          ↑          ↑
         近端      光轴指向

140°广角镜头在边缘会有畸变,实际近端距离会比理论值更近(因为边缘被拉伸)。

实际近端距离 ≈ 12cm × (0.8~0.9) = 10-15cm

2.3 launch文件

launch文件夹下创建display.launch.py文件;

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch_ros.parameter_descriptions import ParameterValue
from launch_ros.actions import Node
from launch.substitutions import Command, LaunchConfiguration
from ament_index_python.packages import get_package_share_directory
import os

def generate_launch_description():
    # xacro 文件路径(注意扩展名是 .xacro)
    xacro_path = os.path.join(
        get_package_share_directory('car_description'),
        'urdf',
        'car.xacro'  # 关键:改为 .xacro
    )
    
    # 声明 model 参数
    model_arg = DeclareLaunchArgument(
        name='model',
        default_value=xacro_path,
        description='Absolute path to robot xacro file'
    )
    
    # 使用 xacro 命令解析 URDF
    robot_description = ParameterValue(
        Command(['xacro ', LaunchConfiguration('model')]),
        value_type=str
    )
    
    return LaunchDescription([
        model_arg,  # 必须包含这个声明
        
        # 机器人状态发布器
        Node(
            package='robot_state_publisher',
            executable='robot_state_publisher',
            output='screen',
            parameters=[{'robot_description': robot_description}]
        ),
        
        # 关节状态发布器 GUI(可手动拖动关节)
        Node(
            package='joint_state_publisher_gui',
            executable='joint_state_publisher_gui',
            name='joint_state_publisher_gui'
        ),
        
        # RViz2 可视化
        Node(
            package='rviz2',
            executable='rviz2',
            name='rviz2',
            arguments=['-d', os.path.join(get_package_share_directory('car_description'), 'rviz', 'car_display.rviz')],
	    	output='screen'
        )
    ])

这个Launch文件主要做三件事:

  • 加载机器人URDF模型(支持xacro格式);
  • 发布机器人的状态变换(TF);
  • rviz2中可视化机器人。
2.3.1 节点

脚本运行会创建以下几个节点:

  • joint_state_publisher_gui:发布每个joint(除fixed类型)的状态,可以通过UI界面对joint进行控制;
  • robot_state_publisher:将机器人各个linksjoints之间的关系,通过TF的形式,整理成三维姿态信息发布。
  • rviz2:在rviz2中可视化机器人;

joint_state_publisher这是一个官方ROS2包,主要功能:

  • 输入:
    • 读取URDF中的关节定义;
    • 接收用户或程序指定的关节角度;
  • 输出:
    • 发布 /joint_states 话题,消息类型为 sensor_msgs/msg/JointState
    • 包含所有关节的名称、位置、速度、力等信息。
2.3.2 数据流与节点关系

数据流与节点关系:

用户通过滑动条/GUI或程序 → joint_state_publisher_gui
                                     ↓ 发布/joint_states话题
                        robot_state_publisher
                                     ↓ 计算并发布TF变换
                              rviz2 和其他节点
                                     ↓ 接收TF并可视化

2.4 car_display.rviz

创建rviz配置文件,避免手动设置rviz,在rviz目录下新建car_display.rviz文件;

Panels:
  - Class: rviz_common/Displays
    Name: Displays
  - Class: rviz_common/Views
    Name: Views
Visualization Manager:
  Class: ""
  Displays:
    - Class: rviz_default_plugins/Grid
      Name: Grid
      Value: true
    - Alpha: 0.8
      Class: rviz_default_plugins/RobotModel
      Description Source: Topic
      Description Topic:
        Value: /robot_description
      Enabled: true
      Name: RobotModel
      Value: true
    - Class: rviz_default_plugins/TF
      Name: TF
      Value: true
  Global Options:
    Fixed Frame: base_link
    Frame Rate: 30
  Name: root
  Tools:
    - Class: rviz_default_plugins/MoveCamera
  Value: true
  Views:
    Current:
      Class: rviz_default_plugins/Orbit
      Distance: 1.7
      Name: Current View
      Pitch: 0.33
      Value: Orbit (rviz)
      Yaw: 5.5
Window Geometry:
  Height: 800
  Width: 1200

2.5 编译运行

2.4.1 编译

car_ws 目录下编译:

zhengyang@ubuntu:~$ cd /opt/2k0300/loongson_2k300_lib/car_ws

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws$ colcon build --paths src/car_description
.....
Finished <<< car_description [0.92s]

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws$ source install/setup.sh
2.4.2 运行

启动终端,运行如下命令;

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws$ ros2 launch car_description display.launch.py

可以看到rviz窗口打开,并且显示了小车模型;

取消左侧的TF勾选,可以更加清晰的看到小车模型;

三、Gazebo仿真环境

3.1 赛道制作

要实现“地面显示赛道图”,首先需要创建一个独立的地面模型,把图片作为纹理贴上去。以下是完整步骤:

3.1.1 创建模型文件夹结构

Gazebo的模型目录下创建地面模型文件夹(注意:这是用户目录下的.gazebo,不是系统目录):

zhengyang@ubuntu:~$ cd ~/.gazebo/models
zhengyang@ubuntu:~/.gazebo/models$ mkdir -p my_track_ground/materials/{scripts,textures}

最终目录结构:

~/.gazebo/models/
└── my_track_ground/
    ├── model.config
    ├── model.sdf
    └── materials/
        ├── scripts/
        │   └── track.material
        └── textures/
            └── my_track.jpg  
3.1.2 准备赛道图片

将你的赛道图片(JPEGPNG格式)放到textures/目录下:

zhengyang@ubuntu:~/.gazebo/models$ cp /home/zhengyang/桌面/my_track.jpg ~/.gazebo/models/my_track_ground/materials/textures/

图片建议:

  • 分辨率控制在1024x1024以内`,过大会导致加载缓慢或失败;
  • 图片内容应该是俯视视角的赛道布局图(从正上方看下去的视图)。
3.1.3 创建材质脚本文件

创建 materials/scripts/track.material 文件:

zhengyang@ubuntu:~/.gazebo/models$ vim ~/.gazebo/models/my_track_ground/materials/scripts/track.material

写入以下内容:

material Track/Ground
{
  technique
  {
    pass
    {
      ambient 1.0 1.0 1.0 1.0
      diffuse 0.8 0.8 0.8 1.0
      specular 0.0 0.0 0.0 1.0 2.0

      texture_unit
      {
        texture my_track.jpg
        filtering trilinear
      }
    }
  }
}

这是一个OGRE材质脚本,Gazebo底层使用OGRE渲染引擎,通过这个文件告诉系统“如何显示赛道图片”。

material Track/GroundTrack/Ground是这个材质的唯一标识符。在model.sdf中通过 <name>Track/Ground</name> 引用它。可以理解为给这个“皮肤”起了个名字。

关键点:texture my_track.jpg 这一行的文件名必须和图片文件名完全一致。

3.1.4 创建模型配置文件 model.config
zhengyang@ubuntu:~/.gazebo/models$ vim ~/.gazebo/models/my_track_ground/model.config

写入:

<?xml version="1.0"?>
<model>
  <name>My Track Ground</name>
  <version>1.0</version>
  <sdf version="1.6">model.sdf</sdf>
  <description>
    A ground plane with my custom track image
  </description>
</model>

这个文件是Gazebo模型的身份证,告诉系统“这是什么模型、版本多少、去哪里找模型描述文件”。

3.1.5 创建模型描述文件 model.sdf
zhengyang@ubuntu:~/.gazebo/models$ vim ~/.gazebo/models/my_track_ground/model.sdf

写入:

<?xml version="1.0" ?>
<sdf version="1.6">
  <model name="my_track_ground">
    <static>true</static>
    <link name="link">
      <!-- 碰撞属性 -->
      <collision name="collision">
        <geometry>
          <plane>
            <normal>0 0 1</normal>
            <size>5.5 5.5</size>
          </plane>
        </geometry>
        <surface>
          <friction>
            <ode>
              <mu>100</mu>
              <mu2>50</mu2>
            </ode>
          </friction>
        </surface>
      </collision>
      
      <!-- 视觉属性(显示图片的地方) -->
      <visual name="visual">
        <cast_shadows>false</cast_shadows>
        <geometry>
          <plane>
            <normal>0 0 1</normal>
            <size>5.5 5.5</size>
          </plane>
        </geometry>
        <material>
          <script>
            <uri>model://my_track_ground/materials/scripts</uri>
            <uri>model://my_track_ground/materials/textures</uri>
            <name>Track/Ground</name>
          </script>
        </material>
      </visual>
    </link>
  </model>
</sdf>

这是模型的核心结构文件,定义了地面模型的物理属性(碰撞、摩擦力)和视觉属性(图片纹理)。

关键点:

  • <size>5.5 5.5</size> 中的数字是地面的大小(单位:米),根据赛道实际尺寸调整;
  • <uri> 中的路径使用 model:// 协议,指向模型文件夹;
  • <name>Track/Ground</name> 必须与 .material 文件中的材质名完全一致。
3.1.6 测试模型

① 重启Gazebo(确保刷新模型缓存);

② 在左侧Insert标签页中,找到My Track Ground

③ 点击它,然后在3D场景中单击放置;

3.2 car_gazebo功能包

car_gazebo 是整个仿真项目的“启动与场景中心”,它负责把创建的所有组件(小车模型、赛道模型、物理环境)组装成一个可运行的仿真系统。其核心功能:

功能 说明
存放世界文件 提供 .world 文件,定义赛道、光照、物理参数
存放赛道模型 管理自定义模型( my_track_ground
启动 Gazebo 一键启动仿真服务器(gzserver)和 GUIgzclient
生成小车 car_description 中的小车模型放入仿真世界

接下来我们创建car_gazebo Python版本的功能包;

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src$ ros2 pkg create --build-type ament_python car_gazebo

运行成功后,终端会显示创建的文件和目录信息。此时, car_gazebo 功能包目录结构将如下所示:

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src$ tree ./car_gazebo/
./car_gazebo/
├── car_gazebo      # 核心Python模块目录,用于存放Python代码
│   └── __init__.py
├── package.xml      # 功能包的描述文件(含依赖信息)
├── resource         # 资源文件夹
│   └── car_gazebo
├── setup.cfg        # setuptools 的配置文件 
├── setup.py         # Python 包的安装脚本
└── test             # 测试文件夹
3.2.1 子目录

car_gazebo功能包创建好了,我们需要创建如下子目录;

  • worlds:存放.world世界场景文件,描述赛道、光照、物理参数等;
  • launch:存放启动脚本,一键启动完整仿真环境;
  • models:存放自定义模型;
  • config:存放Gazebo控制器的配置文件。

在终端中执行:

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src$ cd car_gazebo/
zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src/car_gazebo$ mkdir worlds config launch models

我们需要修改setup.py文件,告诉Python安装哪些资源文件;

import os
from glob import glob

    ...

    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*.launch.py'))),
        (os.path.join('share', package_name, 'worlds'), glob(os.path.join('worlds', '*.*'))),
        (os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.*'))),
        (os.path.join('share', package_name, 'models'), glob(os.path.join('models', '*.*'))),
    ],

    ...
3.2.2 放置赛道模型

复制赛道模型:

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src/car_gazebo$ cp -r ~/.gazebo/models/my_track_ground ./models/
3.2.3 创建世界文件

worlds/目录下创建track.world文件:

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src/car_gazebo$ cd worlds
zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src/car_gazebo/worlds$ vim track.world

填入以下内容:

<?xml version="1.0" ?>
<sdf version="1.6">
  <world name="car_world">
    
    <!-- 场景配置 -->
    <scene>
      <grid>true</grid>
      <origin_visual>true</origin_visual>
    </scene>

    <!-- 环境光照 -->
    <include>
      <uri>model://sun</uri>
    </include>

    <!-- 赛道模型(删除 ground_plane,避免 Z-fighting) -->
    <include>
      <uri>model://my_track_ground</uri>
      <pose>0 0 0 0 0 0</pose>
    </include>

    <!-- 物理引擎配置 -->
    <physics name="default_physics" default="true" type="ode">
      <!-- 步长:1ms -->
      <max_step_size>0.001</max_step_size>
      
      <!-- 目标更新率:1000Hz -->
      <real_time_update_rate>1000.0</real_time_update_rate>

      <!-- ODE 求解器配置 -->
      <ode>
        <solver>
          <type>quick</type>
          <iters>50</iters>
          <sor>1.3</sor>
        </solver>
        <constraints>
          <cfm>0.0</cfm>
          <erp>0.2</erp>
          <contact_max_correcting_vel>100.0</contact_max_correcting_vel>
          <contact_surface_layer>0.001</contact_surface_layer>
        </constraints>
      </ode>
      
      <!-- 禁止自动休眠 -->
      <enable_auto_disable>false</enable_auto_disable>
    </physics>

  </world>
</sdf>

世界文件是把创建的赛道模型、光照、物理规则组装成一个完整的仿真场景,让小车能在里面跑起来。

需要注意的是max_step_sizereal_time_update_rate影响仿真和实际时间;

  • <max_step_size>0.001</max_step_size>
    • 含义:仿真步长。
    • 解释:仿真世界每一步向前推进1毫秒 (0.001秒)。步长越小,仿真精度越高(物理计算更细腻),但计算量越大;
  • <real_time_update_rate>1000.0</real_time_update_rate>
    • 含义:目标实时更新率。
    • 解释:希望仿真器以1000Hz的频率运行,即每秒进行1000次物理计算。这通常与步长配合,以确保仿真速度能跟上真实时间。
3.2.4 launch文件

launch/目录下创建gazebo.launch.py

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

def generate_launch_description():
    # 获取功能包路径
    pkg_car_gazebo = get_package_share_directory('car_gazebo')
    pkg_car_description = get_package_share_directory('car_description')

    # ========== 关键:1. 设置 Gazebo 模型路径 ==========
    # 将功能包内的 models 目录添加到模型搜索路径
    gazebo_models_path = os.path.join(pkg_car_gazebo, 'models')
    
    # 获取原有的 GAZEBO_MODEL_PATH(如果存在)
    original_model_path = os.environ.get('GAZEBO_MODEL_PATH', '')
    
    # 拼接路径:新路径 + 分隔符 + 原路径
    if original_model_path:
        model_path = gazebo_models_path + ':' + original_model_path
    else:
        model_path = gazebo_models_path
    
    set_model_path = SetEnvironmentVariable(
        name='GAZEBO_MODEL_PATH',
        value=model_path
    )
    # ================================================

    # 2. 启动 Gazebo 服务端(加载世界)
    gazebo_server = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(get_package_share_directory('gazebo_ros'), 'launch', 'gzserver.launch.py')
        ),
        # 指定要加载的世界文件路径 
        launch_arguments={'world': os.path.join(pkg_car_gazebo, 'worlds', 'track.world')}.items()
    )

    # 3. 启动 Gazebo 客户端(GUI界面)
    gazebo_client = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(get_package_share_directory('gazebo_ros'), 'launch', 'gzclient.launch.py')
        )
    )

    # 4. 机器人状态发布器:启动robot_state_publisher节点
    robot_state_publisher = Node(
        package='robot_state_publisher',
        executable='robot_state_publisher',
        # 参数
        parameters=[{
            # 通过 xacro 命令解析 car.xacro 文件,得到 URDF 模型描述
            'robot_description': Command(['xacro ', os.path.join(pkg_car_description, 'urdf', 'car.xacro')]),
            # 设置为 True,告诉 ROS 使用仿真时间(/clock 话题)而不是系统时间
            'use_sim_time': True
        }]
    )

    # 5. 生成小车:调用Gazebo的服务,将小车模型生成到仿真世界中
    spawn_entity = Node(
        package='gazebo_ros',
        executable='spawn_entity.py',
        # 从 /robot_description 话题读取模型数据,给生成的小车命名为 f_car_model
        arguments=['-topic', 'robot_description', '-entity', 'f_car_model',
            '-x', '1.2', '-y', '2.25', '-z', '0.05'],
 #            '-x', '1.2', '-y', '2.25', '-z', '0.05',  '-Y', '3.14'],
 #            '-x', '-1.5', '-y', '-2.2', '-z', '0.05'],
 #            '-x', '2.2', '-y', '1.8', '-z', '0.05', '-Y', '-1.6' ],
         # 将节点的输出打印到终端
        output='screen'
    )

    return LaunchDescription([
        set_model_path,        # 1. 先设置环境变量
        gazebo_server,         # 2. 启动 Gazebo 服务端
        gazebo_client,         # 3. 启动 Gazebo 客户端
        robot_state_publisher, # 4. 发布机器人模型
        spawn_entity,          # 5. 在 Gazebo 中生成小车
    ])

整体流程如下:

┌─────────────────────────────────────────────────────────────┐
│                    ros2 launch 执行                         │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  1. 设置 GAZEBO_MODEL_PATH                                   │
│     让 Gazebo 能找到功能包内的 models/ 目录                    │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  2. 启动 gzserver(服务端)                                   │
│     - 加载 track.world(赛道、光照、物理参数)                  │
│     - 运行物理引擎                                           │
│     - 处理传感器数据                                         │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  3. 启动 gzclient(客户端)                                   │
│     - 显示 3D 图形界面                                       │
│     - 允许用户交互                                           │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  4. 启动 robot_state_publisher                               │
│     - 解析 car.xacro → URDF                                  │
│     - 发布到 /robot_description 话题                         │
│     - 发布 TF 坐标变换                                       │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  5. 生成小车(spawn_entity.py)                              │
│     - 从 /robot_description 读取模型                         │
│     - 调用 Gazebo 服务生成实体                               │
│     - 小车出现在赛道上                                       │
└─────────────────────────────────────────────────────────────┘
3.2.5 package.xml

修改package.xml添加以下依赖:

<depend>gazebo_ros</depend>
<depend>robot_state_publisher</depend>
<depend>car_description</depend>
<depend>geometry_msgs</depend>
<depend>sensor_msgs</depend>

<exec_depend>ros2launch</exec_depend>
<exec_depend>xacro</exec_depend>

<depend> :这种依赖在编译时和运行时都需要;

依赖 作用 为什么需要
gazebo_ros Gazebo 与 ROS2 的桥接包 提供 gzserver.launch.pygzclient.launch.pyspawn_entity.py 等启动文件和工具
robot_state_publisher 机器人状态发布器 将 URDF 模型发布到 /robot_description 话题,并计算 TF 坐标变换
car_description 你的小车描述包 car_gazebo 需要引用 car_description 中的 URDF 文件来生成小车
geometry_msgs 几何消息定义 提供 Twist(速度指令)、Pose(位置)等消息类型
sensor_msgs 传感器消息定义 提供 Image(图像)、LaserScan(激光雷达)等消息类型

<exec_depend> :这种依赖只在运行时需要,编译时不需要;

依赖 作用 为什么需要
ros2launch ROS2 启动系统 提供 ros2 launch 命令来执行你的 .launch.py 文件
xacro Xacro 宏处理器 car.xacro 文件解析成标准 URDF 格式

3.3 编译运行

3.3.1 编译

car_ws 目录下编译:

zhengyang@ubuntu:~$ cd /opt/2k0300/loongson_2k300_lib/car_ws

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws$ colcon build --paths src/car_gazebo
.....
Finished <<< car_description [0.92s]

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws$ source install/setup.sh
3.3.2 运行

启动终端,运行如下命令;

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws$ source /usr/share/gazebo/setup.sh
zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws$ ros2 launch car_gazebo gazebo.launch.py

其中第一条命令是Gazebo的环境初始化脚本,它设置了Gazebo运行所需的所有关键环境变量。执行它之后,Gazebo才知道去哪里找模型、纹理、插件等资源。

启动后,在终端中观察输出,应该能看到类似:

[INFO] [launch]: All log files can be found below /home/zhengyang/.ros/log/2026-04-06-22-46-04-945865-ubuntu-7770
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [gzserver-1]: process started with pid [7772]
[INFO] [gzclient-2]: process started with pid [7774]
[INFO] [robot_state_publisher-3]: process started with pid [7776]
[INFO] [spawn_entity.py-4]: process started with pid [7778]
[robot_state_publisher-3] [WARN] [1775486766.270936218] [kdl_parser]: The root link base_link has an inertia specified in the URDF, but KDL does not support a root link with an inertia.  As a workaround, you can add an extra dummy link to your URDF.
[robot_state_publisher-3] [INFO] [1775486766.271055446] [robot_state_publisher]: got segment base_link
[robot_state_publisher-3] [INFO] [1775486766.271113330] [robot_state_publisher]: got segment camera_link
[robot_state_publisher-3] [INFO] [1775486766.271122952] [robot_state_publisher]: got segment caster_mount
[robot_state_publisher-3] [INFO] [1775486766.271130386] [robot_state_publisher]: got segment caster_small_wheel
[robot_state_publisher-3] [INFO] [1775486766.271137270] [robot_state_publisher]: got segment left_wheel
[robot_state_publisher-3] [INFO] [1775486766.271144440] [robot_state_publisher]: got segment right_wheel
[spawn_entity.py-4] [INFO] [1775486766.463558272] [spawn_entity]: Spawn Entity started
[spawn_entity.py-4] [INFO] [1775486766.464229838] [spawn_entity]: Loading entity published on topic robot_description
[spawn_entity.py-4] [INFO] [1775486766.466313740] [spawn_entity]: Waiting for entity xml on robot_description
[spawn_entity.py-4] [INFO] [1775486766.478841252] [spawn_entity]: Waiting for service /spawn_entity, timeout = 30
[spawn_entity.py-4] [INFO] [1775486766.479458411] [spawn_entity]: Waiting for service /spawn_entity
[spawn_entity.py-4] [INFO] [1775486767.541013852] [spawn_entity]: Calling service /spawn_entity
[spawn_entity.py-4] [INFO] [1775486767.862541616] [spawn_entity]: Spawn status: SpawnEntity: Successfully spawned entity [f_car_model]
[INFO] [spawn_entity.py-4]: process has finished cleanly [pid 7778]
[gzserver-1] [INFO] [1775486768.417964902] [camera_controller]: Publishing camera info to [/camera/camera_info]
[gzserver-1] [INFO] [1775486768.499211805] [differential_drive_controller]: Wheel pair 1 separation set to [0.155000m]
[gzserver-1] [INFO] [1775486768.500293362] [differential_drive_controller]: Wheel pair 1 diameter set to [0.064000m]
[gzserver-1] [INFO] [1775486768.502493894] [differential_drive_controller]: Subscribed to [/cmd_vel]

同时在Gazebo GUI中应该能看到:

  • 赛道地面(带有赛道图片纹理);
  • 小车模型出现在赛道上。

如下图所示:

3.3.3 测试小车控制

打开第二个终端,启动键盘控制节点;

zhengyang@ubuntu:~/dev_ws$ ros2 run teleop_twist_keyboard teleop_twist_keyboard

This node takes keypresses from the keyboard and publishes them
as Twist/TwistStamped messages. It works best with a US keyboard layout.
---------------------------
Moving around:
   u    i    o
   j    k    l
   m    ,    .

For Holonomic mode (strafing), hold down the shift key:
---------------------------
   U    I    O
   J    K    L
   M    <    >

t : up (+z)
b : down (-z)

anything else : stop

q/z : increase/decrease max speeds by 10%
w/x : increase/decrease only linear speed by 10%
e/c : increase/decrease only angular speed by 10%

CTRL-C to quit

currently:      speed 0.50      turn 1.00

在终端中按 uiojklm 等键控制小车移动。

3.3.4 实时显示

在桌面系统打开第三个终端,查看topic列表;

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws$ ros2 topic list
/camera/camera_info
/camera/image_raw
/clock
/cmd_vel
/joint_states
/parameter_events
/performance_metrics
/robot_description
/rosout
/tf
/tf_static

安装 image_view 包(通常默认已安装);

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws$ sudo apt install ros-humble-image-view

运行以下命令,这会直接弹出一个单独的窗口显示摄像头画面;

zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws$ ros2 run image_view image_view image:=/camera/image_raw

四、代码下载

loongson_2k300_lib

posted @ 2026-04-05 17:58  大奥特曼打小怪兽  阅读(234)  评论(0)    收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步