0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ブラウザだけで人物・物体の動きを追跡する——セマンティック検出とオプティカルフローを組み合わせた動画エフェクトの実装

0
Posted at

プロジェクト

GitHub:
https://github.com/MartinDelophy/ai-video-editor

オンラインデモ:
https://video-editor.ai-creator.top/

Timeline Studioは、ブラウザ上で動作するローカルファーストのAI動画編集ツールです。

今回、このエディターに実験的な「オプティカルフロー追跡」機能を追加しました。単純にフレーム間のピクセル移動を矢印で表示するのではなく、最初に人物や物体を検出し、そのセマンティック領域内でオプティカルフローを計算します。

最終的には、局所モーションベクトル、人物・物体単位のモーショングループ、主な移動方向、時間方向に累積した軌跡、実験タイムコードを動画上に描画します。解析結果はWebM動画としてエンコードされ、My assetsに追加されます。

なぜ通常のオプティカルフローだけでは不十分なのか

オプティカルフローは、隣接フレーム間のピクセル移動を2次元ベクトルとして表現します。

$$
v=(\Delta x,\Delta y)
$$

全画面で計算すると、人物だけでなく次の変化もベクトルとして現れます。

  • カメラの揺れ
  • 背景テクスチャの移動
  • 字幕の切り替わり
  • 照明の変化
  • 動画圧縮によるノイズ
  • 髪や衣服の細かな変形

これらをそのままクラスタリングすると、背景や字幕まで「移動グループ」として扱われる可能性があります。

そこで今回は「どのピクセルが動いたか」ではなく、「どの人物・物体が、どの方向へ動いたか」を表現する設計にしました。

全体の処理フロー

選択中の動画クリップ
        ↓
人物・物体の検出
        ↓
大きなセマンティック領域を生成
        ↓
領域内で局所オプティカルフローを計算
        ↓
ベクトルをグループ単位で集約
        ↓
時間方向に軌跡を累積
        ↓
高解像度フレームへ描画
        ↓
WebMとしてエンコード
        ↓
My assetsへ追加

動画の読み取りから結果生成まで、すべてブラウザ内で実行します。動画を解析サーバーへアップロードする必要はありません。

人物・物体を先に検出する

最初のアンカーフレームで人物または主要な物体を検出します。現在の実装では複数のローカル推論経路を段階的に利用しています。

  1. NanoDetによる軽量な人物・物体検出
  2. MediaPipeによる人物領域の確認
  3. YOLOSによるフォールバック検出
  4. 推論ランタイムが利用できない場合の保守的な中央領域

複数人が存在する必要はありません。1人の人物、または1つの主要な物体だけでも、有効なセマンティックモーショングループとして扱います。

クローズアップ映像では人物が画面の大部分を占めるため、検出領域の面積を厳しく制限しすぎないようにしています。

セマンティックモーショングループ

検出された人物や物体は、オプティカルフローを伝播させる大きな領域へ変換します。

const cohort = {
  id: "cohort-1",
  label: "person",
  box: {
    xmin: 0.2,
    ymin: 0.1,
    xmax: 0.8,
    ymax: 0.95
  },
  center: { x: 0.5, y: 0.525 },
  dx: 0,
  dy: 0,
  confidence: 0,
  stability: 0
};

座標を0〜1に正規化することで、低解像度の解析フレームで得られた情報を、プレビューや生成動画の解像度に合わせて再利用できます。

セマンティック領域内で光学フローを計算する

動画フレームをグレースケールに変換し、前後フレーム間で局所的なブロックマッチングを行います。

候補変位の誤差は、簡略化すると次のように表せます。

$$
E(dx,dy)=\frac{1}{N}\sum_{i=1}^{N}|I_t(i)-I_{t+1}(i+dx,i+dy)|
$$

誤差が最小になる位置を、そのブロックの移動先として採用します。ただし、計算されたベクトルをすべて利用するわけではありません。

  • 移動量が小さすぎるベクトル
  • マッチング誤差が大きいベクトル
  • セマンティック領域外のベクトル
  • グループの主方向から大きく外れたベクトル
  • 信頼度が低い局所変化

これらを除外し、背景ベクトルだけを人物グループとして扱わないようにします。

グループの主方向を計算する

セマンティック領域内に複数の有効ベクトルがある場合、信頼度を重みとして平均ベクトルを計算します。

$$
V_g=\frac{\sum_i c_i v_i}{\sum_i c_i}
$$

function aggregateVectors(vectors) {
  let dx = 0;
  let dy = 0;
  let totalWeight = 0;

  for (const vector of vectors) {
    const weight = vector.confidence;
    dx += vector.dx * weight;
    dy += vector.dy * weight;
    totalWeight += weight;
  }

  return totalWeight
    ? { dx: dx / totalWeight, dy: dy / totalWeight }
    : { dx: 0, dy: 0 };
}

領域内のベクトルが同じ方向を向いているかも確認し、軌跡安定度として解析パネルに表示します。

