Wiki
map_saver(二維柵格地圖儲存)
本文由簡體中文內容確定性轉換,並受版本化術語表保護。
1.map_saver(二維柵格地圖儲存)
1.1.實車先看:儲存哪個話題,檔案有沒有保留未知區域
map_saver接收一份OccupancyGrid,寫出圖片及配套地圖YAML。 當前slam_launch啟動map_saver_server可執行程式,其節點名為map_saver;配置這些引數或啟動服務不會自動把地圖儲存到磁碟,仍需發起儲存。
slam_toolbox → namespace/map(OccupancyGrid)
├─ SaveMap服务请求 → 建图launch中的map_saver
│ → 临时订阅、等待一份地图 → 写图片 + 地图YAML
│
└─ map_saver_cli → 新建一个独立MapSaver
→ 自己订阅、等待一份地图 → 写图片 + 地图YAML
保存的图片 + 地图YAML → map_server → 重新得到OccupancyGrid
map_saver_server、map_saver_cli和節點名map_saver需要區分。 兩個可執行程式使用同一個MapSaver類,但CLI自行儲存,不通過正在執行的伺服器傳送請求;因此當前launch中的5秒引數不會自動傳給另一條CLI命令。儲存器實現、CLI入口
| 實車上看到的現象 | 先核對什麼 | 當前配置怎樣理解 |
|---|---|---|
| 建圖結束卻沒出現PGM/YAML | 是否真正呼叫儲存服務或CLI、返回結果與目錄 | 啟動儲存器不是自動儲存,也不是退出時自動匯出二維圖 |
| YAML設了5 s,CLI卻很快等圖失敗 | 儲存的是哪個程序及CLI引數 | 未給CLI載入/設定引數時,它用原始碼預設2 s,不沿用launch儲存器的5 s |
| 儲存不到圖,一直等到超時 | map_topic、namespace、QoS及地圖釋出狀態 | 當前每次儲存才臨時訂閱一份地圖,True要求相容的Transient Local釋出端 |
| 改預設閾值,卻看不到這次輸出變化 | SaveMap請求或CLI是否明確指定閾值、map_mode | 請求非零閾值優先;三態與其他模式的編碼不同;地圖若只含0、100、-1,許多門檻變化不改變儲存階段分類 |
| 圖片是灰色,重新載入後卻成空地 | trinary寫出的灰205與地圖YAML free_thresh | 當前0.25/0.65組合確有讀寫後的未知身份丟失,已用安裝庫臨時地圖驗證 |
| 儲存二維圖後沒有PCD/posegraph | 儲存器的輸入與輸出型別 | 本節點只處理OccupancyGrid;PCD、slam_toolbox內部圖狀態需要各自儲存 |
本次安裝的nav2_map_server為1.1.20,map_saver/map_io安裝標頭檔案與該版本一致。當前reality引數與安裝副本一致;simulation段僅use_sim_time=True不同。
1.2.引數
保留髮送的5項原配置。具體儲存請求的輸入話題、輸出路徑、模式、格式和請求閾值另列,不能把這些請求欄位當成這裡已經寫好的節點引數。
map_saver:
ros__parameters:
use_sim_time: False
save_map_timeout: 5.0
free_thresh_default: 0.25
occupied_thresh_default: 0.65
map_subscribe_transient_local: True
1.2.1.ros__parameters(等待、預設閾值與地圖訂閱)
1.2.1.1.參數列
| 引數名 | 當前值 | 單位 | 實車處理建議 | 作用 | 調大效果 | 調小效果 | 生效條件/備註 |
|---|---|---|---|---|---|---|---|
use_sim_time |
False;模擬True |
不適用,布林 | 上實物前必須核對並正確配置 | 選擇節點ROS時鐘 | 不適用 | 不適用 | 此鍵在兩套配置都存在,slam_launch會重寫;不改變圖片座標/格距;儲存等圖超時不是按/clock走的週期任務 |
save_map_timeout |
5.0;原始碼預設2.0 |
s,一次訂閱等圖的等待期限 | 通常保持當前值,不作為優先除錯項 | 儲存時等待收到一份OccupancyGrid的上限 | 可容忍更慢到圖,也更晚報告等待失敗 | 缺圖時更快失敗,可能來不及取得正常地圖 | 不是每5 s儲存,不是完整寫盤操作的總期限;只作用於使用此引數的儲存器,CLI需單獨設定 |
free_thresh_default |
0.25 |
無量綱,佔用比例;當前對應25 | 上實物前必須核對並正確配置 | 請求free_thresh為0時的預設值;三態儲存把已知值≤25寫為空閒色 | 儲存階段更多已知格判空閒、中間灰色減少 | 儲存階段更少已知格判空閒、中間灰色增加 | 輸入-1仍先寫未知灰;但當前灰205在0.25下過載成空閒,需看下方驗證;請求非零值覆蓋此預設 |
occupied_thresh_default |
0.65 |
無量綱,佔用比例;當前對應65 | 實物執行時重點觀察,有問題再針對性調整 | 請求occupied_thresh為0時的預設值;三態儲存把已知值≥65寫成佔用色 | 儲存階段更少已知格判佔用,更多進入中間灰區 | 儲存階段更多已知格判佔用,中間灰區減少 | 必須大於有效free門檻、且≤1;非零請求覆蓋預設;灰區過載還受free門檻影響 |
map_subscribe_transient_local |
True |
不適用,QoS耐久性選擇 | 上實物前必須核對並正確配置 | 儲存請求期間用Transient Local、Reliable、KeepLast(1)訂閱地圖 | 不適用 | 不適用 | 便於從相容釋出端取得保留地圖;不是儲存檔案快取,也不是“節點重啟不保留地圖”的開關;False仍預設Reliable,改為Volatile、深度10 |
1.2.1.2.引數詳解
1.2.1.2.1.save_map_timeout(等待一份圖,不是整個儲存任務計時)
每次儲存建立一個臨時OccupancyGrid訂閱,將收到的第一份圖放進future,再用executor.spin_until_future_complete等待,當前超時5 s。成功收到圖後才呼叫saveMapToFile寫圖片和YAML,後面的檔案編碼/寫入不被這個5 s等待包住。訂閱與等待程式碼
因此增大它能容忍慢到圖,卻不能修復錯誤topic、namespace、QoS、不存在的釋出者或無效目錄;更不會提高建圖頻率。地圖已通過相容Transient Local釋出時,晚加入的儲存訂閱通常可直接取得最近一份,不必等下一次更新。
該等待使用executor的chrono超時,不能把它當成必須讓模擬/clock前進5 s才結束的定時儲存任務。沒有獲得完整圖,函式返回False;“服務呼叫成功到達”與“響應result=True、檔案完整可讀”仍須分開。
1.2.1.2.2.map_subscribe_transient_local(取得最後地圖的條件)
True時採用Transient Local、Reliable、KeepLast(1);False分支以預設QoS深度10訂閱,耐久性為Volatile、可靠性仍Reliable。True可從相容釋出端取已保留的完整圖;它不讓一個沒有Transient Local能力的釋出端憑空儲存歷史訊息。
map_server使用Transient Local釋出完整圖,slam_toolbox當前建圖也有自己的地圖釋出配置,應核對實際端點。若可靠性或耐久性不相容,延長save_map_timeout不保證收到訊息。儲存訂閱QoS
當前儲存器不在啟動後持續快取所有地圖版本;訂閱在每次儲存中建立,取得一份後移除。若釋出端保留的是舊圖,本次可能迅速儲存舊圖,等待時間短不證明地圖新鮮或建圖完整。
1.2.1.2.3.free_thresh_default與occupied_thresh_default(請求覆蓋與三態儲存邊界)
SaveMap請求的free_thresh或occupied_thresh為0時,儲存器分別使用節點default值;非零請求則使用請求值。CLI的--free、--occ寫入其獨立儲存器的本次SaveParameters,效果同樣是明確指定儲存閾值。0在這裡是“未指定”,不能當成要求精確使用零門檻。
三態儲存先將門檻乘100並用rint取整數,再分類。當前0.25/0.65得到25/65:
| 輸入OccupancyGrid的值 | 當前三態儲存階段 | 寫出的8位灰度 |
|---|---|---|
| -1,未知 | 先按未知處理,不參與已知值門檻比較 | 205 |
| 0~25,含25 | 空閒 | 254 |
| 26~64 | 中間/不確定 | 205 |
| 65~100,含65 | 佔用 | 0 |
這不是“雷達點高於25 cm”或“障礙強度大於0.65”,也不是inflation_layer的內部代價閾值。輸入應是slam_toolbox的二維佔用圖,不是terrain_map_ext點雲,也不要把具有膨脹顏色的costmap誤當原始場地圖儲存。
當前原始地圖若只含-1、0、100,保持合法門檻且不碰端點時,改變門檻常常不改變儲存階段的黑/白/灰分類;它仍會改變寫進地圖YAML的閾值,並可能改變重新讀取後的結果。三態儲存比較
有效非零門檻應滿足0<free<occupied≤1。引數0會經過預設替代;兩門檻相等或反序會使儲存失敗。trinary按門檻編碼圖片,scale主要寫連續灰度及未知透明通道,raw直接編碼佔用數;不能把三態分類效果套給所有模式。儲存引數檢查
1.2.1.2.4.已驗證的讀寫問題:trinary、灰205與free=0.25
儲存階段的未知/中間格寫成灰度205,並把free_thresh=0.25、occupied_thresh=0.65、negate=0寫入配套YAML。重新載入無透明通道三態圖時,MapIO使用畫素佔用比例1−灰度/255,嚴格小於free_thresh便設空閒0:
灰205的占用比例 = 1 − 205 / 255 ≈ 0.196078
0.196078 < 0.25
所以当前三态保存的灰205,在按配套YAML重新读取时变成0
本次沒有隻靠公式推斷:直接呼叫容器中已安裝的libmap_io,用臨時7×1小地圖儲存並重新讀取,得到下面結果。兩組occupied_thresh均為0.65、mode為trinary、圖片為PGM;儲存成功,載入狀態均為LOAD_MAP_SUCCESS。
| 輸入7個格子的值 | 儲存/載入使用的free門檻 | 重新讀取結果 |
|---|---|---|
| -1、0、25、26、64、65、100 | 0.25,當前配置 | 0、0、0、0、0、100、100 |
| -1、0、25、26、64、65、100 | 0.196,對照配置 | -1、0、-1、-1、-1、100、100 |
第一組不僅未知-1變空閒,中間26/64也經灰205變空閒;不能說“25~65一定會在下一次導航時保持未知”。第二組保留灰205對應的未知身份,但已知25也由空閒改為灰區,因為儲存整數free門檻變成rint(19.6)=20。0.196不是隻修後設資料、完全保持原分類的無副作用替換。 灰205寫入與YAML門檻、載入灰度判斷
如果任務需要保留未知區,可以在三態重新匯出時明確使用經此版本核對的free=0.196,並複核20附近的已知佔用分類;也可以選擇其他合適編碼模式,但必須一起核對map_server和StaticLayer的解釋,不能只改mode字串。對既有檔案應先檢查實際畫素和後設資料,不能斷言來自任意工具的灰色圖片都有此問題。
這也解釋了前面global_costmap的邊界:頂層track_unknown_space=True只能保留/map訊息裡仍為-1的格;若地圖載入已把未知轉為0,代價層和Theta*的allow_unknown不能恢復丟失身份。地圖載入、未知區與全域性規劃
本文保留原0.25,沒有自動改工程引數或已有地圖。這裡記錄的是本安裝版本、指定三態讀寫條件下已經驗證的行為;當前實車地圖是否採用相同畫素,仍需核對。
1.2.2.儲存請求與命令列(不屬於原5項節點引數)
1.2.2.1.儲存請求欄位
| SaveMap請求欄位/CLI選項 | 單位或型別 | 當前用法 |
|---|---|---|
| map_topic/-t | 話題名稱 | 指定OccupancyGrid輸入;空請求退回相對map,namespace為/red_standard_robot1時即該namespace/map;絕對話題可明確來源 |
| map_url/-f | 輸出檔案路徑字首 | 例如目錄/site_01會生成site_01.yaml和site_01.pgm;不是輸入地圖路徑,不應把.yaml再當作字首字尾填入 |
| image_format/--fmt | 格式名稱 | 未指定時trinary/raw預設pgm,scale預設png;未知透明通道需選支援它的格式 |
| map_mode/--mode | trinary/scale/raw | 未識別值會警告並回退trinary;需要明確自己採用的畫素解釋 |
| free_thresh/--free | 無量綱比例 | 0走節點預設;非零覆蓋本次值,不自動改節點default引數 |
| occupied_thresh/--occ | 無量綱比例 | 0走節點預設;非零覆蓋本次值,仍需和free一起滿足關係 |
服務預設名為map_saver/save_map,機器人namespace存在時例如/red_standard_robot1/map_saver/save_map。請求響應result表示儲存函式的成功/失敗,不包含PCD或位姿圖。SaveMap介面
介面註釋雖舉file://與package://形式,本次1.1.20的儲存實現直接把map_url當檔名字首交給圖片寫入和檔案流,沒有對應URI解析;這裡應使用實際可寫檔案系統路徑,不把註釋示例當已經支援的協議。
儲存會寫圖片和YAML兩個檔案,目錄需要預先存在且可寫;重複同一路徑會覆蓋同名輸出。預設空路徑會生成帶系統時間秒數的map_字首,仍不保證自動按場地歸檔。檔名生成顯式使用系統時鐘,不由use_sim_time決定。檔名與寫出
1.2.2.2.CLI為何不繼承此處5秒設定
1.1.20的map_saver_cli在當前程序中新建MapSaver,呼叫on_configure讀取自身引數,然後直接呼叫saveMapTopicToFile;沒有給另一個map_saver_server發SaveMap請求。若沒有給CLI提供ROS引數或匹配其節點名/namespace的引數檔案,它仍使用原始碼預設save_map_timeout=2.0,而不是建圖launch中的5.0。
本工程README給出的CLI命令只傳輸出字首和namespace,沒有傳這份引數檔案;因此不要把它當成已使用這裡全部設定。顯式--free/--occ可以改變本次閾值,--ros-args後的-p可以設定該CLI儲存器自己的等待與訂閱配置。CLI新建儲存器
以下只是後續手動儲存示例,未執行。路徑是示例,需要換成實際存在且可寫的目錄;/red_standard_robot1/map也需要按實際機器人修改。此例使用對照門檻0.196,另給CLI獨立名字,避免與已有map_saver同名:
ros2 run nav2_map_server map_saver_cli \
-t /red_standard_robot1/map \
-f /home/tungchiahui/UserFolder/MyMap/site_01/site_01 \
--free 0.196 --occ 0.65 --fmt pgm --mode trinary \
--ros-args -r __ns:=/red_standard_robot1 -r __node:=map_saver_cli \
-p use_sim_time:=False -p save_map_timeout:=5.0 \
-p map_subscribe_transient_local:=True
模擬時按實際時間域設定該CLI的use_sim_time。程式自己的-t、-f、--free等選項應放在--ros-args前;引數檔案若採用map_saver分組,而CLI被改名為map_saver_cli,還要調整節點匹配,不能只傳檔案就認為所有引數已載入。
1.2.3.檔案內容、啟動條件與驗證範圍
1.2.3.1.儲存了什麼,哪些還需要另存
| 輸出或資料 | 當前儲存器是否負責 | 應怎樣理解 |
|---|---|---|
| 二維圖片與配套地圖YAML | 是 | 寫輸入網格的尺寸/解析度/原點x、y、yaw及本次mode、閾值;後續由map_server讀取 |
| OccupancyGrid.header.frame_id | 不作為獨立frame_id欄位寫進地圖YAML | 後續由map_server自己的frame_id引數設定訊息座標名稱;不能靠檔案自動恢復所有座標關係 |
| 三維PCD | 否 | 由Point-LIO等點雲儲存路徑處理,供small_gicp定位 |
| slam_toolbox的.posegraph/.data | 否 | 由slam_toolbox序列化介面另存,供繼續建圖或其內部定位模式使用 |
| 全套TF、軌跡、車輛包絡與可爬坡能力 | 否 | 二維圖不是完整機器人狀態快照,也不能證明坡道機械可通行 |
儲存不新增格距或重新計算地面:輸入是什麼解析度,輸出就按其後設資料記錄。本版本YAML輸出使用Precision(3),非簡單小數的原點/格距需要在儲存後複核;不能一概稱為逐位精度不變。圖片中的相對image路徑只寫檔名,通常和YAML成套儲存、成套搬移。後設資料寫出
1.2.3.2.當前啟動與執行中修改
| 情況 | 當前實現及作用 |
|---|---|
| slam=True | slam_launch啟動map_saver_server,生命週期管理列表為map_saver;提供儲存服務,但launch不自動呼叫儲存 |
| slam=False | 當前定位分支啟動map_server和small_gicp,不由這條分支啟動map_saver_server;引數檔案裡有分組不代表節點一定執行 |
| reality/simulation | 兩份map_saver段僅時鐘False/True不同,其他4項一致;slam_launch重寫已有use_sim_time |
| map_saver_server | configure時讀4項專用引數並建立SaveMap服務;啟用階段建立生命週期bond |
| map_saver_cli | 新建自己的儲存器並手動on_configure後直接儲存;不依賴遠端伺服器是否Active |
| 執行中設定4項專用引數 | 當前沒有動態回撥更新已讀取成員;設定成功不保證生效,需要重新配置/重啟;單次閾值可用請求覆蓋 |
MapSaver的SaveMap回撥沒有MapServer的Active狀態拒絕檢查;服務在configure階段建立。工程正常由lifecycle_manager_slam管理,但不能根據MapServer行為就斷言此版本儲存器在Inactive時一定拒絕請求。儲存服務與生命週期
1.2.3.3.本次驗證與實物待檢查
- 本次已核對: nav2_map_server 1.1.20與安裝標頭檔案;原始碼/安裝引數段一致;建圖launch實際啟動map_saver_server、管理map_saver且沒有自動儲存呼叫;CLI獨立物件路徑及預設2 s。
- 本次已離線驗證: 用安裝的libmap_io和臨時7×1地圖,分別以free=0.25、0.196、occupied=0.65完成trinary/PGM儲存再讀取;確認前者把未知變空閒、後者保留該未知格,並確認25附近分類也改變。沒有啟動ROS節點、地圖話題或儲存服務,臨時圖片與YAML已隨臨時目錄清理。
- 原始碼能確認: 5 s只包等圖階段;請求閾值優先、0為預設哨兵;三態包含邊界的儲存比較、嚴格邊界的載入比較;QoS和臨時訂閱、路徑字首、輸出後設資料及動態引數限制。
- 實物待核實: 實際map_topic及QoS、訊息新鮮度、儲存呼叫返回和檔案完整性、真實地圖的畫素/未知區重新載入、二維圖與PCD對齊、當前儲存方式是否取得需要的引數;本次未對真實建圖或導航任務執行儲存。