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

調度

robot-notes /調度/路網與交管(先讀這兩篇)/路網模型與交通管制

路網模型與交通管制:三條技術路線的第一性原理比較

多台有體積的車共用一個場域,要同時保證「不相撞」和「不卡死」。這件事沒有唯一解,業界實際跑在線上的做法分成幾條差異很大的路線。這篇把問題拆到不能再拆,再看每條路線各自在哪一層做了什麼取捨。

延伸閱讀:OpenRMF:跨車隊調度VDA5050 協定Fleet 深入:API/圖資/座標/避塞車目的點重複預定路徑規劃與軌跡(Nav2)。 選型結論(叉車/搬運車/送貨機器人分場景)在 室內 AMR 路網規劃選型


1. 根本問題:不是「怎麼走」,是「誰在什麼時候可以佔哪塊地」

單車導航解的是「從 A 到 B 怎麼走」。多車場域真正的難題是另一個:同一塊地,同一段時間,只能有一台車

把它拆開,任何多車系統都必須各自回答三個子問題,少答一個就會在現場出事:

子問題 沒答好會怎樣
空間怎麼表示 — 「可以走的地方」是一個什麼樣的集合 表示太細 → 算不動;太粗 → 明明過得去卻判定過不去
衝突怎麼定義與偵測 — 兩台車的計畫在什麼條件下算「打架」 判太鬆 → 擦撞;判太嚴 → 全場互相等待,吞吐崩掉
誰先走、卡死怎麼解 — 衝突發生後的仲裁與脫困 沒有脫困機制 → 死鎖只能靠人去現場推車

三條主流路線的差別,幾乎全部可以歸結成「它們在這三題各自選了什麼」。


2. 第一題:空間表示 — 為什麼室內物流最後都收斂到拓樸路網

2.1 三種表示法

自由空間、拓樸路網、混合三種空間表示法的對照

自由空間(free space):地圖是一張佔據柵格(occupancy grid)——把空間切成固定大小的方格,每格記「這裡有沒有障礙物」,車可以走任何沒被佔據的格子。ROS 2 的導航堆疊 Nav2 用的 costmap(在佔據柵格之上再給每格一個「走這裡有多不划算」的代價值)就是這個模型。

拓樸路網(topological graph):人為在地圖上畫出節點(node)與有向邊(edge),車只准沿著邊走。節點帶座標與朝向,邊帶方向性與成本。

混合:骨幹用路網,局部(進出儲位、閃避臨時障礙)放開一小段自由空間。

2.2 為什麼不是「越自由越好」

直覺上自由空間更聰明 —— 它能走出路網想不到的捷徑。但把多車因素加進來之後,這個直覺會反過來。原因有四層,一層比一層根本:

第一層:搜尋成本。 自由空間的搜尋空間是格子數。一個 100m × 60m 的倉庫,5cm 解析度就是 240 萬格;要考慮朝向再乘上角度離散數。拓樸路網把同一個場域壓縮到幾百到幾千個節點,差兩到四個數量級。這裡的方向容易讀反,所以講清楚:被除的格子數(240 萬)是固定的,變的是路網節點數——節點越少,壓縮倍率就越大。幾百個節點時 240 萬 ÷ 300 ≈ 8000 倍(近四個數量級);上看幾千個節點時 240 萬 ÷ 3000 = 800 倍(近三個)。單車規劃一次還算得動,但交管要每 100ms 對每台車重算一次,這個差距就是「跑得動」與「跑不動」的分界。

第二層:可預測性。 這一層才是關鍵。交管要判斷兩台車會不會打架,前提是知道對方接下來會走哪裡。自由空間裡,同一個起終點在障礙物稍微變動時可能規劃出完全不同的路徑;路網上,同一段路每次都是那一段。可預測性讓「衝突偵測」從機率問題變回幾何問題。

第三層:可驗收與可維護。 現場管理者要能回答「這條走道是不是單行道」「這個路口一次能過幾台」。這些是規則,不是代價函數。路網把規則變成可以指著地圖看的東西;自由空間只能靠調 costmap 參數間接逼近,出事時無法追溯是哪個參數造成的。

