Unitree Go2とG1を活用したロボット目視検査エージェント:Grounding DINOと幾何ポリシーで実現するGo2の準リアルタイム進路リスクダッシュボード

Unitree Go2とG1を活用したロボット目視検査エージェント:Grounding DINOと幾何ポリシーで実現するGo2の準リアルタイム進路リスクダッシュボード

Week 3 progress of the Robot Visual Inspection Agent project!
2026.07.23

はじめに

先週は、Unitree Go2のフロントカメラから周期的に静止画を取得し、ローカルVLMで継続解析するPoCを構築しました。

先週までの構成は以下のようなものでした。

Go2フロントカメラ
  ↓
周期的な静止画取得
  ↓
Qwen2.5-VLによる観測
  ↓
構造化JSON
  ↓
Pythonによるリスク判定
  ↓
Markdownレポート

実機カメラ画像からJSONやレポートを生成できたことは大きな進展でした。

一方で、実画像の評価を進める中で、VLMだけに「物体認識」「位置理解」「進路判定」「リスク判定」をすべて任せることには限界があることも分かりました。

例えば、VLMは人物を認識していても、その人物がロボットの直進経路を塞いでいることを安定して判断できないケースがありました。また、近距離のドア、箱、混在した障害物についても、出力が揺れることがありました。

そこで今週は、ライブのリスク判定経路を以下のように再設計しました。

Go2フロントカメラ
  ↓
周期的な静止画取得
  ↓
Grounding DINO Tinyによる物体検出
  ↓
bounding box
  ↓
進路ROIとの幾何計算
  ↓
決定的なPythonリスクポリシー
  ↓
JSON / overlay PNG
  ↓
DGX Spark上のStreamlitダッシュボード

今回の中心テーマは、VLM中心の構成から、Grounding DINOによる物体位置情報と決定的な幾何ポリシーを中心とする構成への移行です。

また、Go2の実カメラ画像をDGX Sparkへ取り込み、GPU推論結果をオペレーター向けダッシュボードへ継続表示するところまで接続しました。

本記事でいう「準リアルタイム」は、数秒ごとに取得した静止画を継続的に解析し、最新結果を短い更新周期で表示することを意味します。

動画ストリームを直接入力するリアルタイム動画解析でも、安全認証済みの衝突回避システムでもありません。

デモ動画

https://youtube.com/shorts/VX45rbx6deU


今週できたこと

今週は主に以下を実装・検証しました。

  • Grounding DINO Tinyを用いた物体検出パイプラインの構築
  • bounding boxと進路ROIを使った決定的な幾何リスク判定
  • 複数物体のunion occupancyによる進路遮断判定
  • bboxと進路ROIを重ねたoverlay画像の生成
  • Go2の実フロントカメラ画像をDGX Spark上で継続解析
  • Go2画像を単一のlatest.jpgとして扱うmailbox設計
  • Streamlitによるオペレーター向けリスク可視化ダッシュボード
  • START / STOPによる解析プロセス制御
  • 解析ログ、推論時間、検出物体、進路遮断状態の表示
  • DGX Spark GB10 GPU上でのGrounding DINO CUDA推論
  • ダッシュボードの更新周期と解析watcher周期の短縮
  • リスク状態に応じたローカル警告音の実装
  • 実機カメラ入力からdashboard表示までのend-to-end動作確認

今回の構成も、引き続き手動操作の確認支援のみを目的としたadvisory-only systemです。

Go2の移動、停止、旋回、姿勢制御などには一切接続していません。


VLMをライブのリスク判定経路から切り離した理由

先週までの検証では、Qwen2.5-VL-3B / 7Bを用いて、Go2実カメラ画像から以下のような観測情報を取得していました。

{
  "scene": "office",
  "objects": [
    "person",
    "boxes",
    "door"
  ],
  "risk_level": "medium",
  "recommended_action": "operator confirmation"
}

JSONを安定して出力できることと、物理世界の状態を正しく判断できることは別問題でした。

