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

模擬

robot-notes /模擬/用 Gazebo + ROS2 模擬 AMR

用 Gazebo + ROS 2 模擬室內差速 AMR

一句話定位:在電腦裡用 Gazebo(開源機器人物理模擬器)蓋一個虛擬餐廳,放進一台差速送餐機器人,讓它產生跟真車一樣的感測器訊號(雷射、里程、姿態),再接上 Nav2(ROS 2 的導航軟體堆疊)跑 SLAM 與自主導航——整套不碰真硬體就能驗證導航邏輯。

延伸閱讀:Physical AI 總覽導航運動學與座標轉換SLAM 建圖定位

本檔聚焦 Gazebo 這條輕量、CPU 可跑、ROS 原生的路線;與 Isaac Sim 的分工放在最後一節。


1. 先把名字搞清楚:Gazebo Classic 已死,現在的「Gazebo」是 gz sim

這是新手最常踩的坑。同一個品牌「Gazebo」前後是兩套完全不同的軟體:

命名為什麼亂:一個品牌名(Gazebo)被「舊架構(Classic)」和「新架構(原 Ignition)」共用過,中間還改名兩次(Gazebo→Ignition→Gazebo)。判斷手上是哪套,看指令最快:gazebo 是 Classic、gz sim 是新版。

名詞:LTS(Long-Term Support,長期支援版)指維護週期較長的版本。Gazebo Harmonic 是 LTS,支援到 2028-09。

來源:Gazebo Classic EOL 公告(Open Robotics Discourse)gazebo-classic GitHub(導向 gz-sim)

Gazebo 版本 ↔ ROS 2 版本對應(最容易踩雷)

每個 ROS 2 distro(發行版)官方「配對」一個 Gazebo 版本。用配對版本最省事;混搭可行但要自己裝非官方 binary 或從原始碼編 ros_gz

ROS 2 distro 官方配對 Gazebo 備註
Humble(LTS, 22.04) Fortress 官方主支援是 Fortress;也可改裝 Harmonic(走 ros-humble-ros-gzharmonic),屬進階用法
Jazzy(LTS, 24.04) Harmonic 從 Jazzy 起 Gazebo 走 ROS vendor package,整合最順
Kilted(24.04) Ionic  
Rolling / 後續 LTS Jetty Rolling 是滾動開發版

安裝就一行(裝配對版本的橋接 metapackage):

sudo apt-get install ros-${ROS_DISTRO}-ros-gz   # ROS_DISTRO 換成 humble / jazzy / kilted

實務建議:新專案首選 Jazzy + Harmonic(都是 24.04 上的 LTS、官方配對、整合最乾淨)。若被既有 Humble 環境綁住,維持 Humble + Fortress 最穩,要新功能再評估升 Harmonic。版本細節以官方最新為準。

名詞:vendor package 指 ROS 官方把 Gazebo 函式庫重新打包進 ROS apt 倉庫,讓相依關係跟 ROS 套件一致、不必另加第三方來源。

來源:Installing Gazebo with ROS(官方)ros_gz README 相容矩陣


2. 一台差速 AMR 的模擬要哪些零件

把模擬拆成四塊:機器人長相 → 怎麼動 → 怎麼感知 → 在哪裡跑

(a) 機器人描述:URDF / SDF

實務上機器人本體寫 URDF(ROS 端的 robot_state_publisher 也要吃它),在 URDF 裡用 <gazebo> 區塊補上 Gazebo 專屬設定;Gazebo 載入時會把 URDF 轉成 SDF。差速車最少要有:底盤 link、左右驅動輪 joint(continuous)、一個萬向輪。

(b) 怎麼動:ros2_control + diff_drive_controller

讓模擬輪子能被 ROS 指令驅動,標準作法是 ros2_control(ROS 2 的即時控制框架),在 Gazebo 端由 gz_ros2_control 這個外掛接起來:

也就是說 diff_drive_controller 同時做兩件事:/cmd_vel 驅動輪子 + /odom 與 tf。這正好是 Nav2 需要的介面。

名詞:tf(transform)指 ROS 的座標轉換樹,描述各座標系(map/odom/base_link/雷射…)之間的相對位姿。

來源:gz_ros2_control 文件Setting Up Odometry - Gazebo(Nav2)

(c) 怎麼感知:LiDAR / 相機 / IMU 感測器外掛

感測器在 SDF/URDF 裡以 <sensor> 宣告,由 Gazebo 的系統外掛負責模擬出資料:

感測器 SDF type 模擬它的系統外掛 產出(經橋接後的 ROS 訊息)
2D LiDAR gpu_lidar gz-sim-sensors-system(渲染類感測共用) sensor_msgs/LaserScan(SLAM 主力)
3D LiDAR gpu_lidar gz-sim-sensors-system sensor_msgs/PointCloud2(點雲,非 LaserScan)
相機 camera / depth_camera 同上 sensor_msgs/Image
IMU imu gz-sim-imu-system sensor_msgs/Imu

世界檔本身也要掛幾個基礎系統外掛才動得起來:gz-sim-physics-system(物理)、gz-sim-scene-broadcaster-system(場景廣播)、gz-sim-sensors-system(感測渲染)。

