
Unitree Go2とG1を活用したロボット目視検査エージェントのMVPを作り始めました
はじめに
今回のインターンでは、Unitree Go2とG1を活用したロボット目視検査エージェント、いわゆるRobot Visual Inspection Agentの開発に取り組んでいます。
一夜のブレインストーミングした内容ですが、Atsuさんからポジティブなレビューをいただきました。
そのフィードバックをもとに、テーマの方向性を少し整理しました。
この記事では、現時点で考えているテーマ案と、本週実装した最初のMVPスケルトンについてまとめます。

取り組みたいテーマ
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_localbackend として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を試しました。

SmolVLM2はローカルで正常にロードでき、画像から出力を生成することもできました。
また、JSONらしい出力も得られました。
一方で、検査タスクとして見ると、推論能力には課題がありました。
例えば、ドアやボックスなどの物体は認識できても、
通路上の箱を「障害物」や「ハザード」として扱う判断はあまり安定しませんでした。
その後Qwen2.5-VL-3B-Instructを検証しました。

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"]
}
ただし、このケースでもobstacleやhazardのように、期待するフィールド名とは異なる名前で出力されました。
期待するフィールド名は以下です。
obstacles
potential_hazards
このような出力の揺れに対応するため、後処理側でaliasを増やす必要があります。
実画像評価から見えた改善ポイント
実画像評価を通じて、今後改善すべき点がかなり明確になりました。
大きく分けると、課題は以下の3つです。
1. スキーマの揺れ

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形式の崩れ

VLMは、JSONのみを出力するように指示しても、以下のような形式崩れを起こすことがあります。
Markdown fences が含まれる
JavaScript-style comments が含まれる
trailing commas が含まれる
single quotes が使われる
必須フィールドが欠ける
このため、JSON repairの処理も強化する必要があります。
3. リスク判断の不安定さ

画像理解そのものはある程度できている一方で、検査タスクとしての判断にはまだ不安定さがあります。
例えば、以下のような傾向がありました。
- 通常の家具を障害物として扱う
- 明確でないケーブルや床のゴミを幻覚する
- 物が多いだけの部屋を過度に危険と判断する
- プロンプトを厳しくすると、逆に危険を見逃すことがある
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枚程度の評価画像をまとめて処理できるようにしたいです。
次のステップ
次に取り組む予定は以下です。
- alias mappingを追加する
["none"]や["no specific hazards observed"]のようなリスト値を空配列に正規化するrecommended_actionがlistで返ってきた場合に文字列へ変換するsummary_jaのaliasを追加する- 5枚の実画像を再実行する
- 正規化後のJSONを記録する
- evaluation tableを作る
- batch runnerを追加する
- メンターやチームのサポートがあるタイミングで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 が重要。」
次は、正規化処理を強化した上で実画像を再評価し、評価テーブルを作成して、失敗例と改善方針をより具体的に整理していきます。








