2. URDF基础语法:link与joint的定义、几何体与材质、坐标变换基础

URDF,说白了就是机器人的「骨架描述语言」。你想想看,要让机器人动起来、导航、避障,总得先告诉系统它长什么样吧?底盘多宽、轮子多大、激光雷达装在哪儿——这些信息全写在 URDF 里。我个人习惯把 URDF 看作机器人的「身份证」,Nav2 要读懂你的机器人,第一步就是解析这个文件。

我记得刚接触 ROS 那会儿,总觉得 URDF 语法很死板,后来才发现它的设计其实非常巧妙。今天咱们就把最核心的三个东西讲透:link、joint,以及它们背后的坐标变换逻辑。

URDF 基础语法 link (刚体部件) 视觉/碰撞/惯性 joint (关节) fixed / revolute / continuous 坐标变换 (TF) origin / xyz / rpy 几何体 (box/cylinder/sphere) 材质 & 颜色 父子坐标系 & 变换树

2.1 link —— 机器人的每一块骨头

你可以把 link 想象成机器人的一个刚体部件:底盘、轮子、手臂连杆、摄像头支架……每个 link 都有自己的名字,而且名字必须唯一。我见过不少新手在写 URDF 时给两个 link 起了相同的名字,结果 Gazebo 直接罢工——嗯,ROS 在这点上非常严格。

一个典型的 link 包含三个部分:视觉(visual)、碰撞(collision)、惯性(inertial)。其中视觉和碰撞都依赖几何体描述。来看一个最简单的底盘 link:

<link name="base_link">
    <visual>
        <geometry>
            <box size="0.5 0.4 0.2"/>
        </geometry>
        <material name="blue">
            <color rgba="0.2 0.4 0.9 1.0"/>
        </material>
    </visual>
    <collision>
        <geometry>
            <box size="0.5 0.4 0.2"/>
        </geometry>
    </collision>
    <inertial>
        <mass value="5.0"/>
        <inertia ixx="0.1" ixy="0" ixz="0" iyy="0.1" iyz="0" izz="0.1"/>
    </inertial>
</link>

这里我用了 box 几何体,尺寸是 0.5m x 0.4m x 0.2m。材质用了蓝色,RGBA 值注意最后一个通道是透明度,1.0 表示完全不透明。碰撞几何体我通常会和视觉保持一致,除非你想做简化碰撞模型——这个后面会讲到。

💡 我的小习惯: 给 link 命名时尽量用 base_link、left_wheel、laser_frame 这种见名知意的风格。千万别用 link1、link2…… 不然三个月后你自己都看不懂。

2.2 joint —— 连接与运动的核心

joint 定义了 link 之间的连接关系和运动自由度。没有 joint,你的机器人就是一坨散落的零件。joint 的类型决定了运动方式:

类型说明常用场景
fixed刚性连接,无相对运动底盘与激光雷达支架
revolute绕单轴旋转,有角度限制关节臂、舵机轮
continuous绕单轴无限旋转全向轮、差速轮
prismatic沿单轴直线运动升降台、伸缩臂
planar平面内两自由度移动全向底盘(较少用)
floating六自由度完全自由无人机、水下机器人

拿最常见的差速底盘举例,轮子一般用 continuous 关节。我曾经在项目里把轮子关节误设成 revolute,结果轮子只能转 ±90°,机器人原地画圈……排查了半天才发现是关节类型搞错了。

<joint name="left_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="left_wheel"/>
    <origin xyz="-0.2 0.3 0.0" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
</joint>

注意 origin 定义了子 link 相对于父 link 的位置偏移(xyz)和旋转(rpy)。axis 指定了旋转轴,这里 0 1 0 表示绕 Y 轴旋转——也就是轮子向前滚动的方向。

⚠️ 避坑指南: 我曾经在调试时发现机器人轮子悬空,后来才意识到 origin 的 z 值设错了。记住:origin 是子 link 坐标系原点在父 link 坐标系中的位置,不是子 link 的中心点!如果你用圆柱体做轮子,记得把圆柱的轴线与关节轴对齐。

2.3 几何体与材质 —— 让机器人有血有肉

URDF 支持三种基本几何体:box、cylinder、sphere。你还可以用 mesh 导入 STL 或 Collada 文件,实现任意复杂形状。我个人建议:在原型阶段尽量用基本几何体,调试速度快;最后再替换成精细的 mesh。

材质定义可以复用。你可以在 <robot> 根标签下定义全局材质,然后在各个 link 中引用:

<material name="dark_gray">
    <color rgba="0.3 0.3 0.3 1.0"/>
</material>

<link name="right_wheel">
    <visual>
        <geometry>
            <cylinder radius="0.08" length="0.04"/>
        </geometry>
        <material name="dark_gray"/>
    </visual>
</link>

这样写的好处是,想换颜色时只改一处就行。我习惯把颜色定义放在 URDF 文件头部,像变量声明一样清晰。

2.4 坐标变换基础 —— 理解 TF 树

URDF 里每个 link 都有自己的坐标系,joint 的 origin 就是坐标系之间的变换关系。这些变换组合起来就是一棵 TF 树。Nav2 要定位、规划路径,全靠 TF 树知道每个部件在哪儿。

举个例子:base_link 是机器人的本体坐标系,laser_frame 是激光雷达的坐标系。如果激光雷达装在底盘前方 0.2m、高度 0.1m,那么 joint 的 origin 就是 xyz="0.2 0 0.1"。你想想看,如果这个值写错了,激光雷达扫描的数据映射到地图上就会整体偏移——我在现场就吃过这种亏,折腾了一下午才发现是 URDF 里 z 轴差了 5 厘米。

🔑 核心原则: 每个 link 的坐标系原点由父 link 的 joint 决定。整个 TF 树必须是一棵有向无环图,根节点通常是 base_link 或 base_footprint。Nav2 默认从 base_link 开始读取变换。

最后,给你一个快速检查 URDF 的小命令:

# 安装工具
sudo apt install liburdfdom-tools
# 检查 URDF 语法
check_urdf my_robot.urdf

如果输出没有报错,并且显示了 link 和 joint 的数量,基本就稳了。我每次写完 URDF 都会跑一遍这个命令,比肉眼检查靠谱得多。