PHONE APPLI Engineer blog

エンジニアブログ

body-pixを使ってさっくりバーチャル背景

気づいたらもうすぐ6月。ついこの間までお正月だったのに!

はじめましてこんにちは、リサーチデベロップメント(旧テクノロジーリサーチ部)の たかはしとし( @doushiman_jp ) です。

好きな武器はランス、好きな鉄蟲糸技は流転突きです。

前回のブログで、 「Clubhouse っぽい音声チャットを作ってみた」っていう記事を書かせていただいたのですが、Skyway のような WebRTC を使うと、音声チャットの他にも映像付きのWeb会議的なものが実装できたりします。

phoneappli.hatenablog.com

でも、Web会議と言えばアレ、欲しいっすよね。

そう、 f:id:doushiman_jp:20210521140405p:plain バーチャル背景です。

有名どころのWeb会議ツールには、ほぼほぼ標準装備な感じのバーチャル背景ですが、自前でやろうとするとなかなか技術的ハードルが高いです。

特に機械学習的なアレを使って、人間の形をアレするあたりが一見さんお断りな敷居の高さを感じます。

なので、先人の知恵を借りて、バーチャル背景っぽいものが(できるだけ簡単に)実装できないか、を試してみます。

TensorFlow.js

お手軽に、という前提なので、TensorFlow.js の学習済みモデルを使って、人物が切り出せないかな、と考えました。

[TensorFlow.js] はJavaScript で動作する機械学習ライブラリです。

すると、公開されている学習モデルの中に、それっぽいものがありました。

[body-pix]

人体セグメンテーション

人間と体の部位のセグメンテーションをリアルタイムに行います。

なんだか良さげですね。

早速コードを参照しつつ、試してみましょう。

実装

まずはこんな感じで、カメラデバイスのストリームを設定します。

HTML

<video id="video"></video><!-- カメラ画像を出力します -->
<canvas id = "canvas1"></canvas><!-- カメラ画像をCANVASに -->
<canvas id = "canvas2"></canvas><!-- 加工した画像を出力します -->

JS

const video = document.getElementById("video")
navigator.mediaDevices.getUserMedia({
  video: true
  audio: false,
}).then(stream => {
  video.srcObject = stream;
  video.play()
}).catch(e => {
   console.log(e)
})

これで、 video タグにカメラ画像が表示されます。

今度は、この映像をcanvasに書き出します。

let canvas = document.getElementById("canvas1");
video.addEventListener("timeupdate", function(){
   let canvas = document.getElementById("canvas1");
   canvas.getContext("2d").drawImage(video, 0, 0, 120, 100);
}, true);

これで、 canvas1 に video が表示されます。

いよいよ、この canvas1 に描画されている画像を使い、バーチャル背景っぽいものを表示してみます。

ここはほぼ、公式のサンプルを引用しています。

async function loadAndPredict() {
  const img = document.getElementById('canvas1');
 
  const net = await bodyPix.load();
 
  const segmentation = await net.segmentPerson(c);
 
  const backgroundBlurAmount = 10;
  const edgeBlurAmount = 5;
  const flipHorizontal = false;

  const canvas = document.getElementById('canvas2');

  bodyPix.drawBokehEffect(
    canvas, img, segmentation, backgroundBlurAmount,
    edgeBlurAmount, flipHorizontal);
  }
}

この関数を、先ほどの canvas1 への書き出されたタイミングでコールするために、下記のようにします。

let canvas = document.getElementById("canvas1");
video.addEventListener("timeupdate", function(){
   let canvas = document.getElementById("canvas1");
   canvas.getContext("2d").drawImage(video, 0, 0, 120, 100);
   loadAndPredict() // ←追加
}, true);

さて、では試してみます。

適当なボタンをつけて、コピーする関数をコールするようにして実行。

f:id:doushiman_jp:20210520163341p:plain

おお、ボケた。

左が元々の動画、右がTensorFlow.js を通してcanvasに出力した加工済みの画像になります。

僕の顔を中心にして、周囲がボケているのがお分かりになるかと思います。

f:id:doushiman_jp:20210520163403p:plain 移動しても追従します。

ただ、やはり一般的なWeb会議ツールのそれと比較すると、あたりまえですが認識制度が甘い印象で、 顔まで被るようにボケてしまって、天に召されそうな画像になったり、 顔に何かが被った時などは、容易に画面全体にボケちゃったりしてしまいます。

ここの精度は各社頑張って上げている部分なのでしょうね。

あとは、1回Canvasに書き出すところがVideoからのキャプチャを、連続して出力している(パラパラ漫画の要領)ので、どうしてもガタついてしまいます。

VideoをそのままTensorFlowに食わせる方法が見つからなかったのですが、ここをクリアしないと実用は難しいかな、という印象です。

というところで、今回はここまでとしたいと思います。

最後までご覧いただき、ありがとうございました。


PHONE APPLIについて

phoneappli.net
phoneappli.net