National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 80 24°C PM2.5 21
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 07

launch 與 TF

一次啟動很多節點,用座標樹管理所有感測器的位置
Interaction 30 minute(s)

Learning Objectives

  • 說明 launch 檔的用途,讀懂 Python launch 檔的結構
  • 畫出 map、odom、base_link、laser 的 TF 樹,說明為什麼要分 map 和 odom
  • 用 lookup_transform 查兩個座標系的關係,處理查不到的情況
  • 用 StaticTransformBroadcaster 發布固定的轉換
  • 把光達的點換到 odom 座標系

十幾個節點,不可能開十幾個終端機

一台能導航的機器人,光達、里程計、定位、規劃、控制加起來可能有十幾個節點,每個還有自己的參數。一個一個 ros2 run 既麻煩又容易漏掉。

launch 檔用一個檔案描述「這個系統要啟動哪些節點、各帶什麼參數」,一個指令全部帶起來。可以用 XML、YAML 或 Python 寫,官方三種都支援。下面是 Python 的樣子:

from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
    return LaunchDescription([
        Node(package='my_robot', executable='limiter', name='limiter',
             parameters=[{'max_speed': 0.15}]),
        Node(package='my_robot', executable='planner', name='planner',
             remappings=[('/cmd_vel_raw', '/planner/cmd_vel')]),
    ])
ros2 launch my_robot robot_launch.py       # 一次啟動
ros2 run my_robot limiter                  # 只跑一個節點,除錯時常用
  • parameters 在啟動時帶入參數,取代第 5 課的 --ros-args -p。
  • remappings 把節點裡的 topic 名稱換成別的,不用改程式。這是節點能重複使用的關鍵。

這個 launch 檔瀏覽器裡不會執行,它要真實的 ROS 2 環境(第 20 課會說明)。下面的 robot-lab 用「同時跑兩支程式」加上「帶入參數」達到一樣的效果,你可以對照看。

互動練習:帶參數啟動兩個節點

這裡的設定等於上面 launch 檔的兩個節點:limiter 在啟動時帶入 max_speed: 0.15。程式裡的預設值是 0.3,看看實際用哪一個。

TF:把所有座標系接成一棵樹

第 2 課算過:光達的點要換到地圖,要把 T_map_base 和 T_base_laser 相乘。真實機器人有更多座標系,有的會動(車身),有的不動(感測器的安裝位置),每個節點都自己算太容易出錯。

TF(tf2 函式庫)把這件事集中處理:

  • 誰知道某兩個座標系的關係,誰就發布這個轉換(/tf 給會動的,/tf_static 給固定的)。
  • 誰需要,就查詢任意兩個座標系的關係,TF 沿著樹把中間的轉換乘起來。

每個座標系只有一個父座標系,所以整體是一棵樹。移動機器人的標準結構由 REP-105 定義:

map ──▶ odom ──▶ base_link ──▶ laser
座標系 意思 特性
base_link 固定在機器人身上 跟著機器人走
odom 里程計的世界 連續,數字不會跳;但誤差會一直累積,沒有上限
map 地圖的世界 長期不漂移;但定位修正時數字可能突然跳一下

為什麼要同時有 map 和 odom? 因為兩種需求互相衝突。控制機器人走直線要連續的數字:位置突然跳 10 公分,控制器會以為自己偏了,猛然轉向。要知道自己在建築物哪個位置,就要不漂移的數字。所以兩個都留,各給適合的人用:避障、短距離控制看 odom;全域規劃看 map。

REP-105 還規定了一個細節:每個座標系只能有一個父座標系,所以定位演算法不發布 map→base_link(base_link 的父座標系已經是 odom),而是算出 map→odom 來修正。

定位修正時,哪個數字會跳

用矩陣把這條鏈算一次,看定位修正時發生什麼:odom 裡的數字沒有變,所以依賴它的控制器不受影響;map 裡的位置被修正了。

在程式裡查詢與發布

查詢要一個 Buffer(存資料)和一個 TransformListener(把 /tf 收進來):

