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介绍可以参考《ROS2之URDF建模》;
③ 赛道建模(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_description的Python版本的功能包;
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语法可以参考《ROS2之URDF建模》; - 有关
XACRO语法可以参考《ROS2之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。里面包含焦距、光心等标定参数;frameName:TF坐标系名称;- 发布的图像消息里会包含
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>
这里我们通过通过 nose 和 main_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。
当然如果熟悉SolidWorks或Fusion 360可以按照图片画一个精确的3D模型,将其导出为 .stl 或 .dae 文件。在URDF中,把 <box> 标签换成:
<mesh filename="package://your_package/meshes/f_car_body.stl"/>
这样在rviz和Gazebo中看到的就完全是一模一样的车了。
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.so是ROS与Gazebo通信的桥梁,它的作用是:接收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会在终端打印大量信息(比如“我收到速度指令了”、“我发布里程计了”)。调试时很有用,正式运行可以改为Info或Warn; -
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_link的TF变换; -
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 = 33cm(camera_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:将机器人各个links、joints之间的关系,通过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 准备赛道图片
将你的赛道图片(JPEG或PNG格式)放到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/Ground:Track/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)和 GUI(gzclient) |
| 生成小车 | 将 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_size和real_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.py、gzclient.launch.py、spawn_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
在终端中按 u、i、o、j、k、l、m 等键控制小车移动。
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

浙公网安备 33010602011771号