Компания
О комапнии
olga@epoch8.co
Контакты
Блог

Детекция сдвигов камеры часть 1

Введение

В магазинах установлены тысячи камер, направленных на полки, холодильники и витрины. На видеопотоке с них работает экосистема моделей: детекторы продуктов, контроль выкладки, поиск out-of-stock, обнаружение разрывов в ассортименте. Все эти модели зависят от прямоугольных областей (вьюпортов), определяющих где находится полка или холодильник. Пока камера не смещается, достаточно один раз размечать такие вьюпорты и использовать их для всех новых кадров. Однако на практике камеры могут двигаться: наклон при уборке, случайный удар рукой или предметом, ослабленное крепление – всё это приводит к смещению обзора. Для моделей это катастрофа: вьюпорты «уезжают», детектор продуктов начинает смотреть в потолок или на покупателей, а отчёты по выкладке превращаются в шум. Обработать такие случаи вручную невозможно (слишком много камер и частые изменения), поэтому нужен автоматический детектор сдвига камеры, который:
  • отличает настоящий сдвиг от изменения содержимого полки;
  • устойчив к внешним факторам (люди, тележки, перестановка товаров и т.д.);
  • масштабируется на сотни камер без ручной перенастройки.
В этой статье мы расскажем, как эволюционировал наш детектор сдвига – от простого подхода на SIFT+RANSAC до современного пайплайна на связке SuperPoint+LightGlue с детектором оборудования – и какую пользу дала каждая итерация.

Постановка задачи и данные

На вход алгоритма подаётся пара последовательных изображений одной и той же сцены: «референсный» кадр и новый кадр с той же камеры. Необходимо: (1) определить, произошёл ли сдвиг камеры; (2) оценить величину сдвига; (3) классифицировать ситуацию по уровням – это позволит правильно обновить вьюпорты и при необходимости отправить задачу модераторам.
Ранний вариант классификации сдвигов имел следующие классы:
  1. всё норм – камера не сдвинута;
  2. смена продуктов – геометрия та же, но изменилось содержимое полки;
  3. сдвиг – небольшой сдвиг камеры;
  4. сильный сдвиг – требуется перестройка вьюпортов;
  5. сшибли камеру / не тот ракурс – кадр сильно отличается от предыдущего (грубое смещение).
Позже классификация была упрощена до практичных классов, с которыми работают модерация и автоматический фикс:
  • without – камера стоит ровно (нет сдвига);
  • normal – небольшой сдвиг, который можно компенсировать автоматически;
  • high – сильный сдвиг (нужна ручная или полуавтоматическая перенастройка);
  • unknown / мусор – кадр некорректный (закрыт препятствием, чёрный экран и т.п.) либо алгоритм не уверен в результате.

Датасеты

Для обучения и проверки детектора мы используем реальные данные – пары изображений, снятые на продакшене с разных камер и в разных магазинах. Каждая пара – два последовательных кадра во времени: берётся кадр t, затем кадр t+1 (следующий по времени), это одна пара; далее кадр t+1 берётся в качестве начала новой пары с t+2, и так далее. Такой способ формирования выборки сохраняет естественное поведение камеры и сцены.
изображение (1) (2).png
Разметка классов (without, normal, high, смена продуктов, мусор и т.д.) выполнялась полуавтоматически по следующей схеме:
  1. сначала на сырых данных запускался текущий детектор сдвигов;
  2. результаты просматривались вручную;
  3. ошибочные и спорные случаи переразмечивались вручную и добавлялись в итоговый датасет для обучения и валидации.
