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

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

Week1 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 が重要。」

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

この記事をシェアする

関連記事