Weaviateを用いた動画フレームの自動インデックス化とセマンティック検索

動画の中身を検索する:Weaviateで構築するセキュアな内製動画検索基盤の実装全手順

この記事は急速に進化する技術について解説しています。最新情報は公式ドキュメントをご確認ください。

約10分で読めます
文字サイズ:
動画の中身を検索する:Weaviateで構築するセキュアな内製動画検索基盤の実装全手順
目次

この記事の要点

  • Weaviateを用いた動画フレームの自動ベクトルインデックス化
  • AI(CLIPなど)による動画内容のセマンティック検索
  • プライバシー保護とコスト削減を実現する内製システム構築

動画の中身を検索する:Weaviateで構築するセキュアな内製動画検索基盤の実装全手順

社内の膨大な動画マニュアルやEラーニング教材から、特定の手順やシーンを探すのに時間を浪費していませんか。

動画は情報量が豊富ですが、検索性が著しく低いデータ形式です。タイトルやタグだけでは映像の中身に辿り着けません。クラウドの動画解析APIは便利ですが、機密性の高い社内会議や未公開映像を外部送信するリスクがあります。また、分単位課金のSaaSはデータ量増加に伴いコストが跳ね上がります。

本記事では、オープンソースのベクトルデータベース「Weaviate」を活用し、自社環境(オンプレミスまたはプライベートクラウド)で完結する動画セマンティック検索システムの構築手順を解説します。データプライバシーを守り、コストを抑えて「動画の中身」を検索可能にする実践的なガイドです。

1. 動画データ活用における「検索性」の壁と突破口

動画データ活用の最大のボトルネックは「不透明性」です。テキストは grep 等で容易に検索できますが、バイナリデータ(0と1のデータ列)である動画ファイルの中身は、そのままではコンピュータに意味を解釈させることができません。

メタデータ検索の限界と「意味検索」の必要性

従来の手動による「タグ」や「説明文」の付与には明確な限界があります。

  • 入力コスト: 1時間の動画に詳細なタグ付けを行うには、視聴時間以上の工数が必要です。
  • 主観のバラつき: 同じシーンでも「会議」「打ち合わせ」と担当者によってタグが異なり、検索漏れが生じます。
  • 粒度の粗さ: 動画全体にタグが付与されても、「開始15分30秒のホワイトボードの議論」をピンポイントで探すのは困難です。

これを解決するのが、複数のデータ形式(テキスト、画像など)を統合して処理するマルチモーダルAIを用いた「セマンティック検索(意味検索)」です。映像の各フレームをベクトル化(数値の意味空間へ変換)し、「赤い車が走っているシーン」といった自然言語クエリや類似画像を用いて、特定の瞬間を正確に探索可能にします。

なぜ今、Weaviateなのか?

動画検索の内製化にWeaviateを推奨する理由は、柔軟なモジュールシステム統合の容易さです。

Weaviateは、CLIPモデル等のマルチモーダルモジュール(例: multi2vec-clip)を組み込み、画像とテキストを共通のベクトル空間にマッピングする機能をデータベース自体に持たせられます。複雑な推論APIサーバーを別途構築せずとも、データを投入するだけで自動的にベクトル化とインデックス作成が行われます。

※利用可能なモジュールや対応モデルは頻繁にアップデートされます。最新構成や設定方法は、必ずWeaviate公式ドキュメントをご参照ください。

内製化によるコストメリットとセキュリティ担保

Google Cloud Video Intelligence APIやAmazon Rekognition Video等のクラウドAIサービスは強力ですが、多くは従量課金制です。数千時間規模の処理ではランニングコストが課題になります。

対して、Weaviateを用いた内製化(Self-Hosted)であれば、主なコストはインフラ費用(特にGPUインスタンス)に集約されます。オープンソースの学習済みモデル(CLIP等)を活用し、追加ライセンス料や学習コストを抑えられます。

さらに重要なのがデータガバナンスです。動画データを自社のVPC(仮想プライベートクラウド)やオンプレミス(自社運用設備)から外部に出さず処理できるため、GDPR等の規制や社内セキュリティポリシーに準拠した基盤を構築できます。これは機密情報や未公開素材を扱う上で決定的な利点です。