第四層:與現場語言對齊。 倉儲現場本來就用「走道」「路口」「儲位」「暫存區」在講話。路網的節點與邊直接對應這些名詞,圖資才可能被非軟體的人維護。

2.3 代價要誠實講

路網不是免費的:

2.4 路網上要放哪些欄位,是被交管需求逼出來的

一個只服務單車導航的路網,節點只需要 (x, y)。實際跑多車的路網會長出一堆欄位,每一個都對應一個現場踩過的坑:

欄位 它在擋什麼
節點朝向 arc/theta 車到了節點還得「朝對的方向」,否則進儲位要先原地轉,那段時間佔著路口
邊的方向型別(直行前進/倒退/轉彎/偏移/自轉) 倒車與自轉的耗時和掃掠面積跟直行完全不同,交管必須分開算
邊的成本 weight 依動作分別給值 倒車成本 1.5、轉彎 2.0、直行 1.0 —— 讓規劃器主動避開昂貴動作,而不是事後才發現走不動
isBlock / is_closed 現場臨時封路,不重畫圖也要能立刻生效
isWaiting / traffic_zone 標出「這個節點是等待點」「這個節點屬於哪個交管區」,把交管規則掛在拓樸上
儲位的進入點觀察點 進出儲位是一段有嚴格姿態要求的動作,不能當成普通邊
節點 fixed(不可被合併) 自動化工具會合併冗餘節點,但有些節點的存在理由是交管而非幾何

最後一列是柵欄原則的典型案例:一個看起來多餘、可以被自動合併掉的節點,存在理由可能不在幾何裡,而在「它是某個等待點」。判斷路網哪裡可以簡化之前,要先知道每個節點當初為什麼被放進去。

2.5 兩層路網:為什麼還要一張「道路級」的粗圖

大型場域會再疊一層更粗的圖:交叉口(junction)+ 道路(road),道路用多邊形描述涵蓋範圍。

它解的是一個很具體的問題:估距離。交管要頻繁問「從這裡到那個候選點大概多遠」(選迴避點時對每個候選點都要問一次)。用歐氏或曼哈頓距離估會嚴重失真 —— 場域是一條一條的走道,直線距離跟實際行走距離可能差兩三倍。用完整路網做 Dijkstra 又太貴。

道路級圖是中間解:節點數從幾千降到幾十,在上面跑一次 Dijkstra 幾乎免費,精度卻比直線距離好得多。代價是多一份要維護的圖資。

左為細路網:密集節點沿走道排列,A 到 B 的直線距離穿牆而過造成低估,完整 Dijkstra 準確但每個候選點都要跑一次;右為道路級粗圖:九個交叉口加道路多邊形,跑一次 Dijkstra 幾乎免費

兩張圖描述的是同一個場域,差別只在解析度。左邊那條灰虛線是問題所在:直線距離不知道貨架擋在中間,估出來的值跟車實際要走的路差很多。右邊把同一個佈局粗化成幾個交叉口與幾段道路後,估距離便宜到可以對每個候選迴避點都問一次。

另一條路是預先算好全點對最短路(啟動時對每個節點跑一次 Dijkstra,存成 dist_cache),查詢降到 O(1)。這條路的邊界很清楚:節點數 V 的記憶體是 O(V²),預計算是 O(V² log V)。幾百個節點沒問題,上萬個節點就會在啟動時卡住、在記憶體上爆掉。路網規模決定了該用哪種估距法,不是偏好問題。


3. 第二題:衝突偵測 — 純空間 vs 時空

3.1 兩種座標系,取捨在「時間預測可不可信」

滾動視窗空間佔用與時空軌跡協商兩種衝突偵測方式的對照

純空間佔用:只問「這塊地現在誰佔著」。時間不進模型,而是靠只預約前方一小段把時間隱含進去 —— 車走到哪,預約窗就往前推到哪。

