広告

VLMベースの行動認識のためのカメラ評価システムをどう設計したか――そして現実世界ではなぜ見え方が違うのか

Dev.to / 2026/3/31

💬 オピニオン

要点

  • この投稿では、学習不要のホームロボット向け行動認識システムが、ゼロショットの視覚言語モデル(VLM)を用いることで、視点選択が極めて重要になる理由を説明する。VLMの精度は画像品質に直接依存するためである。

「トレーニング不要の家庭用ロボット」シリーズの第2部です。第1部では、固定式の天井設置ノードが知覚の土台になった理由を扱いました。この記事では、もう1つの特定のアルゴリズムに深く踏み込みます。すなわち、各行動エピソードでシステムがどのカメラ角度を選ぶのか、そしてその判断がシミュレーションを出たときにどのように見えるのか、を説明します。

私はまず、なぜ固定式のグローバルカメラが理にかなっているのかを自分で検証しました。そこに至るまでには、必要性に対する本気の懐疑から始めて、手痛い形で結論にたどり着きました。次に私が直面した課題は、完全に自分の問題でした。12個の候補視点のうち、実際にどれを使うのか?

私のアドバイザーは入力モダリティを指定しました。選択アルゴリズム、スコアリングの重み、厳格なFOVゲート、フォールバックのロジック——それらは私には与えられませんでした。この記事はその設計作業です。各判断がどこから来たのか、どんなトレードオフを生むのか、そしてUnityのシミュレーションから実際の部屋へ移すと何が変わるのかを示します。

中核となる問題

私のシステムは、ユーザーが何をしているか——飲む、読む、タイピングする——を、カメラ画像をビジョン・ランゲージ・モデル(VLM)に送ることで認識します。VLMはゼロショットです。つまり、学習データもファインチューニングもありません。画像を見て、いま何が起きているのかを記述するだけです。

これにより、厳しい依存関係が生まれます。VLMの精度は画像品質に直結し、画像品質は視点選択に直結します。

学習済みの活動認識モデルなら、不適切な視点による不具合をある程度は相殺できます。学習中に、隠れた(occluded)り、斜めの角度になった例を何千という回数で見ているからです。ゼロショットのVLMではそれができません。ユーザーがフレームの端にいる、あるいは家具の一部の後ろに半分隠れている場合、VLMは信頼できない出力になります。「壁のそばに立っている人」ではなく、「瓶から飲んでいる人」といった内容を推定できない、ということです。

そのため、どのAI推論も始まる前にシステムは答える必要があります。12個の利用可能なカメラノードのうち、今この瞬間に最も有用な画像を生成するのはどれか?

なぜ単に最も近いノードを選ばないのか?

素朴なアプローチは距離だけです。つまり、ユーザーに最も近いノードを選びます。しかし距離だけでは、2つの重要な失敗パターンを見落とします。

遮蔽(Occlusion)。 ユーザーから1.5m離れているノードがソファのちょうど後ろにあり、直接視線が通らない場合、画像は完全に遮られます。4m離れていても見通しが良いノードのほうが、はるかに有用です。

軸外角度(Off-axis angle)。 デスクに向かっているユーザーの横に位置するノードは、せいぜい横顔のような映り方になるだけで、最悪の場合はユーザーの頭の後ろが映ります。VLMは活動認識において正面、あるいはほぼ正面の視点を強く好みます。なぜなら、人がカメラを向いているインターネット画像で訓練されているからです。

距離は重要ですが、3つの要因のうちの1つにすぎません。

スコアリングの式

私は最終的に、3つの幾何学的要因の重み付き結合に加えて、どれよりも先に動く厳格なゲートを組み合わせる形にしました。

ステップ0 — 厳格なFOVゲート

いかなるスコア計算も行う前に、ユーザーがノードの視野(FOV)錐の中に入っているかを確認します。入っていなければ、そのノードは即座に除外します——スコア=0、以降の計算は行いません。

if θ_i > FOV_i / 2  →  s_i = 0   (hard gate, skip remaining calculation)

ここで、θ_i はノードの前方向と、ユーザーの胸部を指すベクトルのなす角です。狙い点(aim point)は胸の高さに設定します:aim = user.position + (0, 1.2, 0).

このゲートは、見かけよりもずっと重要です。これがないと、重み付きの式は「物理的にユーザーを見ているはずがない」ノードに対して、スコアを非ゼロで割り当ててしまうことがあります。ただ近いだけ、あるいは別方向に対して視界が良いだけ、という理由でそうなります。厳格なゲートによって、そもそも計算が始まる前にこの種の不適切な選択を丸ごと排除できます。

ステップ1 — 視認性ファクター v_i

v_i = 1   if linecast(node → user chest) is unobstructed
      0   otherwise

