URDF 基礎:描述機器人的外型與結構

2026-07-18
  • ros2
  • urdf
  • robot-model
  • xml

問題定義

在 RViz2 裡看到自己寫的機器人、在 Gazebo 裡讓它做物理模擬、用 MoveIt2 幫手臂規劃路徑——這些工具全部都需要一份共同的輸入:機器人長什麼樣子、由哪些部件組成、部件之間怎麼連接。URDF(Unified Robot Description Format)就是這份共同語言,是後面模擬與建模章節的地基。

核心概念說明

link 與 joint:機器人是一棵樹

URDF 把機器人拆成兩種基本元素:<link> 代表一個剛體部件(機身、一節手臂、一個輪子),<joint> 代表兩個 link 之間的連接關係與相對運動方式。整份 URDF 描述的機器人結構本質上是一棵樹——每個 link 只能有一個父 joint,從某個根 link(通常叫 base_link)往外展開。

joint 的 type 決定了兩個 link 之間允許的相對運動:

type說明
fixed完全固定,沒有相對運動(例如感測器外殼鎖死在機身上)
revolute繞單一軸旋轉,有角度上下限(例如手臂關節)
continuous繞單一軸旋轉,沒有角度限制(例如輪子)
prismatic沿單一軸線性滑動,有位移上下限(例如升降機構)
floating / planar較少用,分別是六自由度自由運動與平面內自由運動

一個 link 裡的三種幾何描述,各自服務不同用途

新手最容易搞混的地方,是一個 <link> 底下可以同時出現 <visual><collision><inertial> 三個區塊,且彼此互相獨立、可以使用不同的幾何形狀

  • <visual>:純粹決定「畫面上長什麼樣子」,可以用複雜的 mesh 檔案(.dae.stl),因為它只影響顯示,不影響運算。
  • <collision>:決定「物理引擎判斷碰撞時用的形狀」,實務上常故意用比 visual 更簡單的幾何(例如用一個圓柱體去近似一隻造型複雜的機械手),因為複雜 mesh 的碰撞運算成本高很多,簡化過的 collision 形狀能大幅提升模擬效能。
  • <inertial>:質量與慣性張量,只有做物理模擬(Gazebo)或動力學運算時才會用到,純粹看模型、不模擬物理的話可以省略。

實作範例:一台兩節機械手臂

檔案位置:~/ros2_ws/src/my_package/urdf/simple_arm.urdf

xml
<?xml version="1.0"?>
<robot name="simple_arm">

  <link name="base_link">
    <visual>
      <geometry>
        <cylinder radius="0.08" length="0.05"/>
      </geometry>
      <material name="grey">
        <color rgba="0.5 0.5 0.5 1"/>
      </material>
    </visual>
    <collision>
      <geometry>
        <cylinder radius="0.08" length="0.05"/>
      </geometry>
    </collision>
    <inertial>
      <mass value="1.0"/>
      <inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/>
    </inertial>
  </link>

  <link name="upper_arm">
    <visual>
      <origin xyz="0 0 0.15" rpy="0 0 0"/>
      <geometry>
        <box size="0.05 0.05 0.3"/>
      </geometry>
      <material name="blue">
        <color rgba="0.2 0.2 0.8 1"/>
      </material>
    </visual>
    <collision>
      <origin xyz="0 0 0.15" rpy="0 0 0"/>
      <geometry>
        <box size="0.05 0.05 0.3"/>
      </geometry>
    </collision>
    <inertial>
      <mass value="0.5"/>
      <inertia ixx="0.005" ixy="0" ixz="0" iyy="0.005" iyz="0" izz="0.001"/>
    </inertial>
  </link>

  <link name="forearm">
    <visual>
      <origin xyz="0 0 0.12" rpy="0 0 0"/>
      <geometry>
        <box size="0.04 0.04 0.24"/>
      </geometry>
      <material name="orange">
        <color rgba="0.9 0.5 0.1 1"/>
      </material>
    </visual>
    <collision>
      <origin xyz="0 0 0.12" rpy="0 0 0"/>
      <geometry>
        <box size="0.04 0.04 0.24"/>
      </geometry>
    </collision>
    <inertial>
      <mass value="0.3"/>
      <inertia ixx="0.002" ixy="0" ixz="0" iyy="0.002" iyz="0" izz="0.0005"/>
    </inertial>
  </link>

  <joint name="shoulder_joint" type="revolute">
    <parent link="base_link"/>
    <child link="upper_arm"/>
    <origin xyz="0 0 0.025" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-1.57" upper="1.57" effort="20" velocity="1.0"/>
  </joint>

  <joint name="elbow_joint" type="revolute">
    <parent link="upper_arm"/>
    <child link="forearm"/>
    <origin xyz="0 0 0.3" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-2.0" upper="2.0" effort="10" velocity="1.0"/>
  </joint>

</robot>

