Unitree Go2 EDUで障害物を避けてみた

Unitree Go2 EDUで障害物を避けてみた

2026.06.12

こんにちは。atsuです。

Unitree Go2 EDU のフロア巡回機能を9つに分解した全体像は、別記事で整理しています。本記事では、そのうち 障害物を避ける 機能を扱います。

https://dev.classmethod.jp/articles/go2-patrol/

Nav2 で目的地まで移動できるようになっても、実際のフロア巡回ではまだ足りないことがあります。保存済みの地図には壁や柱のような固定物は入っていますが、巡回中にだけ現れる人、椅子、段ボールなどは入っていません。

この「いま目の前にあるもの」を Nav2 が避けられる形にするために使うのが costmap です。この記事では Nav2 costmap の全機能を網羅するのではなく、Go2 EDU をフロア巡回ロボットとして動かすために、何を costmap に入れ、どこを確認しながら障害物回避を調整したかを扱います。

Go2 EDU(プリインストールのROS2 Foxy)と Nav2、開発PCは M3 Mac という構成です。ROS2 Foxy はすでに公式サポートを終えていますが、Go2実機側の環境に合わせています。マップ作成や自己位置推定などの前提は、本ブログの参考リンクにある過去記事で扱っています。

この記事の中心は、以下の関係です。

この記事でわかること

  • Go2 の巡回で costmap がなぜ必要か
  • 保存済みマップ、LiDAR、costmap の役割の違い
  • local costmap と global costmap の見方
  • Go2 の LiDAR(3D 点群)を costmap に入れるときに注意したこと
  • 障害物回避が期待通りに動かないときの確認ポイント

先に用語を整理する

用語が多いので、まずは「もともとの地図」と「巡回中に見えている障害物」を分けて読むと追いやすいです。

用語 意味 この記事での役割
costmap 通りやすさ、危険度を持つ 2D グリッド 経路計算と障害物回避に使う
static layer 保存済みマップを costmap に入れる層 壁や柱を経路計算に反映する
obstacle layer センサーで見えた障害物を入れる層 人や椅子など、巡回中に現れたものを反映する
inflation layer 障害物の周囲に余白を作る層 壁や障害物に近づきすぎないようにする

この記事で主に見る ROS topic は以下です。

topic 何が流れるか この記事での見方
/scan 2D LiDAR スキャン costmap に入る前の障害物入力を見る
/local_costmap/costmap ロボット周辺の costmap controller が避ける対象を見る
/global_costmap/costmap 広い範囲の costmap planner が見る地図を確認する

Planner(経路計算)や Controller(速度指令)は topic 名ではなく Nav2 の役割です。

Go2 が障害物を避ける流れ

冒頭の図を、Nav2 costmap の内側まで少し分解します。

なお、この図は簡略化しています。実際の Nav2 では global costmap と local costmap は別インスタンスとして動いており、planner_server は global costmap を、controller_server は local costmap を参照します。

Go2 EDU の LiDAR は周囲を3D点群として出します。一方、今回の Nav2 costmap では2Dのスキャンとして扱う構成にしています。そのため、Go2の3D点群を Nav2 が扱いやすい /scan に整えてから costmap に入れています。

costmap 自体は Nav2 の標準機能です。Go2 固有なのは、その手前で Go2 のセンサー情報を Nav2 が読みやすい形に整える部分です。

Go2の巡回で costmap が必要な理由

保存済みマップは、フロアの壁や柱を表す地図です。同じフロアを巡回するなら、この地図は何度も使えます。

ただし、実際のフロアは毎回同じ状態ではありません。椅子の位置が変わることもありますし、人が通路を横切ることもあります。保存済みマップだけを見て経路を引くと、地図上では通れる場所でも、実際には通れないことがあります。

costmap は、保存済みマップに「今見えている障害物」と「近づきすぎないための余白」を重ねた地図です。Nav2 の planner は global costmap を見て経路を考え、controller は local costmap を見ながら速度指令を出します。

diagram-costmap-layers

