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 顺序)

注意rpyquat_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> 表示 inertialvisualcollision 可以按任意顺序出现,inertial 最多一次,其余不限次数。

3.2 C++ API 数据结构(urdfdom_headers v3.0.0)

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; // 惯性坐标系相对于 link 原点的偏移
double mass; // 质量(kg)
double ixx, ixy, ixz, iyy, iyz, izz; // 惯性张量(kg·m²)
};

惯性参数详解

子元素 属性 类型 默认值 说明
<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; // rgba 四分量
};

四、<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 轴)
  • 对于 revolutecontinuous:旋转轴
  • 对于 prismatic:平移方向
  • 对于 planar:平面法线
  • 对于 fixedfloating:忽略

<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; // 安全控制器(PR2 特有)
JointCalibrationSharedPtr calibration; // 校准(PR2 特有)
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(默认)、parentglobal

表达式求值 ${...}

内部的 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
# 检查 URDF 合法性
check_urdf my_robot.urdf

# 解析成模型图
urdf_to_graphiz my_robot.urdf

# C++ 加载

C++ API 签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// urdfdom 解析入口(urdf_parser/include/urdf_parser/urdf_parser.h)
namespace urdf {
ModelInterfaceSharedPtr parseURDF(const std::string &xml_string);
ModelInterfaceSharedPtr parseURDFFile(const std::string &path);
}

// ModelInterface 关键接口
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_description 加载
robot = URDF.from_xml_string(xml_str) # 从 XML 字符串加载

# 访问结构
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
{
// 1. 根据 joint_positions 计算所有 link 的位姿
// 2. 发布 <parent_frame> → link_frame 的 tf 变换
// 3. 支持前缀机制用于多机器人
}

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);

// robot_state_publisher 参数配置
// launch 文件示例
RobotStatePublisher rsp(node);
rsp.subscribe(); // 自动订阅 /joint_states

// 支持从 topic 而非参数读取描述
// 新增功能:robot_state_publisher 可在 topic 上接收 URDF

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,任何内容均可,缺乏结构

八、从 URDF 到 SDFormat 的演进

8.1 SDFormat 概述

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 持续演进

8.2 SDFormat 扩展 URDF 的方式

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 扩展 -->
<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 扩展 -->
<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
// libsdformat 的 URDF 加载
#include <sdf/sdf.hh>

sdf::Root root;
sdf::Errors errors = root.Load(urdf_string, sdf::ParserConfig());
// 内部使用 urdfdom 解析 URDF,再转为 SDF DOM

// 等价于命令行
// gz sdf -p my_robot.urdf # 输出带 Gazebo 扩展的 SDF

转换损失

数据类型 转换保真度 说明
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 生态的标准原因:

  1. 机器人描述参数:ROS 2 的 robot_state_publisherrobot_localizationmoveit2 均原生支持 URDF
  2. 工具链成熟:check_urdf, urdf_to_graphiz 等工具完善
  3. Xacro 生态:大量现有机器人模型使用 xacro + URDF
  4. 社区惯性:发布 18 年,积累了海量兼容模型
  5. 简洁性:对于纯运动学/动力学描述,URDF 的 XML 更简洁
  6. MoveIt 2 依赖:MoveIt 的运动规划核心依赖 URDF 的 SRDF 扩展
  7. 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>

<!-- 关节 1(肩关节) -->
<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>

<!-- 连杆 1 -->
<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>

<!-- 关节 2(肘关节) -->
<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>

<!-- 连杆 2 -->
<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 参数 -->
<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>

<!-- 关节 1 -->
<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"/>

<!-- 关节 2 -->
<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 特定限制

  1. 无官方 URDF→SDF 转换工具链:推荐手动迁移
  2. urdfdom API 不稳定:v3→v4 有 breaking changes
  3. 缺乏大规模验证:较新的 Rolling 版本偶有回归
  4. 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 内部的数据结构和解析机制都值得深入研究。


参考资源