找回密码
 立即注册
首页 业界区 业界 龙芯2k0300 - 走马观碑组Gazebo仿真环境搭建 ...

龙芯2k0300 - 走马观碑组Gazebo仿真环境搭建

撙仿 昨天 16:42
一、概述

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;
  1. zhengyang@ubuntu:~$ cd /opt/2k0300/loongson_2k300_lib
  2. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib$ mkdir -p car_ws/src
  3. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib$ cd car_ws/src
复制代码
完整的目录结构大致如下,这个我们后续内容会依次创建:
  1. car_ws/
  2. ├── src/
  3. │   ├── car_description/      # 功能包1:机器人URDF模型
  4. │   ├── car_gazebo/           # 功能包2:Gazebo仿真
  5. │   └── car_vision/           # 功能包3:视觉算法
复制代码
在ROS的开发规范中,src/ 下的每个子目录叫功能包(Package),而不是独立项目。它们共同组成一个完整的机器人项目。
功能包职责修改频率car_description小车的物理模型(尺寸、颜色、传感器位置)低(硬件确定后很少改)car_gazebo仿真环境、赛道、启动脚本中(换赛道时需要改)car_vision图像处理、巡线算法高(天天调参)1.2.2 功能包关系

功能包之间的协作关系如下:
  1. 启动顺序:
  2. 1. car_gazebo 启动仿真世界,生成小车模型
  3.    └── 调用 car_description 中的 car.urdf 描述小车长什么样
  4. 2. car_vision 订阅摄像头图像
  5.    └── 处理图像 → 发布速度指令
  6. 3. car_gazebo 中的 Gazebo 接收速度指令
  7.    └── 驱动仿真小车运动
复制代码
二、小车建模

创建car_description的Python版本的功能包;
  1. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src$ ros2 pkg create --build-type ament_python car_description
复制代码
运行成功后,终端会显示创建的文件和目录信息。此时, car_description 功能包目录结构将如下所示:
  1. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src$ tree ./car_description/
  2. ./car_description/
  3. ├── car_description  # 核心Python模块目录,用于存放Python代码
  4. │   └── __init__.py
  5. ├── package.xml      # 功能包的描述文件(含依赖信息)
  6. ├── resource         # 资源文件夹
  7. │   └── car_description
  8. ├── setup.cfg        # setuptools 的配置文件
  9. ├── setup.py         # Python 包的安装脚本
  10. └── test             # 测试文件夹
复制代码
2.1 子目录

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

  • urdf: 专门存放 .urdf 或 .xacro 模型文件;
  • launch:保存相关启动文件;
  • rviz:保存rviz的配置文件;
  • meshes:放置URDF中引用的模型渲染文件;
  • config: 存放Gazebo控制器的配置文件。
在终端中执行:
  1. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src$ cd car_description/
  2. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src/car_description$ mkdir urdf config launch rviz meshes
