Как вы индексируете видео-контент в RAG-системе?

Краткий тезис

Индексация видео в RAG — это мультимодальная задача, требующая извлечения и объединения информации из визуального ряда, аудиодорожки и возможных текстовых субтитров. Основной подход: разбить видео на сцены (шоты), из каждого шота взять ключевой кадр для визуального эмбеддинга (CLIP), транскрибировать аудио (Whisper) для текстового эмбеддинга, затем объединить их взвешенным средним или attention fusion и сохранить в векторную БД с метаданными (timestamp, источник, спикер). Это позволяет отвечать на вопросы по содержанию видео, искать моменты по описанию и строить агентные системы, работающие с видеоконтентом.


1. Термин: Индексация видео в RAG

Индексация видео — процесс преобразования неструктурированного видеопотока в набор поисковых векторов (эмбеддингов) и метаданных, которые можно хранить в векторной базе данных и использовать для retrieval.

В отличие от текстовых документов, видео содержит несколько модальностей:

  • Визуальный ряд (кадры, объекты, сцены)
  • Аудиодорожка (речь, звуки, музыка)
  • Текст (субтитры, OCR-текст на экране)

Задача RAG-системы — по текстовому запросу пользователя найти релевантный фрагмент видео и вернуть его вместе с контекстом (например, таймкод, скриншот, транскрипцию).


2. Детекция сцен (Scene Detection)

Первый шаг — разбить видео на логические сегменты (шоты). Шот — непрерывная последовательность кадров, снятая одной камерой без монтажного склея.

Инструменты

  • PySceneDetect (Python) — детектор сцен по резкому изменению гистограммы или контента.
  • FFmpeg с фильтром scene — быстрый, но менее точный.
  • Глубокие методы (например, TransNetV2) — для сложных переходов (затемнения, наплывы).

Параметры

  • Порог чувствительности (threshold) — чем ниже, тем больше шотов.
  • Обычная длительность шота: 2–5 секунд (для новостей/лекций) или до 10–15 секунд (для фильмов).

Пример кода (PySceneDetect):

from scenedetect import detect, ContentDetector, split_video_ffmpeg

scene_list = detect("video.mp4", ContentDetector(threshold=27))
for i, scene in enumerate(scene_list):
    print(f"Scene {i}: {scene[0].get_timecode()} -> {scene[1].get_timecode()}")

После детекции каждый шот становится единицей индексации (чанком видео).


3. Извлечение ключевых кадров

Для каждого шота нужно выбрать один или несколько ключевых кадров (keyframes), которые репрезентируют визуальное содержание.

Методы

  • Центральный кадр — кадр посередине шота (просто, но может быть неинформативным).
  • Кадр с максимальным изменением — кадр, наиболее отличающийся от предыдущего (например, по гистограмме).
  • Кадр с максимальной чёткостью — оценка резкости (Laplacian variance) для выбора самого чёткого кадра.
  • Кластеризация — все кадры шота эмбеддируются (например, через CLIP) и выбирается центроид.

Рекомендация для большинства сценариев достаточно одного ключевого кадра на шот (центральный или с максимальной чёткостью). Для длинных шотов (>10 сек) можно взять 2–3 кадра.


4. Визуальные эмбеддинги (CLIP)

Ключевой кадр подаётся в мультимодальную модель CLIP (Contrastive Language-Image Pre-training) для получения визуального эмбеддинга.

Почему CLIP

  • Обучался на парах (изображение, текст) — эмбеддинги изображения и текста находятся в общем пространстве.
  • Позволяет искать видео по текстовому описанию (например, "человек в красной шляпе").
  • Размер эмбеддинга: 512 (ViT-B/32) или 768 (ViT-L/14).

Альтернативы

  • ImageBind (Meta) — объединяет 6 модальностей (изображение, текст, аудио, видео, depth, thermal).
  • VideoCLIP — учитывает временную динамику (но сложнее в индексации).

Пример (CLIP):

import clip
import torch
from PIL import Image

model, preprocess = clip.load("ViT-B/32")
image = preprocess(Image.open("keyframe.jpg")).unsqueeze(0)
with torch.no_grad():
    embedding = model.encode_image(image)  # shape (1, 512)