名詞:plugin / system(系統外掛)— Gazebo 把功能模組化成可掛載的外掛,物理、感測、控制各是一個 system,在 SDF 裡用 <plugin filename=...> 掛進去。

來源:Gazebo Harmonic Sensors(官方)Setting Up Sensors - Gazebo(Nav2)

(d) 在哪裡跑:世界檔(world)

world 是一份 SDF,描述場景:地面、牆壁、桌椅、光源、物理參數。送餐情境就是擺一個餐廳:走道、餐桌、出餐口。可以自己用 SDF 拼,或載入現成模型(Gazebo Fuel 線上模型庫)。世界檔決定了 LiDAR 會掃到什麼、SLAM 要建出什麼地圖。


3. 跟 Nav2 串接:sim → Nav2 閉迴路

核心觀念:Gazebo 不知道 Nav2 存在,Nav2 也不知道 Gazebo 存在。它們只透過幾個標準 ROS topic 與 tf 對話,中間靠 ros_gz_bridge 翻譯。把 Gazebo 換成真車(同樣發 /scan /odom /tf、收 /cmd_vel),Nav2 那側完全不用改——這正是模擬的價值。

資料流(閉迴路):

Gazebo↔ros_gz_bridge↔Nav2 閉迴路:gz 出 /scan /odom /tf,Nav2 規劃發 /cmd_vel 回 gz

tf 樹由三方各補一段,合起來才完整:

提供者 提供的 tf 意義
Gazebo / diff_drive_controller odom → base_link 機器人相對里程原點的位姿(相對定位,會漂移)
robot_state_publisher(讀 URDF) base_link → 各感測器/輪子 機器人本體固定幾何
slam_toolbox(建圖時)/ AMCL(已知地圖時) map → odom 把里程漂移修正回全域地圖座標

來源:Setting Up Odometry - Gazebo(Nav2)Navigating while Mapping (SLAM)(Nav2)Mapping and Localization(Nav2)


4. 典型啟動方式(結構,不貼完整程式)

一個完整模擬通常由一個 ROS 2 launch file(啟動描述檔,Python 或 XML)把下列節點一次拉起來:

  1. 啟 Gazebo + 載世界:用 ros_gz_sim 提供的 launch 包起 gz sim <restaurant.world>
  2. 生成機器人:把 URDF 餵給 ros_gz_simcreate,在世界裡 spawn 出 AMR。
  3. robot_state_publisher:讀 URDF 發布機器人本體 tf。
  4. ros_gz_bridge:逐一宣告要橋接的 topic 對應,把 gz 訊息翻成 ROS 訊息(雙向)。語法形如:
    /scan@sensor_msgs/msg/LaserScan@gz.msgs.LaserScan        # gz → ROS
    /cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist           # ROS → gz
    /odom@nav_msgs/msg/Odometry@gz.msgs.Odometry
    

    (@ROS型別@gz型別,中間的方向由箭頭符號決定)

  5. controller_manager + spawner:載入 diff_drive_controllerjoint_state_broadcaster
  6. slam_toolbox 或 Nav2:依「建圖」或「導航」階段擇一啟動,通常各自一份 launch,再用一個上層 launch 組合。

名詞:ros_gz_bridge / parameter_bridgeros_gz 套件群裡負責 Gazebo Transport 與 ROS 2 之間雙向轉訊息的橋。ros_gz 還含 ros_gz_sim(啟動/生成工具)、ros_gz_image(影像單向高效傳輸)等。

來源:ros_gz README(套件職責)Setting Up Sensors - Gazebo(Nav2,橋接語法)


5. Gazebo 的定位 vs Isaac Sim:各適合什麼

兩者不是取代關係,是不同階段、不同目的的工具。

面向 Gazebo (gz sim) NVIDIA Isaac Sim
授權/生態 開源,Open Robotics,ROS 原生 免費但閉源,跑在 NVIDIA Omniverse 平台上
物理/算力 CPU 即可跑,輕量 GPU 加速物理;Isaac Lab 可在 GPU 上並行上千個環境
畫面擬真 夠用,非照片級 RTX 光線追蹤,照片級渲染
強項定位 導航/控制/感測整合驗證、ROS2 堆疊聯調、教學、CI 強化學習(RL)大規模訓練、合成資料生成(Replicator + 域隨機化)、電腦視覺/感知擬真
ROS2 整合 best-in-class(ros_gz 橋接) 可接但學習曲線較陡
硬體門檻 低,無獨顯也能(可 headless) 高,需較強 NVIDIA GPU

選用建議:

名詞:RL(Reinforcement Learning,強化學習)讓機器人在模擬中反覆試錯學策略;合成資料指模擬器自動產生帶標註的訓練影像,省去真實標註成本;域隨機化(Domain Randomization) 隨機化光照/材質/物件以提升真實世界泛化(見 Physical AI 總覽)。

來源:Gazebo vs Isaac Sim 比較(SVRC)Robot Simulation Software: 2026 Perspective(Black Coffee Robotics)


6. 安裝與硬體需求概況