self.tf_buffer = Buffer()
self.tf_listener = TransformListener(self.tf_buffer, self)

t = self.tf_buffer.lookup_transform('odom', 'laser', Time())

lookup_transform(target, source, time) 回傳的轉換,可以把 source 座標系裡的點換到 target。Time() 表示「最新的一筆」。參數順序很容易記反,反了不會報錯,只會得到反方向的轉換。

查詢一定要處理失敗。程式剛啟動時可能還沒收到 TF,發布的節點也可能晚點才起來,所以官方範例都用 try 包起來,查不到就等下一輪:

錯誤訊息的關鍵字 通常代表 怎麼查
does not exist 這個座標系名稱沒有人發布過 名稱有沒有打錯;發布的節點有沒有啟動
not part of the same tree 兩個座標系都有,但沒有接在同一棵樹上 中間少一段;常見是 map→odom 沒有人發布
時間相關(外插) 你要的時間點超出資料範圍 感測器時間戳記與 TF 的時間差。模擬環境只留最新一筆,不會出現這個錯誤

真實環境可以用 ros2 run tf2_ros tf2_echo map base_link 印出轉換,用 ros2 run tf2_tools view_frames 把整棵樹畫成圖。

發布固定的轉換用 StaticTransformBroadcaster,它用第 4 課的 TRANSIENT_LOCAL,晚啟動的節點也拿得到;會動的用 TransformBroadcaster,要持續發布。兩者都是填一個 TransformStamped:header.frame_id 是父座標系、child_frame_id 是子座標系。

互動練習:修好 TF 樹

模擬世界發布 odom→base_link 和 base_link→laser。localizer.py 應該補上 map→odom,但它和 frame_reader.py 各有一個問題。

機器人會以 0.2 m/s 往前走,起點在地圖的 (1.0, 1.5)。下面的錯誤訊息是模擬環境的輸出,文字依 tf2 原始碼的寫法。

互動練習:牆不會動

機器人一邊往前走、一邊慢慢左轉。程式讀光達車頭偏右 20 度那一筆,算出它打到的點的位置,發布到 /wall_x。

牆是固定的,所以換到 odom 座標系之後,x 應該一直接近 4.5(牆在地圖 x=5.5,起點在 x=1)。現在的程式直接用光達座標系的數字,你會看到它一直變小。

第 2 課的旋轉公式:x' = cos θ × x − sin θ × y。兩項都要有:這一筆不在正前方,所以 y 不是 0。

確認一下

1.

依 REP-105,定位演算法想修正機器人在地圖上的位置,它應該發布哪個轉換?

2.

機器人要沿著走廊貼牆走 2 公尺(短距離控制)。該用哪個座標系的位置?

3.

lookup_transform 丟出「Could not find a connection … not part of the same tree」。最可能的原因是?

4.

感測器的安裝位置為什麼發布在 /tf_static,不用計時器每秒發布到 /tf?

5.

下面這段 launch 設定,哪個敘述正確?

Node(package='my_robot', executable='limiter', parameters=[{'max_speed': 0.15}])
第 1 次作答

重點整理

  • launch 檔描述要啟動哪些節點與參數,ros2 launch 一次帶起來;ros2 run 只跑一個。
  • TF 把所有座標系接成一棵樹:map → odom → base_link → laser(REP-105)。
  • odom 連續但會漂移,適合短距離控制;map 不漂移但會跳,適合全域定位。定位演算法發布的是 map→odom。
  • lookup_transform(target, source, Time()) 把 source 的點換到 target。一定要處理查不到的情況;參數順序反了不會報錯。
  • does not exist 查名稱,not part of the same tree 查少了哪一段。

ROS 2 的基本工具到這裡告一段落。下一課開始看機器人怎麼動:左右輪速度怎麼變成移動,里程計的誤差又是從哪裡來的。

(launch 檔的寫法改寫自 ROS 2 官方文件 Creating a launch file,CC BY 4.0,有修改。)

這一課完成了!
Next lesson:差速驅動與里程計
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。