
Всем привет, на связи команда 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 работает поверх трекера и решает три задачи:
-
Галерея эмбеддингов. Для каждого активного трека хранится очередь из последних 30 эмбеддингов. На их основе рассчитывается прототип — усреднённый EMA-вектор, представляющий типичную внешность. Чем больше наблюдений, тем стабильнее прототип.
-
ReLink. Когда BoxMOT создаёт новый трек, менеджер проверяет, не тот ли это человек, которого потеряли раньше. Эмбеддинг нового трека сравнивается с прототипами недавно потерянных. Порог восстановления relink_dist_thresh = 0.25 — если косинусное расстояние ниже, старому треку возвращается его ID. Дополнительно учитывается близость к точке исчезновения: если новый трек появился рядом с тем местом, где пропал старый, порог смягчается. Один ID не может быть присвоен нескольким трекам в одном кадре.
-
Защита от ID-swap. ID-swap — когда два человека проходят мимо друг друга, их bounding box’ы перекрываются, и трекер меняет их идентификаторы местами. На fisheye это происходит регулярно. Механизм защиты: если новый эмбеддинг сильно отличается от прототипа, он помечается как подозрительный. После двух подозрительных эмбеддингов подряд система отказывается добавлять их в галерею и перекалибрует прототип по последним стабильным наблюдениям.
Защита от подмены ID
Коварная ошибка системы: когда два человека подходят друг к другу, их bounding box’ы сливаются в один, один трек поглощает другой. Затем они расходятся — нужно восстановить потерянный ID. TrackManager обрабатывает это через двухфазную логику:
-
Детекция слияния. Трек пропал, рядом с ним появился другой трек с заметно увеличенным bbox. Критерии: расстояние центров не превышает заданного порога, площадь bbox выросла больше чем в 1.5 раза.
-
Детекция разделения. После зафиксированного слияния рядом с поглотившим треком появляется новая небольшая детекция — система пытается присвоить ей ID поглощённого трека. Восстановление по минимальному расстоянию между центрами с проверкой размера бокса.
Расчёт скорости
Пиксельное смещение на fisheye нельзя прямо переводить в метры — пространство нелинейно. Для расчёта скорости нужно евклидово пространство, поэтому здесь и только здесь применяется undistort — не полный рендер кадра, а точечная трансформация координат.
Конвейер расчёта:
-
Сглаживание bbox — отдельно позиция центра и размер. Разделение убирает дрожание детекций, но сохраняет отзывчивость на реальное перемещение.
-
Undistort bbox — четыре угла сглаженного бокса переводятся в выпрямленное пространство.
-
Расчёт footpoint — при виде сверху через fisheye ноги человека смещены к центру кадра. Используется алгоритм с экспоненциальным затуханием смещения от центра.
-
Сглаживание footpoint через EMA, чтобы скорость не прыгала от кадра к кадру.
-
Расчёт скорости — смещение footpoint между кадрами, умноженное на коэффициент пиксель/метр и делённое на dt.
-
Обратная проекция — 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 при длительных окклюзиях.
ссылка на оригинал статьи https://habr.com/ru/articles/1039354/