Эмбеддинг нормализуется (L2) для косинусного поиска.


5. Аудиодорожка: транскрипция через Whisper

Параллельно с визуальным рядом обрабатывается аудиодорожка. Основная цель — получить текстовую транскрипцию речи.

Инструмент: Whisper (OpenAI) — модель для распознавания речи (ASR), поддерживает 100+ языков, выдаёт таймкоды для каждого слова.

Процесс

  1. Извлечь аудио из видео (ffmpeg -i video.mp4 -vn audio.wav).
  2. Разбить аудио на сегменты, соответствующие шотам (по таймкодам сцен).
  3. Транскрибировать каждый сегмент через Whisper.
  4. Получить текст и таймкоды слов.

Пример (Whisper):

import whisper

model = whisper.load_model("base")
result = model.transcribe("audio.wav", word_timestamps=True)
for segment in result["segments"]:
    print(segment["start"], segment["end"], segment["text"])

Важно Whisper может работать на GPU, но для больших объёмов лучше использовать оптимизированные рантаймы (whisper.cpp, faster-whisper).


6. Текстовые эмбеддинги

Транскрибированный текст каждого шота превращается в текстовый эмбеддинг с помощью той же модели, что используется для текстовых запросов (например, text-embedding-3-small от OpenAI или intfloat/e5-mistral-7b-instruct).

Особенности

  • Текст может быть коротким (2–5 секунд речи) — используйте модель, хорошо работающую с короткими фрагментами.
  • Если в шоте нет речи (музыка, тишина) — текстовый эмбеддинг можно пропустить или использовать пустой вектор.

Пример (OpenAI):

from openai import OpenAI

client = OpenAI()
response = client.embeddings.create(
    input="Транскрипция шота",
    model="text-embedding-3-small"
)
text_emb = response.data[0].embedding

7. Объединение модальностей (Fusion)

Теперь у нас есть два эмбеддинга на один шот: визуальный (CLIP) и текстовый (Whisper). Их нужно объединить в один вектор для индексации.

Методы объединения

МетодОписаниеПлюсыМинусы
Взвешенное среднееemb = α * vis_emb + (1-α) * text_embПросто, интерпретируемоВес α подбирается эмпирически
Конкатенацияemb = concat(vis_emb, text_emb)Сохраняет всю информациюУдваивает размерность, требует переобучения модели поиска
Attention fusionОбучаемый слой cross-attention между модальностямиАдаптивен к контекстуТребует данных для обучения
Late fusionДва отдельных индекса, поиск по каждой модальности отдельно, затем ранжированиеГибкостьДвойное хранение, сложнее запрос

Рекомендация для старта используйте взвешенное среднее с α=0.6 для текста и 0.4 для визуала (текст обычно информативнее). Если визуал критичен (например, поиск по жестам), увеличьте вес визуала.

Пример (взвешенное среднее):

import numpy as np

alpha = 0.6
combined_emb = alpha * text_emb + (1 - alpha) * vis_emb
combined_emb = combined_emb / np.linalg.norm(combined_emb)  # нормализация

8. Индексация в векторной базе данных

Каждый шот сохраняется как запись в векторной БД (Pinecone, Qdrant, Weaviate, Milvus, FAISS). Поля:

  • id — уникальный идентификатор шота (например, video_id_scene_num).
  • vector — объединённый эмбеддинг (нормализованный).
  • metadata:
    • video_source — имя файла или URL.
    • start_time, end_time — таймкоды в секундах.
    • scene_index — номер шота.
    • transcript — полный текст транскрипции.
    • speaker — если есть диаризация (кто говорит).
    • keyframe_path — путь к сохранённому ключевому кадру (опционально).
    • visual_embedding — отдельно визуальный эмбеддинг (для гибридного поиска).

Пример записи (JSON):

{
  "id": "lecture1_scene42",
  "vector": [0.123, -0.456, ...],
  "metadata": {
    "video_source": "lecture1.mp4",
    "start_time": 125.3,
    "end_time": 128.7,
    "transcript": "Теперь рассмотрим градиентный спуск",
    "speaker": "Профессор Иванов",
    "keyframe_path": "keyframes/lecture1_scene42.jpg"
  }
}

