URDF 格式完全解读:从 XML 规范到 ROS 2 的现状与未来
一、引言
URDF(Unified Robot Description Format,统一机器人描述格式)是 ROS(Robot Operating System)生态中最核心的机器人建模语言。自 2008 年 Willow Garage 在 PR2 项目引入以来,URDF 已成为机器人学领域事实上的运动学/动力学建模标准。本文从原始 XML Schema 出发,深入每个标签的内部数据结构,剖析其限制,并讨论向 SDFormat 演进的必然趋势。
二、全局结构与 XML Schema
2.1 根元素 <robot>
URDF 的根元素是 <robot>,定义在 XSD 中如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <xs:element name="robot"> <xs:complexType> <xs:sequence minOccurs="0" maxOccurs="unbounded"> <xs:element name="joint" type="joint" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="link" type="link" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="material" type="material_global" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="transmission" type="transmission" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="gazebo" type="gazebo" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="sensor" type="sensor" minOccurs="0" maxOccurs="unbounded" /> </xs:sequence> <xs:attribute name="name" type="xs:string" use="required" /> <xs:attribute name="version" type="xs:string" default="1.0" /> </xs:complexType> </xs:element>
|
属性表:
| 属性 |
类型 |
必需 |
默认值 |
说明 |
name |
string |
是 |
— |
机器人名称 |
version |
string |
否 |
"1.0" |
URDF 版本号 |
子元素顺序(XSD 定义的序列,解析器按顺序匹配):
| 子元素 |
出现次数 |
说明 |
<joint> |
0..N |
关节定义 |
<link> |
0..N |
连杆定义 |
<material> |
0..N |
全局材质声明(可供 <visual> 引用) |
<transmission> |
0..N |
传动系统(PR2 扩展) |
<gazebo> |
0..N |
Gazebo 仿真扩展 |
<sensor> |
0..N |
传感器定义 |
2.2 基本数据类型
URDF XSD 定义了以下公用复合类型:
pose 类型——位置与姿态:
1 2 3 4 5
| <xs:complexType name="pose"> <xs:attribute name="xyz" type="xs:string" default="0 0 0" /> <xs:attribute name="rpy" type="xs:string" default="0 0 0" /> <xs:attribute name="quat_xyzw" type="xs:string" default="0 0 0 1" /> </xs:complexType>
|
| 属性 |
默认值 |
格式 |
说明 |
xyz |
"0 0 0" |
"x y z" |
平移向量(米) |
rpy |
"0 0 0" |
"roll pitch yaw" |
欧拉角(弧度),绕固定轴 X→Y→Z |
quat_xyzw |
"0 0 0 1" |
"x y z w" |
四元数(x y z w 顺序) |
注意:rpy 和 quat_xyzw 互斥。解析器优先处理 rpy。C++ 实现中,Rotation::init() 解析 rpy 字符串并调用 setFromRPY();Rotation::initQuaternion() 解析 quat_xyzw 并调用 setFromQuaternion()。
color 类型:
1 2 3
| <xs:complexType name="color"> <xs:attribute name="rgba" type="xs:string" default="0 0 0 0" /> </xs:complexType>
|
格式:"r g b a",各分量范围 [0,1]。
三、<link> 元素深度解析
3.1 XSD 定义
1 2 3 4 5 6 7 8 9
| <xs:complexType name="link"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="inertial" type="inertial" minOccurs="0" maxOccurs="1" /> <xs:element name="visual" type="visual" /> <xs:element name="collision" type="collision" /> </xs:choice> <xs:attribute name="name" type="xs:string" use="required" /> <xs:attribute name="type" type="xs:string" /> </xs:complexType>
|
| 属性 |
必需 |
说明 |
name |
是 |
连杆名称,全局唯一 |
type |
否 |
未文档化(PR2 遗留) |
子元素顺序:使用 <xs:choice> 表示 inertial、visual、collision 可以按任意顺序出现,inertial 最多一次,其余不限次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Link { public: std::string name; InertialSharedPtr inertial; VisualSharedPtr visual; CollisionSharedPtr collision; std::vector<CollisionSharedPtr> collision_array; std::vector<VisualSharedPtr> visual_array; JointSharedPtr parent_joint; std::vector<JointSharedPtr> child_joints; std::vector<LinkSharedPtr> child_links; LinkSharedPtr getParent() const; void setParent(const LinkSharedPtr &parent); };
|
3.3 <inertial> 元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <xs:complexType name="inertial"> <xs:all> <xs:element name="origin" type="pose" minOccurs="0" maxOccurs="1" /> <xs:element name="mass" type="mass" minOccurs="0" maxOccurs="1" /> <xs:element name="inertia" type="inertia" minOccurs="0" maxOccurs="1" /> </xs:all> </xs:complexType>
<xs:complexType name="inertia"> <xs:attribute name="ixx" type="xs:double" default="0" /> <xs:attribute name="ixy" type="xs:double" default="0" /> <xs:attribute name="ixz" type="xs:double" default="0" /> <xs:attribute name="iyy" type="xs:double" default="0" /> <xs:attribute name="iyz" type="xs:double" default="0" /> <xs:attribute name="izz" type="xs:double" default="0" /> </xs:complexType>
|
C++ 实现:
1 2 3 4 5 6
| class Inertial { public: Pose origin; double mass; double ixx, ixy, ixz, iyy, iyz, izz; };
|
惯性参数详解:
| 子元素 |
属性 |
类型 |
默认值 |
说明 |
<origin> |
xyz |
string |
“0 0 0” |
惯性系原点 |
|
rpy |
string |
“0 0 0” |
惯性系姿态 |
<mass> |
value |
double |
0 |
连杆质量 |
<inertia> |
ixx |
double |
0 |
绕 X 轴的转动惯量 |
|
ixy |
double |
0 |
XY 平面惯性积 |
|
ixz |
double |
0 |
XZ 平面惯性积 |
|
iyy |
double |
0 |
绕 Y 轴的转动惯量 |
|
iyz |
double |
0 |
YZ 平面惯性积 |
|
izz |
double |
0 |
绕 Z 轴的转动惯量 |
物理约束:惯性张量矩阵必须为正定对称矩阵。即:ixx > 0, iyy > 0, izz > 0,
ixx*iyy > ixy², ixx*izz > ixz², iyy*izz > iyz², 以及行列式 > 0。
URDF 解析器不验证这些约束,由使用者保证。
3.4 <visual> 元素
1 2 3 4 5 6 7
| <xs:complexType name="visual"> <xs:sequence> <xs:element name="origin" type="pose" minOccurs="0" maxOccurs="1" /> <xs:element name="geometry" type="geometry" minOccurs="1" maxOccurs="1" /> <xs:element name="material" type="material" minOccurs="0" maxOccurs="1" /> </xs:sequence> </xs:complexType>
|
C++ 实现:
1 2 3 4 5 6 7 8
| class Visual { public: Pose origin; GeometrySharedPtr geometry; std::string material_name; MaterialSharedPtr material; std::string name; };
|
3.5 <collision> 元素
1 2 3 4 5 6 7 8
| <xs:complexType name="collision"> <xs:sequence> <xs:element name="origin" type="pose" minOccurs="0" maxOccurs="1" /> <xs:element name="geometry" type="geometry" minOccurs="1" maxOccurs="1" /> <xs:element name="verbose" type="verbose" minOccurs="0" maxOccurs="1" /> </xs:sequence> <xs:attribute name="name" type="xs:string" /> </xs:complexType>
|
与 <visual> 的关键区别:
- 不支持
<material> 子元素
- 有可选的
name 属性(用于标识特定的碰撞体)
- 有一个弃用的
verbose 子元素
3.6 <geometry> 支持的几何类型
1 2 3 4 5 6 7 8 9
| <xs:complexType name="geometry"> <xs:choice> <xs:element name="box" type="box" /> <xs:element name="cylinder" type="cylinder" /> <xs:element name="sphere" type="sphere" /> <xs:element name="mesh" type="mesh" /> <xs:element name="capsule" type="capsule" /> </xs:choice> </xs:complexType>
|
| 几何类型 |
参数 |
说明 |
<box size="x y z"/> |
size: 三边长 |
轴对齐盒体 |
<cylinder radius="r" length="l"/> |
radius, length |
圆柱,轴沿 Z |
<sphere radius="r"/> |
radius |
球体 |
<mesh filename="path" scale="x y z"/> |
filename(必填), scale |
三角网格(STL/DAE/OBJ) |
<capsule radius="r" length="l"/> |
radius, length |
胶囊体(XSD 扩展) |
C++ 继承体系:
1 2 3 4 5 6
| Geometry (abstract) ├── Sphere: double radius ├── Box: Vector3 dim ├── Cylinder: double length, radius ├── Mesh: std::string filename, Vector3 scale └── Capsule: double length, radius
|
类型枚举:SPHERE=0, BOX=1, CYLINDER=2, MESH=3, CAPSULE=4
3.7 <material> 元素
全局材质(在 <robot> 下声明,可被多个 <visual> 引用):
1 2 3 4 5 6 7
| <xs:complexType name="material_global"> <xs:sequence> <xs:element name="color" type="color" minOccurs="0" maxOccurs="1" /> <xs:element name="texture" type="texture" minOccurs="0" maxOccurs="1" /> </xs:sequence> <xs:attribute name="name" type="xs:string" use="required" /> </xs:complexType>
|
内联材质(在 <visual> 内声明,无需 name):
1 2 3 4 5 6 7
| <xs:complexType name="material"> <xs:sequence> <xs:element name="color" type="color" minOccurs="0" maxOccurs="1" /> <xs:element name="texture" type="texture" minOccurs="0" maxOccurs="1" /> </xs:sequence> <xs:attribute name="name" type="xs:string" /> </xs:complexType>
|
C++ 类:
1 2 3 4 5 6
| class Material { public: std::string name; std::string texture_filename; Color color; };
|
四、<joint> 元素深度解析
4.1 XSD 定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <xs:complexType name="joint"> <xs:all> <xs:element name="origin" type="pose" minOccurs="0" maxOccurs="1" /> <xs:element name="parent" type="parent" minOccurs="1" maxOccurs="1" /> <xs:element name="child" type="child" minOccurs="1" maxOccurs="1" /> <xs:element name="axis" type="axis" minOccurs="0" maxOccurs="1" /> <xs:element name="calibration" type="calibration" minOccurs="0" maxOccurs="1" /> <xs:element name="dynamics" type="dynamics" minOccurs="0" maxOccurs="1" /> <xs:element name="limit" type="limit" minOccurs="0" maxOccurs="1" /> <xs:element name="safety_controller" type="safety_controller" minOccurs="0" maxOccurs="1" /> <xs:element name="mimic" type="mimic" minOccurs="0" maxOccurs="1" /> </xs:all> <xs:attribute name="name" type="xs:string" use="required" /> <xs:attribute name="type" type="JointType" use="required" /> </xs:complexType>
|
4.2 关节类型(JointType)
1 2 3 4 5 6 7 8 9 10
| <xs:simpleType name="JointType"> <xs:restriction base="xs:string"> <xs:enumeration value="revolute"/> <xs:enumeration value="continuous"/> <xs:enumeration value="prismatic"/> <xs:enumeration value="fixed"/> <xs:enumeration value="floating"/> <xs:enumeration value="planar"/> </xs:restriction> </xs:simpleType>
|
| 类型 |
自由度 |
轴含义 |
是否需要 <limit> |
说明 |
revolute |
1 (旋转) |
旋转轴 |
是 |
有限角度旋转关节 |
continuous |
1 (旋转) |
旋转轴 |
否 |
无限旋转关节(如轮子) |
prismatic |
1 (平移) |
平移方向 |
是 |
有限位移滑动关节 |
fixed |
0 |
N/A |
否 |
刚性连接 |
floating |
6 (3T+3R) |
N/A |
否 |
六自由度浮动关节 |
planar |
3 (2T+1R) |
平面法线 |
否 |
平面运动关节 |
C++ 枚举:
1
| enum { UNKNOWN, REVOLUTE, CONTINUOUS, PRISMATIC, FLOATING, PLANAR, FIXED };
|
4.3 子元素详解
<parent> 和 <child>
1 2 3 4 5 6
| <xs:complexType name="parent"> <xs:attribute name="link" type="xs:string" use="required" /> </xs:complexType> <xs:complexType name="child"> <xs:attribute name="link" type="xs:string" use="required" /> </xs:complexType>
|
parent.link: 父连杆名称
child.link: 子连杆名称
- URDF 必须有向无环图结构,不能形成闭环
<origin>
从父连杆坐标系到关节坐标系的变换。
<axis>
1 2 3
| <xs:complexType name="axis"> <xs:attribute name="xyz" type="xs:string" default="1 0 0" /> </xs:complexType>
|
- 默认值:
"1 0 0"(X 轴)
- 对于
revolute 和 continuous:旋转轴
- 对于
prismatic:平移方向
- 对于
planar:平面法线
- 对于
fixed 和 floating:忽略
<limit>
1 2 3 4 5 6 7 8 9
| <xs:complexType name="limit"> <xs:attribute name="lower" type="xs:double" default="-INF" /> <xs:attribute name="upper" type="xs:double" default="INF" /> <xs:attribute name="effort" type="xs:double" default="INF" /> <xs:attribute name="velocity" type="xs:double" default="INF" /> <xs:attribute name="acceleration" type="xs:double" default="INF" /> <xs:attribute name="deceleration" type="xs:double" default="INF" /> <xs:attribute name="jerk" type="xs:double" default="INF" /> </xs:complexType>
|
| 属性 |
单位 |
默认值 |
适用关节类型 |
lower |
rad 或 m |
-INF |
revolute, prismatic |
upper |
rad 或 m |
INF |
revolute, prismatic |
effort |
N·m 或 N |
INF |
所有(除 fixed) |
velocity |
rad/s 或 m/s |
INF |
所有(除 fixed) |
acceleration |
rad/s² 或 m/s² |
INF |
扩展字段 |
deceleration |
rad/s² 或 m/s² |
INF |
扩展字段 |
jerk |
rad/s³ 或 m/s³ |
INF |
扩展字段 |
C++ 默认值(v3.0.0 更新):
1 2 3 4 5 6 7 8 9 10
| class JointLimits { public: double lower = -std::numeric_limits<double>::infinity(); double upper = std::numeric_limits<double>::infinity(); double effort = std::numeric_limits<double>::infinity(); double velocity = std::numeric_limits<double>::infinity(); double acceleration = std::numeric_limits<double>::infinity(); double deceleration = std::numeric_limits<double>::infinity(); double jerk = std::numeric_limits<double>::infinity(); };
|
<dynamics>
1 2 3 4
| <xs:complexType name="dynamics"> <xs:attribute name="damping" type="xs:double" default="0" /> <xs:attribute name="friction" type="xs:double" default="0" /> </xs:complexType>
|
| 属性 |
单位 |
说明 |
damping |
N·s/m 或 N·m·s/rad |
粘性阻尼系数 |
friction |
N 或 N·m |
静摩擦力矩/力 |
<mimic>
1 2 3 4 5
| <xs:complexType name="mimic"> <xs:attribute name="joint" type="xs:string" use="required" /> <xs:attribute name="multiplier" type="xs:double" default="1" /> <xs:attribute name="offset" type="xs:double" default="0" /> </xs:complexType>
|
公式:q_joint = multiplier × q_mimicked_joint + offset
1 2 3 4 5
| JointMimic { std::string joint_name; // 被模仿的关节名称 double offset; // 偏置 double multiplier; // 乘数因子 }
|
<calibration>
1 2 3 4 5
| <xs:complexType name="calibration"> <xs:attribute name="reference_position" type="xs:double"/> <xs:attribute name="rising" type="xs:double"/> <xs:attribute name="falling" type="xs:double"/> </xs:complexType>
|
标记为”未受支持的隐藏特性”——用于关节校准参考数据。
<safety_controller>
1 2 3 4 5 6
| <xs:complexType name="safety_controller"> <xs:attribute name="soft_lower_limit" type="xs:double" default="0" /> <xs:attribute name="soft_upper_limit" type="xs:double" default="0" /> <xs:attribute name="k_position" type="xs:double" default="0" /> <xs:attribute name="k_velocity" type="xs:double" use="required" /> </xs:complexType>
|
标记为”高度 PR2 特有,不推荐通用使用”——实现软限位保护。
4.4 C++ Joint 完整数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Joint { public: std::string name; enum { UNKNOWN, REVOLUTE, CONTINUOUS, PRISMATIC, FLOATING, PLANAR, FIXED } type; Vector3 axis; std::string child_link_name; std::string parent_link_name; Pose parent_to_joint_origin_transform; JointDynamicsSharedPtr dynamics; JointLimitsSharedPtr limits; JointSafetySharedPtr safety; JointCalibrationSharedPtr calibration; JointMimicSharedPtr mimic; };
|
五、Xacro 宏扩展系统
5.1 概述
Xacro(XML Macros)是 URDF 生态中最重要的配套工具。它是一个 XML 宏预处理语言,在 ROS 1 和 ROS 2 中均受支持,通过 xacro CLI 将 .xacro 文件展开为纯 .urdf。
安装(ROS 2):
1
| sudo apt install ros-${ROS_DISTRO}-xacro
|
命令行:
1 2
| xacro my_robot.xacro > my_robot.urdf xacro my_robot.xacro -o my_robot.urdf
|
5.2 核心语法
命名空间声明
1
| <robot xmlns:xacro="http://www.ros.org/wiki/xacro">
|
属性定义(Properties)
1 2 3 4 5
| <xacro:property name="arm_length" value="0.5" /> <xacro:property name="pi" value="${math.pi}" /> <xacro:property name="zero_pose"> <origin xyz="0 0 0" rpy="0 0 0" /> </xacro:property>
|
| 属性 |
说明 |
name |
属性名称 |
value |
属性值,支持 ${...} 表达式 |
scope |
作用域:local(默认)、parent、global |
表达式求值 ${...}
内部的 Python 表达式可以访问:
- 标准操作:算术运算、字符串操作、列表推导式
- 可用符号:
pi, sin, cos, tan, exp, log, sqrt, fabs, radians, degrees
- Python 内置:
list, dict, map, len, str, float, int, True, False, min, max, round
- xacro 特有:
| 函数 |
说明 |
load_yaml(file) |
加载 YAML 配置文件 |
dotify(dict) |
将字典转换为点式访问 |
message(msg) |
打印普通信息 |
warning(msg) |
打印黄色警告 |
error(msg) |
打印红色错误 |
abs_filename(file) |
返回当前文件目录下的绝对路径 |
宏定义
1 2 3 4 5 6 7
| <xacro:macro name="arm" params="prefix parent reflect:=1"> <joint name="${prefix}_joint" type="revolute"> <parent link="${parent}"/> <child link="${prefix}_link"/> <limit effort="100" velocity="${reflect * 2}" lower="-1" upper="1"/> </joint> </xacro:macro>
|
| 参数格式 |
含义 |
name |
普通文本参数,使用 ${name} |
*block |
单星号 XML 块参数,保持外层标签 |
**block |
双星号 XML 块参数,只取内部内容 |
x:=default |
带默认值参数 |
x:=^ |
继承外层同名属性 |
| `x:=^ |
${default_val}` |
条件控制
1 2 3 4 5 6 7 8 9 10
| <xacro:if value="${use_gazebo}"> <gazebo> <plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so"/> </gazebo> </xacro:if> <xacro:unless value="${sim_only}"> <transmission name="${prefix}_trans"> ... </transmission> </xacro:unless>
|
文件包含
1 2
| <xacro:include filename="$(find my_robot)/urdf/common.xacro" /> <xacro:include filename="sensors.xacro" ns="sensors" />
|
| 属性 |
说明 |
filename |
文件路径,支持 $(find pkg) / $(cwd) / 相对路径 |
ns |
命名空间前缀,防止名称冲突(如 sensors.my_macro) |
循环模拟
Xacro 不直接支持循环,但可以通过递归宏实现:
1 2 3 4 5 6 7
| <xacro:macro name="loop" params="items:=^"> <xacro:if value="${items}"> <xacro:property name="item" value="${items.pop(0)}"/> <link name="${item}"/> <xacro:loop/> </xacro:if> </xacro:macro>
|
5.3 处理管线
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| .xacro 文件 │ ├── XML 解析 ├── 包含文件递归展开(xacro:include) ├── 属性求值(lazy evaluation) ├── 条件分支处理(xacro:if/unless) ├── 宏展开(参数替换 + 递归) ├── 表达式计算(${...}, $(arg...)) └── 注释清理(xacro 标签前的注释被移除) │ ▼ .urdf 纯 XML 文件 │ ▼ urdfdom 解析器(C++ 或 Python)
|
六、ROS 中 URDF 的使用方式
6.1 ROS 1 典型管线
urdfdom 解析链:
1 2 3 4 5 6 7
| check_urdf my_robot.urdf
urdf_to_graphiz my_robot.urdf
|
C++ API 签名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| namespace urdf { ModelInterfaceSharedPtr parseURDF(const std::string &xml_string); ModelInterfaceSharedPtr parseURDFFile(const std::string &path); }
class ModelInterface { public: LinkConstSharedPtr getRoot() const; LinkConstSharedPtr getLink(const std::string& name) const; JointConstSharedPtr getJoint(const std::string& name) const; const std::string& getName() const; MaterialSharedPtr getMaterial(const std::string& name) const; void getLinks(std::vector<LinkSharedPtr>& links) const; void clear(); };
|
Python API(ROS 1):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import rospy from urdf_parser_py.urdf import URDF
robot = URDF.from_parameter_server() robot = URDF.from_xml_string(xml_str)
for joint_name, joint in robot.joint_map.items(): print(f"Joint: {joint_name}, type: {joint.type}") print(f" parent: {joint.parent}, child: {joint.child}")
for link_name, link in robot.link_map.items(): if link.inertial: print(f"Link: {link_name}, mass: {link.inertial.mass}")
|
关键 ROS 节点:
| 节点/功能 |
说明 |
robot_state_publisher |
读取 URDF,根据关节状态发布 tf 变换 |
joint_state_publisher |
从 GUI/参数发布关节状态 |
robot_description 参数 |
标准参数名,存储 URDF 文本 |
check_urdf |
URDF 合法性检查工具 |
6.2 robot_state_publisher 实现
1 2 3 4 5 6 7 8 9 10
| void RobotStatePublisher::publishTransforms( const std::map<std::string, double>& joint_positions, const ros::Time& time, const std::string& parent_frame) const { }
|
6.3 ROS 2 中 URDF 的使用
ROS 2 继承了 URDF 的大部分功能,但有显著变化:
| 方面 |
ROS 1 |
ROS 2 |
| 解析库 |
urdfdom + urdf_parser_py |
urdfdom (C++), Python 绑定通过 urdf_parser_py |
| xacro |
ROS 包 |
独立维护,ros2 分支 |
| robot_state_publisher |
C++ 节点 |
C++ 节点,可选的 Python 实现 |
| 参数服务 |
/robot_description 参数 |
节点参数 robot_description |
| 发布方式 |
tf (topic: /tf) |
tf2 (topic: /tf, /tf_static) |
| 多机器人支持 |
需手动前缀 |
原生名空间支持 |
ROS 2 中的 C++ 用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <urdf_parser/urdf_parser.h>
std::string urdf_str; node->get_parameter("robot_description", urdf_str); auto model = urdf::parseURDF(urdf_str);
RobotStatePublisher rsp(node); rsp.subscribe();
|
ROS 2 Python 用法:
1 2 3 4 5 6 7
| from urdf_parser_py.urdf import URDF import rclpy
node = rclpy.create_node('urdf_loader')
param = node.get_parameter('robot_description').value robot = URDF.from_xml_string(param)
|
七、URDF 的已知限制
7.1 结构限制
| 限制 |
说明 |
影响场景 |
| 树形结构 |
URDF 强制有向无环树,不支持闭环运动链 |
并联机构、闭链机器人无法直接表示 |
| 单根 |
必须有且仅有一个根 link(无父关节的 link) |
多机器人系统需要拼接 |
| 无世界坐标系 |
没有 <world> 概念,需要仿真器补充 |
移动机器人需额外处理 base_link 语义 |
| 无环境描述 |
只能描述单个机器人,不能描述场景 |
不能描述障碍物、地面等 |
| 固定关节语义弱 |
fixed 关节只是没有自由度的关节,不支持刚性连接外的语义 |
焊接、粘合无差异表达 |
| 传感器支持差 |
只有不稳定、几乎无人用的 <sensor> 元素 |
实际用 Gazebo 插件扩展 |
7.2 物理/仿真限制
| 限制 |
说明 |
| 无摩擦定义 |
接触面摩擦参数只能通过 <gazebo> 扩展 |
| 无接触参数 |
无 restitution(恢复系数)、刚度等接触参数 |
| 无材质物理属性 |
<material> 只定义视觉属性 |
| 无轮胎/弹簧模型 |
无专业模型支持 |
| 无软体/可变形体 |
所有 link 均为刚体 |
| 惯性参数不可选 |
每个 link 可不指定 inertial,但几乎所有物理引擎都需要 |
7.3 工程限制
| 限制 |
说明 |
| 无版本控制 |
version 属性存在但不被解析器检查 |
| 无命名空间 |
所有 link/joint 名称全局平坦 |
| 无条件包含 |
需要 xacro 补偿 |
| 无模块化 |
大型机器人(如 PR2)的 URDF 文件极为庞大 |
| XML 不可编程 |
无循环、无变量、无计算能力 |
| 字符串类型传递 |
所有数值属性在 XML 中均为字符串,解析容错差 |
| 无严格 Schema 验证 |
虽然存在 XSD,但解析器默认不验证 |
| Gazebo 扩展非标准 |
<gazebo> 标签使用 lax content,任何内容均可,缺乏结构 |
SDFormat(Simulation Description Format)由 Open Source Robotics Foundation(OSRF)开发,最初是 Gazebo Classic 仿真器的原生格式。现由 GitHub 上的 gazebosim/sdformat 项目维护,最新版本为 SDF 1.12+(libsdformat 15/16)。
核心差异对比:
| 维度 |
URDF |
SDFormat |
| 描述范围 |
单一机器人 |
完整世界(机器人+环境+物理+光照) |
| 结构 |
树形 |
带闭环的图结构(Supports closed chains) |
| 物理引擎参数 |
无 |
完整支持(摩擦、接触、重力等) |
| 传感器 |
简陋 <sensor> |
完整(camera, lidar, IMU, contact, force-torque, GPS…) |
| 软体 |
不支持 |
支持(有限元模型) |
| 执行器 |
无 |
支持(螺旋桨、电机模型) |
| 粒子系统 |
无 |
支持 |
| 燃油模型 |
无 |
原生支持 |
| 多重碰撞 |
单 link 可多 collision |
同左(sdf 更完善) |
| 模块化/包含 |
仅 xacro |
原生 <include> 和 <model> 嵌套 |
| 版本管理 |
名义上有 |
严格版本控制,向后兼容 |
| 范式演进 |
无,停滞 v1.0 |
持续演进 |
Gazebo Classic 支持在 URDF 中嵌入 <gazebo> 标签来补充仿真参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <robot name="my_robot"> <link name="base_link"> <visual> <geometry><mesh filename="base.dae"/></geometry> </visual> </link> <gazebo reference="base_link"> <mu1>0.5</mu1> <mu2>0.5</mu2> <kp>1000000</kp> <kd>100</kd> <minDepth>0.001</minDepth> <material>Gazebo/Grey</material> </gazebo> <gazebo reference="base_joint"> <disableFixedJointLimits>true</disableFixedJointLimits> <preserveFixedJoint>false</preserveFixedJoint> <maxVel>10.0</maxVel> <minVel>-10.0</minVel> </gazebo> <gazebo reference="camera_link"> <sensor name="head_camera" type="camera"> <camera> <horizontal_fov>1.3962634</horizontal_fov> <image> <width>640</width> <height>480</height> <format>R8G8B8</format> </image> <clip><near>0.01</near><far>100</far></clip> </camera> </sensor> </gazebo> </robot>
|
8.3 URDF→SDF 转换
sdformat 内部包含 URDF 解析器,可以自动将 URDF 转换为 SDF:
1 2 3 4 5 6 7 8 9
| #include <sdf/sdf.hh>
sdf::Root root; sdf::Errors errors = root.Load(urdf_string, sdf::ParserConfig());
|
转换损失:
| 数据类型 |
转换保真度 |
说明 |
| Link/Joint 名称 |
✅ 完整 |
直接映射 |
| 几何体 |
✅ 完整 |
box/sphere/cylinder/mesh 映射 |
| 惯性参数 |
✅ 完整 |
惯性张量直接映射 |
| 运动学树 |
✅ 完整 |
URDF 树 → SDF model |
| 关节限位 |
✅ 完整 |
<limit> → SDF 关节轴属性 |
| 视觉材质 |
⚠️ 部分 |
URDF 材质不含 PBR 参数 |
| Gazebo 扩展 |
⚠️ 部分 |
大部分扩展可映射 |
| 传动系统 |
❌ 不标准 |
URDF <transmission> 无 SDF 等价物,需 Plugin |
| Safety 控制器 |
❌ 不标准 |
PR2 特有,SDF 无等价物 |
| Calibration |
❌ 不标准 |
硬件校准信息,仿真场景不需要 |
8.4 为何 URDF 仍然重要
尽管 SDFormat 在各方面都更强大,URDF 依然是 ROS 生态的标准原因:
- 机器人描述参数:ROS 2 的
robot_state_publisher、robot_localization、moveit2 均原生支持 URDF
- 工具链成熟:check_urdf, urdf_to_graphiz 等工具完善
- Xacro 生态:大量现有机器人模型使用 xacro + URDF
- 社区惯性:发布 18 年,积累了海量兼容模型
- 简洁性:对于纯运动学/动力学描述,URDF 的 XML 更简洁
- MoveIt 2 依赖:MoveIt 的运动规划核心依赖 URDF 的 SRDF 扩展
- ROS 2 混合模式:ROS 2 使用 URDF 描述机器人,但推荐的 Gazebo 仿真使用 SDF 描述世界
九、URDF 与现代机器人系统的适配问题
9.1 移动操作机器人
现代移动操作机器人(如 Stretch、Fetch、Tiago)面临的 URDF 局限:
1 2 3 4 5 6 7 8 9
| 问题:移动基座与机械臂的 URDF 拼接 ├── 单独描述机械臂(arm.urdf.xacro) ├── 单独描述移动基座(base.urdf.xacro) ├── xacro:include 合并 ├── 但 base_link 语义在不同场景下不同: │ ├── 固定基座仿真 → base_link 为根 │ ├── 移动仿真 → base_link 浮动,odom 为世界 │ └── 真实机器人 → base_link 与 odom 由里程计连接 └── ROS 2 通过 tf 树解决,URDF 本身无世界坐标
|
9.2 多足机器人
四足/双足机器人的极端挑战:
1 2 3 4 5 6 7 8
| 问题:支撑相切换时的运动链变化 ├── 行走时:左腿 swing → 右腿 stance,接触点变化 ├── URDF 要求固定父子关系 ├── 解决办法:将所有足端定义为浮动或固定关节 ├── 接触力由物理引擎/控制器管理 └── ROS 2 的 quadruped 方案通常用: ├── URDF 描述运动学(腿关节) └── 外部接触模型(不依赖于 URDF)
|
9.3 软体机器人
URDF 完全不支持软体机器人。解决方案:
- 在 URDF 中用多个刚体段近似
- 使用 Gazebo/Ignition 的软体插件
- 转向 SDFormat(有限元支持)
9.4 具身智能/机器人基础模型
现代 AI 驱动的机器人系统对 URDF 依赖的变化:
| 系统 |
URDF 使用方式 |
| NVIDIA Isaac Sim |
原生支持 URDF 导入,但推荐转换为 USD |
| MuJoCo |
通过 mujoco_urdf 转换,但推荐原生 MJCF |
| PyBullet |
直接加载 URDF,性能优秀 |
| Genesis |
直接加载 URDF,支持程序化创建 |
| SAPIEN |
原生支持 URDF,用于物体/机器人建模 |
| ManiSkill |
使用 SAPIEN 的 URDF 加载 |
十、完整的 URDF 建模范例
10.1 两连杆机械臂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| <?xml version="1.0"?> <robot name="two_link_arm" version="1.0"> <material name="blue"> <color rgba="0 0 0.8 1"/> </material> <material name="red"> <color rgba="0.8 0.1 0.1 1"/> </material>
<link name="base_link"> <inertial> <origin xyz="0 0 0" rpy="0 0 0"/> <mass value="1.0"/> <inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/> </inertial> <visual> <origin xyz="0 0 0" rpy="0 0 0"/> <geometry><box size="0.2 0.2 0.1"/></geometry> <material name="blue"/> </visual> <collision> <origin xyz="0 0 0" rpy="0 0 0"/> <geometry><box size="0.2 0.2 0.1"/></geometry> </collision> </link>
<joint name="joint1" type="revolute"> <origin xyz="0 0 0.05" rpy="0 0 0"/> <parent link="base_link"/> <child link="link1"/> <axis xyz="0 0 1"/> <limit lower="-2.618" upper="2.618" effort="100" velocity="3.14"/> <dynamics damping="0.1" friction="0.01"/> </joint>
<link name="link1"> <inertial> <origin xyz="0 0.25 0" rpy="0 0 0"/> <mass value="2.0"/> <inertia ixx="0.02" ixy="0" ixz="0" iyy="0.05" iyz="0" izz="0.02"/> </inertial> <visual> <origin xyz="0 0.25 0" rpy="0 0 0"/> <geometry><cylinder radius="0.03" length="0.5"/></geometry> <material name="red"/> </visual> <collision> <origin xyz="0 0.25 0" rpy="0 0 0"/> <geometry><cylinder radius="0.03" length="0.5"/></geometry> </collision> </link>
<joint name="joint2" type="revolute"> <origin xyz="0 0.5 0" rpy="0 0 0"/> <parent link="link1"/> <child link="link2"/> <axis xyz="0 0 1"/> <limit lower="-2.618" upper="2.618" effort="50" velocity="3.14"/> <dynamics damping="0.1" friction="0.01"/> </joint>
<link name="link2"> <inertial> <origin xyz="0 0.25 0" rpy="0 0 0"/> <mass value="1.5"/> <inertia ixx="0.015" ixy="0" ixz="0" iyy="0.03" iyz="0" izz="0.015"/> </inertial> <visual> <origin xyz="0 0.25 0" rpy="0 0 0"/> <geometry><cylinder radius="0.02" length="0.5"/></geometry> <material name="blue"/> </visual> <collision> <origin xyz="0 0.25 0" rpy="0 0 0"/> <geometry><cylinder radius="0.02" length="0.5"/></geometry> </collision> </link>
</robot>
|
10.2 使用 xacro 重构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| <?xml version="1.0"?> <robot name="two_link_arm" xmlns:xacro="http://www.ros.org/wiki/xacro"> <xacro:property name="link_length" value="0.5"/> <xacro:property name="link_radius" value="0.03"/> <xacro:macro name="default_materials"> <material name="blue"><color rgba="0 0 0.8 1"/></material> <material name="red"><color rgba="0.8 0.1 0.1 1"/></material> </xacro:macro> <xacro:default_materials/>
<xacro:macro name="simple_link" params="name length radius mass color"> <link name="${name}"> <inertial> <origin xyz="0 ${length/2} 0" rpy="0 0 0"/> <mass value="${mass}"/> <inertia ixx="${mass*(3*radius*radius + length*length)/12}" iyy="${mass*radius*radius/2}" izz="${mass*(3*radius*radius + length*length)/12}" ixy="0" ixz="0" iyz="0"/> </inertial> <visual> <origin xyz="0 ${length/2} 0" rpy="0 0 0"/> <geometry><cylinder radius="${radius}" length="${length}"/></geometry> <material name="${color}"/> </visual> <collision> <origin xyz="0 ${length/2} 0" rpy="0 0 0"/> <geometry><cylinder radius="${radius}" length="${length}"/></geometry> </collision> </link> </xacro:macro>
<link name="base_link"> <visual> <geometry><box size="0.2 0.2 0.1"/></geometry> <material name="blue"/> </visual> <inertial> <mass value="1.0"/> <inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/> </inertial> </link>
<joint name="joint1" type="revolute"> <origin xyz="0 0 0.05"/> <parent link="base_link"/> <child link="link1"/> <axis xyz="0 0 1"/> <limit lower="-2.618" upper="2.618" effort="100" velocity="3.14"/> </joint>
<xacro:simple_link name="link1" length="${link_length}" radius="${link_radius}" mass="2.0" color="red"/> <joint name="joint2" type="revolute"> <origin xyz="0 ${link_length} 0"/> <parent link="link1"/> <child link="link2"/> <axis xyz="0 0 1"/> <limit lower="-2.618" upper="2.618" effort="50" velocity="3.14"/> </joint> <xacro:simple_link name="link2" length="${link_length}" radius="${link_radius}" mass="1.5" color="blue"/>
</robot>
|
十一、ROS 2 中 URDF 的现状与未来
11.1 当前状态(2026)
| 组件 |
版本 |
ROS 2 支持 |
| urdfdom |
4.0+ (rolling) |
✅ 完全支持 |
| urdfdom_headers |
3.0.0 |
✅ 完全支持 |
| xacro |
ros2 分支 |
✅ 完全支持 |
| robot_state_publisher |
3.6+ |
✅ C++/Python 双实现 |
| urdf_parser_py |
ROS 2 fork |
✅ |
| sdformat |
14/15/16 |
✅ ROS 2 Gazebo 集成 |
11.2 已知的 ROS 2 特定限制
- 无官方 URDF→SDF 转换工具链:推荐手动迁移
- urdfdom API 不稳定:v3→v4 有 breaking changes
- 缺乏大规模验证:较新的 Rolling 版本偶有回归
- Python 绑定薄弱:ROS 2 中 urdf_parser_py 维护资源不足
11.3 社区动向
- ROS 2 + Gazebo(Ignition):推荐使用 SDF 描述完整仿真,URDF 仅用于机器人运动学
- MoveIt 2:仍然强依赖 URDF + SRDF,短期内不会全面迁移到 SDF
- 机器人基础模型:多种格式共存,URDF 通过转换器与其他格式互通
- SDG(场景描述生成):AI 数据集生成越来越多使用 SDFormat
11.4 迁移建议
| 场景 |
推荐格式 |
原因 |
| 纯 ROS 2 机器人(无仿真) |
URDF + xacro |
与现有工具兼容 |
| ROS 2 + Gazebo 仿真 |
URDF + SDF 扩展 |
渐进的迁移路径 |
| 新项目(重仿真) |
SDFormat |
功能最完整 |
| Isaac Sim / MuJoCo |
URDF + 转换 |
利用现有生态 |
| 自定义格式 |
建模语言 + 导出器 |
灵活性最高 |
十二、总结
URDF 是机器人领域使用最广泛的运动学描述语言,在 ROS 1、ROS 2、MoveIt 和大量仿真器中占据核心地位。它的设计简洁直观,但同时也面临诸多限制——树形结构、无环境描述、无物理参数、无版本管理等。
SDFormat 作为 URDF 的继任者,在描述能力上全面超越,但 URDF 凭借其庞大的社区积累、成熟的工具链和简洁的设计,短期内不会退出历史舞台。实际上,URDF 和 SDFormat 正在形成一个共生的生态系统——URDF 负责机器人运动学描述,SDFormat 负责完整仿真世界,两者通过 ROS 2 和 Gazebo 桥接。
对于机器人开发者,理解 URDF 的技术细节不仅是使用 ROS 的基础,更是理解整个现代机器人软件栈的起点。无论是使用 xacro 宏简化大型机械臂的描述,还是通过 urdfdom 的 C++ API 程序化检查机器人模型,URDF 内部的数据结构和解析机制都值得深入研究。
参考资源