Azure Custom Vision без Azure, или «где у них маска». Как мы распознавали маску на лице (и других частях тела)

Среди набора примеров для Azure на GitHub был найден один очень интересный: распознавание образов на Raspberry Pi, в офлайне. Авторами предлагается подготовить модель машинного обучения в одном из облачных сервисов Azure, затем перенести ее на компьютер, у которого большую часть времени нет подключения к Интернет, после чего распознавание образов будет работать автономно. Разработчики подготовили проект для двух платформ: ARM32 (собственно Raspberry Pi) и AMD64 (но без поддержки веб-камеры).

На волне коронавирусного хайпа мы решили адаптировать пример для распознавания, надета маска на человеке или нет. Сразу выяснилось, что шаги по адаптации примера не вполне очевидны. В этом материале рассмотрим последовательно, как этот пример, во-первых, модифицировать для работы со своей моделью машинного обучения, во-вторых, перенести его на платформу AMD64. Будем считать, что наш компьютер, на котором работает модель, управляет турникетом: если человек пришел в маске, турникет открывается, если нет — остается закрытым.

Все сложно...

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

В Azure Computer Vision, например, даже не нужно обучать модель перед ее использованием: есть набор облачных API, уже готовый к применению. Для более сложных задач есть Azure Custom Vision, где мы сначала обучаем модель, потом ей пользуемся. Причем знание специфической математики практически не требуется, все делается прямо в браузере мышкой. Знание пары терминов из машинного обучения все-таки понадобится, чтобы понять, насколько качественно работает модель.

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

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

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

  • Для всего придется разрабатывать свой код и настраивать все вручную;
  • Непонятно, как впоследствии масштабировать решение. Что, если таких "турникетов" нужно 100 штук?

Что делать?

Итак, есть вариант "только облако", есть "совсем без облака". Оба варианта — крайности. Было бы удобно взять лучшее от облачных сервисов, но "спустить их на землю". Например, однократно обучить модель на Azure Custom Vision, не углубляясь в вопросы математики, а затем использовать эту модель автономно, без подключения к Интернет.

Такое решение уже существует: Azure IoT Edge позволяет использовать предварительно экспортированную модель машинного обучения без постоянного подключения к Интернет. При этом мы получаем все преимущества и со стороны облака, и со стороны полностью наземного решения:

  • Будем передавать в облако только необходимую телеметрию;
  • Для обучения модели — практически неограниченные объемы хранилища и вычислительной мощности;
  • Быстрый цикл управления, т.к. все решения принимаются локально, а не в облаке;

Azure IoT Edge и с чем его едят

Подробно мы описывали IoT Edge в этой статье. Вкратце, IoT Edge включает среду выполнения — демон Linux, "внутри" которого выполняются модули, которые, в свою очередь, являются Docker-совместимыми контейнерами. Поддержка устройств IoT Edge включена в Azure IoT Hub. Среда выполнения IoT Edge единожды устанавливается на устройство, а затем набор модулей конфигурируется через облако, с портала Azure, после чего компьютер с IoT Edge может работать автономно, без подключения.

"Родной" платформой для IoT Edge по архитектурным причинам является Linux, хотя с 2019 года IoT Edge доступен и для Windows 10 Enterprise LTSC — ОС для устройств специального назначения.

Модули IoT Edge могут содержать произвольный код, или в модули можно "обернуть" следующие службы Azure:

В нашем случае интерес представляет как раз Custom Vision. В этом сервисе мы подготовим модель распознавания маски и "обернем" ее в модуль.

В IoT Edge заложен механизм обмена сообщениями с между модулями и между модулями и облаком. Сами сообщения передаются в подобии текстового формата, поэтому такой механизм плохо подходит для обмена сырыми двоичными данными (картинками), поэтому в исходном примере от Microsoft для обмена картинками с камеры между модулями используется HTTP, т.е. один из обменивающихся данными модулей является web-сервером, а другой клиентом.