被預約的那一塊空間,實作上通常叫 token(佔用權),一次只發給一台車。它分兩種來源:動態 token 由「兩台車的路徑重疊區」自動產生,重疊在哪就在哪生一塊;固定 token 則是人工在地圖上劃定的會車區,帶一個入場名額(例如「這條窄道同時只准一台」),名額滿了後到的車就得在區外等。動態的處理幾何衝突,固定的處理「現場管理者知道、但演算法看不出來」的規則。

時空佔用:每台車發布一條帶時間戳的軌跡(itinerary),衝突定義成「兩條軌跡在時空中相交」。A 車 10:00:03 經過路口、B 車 10:00:09 經過,不算衝突。

時空模型明顯更精確,也更能榨出吞吐 —— 但它有一個硬前提:ETA 要可信。而 ETA 的誤差來源在室內場域全部都在:人擋路、地面打滑、貨物重量改變加速度、局部避障臨時減速、電梯等待。當 ETA 誤差達到秒級以上,時空排程算出的「錯開 6 秒」就變成一張支票,兌現不了就得重新協商;協商失敗只好保守等待,精度優勢被吃掉。

這是兩派的分水嶺:時間預測誤差越小,時空模型的優勢越大;誤差越大,純空間的滾動視窗越穩健

3.2 滾動視窗要開多長:前導線的推導

純空間路線的核心參數只有一個 —— 往前預約多遠。太短會來不及煞停,太長會把整條走道鎖死。這個值不該用試的,可以直接推出來。

前導線由制動距離、通訊延遲位移、車身長度與安全餘裕四段組成

從「車在什麼時候真的停下來」逆推,一台車從決定停到完全靜止,會多走這幾段:

① 車身長度 L_head 路徑上的點描述的是車的參考點——ROS 慣例裡叫 base_link,一個剛性固定在車身上、跟著車一起走的座標系,差速車通常設在兩驅動輪的軸心中點(詳見座標轉換與 TF)。但真正會撞到東西的是車頭,所以要補上參考點到車頭的這一段。載貨的叉車還要再加上貨叉與貨物的外伸。

② 通訊與處理延遲造成的位移。 交管拿到的位置是上一次回報的,決策送到車上還要一段時間。設單程延遲為 τ——車回報位置一趟、指令下發一趟,一來一回是兩個 τ;再加上交管自己算一輪的時間 t_p。這整段期間車都還在以 v 前進:

d_delay = v · (2τ + t_p)

③ 制動距離。 車以速度 v 行駛,要在減速度 a 下停住。制動力是 F = m·a(牛頓第二定律),它在距離 d 上做的功是 W = F·d = m·a·d(功 = 力 × 距離)。這份功要剛好把車的動能 ½mv² 吃光:

½mv² = m·a·d_brake   →   d_brake = v² / (2a)

質量 m 兩邊消掉了:在同一個減速度下,制動距離與車多重無關。車重的影響藏在另一個地方——「這台車實際做得到多大的 a」。

④ 安全餘裕 b 定位誤差、路網與現場的偏差、模型誤差的總和。

四段相加(順序即上圖由車往前的排列):

D_lead = L_head + v·(2τ + t_p) + v²/(2a) + b

實作上常看到寫死成 brake_dist = 0.5 * v²。這不是別的公式,就是 a = 1 m/s² 代進去的結果。把它當常數抄走的風險是:換一台減速度只有 0.5 m/s² 的重載叉車,制動距離會差一倍,而公式看起來完全沒問題。這類「常數裡藏著一個未寫出來的物理假設」是交管參數最容易出事的地方。

這一項還有一個實務推論:前導線隨速度平方成長。速度加倍,預約長度不是兩倍而是接近四倍。這解釋了一個反直覺的現場現象 —— 把車速調高之後,吞吐反而下降,因為每台車鎖住的路變長了。

3.3 排隊距離要跟避讓距離分開

同向跟車與對向會車是兩件事。跟車時前車不會突然反向衝過來,只需要留「前車急停我也停得住」;會車時要留出雙方都能反應的空間。所以實作上會有兩個窗:avoid_distlineup_dist,後者短得多。用同一個值會讓排隊變成一列稀疏的車陣,把走道容量浪費掉。

