Всем привет, на связи команда NeuroCore. Сегодня расскажем про кейс разработки системы видеоаналитики для магазинов самообслуживания: почему fisheye-камеры - настоящее проклятие, почему SORT и DeepSORT не справились с задачей, как мы выстроили конвейер от детекции до бизнес-событий, и какие инженерные решения позволили добиться стабильной работы в продакшене.

Дано: магазины самообслуживания, которые работают без кассиров и продавцов. Покупатель входит по QR, выбирает товары, рассчитывается и выходит. Заказчику нужна автоматизированная система отслеживания: кто находится внутри, сколько времени, в каких зонах, а также распознает несанкционированный доступ и вход группами. В случае нарушений, система должна генерировать алерты по 7 типам событий.

Что есть: одна потолочная fisheye-камера, которая покрывает весь зал. Это идеальный выбор для ритейла: угол обзора 180 градусов, не нужно ставить десятки обычных камер, не нужно сшивать панорамы. Но за этот комфорт приходится платить.

Что не так с “рыбьим глазом”

Fisheye-объектив проецирует сферическую сцену на плоский сенсор через нелинейное отображение. Модель искажения OpenCV описывается формулой:

θ_dist = θ × (1 + k1×θ² + k2×θ⁴ + k3×θ⁶ + k4×θ⁸)

На практике это означает:

  • Нелинейный масштаб. Один и тот же человек у центра кадра и у края выглядит по-разному: у центра — нормальный силуэт сверху, у края — вытянутый и искривлённый. Смещение на 50 пикселей в центре и у края — это разные реальные расстояния.

  • Форма детекций “плывёт”. Когда человек идёт от центра к краю, его bounding box меняет пропорции от почти квадратного до вытянутого прямоугольника. Трекер, ориентирующийся на IoU, воспринимает это как потерю объекта.

  • Полные окклюзии при виде сверху. Камера смотрит строго вниз, люди ходят друг за другом. Один полностью перекрывает другого, IoU между детекциями падает до нуля за один кадр.

Почему SORT и DeepSORT не справляются

SORT и DeepSORT для трекинга - логичный выбор в данном классе задач. Это стандартные алгоритмы, которые используют для отслеживания людей в видеопотоке.

SORT опирается на IoU между предсказанной и обнаруженной рамкой плюс модель движения на основе фильтра Калмана. На fisheye оба механизма ломаются одновременно: IoU ненадёжен из-за геометрических искажений, модель движения некорректна в неевклидовом пространстве.

DeepSORT добавляет ReID-эмбеддинги, но по-прежнему опирается на IoU и предсказание позиции. При длительных окклюзиях — а на fisheye они норма— ID теряется, потому что трекер не ждёт возвращения человека достаточно долго.

Прогнали оба на реальной записи с торгового зала. При первом же скоплении людей — через 30–40 секунд — ID начали перемешиваться. Счётчик посетителей после этого не восстанавливался.

Архитектура системы

Мы построили pipeline из шести компонентов, каждый из которых решает свою задачу:

Компонент

Технология

Задача

Детектор

YOLO

Обнаружение людей на fisheye кадре

Трекер

DeepOcSort (BoxMOT)

Связывание детекций между кадрами

ReID-модель

OSNet / ResNet50

Извлечение вектора внешности (эмбеддинга)

ReID менеджер

Галерея эмбеддингов + EMA

Восстановление потерянных ID

Менеджер треков

Алгоритмы

Подсчёт посетителей, слияния/разделения

Оценщик скорости

FisheyeUndistorter и VelocityEstimator

Расчёт реальной скорости в м/с

Детекция и трекинг работают на сыром fisheye-кадре, без выпрямления геометрии.Тестировали undistort для компенсации геометрических искажений объектива, которая переводит координаты из искажённого fisheye-пространства в евклидово, чтобы “выпрямить” кадр перед подачей в детектор.

На практике это не дало особого выигрыша. Нейросети детектора и ReID устойчиво работали на искажённом кадре — они обучены достаточно, чтобы справляться с перспективными искажениями. Выпрямление не улучшало качество детекций.

Undistort остался только в одном месте — в расчёте скорости, где он действительно нужен: пиксельное смещение на fisheye нелинейно, и перевести его в метры без выпрямления координат нельзя. Но это точечная трансформация координат, а не рендер всего кадра.