复制代码
我们需要修改setup.py文件,添加配置文件:
  1. import os
  2. from glob import glob
  3.     ...
  4.     data_files=[
  5.         ('share/ament_index/resource_index/packages',
  6.             ['resource/' + package_name]),
  7.         ('share/' + package_name, ['package.xml']),
  8.         (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*.launch.py'))),
  9.         (os.path.join('share', package_name, 'urdf'), glob(os.path.join('urdf', '*.*'))),
  10.         (os.path.join('share', package_name, 'urdf/sensors'), glob(os.path.join('urdf/sensors', '*.*'))),
  11.         (os.path.join('share', package_name, 'meshes'), glob(os.path.join('meshes', '*.*'))),
  12.         (os.path.join('share', package_name, 'rviz'), glob(os.path.join('rviz', '*.rviz'))),
  13.         (os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.*'))),
  14.     ],
  15.     ...
复制代码
2.2 模型文件

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

  • 有关URDF语法可以参考《ROS2之URDF建模》;
  • 有关XACRO语法可以参考《ROS2之Gazebo物理仿真平台》。
首先进入 urdf 目录并创建文件:
  1. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src/car_description$ cd urdf/
  2. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws/src/car_description/urdf$ vim car.xacro
复制代码
内容如下:
点击查看详情
  1. <?xml version="1.0"?>
  2. <robot name="f_car_model" xmlns:xacro="http://www.ros.org/wiki/xacro">
  3.   
  4.   
  5.   
  6.   <xacro:property name="main_body_len" value="0.11"/>
  7.   <xacro:property name="main_body_width" value="0.16"/>
  8.   <xacro:property name="main_body_height" value="0.03"/>
  9.   <xacro:property name="nose_len" value="0.07"/>
  10.   <xacro:property name="nose_width" value="0.06"/>      
  11.   <xacro:property name="nose_height" value="0.02"/>
  12.   
  13.   <xacro:property name="wheel_diameter" value="0.064"/>
  14.   <xacro:property name="wheel_radius" value="0.032"/>
  15.   <xacro:property name="wheel_width" value="0.027"/>   
  16.   <xacro:property name="wheel_track" value="0.155"/>   
  17.   
  18.   <xacro:property name="caster_radius" value="0.012"/>
  19.   <xacro:property name="caster_offset_x" value="0.08"/>
  20.   
  21.   <xacro:property name="camera_height" value="0.12"/>   
  22.   <xacro:property name="camera_pitch" value="0.15"/>   
  23.   
  24.   <xacro:property name="mass_body" value="0.6"/>
  25.   <xacro:property name="mass_wheel" value="0.08"/>
  26.   <xacro:property name="mass_caster" value="0.01"/>
  27.   
  28.   <material name="black">
  29.     <color rgba="0.1 0.1 0.1 1.0"/>
  30.   </material>
  31.   <material name="blue">
  32.     <color rgba="0.0 0.0 0.6 1.0"/>
  33.   </material>
  34.   <material name="white">
  35.     <color rgba="0.9 0.9 0.9 1.0"/>
  36.   </material>
  37.   <material name="silver">
  38.     <color rgba="0.7 0.7 0.7 1.0"/>
  39.   </material>
  40.   
  41.   <xacro:macro name="wheel" params="prefix parent *origin">
  42.     <link name="${prefix}_wheel">
  43.       <visual>
  44.         <geometry>
  45.           <cylinder radius="${wheel_radius}" length="${wheel_width}"/>
  46.         </geometry>
  47.         <material name="black"/>
  48.         <origin xyz="0 0 0" rpy="1.5707 0 0"/>
  49.       </visual>
  50.       <collision>
  51.         <geometry>
  52.           <cylinder radius="${wheel_radius}" length="${wheel_width}"/>
  53.         </geometry>
  54.         <origin xyz="0 0 0" rpy="1.5707 0 0"/>
  55.       </collision>
  56.       <inertial>
  57.         <mass value="${mass_wheel}"/>
  58.         <inertia ixx="0.0001" ixy="0.0" ixz="0.0" iyy="0.0001" iyz="0.0" izz="0.0001"/>
  59.       </inertial>
  60.     </link>
  61.     <joint name="${prefix}_wheel_joint" type="continuous">
  62.       <parent link="${parent}"/>
  63.       <child link="${prefix}_wheel"/>
  64.       
  65.       <xacro:insert_block name="origin"/>
  66.     </joint>
  67.    
  68.     <gazebo reference="${prefix}_wheel">
  69.       <material>Gazebo/Black</material>
  70.       <mu1 value="1.2"/>
  71.       <mu2 value="1.2"/>
  72.       <kp value="10000000.0" />
  73.       <kd value="10.0" />
  74.     </gazebo>
  75.   </xacro:macro>
  76.   
  77.   <link name="base_link">
  78.    
  79.     <visual>
  80.       <origin xyz="0 0 0" rpy="0 0 0"/>
  81.       <geometry>
  82.         <mesh filename="package://your_package_name/meshes/f_car_body.stl" scale="1 1 1"/>
  83.         
  84.         <box size="${main_body_len} ${main_body_width} ${main_body_height}"/>
  85.       </geometry>
  86.       <material name="blue"/>
  87.     </visual>
  88.    
  89.     <visual>
  90.       <origin xyz="${main_body_len/2 + nose_len/2} 0 0" rpy="0 0 0"/>
  91.       <geometry>
  92.         <box size="${nose_len} ${nose_width} ${nose_height}"/>
  93.       </geometry>
  94.       <material name="silver"/>
  95.     </visual>
  96.    
  97.     <collision>
  98.       <origin xyz="0 0 0" rpy="0 0 0"/>
  99.       <geometry>
  100.         <box size="${main_body_len} ${main_body_width} ${main_body_height}"/>
  101.       </geometry>
  102.     </collision>
  103.     <collision>
  104.       <origin xyz="${main_body_len/2 + nose_len/2} 0 0" rpy="0 0 0"/>
  105.       <geometry>
  106.         <box size="${nose_len} ${nose_width} ${nose_height}"/>
  107.       </geometry>
  108.     </collision>
  109.    
  110.     <inertial>
  111.       <origin xyz="-0.02 0 0" rpy="0 0 0"/>
  112.       <mass value="${mass_body}"/>
  113.       <inertia ixx="0.003" ixy="0.0" ixz="0.0" iyy="0.005" iyz="0.0" izz="0.006"/>
  114.     </inertial>
  115.   </link>
  116.   
  117.   
  118.   <xacro:wheel prefix="left" parent="base_link">
  119.     <origin xyz="${-main_body_len/2 + 0.01} ${wheel_track/2} ${-wheel_radius}" rpy="0 0 0"/>
  120.   </xacro:wheel>
  121.   
  122.   <xacro:wheel prefix="right" parent="base_link">
  123.     <origin xyz="${-main_body_len/2 + 0.01} ${-wheel_track/2} ${-wheel_radius}" rpy="0 0 0"/>
  124.   </xacro:wheel>
  125.   
  126.   <link name="caster_wheel">
  127.     <visual>
  128.       <geometry>
  129.         <sphere radius="${caster_radius}"/>
  130.       </geometry>
  131.       <material name="white"/>
  132.     </visual>
  133.     <collision>
  134.       <geometry>
  135.         <sphere radius="${caster_radius}"/>
  136.       </geometry>
  137.     </collision>
  138.     <inertial>
  139.       <mass value="${mass_caster}"/>
  140.       <inertia ixx="0.00001" ixy="0.0" ixz="0.0" iyy="0.00001" iyz="0.0" izz="0.00001"/>
  141.     </inertial>
  142.   </link>
  143.   <joint name="caster_wheel_joint" type="fixed">
  144.     <parent link="base_link"/>
  145.     <child link="caster_wheel"/>
  146.    
  147.     <origin xyz="${caster_offset_x} 0 ${-(wheel_radius - caster_radius)}" rpy="0 0 0"/>
  148.   </joint>
  149.   <gazebo reference="caster_wheel">
  150.     <material>Gazebo/White</material>
  151.     <mu1 value="0.1"/>
  152.     <mu2 value="0.1"/>
  153.   </gazebo>
  154.   
  155.   <link name="camera_link">
  156.     <visual>
  157.       <geometry>
  158.         <box size="0.03 0.03 0.03"/>
  159.       </geometry>
  160.       <material name="black"/>
  161.     </visual>
  162.   </link>
  163.   <joint name="camera_joint" type="fixed">
  164.     <parent link="base_link"/>
  165.     <child link="camera_link"/>
  166.    
  167.     <origin xyz="${main_body_len/2 + nose_len/2} 0 ${nose_height/2 + 0.05}" rpy="0 ${camera_pitch} 0"/>
  168.   </joint>
  169.   
  170.   <gazebo>
  171.     <plugin name="differential_drive_controller" filename="libgazebo_ros_diff_drive.so">
  172.       <rosDebugLevel>Debug</rosDebugLevel>
  173.       <publishWheelTF>false</publishWheelTF>
  174.       <robotNamespace>/</robotNamespace>
  175.       <publishTf>1</publishTf>
  176.       <publishWheelJointState>true</publishWheelJointState>
  177.       true</alwaysOn>
  178.       <updateRate>100.0</updateRate>
  179.       <leftJoint>left_wheel_joint</leftJoint>
  180.       <rightJoint>right_wheel_joint</rightJoint>
  181.       <wheelSeparation>${wheel_track}</wheelSeparation>
  182.       <wheelDiameter>${wheel_diameter}</wheelDiameter>
  183.       <broadcastTF>1</broadcastTF>
  184.       <wheelTorque>30</wheelTorque>
  185.       <wheelAcceleration>1.8</wheelAcceleration>
  186.       <commandTopic>cmd_vel</commandTopic>
  187.       <odometryFrame>odom</odometryFrame>
  188.       <odometryTopic>odom</odometryTopic>
  189.       <robotBaseFrame>base_link</robotBaseFrame>
  190.     </plugin>
  191.   </gazebo>
  192.   
  193.   <gazebo reference="camera_link">
  194.     <sensor type="camera" name="camera1">
  195.       <update_rate>30.0</update_rate>
  196.       <camera name="head">
  197.         <horizontal_fov>1.047</horizontal_fov>
  198.         <image>
  199.           <width>320</width>
  200.           <height>240</height>
  201.           <format>R8G8B8</format>
  202.         </image>
  203.         <clip>
  204.           <near>0.02</near>
  205.           <far>300</far>
  206.         </clip>
  207.       </camera>
  208.       <plugin name="camera_controller" filename="libgazebo_ros_camera.so">
  209.         true</alwaysOn>
  210.         <updateRate>0.0</updateRate>
  211.         <cameraName>camera</cameraName>
  212.         <imageTopicName>image_raw</imageTopicName>
  213.         <cameraInfoTopicName>camera_info</cameraInfoTopicName>
  214.         <frameName>camera_link</frameName>
  215.         <hackBaseline>0.07</hackBaseline>
  216.         <distortionK1>0.0</distortionK1>
  217.         <distortionK2>0.0</distortionK2>
  218.         <distortionK3>0.0</distortionK3>
  219.         <distortionT1>0.0</distortionT1>
  220.         <distortionT2>0.0</distortionT2>
  221.       </plugin>
  222.     </sensor>
  223.   </gazebo>
  224. </robot>
复制代码
这里我们通过通过 nose 和 main_body 的组合来模拟车模前窄后宽的形状,这更符合F车模的真实外观;

  • 长:18cm、宽20cm、高6.5cm;
  • 车轮:直径6.4cm、宽度2.7cm、轮距15.5cm。
1.png

2.2.1 车身几何结构

使用了两个  来拼接车身;

  • 后部 (main_body):尺寸0.11m x 0.16m,这是电机和后轮所在的位置,比较宽;
  • 前部 (nose):尺寸0.07m x 0.06m,这是向前延伸的部分,模拟真实的窄支架;
总长:0.11 + 0.07 = 0.18m。
2.2.2 轮距与轮子

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

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

为了适应“走马观碑”组别,我将摄像头高度设定为离地约12cm (camera_height),并增加了一个俯仰角 camera_pitch,这更符合实际比赛中俯视赛道的情况。
当然如果熟悉SolidWorks或Fusion 360可以按照图片画一个精确的3D模型,将其导出为 .stl 或 .dae 文件。在URDF中,把  标签换成:
  1. [/code]这样在rviz和Gazebo中看到的就完全是一模一样的车了。
  2. [size=3]2.3  launch文件[/size]
  3. 在launch文件夹下创建display.launch.py文件;
  4. [code]from launch import LaunchDescription
  5. from launch.actions import DeclareLaunchArgument
  6. from launch_ros.parameter_descriptions import ParameterValue
  7. from launch_ros.actions import Node
  8. from launch.substitutions import Command, LaunchConfiguration
  9. from ament_index_python.packages import get_package_share_directory
  10. import os
  11. def generate_launch_description():
  12.     # xacro 文件路径(注意扩展名是 .xacro)
  13.     xacro_path = os.path.join(
  14.         get_package_share_directory('car_description'),
  15.         'urdf',
  16.         'car.xacro'  # 关键:改为 .xacro
  17.     )
  18.    
  19.     # 声明 model 参数
  20.     model_arg = DeclareLaunchArgument(
  21.         name='model',
  22.         default_value=xacro_path,
  23.         description='Absolute path to robot xacro file'
  24.     )
  25.    
  26.     # 使用 xacro 命令解析 URDF
  27.     robot_description = ParameterValue(
  28.         Command(['xacro ', LaunchConfiguration('model')]),
  29.         value_type=str
  30.     )
  31.    
  32.     return LaunchDescription([
  33.         model_arg,  # 必须包含这个声明
  34.         
  35.         # 机器人状态发布器
  36.         Node(
  37.             package='robot_state_publisher',
  38.             executable='robot_state_publisher',
  39.             output='screen',
  40.             parameters=[{'robot_description': robot_description}]
  41.         ),
  42.         
  43.         # 关节状态发布器 GUI(可手动拖动关节)
  44.         Node(
  45.             package='joint_state_publisher_gui',
  46.             executable='joint_state_publisher_gui',
  47.             name='joint_state_publisher_gui'
  48.         ),
  49.         
  50.         # RViz2 可视化
  51.         Node(
  52.             package='rviz2',
  53.             executable='rviz2',
  54.             name='rviz2',
  55.             arguments=['-d', os.path.join(get_package_share_directory('car_description'), 'rviz', 'car_display.rviz')],
  56.                     output='screen'
  57.         )
  58.     ])
复制代码
这个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 数据流与节点关系

数据流与节点关系:
  1. 用户通过滑动条/GUI或程序 → joint_state_publisher_gui
  2.                                      ↓ 发布/joint_states话题
  3.                         robot_state_publisher
  4.                                      ↓ 计算并发布TF变换
  5.                               rviz2 和其他节点
  6.                                      ↓ 接收TF并可视化
复制代码
2.4 car_display.rviz

创建rviz配置文件,避免手动设置rviz,在rviz目录下新建car_display.rviz文件;
  1. Panels:
  2.   - Class: rviz_common/Displays
  3.     Name: Displays
  4.   - Class: rviz_common/Views
  5.     Name: Views
  6. Visualization Manager:
  7.   Class: ""
  8.   Displays:
  9.     - Class: rviz_default_plugins/Grid
  10.       Name: Grid
  11.       Value: true
  12.     - Alpha: 0.8
  13.       Class: rviz_default_plugins/RobotModel
  14.       Description Source: Topic
  15.       Description Topic:
  16.         Value: /robot_description
  17.       Enabled: true
  18.       Name: RobotModel
  19.       Value: true
  20.     - Class: rviz_default_plugins/TF
  21.       Name: TF
  22.       Value: true
  23.   Global Options:
  24.     Fixed Frame: base_link
  25.     Frame Rate: 30
  26.   Name: root
  27.   Tools:
  28.     - Class: rviz_default_plugins/MoveCamera
  29.   Value: true
  30.   Views:
  31.     Current:
  32.       Class: rviz_default_plugins/Orbit
  33.       Distance: 1.7
  34.       Name: Current View
  35.       Pitch: 0.33
  36.       Value: Orbit (rviz)
  37.       Yaw: 5.5
  38. Window Geometry:
  39.   Height: 800
  40.   Width: 1200
复制代码
2.5 编译运行

2.4.1 编译

在 car_ws 目录下编译并检查:
[code]zhengyang@ubuntu:~$ cd /opt/2k0300/loongson_2k300_lib/car_wszhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/car_ws$ colcon build --paths src/car_description.....Finished
您需要登录后才可以回帖 登录 | 立即注册