Со временем у нас появилось несколько типов датасетов под разные задачи:
  1. Датасеты со сменой продуктов. Пары кадров, где геометрия сцены не меняется, но внутри полки существенно меняется ассортимент. Одни товары заменяются другими, меняется выкладка, появляются или исчезают яркие объекты. Такие данные нужны, чтобы детектор не путал смену ассортимента со сдвигом камеры.
  2. Датасеты со сложными сдвигами камеры. Пары с диагональными сдвигами, поворотами, перспективными искажениями, небольшими наклонами и нестандартными ракурсами. Эти данные проверяют, умеет ли алгоритм работать не только с простым горизонтальным/вертикальным смещением, но и с более сложными трансформациями.
  3. Крупные временные срезы. Большие датасеты, собранные за разные периоды времени. Они нужны для оценки работы детектора в реальном прод-потоке с разными схемами выкладки, сезонными изменениями, перестановками оборудования. На таких срезах мы смотрим общее покрытие случаев, долю детектированных сдвигов и долю пропусков.
  4. Негативный датасет без сдвигов. Набор пар, где камера гарантированно стоит ровно, а различия между кадрами связаны только с движением товаров или людей. Этот датасет используется как sanity check, чтобы убедиться, что новая версия детектора не начнёт «находить» сдвиги там, где их нет, и не станет принимать каждую смену ассортимента за сильный сдвиг.
Такое разбиение на типы датасетов позволяет отдельно тестировать разные аспекты модели: устойчивость к сменам продуктов, способность находить сложные сдвиги, стабильность на больших временных интервалах и отсутствие ложных тревог на статичных камерах.

Базовый алгоритм: SIFT + RANSAC + KNN

Идея. Самый первый рабочий детектор был полностью геометрическим:
  1. Извлекаем SIFT-фичи на двух кадрах (оригинальном и новом).
  2. Матчим найденные дескрипторы (например, KNN или FLANN-матчинг).
  3. По совпавшим точкам оцениваем глобальное преобразование через RANSAC.
  4. Из полученной аффинной трансформации берём только компоненту сдвига (dx, dy), так как нас интересует именно сдвиг камеры, а не масштаб/поворот.
  5. Переносим все вьюпорты на новом кадре на вектор (dx, dy).
Для оценки трансформации использовалась функция cv2.estimateAffinePartial2D с методом RANSAC, ограниченным моделью «только сдвиг». Далее, по модулю dx, dy и количеству матчей применялись простые пороги: если совпадений мало – кадры считаются одинаковыми; если модуль сдвига по любой оси больше порога – считаем случай high и поднимаем задачу модератору.

Что сломалось в реальности

Что сломалось в реальности. На аккуратных примерах с полкой такой подход работал хорошо: нормальные и сильные сдвиги детектировались, вьюпорты сдвигались корректно.
Пример правильного поиска и коррекции сдвинутого viewport’a. Желтым показан изначальный viewport, зеленым сдвинутый при помощи алгоритма
Пример правильного поиска и коррекции сдвинутого viewport’a. Желтым показан изначальный viewport, зеленым сдвинутый при помощи алгоритма
Но в реальных магазинах всплыли три большие проблемы:
Смена продуктов. Внутри вьюпортов периодически полностью меняется ассортимент (например, апельсины → яблоки, одно пиво → другое), меняют формат выкладки. SIFT-ключевые точки привязаны к текстурам и локальным паттернам, поэтому: матчей становится мало или, наоборот, они «расползаются» по всей полке; RANSAC начинает подгонять “среднюю” трансформацию по шумным точкам; в итоге мы фиксируем «якобы сдвиг», которого на самом деле нет (ложная тревога).
Пример определения ложного сдвига при смене выкладки. Желтым показан изначальный viewport, зеленым сдвинутый при помощи алгоритма.
Пример определения ложного сдвига при смене выкладки. Желтым показан изначальный viewport, зеленым сдвинутый при помощи алгоритма.
Неправильная коррекция вьюпортов. Иногда реальный сдвиг камеры обнаруживается, но вьюпорты сдвигаются некорректно. Например, поворот или перспектива – чистым трансляционным смещением не описываются, SIFT-матчи ведут себя странно, появляется асимметрия по осям и «сломанные» оценки RANSAC. Некоторые ошибки начинаются уже на границе небольших (normal) сдвигов и дают смещение по товарам и ценникам (вьюпорт уехал не туда).
Пример правильно найденного сдвига с неправильной коррекцией viewport’a
Пример правильно найденного сдвига с неправильной коррекцией viewport’a
Обстаклы и частичная видимость. временные препятствия в кадре (люди, тележки, двери холодильников и т.п.). Алгоритм не всегда справлялся, если на одном из кадров часть полки закрыта или исчезла из-за посторонних объектов. В отличие от товаров, такие объекты не являются частью сцены и при неподвижной камере либо остаются на месте, либо временно перекрывают обзор, но не «переезжают» сами по себе. Тем не менее, обстаклы могли приводить к нестабильным SIFT-сопоставлениям и ошибкам детектора.