3.4 為什麼要把車體柵格化成像素圖

看起來很粗暴的一步:把每台車的輪廓沿路徑「蓋章」畫進一張二值影像,再用影像的位元運算判斷重疊。為什麼不用多邊形求交這種「乾淨」的幾何解法?

代價是解析度誤差(5cm 格子帶來 ±2.5cm 的判定誤差)與記憶體。對室內物流這個誤差遠小於定位誤差,不是瓶頸。

3.5 延遲補償:判斷要基於「車現在應該在哪」

交管手上的位置永遠是過期的。直接拿它做碰撞判斷,等於用 200ms 前的世界做決策,而車在這段時間已經走了 20–30cm。

補償的做法是外推:

t_est = t_odom + 2τ + t_p
p_est = p_odom + v · (t_est − t_odom)

用速度向量把位置推到「決策生效的那一刻」。這一步的重要性容易被低估 —— 沒有它,交管會在通訊抖動時產生大量假衝突,現場表現為「車莫名其妙一直停一下又走」。


4. 第三題:仲裁與脫困

4.1 仲裁順序不能只看距離

多台車搶同一塊地時,先來後到的判準看起來很自然,實際上會產生震盪。穩定的仲裁順序通常是這樣分層的:

  1. 上一輪已經持有的車繼續持有。這一條是防震盪的關鍵 —— 沒有它,兩台距離相近的車會在每個週期互相搶走對方的路權,結果是兩台都走不動。
  2. 距離最近者優先。已經接近的車讓它先過,總等待時間較小。
  3. 路線下發較早者優先。剛換路線的車有一段「穩定期」,先讓舊路線持有者通過,避免換路瞬間的搶佔亂序。

第一條是設計上最違反直覺、也最容易在重構時被拿掉的一條。它的存在理由是遲滯(hysteresis):任何基於瞬時量測的仲裁,都需要一點黏性來對抗量測噪聲。

4.2 兩種卡死,要分開偵測

兩車對頂死鎖與多車環形鎖的偵測方式對照

對頂(pairwise deadlock):兩台車在同一段路上面對面。判定的關鍵不是「兩台車都在衝突區裡」,而是「雙方都必須進入對方要用的空間才能通過」。做法是用 A 的車身輪廓沿它的行進方向投影,一路蓋到碰到 B 為止;若在碰到 B 之前輪廓從未壓到衝突區,代表 A 其實擦得過去,不是死鎖。兩邊都成立才判死鎖。這一步避免了把「窄但過得去」誤判成死鎖。

環形鎖(chain lock):A 等 B、B 等 C、C 等 A。這是作業系統資源分配圖上找環的同一個問題 —— 記錄「我是因為誰停下來的」形成等待圖(wait-for graph),在上面做 DFS,遇到已訪問過的節點就是一個環。

兩者要分開,因為解法不同:對頂只要挑一台讓路;環形鎖要挑斷環代價最小的那一台。

這裡跟 目的點重複預定 那篇是互補的兩面:那篇談的是預防(banker’s algorithm 這類「先確認安全序列再放行」),這裡談的是偵測後脫困。工程上兩者常常並存 —— 預防用在資源粒度粗、可以事先算的地方(儲位、電梯);偵測用在粒度細、狀態變化快的地方(走道空間)。

4.3 脫困:選一個迴避點,難在「不要製造新的死鎖」

挑一台車讓路之後,真正的難題是「讓到哪去」。一個看似合理的迴避點很容易變成新的阻塞源。實務上的做法是先用禁止層把不能去的地方刷掉,再對剩下的候選點評分:

挑迴避點分兩步:先用禁止層(明言禁止區、他車路徑頭尾、已佔用點的緩衝帶)刷掉候選,再對活下來的用距離成本加動作成本評分,取最低分

