Nav2 代價地圖與路徑規劃
- ros2
- nav2
- costmap
- path-planning
問題定義
上一節建好的地圖,本質上只是一張標示「這裡是牆、這裡是空地」的靜態影像,路徑規劃演算法沒辦法直接拿它做決策——演算法需要知道的是「每個位置的可通行代價有多高」,包括要離牆面保持多遠、附近突然出現的障礙物有多危險。代價地圖(costmap)就是把原始地圖與即時感測資料,轉換成規劃演算法可以直接運算的代價數值網格。
核心概念說明
Global Costmap 與 Local Costmap 的分工
延續上一節提到的兩層時間尺度概念,Nav2 用兩張獨立的代價地圖分別服務不同目的:
- Global Costmap:涵蓋整張已知地圖範圍,主要基於 SLAM 建好的靜態地圖產生,更新頻率較低,供
planner_server規劃一條從起點到終點的全域路徑。 - Local Costmap:只涵蓋機器人周圍一個固定大小的視窗(例如 3 公尺見方),完全基於即時感測資料(例如當下的雷射掃描),更新頻率很高,供
controller_server即時避開規劃當下才出現、地圖上原本沒有的障礙物。
Layer:代價地圖是多層疊加出來的結果
無論是 global 還是 local costmap,實際數值都是由多個 layer 插件依序疊加計算出來的,這是一種類似圖層合成的設計,讓每種「代價來源」可以獨立開關、調整,互不影響彼此的邏輯:
| Layer | 作用 |
|---|---|
static_layer | 讀取 SLAM 建好的靜態地圖,牆面標記為高代價 |
obstacle_layer | 讀取即時感測資料(雷射掃描),動態標記當下偵測到的障礙物 |
inflation_layer | 把每個障礙物周圍的代價往外「膨脹」,離障礙物越近代價越高,形成漸層 |
inflation_layer 特別值得展開說明:它解決的問題是「機器人不是一個點」——單純知道障礙物的確切輪廓不夠,規劃器還需要知道機器人本體的實際尺寸,離障礙物太近的路徑即使技術上沒有真的壓到障礙物,也應該被視為高風險而避免。膨脹半徑通常會設定成略大於機器人本體半徑,確保規劃出的路徑留有安全餘裕。
實作範例:設定代價地圖並觀察規劃結果
1. 代價地圖參數設定
檔案位置:~/ros2_ws/src/my_package/config/nav2_params.yaml
global_costmap:
global_costmap:
ros__parameters:
update_frequency: 1.0
publish_frequency: 1.0
global_frame: map
robot_base_frame: base_link
resolution: 0.05
plugins: ["static_layer", "obstacle_layer", "inflation_layer"]
static_layer:
plugin: "nav2_costmap_2d::StaticLayer"
map_topic: /map
obstacle_layer:
plugin: "nav2_costmap_2d::ObstacleLayer"
observation_sources: scan
scan:
topic: /scan
max_obstacle_height: 2.0
inflation_layer:
plugin: "nav2_costmap_2d::InflationLayer"
inflation_radius: 0.3
cost_scaling_factor: 3.0
local_costmap:
local_costmap:
ros__parameters:
update_frequency: 5.0
publish_frequency: 2.0
global_frame: odom
robot_base_frame: base_link
rolling_window: true
width: 3
height: 3
resolution: 0.05
plugins: ["obstacle_layer", "inflation_layer"]
obstacle_layer:
plugin: "nav2_costmap_2d::ObstacleLayer"
observation_sources: scan
scan:
topic: /scan
max_obstacle_height: 2.0
inflation_layer:
plugin: "nav2_costmap_2d::InflationLayer"
inflation_radius: 0.3
cost_scaling_factor: 3.0
留意兩個地方跟第 6.1 節提到的座標系概念直接對應:global_costmap 的 global_frame 設成 map(全域、精準),local_costmap 的 global_frame 設成 odom(局部、平滑不跳動),這不是隨意選擇,而是刻意讓即時避障運算建立在不會突然跳動的座標系上,避免 local costmap 因為定位修正的瞬間跳動而產生錯誤的避障行為。
2. 啟動 costmap 節點並載入參數
ros2 run nav2_costmap_2d nav2_costmap_2d --ros-args \
--params-file ~/ros2_ws/src/my_package/config/nav2_params.yaml \
-r __ns:=/global_costmap
3. 用 RViz2 觀察代價地圖
在 RViz2 加入 Map Display,Topic 選 /global_costmap/costmap。
預期畫面
牆面附近會出現從深色(高代價,緊鄰障礙物)到淺色(低代價,遠離障礙物)的漸層色帶,這條漸層帶的寬度大約對應 inflation_radius 設定的 0.3 公尺——這是驗證 inflation_layer 有正確運作最直觀的方式。
4. 發送一個規劃請求,觀察路徑繞行障礙物膨脹區域
ros2 action send_goal /compute_path_to_pose nav2_msgs/action/ComputePathToPose \
"{goal: {pose: {position: {x: 2.0, y: 1.0, z: 0.0}}}}"
預期輸出
Waiting for an action server to become available...
Sending goal:
goal:
pose:
header:
frame_id: ''
pose:
position:
x: 2.0
y: 1.0
z: 0.0
...
Result:
path:
poses: [...]
在 RViz2 裡加入 Path Display 觀察這條路徑,會看到路徑刻意繞開了膨脹層的深色高代價區域,即使技術上直線穿過那個區域也不會真的撞到障礙物本體。
常見錯誤與除錯技巧
錯誤一:規劃請求回報找不到可行路徑
Result:
error_code: 202
error_msg: 'No valid path found'
原因:常見原因有兩種:目標點本身落在高代價(甚至無法通行)區域內,或整張代價地圖上,起點跟目標點之間被連續的高代價區域完全阻隔(例如 inflation_radius 設太大,把一條原本能通過的窄通道也判定為不可通行)。
排除方式:先用 RViz2 目視確認目標點座標是否真的落在合理的可通行區域,再檢查是否有場景整體被過度膨脹的問題——如果窄通道確實存在但規劃不出路徑,嘗試降低 inflation_radius 或 cost_scaling_factor 做測試,確認是不是膨脹設定過度保守導致的。
錯誤二:Local costmap 裡出現「幽靈障礙物」,明明現場已經沒有東西了
現象:實際場景中的障礙物已經移開,但 local costmap 上原本的位置依然標記著高代價區域,遲遲沒有清除。
原因:obstacle_layer 需要靠新的感測資料主動「觀察到那個位置現在是空的」才會清除舊的標記;如果感測器視角有死角(例如障礙物移到了雷射掃描不到的角度),代價地圖沒有機會被更新,會一直保留舊資料。
排除方式:確認感測器的視野範圍涵蓋需要清除代價的區域,必要時調整感測器安裝角度或加裝額外感測器覆蓋死角;也可以用 ros2 topic echo /local_costmap/costmap_updates 確認代價地圖確實有在持續接收更新,而不是完全沒有在運作。
小結
代價地圖是連接「靜態地圖/即時感測資料」與「路徑規劃演算法」之間的橋樑,global/local 兩張地圖分別服務全域規劃與即時避障,多個 layer 插件疊加計算出最終代價,inflation_layer 確保規劃結果考慮到機器人本體實際尺寸的安全餘裕。有了代價地圖,下一節要介紹 Nav2 怎麼用行為樹,把規劃、避障、失敗重試這些行為,組織成一套完整、可應付各種異常狀況的自主導航邏輯。
延伸閱讀
常見問題
- 為什麼需要兩張代價地圖,一張不夠嗎?
- global costmap 涵蓋整張地圖,更新頻率較低,負責全域路徑規劃;local costmap 只涵蓋機器人周圍一小塊範圍,但更新頻率很高,負責即時避開規劃當下才出現的障礙物(例如突然有人走過)。如果只用一張全域地圖做即時避障,範圍太大導致更新太慢,來不及反應新出現的障礙物;如果只用一張區域地圖做全域規劃,機器人看不到範圍外的整體地形,容易規劃出繞遠路甚至走不通的路徑。兩者分工,各自針對不同的時間尺度與空間範圍優化。
- inflation layer 的膨脹半徑要設多大?
- 至少要大於機器人本體的半徑加上一些安全餘裕,否則規劃出來的路徑理論上『沒有壓到障礙物代價』,但機器人本體的實際尺寸還是可能撞上障礙物邊緣。膨脹半徑設太大則會讓機器人在狹窄通道(例如門框)誤判為無法通過,实務上需要根據機器人實際尺寸與場景寬度做權衡調整。
- 規劃器規劃出的路徑,跟機器人實際走的路線會完全一樣嗎?
- 不會,也不應該期待完全一樣。全域路徑規劃器(planner_server)產出的是一條理論上可行的路徑,實際執行時交給 controller_server 即時根據 local costmap 與當下感測資料微調路線,尤其是遇到規劃當時不存在、之後才出現的障礙物時,會偏離原始全域路徑做局部避讓,這是正常且必要的行為,不是規劃器算錯。