
Unitree Go2とG1を活用したロボット目視検査エージェント:処理速度の最適化とG1カメラ互換性検証によるPoCの完結
はじめに
前回までに、Unitree Go2のフロントカメラ画像を入力として、Grounding DINO Tinyによる物体検出、進路ROIとの幾何計算、決定的なPythonポリシー、Streamlitダッシュボードを組み合わせた目視検査エージェントを構築しました。
現在の基本構成は以下です。
Go2フロントカメラ
↓
read-only camera capture
↓
latest.jpg mailbox
↓
Grounding DINO Tiny
↓
bounding box
↓
前方進路ROIとの幾何計算
↓
deterministic risk policy
↓
JSON / overlay
↓
Streamlit dashboard
このシステムは、手動操作中のオペレーターが前方の状況を確認するためのadvisory-only prototypeです。
Go2やG1の移動、停止、腕、手、graspなどのrobot controlには一切接続していません。
今回の記事では、以下の2点を中心にまとめます。
- Inspection pipelineの処理速度をどのように分析・最適化したか
- G1でもread-only camera imageを取得し、既存のGrounding DINO analyzerへ入力できることを確認したこと
最終的に、Go2では最新画像のmailbox更新を約3〜4秒ごとから約1秒ごとへ改善できました。また、G1では同じVideoClient互換経路からJPEG画像を取得し、既存のanalyzerで画像を処理できることを確認しました。
Go2デモ動画
以下にGo2の最終デモ動画を掲載します。
今週できたこと
今週は主に以下を実装・検証しました。
- analyzer処理をstage単位で計測できるように改善
- full-resolution PNG overlay出力が大きなボトルネックであることを特定
- dashboard表示用overlayを最大幅1280pxへ縮小し、PNG出力コストを最適化
- persistent analyzerにおける処理時間を計測
- Go2 camera captureを、単発process起動方式からpersistent VideoClient capture loopへ変更
- Go2の
latest.jpg更新間隔を約3〜4秒から約1秒へ改善 latest.jpgmailbox、CUDA analyzer、dashboardを含む実機Go2 pipelineを再検証- G1 Jetson上でcamera sourceを調査
- G1の
/frontvideostreamtopicと既存camera関連実装を確認 - G1でGo2 VideoClient互換経路からJPEG画像をread-onlyで取得
- G1 camera imageを既存のGrounding DINO analyzerへ入力し、detector / overlayが動作することを確認
今回も、Go2およびG1の移動、腕、手、grasp、sport APIなどのmovement commandは使用していません。
analyzerの処理時間を分解する
最初は、Grounding DINO Tinyのmodel forwardが最も時間を使っていると考えていました。
しかし、実際にanalyzerの処理を以下のstageへ分解して計測すると、別のボトルネックが見えてきました。
image load
preprocessing
model forward
post-process
object conversion
geometry policy
overlay write
JSON output
CUDA処理は非同期で実行されるため、model forwardだけをCPU側の時計で測ると、実際より短く見える可能性があります。
そのため、model forward前後でCUDA synchronizationを行い、より実態に近い時間を記録するようにしました。
torch.cuda.synchronize()
forward_start = time.perf_counter()
with torch.inference_mode():
outputs = model(**inputs)
torch.cuda.synchronize()
model_forward_sec = time.perf_counter() - forward_start
この計測により、推論だけでなく、画像読み込み、前処理、post-process、overlay出力などを比較できるようになりました。
想定外のボトルネック:full-resolution PNG overlay
初期のoffline benchmarkでは、以下の結果になりました。
model_forward_sec:
1.8079 sec
overlay_write_sec:
1.9481 sec
total_analyze_sec:
3.9458 sec
Grounding DINO Tinyのmodel forwardよりも、1920×1080の画像にbboxとROIを描画し、PNGとして保存する処理の方が時間を使っていました。
この結果から、モデルを変更する前に、まず表示用outputを最適化する方が効果的だと判断しました。
重要なのは、risk判定に使う画像・bbox・ROI・threshold・geometry policyは変更しないことです。
変更対象は、あくまでdashboardに表示するoverlay imageだけです。
dashboard overlayの最適化
最適化では、以下の方針を採用しました。
判定用画像:
元解像度を維持
Grounding DINO input:
変更なし
bounding box:
変更なし
ROI geometry:
変更なし
risk policy:
変更なし
dashboard表示用overlay:
bboxとROIを元画像に描画後、
最大幅1280pxへ縮小してPNG保存
つまり、物理的なリスク判断に関わる部分には触れず、operator-facing display outputだけを軽量化しました。
最適化後のsingle-image benchmarkでは以下の結果になりました。
model_forward_sec:
1.2296 sec
overlay_write_sec:
0.1480 sec
total_analyze_sec:
1.5777 sec
overlay writeは約1.95秒から約0.15秒まで削減されました。
1.9481 sec
↓
0.1480 sec
これは約92%の削減です!
ただし、このsingle-image benchmarkは、新しく起動したanalyzerが最初の画像を処理した結果です。
CUDA context initialization、GPU memory allocation、kernel selectionなどのwarm-upコストが含まれるため、通常のpersistent runtimeの代表値としては扱いません。
persistent analyzerのwarm performance
同じanalyzer processを維持した状態で複数画像を処理するwarm benchmarkでは、以下の値を確認しました。
model forward:
approximately 0.3356 sec
image load:
approximately 0.0646 sec
preprocessing:
approximately 0.0285 sec
post-processing:
approximately 0.0606 sec
overlay write:
approximately 0.1393 sec
analyzer processing before result publication:
approximately 0.6507 sec
この結果から、persistent analyzerの内部処理は約0.65秒まで改善できていました。
一方、保存済みの実機Go2 dashboard結果では、以下の値も確認できています。
Inference:
0.235 sec
Capture to Analysis:
0.775 sec
Capture to Analysisには、mailbox更新後のanalyzer検知、画像decode、前処理、model forward、geometry、overlay、JSON出力が含まれます。
browser renderingやオペレーターの反応時間は含まれません。
ここで重要なのは、analyzerの処理を高速化しても、camera imageそのものが数秒ごとにしか供給されなければ、dashboardは滑らかには見えないということです。
次のボトルネック:Go2 camera image supply
analyzerの処理時間を改善した後、Go2実機pipelineでdashboardを確認すると、結果は早く出るものの、画面はまだ数秒ごとに更新されるように見えました。
原因を調べると、従来のcapture pathは以下のような構造でした。
Python process起動
↓
ChannelFactoryInitialize
↓
VideoClient.Init
↓
GetImageSampleを1回実行
↓
JPEG出力
↓
process終了
↓
次の周期で再起動
capture intervalを1秒へ変更しても、実際のlatest.jpg更新は約3〜4秒ごとでした。
これは、設定したsleep intervalだけでなく、毎回のPython process起動、DDS初期化、VideoClient初期化、単発camera requestの時間が含まれるためです。
persistent VideoClient capture loop
そこで、Go2 camera captureをpersistent processへ変更しました。
新しい構成は以下です。
ChannelFactoryInitialize
↓
VideoClient.Init
↓
while loop
↓
GetImageSample
↓
JPEG validation
↓
.latest.jpg.tmp
↓
atomic replace
↓
latest.jpg
初期化を1回だけ行い、その後は同じVideoClientを使って繰り返しGetImageSample()を実行します。
概念的には以下のような構成です。
ChannelFactoryInitialize(0, interface)
client = VideoClient()
client.SetTimeout(3.0)
client.Init()
while True:
code, data = client.GetImageSample()
if code == 0:
write_latest_jpeg_atomically(data)
このcapture loopは、Go2のmovement APIを呼び出しません。
実行するのはfront camera image requestと、host上のlatest.jpg書き込みだけです。
また、入力ディレクトリにはtimestamp付きraw imageを蓄積せず、常に最新の1枚だけを保持します。
Go2の画像更新間隔を約1秒へ改善
persistent VideoClient capture loopを実機Go2で検証した結果、latest.jpgの更新は__約1秒__ごとになりました。
former repeated one-shot capture:
approximately every 3–4 sec
persistent VideoClient capture:
approximately every 1 sec
初回取得時には、以下のような軽い処理時間も確認できました。
GetImageSample request:
approximately 0.009 sec
mailbox publish:
approximately 0.0125 sec
もちろん、実際のcamera frame availabilityやDDS通信状態によって変動する可能性はあります。
しかし、以前のように毎回Python processとVideoClientを立ち上げ直す方式と比べ、dashboardの更新感は明らかに改善しました。
現在のGo2 pipelineは以下のようになります。
Go2 front camera
↓
persistent read-only VideoClient capture loop
↓
latest.jpg mailbox
↓
Grounding DINO Tiny on CUDA
↓
deterministic ROI geometry
↓
JSON / overlay
↓
Streamlit dashboard
本記事でいう約1秒の更新は、periodic still-image mailboxの更新間隔です。
連続video streamを直接解析しているわけではありません。
G1 camera compatibility smoke test
Go2の処理速度改善に加えて、G1 camera compatibilityも確認しました。
最初にG1 Jetson上でcamera関連の既存実装を調査しました。
確認できた前方camera topicは以下です。
ROS 2 topic:
/frontvideostream
message type:
unitree_go/msg/Go2FrontVideoData
既存projectのdocumentationでは、このtopicにはH.264 encoded video streamが含まれることが記録されていました。
一方で、現在のG1環境では、Go2 SDKのVideoClient互換経路も実際に動作することを確認できました。
ChannelFactoryInitialize(0, "eth0")
↓
VideoClient.Init
↓
GetImageSample
↓
valid JPEG bytes
取得したJPEGはPillowでdecodeでき、既存のGrounding DINO analyzerへそのまま入力できました。
G1デモ画像
G1では今回、camera compatibilityとanalyzer inputを確認するread-only smoke testを実施しました。
G1のmovementやmanipulationは今回のscopeに含めていないため、Go2のような移動デモ動画は作成していません。
代わりに、G1 camera imageに対するdetector / overlay結果を掲載します。
デモ画像
今回確認できたことは以下です。
G1 front camera
↓
VideoClient compatibility path
↓
valid JPEG
↓
Grounding DINO analyzer input
↓
detection / overlay output
G1 camera imageは既存analyzerで概ね問題なく処理できました。
ただし、Go2用の前方進路ROIは、G1のnavigationまたはmanipulation safety policyとして検証したものではありません。
G1専用のg1_near_field_v0 ROIを追加するかは、将来のnear-field inspection taskに必要になった時点で、controlled imageと明確な評価目的に基づいて検討します。
今回は、G1 camera image compatibilityを確認できたことを成果とし、無理に未校正ROIや新しいrisk policyを追加しない判断としました。
dashboardとoperator advisory
Streamlit dashboardには以下の情報を表示しています。
latest analyzed image
bounding box overlay
forward-route ROI
ADVISORY RISK
PATH BLOCKED
PRIMARY OBJECT
INFERENCE
CAPTURE TO ANALYSIS
ROUTE OCCUPANCY
OBJECTS USED
CAPTURED
ANALYZED
analyzer log
operatorはSTART / STOPを通じてanalyzer processを管理できます。
ただし、STOPはanalyzerのみを停止します。
STOP analyzer
≠ Go2 stop
≠ camera capture stop
≠ robot movement control
≠ emergency stop
capture loopは最新画像をlatest.jpgへ書き続けます。
analyzerを再びSTARTした際には、過去画像のbacklogではなく、現在の最新画像だけを処理します。
この設計により、operator advisory dashboardとして現在の場面に戻りやすくなります。
試行錯誤から得られた学び
今回の最適化では、モデルを変更する前に、実際のpipeline全体を分解して計測する重要性を学びました。
最初はmodel inferenceが最も遅いと考えていました。
しかし、実測では以下のような順番で課題が見つかりました。
1. full-resolution PNG overlay出力
2. analyzer warm-up
3. camera image supply cadence
4. dashboard port conflict
5. process lifecycle / cleanup
特に重要だったのは、Grounding DINO Tinyの推論速度だけを最適化しても、camera imageが数秒ごとにしか更新されなければ、operatorが見るdashboardの更新感は改善しないことです。
そのため、以下のようにボトルネックを段階的に解消しました。
full-resolution overlay
↓
1280px display overlay + PNG encoding optimization
one-shot capture process
↓
persistent VideoClient capture loop
このように、robotics systemではAI modelだけでなく、camera I/O、DDS、process lifecycle、file mailbox、container、browser UIを含めた全体設計が重要だと感じました。
まとめ
今回のロボット目視検査エージェントでは、Go2とG1を対象として、visual inspection pipelineを構築・検証しました。
主な成果は以下です。
Go2 front cameraのread-only JPEG取得
Grounding DINO Tinyによるobject detection
bounding boxとROIを用いたdeterministic geometry policy
multi-object union occupancy
Streamlit operator dashboard
analyzer-side overlay output optimization
persistent VideoClient capture loop
Go2 latest.jpg更新間隔の約3〜4秒から約1秒への改善
G1 camera JPEG compatibilityの確認
G1 imageのGrounding DINO analyzer入力確認
今回の設計では、VLMによる意味理解と、進路に対する幾何的判定を分離しました。
Grounding DINO:
object localization
Python geometry:
ROI overlap and occupancy
deterministic policy:
risk level and path_blocked
VLM / LLM:
future semantic explanation and reporting
この役割分担により、進路リスクadvisoryに必要な速度、再現性、説明可能性を改善できました。
本システムは、手動操作中のoperatorが最新のvisual risk evidenceを確認するためのadvisory-only prototypeです。
Go2またはG1の移動、停止、腕、手、grasping、emergency stopなどのrobot controlには接続していません。
今回のinspection projectはここでfeature freezeとし、今後は別projectとして、Isaac Labを用いたG1 stationary bottle manipulation reinforcement learningへ進む予定です。
Go2とG1で構築したcamera、detector、dashboard、evaluationの知見は、次のPhysical AI projectにおける観察・検証基盤として活用していく予定です!






