Unitree Go2とG1を活用したロボット目視検査エージェント:MVPを作り始めました

Unitree Go2とG1を活用したロボット目視検査エージェント:MVPを作り始めました

Week 1 progress of the Robot Visual Inspection Agent project!
2026.07.09

はじめに

今回のインターンでは、Unitree Go2とG1を活用したロボット目視検査エージェント、いわゆるRobot Visual Inspection Agentの開発に取り組んでいます。

一夜のブレインストーミングした内容ですが、Atsuさんからポジティブなレビューをいただきました。
そのフィードバックをもとに、テーマの方向性を少し整理しました。

この記事では、現時点で考えているテーマ案と、本週実装した最初のMVPスケルトンについてまとめます。

Go2 (1)


取り組みたいテーマ

Robot Visual Inspection Agent

目的は、実機ロボットのカメラ画像を使って、オフィスや施設内の状況を確認し、VLM/LLM によって以下の処理を行う PoC を作ることです。

  • シーン理解
  • 異常検知
  • リスク判断
  • 構造化JSON出力
  • 日本語 / 英語レポート生成

全体の流れとしては、以下のようなパイプラインを想定しています。

Unitree Go2/G1 のカメラ画像

VLMによるシーン理解

構造化JSON出力

LLMによる日本語/英語レポート生成

Markdown/Slack等で出力

想定ユースケース

例えば、以下のような指示を想定しています。

Go2で入口周辺を確認して、異常があれば報告してください。

この場合、VLM / LLM によって以下のような検査結果を出力するイメージです。

{
  "robot": "Go2",
  "location": "office entrance",
  "status": "warning",
  "observations": [
    "通路付近に段ボール箱が置かれている",
    "通行の妨げになる可能性がある"
  ],
  "risk_level": "medium",
  "recommended_action": "通路の妨げになっていないか確認し、必要であれば箱を移動する",
  "summary_ja": "入口付近の通路に段ボールが置かれており、通行の妨げになる可能性があります。確認を推奨します。"
}

単に「画像に何が写っているか」を説明するだけでなく、
施設点検・安全確認の観点から、状況を判断してレポート化することを目指しています。


MVPの範囲

最初から自律移動やリアルタイム動画解析までは狙わず、まずは最小構成のMVPを作ります。

今回のMVPでは、以下のパイプラインを動かすことを目標にしています。

画像入力

VLM解析

JSON化

レポート生成

まずはローカル画像でパイプラインを構築し、その後 Go2 で取得した画像に接続する方針です。


技術構成案

現時点で考えている技術構成は以下です。

項目 内容
Robot 前半は Unitree Go2、後半は Unitree G1 への対応を検討
Input Go2 / G1 のカメラ画像、またはローカル画像ファイル
VLM 会社で利用可能な承認済みモデルを確認して選定
LLM JSONから日本語 / 英語レポートを生成
Output JSON、Markdown report、必要に応じて Slack notification
Evaluation 30枚程度の画像で、JSON妥当性・異常検知精度・リスクレベル一致率・失敗例を評価

期待する成果物

最終的には、以下の成果物を残したいと考えています。

  • 画像 → VLM → JSON → レポート生成の動作するPoC
  • Go2で取得した画像を使ったデモ
  • サンプル画像と評価結果
  • 失敗例・改善点の整理
  • セットアップ手順と技術ドキュメント

今週できたこと

今週は、ロボット目視検査エージェントのアイデア整理から始めて、最終的にローカルVLMを使った実画像評価まで動くPoCまで進めることができました。

最初は、Unitree Go2/G1のカメラ画像を使って、オフィスや施設内の状況を確認し、VLM/LLM によって異常検知・リスク判断・レポート生成を行う構成を検討しました。

その後、いきなり実機連携やリアルタイム処理に進むのではなく、まずは以下のような最小構成のMVPを作る方針にしました。

画像入力

VLM解析

構造化JSON出力

スキーマ検証

Markdownレポート生成