特に課題だったのは、以下のようなケースです。

人物を検出しているが、進路はclearと判断する
閉じたドアが画面中央を占めているが、highにならない
複数の箱が前方にあるが、個別にはmedium扱いになる
近い物体と遠い物体の違いを安定して表現できない
同じ画像でも観測結果やリスク表現が揺れる場合がある

そのため、VLMを完全に不要と判断したのではなく、高速かつ継続的に動かす進路リスク判定の中心から切り離すことにしました。

現在の役割分担は以下です。

Grounding DINO:
  物体を検出し、bounding boxで位置を取得する

Python geometry:
  bounding boxと進路ROIの重なりを計算する

Deterministic policy:
  occupancyやbbox位置からrisk levelとpath_blockedを決定する

VLM / LLM:
  将来的に場面説明、レポート生成、標識や未知物体の意味理解、
  オペレーターが要求した場合の補助説明に利用する

VLMとGrounding DINOを比較すると、今回の「前方進路がどの程度占有されているか」というタスクでは、以下の違いがありました。

観点 VLM中心の構成 Grounding DINO + 幾何ポリシー
物体の位置 テキスト説明中心で、位置表現が曖昧になりやすい bounding boxとして取得できる
進路ROIとの重なり 「中央」「近い」などの文章に依存する ROI交差面積・占有率として計算できる
path_blocked判定 人物や箱を認識しても進路clearと判断する場合がある 明示的な閾値とルールで決定できる
推論速度 Qwen 3Bで約4〜7秒、7Bでは約16〜19秒 GB10上のモデルforward inferenceはsub-secondを確認
再現性 プロンプトや生成結果により揺れる可能性がある 同じbbox・同じルールなら再現しやすい
テスト 期待結果を細かくunit testしにくい ROI、交差、union occupancy、policyをtest可能
説明可能性 なぜhighなのかを追跡しにくい場合がある bbox、ROI、occupancy、policy reasonを表示できる
向いている役割 場面説明、報告、未知物体の補助理解 ライブの進路リスクadvisory

この比較から、現在のライブ経路は以下の構成を採用しました。

Fast path:
  Grounding DINO
  → ROI geometry
  → deterministic policy
  → dashboard / advisory

Slow optional path:
  VLM / LLM
  → 日本語説明
  → レポート
  → 曖昧な物体や場面の補助理解

つまり、VLMは失敗として捨てたのではなく、速度・安定性・位置情報が重要なライブ判定経路から分離し、意味理解や説明に適した役割へ再配置したという設計です。


Grounding DINOと進路ROI

今回使用した物体検出モデルは以下です。

IDEA-Research/grounding-dino-tiny

初期プロンプトは以下のように設定しています。

person. box. chair. cart. door. robot. bag.

検出結果は、正規化されたbounding boxとして扱います。

{
  "label": "person",
  "score": 0.92,
  "bbox_normalized": [
    0.31,
    0.42,
    0.69,
    0.98
  ]
}

Go2前方カメラの画像に対して、下部中央を中心とした前方進路ROIを設定しました。

概念的には以下のような領域です。

画像上部:
  遠方や背景

画像中央:
  前方の空間

画像下部中央:
  Go2が直進する際に重要な近接進路領域

物体のbounding boxとこのROIを交差させることで、以下のような情報を計算します。

物体がROIに重なっているか
ROI内で物体が占める割合
物体が画像下部まで到達しているか
複数の物体が合計で進路をどの程度占有しているか

この構成では、リスク判定の理由を説明できます。

例えば、人物が下部中央の進路を大きく占有している場合は、以下のような結果になります。

{
  "risk_level": "high",
  "path_blocked": true,
  "primary_obstacle": "person",
  "aggregate_route_occupancy_ratio": 0.5948
}

複数物体のunion occupancy

単一物体だけを見ると、混在した障害物シーンを十分に扱えないことがありました。