Индексация используйте HNSW (Hierarchical Navigable Small World) для быстрого приближённого поиска (ANN). Настройте ef_construction и M под размер данных.


9. Метаданные и таймстемпы

Метаданные критичны для ответа пользователю. После retrieval нужно не только вернуть текст, но и указать точное время в видео.

Дополнительные метаданные

  • Диаризация спикеров — разделение речи по говорящим (модели pyannote-audio). Позволяет искать по фразам конкретного человека.
  • OCR-текст — если в видео есть текст на экране (слайды, титры). Используйте PaddleOCR или Tesseract на ключевых кадрах.
  • Эмбеддинги звуков — для поиска по неречевым звукам (аплодисменты, музыка). Можно использовать CLAP (Contrastive Language-Audio Pretraining).

Пример использования таймкода в ответе

"Вот фрагмент видео с 2:05 по 2:08, где профессор объясняет градиентный спуск."


10. Продвинутые техники

10.1 Иерархическая индексация

Видео разбивается на несколько уровней: всё видео → главы → сцены → кадры. Поиск сначала на уровне глав, затем уточнение.

10.2 Гибридный поиск (визуал + текст)

При запросе можно вычислять два эмбеддинга (текстовый и визуальный) и искать отдельно, затем объединять результаты через weighted sum или Reciprocal Rank Fusion (RRF).

10.3 Агентный подход

Агент может сначала определить, нужен ли визуальный контекст (например, запрос "как выглядит график?"), и выбрать соответствующую модальность для поиска.

10.4 Сжатие эмбеддингов

Для больших коллекций видео используйте Product Quantization (PQ) для уменьшения размера векторов без сильной потери качества.


11. Проблемы и компромиссы

ПроблемаРешение
Шум в транскрипции (акцент, фон)Использовать Whisper large-v3, постобработка (LLM для исправления)
Пустые шоты (только музыка)Пропускать или добавлять аудио-эмбеддинг (CLAP)
Дублирование информации (один и тот же текст в нескольких шотах)Дедупликация по эмбеддингам (cosine similarity > 0.95)
Скорость индексацииПараллельная обработка шотов, GPU для CLIP и Whisper
Хранение ключевых кадровСжимать в WebP, хранить в object storage (S3)

Пет-проект для закрепления

Задача Создать RAG-систему для поиска по лекциям на YouTube (10–20 видео).

Инструменты

  • yt-dlp — скачивание видео и аудио.
  • PySceneDetect — детекция сцен.
  • CLIP (OpenAI CLIP или open_clip) — визуальные эмбеддинги.
  • faster-whisper — транскрипция.
  • Qdrant (in-memory) — векторная БД.
  • LangChain или простой FastAPIorchestration.

Шаги:

  1. Скачать 5–10 видео-лекций (например, по машинному обучению).
  2. Разбить каждое на шоты (порог 27).
  3. Для каждого шота извлечь ключевой кадр (центральный) и транскрипцию.
  4. Получить визуальный эмбеддинг (CLIP) и текстовый (e5-small).
  5. Объединить взвешенным средним (α=0.6).
  6. Загрузить в Qdrant с метаданными (таймкод, источник, текст).
  7. Написать простой интерфейс: пользователь вводит запрос, система возвращает top-3 шота с таймкодами и ссылками на видео.

Ожидаемый результат Работающий сервис, который на запрос "градиентный спуск" находит точные моменты в лекциях, показывает скриншот и текст. Можно добавить оценку качества (HR@5, MRR) на размеченных 20 запросах.


Связь с другими вопросами

ВопросТема
1Как спроектировать RAG-систему для 10 000 документов
3Стратегии chunking'а (аналогия с разбивкой на шоты)
5Оценка качества retrieval (применимо к видео)
7Уменьшение latency (оптимизация CLIP/Whisper)
10Self-RAG (можно адаптировать для видео)
12Мультимодальные RAG (общая теория)

Навигация