主に実装・検証した内容は以下です。

  • プロジェクトのリポジトリ構造を作成
  • 検査結果用のPydanticスキーマを定義
  • モックVLMクライアントを実装
  • Markdownレポートジェネレータを実装
  • 入力画像からJSONとMarkdownレポートを生成するCLIを実装
  • 初期検査プロンプトを作成
  • READMEとセットアップ手順を整理
  • Brev / Docker環境でローカルVLM実行環境を構築
  • SmolVLM2-2.2B-Instruct を検証
  • Qwen2.5-VL-3B-Instruct を検証
  • Qwen2.5-VLを qwen_local backend としてCLIに統合
  • 検査用途に合わせてプロンプトを改善
  • JSON repair / alias normalization を一部実装
  • iPhone低角度画像を使った実画像評価を開始
  • evaluation/labels.json を作成
  • 失敗例と改善点を整理

現時点では、以下のパイプラインがエンドツーエンドで動作しています。

画像入力

ローカル Qwen VLM 推論

構造化 JSON 出力

InspectionResult スキーマ検証

Markdown レポート生成

今週の大きな成果は、単にモデルを動かしたことではなく、
画像入力からレポート生成までのアプリケーションとしての骨格を作り、さらに実画像で失敗例まで確認できたこと だと思っています。


MVPスケルトンの実装

まず、最初のステップとして、ロボット目視検査エージェントのMVPスケルトンを実装しました。

リポジトリには、主に以下のような構成を用意しました。

robot_inspection_agent/
├── app/
│   └── cli.py
├── evaluation/
│   └── labels.json
├── outputs/
├── reports/
│   └── generate_report.py
├── samples/
│   └── real_tests/
├── vlm/
│   ├── clients/
│   │   ├── base.py
│   │   ├── mock_client.py
│   │   └── qwen_local_client.py
│   ├── prompts/
│   │   ├── inspection_prompt_v1.md
│   │   └── inspection_prompt_v2.md
│   └── schema.py
├── README.md
├── requirements.txt
└── requirements-vlm.txt

この段階では、まずVLMそのものの精度よりも、アプリケーション全体の流れを確認することを優先しました。

そのため、最初にmock_client.pyを実装し、固定の検査結果を返すモックVLMで以下の流れを確認しました。

画像パスをCLIに渡す

mock backend が検査結果を返す

InspectionResult スキーマで検証する

JSONとして保存する

Markdownレポートを生成する

これにより、VLM backend を差し替えても、CLI・スキーマ・レポート生成部分は共通で使える構成にできました。


検査結果スキーマとレポート生成

今回のPoCでは、VLMに自由形式の文章を出力させるのではなく、構造化JSONを出力させる方針にしました。

理由は、後続処理で以下を行いやすくするためです。

  • スキーマ検証
  • リスクレベルの比較
  • 評価データとの照合
  • Markdownレポート生成
  • 将来的なSlack通知やダッシュボード連携

検査結果はInspectionResultとして定義し、例えば以下のような情報を持たせています。

scene_type
image_quality
detected_objects
human_presence
door_state
obstacles
potential_hazards
path_blocked
risk_level
recommended_action
confidence
summary_ja

Markdownレポートジェネレータでは、この構造化JSONをもとに、人間のオペレーターが読みやすい形の日本語レポートを生成します。

この時点で、アプリケーションとしては以下の役割分担が見えてきました。

VLM: 画像を見て構造化情報を出す
Schema: 出力形式を検証する
Normalizer: 揺れた出力を整える
Report Generator: 人間向けにレポート化する
CLI: 全体をつなぐ

ローカルVLM backendの検証

MVPスケルトンが動いた後、Brev / Docker環境でローカルVLMの検証を行いました。

まず、Isaac SimコンテナがNVIDIA L40S GPUにアクセスできることを確認し、VLM実験用のPython仮想環境を作成しました。

その後、以下のようなパッケージをインストールしました。

  • PyTorch
  • Transformers
  • Hugging Face Hub
  • その他VLM実行に必要な依存パッケージ

最初に軽量モデルとしてSmolVLM2-2.2B-Instructを試しました。

9E94E1FB-C81B-4F93-87F2-A6B63B79E799

