3、机器人模型URDF详解:URDF文件结构、link与joint的定义、坐标系与变换
URDF,全称是 Unified Robot Description Format。说白了,它就是机器人界的「身份证」。
你想想看,要让 MoveIt 知道你的机器人长什么样、胳膊往哪转、轮子在哪,总得有个标准格式吧?URDF 就是干这个的。我刚开始接触 ROS 时,觉得这玩意儿不就是个 XML 嘛,随便写写就行。结果第一次加载模型,机器人直接在地上劈叉——关节方向全反了。嗯,从那以后我再也不敢小看 URDF 了。
3.1 URDF 文件结构:一棵树
URDF 的核心结构其实很简单——一棵树。根节点是 <robot>,下面挂着两种孩子:<link> 和 <joint>。
核心原则:URDF 必须是一棵无环树。不能有闭环,不能有分叉后又合并的结构。如果你做的是四足机器人或并联机械臂,URDF 搞不定——得用 SRDF 或 xacro 配合。
来看一个最简结构:
<robot name="my_robot">
<link name="base_link">
<visual>
<geometry>
<box size="0.4 0.3 0.1"/>
</geometry>
</visual>
</link>
<joint name="joint1" type="revolute">
<parent link="base_link"/>
<child link="arm_link"/>
<origin xyz="0 0 0.1" rpy="0 0 0"/>
<axis xyz="0 0 1"/>
</joint>
<link name="arm_link">
<visual>
<geometry>
<cylinder length="0.3" radius="0.05"/>
</geometry>
</visual>
</link>
</robot>
这段代码定义了一个底座加一个旋转关节的简单机械臂。注意看,base_link 是根,joint1 连接它和 arm_link。这就是一棵树——从根出发,一路向下。
3.2 link 的定义:机器人的「骨头」
每个 <link> 代表机器人身上的一个刚体部件。它有三个主要子标签:
| 子标签 | 作用 | 是否必须 |
|---|---|---|
<visual> |
可视化外观,用于 Rviz 显示 | 推荐有 |
<collision> |
碰撞检测模型,用于运动规划 | 强烈推荐 |
<inertial> |
惯性参数,用于动力学仿真 | 动力学需要 |
我个人习惯是:visual 和 collision 尽量保持一致。如果 collision 比 visual 简单太多,MoveIt 规划出来的路径可能看着没问题,实际上一碰撞就报错。我在项目里遇到过这种情况——明明 Rviz 里看着没撞,一跑仿真就卡住。查了半天,原来是 collision 模型把一块凸起给简化掉了。
小技巧:collision 可以用更简单的几何体替代 visual,但形状和位置不能差太远。比如 visual 是个复杂的 STL 模型,collision 可以用几个 box 和 cylinder 拼出来。这样既保证碰撞检测效率,又不会漏掉关键干涉区域。
inertial 部分很多人会忽略。如果你只做运动学规划,确实可以跳过。但一旦涉及动力学——比如力控、阻抗控制、拖动示教——惯性参数必须准确。我见过一个案例:某协作机器人 URDF 里所有 link 的 inertia 都填了默认值,结果仿真出来的关节力矩和实际差了 3 倍。嗯,这坑我踩过。
<inertial>
<mass value="1.5"/>
<inertia ixx="0.01" ixy="0" ixz="0"
iyy="0.01" iyz="0"
izz="0.01"/>
</inertial>
注意惯性张量的单位是 kg·m²,数值别瞎填。如果你不确定,可以用 SolidWorks 或 Fusion 360 导出,或者用 urdf_sim_tutorial 里的工具估算。
3.3 joint 的定义:机器人的「关节」
joint 是连接两个 link 的纽带。它定义了父子关系、运动类型和变换。
joint 的类型有 6 种:
| 类型 | 说明 | 自由度 |
|---|---|---|
revolute |
旋转关节,有限位 | 1 |
continuous |
连续旋转关节,无限位 | 1 |
prismatic |
平移关节 | 1 |
fixed |
固定连接,无运动 | 0 |
floating |
6 自由度浮动 | 6 |
planar |
平面运动(2 平移 + 1 旋转) | 3 |
最常用的是 revolute 和 fixed。连续旋转关节(比如 AGV 的轮子)用 continuous。浮动关节一般只用于移动机器人的 base 与世界的连接。
joint 里最重要的三个子标签:
<origin>:子 link 相对于父 link 的变换。包含xyz(平移)和rpy(旋转,单位弧度)。<axis>:关节的运动轴。对于 revolute,就是旋转轴;对于 prismatic,就是平移方向。<limit>:关节限位。包含lower、upper、effort、velocity。
注意:axis 的默认值是 (1, 0, 0),也就是 X 轴。很多人忘记写 axis,结果关节绕着 X 轴转,跟预期完全不一样。我曾经调试一个六轴机械臂,第五关节怎么转都不对,最后发现 axis 没写,默认绕 X 轴,而实际应该绕 Y 轴。改完就好了。
3.4 坐标系与变换:TF 树的基石
URDF 里的坐标系关系,直接决定了 TF 树的结构。每个 joint 的 <origin> 定义的就是子 link 坐标系相对于父 link 坐标系的变换。
举个例子:
<joint name="shoulder_pan" type="revolute">
<parent link="base_link"/>
<child link="shoulder_link"/>
<origin xyz="0 0 0.2" rpy="0 0 0"/>
<axis xyz="0 0 1"/>
</joint>
这表示 shoulder_link 的坐标系原点在 base_link 坐标系下 (0, 0, 0.2) 的位置,姿态与 base_link 一致。关节绕 Z 轴旋转。
你想想看,如果这里 xyz 写错了,整个机械臂的位置就全偏了。我见过有人把 xyz 和 rpy 写反——把旋转填到平移里,结果模型飞到天上去。
URDF 的坐标系遵循右手定则。X 向前,Y 向左,Z 向上——这是 ROS 的默认约定。但有些机器人厂商有自己的习惯,比如 Z 向上但 X 向右。这时候你需要在 URDF 里做一次坐标系转换,或者在 launch 文件中用 static_transform_publisher 发布一个额外的变换。
关键点:URDF 定义的 TF 树是静态的。关节运动后的变换由 robot_state_publisher 根据关节角度实时计算并发布。所以 URDF 里只需要定义「零位」时的坐标系关系。
3.5 知识体系总览
下面这张图帮你理清 URDF 的核心逻辑:
3.6 常见错误与避坑指南
写 URDF 时最容易犯的几个错误,我帮你列出来:
- 坐标系方向搞反:rpy 的顺序是 Z-Y-X(绕固定轴),不是 X-Y-Z。很多人写成
rpy="0 0 1.57"想绕 Z 转 90 度,结果绕 X 转了。正确写法是rpy="0 0 1.57"没错——但前提是你理解这是绕固定轴 Z 转 1.57。 - 忘记写 collision:MoveIt 的规划场景需要 collision 模型。没有 collision,碰撞检测会报错或直接跳过。我见过有人只写了 visual,结果规划出来的路径看着没问题,实际上一执行就撞。
- joint 的 parent 和 child 写反:这会导致 TF 树方向反了。检查方法很简单——用
check_urdf工具跑一遍,它会告诉你树结构是否合理。 - limit 里的 effort 和 velocity 填 0:这会导致关节无法运动。effort 是最大力矩,velocity 是最大速度。填 0 等于告诉 MoveIt「这个关节动不了」。
调试工具推荐:
check_urdf my_robot.urdf— 检查 URDF 语法和树结构urdf_to_graphiz my_robot.urdf— 生成树结构 PDF 图roslaunch urdf_tutorial display.launch model:=my_robot.urdf— 在 Rviz 中可视化