Введение
Основная цель - обнаружение лица и маски в браузере, не используя бэкенд на Python. Это простое приложение WebApp / SPA, которое содержит только JS-код и может отправлять некоторые данные на серверную часть для следующей обработки. Но начальное обнаружение лица и маски выполняется на стороне браузера и никакой реализации Python для этого не требуется.
На данный момент приложение работает только в браузере Chrome.
В следующей статье я опишу более технические подробности реализации на основе наших исследований.
Есть 2 подхода, как можно это реализовать в браузере:
Оба они поддерживают WASM, WebGL и CPU бэкенд. Но мы будем сравнивать только WASM и WebGL, так как производительность на CPU очень низкая и не может быть использована в конечной реализации.
TensorFlow.js
На официальном сайте TensorFlow.js предлагает несколько обученных и готовых к использованию моделей с готовой постобработкой. Для обнаружения лиц в реальном времени мы взяли BlazeFace модель, онлайн демо доступно тут.
Больше информации о BlazeFace можно найти тут.
Мы создали демо, чтоб немного поиграться со средой выполнения и моделью, выявить любые существующие проблемы. Ссылки для запуска приложения с различными моделями и средой выполнения можно найти ниже:
WASM (размер изображения для определения лица: 160x120px; размер изображения для определения маски: 64x64px)
WebGL (размер изображения для определения лица: 160x120px; размер изображения для определения маски: 64x64px)
Результаты производительности: получение кадров
Мы можем получать кадры с помощью соответствующих функций HTML API. Но и этот процесс занимает время. Поэтому нам нужно понимать, сколько времени мы потратим на такие действия. Ниже приведены соответствующие временные метрики.
Наша цель - обнаружить лицо как можно быстрее, поэтому мы должны получать каждый кадр стрима и обрабатывать его. Для этой задачи мы можем использовать requestAnimationFrame, который вызывается каждые 16,6мс или каждый фрейм.
Метод grabFrame() из интерфейса ImageCapture позволяет нам взять текущий кадр из видео в MediaStreamTrack и вернуть Promise, который вернет нам изображение в формате ImageBitmap.
![](https://habrastorage.org/getpro/habr/upload_files/35d/518/d47/35d518d47f8adf2ae5583d3a7957aec2.png)
Одиночный режим - это максимальная производительность при получении кадра. Режим синхронизации - это то, как часто мы можем получать кадр с распознаванием лиц.
Результаты производительности: определения лица и маски
![](https://habrastorage.org/getpro/habr/upload_files/0c4/5e5/91a/0c45e591a9cef02d60a9dad825ac4d9f.jpeg)
Цветовая схема: < 6 fps красный, 7-12 fps оранжевый, 13-18 fps желтый, 19+ fps зеленый.
Результаты:
Мы не учитываем временные метрики при запуске приложения. Очевидно, что во время запуска приложения и первых запусков модели оно будет потреблять больше ресурсов и времени. Когда приложение находится в "теплом" режиме, только тогда стоит получать показатели производительности. Теплый режим в нашем случае - это просто позволить приложению проработать 5-10 секунд, а затем получать показатели производительности.
Возможные неточности в собранных временных показателях - до 50мс.
Модель BlazeFace была разработана специально для мобильных устройств и помогает достичь хорошей производительности при использовании TFLite на платформах Android и IOS (~ 50-200 FPS).
Больше информации тут.
Набор данных для переобучения модели с нуля недоступен (исследовательская группа Google не поделилась им).
BlazeFace содержит 2 модели:
Фронтальную камеру: размер изображения 128 x 128px, быстрее, но с меньшей точностью.
Задняя камера: размер изображения 256 x 256px, медленнее, но с высокой точностью.
Размер изображения
Исходное изображение может быть любого размера в соответствии с настройками камеры и бизнес требованиями. Но когда мы обрабатываем кадры для обнаружения лица и маски, размер исходного кадра изменяется до подходящего размера в зависимости от модели.
Изображение, используемое BlazeFace для распознавания лиц, имеет размер 128 x 128px. Исходное изображение будет изменено до этого размера с учетом его пропорций. Изображение, которое используется для обнаружения маски, имеет размер 64 x 64px.
Мы выбрали минимальные разрешения для обоих изображений с учетом требований к производительности и результатам. Такие минимальные изображения показали наилучшие результаты на ПК и мобильных устройствах. Мы используем изображения размером 64 x 64px для обнаружения маски, потому что размера 32 x 32px недостаточно для обнаружения маски с достаточной точностью.
Как получить лучшие изображения для анализа приложением?
Используя TensorFlow.js у нас есть следующие опции для получения лучшего изображения, чтоб использовать в приложении:
BlazeFace позволяет настроить точность определения лица. Мы установили этот показатель с высоким значением (> 0,9), чтобы избежать неожиданных положительных результатов (например "призраки" в темном помещении или затылок человека определяло как лицо).
BlazeFace позволяет настроить максимальное количество лиц, которые возвращаются после анализа изображения. Можно указать для этого параметра значение 1, чтобы вернуть только 1 лицо; можно установить значение 2 и более и в этом случае при обнаружении более 1-го лица сообщать о том, что один человек / лицо может находиться перед камерой.
BlazeFace возвращает область лица и ориентиры (глаза, уши, нос, рот). Таким образом у нас есть все возможности для проверки этих результатов, чтобы убедиться, что обнаруженные лица подходят для последующей обработки.
Такие проверки могут быть следующими:
Область лица должна находиться в калибровочной рамке в X%.
Все ориентиры или их часть должны быть в калибровочной рамке.
Ширина и высота области лица должны быть достаточными для дальнейшего анализа
Проверки ширины / высоты области лица и ориентиров выполняются очень быстро на стороне клиента с помощью JS.
Выбранные изображения, в соответствии с указанными выше правилами, будут отправлены на следующие этапы обработки: обнаружение маски. Такая логика обеспечит более быстрое обнаружение лица до того момента, когда мы будем уверены, что обнаруженное лицо хорошего качества.
Размер моделей определения маски
![](https://habrastorage.org/getpro/habr/upload_files/a6b/ecc/4ac/a6becc4ac5db2ace3be60fb850d5e9f8.png)
Для определения маски мы использовали MobileNetV2 и MobileNetV3 с различными типами и мультипликаторами.
Мы предлагаем использовать легкие или сверхлегкие модели с TensorFlow.js в браузере (<3Mb). Основная причина в том, что WASM работает быстрее с такими моделями. Это указано в официальной документации, а также подтверждено нашими тестами производительности.
Дополнительные ресурсы
WASM JS бэкенд: ~60Kb
OpenCV.js: 1.6Mb
Наше SPA приложение (+TensorFlow.js): ~500Kb
BlazeFace модель: 466Kb
Для веб приложений время до взаимодействия (TTI) с ~3.5Mb JavaScript кода и бинарниками + JSON модели размером 1.5Mb to 6Mb будет >10 сек в холодном режиме; в прогретом режиме ожидается TTI - 4-5 сек.
Если использовать Web Worker (и OpenCV.js будет только в воркерах), это значительно уменьшит размер основного приложения до 800-900Kb. TTI будет 7-8 секунд в холодном режиме; в прогретом режиме <5 сек.
Возможные подходы к запуску моделей нейронных сетей в браузере
Однопоточная реализация
![](https://habrastorage.org/getpro/habr/upload_files/e7e/ca0/62b/e7eca062bb2463669fbc71c5bdc155d1.png)
Это реализация по умолчанию для браузеров. Мы запускаем обе модели для обнаружения лиц и масок в одном потоке. Ключевым моментом является обеспечение хорошей производительности для обеих моделей для работы с любыми проблемами в одном потоке. Это текущий подход, который использовался для получения указанных выше показателей производительности.
У этого подхода есть некоторые ограничения. Если мы хотим добавить дополнительные модели для запуска последовательной обработки, то они будут выполняться в том же потоке хоть и асинхронно, но последовательно. Это снизит общие показатели производительности при обработке кадров.
Использование Web Workers для запуска моделей в разном контексте и распараллеливание в браузере
![](https://habrastorage.org/getpro/habr/upload_files/366/548/fd0/366548fd0da078186b6ebcac30d556d1.png)
В основном потоке JS запускается модель BlazeFace для обнаружения лиц, а обнаружение маски выполняется в отдельном потоке через Web Worker. Благодаря такой реализации мы можем разделить обе запущенные модели и ввести параллельную обработку кадров в браузере. Это положительно повлияет на общее восприятие UX в приложении. Веб-воркеры будут загружать библиотеки TensorFlow.js и OpenCV.js, основной поток JS - только TensorFlow.js. Это означает, что основное приложение будет запускаться намного быстрее, и тем самым мы сможем значительно сократить время TTI в браузере. Обнаружение лиц будет запускаться чаще, это увеличит FPS процесса обнаружения лиц. В результате процесс обнаружения маски будет запускаться чаще, а также будет увеличиваться FPS этого процесса. Ожидаемые улучшения до ~ 20%. Это означает, что указанные в статье выше FPS и миллисекунды могут быть улучшены на это значение.
При таком подходе к запуску разных моделей в разных контекстах с помощью веб-воркеров мы можем запускать больше моделей нейронных сетей в браузере с хорошей производительностью. Основным фактором здесь будут аппаратные характеристики устройства, на котором это запускается.
Мы реализовали такой подход в приложении, и он работает. Но у нас есть некоторые технические проблемы с обратным вызовом postMessage, когда веб-воркер отправляет сообщение обратно в основной поток. По каким-то причинам он вводит дополнительную задержку (до 200мс на мобильных устройствах), которая убивает улучшение производительности, которого мы достигли с помощью распараллеливания (эта проблема актуальна только для чистого JS, после реализации на React.js эта проблема исчезла).
Результаты реализации с Web Workers
Наша идея состоит в том, чтобы использовать веб-воркеры для запуска каждой модели в отдельном воркере / потоке / контексте и добиться параллельной обработки моделей в браузере. Но мы заметили, что callback-функция в воркере вызывается с некоторыми задержками. Ниже вы можете найти соответствующие измерения и выводы о том, от каких факторов это зависит.
Тестовые параметры:
mobileNetVersion=V3
mobileNetVersionMultiplier = 0.75
mobileNetVersionType = float16
thumbnailSize=32px
backend = wasm
В приложении мы запускаем BlazeFace в основном потоке и модель обнаружения маски в веб-воркере.
Измерения времени обработки Web Worker на разных устройствах:
![](https://habrastorage.org/getpro/habr/upload_files/259/10c/354/25910c3540514a5bfeb65413ec5fc80c.png)
![](https://habrastorage.org/getpro/habr/upload_files/6e5/9b5/c4d/6e59b5c4dec9fbac93053c30729f7558.png)
![](https://habrastorage.org/getpro/habr/upload_files/187/e2c/72e/187e2c72e98443e46ccbc3e75cf9367a.png)
Приведенные выше результаты демонстрируют, что по некоторым причинам время для отправки обратного вызова из Web Worker в основной поток зависит от модели, работающей или использующей метод TensorFlow browser.fromPixels в этом Web Worker. Если он запущен с моделью, обратный вызов в Mac OS отправляется ~ 27мс, если модель не запущена - 5мс. Эта разница в 22мс для Mac OS может привести к задержкам 100–300мс на более слабых устройствах и повлияет на общую производительность приложения при использовании Web Worker. В настоящее время мы не понимаем, почему это происходит.
Как повысить точность и уменьшить количество ложных срабатываний?
Нам нужно иметь больше «контекста», чтобы принять соответствующее решение, чтобы сообщить об обнаружении лица и отсутствии маски. Такой контекст - это ранее обработанные кадры и их состояние (лицо есть или нет, лицо есть или нет, маска есть или нет). Мы реализовали «плавающий» сегмент для управления таким дополнительным контекстом. Длина сегмента настраивается и зависит от текущего FPS, но в любом случае она не должна быть больше 200–300мс, чтобы не было видимых задержек. Ниже приведена соответствующая схема, описывающая идею:
![](https://habrastorage.org/getpro/habr/upload_files/230/7e2/603/2307e26037b55a0091af4a3b5e6fea78.png)
Заключение:
Чем мощнее устройство, тем лучше результаты производительности при измерении:
Для ПК мы могли получить следующие показатели:
Определение лица: > 30fps
Определение лица + определение маски: до 45fps
Для мобильных устройств мы могли получить следующие показатели:
Определение лица: от 2.5fps до 12-15fps в зависимости от мобильного устройства
Определение лица + определение маски: от 2 до 12fps в зависимости от мобильного устройства
Обращаем внимание, что видео всегда в реальном времени и его производительность зависит от самого устройства, но всегда будет от 30 кадров в секунду.
Для модели обнаружения маски в большинстве случаев лучшие результаты демонстрирует модель MobileNetV2 0.35, типы не влияют на метрики производительности.
Размер модели обнаружения маски зависит от типов. Поскольку типы не влияют на показатели производительности, рекомендуется использовать модели uint16 или float16, чтобы иметь меньший размер модели в браузере и более быстрый TTI.
Среда выполнения WASM показывает лучшие результаты по сравнению с WebGL для модели BlazeFace. Это соответствует официальной документации TensorFlow.js относительно производительности небольших моделей (<3Mb):
Для большинства моделей серверная часть WebGL по-прежнему будет превосходить серверную часть WASM, однако WASM может быть быстрее для сверхлегких моделей (менее 3Mb и 60млн операций умножения и добавления). В этом случае преимущества распараллеливания GPU перевешиваются фиксированными накладными расходами на выполнение шейдеров WebGL.
Время TTI всегда лучше для моделей WASM по сравнению с WebGL с той же конфигурацией моделей.
Производительность среды выполнения и моделей TensorFlow.js можно повысить, используя расширение WASM для добавления инструкций SIMD, позволяющих векторизовать и выполнять несколько операций с плавающей точкой параллельно. Предварительные тесты показывают, что включение этих расширений обеспечивает ускорение в 2–3 раза по сравнению с WASM. Больше информации здесь. Это все еще экспериментальная функция, которая по умолчанию не поставляется со средой выполнения. Также после релиза он будет доступен во время выполнения по умолчанию или через дополнительные параметры конфигурации.
На стороне клиента ожидаемый размер приложения составляет ~ 3,5Mb JS кода со всеми зависимостями, 466Kb модели BlazeFace, от 1,1Mb до 5,6Mb модели обнаружения маски. Ожидаемое время TTI для приложения составляет > 10 секунд для мобильных устройств в холодном режиме; в теплом режиме - ~ 5сек.
При использовании веб-воркеров OpenCV.js может быть загружен только в веб-воркеры, это значительно снижает TTI для основного приложения.
Во время тестирования мы заметили, что устройства сильно нагреваются. Поскольку ожидается круглосуточная работа приложения, этот факт может иметь большое влияние на окончательное решение, какие устройства использовать. Вам необходимо принять во внимание эту информацию.
Также было замечено, что лучше, если устройства будут полностью заряжены и подключены к зарядному устройству во время работы. В этом случае производительность устройств по нашим наблюдениям лучше. Зарядка от USB не позволяет поддерживать батарею на одном уровне долго, поэтому следует использовать зарядку от розетки. Эту информацию следует принимать во внимание.
На наш взгляд, текущему решению достаточно даже 4-5 кадров в секунду, чтобы обеспечить хорошее восприятие пользователем UX. Но важно не показывать на экране область лица или ориентиры, а управлять всем на видео / экране:
Выделяя на экране, когда мы обнаружили человека.
Информирование о маске / отсутствии маски с помощью текстовых сообщений или другого выделения экрана.
При таком взаимодействии пользователя задержки между реальным временем и нашими метаданными на экране будут составлять 200–300мс. Такие значения будут рассматриваться пользователями системы как некритические задержки.
Вышла 2я часть, с описанием технических подробностей реализации. Читать тут.