ロボットに詳しくない方向けに言うと、costmap はロボット用の「危険度つき床面図」です。ここを分けておくと、「地図は合っているのに Go2 が止まる」「何もないように見える場所を避ける」といった現象を切り分けやすくなります。

local costmap と global costmap

Nav2 では、主に local costmap と global costmap の 2 種類を使います。

costmap 見ている範囲 主な使い道
global costmap ゴールまでの広い範囲 大きな経路や迂回経路を考える
local costmap Go2 の周辺 目の前の障害物を見ながら速度を決める

diagram-local-global-costmap

巡回中に人が横切るような場面では、local costmap の見え方が特に重要です。global plan は大きな方向を示し、controller が local costmap を見ながら実際の速度を調整します。

一方で、遠回りが必要な障害物や、通路全体が塞がっているような状況では global costmap も効いてきます。

Go2実機で costmap を使うために必要だった3つの対応

Go2 実機で Nav2 costmap を使うために、次の3つを意識しました。

  • Go2 の 3D 点群を 2D スキャンとして扱える形に整える
  • costmap layer の役割を分けて見る
  • マッピング用と巡回用でセンサー処理の設定を分ける

1. Go2 の3D点群を2Dスキャンとして整える

前述のとおり、Go2 の3D点群は2Dの /scan に変換してから costmap に入れています。

このとき、Go2自身の体に当たった反射や、床面近くのノイズをそのまま入れると、Nav2 はそれも障害物として扱います。実際には通れる場所でも、costmap 上では前が塞がっているように見えることがあります。

そのため、3D点群から2Dスキャンへ整える段階で、高さ、距離、孤立点のフィルタを入れました。変換は自作のブリッジノードで行っています(標準の pointcloud_to_laserscan パッケージ相当の変換に、上記のフィルタを加えたもの)。Nav2 の costmap パラメータを調整する前に、まず /scan が妥当かを見るようにしています。

2. costmap layer の役割を分けて見る

costmap は1枚の地図に見えますが、内部では複数の layer を重ねています。

layer 何を担当するか 調整を間違えたときに起きること
static layer 保存済みマップを入れる 壁に近い経路を引く、存在しない壁を避ける
obstacle layer センサーで見えた障害物を入れる 人や椅子を無視する、何もない場所で止まる
inflation layer 障害物の周囲に余白を作る 大回りしすぎる、障害物に近づきすぎる

Nav2 公式ドキュメントでも、costmap は planner や controller が衝突や高コスト領域を確認するために使う 2D グリッドとして説明されています。Obstacle Layer は 2D raycasting を使ってセンサー情報を costmap に入れ、Inflation Layer は障害物の周囲にコストを広げます。

実機では、「どの layer の問題か」を分けて見るのが大事でした。たとえば Go2 が何もない場所で止まる場合、inflation が強すぎることもあれば、obstacle layer に古い障害物が残っていることもあります。見た目は同じ「止まる」でも、見る場所が変わります。

3. マッピング用と巡回用で設定を分ける

マップ作成では、壁の形を安定して取りたいのでノイズ除去を強めたくなります。一方、巡回では低い障害物も見たいので、低い点を落としすぎると困ります。

1つの設定で全部を賄おうとすると、マップ作成ではノイズが増え、巡回では低い障害物を見落とす、といったトレードオフが生じます。

そのため、この環境では3D点群から2Dスキャンへ整える設定を、マップ作成用と巡回用で分けました。マップを作るときは地図の安定性を優先し、巡回するときは障害物検知と停止しやすさを優先しています。具体的には、高さフィルタの下限をマッピング時は LiDAR の取り付け高さ、巡回時は地面から約10cm に設定しました。

Go2 の障害物回避を確認するポイント

障害物回避が期待通りに動かないときは、最初から Nav2 全体を疑うより、情報の流れに沿って見る方が切り分けやすいです。