2. 導入前設計:失敗しないためのサイジングとモデル選定

コードを書く前にアーキテクチャ設計を行います。動画検索はテキスト検索よりデータ量が桁違いに大きく、無計画な実装はストレージ枯渇や検索速度低下を招きます。段階的に理解できるよう、まずは全体像から整理しましょう。

動画処理パイプラインの全体像

システムは「インジェスト(データの取り込み・登録)」と「検索」の2フェーズで構成されます。

  1. 動画入力: MP4などの動画ファイル。
  2. フレーム抽出: FFmpeg等で動画を静止画(フレーム)に分解。
  3. ベクトル化: 抽出したフレームをAIモデル(CLIP等)でベクトル化。
  4. インデックス: Weaviateにベクトルとメタデータ(動画ID、タイムスタンプ)を保存。
  5. 検索: ユーザーのテキストクエリをベクトル化し、近傍探索を実行。

埋め込みモデルの選定基準(CLIP vs ImageBind)

検索精度と対応モダリティを決める「Embedding(埋め込み)モデル」を選定します。

  • CLIP (OpenAI): 画像とテキストのペアで学習された安定モデル。「テキストでシーン検索」の第一選択肢です。Weaviate標準サポートで導入が容易です。
  • ImageBind (Meta): 画像、テキストに加え音声や熱画像など6つのモダリティを統合。音声内容も含めた検索に有効ですが、計算コストと実装難易度が上がります。

今回は実用性とリソース効率のバランスが良いCLIPを採用します。

専門家の視点:生成AIとの連携について
検索インデックス作成にはCLIP等の軽量モデルが適していますが、検索後の「回答生成」や「推論」には、OpenAIのGPT-4o(GPT-4系列など)を活用するケースが増えています。最新モデルはエージェント機能やマルチモーダル理解が強化されており、見つけたシーンに「この場面の感情を分析して」と複雑な指示を出せます。設計時は「検索はCLIP、推論はGPT-4o」という役割分担を意識すると良いでしょう。

ハードウェア要件とリソース見積もり

動画の全フレーム保存は避けてください。

30fpsの動画1時間分は 30 * 60 * 60 = 108,000 枚となり、全てベクトル化するのは計算資源の無駄です。検索に必要なのは「シーンの変わり目」や「数秒ごとのキーフレーム」のみです。

推奨設定:

  • サンプリングレート: 1fps(1秒に1枚)または 0.5fps(2秒に1枚)。データ量を1/30〜1/60に圧縮します。
  • GPU: ベクトル化処理に必須です。コストパフォーマンスに優れたNVIDIA T4や、L4A10G等が推奨されます。CPU処理は非現実的です。

3. 実装ステップ①:環境構築とスキーマ定義

導入前設計:失敗しないためのサイジングとモデル選定 - Section Image

Weaviateの環境構築を行います。再現性を高め、チームで統一環境を利用するためDocker Composeを使用します。マイクロサービスアーキテクチャの観点からも、各コンポーネントを独立して管理できるコンテナ技術は開発環境の構築に最適です。

Docker ComposeによるWeaviateの立ち上げ

以下の docker-compose.yml を作成します。最新のDocker Compose仕様に準拠し、Weaviate本体と画像処理用の multi2vec-clip モジュールを定義します。

services:
  weaviate:
    command:
    - --host
    - 0.0.0.0
    - --port
    - '8080'
    - --scheme
    - http
    # 重要: 本番運用時は必ず最新の安定版バージョンタグを指定してください
    # 例: image: cr.weaviate.io/semitechnologies/weaviate:1.25.x
    image: semitechnologies/weaviate:latest
    ports:
    - 8080:8080
    - 50051:50051
    restart: on-failure:0
    environment:
      QUERY_DEFAULTS_LIMIT: 25
      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
      PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
      DEFAULT_VECTORIZER_MODULE: 'multi2vec-clip'
      ENABLE_MODULES: 'multi2vec-clip'
      # CLIPモジュールの推論コンテナを指定
      CLIP_INFERENCE_API: 'http://multi2vec-clip:8080'
      CLUSTER_HOSTNAME: 'node1'

  multi2vec-clip:
    image: semitechnologies/multi2vec-clip:clip-vit-base-patch32
    environment:
      ENABLE_CUDA: '0' # GPUがある場合は'1'に設定