SmolVLM2はローカルで正常にロードでき、画像から出力を生成することもできました。
また、JSONらしい出力も得られました。

一方で、検査タスクとして見ると、推論能力には課題がありました。

例えば、ドアやボックスなどの物体は認識できても、
通路上の箱を「障害物」や「ハザード」として扱う判断はあまり安定しませんでした。

その後Qwen2.5-VL-3B-Instructを検証しました。

C45B35CF-F37F-46BF-9930-311CC354AB72

Qwen2.5-VLは、SmolVLM2と比較して以下の点で良い結果が出ました。

  • 構造化JSONを比較的安定して出力できる
  • 日本語要約が生成できる
  • 画像内の主要な物体をある程度認識できる
  • プロンプト改善に対する反応が良い

そのため、現時点ではQwen2.5-VL-3B-Instructをqwen_localbackendとしてメインCLIに統合しています。


プロンプト改善で分かったこと

初期プロンプトでは、Qwenは画像内の物体を認識できても、検査用途としては期待と異なる判断をすることがありました。

例えば、通路上に箱がある画像でも、単に「boxがある」と説明するだけで、障害物として扱わないケースがありました。

また、recommended_actionに以下のようなロボット制御寄りの内容が出ることもありました。

ロボットは別の経路に移動してください

しかし、今回作りたいのはロボットを直接制御するエージェントではなく、
人間のオペレーターに状況を報告する目視検査エージェントです。

そのため、プロンプトを以下のように調整しました。

  • ロボットを制御する指示を出さない
  • 人間 / オペレーター向けの推奨アクションを出す
  • 通路上の物体は障害物候補として扱う
  • 経路が塞がれているかを明示的に判断する
  • 通常の家具は、通路を塞いでいない限り障害物扱いしない
  • 床のゴミやケーブルなどを根拠なく幻覚しない
  • JSONのみを出力する
  • 指定したフィールド名を変更しない

改善後のプロンプトでは、通路上の箱を障害物として扱い、以下のような判断ができるようになりました。

{
  "path_blocked": true,
  "risk_level": "high",
  "recommended_action": "通路上の箱を確認し、必要に応じて撤去してください。"
}

この検証を通じて、VLMを検査エージェントとして使う場合、モデル性能だけでなく、
タスク定義・出力形式・推奨アクションの立場を明確にすることが重要だと分かりました。


実画像評価の開始

週の後半では、合成画像やサンプル画像だけでなく、実際に撮影した室内画像を使った評価も始めました。

本来はGo2のカメラ画像を使いたいところですが、一人で作業している状態でDDS/ROS/実機カメラ連携を無理に進めるのは避けました。
そのため、まずは安全な代替手段として、iPhoneを低い位置に構えて、Go2視点に近い画像を撮影しました。

この手動撮影画像は、以下のメタデータで扱っています。

image_source = manual_capture
camera_source = phone_low_angle_go2_like

撮影した画像は以下です。

manual_entrance_clear_001.jpg
manual_lab_clear_001.jpg
manual_lab_obstacle_001.jpg
manual_seat_clear_001.jpg
manual_seat_obstacle_001.jpg

画像はGitにはコミットせず、Brev環境に転送し、Dockerコンテナ内で評価しました。

ローカルQwen backendを使うCLIの実行例は以下です。

python app/cli.py \
  --image samples/real_tests/manual_entrance_clear_001.jpg \
  --robot Go2 \
  --location "office entrance" \
  --image-source manual_capture \
  --camera-source phone_low_angle_go2_like \
  --vlm qwen_local

この段階で、ローカルQwen推論からJSON生成、スキーマ検証、Markdownレポート生成までを実画像で確認できました。


実画像で確認できた結果

実画像評価では、いくつかの良い結果と失敗例が見えてきました。

入口画像

入口の画像では、Qwenはおおむね正しくシーンを理解できました。

Raw outputの一部は以下です。

