Pull to refresh
Аурига
Аурига — это люди

My weekend pet project или AR за пару дней

Level of difficultyEasy
Reading time8 min
Views1.6K

Одному моему знакомому из фирмы, выпускающей разнообразный мерч, пришла как‑то в голову идея дополнить свою продукцию интерактивной графикой — навел телефон на майку/кружку/скетчбук и получил на экране дополнительную визуальную информацию для данного предмета в виде AR (Augmented Reality) графики поверх видео потока с камеры телефона.

И вот, у нас было несколько телефонов, питон, flask, keras, supervisord, gunicorn, AR.js и выходные на то, чтобы сделать прототип…

Ограничения и возможности

В связи с заявленными дедлайнами («нам бы демку к понедельнику») нужно было сделать все, не привязываясь к магазинам приложений, и максимально однотипноe для всех мобильных платформ (что, к сожалению, мало возможно в принципе в виду разных требований платформ к графическим данным, ну да речь не о том). Поэтому было решено делать UI на AR.js, а серверную часть набросать на Flask»e.

Тех. задание по сути

И так, заказчик хочет, чтобы, цитирую: «Навёл камеру телефона на предмет, и на экране поверх него какая‑то прикольная графика появилась».

Давайте попробуем это формализовать в какие‑то более‑менее приемлемые функциональные требования. Сама по себе «хотелка» состоит из двух частей — «навёл» и «появилась». Давайте разберем их в обратном порядке.

«Появилась»

И так, нам необходимо поверх изображения, снятого камерой телефона, вывести некоторое другое изображение, привязанное к основному. Сама задача напоминает задачу дополненной реальности. Поэтому выбор пал на AR.js, с которым я был более‑менее знаком по каким‑то предыдущим своим pet‑проектам.

Так как мы работаем с полноцветным изображением, а не AR‑маркером, то в AR.js придется использовать NFT — Natural Feature Tracking, технологию которая позволяет использовать «нормальные» изображения вместо специальных AR маркеров. На деле нужно на основе отслеживаемых изображений сгенерировать специфические iset/fset3 файлы, которые затем используются AR.js для того, чтобы выполнить обнаружение и привязку экранных координат к обнаруженному на видео изображению маркера. Дальше, используя функции AR.js можно вывести графику или видео поверх наблюдаемого через камеру изображения, причем как графика, так и видео могут иметь альфа‑канал, что позволяет добиться требуемого выразительного эффекта.

«Навёл»

Вот эта часть, как мне кажется, гораздо интереснее. Нам нужно чтобы на экране телефона отобразилась AR‑сцена которая соответствует ровно тому изображению, на которое наведена камера. Если таких изображений, скажем, 5, то особой проблемы нет. Мы можем для всех 5 изображений создать NFT‑маркеры, видео или графику и загружать всю эту информацию в телефон при открытии странички с веб‑приложением. А если их 500 или 5000 (число разных позиций в ассортименте мерча можете выбрать сами)? То есть решать задачу простым увеличением размера набора данных, загружаемых веб‑приложением на телефон — не выход.

Хорошо, если нельзя иметь информацию для всех вариантов то, очевидно, нужно как‑то сократить этот список. На самом мобильном устройстве данную задачу очевидно не решить. Значит необходимо переносить решение на сторону сервера.

Анализируй это

Итак, мы отправляем изображение, захваченное из видеопотока камеры телефона на сервер. Давайте рассмотрим работу приложения со стороны сервера. Сервер (в общем смысле), помимо очевидной части обеспечения основных функций веб‑приложения (послать страницу, отработать GET|POST запросы), должен хранить набор изображений, соответствующих мерчу и каким‑то образом сообщать приложению в телефоне, а какую собственно AR‑сцену необходимо отобразить. Сервер получает с телефона изображение и ему прежде всего требуется сопоставить это изображение с тем, что у него есть в базе данных для того, чтобы переслать приложению в телефоне данные по NFT и графику для AR‑сцены.

Тут стоит сделать отступление. NFT в AR.js достаточно хорошо распознает изображение и способно различить незначительно отличающиеся картинки (ну до определенного предела, конечно) c высоким процентом успешных случаев. А раз уж оно все равно так хорошо работает, то сервер в плане распознавания изображений может быть чуть менее точным. Но об этом далее.

Трое из ларца, одинаковых с лица

И так, давайте вернемся к картинке, которая к нам пришла на сервер. Допустим, вот такая (картинки из общего доступа, исключительно для иллюстрации):

REI1. Красноглазая девочка номер раз.
REI1. Красноглазая девочка номер раз.

Хорошо, давайте посмотрим, что у нас есть в базе (фон у изображений как правило отсутствует — там альфа‑канал, заменяемый белым цветом):

REI2. Красноглазая девочка номер два.
REI2. Красноглазая девочка номер два.
REI2. Красноглазая девочка номер три.
REI2. Красноглазая девочка номер три.
REI2. Красноглазая девочка номер четыре.
REI2. Красноглазая девочка номер четыре.

Однако, одной картинки с красноглазой девочкой на футболку людям показалось мало. Бывает…