ノード位置からユーザーの胸部までを物理的に線(linecast)で結んだときに、障害物に当たらないかどうかを判定します。家具や壁に当たれば v_i = 0 です。これは二値です——見通しがはっきりあるか、ないか。

重み:0.5 — 最も大きい重みです。遮られているノードは、他の性質がどれほど良くてもほぼ役に立たないからです。

ステップ2 — 角度ファクター α_i

α_i = max(0,  1 - θ_i / (FOV_i / 2))

ユーザーの角度位置を、FOV錐の中で連続的なスコアにマッピングします。中心(デッドセンター)では1.0、FOV境界では0.0です。線分キャスト(linecast)が通っていても、ユーザーがフレームの端のほうに見えるノードは低い角度スコアになります。

重み:0.3

ステップ3 — 距離ファクター d_i

d_i = max(0,  1 - dist(node, user_chest) / 10)

0mで1.0、10mで0.0となる線形減衰です。私はこの減衰定数として10mを選びました。私のシミュレーション内で最大の部屋の横幅がだいたい6m程度だったからです。つまり10mなら、最大の部屋の反対隅にあるノードでも距離スコアは非ゼロになりますが、それでも明確にペナルティを受けます。

重み:0.2 — 最小の重みです。距離は遮蔽や角度よりも重要度が低いためです。

最終スコア

s_i = (v_i × 0.5 + α_i × 0.3 + d_i × 0.2) × m_i

m_i ∈ [0.5, 1.0] は、Unity Inspectorでノードごとに設定する乗数で、既知の制限を持つノードの重みを手動で下げられるようにします(例:窓のほうを向いており、午後の光でグレアが出るノードなど)。

s_i ≥ 0.50 を満たすノードは候補リストに採用され、降順に並べ替えます。上位2つを撮影します。

疑似コード

function ScoreCamerasRanked(user, cameras, s_min=0.50):
    aimPos  user.position + [0, 1.2, 0]
    qualified  []

    for node in cameras:
        θ  angle(node.forward, aimPos - node.position)

        if θ > node.FOV / 2:
            node.score  0
            continue                    // 厳格なFOVゲート

返却形式: {"translated": "翻訳されたHTML"}v  1 if Linecast(node.position, aimPos) clear else 0
        α  clamp(1 - θ / (node.FOV/2), 0) , 1)
        d  clamp(1 - dist(node.position, aimPos) / 10, 0, 1)

        node.score  (v*0.5 + α*0.3 + d*0.2) * node.multiplier

        if node.score  s_min:
            qualified.append(node)

    return sort(qualified, key=score, descending=True)

なぜこの設計なのか — 正直な答え

率直に言います:このスコアリング式は、理論的に最適解だからというより、主としてハードウェアの制約のために存在します。

私のシミュレーションは1台のワークステーションで動かしています。Unityシーンには物理カメラが1台あり、選択した各ノード位置へテレポートして1フレームをレンダリングし、次へ進みます。レンダリングコストを12倍にせずに、12台の同時カメラを動かすことはできませんでした。シミュレーションであっても、最初からすべてのノードを実際にレンダリングすることなしに、ノードを素早く順位付けする軽量な方法が必要でした。

3つの幾何学的要因による加重式は、その制約にぴったり合います:

  • ノード数を N とするとO(N)。たとえば N=100 でも自明に高速です
  • 空間座標と角度だけを使います — 画像のレンダリングは不要です
  • 解釈可能です。ノードのスコアが低いとき、すぐに理由が分かります(遮蔽によるもの?角度?距離?)

より高度なアプローチでは、候補ノードごとに低解像度のサムネイルをレンダリングし、選択前にその上で簡易な品質評価モデルを実行するでしょう。これなら、幾何学的な式では見落とすケースを拾えます。たとえば、線分(linecast)はクリアでも、ユーザーから見るとその背後にいるノード、などです。ただし、それには各選択判断につき N 回のレンダリングが必要で、私のハードウェアでは現実的ではありませんでした。

実用上のトレードオフ: 幾何学的な式は、一般的なケースでは高速かつ正確です。失敗する主な場面は、ユーザーの向きがノードへの視線(line of sight)と一致していないときであり、この制限は論文で明示的に記述しています。

シミュレーションと現実のギャップ

ここまでの内容はすべて Unity 上で動いています。実際の部屋で実IPカメラを使うようにすると、シミュレーションでは完全に回避できる3つのギャップが生じます。

ギャップ1:ユーザーがどこにいるか分からない

Unityでは、user.position が真値として利用できます。これは、キャラクターの正確なワールド座標で、毎フレーム更新されます。

実際の部屋ではそれがありません。ユーザーの位置は、カメラそのものから推定する必要があります(人物検出+奥行き推定、または三角測量)、ウェアラブルから、あるいは床センサーからです。これらはいずれも推定誤差を生み、その誤差はスコアリング式へ直接的に反映されます。

