От object detection к классическому CV: разрабатываем сканер для слабых смартфонов

от автора

Привет хабр!

Мы разрабатываем приложение для управления линейным персоналом.

Хочу рассказать об одной из самых сложных наших разработок — динамического визуального кода, который должен считываться камерой обычного смартфона в реальном времени.

Внутри продукта эта технология называется MotionCode. Внешне всё выглядит довольно просто: панель воспроизводит последовательность визуальных элементов, камера телефона считывает их, а приложение восстанавливает код.

На практике путь от первого прототипа до стабильной работы оказался гораздо сложнее. Мы успели обучить модель для object detection, столкнуться с ограничениями мобильного железа, отказаться от нейросети и почти без опыта в компьютерном зрении построить собственный real-time-пайплайн обработки кадров.

Зачем нам понадобился динамический код

Одна из задач приложения — подтверждать фактическое присутствие сотрудника на рабочем объекте с помощью спцециализированой панели которая предоставляет визуальный код для считывания камерой.

Статический QR-код решает эту задачу только частично. Его можно сфотографировать, сохранить или переслать коллеге. Геолокация также не является универсальным решением: внутри помещений она работает нестабильно, а на некоторых объектах её использование ограничено технически или организационно.

Нам требовался код, который:

  • существует только в конкретный момент времени;

  • привязан к определённой панели и объекту;

  • постоянно меняется;

  • считывается обычной камерой телефона;

  • не требует специализированного терминала;

  • работает на устройствах разной производительности.

Так появилась идея MotionCode — визуальной последовательности, которую недостаточно один раз сфотографировать и сохранить.

Первая идея: поручить распознавание нейросети

Первую реализацию мы построили на object detection.

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

На мощных устройствах прототип действительно работал.

Проблемы начались, когда мы перешли от демонстрации к реальным сценариям.

Панель могли сканировать:

  • недорогим Android-смартфоном;

  • устройством с небольшим объёмом оперативной памяти;

  • телефоном с медленным автофокусом;

  • перегретым после продолжительной работы устройством;

  • камерой при плохом освещении;

  • под углом;

  • с бликами или муаром от экрана.

Для object detection было недостаточно один раз обработать фотографию. MotionCode меняется во времени, поэтому модель должна работать постоянно и достаточно часто анализировать новые кадры.

Получался тяжёлый цикл:

Обратботка с участием нейросети

Обратботка с участием нейросети

На слабых устройствах обработка одного кадра занимала слишком много времени. Частота распознавания снижалась, интерфейс начинал дёргаться, телефон нагревался, а код иногда успевал изменить состояние раньше, чем приложение заканчивало анализ предыдущих кадров.

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

В какой-то момент мы поняли: модель решает гораздо более общую задачу, чем нам требуется.

Нам не нужно находить произвольные объекты в неизвестной сцене. Мы заранее знаем структуру панели, расположение ячеек и возможные состояния каждой из них.

Значит, нейросеть здесь была не единственным и, как выяснилось, не самым подходящим инструментом.

Решение, которое сначала казалось шагом назад

Отказаться от модели было непросто. В object detection уже вложили много времени: собирали и размечали данные, обучали модель, тестировали её и интегрировали в мобильное приложение.

При этом серьёзного опыта разработки собственных алгоритмов компьютерного зрения у нас не было.

Но у классического CV было важное преимущество: мы могли контролировать стоимость каждой операции и выполнять только ту работу, которая действительно необходима.

Новый пайплайн в упрощённом виде стал выглядеть так:

CV пайплайн

CV пайплайн

Вместо вопроса «какие объекты находятся на изображении?» мы стали задавать гораздо более конкретный вопрос:

Какие из заранее известных ячеек активны на этом кадре?

Это существенно уменьшило объём вычислений.

Сначала найти панель, затем перестать искать её заново

Обрабатывать весь кадр камеры на каждом проходе невыгодно. Большая часть изображения вообще не относится к нашему коду.

Поэтому первым этапом стала область интереса — ROI. Сканер ищет характерную геометрию панели внутри ограниченной зоны, после чего сохраняет найденные координаты и отслеживает их на следующих кадрах.

Пока положение телефона существенно не меняется, повторный полный поиск не требуется. Алгоритм работает преимущественно внутри уже найденной области.

Но ROI нельзя просто зафиксировать навсегда. Руки пользователя двигаются, автофокус меняет изображение, а панель может ненадолго исчезнуть из кадра. Поэтому область поиска постепенно корректируется и сбрасывается только после нескольких неудачных кадров.

Так мы избавились от постоянного повторения одной из наиболее дорогих операций.

Ячейка — это не просто светлый пиксель

На первый взгляд состояние ячейки можно определить по средней яркости. Если область светлая — ячейка активна, если тёмная — неактивна.

В реальности такой подход быстро ломается.

На результат влияют:

  • яркость экрана панели;

  • автоматическая экспозиция камеры;

  • блики;

  • тени;

  • угол съёмки;

  • цветовая температура;

  • мерцание дисплея;

  • муар;

  • соседние активные элементы.

Поэтому мы оцениваем не отдельный пиксель, а несколько характеристик области вокруг ожидаемого центра ячейки. В частности, сравниваем внутреннюю часть элемента с окружающей областью.

Это помогает отличить действительно активную ячейку от блика или общего повышения яркости кадра.

Дополнительно алгоритм проверяет обе возможные полярности изображения. В одних условиях активная ячейка выглядит светлее фона, в других из-за экспозиции и особенностей экрана устойчивее определяется обратное соотношение.

Для каждого кандидата рассчитывается условная уверенность, после чего выбирается наиболее согласованная комбинация.