{
  "scene": "entrance",
  "quality": "clear",
  "objects_detected": ["glass door"],
  "human": false,
  "door_status": "closed",
  "floor_condition": "wooden",
  "potential_risks": ["no specific hazards observed"],
  "obstructions": ["none"],
  "recommended_actions": [
    "Ensure the door is properly locked when leaving the building."
  ],
  "confidence_score": 0,
  "status_message": "The area appears safe and ready for entry."
}

入口であること、ガラスドアがあること、通路が塞がれていないことは認識できていました。

一方で、期待しているスキーマとは異なるフィールド名が使われていました。

例えば、期待するscene_typeの代わりにscene
image_qualityの代わりにquality
potential_hazardsの代わりにpotential_risksが使われるケースがありました。

また、障害物がないことを["none"]のようなリストで表現することもありました。

このため、VLM出力をそのままスキーマに渡すのではなく、alias normalizationと list cleanupが必要になります。


ラボ画像

ラボまたはストレージに近い画像では、Qwenは多くの物体を検出できました。

{
  "objectsDetected": [
    "floor",
    "cabinets",
    "cart",
    "box",
    "plastic bag",
    "air purifier",
    "monitor",
    "signboard"
  ],
  "obstacles": ["cabinet"],
  "potentialHazards": ["cable on floor"],
  "pathBlocked": true,
  "riskLevel": "medium"
}

物体検出自体はある程度できていました。

一方で、明確に危険とは言い切れない画像に対して、cable on floor を幻覚したり、通常のキャビネットを障害物として扱ったりするケースがありました。

この結果から、Qwenは物が多いが通行可能なシーンに対して、リスクをやや過大評価する傾向があると感じました。


座席周辺の障害物画像

座席周辺にケーブルが見える画像では、Qwenはケーブルを危険要素として認識できました。

{
  "scene": "office",
  "quality": "clear",
  "objects_detected": ["yellow chair", "desk", "monitor"],
  "obstacle": ["cable on floor"],
  "hazard": ["cables on floor"]
}

ただし、このケースでもobstaclehazardのように、期待するフィールド名とは異なる名前で出力されました。

期待するフィールド名は以下です。

obstacles
potential_hazards

このような出力の揺れに対応するため、後処理側でaliasを増やす必要があります。


実画像評価から見えた改善ポイント

実画像評価を通じて、今後改善すべき点がかなり明確になりました。

大きく分けると、課題は以下の3つです。

1. スキーマの揺れ

CB43597C-C385-43F1-96FD-BFB6A8368A91

Qwenは、同じ意味の項目でも異なるフィールド名を使うことがあります。

例:

scene
sceneType
quality
imageUrlQuality
objectsDetected
objects_detected
human
humanPresence
door_status
doorState
obstacle
hazard
potentialHazards
potential_risks
pathBlocked
riskLevel
recommendedAction
recommended_actions
confidence_score
summaryJa
status_message

プロンプトで「指定したキーを使ってください」と指示しても、完全には守られませんでした。

そのため、prompt engineeringだけでなく、後処理でのalias normalizationが必要です。


2. JSON形式の崩れ

82B1C181-B56E-473C-B2E0-5C1EC28486CC

VLMは、JSONのみを出力するように指示しても、以下のような形式崩れを起こすことがあります。

Markdown fences が含まれる
JavaScript-style comments が含まれる
trailing commas が含まれる
single quotes が使われる
必須フィールドが欠ける

このため、JSON repairの処理も強化する必要があります。


3. リスク判断の不安定さ

20FE1773-BAD2-4491-AF62-B132D17EC951

画像理解そのものはある程度できている一方で、検査タスクとしての判断にはまだ不安定さがあります。

例えば、以下のような傾向がありました。

  • 通常の家具を障害物として扱う
  • 明確でないケーブルや床のゴミを幻覚する
  • 物が多いだけの部屋を過度に危険と判断する
  • プロンプトを厳しくすると、逆に危険を見逃すことがある
  • confidence: 90 のように、0〜1ではなく0〜100スケールで返すことがある

このあたりは、今後評価データを増やしながら、プロンプト・正規化・評価ルールの3つを組み合わせて改善していく必要があります。


次に入れる修正

次の実装では、まず_normalize_parsed_json()のalias mapを強化する予定です。

