robot-notes — 機器人知識筆記 GitHub ↗
本章與本頁目次

核心

robot-notes /核心/導航/座標轉換與 TF

座標轉換與 TF

機器人身上每個感測器裝在不同位置、朝不同方向;地圖、里程、車體又各有原點。一筆「LiDAR 看到前方 2m 有牆」要變成「這牆在地圖的哪裡」,中間隔著好幾次座標換算。這篇從這個根本問題出發,講 ROS 的座標慣例、2D 剛體變換的數學(為什麼用齊次矩陣),以及 tf2 怎麼用一棵樹管理所有座標系。

差速車本身的運動學(輪速 ↔ v/ω)見 底盤 §1.1下位機運動控制;本篇專講「座標系之間怎麼換算」。 延伸閱讀:定位SLAM路徑規劃


1. 根本問題:每個量測都「只對某個座標系」有意義

LiDAR 回報「正前方 2m 有牆」,這個「2m、正前方」是相對 LiDAR 自己的。但 LiDAR 鎖在車身某個位置、相機在另一個位置、IMU 又在別處;而「我在地圖哪裡」是相對 地圖的。要回答「這牆在地圖哪裡、要不要閃」,就得把這筆資料從感測器座標系,一路換算到車體、再到地圖。

座標轉換就是這個換算;TF(transform) 是 ROS 用來集中管理「所有座標系彼此關係、且隨時間更新」的系統,tf2 是其第二代實作。REP-103 開宗明義:單位與慣例不一致是整合 bug 的常見來源——統一慣例 + 集中管理變換,就是為了消除這類風險。

2. ROS 座標慣例(REP-103)

要大家接得起來,先約定座標軸與單位:

roll / pitch / yaw 就是繞這三根軸的轉動,名字的排列順序跟 x / y / z 一一對應——軸向定了,這三個名字不必另外背。(要合成一個姿態時,ROS 用的是固定軸 X→Y→Z 的順序,等價於內旋 ZYX。)

roll 繞 x 前後軸為左右側傾、正向是左側抬起;pitch 繞 y 左右軸為前後俯仰、正向是車頭往下;yaw 繞 z 垂直軸為水平轉向、正向是向左轉

名稱 繞哪根軸 直覺 對輪式車 對足式
roll(側傾) x(前後軸) 車身左右歪 幾乎恆為 0(橫坡、單邊過門檻、有懸吊的車轉彎側傾才變) 會變,要估
pitch(俯仰) y(左右軸) 車頭上下點 幾乎恆為 0(上下坡、急加減速才變) 會變,要估
yaw(偏航) z(垂直軸) 原地轉向 唯一會變的角度 會變,而且光靠本體感測在數學上不可觀測

方向的正負最容易踩坑:在這個右手系裡,正的 roll 是左側抬起,正的 pitch 是車頭往下(不是往上),正的 yaw 是向左轉。pitch 那個特別反直覺——「抬頭」是負的。這一節開頭講的慣例衝突,實務上有一大半就是在這種地方翻車。

最右邊那兩欄是輪式與足式在狀態估計上的分水嶺:在室內平整地面上,輪式車只需要 (x, y, yaw) 三個量,roll 與 pitch 當常數處理不會出大錯——「yaw 是唯一會變的角度」是這個假設的結果,不是量出來的事實。地面一不平就會咬人:2D 光達在車身傾斜時掃描面會打到地板或天花板。足式的軀幹六個自由度(3 個位置 + 3 個姿態)全都在動,一個都不能省。

3. REP-105:frame 鏈,以及為什麼分 map 與 odom 兩層

REP-105 規範移動平台的座標鏈:map → odom → base_link(再往下接各感測器)。

為什麼要分這兩層?(第一性原理)因為導航同時要兩種互斥的特性:

把兩種需求拆成兩層,就能各取所需:近程平滑用 odom、全域準確用 map。

TF tree:map→odom→base_link→感測器;map 跳變但準、odom 連續但漂

