Разработка AI-системы детекции оставленных предметов

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1Все 1566 услуг
Разработка AI-системы детекции оставленных предметов
Средний
~1-2 недели
Часто задаваемые вопросы

Направления AI-разработки

Этапы разработки AI-решения

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1284
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1196
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    901
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1119
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    586
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    853

Разработка системы обнаружения оставленных предметов

Оставленный предмет — сумка у колонны в метро, коробка у стойки регистрации, рюкзак под сиденьем автобуса. Задача выглядит несложной, пока не сталкиваешься с реальным потоком: тысячи кадров в час, где «оставленным» может оказаться тень, статичный мусор, или человек, который на минуту присел рядом с вещью.

Хорошая система оставленных предметов должна держать recall > 90% при False Alarm Rate < 3 в час на камеру в условиях загруженного общественного пространства.

Почему классический motion detection здесь не работает

MOG2 и KNN background subtractors обнаруживают изменения фона, а не факт оставления. Они дают FAR 50–100 событий в час на оживлённой точке — охрана перестаёт реагировать через день эксплуатации.

Реальная задача — не «детектировать статичный объект», а установить причинно-следственную связь: объект был при человеке, человек ушёл, объект остался.

Архитектура детектора оставленных предметов

import cv2
import numpy as np
from ultralytics import YOLO
from collections import defaultdict
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class TrackedObject:
    obj_id: int
    bbox: list
    class_name: str
    last_owner_id: Optional[int]  # track_id человека-владельца
    frames_static: int = 0
    frames_unattended: int = 0
    is_abandoned: bool = False

class AbandonedObjectDetector:
    def __init__(self, model_path: str, config: dict):
        self.detector = YOLO(model_path)
        self.objects: dict[int, TrackedObject] = {}
        self.persons: dict = {}

        # Ключевые пороги — именно здесь вся магия
        self.static_threshold = config.get('static_frames', 90)    # 3 сек @ 30fps
        self.unattended_threshold = config.get('unattended_frames', 150)  # 5 сек
        self.ownership_distance = config.get('owner_dist_px', 150)  # пикселей

        self.bag_classes = ['backpack', 'handbag', 'suitcase',
                            'umbrella', 'sports ball']

    def _find_owner(self, obj_bbox: list,
                    person_tracks: list) -> Optional[int]:
        """Ищем ближайшего человека в радиусе ownership_distance"""
        obj_center = np.array([(obj_bbox[0]+obj_bbox[2])//2,
                                (obj_bbox[1]+obj_bbox[3])//2])

        min_dist = float('inf')
        owner_id = None

        for person in person_tracks:
            p_center = np.array([(person.bbox[0]+person.bbox[2])//2,
                                  (person.bbox[1]+person.bbox[3])//2])
            dist = np.linalg.norm(obj_center - p_center)
            if dist < min_dist and dist < self.ownership_distance:
                min_dist = dist
                owner_id = person.track_id

        return owner_id

    def process_frame(self, frame: np.ndarray) -> list[TrackedObject]:
        results = self.detector.track(frame, persist=True,
                                       classes=[0,24,26,28])  # person+bags
        abandoned = []

        persons = [r for r in results[0].boxes
                   if self.detector.model.names[int(r.cls)] == 'person']
        bags = [r for r in results[0].boxes
                if self.detector.model.names[int(r.cls)] in self.bag_classes]

        for bag in bags:
            bid = int(bag.id) if bag.id is not None else -1
            bbox = list(map(int, bag.xyxy[0]))

            if bid not in self.objects:
                self.objects[bid] = TrackedObject(
                    obj_id=bid,
                    bbox=bbox,
                    class_name=self.detector.model.names[int(bag.cls)],
                    last_owner_id=None
                )

            tracked = self.objects[bid]
            owner = self._find_owner(bbox, persons)

            if owner is not None:
                tracked.last_owner_id = owner
                tracked.frames_unattended = 0  # сброс счётчика
            else:
                if tracked.last_owner_id is not None:
                    tracked.frames_unattended += 1

            # Статичность объекта
            prev_center = np.array([(tracked.bbox[0]+tracked.bbox[2])//2,
                                     (tracked.bbox[1]+tracked.bbox[3])//2])
            curr_center = np.array([(bbox[0]+bbox[2])//2, (bbox[1]+bbox[3])//2])
            if np.linalg.norm(curr_center - prev_center) < 5:
                tracked.frames_static += 1
            else:
                tracked.frames_static = 0

            tracked.bbox = bbox

            if (tracked.frames_static >= self.static_threshold and
                    tracked.frames_unattended >= self.unattended_threshold):
                tracked.is_abandoned = True
                abandoned.append(tracked)

        return abandoned

Проблема «временного хозяина»

Один из главных источников ложных тревог — ситуация, когда человек поставил сумку, отошёл на 2 метра взять кофе, и система уже считает предмет брошенным. Решение — ownership hysteresis: связь «владелец-предмет» разрывается только если расстояние превышает порог в течение N кадров подряд, а не в один момент.

Второй сложный случай: несколько людей стоят рядом с предметом, потом все уходят. Нужно трекать last_owner_id с историей последних 3–5 «хозяев».

Настройка под сценарий

Сценарий static_frames unattended_frames owner_dist_px
Метро, высокий трафик 60 (2 сек) 90 (3 сек) 120
Аэропорт, низкий трафик 150 (5 сек) 300 (10 сек) 180
Офисный холл 300 (10 сек) 600 (20 сек) 200
Склад/стоянка 450 (15 сек) 900 (30 сек) 250

Кейс: вокзал, 12 камер

На одном вокзале запустили naive static object detection — 80+ ложных тревог в смену. Персонал жаловался и стал игнорировать систему. После внедрения ownership tracking с гистерезисом 3 секунды и минимальным размером bbox 40×40 пикселей (фильтр мусора на полу):

  • FAR снизился с 80+ до 4–6 событий в смену
  • Recall на тестовом наборе (30 инсценированных оставлений): 93%
  • Среднее время до тревоги: 8 секунд после фактического оставления

Модель: YOLOv8m, дообученная на 2400 изображениях вокзальных сумок в разных ракурсах. Инференс на NVIDIA T4 — 28ms на кадр при 1080p.

Интеграция и деплой

  • Видеопоток: RTSP через GStreamer или FFmpeg с буфером 200ms
  • Хранение событий: снимок кадра + 30-секундный клип до/после в S3
  • Уведомления: webhook → охрана, Telegram-бот, VMS-интеграция
  • Edge деплой: NVIDIA Jetson Orin (NX 16GB) тянет 8 потоков 1080p@15fps
Масштаб Срок
1–4 камеры, пилот 3–4 недели
10–30 камер, production 6–10 недель
50+ камер, enterprise 14–20 недель