MediaPipe предоставляет модели машинного обучения, упакованные и легко доступные на различных платформах — Android, ios, C++, Python и JavaScript в соответствии с документацией. В этой истории используется реализация JavaScript.

В этом посте используется модель Сегментация селфи MediaPipe, которая сегментирует выдающихся людей на сцене. В посте показано, как использовать эту модель для извлечения переднего плана, где находится выдающийся человек, и фона, оставшейся части изображения, из веб-камеры. Живая версия представлена ​​на демо-странице https://www.video-mash.com/demo.html.

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

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

Как и многие люди, большую часть последних 18 месяцев я работал из дома. За это время я заметил, что люди указывали на экран, чтобы подчеркнуть то, что они пытались донести. Это отлично работает при личном общении… но не очень во время видеозвонка…

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

В то же время MS Teams выпустила функцию режима докладчика — см. https://www.theverge.com/2021/3/2/22308927/microsoft-teams-presenter-mode-powerpoint-live-features. Так что необходимости в прецеденте больше не было.

Поэтому мы изменили идею, чтобы сделать что-то более веселое. Можем ли мы извлечь передний план видео и наложить его на изображение с камеры, чтобы создать впечатление, что действие происходит в гостиной? И можем ли мы наложить их на фон видео, чтобы можно было бомбить любое видео?

Мы получили веб-сайт с грубой реализацией этих функций и разместили его по адресу https://www.video-mash.com.

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

Исходный код этой истории доступен на следующем проекте github.

Предварительные

Настройте веб-сайт с помощью Vue CLI, чтобы создать новый проект с фреймворком VueJS. Выберите Vue 2.x и используйте многостраничное веб-приложение, не полагаясь на магазин Vuex. Также установите tailwind CSS со сборкой, совместимой с PostCSS 7.

После создания проекта установите MediaPipe Selfie Segmentation, используя:

npm install @mediapipe/selfie_segmentation

Модель сегментации селфи требует ссылки на файлы моделей при ее запуске. В этом примере используются модели, предоставленные CDN, как указано в документации. На веб-сайте используется другой подход с моделями, предоставляемыми в качестве активов на веб-сайте, чтобы избежать зависимости от внешней третьей стороны, которая может измениться в будущем и, следовательно, может нарушить функциональность. Эта проблема произошла с https://www.share-ml.io, и внедрение исправления все еще в моем списке дел.

Модели были скопированы из node_modules\@mediapipe\selfie_segmentation, чтобы гарантировать их соответствие установленной версии MediaPipe.

Веб-камера

Поток с веб-камеры получается в JavaScript с использованием Navigator.mediaDevices, как описано в Веб-документах MDN. Объект MediaDevice используется для запроса разрешения пользователя и получения веб-канала с помощью функции getUserMedia, как показано в приведенном ниже сценарии. Функция вызывается с предпочтением камеры, обращенной к пользователю, и без звука. В случае успеха функция возвращает обещание с медиа-потоком. Этот медиапоток применяется в качестве источника HTML-элемента видео. Этот элемент скрыт от просмотра, но используется для передачи медиапотока на вход, подходящий для модели сегментации селфи MediaPipe.

Фрагмент кода ниже показывает запрос канала веб-камеры и его обработку с помощью видеоэлемента, созданного на лету.

navigator
.mediaDevices
.getUserMedia({audio: false, video: {facingMode: "user"}})
.then((media_stream) => {
  this.webcam_video = document.createElement('video');
  this.webcam_video.srcObject = media_stream;
  this.webcam_video.style.display = 'none';
  document.body.appendChild(this.webcam_video);
  this.webcam_video.onplay = this.playing;
  this.webcam_video.play();
})
.catch((e) => {this.on_error("Unable to start webcam", e);});

Сегментация селфи MediaPipe

Модель сегментации селфи создается так, как описано в документации модели, с рядом незначительных отличий: (а) модель хранится в переменной компонента this.self_segmentation, и (б) после модели сегментации селфи устанавливается флаг true создано.

Модель сегментации селфи предоставляет функцию onResults, которая используется для установки функции обратного вызова, которая будет вызываться после запуска модели. Этот обратный вызов установлен на функцию компонента this.on_results в данном случае.

this.selfie_segmentation 
= new SelfieSegmentation({locateFile: (file) => {
       return `https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/${file}`;
}});
this.selfie_segmentation.setOptions({ modelSelection: 1, });
this.selfie_segmentation.onResults(this.on_results);
this.selfie_segmentation_ready = true;

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

Обработка результатов сегментации селфи

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

Эти два изображения можно использовать с операциями компоновки холста, описанными здесь, для извлечения соответствующих частей изображения. Например, рисование image , установка составной операции на destination-in и рисование маски сегментации отобразит выдающихся людей. Тот же подход с использованием составной операции destination-out используется для рендеринга фона. Параметры контекста сохраняются перед настройкой составной операции и восстанавливаются впоследствии, так что составная операция возвращается в исходное состояние.

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

on_results: function(results) {
  { // Draw the original image
    const c = this.canvas_in();
    const ctx = c.getContext('2d');
    ctx.clearRect(0, 0, c.width, c.height);
    ctx.drawImage(results.image, 0, 0, c.width, c.height);
  }
  { // Draw the segmentation mask
    const c = this.canvas_mask();
    const ctx = c.getContext('2d');
    ctx.clearRect(0, 0, c.width, c.height);
    ctx.drawImage(results.segmentationMask, 0, 0, c.width, c.height);
  }
  { // Draw the background
    const c = this.canvas_background();
    const ctx = c.getContext('2d');
    ctx.save();
    ctx.clearRect(0, 0, c.width, c.height);
    ctx.drawImage(results.image, 0, 0, c.width, c.height);
    ctx.globalCompositeOperation = 'destination-out';
    ctx.drawImage(results.segmentationMask, 0, 0, c.width, c.height);
    ctx.restore();
  }
  { // Draw the foreground
    const c = this.canvas_foreground();
    const ctx = c.getContext('2d');
    ctx.save();
    ctx.clearRect(0, 0, c.width, c.height);
    ctx.drawImage(results.image, 0, 0, c.width, c.height);
    ctx.globalCompositeOperation = 'destination-in';
    ctx.drawImage(results.segmentationMask, 0, 0, c.width, c.height);
    ctx.restore();
  }
}

Запуск сегментации селфи

Единственная отсутствующая часть связана с запуском модели сегментации селфи, то есть с вызовом ее с помощью кадра веб-камеры. Это делается в функции playing, которая была назначена свойствам onplay элемента webcam_video, как показано в разделе Поток с веб-камеры выше . Эта функция вызывается один раз, когда веб-камера начинает играть. Его реализация вызывает себя с помощью обратного вызова requestAnimationFrame и вызывает функцию send объекта модели сегментации селфи. Вызов send является асинхронным, но должен быть await, так как он не может обрабатывать несколько одновременных вызовов, исходя из моего опыта.

playing: async function() {
  if (this.selfie_segmentation_ready) {
    await this.selfie_segmentation.send({image: this.webcam_video});
  }
  window.requestAnimationFrame(this.playing);
}

Ограничения

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

Реализация не учитывает соотношение сторон камеры и холста. Это может привести к искажению изображения. Более подходящая реализация будет рисовать изображения на холсте на основе соотношения сторон камеры, чтобы избежать этого искажения.

Следующий

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

Я попытался дать небольшую подборку видео, но если вы думаете о хороших видео для использования на веб-сайте https://www.video-mash.com, пожалуйста, дайте мне знать.