Хотите создать веб-сайт, использующий отслеживание лиц? Что ж, вы попали в нужное место!

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

Все демонстрации можно найти здесь: facemeshmedium.netlify.app/

О модели

Благодаря быстрому развитию Tensorflow.JS в Google машинное обучение в Интернете стало еще проще. Мы собираемся использовать модель Face Mesh Google MediaPipe для всех наших потребностей в отслеживании лиц.

В Google Research есть подразделение под названием MediaPipe, которое занимается созданием моделей машинного обучения для работы на разных устройствах. Это позволяет им предсказывать все, что угодно, от большого компьютера до мобильного смартфона без графического процессора (основной сайт здесь). Face Mesh - их модель отслеживания лиц, которая берет кадр камеры и выводит 468 помеченных ориентиров на обнаруженных лицах. Он также группирует ориентиры по области лица (Верхняя губа, Левый глаз и т. Д.) И дает ограничивающие рамки лица на выходе.

О, и я упоминал, что ориентиры, которые он дает вам, имеют трехмерные координаты - с глубиной для лица ?! Как это круто?

Если вы прокрутите страницу Face Mesh вниз, вам может быть интересно, зачем я пишу это руководство; MediaPipe уже предоставляет примеры HTML с использованием Face Mesh. На это есть две причины:

  1. Их примеры не работают в мобильных браузерах из-за разницы в способах обработки видеопотоков на JavaScript для мобильных устройств и компьютеров.
  2. Библиотеки рисования и редактирования находятся в этом формате импорта ‹script›, который скрывает, что на самом деле происходит с выходными данными Face Mesh. Это раздражает, потому что их примеры затрудняют любителю кодирования возможность изменять и расширять свой код, что является своего рода целым смыслом предоставления примеров!

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

Настройка камеры

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

Код JavaScript предполагает, что рядом с вами настроена HTML-страница с элементами <video> и <canvas>. Мы проходим холст, чтобы нарисовать что-то на нашем веб-сайте. Вот код:

JavaScript настраивает камеру через mediaDevices API, а затем назначает холст и контекст (ctx), которые нам нужно будет нарисовать на них. Наконец, видеопоток зацикливается на холсте.

Импорт Face Mesh и Tensorflow.JS

Теперь нам нужно вызвать Face Mesh в видеопотоке. Мы включаем модель в импорт нашего JavaScript, добавляя эти теги в заголовок нашей HTML-страницы.

<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
<script src=”https://cdn.jsdelivr.net/npm/@tensorflow-models/facemesh"></script>

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

// In main()
fmesh = await facemesh.load({detectionConfidence:0.9, maxFaces:3});

В нашем JavaScript мы собираемся добавить цикл, который запускает Face Mesh в нашем видеопотоке и выводит предсказанные лица. Вы также можете ввести холст HTML в facemesh.estimateFaces, но, поскольку мы будем рисовать поверх холста, я предпочитаю прогнозировать Face Mesh в неотредактированном потоке video. Вот код, который вам поможет:

Большой! Теперь, когда мы появляемся в веб-камере, Face Mesh определяет, где мы находимся в кадре, и подгоняет к нашему лицу несколько ориентиров в виде curFaces. Экспорт этих прогнозов в глобальную переменную позволяет нашему циклу рисования (или любым другим функциям) делать что-то с местоположением наших лиц. Не забудьте где-нибудь позвонить renderPrediction() в свой главный телефон; он повторяется бесконечно, но сначала нужно запустить его.

Мы собираемся рисовать точки на ориентирах лица, которые выводятся Face Mesh, но для этого нам нужно понимать, что именно содержит curFaces.

Формат вывода сетки лица

Результатом Face Mesh является Array предсказаний лиц, каждый из которых имеет следующий формат:

Мы подробно рассмотрим каждый из них.

  • faceInViewConfidence находится в диапазоне от 0,0 до 1,0 и обычно составляет 0,99, когда лицо находится в кадре. Мы используем это, чтобы отсеять ложные срабатывания распознавания лиц.
  • boundingBox содержит два Coord2Ds, которые являются объектами, к которым вы можете получить доступ с помощью topLeft.x и topLeft.y. Они дают углы лица во входных координатах, так что вы можете индексировать прямо в изображение, используя их.
  • mesh показывает ориентиры на лицах, используя систему координат, отличную от камеры. Это затрудняет его использование, поэтому мы рассмотрим его брата,
  • scaledMesh содержит 468 трехмерных ориентиров лица в формате (X, Y, Z), где X и Y - координаты нашего входного видеопотока, а Z - глубина. Каждый ориентир обозначает одну точку на лице, и на их Github есть карта сетки, которую вы можете использовать для выбора нужных точек. Мы будем использовать это позже для нашего фильтра Snapchat.

  • annotations - последний и довольно полезный. Разработчики Face Mesh пошли дальше и создали именованные наборы точек для каждой области лица. Например, annotations.lipsLowerOuter содержит все точки, которые находятся на внешней стороне нижней губы. Мы будем использовать их позже, чтобы создать обрезку определенных частей лица.