Почему одного удачного кадра недостаточно

Даже хороший алгоритм иногда ошибается на отдельном кадре. Причиной может стать движение телефона, кратковременный расфокус или момент обновления изображения на панели.

Поэтому сканер не доверяет единичному результату.

Он собирает наблюдения во времени и проверяет, что найденные состояния:

  • повторяются на нескольких кадрах;

  • появляются в ожидаемой последовательности;

  • имеют достаточную уверенность;

  • не противоречат соседним состояниям;

  • укладываются в допустимые временные интервалы.

Временная составляющая оказалась не менее важна, чем анализ отдельного изображения.

MotionCode — это не статичная картинка, а процесс. Поэтому и распознавать его нужно как процесс.

Долгие часы калибровок

После создания базового алгоритма началась, пожалуй, самая долгая часть работы — калибровка.

Нужно было подобрать размеры областей выборки, допустимые отклонения координат, пороги яркости, коэффициенты уверенности, скорость обновления ROI и количество подтверждающих кадров.

Параметры, идеально работавшие на одном телефоне, могли давать ошибки на другом. Настройки для яркого монитора плохо переносились на тёмный экран планшета. Увеличение устойчивости к бликам иногда снижало чувствительность при слабом освещении.

Отдельной задачей стала согласованность iOS и Android. Камеры этих платформ по-разному передают кадры, управляют экспозицией и представляют данные изображения. Поэтому полностью общей реализации оказалось недостаточно: высокоуровневая логика совпадает, но низкоуровневая обработка адаптирована под каждую платформу.

Большая часть работы выглядела примерно так:

  1. Запустить панель.

  2. Навести конкретный телефон.

  3. Получить ошибочное состояние.

  4. Сохранить диагностические данные.

  5. Изменить один параметр.

  6. Повторить тест при другом освещении.

  7. Обнаружить, что исправление сломало предыдущий сценарий.

  8. Вернуться на несколько шагов назад.

И так много раз.

На этом этапе стало понятно, что разработка компьютерного зрения — это не только выбор алгоритма. Это ещё и систематическая работа с большим количеством пограничных условий.

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

По сути, AI стал для нас дополнительным инженерным инструментом. Модель формулировала гипотезы и помогала быстрее готовить эксперименты, а мы проверяли их на реальных устройствах, при разном освещении и под разными углами. Это не избавило нас от ручного тестирования, но сократило количество слепых итераций и позволило сделать калибровку более системной.

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

Что происходит между сервером, панелью и сканером

Мы не будем подробно раскрывать всю внутреннюю архитектуру, но общую идею можно описать.

Сервер создаёт временный числовой код и отдельную матрицу соответствий. Исходное число не отправляется на панель в открытом виде.

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

Сканер считывает состояния ячеек и собирает последовательность индексов. Затем приложение получает от бэкенда матрицу, относящуюся к конкретной короткоживущей сессии, и восстанавливает исходное значение.

Упрощённо обмен выглядит так:

MotionCode

MotionCode

На сервере хранится не только матрица, но и проверочное представление кода. Сессия живёт ограниченное время, а успешное считывание связывается с конкретной панелью и пользователем.

За счёт этого скриншот или запись старой последовательности быстро теряют практический смысл.

Почему классический CV в итоге победил

Главным преимуществом нового подхода стала предсказуемость.

Мы знаем:

  • сколько ячеек нужно проверить;

  • где примерно они должны находиться;

  • какие состояния допустимы;

  • какая последовательность считается корректной;

  • сколько вычислений выполняется на каждом кадре.

В object detection производительность в значительной степени зависела от модели, размера входного изображения и возможностей устройства. В классическом пайплайне мы можем отдельно оптимизировать каждый этап и пропускать ненужную работу.

Кроме того, для обновления логики больше не требуется собирать датасет, размечать новые изображения и переобучать модель. Многие проблемы теперь решаются изменением конкретного этапа обработки.

Это не означает, что классическое компьютерное зрение всегда лучше нейросетей. Если бы нам требовалось находить произвольные объекты в неизвестной сцене, модель была бы естественным выбором.

Но в задаче с заданной геометрией и ограниченным набором состояний специализированный алгоритм оказался быстрее, легче и управляемее.

Что получилось:

Сканирование MotionCode

Сканирование MotionCode

Что мы вынесли из этой разработки

Первый вывод — не каждая задача компьютерного зрения требует нейросети.

Object detection позволил быстро создать прототип и доказать, что сама идея работает. Но попытка превратить прототип в массовую мобильную функцию показала ограничения выбранного подхода.

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

Третий вывод — real-time-система должна учитывать время. Недостаточно хорошо распознать один кадр: нужно контролировать частоту анализа, устойчивость последовательности и смену состояний.

И наконец, отсутствие экспертизы в конкретной области не всегда означает, что от задачи нужно отказаться. Иногда это означает много документации, экспериментов, неудачных гипотез и часов калибровки.

Сейчас MotionCode работает без постоянного инференса тяжёлой модели и остаётся доступным для устройств, которые не справлялись с первой версией технологии.

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

Идея остаётся прежней: вместо статичного секрета пользователь считывает короткоживущую визуальную последовательность, связанную с конкретной сессией и доверенной панелью. Пока это направление находится в планах, но сама архитектура MotionCode позволяет развивать технологию далеко за пределами первоначальной задачи.

А у нас осталось ещё немало историй: о внедрении AI в продукт, использовании AI в разработке, автоматизации тестирования и решениях, которые сначала казались удачными, но не пережили встречу с реальными пользователями.

Если такой формат окажется интересен, расскажем о них в следующих статьях.

ссылка на оригинал статьи https://habr.com/ru/articles/1067188/