ブリッジングアプローチ: 固定ノードのカメラを使って軽量な人物検出器(例:YOLOv8-nano)を動かし、ホモグラフィによって2Dの床位置を推定します。これにより、奥行きセンサーがなくても、スコアリング式に十分な概略の(x, z)座標が得られます。

ギャップ2:node.forward は外部パラメータ(Extrinsic)のキャリブレーションを要する

Unityでは、各ノードの位置と前方向はエディタで設定されます — 正確で、ゼロ誤差、常に最新です。実際の部屋では、各カメラの外部パラメータ(共有されたワールド座標系に対する位置と向き)を物理的にキャリブレーションする必要があります。

キャリブレーションのドリフトは現実に起きます。振動や不注意による接触で、カメラが2cmずれると、linecast の起点が変わり、可視性の計算に影響します。特に境界的なケースでは影響が大きくなります。

ブリッジングアプローチ: 設置時にArUcoマーカベースでキャリブレーションし、定期的に再検証します。キャリブレーションパラメータは設定ファイルに保存し、実行時にスコアリング式へ反映します。キャリブレーションが閾値より古いノードは、再キャリブレーションの対象としてフラグを立てます。

ギャップ3:Linecast ≠ 現実世界の遮蔽

Unityのlinecastは、静的な衝突メッシュを貫く完璧で瞬時のレイです。実際の部屋では、遮蔽は動的です(人、ペット、動かされた家具)。また部分的に透明です(ガラスのテーブル、薄いカーテン)。さらに、遮蔽には確率的要素があります。

ブリッジングアプローチ: 二値のlinecastを、カメラ自身の映像から推定した可視確率に置き換えます。選択したノードの画像で、直前のフレームではユーザーが部分的に遮蔽されていた場合は、現在の選択に対してそのノードのスコアを下げます。これによりフィードバックループが生まれます:実際の画像品質が、将来のノード選択に反映されるのです。

実運用でのスコアリングの見え方

Unityのシミュレーションでは、Scene View 上で Gizmos を使ってノードのスコアを可視化します:

  • 緑の球 — スコア ≥ 0.50。候補リストに採用
  • 黄色の球 — スコアが0.35〜0.50。閾値付近
  • 赤の球 — スコア > 0 だが閾値未満
  • 灰色の球 — FOVでゲートされ、スコア = 0

実験のセットアップ中は、この可視化を使って、各行動スポット(ソファ、デスク、キッチンカウンター)ごとに、少なくとも部屋ごとに2つ以上のノードが確実に緑(green)スコアになることを確認します。ある部屋で、特定のスポットに対して確実に緑になるノードが1つしかない場合は、実験を行う前にノードの配置を変更します。

このデバッグ手順――推論を実行する前のスコアの空間可視化――は、数式そのものと同じくらい重要だと分かりました。数式は、その数式が作用するノード配置に対してのみ良いものになります。

まとめ

設計上の判断 理由 現実世界での相当物
加重和の前にハードFOVゲート ユーザーを見られないノードに対するスコアリングを防ぐ 同じゲートが適用される。正確な外部パラメータ(extrinsic)のキャリブレーションが必要
ラインキャストによる可視性判定 シミュレーションでは高速かつ正確 ライブ映像から得られる可視性確率に置き換える
胸の高さの狙いポイント(1.2m) 胴体を捉え、活動認識にとって最も有益 同じ。正確性のために深度カメラまたはポーズ推定器が必要
上位2ノードのキャプチャ 単一ノードのオクルージョン失敗に対応 同じ戦略。2つ目のノードが保険になる
ノードごとの乗数 m_i 既知の問題ノードに対する手動上書き (グレア、恒常的な遮蔽などの)固定的な環境問題を抱えるノードをフラグ付けするのに有用

スコアリング数式は、特定のハードウェア制約を中心に組み立てた実用的な解決策です。すなわち、レンダリングカメラは1台、仮想視点は12個、選択は高速かつ解釈可能である必要がある、という制約です。シミュレーションではうまく機能し、幾何学的ロジックは実運用の展開にもそのままきれいに移植できます。しかし、数式への入力(ユーザーの位置、ノードの向き、オクルージョン)は、シミュレーションが無償で提供してくれるのと同様に、現実世界で計測できる計測パイプラインがすべて必要になります。

シリーズの次:キャプチャした画像がゼロショットVLMパイプラインにどう投入されるのか、またSBERTのセマンティック正規化が、学習データなしで自由形式のVLM記述を正準の行動ラベルへどのように写像するのか。

論文全文:「スマートホームロボットにおけるパーソナライズされた積極的サービス:VLMベースのシーン・グラウンディング、RAGメモリ、マニフォールド学習を統合するトレーニング不要の視覚知覚フレームワーク」— NCKU、2025年。

広告