フレームごとの移動を軌跡として累積する

単一フレームのオプティカルフローだけでは、その瞬間の方向しか分かりません。そこでグループ中心をフレームごとに更新します。

$$
P_t=P_{t-1}+V_t
$$

const nextPoint = {
  x: previousPoint.x + groupVector.dx,
  y: previousPoint.y + groupVector.dy,
  time: frameTime
};

track.points.push(nextPoint);

蓄積した座標を線で結ぶことで、人物や物体がA地点からB地点へ移動した軌跡を表現できます。

Canvasへの描画

結果画面には以下を重ねます。

  • 元動画フレーム
  • 薄い解析グリッド
  • シアン色の局所ベクトル
  • モーショングループの境界
  • グループ単位の移動軌跡
  • 右上のタイムコード

グループ境界は残しますが、人物名などのラベルは動画内に表示しません。人物が大きく映る映像では文字が被写体を隠してしまうためです。

低解像度で解析し、高解像度で生成する

最初の実装では、幅192px程度の解析フレームをそのまま生成サイズまで拡大していたため、背景動画が大きくぼやけていました。

そこで解析とレンダリングを分離しました。

解析経路

  • 小さいフレームへ縮小
  • グレースケール化
  • 局所オプティカルフローを計算
  • グループと軌跡を更新

レンダリング経路

  • 元動画から鮮明なフレームを取得
  • 最大幅1280pxまで保持
  • 解析座標を高解像度Canvasへ変換
  • ベクトル、境界、軌跡、タイムコードを再描画
  • WebMとしてエンコード
const scaleX = renderWidth / analysisWidth;
const scaleY = renderHeight / analysisHeight;

const renderX = vector.x * scaleX;
const renderY = vector.y * scaleY;

この構成により、解析負荷を大きく増やさずに、元動画に近い画質で出力できます。

WebCodecsで結果動画を生成する

解析後、各サンプリング時刻の結果をCanvasへ描画し、WebCodecsを利用してWebMへエンコードします。

const resultBlob = await encodeFrames(
  renderedFrameBlobs,
  renderWidth,
  renderHeight,
  sampleRate,
  keyframeTimes,
  duration,
  {
    signal: abortController.signal,
    onProgress(value) {
      updateProgress(92 + Math.round(value * 8));
    }
  }
);

生成動画にはタイムライン用のサムネイルも付与します。これにより、タイムラインへ配置した直後から実際のフレーム列を確認できます。

生成結果をMy assetsへ追加する

setUserAssets(current => [asset, ...current]);
setSelectedLibraryAssetId(asset.id);
setActiveTool("media");
setMediaTab("mine");

解析結果は自動的にMy assetsへ追加しますが、タイムラインには自動挿入しません。結果を確認してから、ユーザー自身がメイン映像またはオーバーレイとして配置できます。

実際のテスト結果

約2秒、852×480の動画を4fpsで解析しました。

  • 解析フレーム:9
  • 有効なモーションベクトル:268
  • セマンティックモーショングループ:1
  • 主な移動方向:約235度
  • 軌跡安定度:約78%
  • 生成動画:852×480 WebM

低解像度解析と高解像度レンダリングを分離したため、出力動画は元動画の解像度を維持できました。

ブラウザ内処理のメリットと制約

この機能では、動画フレームの読み取り、人物・物体検出、オプティカルフロー計算、軌跡集約、Canvasレンダリング、WebMエンコード、タイムライン画像生成をすべてブラウザ内で実行します。

メリットは次のとおりです。

  • 動画を解析サーバーへアップロードしなくてよい
  • バックエンドの推論キューが不要
  • パラメータを変更してすぐ再実行できる
  • 結果をそのまま編集素材として利用できる
  • サーバー側の動画保存コストを抑えられる

一方、長時間動画、高いサンプリングレート、4K動画ではメモリ消費と処理時間に注意が必要です。激しいモーションブラー、長時間の遮蔽、カット切り替えなどでも軌跡の安定性が低下します。

まとめ

今回の実装では、オプティカルフローを単なるピクセル可視化ではなく、セマンティックな動画編集機能として構成しました。

人物・物体検出
  +
セマンティックROI
  +
局所オプティカルフロー
  +
グループベクトル集約
  +
軌跡の時間累積
  +
高解像度Canvasレンダリング
  +
WebCodecsエンコード
  +
動画素材として再利用

重要だったポイントは次の3つです。

  1. 背景ベクトルを人物グループとして扱わない
  2. 低解像度解析と高解像度生成を分離する
  3. 解析結果を再利用可能な動画素材として完成させる

これにより、ベクトルが大量に表示されるだけのデモではなく、人物や物体の移動を理解しやすい実験動画をブラウザ内だけで生成できるようになりました。

ブラウザAI、WebCodecs、動画編集、コンピュータビジョンに興味がある方は、ぜひリポジトリをご覧ください。

GitHub:
https://github.com/MartinDelophy/ai-video-editor

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?