追加したいaliasは以下です。

alias_map = {
    "scene": "scene_type",
    "human": "human_presence",
    "human_detection": "human_presence",
    "imageUrlQuality": "image_quality",
    "image_url_quality": "image_quality",
    "summaryJa": "summary_ja",
    "summaryJA": "summary_ja",
    "status_message": "summary_ja",
    "statusMessage": "summary_ja",
    "obstacle": "obstacles",
    "hazard": "potential_hazards",
    "potential_risks": "potential_hazards",
    "potentialRisks": "potential_hazards",
    "recommended_actions": "recommended_action",
    "recommendedActions": "recommended_action",
    "confidence_score": "confidence",
    "confidenceScore": "confidence"
}

また、以下のような「値なし」を意味する文字列をリストから除去する処理も追加したいです。

no_value_strings = {
    "none",
    "n/a",
    "na",
    "null",
    "unknown",
    "no",
    "nothing",
    "no hazards",
    "no hazard",
    "no specific hazards observed",
    "no specific hazard observed",
    "no obstacles",
    "no obstacle",
    "not visible"
}

例えば、以下のような出力は、

{
  "obstacles": ["none"],
  "potential_hazards": ["no specific hazards observed"]
}

最終的には以下のように正規化したいです。

{
  "obstacles": [],
  "potential_hazards": []
}

現在のボトルネック

現在のCLIでは、画像を1枚処理するたびにQwenモデルをロードしています。

そのため、複数画像を評価する場合は実行時間が長くなります。

今後は、以下のようなbatch runnerを作りたいです。

QwenLocalVLMClientを1回だけ初期化

複数画像を順番に処理

JSON / Markdown / evaluation resultを保存

例えば、app/batch_run.pyやevaluation runnerを追加し、5枚または30枚程度の評価画像をまとめて処理できるようにしたいです。


次のステップ

次に取り組む予定は以下です。

  1. alias mappingを追加する
  2. ["none"]["no specific hazards observed"]のようなリスト値を空配列に正規化する
  3. recommended_action がlistで返ってきた場合に文字列へ変換する
  4. summary_jaのaliasを追加する
  5. 5枚の実画像を再実行する
  6. 正規化後のJSONを記録する
  7. evaluation tableを作る
  8. batch runnerを追加する
  9. メンターやチームのサポートがあるタイミングでGo2カメラ連携を試す

評価テーブルでは、以下のような項目を比較する予定です。

image expected scene expected path_blocked expected risk actual scene actual path_blocked actual risk judgment notes
manual_entrance_clear_001 entrance false low entrance false low OK フィールド名のalias対応が必要
manual_lab_clear_001 lab false low lab true medium NG ケーブルを幻覚、キャビネットを障害物扱い
manual_seat_obstacle_001 office true medium office false low NG obstacle / hazard のalias未対応

まとめ

今週は、ロボット目視検査エージェントのテーマ検討から始めて、実際に動作するローカルVLM PoCまで進めることができました。

具体的には、CLI、Pydanticスキーマ、モックVLM、Markdownレポート生成、ローカルQwen backendを実装し、画像入力からJSON生成、スキーマ検証、レポート生成までの一連の流れを構築しました。

また、SmolVLM2とQwen2.5-VLを比較し、現時点ではQwen2.5-VL-3B-Instructの方が今回の検査用途に向いていそうだと分かりました。

今日は、iPhoneで撮影した低い視点の実画像を使って評価を始めました。
その結果、パイプラインが実画像でも動作することを確認できた一方で、スキーマの揺れ、JSON形式の崩れ、リスク判断の不安定さといった課題も見えてきました。

現時点の結論は以下です。

「画像入力からレポート生成までのローカルVLMパイプラインは動作する。
一方で、実用的な目視検査エージェントにするには、
prompt engineering だけでなく、
JSON repair / alias normalization / evaluation が重要。」

次は、正規化処理を強化した上で実画像を再評価し、評価テーブルを作成して、失敗例と改善方針をより具体的に整理していきます。

この記事をシェアする

関連記事