跳到正文

Wiki

map_saver(二維柵格地圖儲存)

約 19 分鐘閱讀

本文由簡體中文內容確定性轉換,並受版本化術語表保護。

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對齊、當前儲存方式是否取得需要的引數;本次未對真實建圖或導航任務執行儲存。