Пример распознавания изображений от Microsoft

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

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

Откуда на этом устройстве возьмется среда IoT Edge? Ее необходимо установить вручную, и после того, как она установит подключение к IoT Hub, ей можно (и нужно) управлять уже с IoT Hub. Под "управлением" я также подразумеваю установку модулей.

Модули устанавливаются по команде с IoT Hub. Причем на IoT Edge "спускается" не сам модуль, а как бы ссылка на его скачивание. Механизм устроен так, что предварительно модули нужно выложить в Container Registry (это специальный репозиторий для хранения модулей).

Модули после установки взаимодействуют, как показано на следующей схеме, взятой из того же примера:

  • Camera: модуль захвата видео с камеры. Видео "нарезается" на картинки (не кадры! Из десятка кадров в обработку может уйти только один) и по HTTP передается в следующий модуль;
  • AI: модуль, содержащий обученную модель машинного обучения. Модель работает с отдельными изображениями, а не с видеопотоком, именно поэтому видео "нарезается" на картинки. Модель машинного обучения предварительно должна быть подготовлена в сервисе Custom Vision;
  • Display: модуль, отображающий результаты распознавания. Конкретно в рассматриваемом примере предлагается отличить банан от яблока, соответственно, на экране будет либо картинка яблока, либо банана (либо ничего).

Помимо всего прочего, телеметрия отсылается непосредственно в IoT Hub.

Если мы хотим использовать какую-то свою модель с данным примером, в описании к нему нам предлагается просто заменить модель на свою. Мы в Кварта Технологии проверили этот сценарий, но из-за ограниченности ресурсов Raspberry Pi работало такое решение ну очень медленно, так что на практике пользоваться им было очень неудобно.

Соответственно, для этого примера напрашивается более мощная платформа. AMD64 уже поддерживается, вот только в такой конфигурации реальная веб-камера почему-то не предусмотрена и предлагается пользоваться ее эмуляцией за счет зацикленного куска видео, на котором есть и яблоко, и банан. Это нас точно не устроит, поэтому во что бы то ни стало будем подключать "живую" веб-камеру, пользуясь кодом от ARM32.

Есть еще одна проблема. У компьютера на базе AMD64 нет "экранчика" SenseHat, как в Raspberry Pi, поэтому так элегантно, как на SenseHat, результат распознавания уже не отобразить, и придется придумывать что-то свое.

Обучение модели (Custom Vision)

Заходим на Custom Vision (понадобится подписка Azure) и нажимаем New Project.

  • Name — вводим произвольное имя;
  • Description — произвольное описание;
  • Resource — нажимаем Create new и создаем новый ресурс типа Cognitive Services;
  • Classification Types — выбираем Multilabel, так как мы будем определять не только наличие/отсутствие маски, но и где именно она надета — на лице или на теле (гуглим "бикини из масок")
  • Domains — General (compact).

После создания проекта загружаем предварительно подготовленные картинки следующих типов:

  • Люди в маске на лице, размечаем тегом MaskOnFace;
  • Люди в маске не на лице ("бикини из масок"), размечаем тегом MaskNotOnFace — на волне маскобикини-хайпа используем и такой случай, чтобы наша модель машинного обучения не пропускала хитрых фитоняш в маскобикини через турникет (наверное, стоящий на входе в фитнес-клуб, где они качаются);
  • Люди без масок, изображения масок без людей, не размечаем тегами (Negative).

Картинки можно найти в интернете или пофоткать всех знакомых.

Нажимаем кнопку Train, далее Advanced training и ждем, пока модель обучается. После чего на вкладке Performace можно посмотреть параметры качества функционирования модели.

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

