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% на вашому датасеті після донавчання.