一個關鍵權責:定位元件不直接發 map→base_link,而是先收 odom→base_link(里程發的),再反推、廣播 map→odom。這樣才能維持「每個 frame 只有一個 parent」(base_link 的 parent 只能是 odom)。

4. 座標轉換數學:旋轉 + 平移,合成一個矩陣

兩個座標系之間的剛體變換 = 旋轉 + 平移。一個點在 B 系的座標 p_B,換算到 A 系:

p_A = R(θ) · p_B + t          R(θ) = | cos θ  −sin θ |   (逆時針為正)
                                      | sin θ   cos θ |

這裡 θ = B 系 x 軸相對 A 系 x 軸的夾角(B 在 A 中的朝向),t 是 B 原點在 A 中的位置——θ 與 t 合起來就是「B 在 A 中的位姿」。

R(θ) 不必背,兩行就能自己寫出來。 B 系的 x 軸,在 A 系裡看是 (cos θ, sin θ);B 系的 y 軸,在 A 系裡看是 (−sin θ, cos θ)(x 軸逆時針轉 90°)。把這兩個向量當成矩陣的兩個直行,就是 R(θ) 這也解釋了它在做什麼:p_B = (2, 0) 代表「沿 B 的 x 軸走 2 格」,乘上 R 之後就變成「沿 A 系裡那個方向走 2 格」。

記住這個來歷,就不會在該寫 R(θ) 還是 R(−θ) 時猜——問自己「我要把誰的軸換算成誰的」,而不是試兩次看哪個結果比較合理。齊次變換(homogeneous transformation) 把「先轉再移」合併成單一矩陣乘法:點補一維成 (x, y, 1),變換寫成 3×3 矩陣 T

2D 座標轉換:p_A = R(θ)p_B + t,齊次矩陣 T 合成旋轉與平移

為什麼用齊次座標?(第一性原理,三個都很實用)

  1. 統一:旋轉本是乘法、平移本是加法;補一維後平移也變乘法,兩者用同一種運算。
  2. 可串接:多段變換直接連乘——T_A←C = T_A←B · T_B←C。整條 frame 鏈(感測器→車體→里程→地圖)就是一串矩陣相乘。
  3. 可逆:反方向換算只要取逆矩陣 T_B←A = (T_A←B)⁻¹

tf2 在做的正是這件事:把樹上各段變換連乘(必要時取逆),算出任兩 frame 之間的總變換。

4.1 算一次:LiDAR 說前方 2 m 有牆,那面牆在地圖的哪裡

開頭那個問題,現在可以直接算完。

已知:

第一步,量測值先變成座標。 LiDAR 的「正前方 2 m」在 LiDAR 自己的座標系裡就是 p_lidar = (2.0, 0)

第二步,換到車體。 只有平移,沒有旋轉:

p_base = (2.0, 0) + (0.3, 0) = (2.3, 0)

第三步,換到 map。 車的朝向是 90°,代進去:

          | cos 90°  −sin 90° |     | 0  −1 |
R(90°) =  |                   |  =  |       |
          | sin 90°   cos 90° |     | 1   0 |

p_map = R(90°) · (2.3, 0) + (4.0, 1.0)
      = (0, 2.3)          + (4.0, 1.0)
      = (4.0, 3.3)

R(90°)(2.3, 0) 轉成 (0, 2.3)——原本是「沿車頭方向 2.3 m」,在 map 上就是「沿 +y 方向 2.3 m」,因為車頭正朝著 +y。

那面牆在地圖的 (4.0, 3.3)

三步各對應一段 frame:lidar → base_link → map。實際系統裡這串是 tf2 幫你連乘的,你只要問它「這個點在 map 裡是多少」。但知道它在算什麼,除錯時差很多——位置整個歪掉通常是某一段的 θ 弄反了,而不是感測器壞了。

5. tf2:用一棵「樹」管理所有座標系

6. 與筆記其他部分的連結

7. 來源