Эвристики по гистограммам SIFT: как отличать сдвиг от смены продуктов

Чтобы научить детектор отличать настоящий сдвиг камеры от смены выкладки товаров, во второй версии мы добавили набор правил на основе распределения SIFT-сдвигов.
Наблюдение: Если смотреть не только на средний вектор сдвига (dx, dy), но и на распределение всех векторов между двумя кадрами, поведение при сдвиге камеры и при смене продуктов заметно отличается. При реальном сдвиге камеры распределение по X и Y имеет один выраженный пик, и большая часть матчей концентрируется около него; дисперсия умеренная.
Пример распределения SIFT при реальном сдвиге камеры
Пример распределения SIFT при реальном сдвиге камеры
При смене продуктов без сдвига может одновременно наблюдаться разброс и в положительную, и в отрицательную стороны по одной оси.
Пример распределения SIFT при смене продуктов
Пример распределения SIFT при смене продуктов
Это навело нас на идею: описать форму гистограммы SIFT-сдвигов несколькими простыми правилами и использовать их, чтобы «гасить» ложные срабатывания на смену ассортимента.
Новые правила. В обновлённом детекторе мы добавили три ключевые эвристики:
  1. Нулевая доля SIFT-сдвигов.
  2. Если хотя бы 10% сопоставлений SIFT находятся около нуля, это считается признаком смены продуктов, а не сдвига камеры.
  3. «Толстая» дисперсия.
  4. Если распределение сдвигов по одной из осей слишком широкое (высокая дисперсия) и нет выраженного пика это тоже смена продуктов.
  5. Разброс в обе стороны от нуля.
  6. Если по оси есть и положительные, и отрицательные сдвиги это сильный сигнал, что внутри полки просто поменяли товары, а камера стоит на месте.
Дополнительно мы:
  • пересмотрели пороги по количеству SIFT-сопоставлений:
  • если не удаётся найти достаточно матчей, функция теперь возвращает «уровень похожести = 1.0» (изображения считаются одинаковыми), а не 0 раньше это ошибочно трактовалось как сильный сдвиг;
  • ввели адаптивный порог сдвига, зависящий от размера картинки:
  • по X: adaptive_max_shift_percent * ширина;
  • по Y: adaptive_max_shift_percent * высота.
  • Если рассчитанный dx или dy превышает этот порог, сдвиг считается аномальным и сбрасывается.

Результаты

На нескольких независимых датасетах обновлённые правила дали заметный выигрыш. По сути, эти простые правила «на гистограмму» позволили:
  • почти полностью убрать ложные срабатывания на смену ассортимента;
  • сохранить чувствительность к реальным сдвигам;
  • не усложнять сильно пайплайн.
Однако остался ряд проблем. Правила описывали большинство кейсов, но не все. Часть из них была довольно избыточной, и любой кадр, который подпадал хотя бы под одно правило, мы перестраховываясь отправляли модераторам. В результате вместе с реальными сдвигами в модерацию уходило много пограничных и неочевидных пар, поток тикетов быстро рос, а нагрузка на людей почти не уменьшалась.
Кроме того, правила плохо работали в сложных сценах. Например, когда на полке одновременно происходила заметная смена ассортимента, формально подходящая под критерии гистограмм, и при этом был небольшой, но реальный сдвиг камеры. В таких ситуациях детектор уверенно сигнализировал только о смене продуктов, а корректировать viewport приходилось вручную. Именно эти остаточные кейсы и стали мотивацией для следующего этапа, где мы начали искать более гибкое, обучаемое поверх правил решение.
Продолжение: часть 2.

Огромное спасибо нашим инженерам, Александру Коротаевскому и Артему Сметанину, за подготовленную статью.
Компьютерное зрение ИИ