Значение Probability Threshold — это вероятность назначенного тега, в зависимости от которой рассчитываются параметры качества функционирования модели. Значение Probability Threshold = 90% означает, что правильными предсказаниями будут считаться теги, вероятность которых оказалась выше 90%. Думаю, эта фраза требует пояснения. Вообще, в машинном обучении ответы обычно не дискретные (да/нет), то есть модель не может "ответить" на вопрос "на лице ли у человека маска" просто "да" или "нет". Грубо говоря, будет что-то типа "маска на лице с вероятностью 89%". Проблема в том, что для расчета параметров качества модели нужны как раз те самые дискретные "да" или "нет". И вот если параметр этот самый Probability Threshold установлен 90%, то ответ "маска на лице с вероятностью 89%" превратится в дискретное "нет", а ответ "маска на лице с вероятностью 91%" превратится в дискретное "да".

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

  • Параметр Recall (полнота) означает способность алгоритма обнаруживать заданный класс вообще;
  • Параметр Precision (точность) — способность отличать этот класс от других классов.

Чем больше каждое из значений — тем лучше.

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

Модель следует опубликовать (Publish), а затем экспортировать (Export — Dockerfile — Linux). Полученный файл далее будет использоваться в модуле IoT Edge.

Подготовка наземной платформы