Вы можете получить доступ ко всему списку аннотаций в консоли Inspect Element, набрав annotations., а затем нажав вкладку для автозаполнения.

Рисование достопримечательностей на моем лице для развлечения и прибыли

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

Для этого мы изменим нашу drawVideo функцию, чтобы также рисовать все грани в curFaces. Уловка for (face of curFaces){ - это версия for … in … для JavaScript с Python, и она дает нам прогноз от curFaces в цикле for. Код выглядит следующим образом:

Нам также нужно будет написать drawFace(), который указан внизу. Рисование на холсте HTML происходит через его Контекст рендеринга холста, который в нашем коде равен ctx. Сначала мы указываем, какого цвета должны быть точки, а затем перебираем точки. Поскольку каждая точка является трехвектором [X, Y, Z], мы можем напрямую индексировать их, чтобы знать, где рисовать наши круги. beginPath() начинает фигуру, а fill() завершает ее.

Вот что мы получаем! Лицо в точках. Попробуйте сами здесь.

Чтобы сделать что-нибудь более сложное, вам нужно знать индекс каждой точки ориентира. Мы можем просто добавить fillText линию, чтобы нарисовать номер индекса рядом с каждым кругом, но, вероятно, легче увидеть индексы на полноразмерной карте сетки. Попробуй!

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

Big Eyes Flipped Mouth (фильтр Snapchat)

Мы собираемся воссоздать фильтр Big Eyes and Mouth с помощью Face Mesh, но мы также собираемся перевернуть рот вверх ногами. По идее, вы можете подумать, что фильтр довольно простой. Удвойте размер глаз, удвойте размер рта, сложите их на исходную фотографию, где они были изначально, верно? Вы правы, все очень просто! Что ж, казнь будет немного сложной, но это общий процесс.

У каждого есть свой любимый язык программирования для редактирования изображений и компьютерного зрения, но держу пари, что никто не выберет JavaScript в качестве своего любимого! Я бы тоже не стал, но я могу показать вам достаточно, чтобы начать вашу долгую и плодотворную карьеру в качестве разработчика JavaScript для компьютерного зрения. Вот изображение фильтра, который мы сделаем в итоге:

Работа с холстом HTML

Начиная со страницы настройки камеры, в нашем HTML-документе присутствует <canvas>. Мы нашли это на странице ориентиров лиц, чтобы мы могли установить размер и создать «контекст» под названием ctx. Это ctx позволило нам нарисовать круги и текст.

Но даже до этого мы использовали ctx. Помните функцию, которую мы вызвали в самом начале для копирования потока video на наш холст?

ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

Как оказалось, это лишь одна из нескольких перегруженных функций JavaScript для рисования из одного источника изображения в другой. drawImage, который мы вызвали выше, указывает только координаты левого верхнего угла места назначения и ширину или высоту для рисованияvideo. Мы можем использовать другой drawImage, который также указывает, как обрезать фрейм из video перед рисованием его на холсте.

ctx.drawImage(video, 
source_x, source_y, source_Width, source_Height, // Source location
dest_x, dest_y, dest_Width, dest_Height);  // Destination location

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

Я вошел в карту сетки и нашел эти ключевые точки вокруг глаза. Индексируя face.scaledMesh в этих 8 местах, мы можем получить верхнюю, левую, правую и нижнюю стороны обоих глаз. Эти координаты нам понадобятся, чтобы нарисовать глаза на холсте. Вот код:

Вызов drawImage (строка 24) смещает расположение глаз, так что глаз в 2 раза больше по-прежнему находится в центре, где был исходный глаз.

Это дает нам что-то вроде этого:

Идеально!

Теперь рот. Увеличить размер вдвое довольно просто, как мы видели раньше, но перевернуть сложнее. На самом деле невозможно использоватьdrawImage вверх ногами. Вместо этого нам нужно извлечь ImageData из холста, преобразовать его в Tensorflow.JS Tensor, перевернуть этот, а затем нарисовать его на холсте. Ух!

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

Теперь у нас есть ImageData. Объект JavaScript ImageData не очень дружелюбен, но его можно преобразовать в Tensorflow.JS Tensor. Затем мы можем использовать на нем все функции Tensorflow, а именно .reverse(0), который переворачивает изображение по строкам (сверху вниз).

lipsUpsideDown = tf.browser.fromPixels(lips,4).reverse(0);

Затем мы преобразуем его обратно в объект ImageData, чтобы нарисовать его на холсте. Вот полный код:

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

Вот конечный продукт; Вы можете попробовать сами здесь!

Хотя использование Tensorflow может показаться излишним для простого переворачивания изображения, Tensorflow.JS - совершенно другой зверь. Поскольку Tensorflow.JS использует WebGL, он отлично работает и быстро загружается (вся библиотека занимает всего 1,1 МБ). Я видел, как корпоративные логотипы занимают 10 МБ, так что TF.JS действительно довольно недорогой по сравнению с этим.

Использование Face Mesh для науки!

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

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

Обнаружение пульса пользователя с помощью камеры (фотоплетизмография)

Вы видели те приложения для смартфонов, которые могут определять ваш пульс по фонарику и камере? Этот эффект называется фотоплетизмографией (ФПГ), потому что он определяет ваш пульс (плетизмография) с помощью света (фото). Он использует тот факт, что ваша кровь меняет объем и цвет при пульсации.

Довольно круто! Тем не менее, я считаю, что это намного круче, когда пользователь вообще ничего не трогает.

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

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

Конечно, ни одна обычная камера не сможет увидеть, как ваше лицо краснеет с каждым ударом, потому что они слишком шумные. Но, усредняя данные по большому участку кожи, мы можем увидеть сердцебиение пользователя на видео, снятом на вашу обычную камеру. И мы собираемся сделать это с помощью Face Mesh.

Создание веб-PPG с использованием Face Mesh

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

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

Мы берем среднее значение в пикселях, чтобы изменение размера поля не повлияло на наши измерения.

Теперь мы можем извлечь этот патч с помощью getImageData и усреднить его. Это среднее значение добавляется в историю бега. Мы также отслеживаем FPS этого цикла, так как он нам понадобится для преобразования БПФ из «индексных чисел» в частоты.

Для построения графиков использую библиотеку Dygraphs. Вот скриншот графика интенсивности:

Если вы попытаетесь найти библиотеку БПФ в JavaScript (не в узле, а в браузере), у вас будет много проблем. К счастью, в прошлом проекте я перенес библиотеку БПФ под названием KISS, написанную на C ++, в JavaScript с помощью Browserify и Emscripten (подробности здесь). Я использую это для вычисления БПФ из массива интенсивностей. Одна из проблем заключается в том, что для преобразования бинов БПФ в частоты не требуется аргумент частоты дискретизации, но мы можем сделать это сами!

БПФ по частоте

БПФ преобразует сигнал временной области в сигнал частотной области и выводит его как величины в частотных «элементах». Их необходимо преобразовать в частоты по следующей формуле:

Эта формула дает частоту центра каждого интервала. Каждая ячейка имеет ширину sampleFreq/numDFTPointsГц и охватывает обе стороны. Вы заметите, что длина нашей истории определяет точность измерения частоты, поскольку ширина интервала делится на numDFTPoints. Итак, какова наша точность частоты?

Наш массив состоит из 64 элементов, а частота дискретизации может меняться (около 8–10 Гц на моем ноутбуке). Это дает нам минимальную ширину бина 8/64 = 0,125 Гц. Так как частота сердечных сокращений человека обычно выражается в ударах в минуту, 0,125 Гц * 60 секунд становится 7,5 уд / мин. Эта точность невелика, когда дело доходит до частоты пульса, но вы увидите, что усреднение нескольких БПФ вместе может дать точную частоту пульса.

Библиотека упрощает вычисление БПФ, но знание того, как выполнять преобразование бинов, важно, поскольку оно обеспечивает гибкость в выборе библиотек, которые вы можете использовать. Вот код:

Добавляем еще один граф для БПФ, и готово! Вы можете попробовать это сами здесь: facemeshmedium.netlify.app/ppg/

Сидя неподвижно, я могу получить частоту сердечных сокращений, которая будет отображаться на БПФ с довольно хорошей точностью по сравнению с не удаленным приложением PPG на моем телефоне:

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

Заключительные примечания

Вы можете найти все демонстрации здесь: facemeshmedium.netlify.app

И весь код на Github: github.com/kongmunist/FaceMeshDemos

Тот факт, что я могу так легко создавать эти демоверсии, уже является огромным признаком прогресса в развитии Интернета. Запустив Tensorflow в браузер, Google значительно упростил демонстрацию проектов и продуктов машинного обучения и компьютерного зрения. Люди больше не будут жаловаться, что «он не работает в моей настройке», потому что теперь он работает только в Google Chrome, Safari или любом другом браузере, который поддерживает современный JavaScript (но не в Internet Explorer).

Хотите создать приложение, которое расскажет, в какой обуви кто-то одет? Или смартфон жизненно важный монитор? Вместо того, чтобы тратить месяцы на написание приложений для Android и iPhone, вы можете взять готовый веб-сайт и добавить несколько строк JavaScript, что сразу сделает вашу идею доступной для всех, у кого есть смартфон.

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

Я надеюсь, что вы сделаете что-то крутое из того, что вы узнали, и если вы это сделаете, я буду рад это увидеть!

Напишите мне электронное письмо или напишите мне в Twitter.

Ся скорее!