Unwanted Behavior Detection System via Video Analytics

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1 servicesAll 1566 services
Unwanted Behavior Detection System via Video Analytics
Complex
~1-2 weeks
FAQ
AI Development Areas
AI Solution Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

System Development распознавания нежелательного поведения (Behavior Detection)

Детекция нежелательного поведения — задача на стыке object detection, pose estimation и action recognition. Система должна понять не только «что находится в кадре», но и «что происходит»: драка, нападение, преследование, вандализм, попытка кражи. Сложность: граница между нормальным и нежелательным поведением размытая, и высокий recall требует работы с неизбежными ложными срабатываниями.

Иерархия обнаруживаемых событий

Уровень 1 (rule-based): простые события, определяемые правилами по позиции и движению объектов — не требуют ML. Быстро, низкое CPU.

Уровень 2 (pose-based): события на основе скелета человека. MediaPipe / RTMPose + анализ углов суставов и скорости движения.

Уровень 3 (video-based): глубокое видео-понимание через 3D CNN / Video Transformer. Высокая точность, требует GPU.

Детекция драки/агрессии

import torch
import torch.nn as nn
from torchvision.models.video import r3d_18, R3D_18_Weights

class FightDetector:
    def __init__(self, model_path: str, threshold: float = 0.7):
        base = r3d_18(weights=R3D_18_Weights.KINETICS400_V1)
        base.fc = nn.Sequential(
            nn.Linear(512, 128),
            nn.GELU(),
            nn.Dropout(0.4),
            nn.Linear(128, 2)  # fight / no_fight
        )
        base.load_state_dict(torch.load(model_path))
        base.eval()
        self.model = base
        self.threshold = threshold

        # Скользящее окно видео
        self.frame_buffer = []
        self.window_size = 16  # 16 кадров = ~0.5 сек при 30fps

    def update(self, frame: np.ndarray) -> dict | None:
        """Обновление буфера и получение результата"""
        self.frame_buffer.append(frame)
        if len(self.frame_buffer) > self.window_size:
            self.frame_buffer.pop(0)

        if len(self.frame_buffer) == self.window_size:
            return self._classify_window()
        return None

    @torch.no_grad()
    def _classify_window(self) -> dict:
        # [T, H, W, C] → [1, C, T, H, W]
        clip = np.stack(self.frame_buffer)
        clip = torch.from_numpy(clip).float().permute(3, 0, 1, 2)
        clip = self._normalize(clip).unsqueeze(0)

        logits = self.model(clip)
        probs = torch.softmax(logits, dim=1).squeeze()
        fight_prob = float(probs[1])

        return {
            'fight_detected': fight_prob > self.threshold,
            'confidence': fight_prob
        }

Skeleton-based Behavior Analysis

import numpy as np
from collections import deque

class BehaviorAnalyzer:
    def __init__(self, window_size: int = 30):
        self.track_history = {}  # track_id -> deque of (frame, keypoints)
        self.window = window_size

    def update(self, track_id: int, frame_num: int,
               keypoints: dict) -> dict:
        if track_id not in self.track_history:
            self.track_history[track_id] = deque(maxlen=self.window)
        self.track_history[track_id].append((frame_num, keypoints))

        if len(self.track_history[track_id]) < 10:
            return {'behavior': 'unknown'}

        return self._analyze(track_id)

    def _analyze(self, track_id: int) -> dict:
        history = list(self.track_history[track_id])
        keypoints_seq = [kp for _, kp in history]

        behaviors = {
            'fall': self._detect_fall(keypoints_seq),
            'running': self._detect_running(keypoints_seq),
            'crouching': self._detect_crouching(keypoints_seq[-1]),
            'loitering': self._detect_loitering(keypoints_seq)
        }

        dominant = max(behaviors, key=lambda k: behaviors[k])
        return {
            'behavior': dominant if behaviors[dominant] > 0.5 else 'normal',
            'scores': behaviors
        }

    def _detect_fall(self, seq: list) -> float:
        """Детекция падения: резкое снижение центра масс"""
        hip_ys = []
        for kp in seq:
            if kp.get('left_hip') and kp.get('right_hip'):
                avg_hip_y = (kp['left_hip']['y'] + kp['right_hip']['y']) / 2
                hip_ys.append(avg_hip_y)

        if len(hip_ys) < 10:
            return 0.0

        # Нормализованные координаты: y растёт вниз
        max_drop = max(hip_ys[-5:]) - min(hip_ys[-15:-5]) if len(hip_ys) >= 15 else 0
        return min(1.0, max_drop / 0.3)  # 0.3 = 30% высоты кадра

    def _detect_loitering(self, seq: list) -> float:
        """Детекция задержки: человек долго на одном месте"""
        if len(seq) < 20:
            return 0.0
        positions = [(kp.get('nose', {}).get('x', 0.5),
                      kp.get('nose', {}).get('y', 0.5))
                     for kp in seq]
        positions = np.array(positions)
        spread = np.std(positions, axis=0).mean()
        return min(1.0, (0.05 - spread) / 0.05)  # < 5% spread = loitering

Ложные срабатывания и коррекция

Главная проблема агрессивных детекторов — false positives. Подход:

  • Temporal confirmation: событие подтверждается только если детектируется N кадров подряд
  • Multi-evidence fusion: комбинирование skeleton + video + context (время суток, зона)
  • Human-in-the-loop: подозрительные события отправляются на проверку охраннику, подтверждённые используются для дообучения
Тип поведения Precision Recall
Падение 91% 94%
Бег/спешка 88% 92%
Агрессия/драка 82% 88%
Вандализм 79% 83%
Кража в кармане 74% 79%
Масштаб Срок
2–3 типа событий, rule-based + pose 4–6 недель
Полная аналитика поведения 9–14 недель
Высокоточная система с обучением 14–22 недели