對照:這份「內顯可跑、headless 可上 CI」正是 Gazebo 相對 Isaac Sim(需較強 NVIDIA GPU)的入門優勢。

來源:Binary Installation on Ubuntu - Harmonic(官方)Installing Gazebo with ROS(官方)


7. 把舊世界搬上新 Gazebo:以 AWS Small Warehouse 為例

第一次讀可以整節跳過。 這節是把一個 Gazebo Classic 時代的現成世界搬到新版的實錄,服務的是「網路上找到一個 world 但打不開」的情境。你還沒跑過 gz sim 的話,先讀完 §1–§6 再回來。

網路上現成的模擬世界(餐廳、倉庫、辦公室)很多都是 Gazebo Classic 時代的,直接丟進 gz sim 不會動。這節用實際把 AWS RoboMaker Small Warehouse(Classic)遷到 Harmonic 的經驗,講「為什麼不能直接載、要改什麼、怎麼確認改對了」。完整可跑的成品在獨立 repo:aws_warehouse_model_for_gazebo_harmonic

為什麼 Classic 的 .world 不能直接在 gz sim 跑

關鍵認知:卡住的不是幾何。倉庫 9 成是靜態 mesh(地板、牆、貨架),mesh(Collada/OBJ/STL)兩套 Gazebo 通用。真正不相容的是三件機械性的事:

卡點 Classic 新 gz sim
資源路徑環境變數 GAZEBO_MODEL_PATH GZ_SIM_RESOURCE_PATH(解析 model://)
plugin libgazebo_ros_* gz-sim-*-system,name= 要填 C++ 類別全名;硬載對方 plugin 會 crash
world 系統 plugin Classic 內建 要自掛 Physics / SceneBroadcaster / UserCommands / Sensors,否則沒物理、沒感測、GUI 不動

加上 SDF 版本要升、材質可能要從 Classic 材質腳本改走 mesh 自帶 / PBR。AWS 倉庫的好消息是:14 個模型全部沒有 plugin、沒有材質腳本、全 <static>,所以模型層只要升 SDF 版本(1.6 → 1.10),材質與 mesh 原封不動;world 層補上 4 個系統 plugin、清掉 <pose frame="">、physics 區塊處理一下即可。

遷移時意外踩到的坑(這幾個最值得記)

把檔案改完不等於對。用 gz sdf 與嚴格 XML 解析器一驗,跳出幾個一開始沒料到的:

  1. <physics>type 是 SDFormat 必填屬性。新版 gz sim 的物理引擎其實是由 gz-sim-physics-system plugin 決定(預設 dartsim),於是直覺會想把 Classic 的 type="ode" 拿掉——結果 SDFormat 直接報錯(缺必填屬性)。正解是保留 type 字串(留 ode 即可),引擎選擇交給 plugin。
  2. 慣性張量要過三角不等式。AWS 的 GroundB(地板)、RoofB(屋頂)原始 inertia 其實是壞的(ixx+izz < iyy),Classic 不檢查、新版 sdformat 直接 Error。這兩個是 static 裝飾物,慣性根本不影響模擬,改成合法值即可——但沒有驗證就不會發現原始資料是錯的
  3. XML 註解裡不能有 --(雙連字號)。在註解寫了 --physics-engine,gz sdf 用的 tinyxml2 容忍,但嚴格的解析器(Python expat)直接拒——這是 XML 規格,不是 bug。寫中文註解時很容易不小心帶到。

這三個都是「Classic 容忍、新版較嚴」的典型:遷移的工作量常常不在搬幾何,而在補上新版才會檢查的正確性。

怎麼確認改對了(驗證 + CI,省本機 CPU)

gz sim 跑起來吃 CPU/GPU;但驗證模型不必真的開模擬。分兩層:

本機 CPU 吃緊時,把這兩層丟上 GitHub Actions:runner 裝 Gazebo Harmonic,push 後自動跑靜態驗證 + gz sim headless 載入(用 xvfb 軟體渲染)。改一行 push 一次,綠燈才算數,完全不占你本機資源。設定見 model repo 的 .github/workflows/validate.ymlMIGRATION.md

一個工作習慣:先在本機把驗證腳本跑通,再寫成 CI。CI 腳本自己也有盲點(例如一開始用 gz sdf -p 想驗 world,卻因為它解析不了 model:// 而誤判),先在本機踩過,CI 才不會綠得不明不白或紅得冤枉。

想在 CI 裡驗證這些改動

搬完之後總要有人確認沒搬壞,而免費 CI runner 沒有 GPU——這件事的完整處方(靜態驗證、headless 載入、純物理跑軌跡、真的要出圖時的 EGL/llvmpipe 設定)整理在另一篇:

GitHub Actions 跑 gz sim 的 playbook

一句話帶走:先問「這一關需不需要 render」。不需要 render 的檢查又快又穩(SDF 靜態解析、world 載入、逐步位姿記錄再用 matplotlib 畫成軌跡圖),需要 render 的那些在免費 runner 上會掉到軟體光柵器,慢到只跑得動 4% 真實時間。

參考來源(實際查證)