2、机器人模型构建:URDF模型基础、link与joint定义、在Gazebo中显示机器人

好,咱们正式开始动手了。这一章,我们要解决一个最根本的问题——怎么让机器人出现在Gazebo里?

你想想看,没有模型,地图构建就是空中楼阁。我刚开始学的时候,也以为直接跑个SLAM算法就行了,结果Gazebo里空空如也,啥也看不到。后来才明白,得先给机器人“画个像”,告诉仿真环境它长什么样、关节怎么动。

这个“画像”的工具,就是URDF。

2.1 URDF模型基础

URDF,全称是Unified Robot Description Format,统一机器人描述格式。说白了,就是一个XML文件,用标签来描述机器人的身体结构。

我个人习惯把URDF理解成“机器人的身份证”。它记录了:

  • 机器人有哪些部件(link)
  • 部件之间怎么连接(joint)
  • 每个部件长什么样(几何形状、尺寸、颜色)

嗯,这里要注意:URDF只适用于树状结构的机器人。什么意思?就是每个部件只能有一个父节点,不能有闭环。比如四足机器人,如果腿和身体形成了闭环,URDF就搞不定了,得用更高级的SDF格式。不过咱们做轮式移动机器人,URDF完全够用。

核心要点:URDF是ROS和Gazebo之间的桥梁。没有它,Gazebo不知道你的机器人长啥样。

2.2 link与joint定义

这是URDF里最重要的两个概念。我当年花了整整一个下午才搞明白它们的区别,其实很简单:

  • link:机器人的“骨头”,也就是一个个刚体部件。比如底盘、轮子、激光雷达支架。
  • joint:机器人的“关节”,用来连接两个link,并定义它们之间的相对运动关系。

举个例子,一个两轮差速底盘,至少需要:

  • 1个底盘link(base_link)
  • 2个轮子link(left_wheel_link、right_wheel_link)
  • 2个轮子joint(left_wheel_joint、right_wheel_joint)

2.2.1 link的定义

一个link标签里,主要包含三部分:

  • 视觉属性(visual):在Rviz里看到的样子
  • 碰撞属性(collision):用于物理碰撞检测的形状
  • 惯性属性(inertial):质量、惯性矩阵,影响物理仿真效果

来看一个最简单的底盘link定义:

<link name="base_link">
  <visual>
    <geometry>
      <box size="0.4 0.3 0.1"/>
    </geometry>
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <material name="blue">
      <color rgba="0 0 0.8 1"/>
    </material>
  </visual>
  <collision>
    <geometry>
      <box size="0.4 0.3 0.1"/>
    </geometry>
  </collision>
  <inertial>
    <mass value="2.0"/>
    <inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.02"/>
  </inertial>
</link>

这里我用了box(长方体)作为底盘形状。尺寸是0.4米长、0.3米宽、0.1米高。颜色设成了蓝色。碰撞模型和视觉模型一样,但实际项目中,为了性能,碰撞模型通常会简化——比如用圆柱体代替复杂的轮子形状。

我的经验:惯性参数别随便填。我曾经偷懒,给轮子设了个很小的质量,结果仿真时机器人一加速就翻车。后来老老实实算了每个部件的质量和惯性矩,才稳定下来。

2.2.2 joint的定义

joint定义了link之间的连接关系。常用的joint类型有:

类型 说明 自由度
fixed 固定连接,两个link之间没有相对运动 0
revolute 旋转关节,绕一个轴转动(有角度限制) 1
continuous 连续旋转关节,没有角度限制(比如轮子) 1
prismatic 滑动关节,沿一个轴直线运动 1

轮子一般用continuous类型,因为它要一直转。来看轮子joint的定义:

<joint name="left_wheel_joint" type="continuous">
  <parent link="base_link"/>
  <child link="left_wheel_link"/>
  <origin xyz="-0.15 0.2 -0.05" rpy="0 0 0"/>
  <axis xyz="0 1 0"/>
</joint>

这里parent是底盘,child是轮子。origin定义了轮子相对于底盘的位置——在底盘左前方。axis指定了旋转轴是Y轴,这样轮子就能前后转动了。

避坑指南:我曾经把parent和child搞反了,结果轮子带着底盘跑,Gazebo里直接炸了。记住:parent是靠近机器人基座的部件,child是末端部件。

2.3 在Gazebo中显示机器人

模型写好了,怎么让它出现在Gazebo里?这里需要引入一个重要的中间件——robot_state_publisher。

它的作用很简单:读取URDF文件,发布每个link的坐标变换(TF)。Gazebo和Rviz都靠这些TF来知道机器人各部件的位置。

启动流程一般是这样的:

  1. 写一个launch文件,加载URDF模型
  2. 启动robot_state_publisher节点
  3. 启动Gazebo,生成机器人实体

来看一个完整的launch文件示例:

<launch>
  <!-- 加载URDF模型 -->
  <param name="robot_description" 
         textfile="$(find my_robot)/urdf/my_robot.urdf"/>

  <!-- 启动状态发布器 -->
  <node name="robot_state_publisher" 
        pkg="robot_state_publisher" 
        type="robot_state_publisher"/>

  <!-- 启动Gazebo -->
  <include file="$(find gazebo_ros)/launch/empty_world.launch">
    <arg name="paused" value="false"/>
    <arg name="use_sim_time" value="true"/>
  </include>

  <!-- 在Gazebo中生成机器人 -->
  <node name="spawn_urdf" 
        pkg="gazebo_ros" 
        type="spawn_model" 
        args="-param robot_description -urdf -model my_robot"/>
</launch>

这里最关键的是spawn_model这个节点。它从参数服务器读取robot_description,然后在Gazebo里生成对应的机器人模型。

为什么会这样?因为Gazebo本身不认识URDF文件,它需要spawn_model把URDF转换成Gazebo能理解的SDF格式。

记住这个流程:URDF文件 → robot_state_publisher发布TF → spawn_model生成实体 → Gazebo显示机器人

嗯,这里还要提一句。如果你在Gazebo里看到机器人是灰色的,别慌。那是因为URDF里定义的material颜色,Gazebo默认不识别。需要在URDF里额外添加Gazebo专用的颜色标签,或者用纹理贴图。这个我们后面章节会细讲。

我个人习惯,第一次测试时先用Rviz看看模型对不对。Rviz里显示正常了,再放到Gazebo里跑物理仿真。这样排查问题快很多。

本章知识体系

下面这张图,帮你理清URDF模型构建的核心逻辑:

URDF模型构建核心逻辑 URDF文件 robot_state_publisher 发布TF变换 spawn_model 生成Gazebo实体 Rviz可视化 Gazebo物理仿真 可选同步 link(骨头)+ joint(关节)= 完整机器人模型 树状结构,每个link只有一个父节点

这张图把整个流程串起来了。你写URDF的时候,心里要时刻想着:这个link的parent是谁?joint的类型对不对?坐标原点有没有偏移?

好了,这一章的内容就到这儿。模型建好了,下一章我们就能让它动起来了。

可可精品资料库