ポイント:

  • イメージの指定: image には開発時点の最新安定版を指定してください。
  • ENABLE_MODULES: multi2vec-clip を有効化し、画像を扱えるようにします。
  • ENABLE_CUDA: 本番環境でGPUを使用する場合は必ず 1 に設定し、推論速度を向上させます。

動画検索用クラス(Video/Frame)のスキーマ設計

データ構造(コレクション)を定義します。Weaviate Python Client(v4系)を使用し、gRPCによる高速なデータ操作を実現します。

import weaviate
import weaviate.classes.config as wvc

# ローカルインスタンスへ接続
client = weaviate.connect_to_local()

# 既存なら削除(開発用: 本番環境では慎重に操作してください)
if client.collections.exists("VideoFrame"):
    client.collections.delete("VideoFrame")

# フレーム単位のコレクション定義
client.collections.create(
    name="VideoFrame",
    vectorizer_config=wvc.Configure.Vectorizer.multi2vec_clip(
        image_fields=["image"], # ベクトル化対象のフィールド
        text_fields=["description"] # テキストも合わせてベクトル化可能
    ),
    properties=[
        wvc.Property(name="video_id", data_type=wvc.DataType.TEXT),
        wvc.Property(name="video_title", data_type=wvc.DataType.TEXT),
        wvc.Property(name="timestamp", data_type=wvc.DataType.NUMBER), # 秒数
        wvc.Property(name="image", data_type=wvc.DataType.BLOB), # 画像データ(Base64)
        wvc.Property(name="description", data_type=wvc.DataType.TEXT),
    ]
)

print("Collection created successfully.")

検索の最小単位である「フレーム」を1オブジェクトとして扱います。video_id を持たせることで、検索結果から元の動画ファイルへ紐付け、該当箇所へのジャンプ機能を実装できます。

4. 実装ステップ②:インジェストパイプラインの構築

動画から画像を切り出し、Weaviateへ登録するETL(抽出・変換・書き出し)処理を実装します。

FFmpegを用いた効率的なキーフレーム抽出

PythonからFFmpegを呼び出し、1秒に1枚(1fps)フレームを抽出します。

import subprocess
import os

def extract_frames(video_path, output_dir, fps=1):
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    
    # ffmpegコマンドの構築
    # -vf fps=1 : 1秒に1フレーム抽出
    # %04d.jpg : 連番ファイル名
    command = [
        'ffmpeg',
        '-i', video_path,
        '-vf', f'fps={fps}',
        os.path.join(output_dir, 'frame_%04d.jpg')
    ]
    
    subprocess.run(command, check=True)
    print(f"Frames extracted to {output_dir}")

# 使用例
# extract_frames("./sample_video.mp4", "./frames")

最適化のヒント: FFmpegの mpdecimate フィルタを使用すると、スライド表示など変化の少ないフレームを間引き、ストレージと処理時間を節約できます。

画像データのベクトル化とWeaviateへのバッチ登録

抽出画像をBase64エンコードし、バッチ処理でWeaviateに送信します。

import base64
from weaviate.util import generate_uuid5

def ingest_frames(client, frames_dir, video_id, video_title):
    collection = client.collections.get("VideoFrame")
    frames = sorted(os.listdir(frames_dir))
    
    with collection.batch.dynamic() as batch:
        for i, frame_file in enumerate(frames):
            if not frame_file.endswith(".jpg"): continue
            
            file_path = os.path.join(frames_dir, frame_file)
            
            # 画像をBase64に変換
            with open(file_path, "rb") as f:
                b64_image = base64.b64encode(f.read()).decode("utf-8")
            
            # タイムスタンプの計算(1fps前提)
            timestamp = i 
            
            # オブジェクトの構築
            properties = {
                "video_id": video_id,
                "video_title": video_title,
                "timestamp": timestamp,
                "image": b64_image,
                "description": ""
            }
            
            # バッチに追加
            batch.add_object(
                properties=properties,
                uuid=generate_uuid5(properties) # 一意なIDを生成
            )
            
    print(f"Ingested {len(frames)} frames for video {video_id}")