幾個容易忽略的細節:

  • 每個 link 自己的幾何形狀是定義在該 link 自己的座標系原點,而不是世界座標。upper_arm<origin xyz="0 0 0.15"> 是相對於 upper_arm 這個 link 自己的座標系往上偏移,讓箱型幾何的視覺中心對齊實際的手臂中段(因為箱體幾何預設是以自己的中心點為原點)。
  • joint 裡的 <origin> 才是決定「父子 link 之間相對位置」的地方,shoulder_jointorigin xyz="0 0 0.025" 代表 upper_arm 的座標系原點,相對於 base_link 座標系原點往上偏移 0.025 公尺。
  • <axis xyz="0 1 0"> 代表旋轉軸沿著 Y 軸——這個軸是定義在 joint 自己的座標系底下,不是世界座標。

用 RViz2 檢查模型(先睹為快,完整介紹在 4.4 節)

不需要 Gazebo,光靠 URDF 本身加上 robot_state_publisher 就能在 RViz2 裡看到模型:

bash
ros2 run robot_state_publisher robot_state_publisher --ros-args -p robot_description:="$(cat ~/ros2_ws/src/my_package/urdf/simple_arm.urdf)"

另開一個終端機驅動關節角度(沒有真實硬體或 Gazebo 時,joint_state_publisher_gui 提供一個可以拖拉滑桿的圖形介面):

bash
ros2 run joint_state_publisher_gui joint_state_publisher_gui

再開一個終端機啟動 RViz2:

bash
rviz2

預期畫面

joint_state_publisher_gui 會跳出一個小視窗,裡面有 shoulder_jointelbow_joint 兩條滑桿。RViz2 裡(把 Fixed Frame 設成 base_link,並加入 RobotModel 顯示項目後)會看到一個灰色圓柱底座,接上一段藍色長方體手臂,再接上一段橘色長方體前臂。拖動滑桿時,手臂會即時跟著轉動。

常見錯誤與除錯技巧

錯誤一:RViz2 裡完全沒有模型,Fixed Frame 顯示紅字警告

text
Fixed Frame [base_link] does not exist

原因robot_state_publisher 沒有成功解析 URDF(通常是 XML 語法錯誤),或者根本沒有執行,導致 TF 樹裡完全沒有任何座標系被廣播出來。

排除方式:先確認 URDF 語法本身沒問題,check_urdf 是專門檢查 URDF 結構合法性的工具,會直接印出 link 樹狀結構:

bash
check_urdf ~/ros2_ws/src/my_package/urdf/simple_arm.urdf
text
robot name is: simple_arm
---------- Successfully Parsed XML ---------------
root Link: base_link has 1 child(ren)
    child(1):  upper_arm
        child(1):  forearm

如果這裡就報錯,代表問題出在 URDF 本身(常見是漏寫 </link>、joint 裡的 parent/child 名稱打錯);如果 check_urdf 通過但 RViz2 還是沒東西,再確認 robot_state_publisher 有沒有正確啟動。

錯誤二:模型出現在 RViz2,但某個部件的位置或方向明顯不對

現象:手臂看起來是從機身側面伸出來,而不是預期的正上方。

原因:幾乎都是 joint 的 <origin><axis> 設錯——特別容易搞混的是 origin 裡的 rpy(roll-pitch-yaw,單位是弳度不是角度)跟預期的旋轉方向不一致。

排除方式:在 RViz2 裡把 TF 顯示打開(加入 TF 顯示項目),每個座標系會畫出紅綠藍三色軸(分別代表 X/Y/Z),可以直觀比對每個 link 的座標系方向是否符合預期,比單純盯著 XML 數字更容易抓出問題。

小結

URDF 用 link 描述部件、joint 描述連接關係與運動類型,一個 link 底下的 visual/collision/inertial 三個區塊彼此獨立、服務不同用途(顯示、碰撞運算、物理模擬)。check_urdf 是排查語法問題的第一步,RViz2 搭配 TF 顯示則是排查座標方向問題最直觀的工具。手寫 URDF 在部件數量變多、或有大量重複結構(例如四足機器人的四條腿)時會變得非常冗長,下一節會介紹用 xacro 解決這個問題。

延伸閱讀

常見問題

faq_01.log
URDF 裡一定要寫 collision 和 inertial 嗎?
如果只是要在 RViz2 裡看外觀、不做物理模擬,collision 和 inertial 可以省略,只寫 visual 也能顯示。但只要打算丟進 Gazebo 做物理模擬,或用 MoveIt2 做碰撞檢測,collision 與 inertial 就是必要的——沒有 inertial 的 link 在 Gazebo 裡預設質量為 0,物理引擎會把它當成無效剛體,行為通常會很怪異。
faq_02.log
URDF 可以描述閉鏈(closed-chain)機構嗎,例如平行四邊形連桿?
不行,URDF 的 joint 結構本質上是一棵樹(tree),每個 link 只能有一個父 joint,不允許形成環狀連接。真正的閉鏈機構在 URDF 裡通常只能近似成開鏈結構,或改用其他描述格式(例如 SDF)處理。
faq_03.log
一個 URDF 檔案可以描述多台機器人嗎?
一份 URDF 只描述一個 <robot> 元素,代表一台機器人(或一個獨立的可動結構)。如果場景裡有多台機器人,通常是分別載入多份 URDF,並各自給予不同的命名空間或 TF 前綴來避免座標系名稱衝突,這在後面 TF2 那一節會提到。