Тут сразу нужно отметить вот что. То, что к нам пришла картинка «вот такая», это еще хороший случай. Горизонт не завален, руки не дрожат, свет и цвета хотя бы похожи на те, которые присутствуют на оригинальном изображении (REI4), пошедшем на принт. Но так будет не всегда. Просто подобрать какие‑то метрики, по которым можно отличить один принт от другого довольно трудно, хорошо работает на трех, вылетает на четвертом. Но как‑то надо решать.

Если своих нейронов не хватает

Пришедшее изображение мало чем отличается от 4 похожих в базе данных, может иметь заранее неизвестный фон подложки и в добавок может имеет качество как будто его сняли на утюг. А не попробовать‑ли поручить решение задачи поиска наиболее подходящего изображения нейросети? Точнее набора наиболее подходящих.

Как уже было сказано ранее AR.js NFT очень хорошо делает своё дело — ищет в кадре именно заданный NFT маркер, так почему бы не дать ему несколько возможных маркеров‑кандидатов, и пусть он сам подберет нужный.

Отлично, а как нам понять на что похоже изображение? В данном случае нам не нужно классифицировать изображение с помощью нейросети (да и посмотрим правде в глаза, лень её учить‑переучивать), нам нужно просто построить вектор признаков для входящего изображения и сопоставить его с таким же набором векторов признаков для изображений, имеющихся в базе данных, и найти наиболее близкие, допустим 5, вариантов. Нейросети, используемые для распознавания изображений можно использовать для выделения признаков (это конечно зависит от архитектуры сети, но в случае CNN это как правило так) и как раз этим свойством мы и воспользуемся, взяв заранее натренированную VGG16. В модель VGG16 загрузим заранее полученные веса, полученные для набора данных «imagenet».

Сама по себе VGG16 представляет из себя набор сверточных слоёв, за которыми следуют 2+1 полносвязных слоя и softmax в конце.

Послойная структура VGG16.
Послойная структура VGG16.

Полносвязные слои (1 х 1 х 4096, нам нужен первый) можно рассматривать как хранящие выделенные признаки, а вся последующая часть модели используется для классификации (и в нашем случае она не нужна). Итак, для каждого изображения в базе мы получаем вектор признаков (4096 значений с плавающей точкой) и теперь можем сравнивать входящие изображения с имеющимися не на основе каких‑либо эмпирических метрик (цвет, размер и т. п.), а на основе выделенных сетью характеристик изображения.

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

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

Сравнение перебором по всей базе, где может в перспективе быть несколько тысяч блобов (англ. Binary Large Object — двоичный большой объект) не выглядит хорошей идеей…

My head is a little fuzzy

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

В качестве прототипа был использован следующий метод:

  • Входной вектор признаков нормируется (0,1).

  • Производится усреднение по выборкам из 32 элементов, по усредненной выборке значения меньше среднего обнуляются, формируется вектор усредненных признаков: больше среднего 1, меньше среднего 0. Таким образом получаются вектора из 128 элементов да/нет [1].

  • Производится повторное усреднение для [1] по выборкам из 8 элементов, и формируется вектор усредненных признаков: больше среднего 1, меньше среднего 0. Таким образом, получаются вектора из 16 элементов да/нет [2].

  • Производится третье усреднение для [2] по выборкам из 2 элементов, и формируется вектор усредненных признаков: больше среднего 1, меньше среднего 0. Таким образом получаются вектора из 8 элементов да/нет [3].

  • Полученные сжатые вектора используются как ключи для поиска списка подходящих полных векторов (25 ключей для каждого полного вектора). Для уменьшения размера строк, по сжатым векторам посчитаны хеш‑значения.

Полученные данным образом наборы хешей сохраняем в базе в виде пар хеш — индекс вектора данных (имя файла в простейшем случае).

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

Метод примитивный, но позволяет сократить объем выборки для поиска в среднем чуть более чем на четверть (в ряде случаев — в половину).

Таким образом для любого входящего изображения будет найден некоторый набор наиболее близких изображений в базе. Для данной задачи ложно‑позитивные срабатывания не имеют значения (это обрабатывается на уровне AR.js — он просто не найдет нужный маркер для отображения в видеопотоке).

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

Собираем все в единое целое

И так, пользователь направляет телефон на предмет, на который нанесено изображение и кадр из видеопотока отправляется на сервер в составе запроса на AR‑сцену, сервер вычисляет вектор признаков для входного изображения, используя VGG16 и вычисляет подобие нечеткого хеша для него, по которому в базе ищет подобные вектора и проводит сравнение признаков входящего изображения с векторами признаков изображений, хранящихся в базе. На основе 5 лучших совпадений сервер формирует ответ, содержащий NFT маркеры и данные AR‑сцены. Телефон, получив набор вероятных NFT маркеров и данные для AR‑сцен, используя AR.js производит обнаружение и позиционирование накладываемой графики (видео), и отображает её на экран.

Данное решение использует заранее обученную нейросеть и позволяет добавлять новые изображения в базу без дообучения используемой сети.

Дальше конечно требуется доводка и работа дизайнеров, но основные моменты «хотелки» были реализованы.

Что в общем и просили.

Скриншот proof of concept.
Скриншот proof of concept.

Tags:
Hubs:
Total votes 5: ↑5 and ↓0+5
Comments1

Articles

Information

Website
hr.auriga.ru
Registered
Founded
Employees
501–1,000 employees
Location
Россия