# 使用例
# ingest_frames(client, "./frames", "v123", "製品デモ動画")

実行すると、Weaviate内の multi2vec-clip モジュールがBase64画像を受け取り、自動でベクトル変換とインデックス作成を行います。

5. 実装ステップ③:検索精度の検証とチューニング

実装ステップ②:インジェストパイプラインの構築 - Section Image

データ登録後、検索テストと調整を行います。

テキストクエリによるシーン検索のテスト

「誰かが握手しているシーン」を検索します。

def search_video(client, query_text):
    collection = client.collections.get("VideoFrame")
    
    response = collection.query.near_text(
        query=query_text,
        limit=3,
        return_metadata=wvc.Query.MetadataQuery(distance=True)
    )
    
    for obj in response.objects:
        print(f"Video: {obj.properties['video_title']}")
        print(f"Time: {obj.properties['timestamp']} sec")
        print(f"Distance: {obj.metadata.distance}")
        print("---")

# 実行
search_video(client, "shaking hands in a meeting room")

注意点: CLIPは英語データセットで学習されているため、英語クエリの方が高精度です。日本語検索の場合は、翻訳APIでクエリを英語化する手法が有効です。

類似画像検索(レコメンデーション)の実装

query.near_image(image=base64_string) を使用し、「この製品が映っている他のシーン」を探す画像検索も簡単に実装できます。

検索結果のフィルタリングとスコア調整

無関係なシーンを防ぐため、distance(距離)や certainty(確信度)の閾値を設定します。一般的に distance が 0.7 以上の結果を除外するフィルタリングをアプリケーション側に実装し、検索品質を担保します。

6. 運用とリスク管理:安定稼働のためのチェックリスト

長期的な安定稼働に向けたリソース管理と運用対策をまとめます。DevOpsのプラクティスを取り入れ、継続的な監視と改善を行うことが重要です。

インデックスサイズの監視と古いデータのパージ戦略

ベクトルインデックス(HNSW:高速な近似近傍探索アルゴリズム)はメモリを消費します。

  • メモリ監視: Prometheus等でWeaviateのメモリ使用量を監視します。
  • Product Quantization (PQ): PQ(量子化)を有効にすると、検索精度をわずかに犠牲にしてメモリ使用量を約1/10に削減できます。大規模運用で検討すべき設定です。

バックアップとリストア手順

S3やGCS、ローカルファイルシステムへの定期的なスナップショット取得を設定します。インデックス再構築には膨大なリソースが必要なため、バックアップ体制は必須です。

# バックアップ作成例
result = client.backup.create(
    backup_id="backup-2023-10-27",
    backend="filesystem"
)

将来的なスケーリングと運用の安全性

データ量増加に伴う拡張性と安全性について考慮します。

  • 構成変更の事前確認: Docker Composeの --dry-run フラグ等を活用し、意図しないコンテナ再作成を防ぎます。
  • セキュリティの確保: SBOM(ソフトウェア部品表)や署名検証等のセキュリティ機能を活用し、コンテナイメージの脆弱性を管理します。
  • Kubernetesへの移行: Docker Composeの限界に達した際は、公式Helmチャートを用いたKubernetesクラスタ上での水平スケーリングを検討します。

まとめ:内製化で実現する「資産としての動画」活用

ingest_frames(client, "./frames", "v123", "製品デモ動画") - Section Image 3

WeaviateとCLIPを組み合わせることで、高額なSaaSに依存せず、セキュアで高精度な動画検索システムを構築できます。

動画の中身が検索可能になれば、過去の映像資産がいつでも取り出せる「ナレッジ」に変わります。まずは手元の動画数本からPoC(概念実証)を始めてみてください。

動画の中身を検索する:Weaviateで構築するセキュアな内製動画検索基盤の実装全手順 - Conclusion Image

コメント

コメントは1週間で消えます
コメントを読み込み中...