第3章 机器人模型URDF:从结构到可视化

各位同学,欢迎来到第三章。前两章我们把MoveIt的安装和环境搭好了,算是有了「厨房」和「灶台」。这一章,咱们得开始准备「食材」了——也就是机器人模型。

URDF,全称Unified Robot Description Format,统一机器人描述格式。说白了,它就是一套用XML写成的「机器人简历」。简历里写什么?写你机器人身上有几块骨头(link),关节怎么转(joint),长什么样,多重。MoveIt拿到这份简历,才知道怎么去规划你的机器人运动。

核心要点:URDF是MoveIt一切工作的基础。模型不对,后面所有调参都是白费功夫。我见过太多人花几周调参数,最后发现是URDF里一个joint的轴方向写反了——嗯,那种感觉,就像你追着一只猫跑了半天,结果发现它只是你的影子。

3.1 URDF文件结构:骨架与血肉

一个URDF文件,本质上就是一个XML树。根节点叫<robot>,里面包着两类核心元素:<link>和<joint>。

你可以这么理解:link是骨头,joint是关节。骨头不能动,关节负责让骨头之间产生相对运动。没有joint的link就是一块死肉,没有link的joint就是空中楼阁。

来看一个最简单的URDF骨架:

<robot name="my_robot">
  <link name="base_link">
    <visual>
      <geometry>
        <box size="0.4 0.3 0.1"/>
      </geometry>
    </visual>
  </link>

  <joint name="base_to_arm" type="fixed">
    <parent link="base_link"/>
    <child link="arm_link"/>
    <origin xyz="0 0 0.05" rpy="0 0 0"/>
  </joint>

  <link name="arm_link">
    <visual>
      <geometry>
        <cylinder length="0.3" radius="0.02"/>
      </geometry>
    </visual>
  </link>
</robot>

这段代码描述了一个底盘(base_link)上立着一根柱子(arm_link)。注意joint里的parent和child——URDF里所有关节都是有方向的,从父link指向子link。这个方向决定了坐标变换的传递链。

我的习惯:命名link和joint时,尽量用有意义的英文。比如shoulder_pan_joint、elbow_flex_joint。别用j1、j2这种,调试的时候你会疯掉的。我在一个项目里接手过别人的URDF,所有joint叫joint_1到joint_12——嗯,那感觉就像读一本没有目录的书。

3.2 link的定义:不只是几何形状

一个完整的link定义,包含三个部分:

  • visual:视觉模型,用来显示。你可以用box、cylinder、sphere,或者导入mesh文件(比如STL、DAE)。
  • collision:碰撞模型,用来做碰撞检测。通常比visual模型更简化,用基本几何体代替复杂mesh,能大幅提升计算速度。
  • inertial:惯性参数,包括质量、惯性矩阵。这个对动力学仿真至关重要。

来看一个带完整信息的link:

<link name="forearm_link">
  <visual>
    <origin xyz="0 0 0.15" rpy="0 0 0"/>
    <geometry>
      <mesh filename="package://my_robot/meshes/forearm.stl"/>
    </geometry>
    <material name="blue">
      <color rgba="0 0 1 1"/>
    </material>
  </visual>

  <collision>
    <origin xyz="0 0 0.15" rpy="0 0 0"/>
    <geometry>
      <cylinder length="0.3" radius="0.04"/>
    </geometry>
  </collision>

  <inertial>
    <mass value="1.5"/>
    <inertia ixx="0.01" ixy="0" ixz="0"
             iyy="0.01" iyz="0"
             izz="0.005"/>
  </inertial>
</link>

避坑指南:我曾经在一个六轴机器人项目里,collision模型直接用了visual的精细STL文件。结果MoveIt做运动规划时,每算一步都要做几万次三角形碰撞检测,规划时间从0.5秒飙到了15秒。后来我把collision全换成圆柱体和长方体,速度直接回到0.3秒。记住:collision模型越简单越好。

3.3 joint的定义:运动的灵魂

joint定义了link之间的运动关系。URDF支持六种关节类型:

