AI-система розуміння та обробки відео: від пошуку до сумаризації
Потік відео з камер спостереження, записів вебінарів, користувацького контенту — години матеріалу, які потрібно проаналізувати. Вручну дивитися кожен кадр неможливо, а автоматичні рішення часто дають хибні спрацьовування. Ми — команда AI/ML-інженерів з великим досвідом у computer vision та понад 20 виконаними проєктами — побудували систему video understanding, яка виділяє дії, суб'єкти та події, заощаджуючи до 70% часу аналітика. Семантичний пошук на основі ембеддінгів CLIP та FAISS працює в 100 разів швидше за ручний перегляд відеоархівів. Неважливо, що аналізувати: тригодинне відеоспостереження чи тисячну колекцію роликів — система знайде будь-який момент за секунди.
Проблеми, які вирішуємо
- Temporal reasoning: відео — не набір статичних кадрів. Об'єкти рухаються, події розгортаються в часі. Без аналізу часової послідовності система не зрозуміє, що «людина спочатку стояла, потім впала».
- Масштаб даних: 24 години запису @30fps = 2,6 млн кадрів. Обробка кожного кадру нераціональна. Ми використовуємо motion-based sampling та adaptive FPS, щоб знизити навантаження в 10 разів без втрати точності.
- Пошук у великих архівах: стандартні теги не працюють. Потрібен семантичний пошук за змістом: «знайди момент, коли вантажівка заїхала на територію після 22:00».
- Автоматична сумаризація: довгі відео потрібно стискати до ключових сцен зі збереженням сенсу, наприклад, для створення тизерів або дайджестів.
Що таке temporal reasoning і чому це ключова проблема відеоаналітики?
Для розуміння відео модель повинна враховувати не лише вміст кожного кадру, але й їх послідовність. Класичний frame-level детектор не відрізнить «людина присіла» від «людина присіла і не встала» — це різні події. Ми вирішуємо це за допомогою VideoMAE та TimeSformer, які працюють з 3D-згортками в часі. При latency 45ms на 16 кадрів можна аналізувати 8 кадрів на секунду в реальному часі.
Як ми прискорюємо пошук по відеоархіву в 1000+ годин?
Семантичний пошук будується на CLIP-ембеддінгах кадрів, проіндексованих в FAISS. Ми семплюємо кожен N-й кадр, частіше по руху (background subtraction). Запит «людина падає» перетворюється в той самий векторний домен, і пошук по 10 млн векторів займає <100 мс. Докладніше про CLIP можна прочитати в оригінальній статті.
Архітектура video understanding системи
Приклад пайплайну на PyTorch та Hugging Face
import torch
import numpy as np
import cv2
from transformers import AutoProcessor, AutoModelForVideoClassification
class VideoUnderstandingPipeline:
def __init__(self, config: dict):
# Video Action Recognition: VideoMAE або TimeSformer
self.action_model = AutoModelForVideoClassification.from_pretrained(
'MCG-NJU/videomae-base-finetuned-kinetics',
torch_dtype=torch.float16
).cuda()
self.action_processor = AutoProcessor.from_pretrained(
'MCG-NJU/videomae-base-finetuned-kinetics'
)
# Для довгих відео: LLaVA-Video або Video-LLaMA
self.vlm_model = self._load_video_llm(config.get('vlm_model'))
self.clip_duration = config.get('clip_duration', 16) # кадрів
self.fps_sample = config.get('fps_sample', 8) # fps для аналізу
def extract_clips(self, video_path: str) -> list[np.ndarray]:
"""Нарізаємо відео на кліпи для action recognition"""
cap = cv2.VideoCapture(video_path)
original_fps = cap.get(cv2.CAP_PROP_FPS)
sample_interval = max(1, int(original_fps / self.fps_sample))
clips = []
current_clip = []
frame_idx = 0
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
if frame_idx % sample_interval == 0:
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
current_clip.append(frame_rgb)
if len(current_clip) == self.clip_duration:
clips.append(np.array(current_clip))
current_clip = current_clip[self.clip_duration // 2:] # overlap 50%
frame_idx += 1
cap.release()
return clips
@torch.no_grad()
def classify_actions(self, clips: list[np.ndarray]) -> list[dict]:
results = []
for i, clip in enumerate(clips):
inputs = self.action_processor(
list(clip), return_tensors='pt'
).to('cuda')
outputs = self.action_model(**inputs)
probs = torch.softmax(outputs.logits, dim=-1)[0]
top5_probs, top5_ids = probs.topk(5)
results.append({
'clip_idx': i,
'start_frame': i * self.clip_duration // 2,
'actions': [
{
'label': self.action_model.config.id2label[idx.item()],
'probability': prob.item()
}
for prob, idx in zip(top5_probs, top5_ids)
]
})
return results
Відео-пошук: семантичний пошук по відеоархіву
import faiss
from transformers import CLIPProcessor, CLIPModel
class VideoSemanticSearch:
"""
CLIP ембеддінги кадрів → FAISS індекс → пошук за текстом.
Швидкий спосіб знайти «момент, де людина падає» в 1000-годинному архіві.
"""
def __init__(self):
self.clip_model = CLIPModel.from_pretrained(
'openai/clip-vit-large-patch14'
).cuda()
self.clip_processor = CLIPProcessor.from_pretrained(
'openai/clip-vit-large-patch14'
)
self.index = faiss.IndexFlatIP(768) # CLIP ViT-L/14 dim = 768
self.frame_metadata = [] # (video_id, timestamp)
@torch.no_grad()
def index_video(self, video_path: str, video_id: str,
sample_every_n: int = 30):
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
frame_idx = 0
batch_frames = []
batch_meta = []
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
if frame_idx % sample_every_n == 0:
pil_frame = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
batch_frames.append(pil_frame)
batch_meta.append((video_id, frame_idx / fps))
if len(batch_frames) >= 32:
self._process_batch(batch_frames, batch_meta)
batch_frames, batch_meta = [], []
frame_idx += 1
if batch_frames:
self._process_batch(batch_frames, batch_meta)
cap.release()
def _process_batch(self, frames: list, meta: list):
inputs = self.clip_processor(
images=frames, return_tensors='pt', padding=True
).to('cuda')
embs = self.clip_model.get_image_features(**inputs)
embs = embs / embs.norm(dim=-1, keepdim=True)
embs_np = embs.cpu().float().numpy()
faiss.normalize_L2(embs_np)
self.index.add(embs_np)
self.frame_metadata.extend(meta)
@torch.no_grad()
def search(self, query: str, top_k: int = 10) -> list[dict]:
inputs = self.clip_processor(
text=[query], return_tensors='pt', padding=True
).to('cuda')
text_emb = self.clip_model.get_text_features(**inputs)
text_emb = text_emb / text_emb.norm(dim=-1, keepdim=True)
text_np = text_emb.cpu().float().numpy()
faiss.normalize_L2(text_np)
scores, indices = self.index.search(text_np, top_k)
results = []
for score, idx in zip(scores[0], indices[0]):
video_id, timestamp = self.frame_metadata[idx]
results.append({
'video_id': video_id,
'timestamp_sec': timestamp,
'score': float(score)
})
return results
Temporal reasoning: Video-LLM для складних запитів
class VideoLLMAnalyzer:
"""
Video-LLaVA, LLaVA-Video або Qwen2-VL з відео-входом.
Для питань типу «що відбувається в кінці відео?»,
«скільки разів людина подивилася в камеру?»
"""
def __init__(self):
# Qwen2-VL підтримує відео до 256 кадрів
from transformers import Qwen2VLForConditionalGeneration
self.model = Qwen2VLForConditionalGeneration.from_pretrained(
'Qwen/Qwen2-VL-7B-Instruct',
torch_dtype=torch.bfloat16,
device_map='auto'
)
def query_video(self, video_path: str, question: str) -> str:
# Семплюємо не більше 32 кадрів рівномірно
frames = self._sample_frames(video_path, n=32)
# Формуємо prompt з відео-токенами
response = self._generate(frames, question)
return response
Як оптимізувати продуктивність video understanding?
Відео генерує величезний обсяг даних. 24 години запису @ 30fps = 2.6М кадрів. Обробляти кожен кадр нераціонально:
- Motion-based sampling: обробляємо тільки кадри з рухом (background subtraction як фільтр)
- Adaptive sampling: для action recognition достатньо 8fps; для детекції об'єктів — 15fps
- Hierarchical indexing: спочатку scene-level (що відбувається в сцені), потім frame-level
| Завдання | Модель | Latency/кадр |
|---|---|---|
| Action recognition (16 кадрів) | VideoMAE-Base | 45ms |
| Semantic search (CLIP indexing) | CLIP ViT-L/14 | 8ms |
| Video QA | Qwen2-VL-7B | 1.2 сек/кліп |
| Object tracking (весь відеопотік) | YOLOv8 + ByteTrack | 20ms |
Процес впровадження video understanding системи
- Аналіз бізнес-задачі: які сценарії (пошук, трекінг, детекція аномалій), обсяг архіву, вимоги до latency, бюджет.
- Збір та розмітка датасету: з ваших записів відбираємо репрезентативні фрагменти, розмічаємо дії та події (якщо потрібно детектувати специфічні об'єкти — донавчаємо моделі).
- Вибір та донавчання моделей: порівнюємо VideoMAE, TimeSformer, CLIP, Video-LLM за точністю та швидкістю. Fine-tuning на ваших даних підвищує recall на 15-25%.
- Розгортання: (API, Docker, інтеграція з вашою інфраструктурою).
- Тестування та оптимізація: заміряємо Precision/Recall, latency p99, зменшуємо інференс за рахунок ONNX/INT8 quantization.
- Супровід: документуємо пайплайн, навчаємо операторів, надаємо підтримку 1 місяць після впровадження.
Що входить в роботу
- Документація: опис архітектури, model card, API-специфікація.
- Доступи: ви отримуєте навчені моделі, скрипти індексування, приклади використання.
- Навчання: ваш персонал може самостійно запускати пошук та інтерпретувати результати.
- Підтримка: виправлення помилок, консультації з налаштування threshold, допомога при масштабуванні на нові дані.
Терміни орієнтовно
| Тип проєкту | Термін |
|---|---|
| Action recognition система | 4–7 тижнів |
| Семантичний пошук по відеоархіву | 5–8 тижнів |
| Повна video understanding платформа | 10–18 тижнів |
Вартість розраховується індивідуально після аналізу ваших даних. Зв'яжіться з нами — оцінимо проєкт за 2 дні. Отримайте консультацію по ваших відеоархівах. Замовте пілотний проєкт, щоб оцінити точність на ваших даних.
VideoMAE paper: Masked Autoencoders are Data-Efficient Learners for Self-Supervised Video Pre-Training
Додаткові доводи: наш досвід показує, що системи video understanding прискорюють роботу аналітика на 70%. Ми гарантуємо точність не нижче 90% на вашому датасеті після донавчання.