禁止層 為什麼
明言禁止區 設定檔硬性排除(消防通道、人員作業區)
其他車路徑的頭尾各數公尺 別停在人家馬上要走、或快要抵達的地方
已被佔用的迴避點的車身輪廓 那裡已經有車
已佔用迴避點周圍的緩衝帶 不讓兩台車擠在同一個角落,否則第二台又卡住第一台

評分則是兩項相加:

最後對候選點由好到壞逐一做精確驗證,通過就採用;通不過的按嚴重程度降級當備選,由輕到重是:

| 瑕疵 | 意思 | 為什麼比下一級輕 | |—|—|—| | 路線略長 | 讓得開,只是繞了點路 | 純效率損失,沒有影響到別人 | | 壓到他人終點 | 避讓後停的位置,正好是另一台車待會兒要停的地方 | 影響的是「未來」的某一台車,而且它還沒到,還有時間再挪一次 | | 與第三方衝突 | 避讓路徑會擋到不相干的第三台車 | 已經在影響「現在」的車,但那台車跟本次衝突無關,可另行協調 | | 與對手衝突 | 避讓路徑跟正在對峙的那台車再次撞上 | 沒解決問題,只是把死結換個位置 | | 需穿越受限區 | 得開進禁行/管制區域才避得開 | 可能違反安全規則,代價最高 | 寧可用一個有瑕疵的解,也不要回傳「找不到」,因為找不到就等於現場真的要人去推車。

4.4 空閒車也要管

無任務的車停在路口,一樣是障礙物。偵測「IDLE 超過門檻且擋住他人路徑」並主動送去迴避點,是很多系統上線後才補上的一塊。它提醒一件事:交管的對象是「所有佔空間的東西」,不是「所有正在執行任務的車」


5. 三條路線的對照

表中的 MAPF(Multi-Agent Path Finding,多智慧體路徑搜尋)指「把全場所有車當成一個問題,一次算出彼此不衝突的完整路徑」的集中式解法,與前兩條「邊走邊決定」的路線是不同的思路。

  A. 拓樸路網 + 滾動視窗集中交管 B. 時空軌跡協商 C. 集中式 MAPF
空間表示 拓樸路網 + 柵格化車體 各車隊自有路網,協調層看時空佔用 柵格或路網
衝突定義 空間佔用重疊 時空軌跡相交 時空(格子, 時步)衝突
決策粒度 每 100ms 全場重算「走/停」 事件驅動:發布行程 → 偵測衝突 → 協商 任務下達時算一次全體最優
對 ETA 誤差 不敏感(不預測時間) 敏感(誤差大會頻繁重協商) 很敏感(偏差即失效)
最優性 局部貪心,非全域最優 中等 理論最優
異質車隊 需自行統一介面 原生支援(協定化) 需統一模型
車數擴展 數十台實務可行 設計上為跨車隊 隨車數與時域指數惡化
死鎖處理 偵測 + 迴避點脫困 協商階段規避 + 路權租約 規劃階段保證無衝突
主要失效模式 迴避點不足 → 連鎖等待 某車隊不誠實回報 → 協調層失準 執行偏差 → 全盤重算
落地成熟度 高(大量現場在跑) 中(開源框架成熟,跨廠牌橋接仍在建設) 低(多見於研究與高度受控場域)

還有一條去中心化互相避讓(ORCA/RVO 這類速度障礙法)。它在開闊空間效果很好,但在室內物流的窄走道會系統性失敗:兩台車在只容一台的走道對頂時,不存在任何「都稍微偏一點」的速度解。缺乏全域資訊的局部避讓無法處理需要「有人退回去」的情況。這不是調參問題,是資訊不足的結構性限制。


6. 路網是交管規則的掛載點,不是單純的路徑資料結構

把路網當成「A* 要吃的圖」,是單車視角的看法。多車視角下,路網其實是交管規則的掛載點:

這解釋了為什麼「換一個更聰明的規劃器」通常無法解決現場的塞車問題 —— 塞車的成因多半不在規劃層,而在路網把交管規則表達得不夠,或圖資與現場已經漂移。規劃器的上限由路網的表達力決定。


7. 誠實的邊界

8. 延伸閱讀