类型 说明 自由度
fixed 固定关节,两个link之间没有相对运动 0
revolute 旋转关节,绕一个轴旋转,有角度限制 1
continuous 连续旋转关节,没有角度限制(比如轮子) 1
prismatic 平移关节,沿一个轴直线运动 1
planar 平面关节,在平面内运动 2
floating 浮动关节,6自由度完全自由 6

实际机器人上,最常用的是revolute和continuous。来看一个典型的旋转关节:

<joint name="shoulder_pan_joint" type="revolute">
  <parent link="base_link"/>
  <child link="shoulder_link"/>
  <origin xyz="0 0 0.1" rpy="0 0 0"/>
  <axis xyz="0 0 1"/>
  <limit lower="-3.14" upper="3.14" effort="100" velocity="1.5"/>
  <dynamics damping="0.1" friction="0.01"/>
</joint>

这里有几个关键点:

  • origin:关节原点在父link坐标系中的位置和姿态。rpy是绕固定轴的旋转(roll, pitch, yaw)。
  • axis:旋转轴在父link坐标系中的方向向量。对于revolute关节,这个必须归一化。
  • limit:关节限位。lower和upper是弧度制,effort是最大力矩,velocity是最大速度。
  • dynamics:动力学参数,阻尼和摩擦系数。这个在仿真中影响很大。

我建议:写joint的时候,origin的rpy尽量用0。除非你明确知道自己在做什么。我见过有人把rpy写成1.57 0 0,结果子link直接歪了90度,整个机器人姿态看起来像喝醉了酒。调试了半天才发现是这里的问题。

3.4 在Rviz中可视化模型:眼见为实

URDF写完了,怎么知道对不对?别靠猜,用Rviz看。

首先,你需要一个launch文件来启动Rviz并加载你的URDF。最简单的方式:

<launch>
  <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"/>
  <node name="rviz" pkg="rviz" type="rviz" 
        args="-d $(find my_robot)/config/display.rviz"/>
</launch>

这里做了三件事:

  1. 把URDF文件内容加载到参数服务器,参数名叫robot_description。
  2. 启动robot_state_publisher节点,它会根据URDF和关节状态发布TF变换。
  3. 启动Rviz,加载一个预先配置好的显示配置文件。

在Rviz中,你需要添加一个RobotModel显示插件。它会自动读取robot_description参数,把机器人模型渲染出来。你还可以添加TF显示,看看各个link的坐标系是否在正确的位置。

调试技巧:如果Rviz里模型显示不出来,先检查终端有没有报错。最常见的错误是:URDF里引用的mesh文件路径不对。ROS里用package://开头,比如package://my_robot/meshes/arm.stl。如果路径写成了路径或者相对路径,Rviz是找不到的。

3.5 知识体系结构图

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

URDF知识体系结构 <robot> <link> 骨头 visual collision inertial <joint> 关节 origin/axis limit dynamics Rviz可视化验证 link定义「有什么」,joint定义「怎么动」,Rviz验证「对不对」

这张图把URDF的核心逻辑拆成了三块:link定义机器人的「有什么」,joint定义「怎么动」,最后用Rviz来验证「对不对」。三者缺一不可。

3.6 实战中的常见问题

最后,分享几个我在项目中踩过的坑:

  • 坐标系混乱:URDF里所有坐标都是相对于父link的。写origin时,一定要搞清楚当前是在哪个坐标系下。我习惯在URDF文件头部加注释,标明每个link的父级关系。
  • 关节轴方向错误:axis向量必须归一化。如果你写xyz="0 0 2",ROS不会报错,但关节运动会变得很奇怪。我曾经因为这个,让一个六轴机器人的末端执行器画出了一个完美的螺旋线——可惜不是我想要的效果。
  • 惯性参数缺失:MoveIt的动力学规划需要惯性参数。如果你不写<inertial>,默认质量是0,会导致仿真崩溃。我的做法是:先用估算值填上,等实际测量后再更新。
  • mesh路径问题:用package://而不是file://或路径。这样你的URDF才能在不同机器上复用。

再强调一次:URDF是MoveIt的基石。花时间把URDF写对、调好,后面所有工作都会顺利很多。别急着跳到运动规划,先把模型在Rviz里转几圈,确认每个关节的运动方向、限位都正确。这一步省下来的时间,会在后面十倍百倍地还给你。