Детекция: YOLO на fisheye

Детектор на базе YOLO фильтрует по классу — из всего, что есть в кадре, нужны только люди. Мелкие детекции отсекаются: на краях fisheye-кадра артефакты искажения дают ложные срабатывания, и без фильтрации по размеру они засоряют трекер.

Проблемой стала форма рамок. ReID-модели обучены на датасетах пешеходов с уличных камер, где человек в кадре вертикальный. Fisheye смотрит сверху, и силуэт человека там часто шире, чем выше. Если подавать такой bbox в ReID без предобработки, модель получает на входе то, на чём не обучалась, и эмбеддинги выходят малоинформативными. Метод normalize_boxes_for_reid приводит рамку к квадратной форме с центрировкой по вертикали — после этого качество ассоциаций заметно выросло.

Трекер: почему OC-SORT, а не DeepSORT

Использовали OC-SORT с ReID-эмбеддингами из библиотеки BoxMOT. Главное отличие от классического SORT: OC-SORT обновляет состояние трека, отталкиваясь от факта наблюдения, а не от предсказания модели движения. В толпе, где траектория непредсказуема, это принципиально.

Параметры, подобранные под fisheye-условия:

w_association_emb = 0.85 - 85% решения об ассоциации принимается на основе сходства внешности.

Формула:

cost = (1 - 0.85) × motion_cost + 0.85 × (1 - cosine_similarity)

iou_thresh = 0.05 - крайне низкий порог IoU. Обычно ставят 0.3–0.5. У нас 0.05, потому что в толпе люди стоят вплотную, их детекты перекрываются и при высоком пороге трекер склеивает двух разных людей в один трек.

max_age = 100 кадров время жизни трека без ассоциации. При 10-15 FPS это примерно 7–10 секунд позволяет удерживать ID, даже если человек надолго пропал.

EMA-обновление эмбеддинга с alpha ≈ 0.985 плавное обновление внешности сглаживает случайные выбросы

ReID-менеджер

ReidManager работает поверх трекера и решает три задачи:

  1. Галерея эмбеддингов. Для каждого активного трека хранится очередь из последних 30 эмбеддингов. На их основе рассчитывается прототип — усреднённый EMA-вектор, представляющий типичную внешность. Чем больше наблюдений, тем стабильнее прототип.

  2. ReLink. Когда BoxMOT создаёт новый трек, менеджер проверяет, не тот ли это человек, которого потеряли раньше. Эмбеддинг нового трека сравнивается с прототипами недавно потерянных. Порог восстановления relink_dist_thresh = 0.25 — если косинусное расстояние ниже, старому треку возвращается его ID. Дополнительно учитывается близость к точке исчезновения: если новый трек появился рядом с тем местом, где пропал старый, порог смягчается. Один ID не может быть присвоен нескольким трекам в одном кадре.

  3. Защита от ID-swap. ID-swap — когда два человека проходят мимо друг друга, их bounding box'ы перекрываются, и трекер меняет их идентификаторы местами. На fisheye это происходит регулярно. Механизм защиты: если новый эмбеддинг сильно отличается от прототипа, он помечается как подозрительный. После двух подозрительных эмбеддингов подряд система отказывается добавлять их в галерею и перекалибрует прототип по последним стабильным наблюдениям.

Защита от подмены ID

Коварная ошибка системы: когда два человека подходят друг к другу, их bounding box'ы сливаются в один, один трек поглощает другой. Затем они расходятся — нужно восстановить потерянный ID. TrackManager обрабатывает это через двухфазную логику:

  • Детекция слияния. Трек пропал, рядом с ним появился другой трек с заметно увеличенным bbox. Критерии: расстояние центров не превышает заданного порога, площадь bbox выросла больше чем в 1.5 раза.

  • Детекция разделения. После зафиксированного слияния рядом с поглотившим треком появляется новая небольшая детекция — система пытается присвоить ей ID поглощённого трека. Восстановление по минимальному расстоянию между центрами с проверкой размера бокса.

Расчёт скорости

Пиксельное смещение на fisheye нельзя прямо переводить в метры — пространство нелинейно. Для расчёта скорости нужно евклидово пространство, поэтому здесь и только здесь применяется undistort — не полный рендер кадра, а точечная трансформация координат.