# Foxy の ros2 topic echo には --once オプションが無いため、内容を確認したら Ctrl+C で停止します
# --no-arr は配列フィールドの中身を省略表示するオプションです
ros2 topic echo /scan --no-arr
ros2 topic echo /local_costmap/costmap --no-arr
ros2 topic echo /global_costmap/costmap --no-arr
ros2 topic echo /plan --no-arr
ros2 topic echo /cmd_vel

echo ではまず各 topic が流れているかを確認します。そのうえで、/scan の障害物が local costmap や global costmap に反映されているか、/plan(planner が計算した経路)が障害物を避ける形になっているか、/cmd_vel が減速や停止につながっているかを順番に見ます。なお、/cmd_vel は Nav2 がナビゲーションを実行している間しか流れません。echo しても何も表示されない場合は、ナビゲーション中かどうかを先に確認します。

障害物がどう見えているかは echo だけでは追いにくいので、可視化ツールの Foxglove Studio で /scan, /local_costmap/costmap, /global_costmap/costmap, /plan, /cmd_vel を同時に見ると確認しやすいです。Go2 実機が止まった瞬間に、どこまで情報が伝わっていたかを確認できます。

切り分けの順番は次のようにしました。

観察 次に見るところ
/scan が想定と違う 3D点群から2Dスキャンへの整え方、高さフィルタ、距離設定
/scan は正しいが costmap が想定と違う obstacle layer、clearing(見えなくなった障害物を costmap から消す処理)、TF
costmap は正しいが /plan が想定と違う global planner、inflation radius
/plan は正しいが Go2 の動きが不自然 controller、速度制限

Go2実機でつまずいたところ

Go2実機では、costmap の設定だけでは説明しきれない場面がありました。つまずいた点は次の2つです。

つまずいた点 何を見たか 対応したこと
何もない場所に障害物が残る /scan と local costmap 3D点群から2Dスキャンへ整える段階でノイズを減らす
障害物が消えた後も避け続ける /scan/local_costmap/costmap_updates clearing と costmap 更新の効き方を見る

何もない場所に障害物が残る

Go2 自身の反射や歩行中の揺れで、実際には何もない場所に障害物が残ることがありました。これが local costmap に残ると、controller は「前が塞がっている」と判断します。

この場合、costmap のパラメータだけを見ても原因をつかみにくいです。私はまず /scan を見て、costmap に入る前のデータに不要な点が含まれていないか確認してから、前述のフィルタ(高さ、距離、孤立点)を調整しました。

障害物が消えた後も避け続ける

障害物が視界から消えたあと、costmap から消えるまでに遅れがあると、Go2は何もない場所を避け続けるように見えます。

このときは、/scan/local_costmap/costmap_updates(local costmap の差分更新 topic)を並べて確認しました。/scan から障害物が消えているのに costmap 側に残る場合は、clearing や TF、更新タイミングを確認対象にしました。

運用上の見立て

巡回ロボットの障害物回避では、完璧な認識よりも「危ないときに止まれる」「何もない場所では止まりすぎない」のバランスが大事でした。

Go2 のような四足歩行ロボットでは、歩行振動とセンサー反射の影響があります。そのため、最初は保守的に止まれる設定から始め、実機で少しずつ調整する方が安全でした。

もう1つ大事なのは、障害物回避を「最短経路で通れるか」だけで評価しないことです。巡回ロボットは人のいる空間に入るので、経路の短さよりも「見ていて不安にならない距離感」を優先して調整する方が、Go2のフロア巡回には合っていると感じました。

まとめ

Go2 EDU の障害物回避は、Nav2 costmap と、costmap に入る前の /scan を整える処理を組み合わせて実現しました。

  • Go2 の障害物回避では、保存済みマップに今見えている障害物と余白を重ねて考える
  • local costmap は Go2 周辺の回避、global costmap は大きな迂回経路を見る
  • Go2 自身の反射や歩行ノイズは、costmap に入る前の /scan 側でも確認する
  • 期待通りに動かないときは、/scan → costmap → /plan/cmd_vel の順に切り分ける

参考リンク

この記事をシェアする

関連記事