例えば、複数の箱や物体が進路上にある場合、各bounding boxはmedium相当でも、合計すると直進が難しい場合があります。

そこで、複数bounding boxのROI内占有率を追加しました。

重要なのは、bounding boxの面積を単純に加算しないことです。

同じ物体に対して重複検出が発生した場合、単純加算すると占有率が過大評価されます。

そのため、ROI内のbounding box領域についてunion coverageを計算しています。

複数bbox
  ↓
ROI内で重なる領域をunion化
  ↓
ROI面積に対する占有率を計算
  ↓
aggregate_route_occupancy_ratio

現在の高リスク集約ルールは概ね以下です。

route-relevant objectが2個以上
かつ
aggregate_route_occupancy_ratio >= 0.35
  ↓
high / path_blocked=true

この処理により、複数箱のケースを補正できました。

Grounding DINO単体の暫定high-risk recall:
  6 / 10
  60%

multi-object aggregation追加後:
  7 / 10
  70%

ただし、この結果は12枚の小規模・high-risk-heavy・暫定ラベル付きデータセットに基づく途中結果です。

低リスクシーンの十分な評価や、安全性の証明を意味するものではありません。


Go2実カメラからDGX Sparkへの接続

今週は、Go2のフロントカメラ画像をDGX Spark上の推論環境へ接続しました。

全体構成は以下です。

Go2 front camera
  ↓
Unitree SDK2 Python / CycloneDDS
  ↓
DGX Spark host
  ↓
latest.jpg mailbox
  ↓
GPU container
  ↓
Grounding DINO + geometry policy
  ↓
Streamlit dashboard

Go2カメラ画像の取得には、Unitree SDK2 Pythonに含まれる公式のfront-camera sampleを利用しています。

python example/go2/front_camera/capture_image.py enP7s7

Go2から取得されたJPEGは、まずDGX Spark host側で確認しました。

その後、周期取得用のcapture loopを用意し、dashboard入力用フォルダに最新画像だけを保存する構成にしました。


latest.jpg mailbox設計

最初は、周期撮影ごとにtimestamp付きファイルを保存する方式を考えていました。

go2_front_20260723_100001.jpg
go2_front_20260723_100004.jpg
go2_front_20260723_100007.jpg
...

しかし、この方式には問題があります。

例えば、dashboardでSTOPを押してanalyzerだけを停止した場合でも、capture processが動き続けると画像が蓄積します。

その後、STARTを押すと過去画像がまとめて処理され、dashboardが現在の場面ではなく古い場面を表示する可能性があります。

そこで、入力を単一画像mailboxへ変更しました。

samples/local_dashboard_input/
  └── latest.jpg

capture loopでは、以下の手順で更新します。

official camera image
  ↓
.latest.jpg.tmpへコピー
  ↓
atomic replace
  ↓
latest.jpg

概念的には以下です。

shutil.copyfile(
    official_img_path,
    temp_path,
)

temp_path.replace(final_path)

これにより、analyzerが読みかけのJPEGを見る可能性を減らしつつ、常に最新の1枚だけを保持できます。

この設計の利点は以下です。

STOP中もraw image backlogが蓄積しない
START後は最新の場面だけを処理する
raw imageの保存量が増えない
実運用時のプライバシー・ストレージ負担を抑えやすい

日常の入力画像はlatest.jpgのみとし、履歴にはoverlay PNGとJSONを限定数だけ保存する方針にしました。


DGX Spark上のGPU推論

推論にはDGX SparkのNVIDIA GB10 GPUを使用しています。

Grounding DINO TinyをCUDAで実行し、実際のGo2フロントカメラ画像に対して以下を生成できました。