Конвейер расчёта:

  1. Сглаживание bbox — отдельно позиция центра и размер. Разделение убирает дрожание детекций, но сохраняет отзывчивость на реальное перемещение.

  2. Undistort bbox — четыре угла сглаженного бокса переводятся в выпрямленное пространство.

  3. Расчёт footpoint — при виде сверху через fisheye ноги человека смещены к центру кадра. Используется алгоритм с экспоненциальным затуханием смещения от центра.

  4. Сглаживание footpoint через EMA, чтобы скорость не прыгала от кадра к кадру.

  5. Расчёт скорости — смещение footpoint между кадрами, умноженное на коэффициент пиксель/метр и делённое на dt.

  6. Обратная проекция — footpoint возвращается в искажённые координаты для проверки попадания в зоны.

События: от координат к бизнес-логике

Финальное звено конвейера это бизнес логика. Оно получает список детекций с координатами и скоростью, проверяет попадание в зоны и генерирует семь типов событий:

Событие

Описание

visitor_enter

Посетитель вошёл в зону магазина

visitor_exit

Посетитель покинул зону

visitor_at_checkout

Посетитель вошёл в зону кассы

rapid_movement

Резкие действия посетителя (скорость более 5 м/с)

slow_movement

Длительная остановка (скорость < 0.005 м/с за 5 сек)

long_stay

Длительное нахождение в зоне (> 30 сек)

group_entry

Групповой вход (3+ человек за 5 сек)

Выход из зоны не генерируется мгновенно — через механизм PendingExit с временной задержкой. Если человек ушел за границу полигона и вернулся, это не засчитывается как выход. Без этой логики любое касание края зоны генерировало ложное событие.

Проблемы

Две сложности проявились сразу при работе с реальным потоком.

Первая — счётчик посетителей. Короткие треки, когда человек зашёл в кадр и сразу скрылся за стеллажом, засчитывались как новые при каждом появлении. Добавили задержку: трек фиксируется как новый посетитель только после нескольких секунд устойчивого присутствия. Остаточный эффект — задержка до 5 секунд между фактическим входом и записью в базу.

Вторая — настройка зон. Алгоритм теряет людей чаще, если граница зоны обрезает часть траектории: человек выходит за полигон на несколько кадров и трекер засчитывает это как выход. Для операторов на стороне клиента это оказалось неочевидным. Закрыли документацией с конкретными правилами расстановки зон под типовые планировки — зона должна перекрывать траекторию с запасом, не обрезая её по краю.

Резюме

Вместо того чтобы чинить fisheye-искажения для старых алгоритмов, мы выстроили архитектуру, которая умеет работать с искажениями:

  • ReID-first подход. 85% решения об ассоциации по внешности. Даже если человек полностью пропал из виду и появился в другой части кадра, система узнаёт его по эмбеддингу.

  • Адаптация к геометрии. Детекция и трекинг в искажённом пространстве. Выпрямление точечное, только для скорости. YOLO и ReID работают с теми данными, на которых обучались.

  • Продвинутое управление ID. ReLink восстанавливает потерянные идентификаторы, Anti-Swap защищает от подмены, merge/split обрабатывает слияние и разделение людей.

  • Observation-centric трекинг. OC-SORT не полагается на модель движения — он обновляет состояние трека по факту наблюдения. В толпе, где траектория непредсказуема, это работает надёжнее.

  • Семь типов бизнес-событий с продуманной логикой задержек, warmup-периодов и дедупликации — превращают сырые координаты в полезную аналитику для ритейла.

Какой эффект дала система для заказчика? В рабочей конфигурации система снимает с оператора просмотр видео для подсчёта посетителей, определения времени пребывания у кассы и фиксации событий долгого стояния. Данные пишутся в базу в структурированном виде и подтягиваются в аналитику магазина.

Что по-прежнему требует человека: первичная калибровка зон под конкретную камеру и планировку зала, разбор нестандартных инцидентов — система генерирует событие slow_movement, но интерпретация остаётся на операторе.

Если вы работали с трекингом на нестандартных камерах — потолочных, панорамных, подводных — интересно услышать, какие решения использовали для удержания ID при длительных окклюзиях.

Комментарии (0)