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, пожалуйста, дайте мне знать.