Из-за архитектурных особенностей примера нам понадобится платформа (а именно — процессор) с поддержкой AVX инструкций (узнать, поддерживает ли платформа AVX инструкции, можно, выполнив команду (grep avx /proc/cpuinfo) — это потребуется для корректной работы библиотеки tensorflow. Если вы хотите использовать платформу без поддержки AVX, потребуется пересобрать библиотеку, что выходит за рамки данной статьи. Упростим себе жизнь и возьмем платформу с соответствующей поддержкой. Мы использовали Intel NUC на базе Core i5.

В качестве ОС будем использовать Ubuntu 20.04 LTS. Сложность заключается в том, что инструкций для установки IoT Edge для данной версии нет (слишком свежая по мнению Microsoft?), поэтому о процессе настройки расскажем достаточно подробно.

Вначале установите саму ОС с графическим окружением, затем откройте командную строку для установки Edge. По умолчанию все команды выполняются в домашнем каталоге (~).

Установим необходимые утилиты:

Устанавливаем конфигурацию репозитория:

Копируем полученный файл в директорию sources.list.d, чтобы ОС "видела" репозитории Microsoft:

Загружаем и устанавливаем публичный ключ Microsoft GPG:

Устанавливаем ПО для контейнеризации (обратим внимание на первую команду — ее обязательно нужно выполнить, так как мы добавили новые репозитории и apt еще "не в курсе"):

Устанавливаем демон IoT Edge:

И с удивлением обнаруживаем, что такого пакета в добавленных выше репозиториях нет (это касается только нашей Ubuntu 20.04 — на момент публикации статьи), поэтому вместо следования документации пойдем своим путем. Можем добавить репозитории от более ранней версии Ubuntu (18.04 — что, строго говоря, не очень хорошая идея) или установить пакеты вручную (правда, опять же, от более ранней версии). Пойдем вторым путем и установим нужные пакеты с GitHub. Ищем Latest release и устанавливаем его при помощи dpkg. Нам также понадобится libssl определенной версии:

Среда выполнения IoT Edge установлена, но она пока не подключена к Azure. Чтобы это сделать, наберем команду:

И в открывшемся файле увидим, что для подключения к Azure необходимо указать значение device_connection_string в разделе provisioning. Для того, чтобы получить это значение, зарегистрируем наш экземпляр IoT Edge в Azure.

Настройки в Azure

Все экземпляры IoT Edge подключаются к IoT Hub, который мы сейчас и создадим на портале Azure. Если у вас нет подписки — создайте пробную.

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

В результате ваше устройство IoT Edge будет подключено к созданному IoT Hub, а также будет подготовлен репозиторий контейнеров.

Разработка

Внутри IoT Edge нашего решения будут исполняться сразу три модуля:

  • Модуль захвата изображения с камеры. Работает напрямую с веб-камерой, подключенной по USB. Отдает картинку на следующий модуль по HTTP;
  • Модуль машинного обучения, распознающий изображения и классифицирующий их (в маске/без маски/маска не на лице). Получает картинку по HTTP, отдает результат распознавания в следующий модуль путем обмена сообщениями;
  • Модуль отображения (веб-сервер). Отдает по запросу веб-страницу, на которой можно увидеть видео с камеры и результат распознавания.

Схема коммуникаций между модулями показана ниже:

Перед тем, как приступить к разработке, нам потребуется загрузить и установить соответствующие инструменты.

  • Загрузите и установите Visual Studio Code. Разработку также можно вести и в Visual Studio;
  • Установите расширение Azure IoT Edge Extension. После установки на открывшейся странице нажмите Select IoT Hub, пройдите аутентификацию и выберите созданный ранее IoT Hub;
  • Установите расширение Azure IoT Tools;
  • Установите Python 3.8.3. При установке отметьте опцию Add Python to PATH — подробнее здесь в секции Task 4;
  • Перейдите в Панель управления, затем Программы и компоненты, и убедитесь, что включена вся группа Hyper-V и Контейнеры. Если нет — отметьте их, нажмите OK и при необходимости перезагрузите компьютер;
  • Установите Docker for Windows — подробнее там же в секции Task 5;
  • Установите клиент Git для Windows последней доступной версии.

Получите исходный код примера, выполнив команду:

Рекомендую также переключиться на коммит, который был использован для модификации примера:

В Visual Studio Code откройте папку с примером (File — Open Folder) и ознакомьтесь с его структурой. Вы увидите три модуля, о которых мы говорили выше. Если при открытии среда предложит установить дополнительные расширения, сделайте это.

Модификация примера

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

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

Итак, поехали.

.env

Необходимо установить параметры реестра (репозитория) контейнеров, взятые с портала Azure:

  • CONTAINER_REGISTRY_ADDRESS="имя_реестра_контейнеров.azurecr.io"
  • CONTAINER_REGISTRY_USERNAME="имя_пользователя"
  • CONTAINER_REGISTRY_PASSWORD="пароль"

deployment.template.json

  • modules — camera-capture — env — RESIZE_WIDTH — установить 640, RESIZE_HEIGHT — установить 480
  • modules — camera-capture — settings — image — установить ${MODULES.CameraCapture.amd64}
  • modules — sensehat-display — env — THRESHOLD — value — установить 0.9
  • modules — sensehat-display — settings — image — установить ${MODULES.SenseHatDisplay.amd64}, createOptions — удалить содержимое HostConfig и установить "PortBindings": { "8000/tcp": [ { "HostPort": "8000" } ] }
  • modules — image — settings — image — установить ${MODULES.ImageClassifierService.amd64}

deployment.template_AMD64

Данный файл следует удалить с файловой системы.

СameraCapture\amd64.Dockerfile

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

CameraCapture.py

Поскольку мы перенесли решение на более мощную платформу (по сравнению с Raspberry Pi), уменьшим временной интервал, за который накапливаются кадры для анализа. Для этого ищем строку time.sleep(1.0) и заменяем 1.0 на 0.1.

ImageClassifierService\amd64.Dockerfile

Здесь потребуется подобрать версии библиотек для AMD64. Модифицированный файл можно найти в приложенном архиве.

labels.txt

Данный файл содержит метки, которые получает каждое изображение. Эти метки должны соответствовать меткам из сервиса Custom Vision, поэтому содержимое файла должно быть следующим:

model.pb

Двоичный файл модели из Custom Vision — следует заменить существующий на подготовленный нами ранее.

SenseHatDisplay\amd64.Dockerfile

Такого файла в примере нет, нам нужно его создать.

SenseHatDisplay — модуль отображения результата распознавания. Но на нашей платформе AMD64 никакого шилда SenseHat нет, поэтому из данного модуля весь код, взаимодействующий, собственно, с SenseHat, уберем, и будем реализовывать простейший веб-сервер на Python, который сможет прямо в браузере показывать результат распознавания.

Файл можно найти в приложенном архиве.

SenseHatDisplay\module.json

В раздел platform нужно внести изменения, а именно добавить amd64:

SenseHatDisplay\app (директория)

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

  • custom.js
  • index.htm
  • jquery.js
  • style.css
  • DisplayManager.py
  • MessageParser.py

Веб-сервер достаточно "хитрый". Он отдает клиенту страницу, в которой выполняются сразу две важных вещи:

  • Отображается видеопоток от модуля захвата изображения с камеры: его можно увидеть прямо с компьютера с IoT Edge, зайдя браузером на адрес http://127.0.0.1:5012;
  • Отображается результат распознавания — от модуля классификатора по адресу http://127.0.0.1:8000/status.

Подчеркну, что фактически получаются два веб-сервера — один с видеопотоком, второй с результатом распознавания. Причем страницу, с которой идет обращение к этим серверам, отдает второй сервер. Сама эта страница доступна по адресу http://127.0.0.1:8000.

Сборка

Для сборки потребуется подключение к Интернет.

  • В VS Code выберите View — Command Pallette — Azure IoT Edge: Set Default Target Platform for IoT Edge Solution и в появившемся списке выберите amd64;
  • Там же выберите команду Azure IoT Edge: Build and Push IoT Edge Solution. Все должно собраться с первого раза и загрузиться в реестр контейнеров, но этого еще недостаточно для попадания на устройство IoT Edge;
  • Разверните решение на IoT Edge, кликнув правой кнопкой на файл config/deployment.json и выбрав Create Deployment for Single device, затем выберите ваше устройство, указав его имя;
  • На развертывание потребуется некоторое время (не забудьте подключить к платформе веб-камеру!). Можете кликнуть на ваше устройство правой кнопкой мыши в расширении IoT Edge Extension и выбрать Start Monitoring D2C Message, тем самым наблюдать телеметрию.

Момент истины

На нашем IoT Edge устройстве логинимся в UI и прямо браузером заходим на веб-страницу по адресу http://localhost:8000, где наблюдаем веб-страницу, как на картинке. Надеваем маску и смотрим в камеру, система определяет, что маска на нас.

Дело за малым — подключить исполнительное устройство, которое открывает наш условный турникет.

К огромному сожалению, моделей в маскобикини, чтобы проверить срабатывание тега MaskNotOnFace, у нас не нашлось, но на тестовых картинках все работало хорошо.

Если что-то пошло не так...

Если модули собрались и отлажены, а устройство в сети, вмешательство на стороне IoT Edge не требуется. Но иногда на этапе запуска возникают проблемы (так я, например, узнал, что без AVX инструкций и/или пересборки библиотеки tensorflow модуль классификатора не работает). IoT Edge предлагает разные способы диагностики. Подробно о них можно прочитать здесь. Самое главное, что может пригодиться:

  • Получить список модулей и их статус: iotedge list. В примере на рисунке видно выполняющиеся модули и их аптайм (более недели);

  • Журнал модуля: iotedge logs имя_модуля. Позволяет понять, что конкретно происходит с модулем.

Обновление модулей

Для того, чтобы обновить модуль на устройстве IoT Edge, нужно либо инкрементировать его версию при публикации, либо удалить существующий и его файлы из реестра модулей (на портале Azure), иначе IoT Edge не увидит изменения.

Что дальше?

Дальше можно отключить компьютер с IoT Edge от Интернета и убедиться, что все работает так же хорошо.

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

Мы также совсем не рассмотрели возможности управления устройством IoT Edge с портала Azure, так как это было незначимо для темы данной статьи.

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

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

Если у вас еще остались вопросы по облачным технологиям Microsoft и Интернету вещей — обращайтесь к нам в Кварта Технологии. Файлы к статье можно скачать по ссылке.

Автор статьи — Сергей Антонович, ведущий инженер Кварта Технологии. Связаться с ним можно по адресу sergant (at) quarta.ru. Статья опубликована в блоге компании на Хабр.