URDF 基礎:描述機器人的外型與結構
- 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 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_joint的origin 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 裡看到模型:
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 提供一個可以拖拉滑桿的圖形介面):
ros2 run joint_state_publisher_gui joint_state_publisher_gui
再開一個終端機啟動 RViz2:
rviz2
預期畫面
joint_state_publisher_gui 會跳出一個小視窗,裡面有 shoulder_joint、elbow_joint 兩條滑桿。RViz2 裡(把 Fixed Frame 設成 base_link,並加入 RobotModel 顯示項目後)會看到一個灰色圓柱底座,接上一段藍色長方體手臂,再接上一段橘色長方體前臂。拖動滑桿時,手臂會即時跟著轉動。
常見錯誤與除錯技巧
錯誤一:RViz2 裡完全沒有模型,Fixed Frame 顯示紅字警告
Fixed Frame [base_link] does not exist
原因:robot_state_publisher 沒有成功解析 URDF(通常是 XML 語法錯誤),或者根本沒有執行,導致 TF 樹裡完全沒有任何座標系被廣播出來。
排除方式:先確認 URDF 語法本身沒問題,check_urdf 是專門檢查 URDF 結構合法性的工具,會直接印出 link 樹狀結構:
check_urdf ~/ros2_ws/src/my_package/urdf/simple_arm.urdf
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 解決這個問題。
延伸閱讀
常見問題
- URDF 裡一定要寫 collision 和 inertial 嗎?
- 如果只是要在 RViz2 裡看外觀、不做物理模擬,collision 和 inertial 可以省略,只寫 visual 也能顯示。但只要打算丟進 Gazebo 做物理模擬,或用 MoveIt2 做碰撞檢測,collision 與 inertial 就是必要的——沒有 inertial 的 link 在 Gazebo 裡預設質量為 0,物理引擎會把它當成無效剛體,行為通常會很怪異。
- URDF 可以描述閉鏈(closed-chain)機構嗎,例如平行四邊形連桿?
- 不行,URDF 的 joint 結構本質上是一棵樹(tree),每個 link 只能有一個父 joint,不允許形成環狀連接。真正的閉鏈機構在 URDF 裡通常只能近似成開鏈結構,或改用其他描述格式(例如 SDF)處理。
- 一個 URDF 檔案可以描述多台機器人嗎?
- 一份 URDF 只描述一個 <robot> 元素,代表一台機器人(或一個獨立的可動結構)。如果場景裡有多台機器人,通常是分別載入多份 URDF,並各自給予不同的命名空間或 TF 前綴來避免座標系名稱衝突,這在後面 TF2 那一節會提到。