Привет, друзья! Я продолжаю изучать MediaPipe — библиотеку с открытым исходным кодом от Google, предоставляющую "кроссплатформенные и кастомизируемые решения на основе машинного обучения для работы с медиа", и в этой статье хочу рассказать вам о 2 инструментах: Репозиторий с кодом проекта. Если вам это интересно, прошу под кат. В предыдущей статье, посвященной MediaPipe, рассматривалась возможность использования координат суставов кисти руки для управления содержимым веб-страницы. Там шаг за шагом и достаточно подробно показан процесс получения данных с видеокамеры пользователя и их обработка с помощью двумерного контекста рисования холста (HTML-элемент canvas). В этой статье я ограничусь особенностями названных инструментов. Для работы с зависимостями будет использоваться Yarn, а для создания шаблона проекта — Vite. Проект будет реализован на чистом JavaScript. Создаем шаблон проекта: Переходим в созданную директорию, устанавливаем зависимости и запускаем сервер для разработки: Устанавливаем пакеты MediaPipe: Определяем начальную разметку в файле index.html: И начальные стили в файле style.css: Обратите внимание: холст располагается поверх видео. Это все, что требуется для подготовки и настройки проекта. Создаем файл selfie_segmentation.js в корне проекта и подключаем его в index.html: Начнем с примера из официальной документации. В нем выделенный на сцене человек закрашивается зеленым цветом. Импортируем зависимости и стили: Получаем ссылки на элементы video и canvas, а также на 2D-контекст рисования: Определяем ширину и высоту холста, равные ширине и высоте области просмотра, и записываем их в переменные: Создаем экземпляр средства выделения человека на сцене: Устанавливаем настройку modelSelection: Данная настройка принимает числа 0 и 1, определяющие используемую модель распознавания, где 0 означает общую модель (general model), а 1 — ландшафтную модель (landscape model). Распознавание с использованием общей модели является более точным, но менее быстрым (подробнее см. здесь). Мы жертвуем точностью в пользу производительности. Регистрируем обработку результатов распознавания: Создаем экземпляр средства захвата данных с видеокамеры пользователя: Запускаем процесс захвата видео: Определяем функцию обработки результатов распознавания: Свойство globalCompositeOperation определяет тип операции компоновки (compositing operation), применяемой при рисовании фигур. В нашем случае тип source-in означает, что зеленый прямоугольник рисуется (fillRect()) только в пределах выделенной области (results.segmentationMask), что приводит к ее окрашиванию в зеленый цвет, а тип destination-atop — что кадр видео рисуется за существующим содержимым хоста, что приводит к заполнению/восстановлению недостающих пикселей. Результат: 
 Реализуем замену фона видео. Идем на Unsplash и скачиваем 3 изображения, которые будут использоваться в качестве фона (или возьмите изображения из репозитория проекта). Помещаем их в директорию public и добавляем в разметку: Обратите внимание, что мы также добавили кнопку для восстановления оригинального фона. Добавляем стили для изображений и кнопки в style.css: Изображение, на которое наведен курсор, выделяется синей рамкой, а выбранное — зеленой. Результат: 
 Получаем ссылки на контейнер для изображений и кнопку, а также определяем переменную для выбранного изображения: Определяем функцию обработки клика по контейнеру для изображений: Определяем функцию обработки нажатия кнопки: Регистрируем обработчики: Для замены фона в onResults() достаточно изменить тип операции компоновки с source-in на source-out (т.е. рисовать только за пределами выделенной области) и рисовать выбранное изображение вместо зеленого прямоугольника: Результат: 
 Создаем файл face_mesh.js в корне проекта. Снова возьмем пример из официальной документации. В нем рисуется сетка лица с выделением овала лица, бровей, глаз, зрачков и губ, а также соединительных линий между контрольными точками лица. Импортируем зависимости и стили: Определяем константы: Создаем экземпляр средства распознавания лица, устанавливаем настройки и регистрируем обработчик распознавания: О настройках распознавания можно почитать здесь. Создаем экземпляр средства захвата данных с камеры и запускаем процесс захвата: Определяем функцию обработки результатов захвата: Результат: 
 Контрольные точки лица с координатами (multiFaceLandmarks[0]) выглядят следующим образом: 
 Как видим, это просто массив из 468 элементов. Здесь возникает закономерный вопрос: как определить, какой индекс к какой точке относится? Без ответа на этот вопрос привязка к координатам конкретной точки с целью реализации каких-либо эффектов сводится к перебору всех точек до обнаружения искомой. Процесс перебора, учитывая количество точек, является, мягко говоря, утомительным. Покопавшись в официальной документации, мне удалось обнаружить эту каноническую модель лица (canonical face model), на которой указаны индексы точек. Следует отметить, что, во-первых, не все индексы соответствуют действительности, т.е. совпадают с индексами массива multiFaceLandmarks[0], во-вторых, некоторые индексы почти нечитаемы (красный на темно-сером — плохое цветовое решение). Начнем с чего-нибудь попроще. Как насчет того, чтобы рендерить клоунский нос на носу (простите за тавтологию)? Находим в сети изображение клоунского носа в формате PNG и добавляем его в разметку: Обратите внимание: мы загружаем изображение, но не отображаем его. Получаем ссылку на изображение и определяем его размер в пикселях: Заменяем цикл for в onResults() на функцию рисования носа: Находим нужную точку на канонической модели лица — точка с индексом 4: 
 Нас интересуют координаты x и y. Они имеют значения от 0 до 1 и, по сути, представляют собой доли или проценты размеров холста. Поэтому положение изображения по осям x и y (его центральную точку) можно вычислить следующим образом: Определяем функцию рисования носа: Результат: 
 Пойдем немного дальше и реализуем рендеринг звезд в глазах. Находим в сети изображение звезды в формате PNG и добавляем его в разметку: Получаем ссылку на изображение: Обратите внимание: мы не определяем размер звезды, поскольку он будет зависеть от размера каждого глаза, т.е. вычисляться динамически. Добавляем функцию рендеринга звезд в onResults(): Для вычисления положения звезды по осям x и y, а также ее размера (применительно к каждому глазу), необходимо определить 4 точки глаза, его ширину, высоту и центральную точку. Индексами искомых точек правого глаза являются: 
 Индексами левого глаза являются 362, 263, 386 и 374. Определяем функцию рисования звезд: Обратите внимание на 2 вещи: Результат: 
 Мои попытки продвинуться еще дальше и подружить Face Mesh с Three.js не увенчались успехом, поскольку мне не удалось обнаружить настроек для камеры (camera), которые являются критически важными для рендеринга трехмерных объектов и моделей на холсте по определенным координатам. Если вы обнаружите эти настройки или найдете способ обойти указанное ограничение, поделитесь, пожалуйста, в комментариях. Пожалуй, это все, о чем я хотел рассказать вам в этой статье. Надеюсь, вы узнали что-то новое и не зря потратили время. Благодарю за внимание и happy coding! И с наступающим Новым годом (очень хочется верить, что он будет лучше уходящего).  JavaScript Developer