latest_result.json
latest_overlay.png
history/*.json
history/*_overlay.png

実機の連続入力では、モデルforward inferenceはおおむねsub-secondで動作しました。

ただし、画面上で新しい結果が見えるまでの時間は、モデル推論だけでは決まりません。

Go2 camera capture
  ↓
JPEG書き込み
  ↓
watcherの検知
  ↓
画像decode / preprocessing
  ↓
Grounding DINO inference
  ↓
geometry / JSON / overlay生成
  ↓
Streamlit refresh
  ↓
browser render

このため、inference_secだけでなく、captureからanalysis、dashboard表示までのend-to-end latencyを分けて考える必要があります。

今回の調整では、以下を短縮しました。

analyzer poll interval:
  0.5 sec
  ↓
  0.2 sec

dashboard status refresh:
  1.0 sec
  ↓
  0.5 sec

dashboard live panel refresh:
  1.0 sec
  ↓
  0.5 sec

この変更により、画面更新の体感速度が改善しました。


Streamlitダッシュボード

今回の成果の中で、特に重要なのがStreamlitダッシュボードです。

これは単に現在のリスクを表示する画面ではなく、今後のロボット認識・検証・運用プロジェクトのための共通可視化基盤として位置付けています。

メイン画面

以下は、実際のGo2フロントカメラ画像を解析中のメイン画面です。

GUIスクリーンショット 1
dashboard1_optimized_2000

左側には、最新の解析済みカメラ画像を表示しています。

赤い矩形はGrounding DINOが検出したbounding boxです。

緑の台形は、Go2が直進する際の前方進路ROIです。

右側には、オペレーターが短時間で確認できるよう、以下の情報を集約しています。

ADVISORY RISK
PATH BLOCKED
PRIMARY OBJECT
INFERENCE
ROUTE OCCUPANCY
OBJECTS USED
CAPTURED
ANALYZED

スクリーンショットでは、椅子などが検出され、進路ROIに部分的に重なっているため、以下のような状態が表示されています。

ADVISORY RISK:
  MEDIUM

PATH BLOCKED:
  NO

PRIMARY OBJECT:
  CHAIR

INFERENCE:
  0.237 sec

ROUTE OCCUPANCY:
  0.149

このように、物体が存在することと、直進経路が完全に遮断されていることを分けて確認できます。

画像内に物体が存在しても、ROI占有率や位置が閾値を超えなければ、必ずしもPATH BLOCKED=YESにはなりません。

上部のSTART、STOP、ENABLE AUDIO、STATUSでは、解析プロセスの起動状態を操作・確認できます。

詳細画面

DETAILS / SETTINGSを開くと、検出結果と解析プロセスの詳細を確認できます。

GUIスクリーンショット 2
dashboard2

左側のDETECTED OBJECTSには、各検出物体について以下を表示します。

label
score
normalized bounding box

これにより、画面上の赤い矩形だけではなく、推論結果そのものを確認できます。

また、GEOMETRY REASONには、Pythonポリシーが出した判断理由を表示します。

例:

A route-relevant object partially overlaps the immediate forward route.

これは、検出物体が直ちに完全遮断を意味するわけではないものの、現在の前方進路ROIと部分的に重なっていることを示します。

右側のDASHBOARD SETTINGSでは、以下を確認できます。

input directory
output directory
audio state
analyzer process state
recent analyzer log

Analyzer process: runningが表示されているため、dashboardから起動したanalyzerが動作中であることを確認できます。

下部のanalyzer logには、最新画像の処理履歴、リスクレベル、path_blocked、主要障害物、ROI占有率、推論時間などが出力されます。

この詳細画面は、単に可視化するためだけでなく、threshold調整、モデル比較、false positive / false negativeの確認、将来のG1 profile調整に利用できる開発・検証用の画面としても重要です。

準リアルタイムな可視化

今回のdashboardは、Go2から定期的に取得した最新画像を継続的に表示します。

Go2 camera
  ↓
latest.jpg更新
  ↓
analyzerが新しい画像を検知
  ↓
JSON / overlay更新
  ↓
dashboardが0.5秒周期で再表示

そのため、オペレーターはSSHログを確認し続けることなく、現在の検出結果と進路判定を画面上で確認できます。

これは動画ストリームを直接処理する意味でのリアルタイム解析ではありません。

ただし、周期撮影・GPU推論・短周期refreshを組み合わせることで、手動操作中の確認支援として十分に実用的な更新感を得られました。

今後のプロジェクトにおける検証ツールとしての価値

このdashboardは、今回のGo2進路リスク判定だけで終わるものではありません。

今後、以下のような機能を追加・検証する際の共通UIとして利用できます。

G1向けnear-field ROI profile
Depth / LiDARによる距離補助情報
SAMなどによるmask refinement
VLMによる非同期の場面説明
物体検出モデルの比較
thresholdやgeometry policyの比較
false positive / false negativeのreview
operator noteやevent history
G1 manipulation / beverage handoverの状態可視化

例えば、将来G1の飲料受け渡しタスクへ進む場合も、dashboardは以下のような状態を表示する基盤になります。

TASK STATE
TARGET OBJECT
GRASP STATUS
HANDOVER READY
OPERATOR CONFIRMATION
ABORT STATUS

このように、今回作成したdashboardは、Go2のinspection PoCであると同時に、今後のロボットAIプロジェクトを開発・観察・検証するためのツールにもなります。


リスクに応じた警告音

dashboardには、リスクレベルに応じて短いローカル警告音を出す機能も実装しました。

オペレーターは画面上のENABLE AUDIOを選択し、必要に応じてMUTEへ切り替えられます。

現在の通知方針は以下です。

low:
  安全
  risk stateがlowへ変化したときのみ通知

medium:
  注意
  state change時に即時通知
  mediumが継続する場合は約5秒間隔

high:
  危険
  state change時に即時通知
  highが継続する場合は約2秒間隔

unknown:
  確認
  state change時に即時通知
  unknownが継続する場合は約10秒間隔

同じ解析結果がdashboard refreshのたびに繰り返し発話されないよう、analyzed_unix_msを用いて新しい解析結果だけを判定対象にしています。

また、risk stateが変わった場合はdebounce時間を待たず、すぐに新しい状態を通知します。

low → high:
  危険

high → medium:
  注意

medium → low:
  安全

現在の実装では、DGX Spark上のLinux環境でespeak-ngを使用する構成です。

espeak-ng -v ja -s 165 "危険"

ただし、今回のデモ環境ではDGX Sparkに接続された利用可能なスピーカーを用意できなかったため、YouTubeデモでは実際の音声通知は使用していません。

音声機能の実装とdashboard上の有効化・ミュート切り替えは完了しています。

今後、HDMI音声、USBスピーカー、またはSpark hostの音声デバイスを接続できれば、現在のadvisory audioをそのまま利用できます。

重要なのは、この音声もあくまでオペレーター向けの注意喚起であり、Go2の停止や移動制御を行わないことです。


実装中に発生した問題

今回の実装では、モデル自体よりも、実際のruntime integrationで多くの課題がありました。

Streamlitの透明なUI layerがbuttonを覆う問題

最初はSTARTボタンが見えているにもかかわらず、クリックできない問題がありました。

原因はPython側ではなく、Streamlitの透明なheader / toolbar layerがbutton上に残っていたことでした。

見た目は透明でも、browser上ではpointer eventを受け取るため、ボタンまでclickが届きません。

以下のような対応を行いました。

header / toolbar / Deploy / decorationを非表示
pointer-events: none
dashboard buttonにz-indexとpointer-events: autoを指定

この問題は、dashboardの見た目だけでは分かりにくく、browser上のUI layerやz-indexを確認する必要がありました。

Docker containerのUIDとPyTorch cacheの問題

GPU containerをhost userのUIDで実行した際、PyTorch内部がユーザー情報を取得できず、analyzerが起動直後に終了する問題もありました。

KeyError:
  getpwuid(): uid not found

これはcontainer内にhost UIDのpasswd entryが存在しないことが原因でした。

最終的には、container runtimeでhostのuser/group情報をread-only mountすることで解決しました。

また、TorchInductor cacheとHugging Face cacheもcontainer内で書き込み可能な場所へ指定しています。

これはロボット認識アルゴリズムとは直接関係ありませんが、実機・GPU・container・dashboardを接続する際には重要な運用上の課題でした。


現在の結果と限界

今回のGo2 v1では、以下の状態まで到達しました。

Go2実カメラ
  ↓
Unitree SDK2 / DDS read-only capture
  ↓
DGX Spark latest.jpg mailbox
  ↓
GB10 CUDA Grounding DINO
  ↓
決定的ROI geometry policy
  ↓
JSON / overlay
  ↓
live Streamlit dashboard

現在の可視化は非常に分かりやすく、人物、ドア、箱、椅子などが進路ROIに入った際に、オペレーターが根拠を確認しながら判断できます。

一方で、以下の限界は残っています。

mixed lower-center clutter
bag
cable
floor equipment
promptに含まれない物体
検出bboxが不安定な場面

また、現在の評価データは小規模で、high-riskケースに偏っています。

したがって、現時点で言えることは以下です。

実機Go2入力に対するgrounded visual advisory prototypeとして動作した

ただし、
安全認証済みの衝突回避システムではない

今後は、low / medium / highをよりバランスよく含むcontrolled datasetを作成し、false positiveやfalse negativeを評価する必要があります。


G1について

今回はG1の実機検証までは進めませんでした。

これは未完了というよりも、Go2向けの前方進路ROIを、そのままG1へ流用しないための判断です。

G1はカメラ位置や視野、近接領域の見え方がGo2と異なる可能性があります。

そのため、次の段階では以下を用意する予定です。

go2_forward_route_v1
g1_near_field_v0

G1向けROIは、まずnear-field inspection用の暫定profileとして扱い、controlled image collection後に調整します。

G1についても、現時点ではread-only camera inspectionを対象とし、歩行・腕・把持・VLA executionには接続しません。


まとめ

今週は、先週のVLM中心PoCを、実機Go2カメラとDGX Spark GPUを利用したGrounded Multimodal Robot Inspection Agentへ発展させました。

実現できた主な内容は以下です。

Go2実フロントカメラ画像のread-only取得
Unitree SDK2 Python / CycloneDDSによる実機カメラ連携
DGX Spark GB10 CUDA上のGrounding DINO推論
bounding boxと進路ROIによる幾何リスク判定
multi-object union occupancy
latest.jpg mailboxによるbacklog回避
JSON / overlay生成
START / STOP可能なStreamlit dashboard
Go2実画像の継続的な可視化
リスク状態に応じたローカル警告音の実装
将来の検証プロジェクトにも利用可能なGUI基盤

今回の大きな学びは以下です。

「進路リスクのように位置と占有率が重要な問題では、VLMの文章出力だけに依存するよりも、Grounding DINOによる位置情報と決定的な幾何ポリシーを分離した方が、速度・再現性・説明可能性を高めやすい。」

現時点で、Go2 v1としては実機カメラ、GPU推論、ダッシュボードを接続した十分に展示可能なプロトタイプになりました。

次の段階では、現在のGo2 v1をより安定させます。

balanced controlled datasetの作成
capture-to-analysis latencyの計測
mixed clutterなどhard caseの分析
false positive / false negativeの記録
thresholdとgeometry policyの安定化
Spark上の音声出力デバイス確認

その後、Go2と異なるカメラ位置・視野を持つG1へ適応するため、G1用ROI profileの設計とcontrolled calibrationを進める予定です。

Go2の安定性向上とG1への適応が進めば、今回のinspection agent v1は一区切りとし、次のロボットAIプロジェクトへ進むための基盤として利用できます。

なお、本システムは手動操作中の確認支援を目的としたadvisory-only prototypeです。

検出器、幾何ポリシー、dashboard、音声通知の出力は、Go2またはG1の移動制御には接続していません。

この記事をシェアする

関連記事