LoginSignup
22
20

More than 3 years have passed since last update.

任意のバーチャル背景を使えるページを作った

Last updated at Posted at 2020-05-09

TL; DR

  • ウェブカムの入力から人物部分のみをくりぬいて背景画像と合成するWebページを作成した
  • OBS Studio + 各種OS向けプラグインで任意のビデオ会議ツールに利用可能

jitsiでの動作例:
image.png

動機

非常に多くの企業・団体等がバーチャル背景を提供しています(例: Web会議で使える「 #バーチャル背景 」配布がブームに 企業まとめ (1/12) - ITmedia NEWS)。
あらかじめバーチャル背景をサポートした配信ツール(Zoom, Microsoft Teams等)では利用が容易ですが、そうでないツール(Google meet, Jitsi等)もあります。Snap Cameraを使う方法もあるのですが、レンズを自作した場合無条件で公開になってしまうようです。となると、再配布可能な素材でしか使えないことになります。

その問題を解決すべく取り組んで得られた知見を紹介します。

docker+v4l loopbackを使った方法

最初に見つけたのは、Open Source Virtual Background | BenTheElderという記事です。
この記事では、GPU対応版Tensorflowjs-nodeをコンテナとして扱いつつ、Python+OpenCVで背景合成を行い、pyfakewebcamv4l2loopbackを使って合成した結果をvideo4linuxデバイスとして見せるような挙動をします。構成図は以下のような感じです。
image.png

悪くはないのですが、いくつか問題があります。

  • docker+GPUコンテナの利用が前提
    • httpを経由した画像のやりとりのオーバーヘッドが気になる
  • Linuxでの利用が前提(v4l2loopback)

この段階で、まずは「BodyPixをpythonで動かせないか?」ということを考えました。

PythonでBodyPixの推論をする(半分成功)

simple_bodypix_pythonというコードが公開されていました。これは、https://storage.googleapis.com/tfjs-modelsで公開されている訓練済みtfjs向けBodyPixモデルをTensorFlow形式に変換して使うためのコードです。
これを試してみたところ、量子化しないモデルではうまく推論できましたが、量子化モデルでは期待した動作になりませんでした。どんな画像を与えても、すべて1が返ってくるような結果しか得られません。tfjs側のコードとも見比べてみたのですが、特別なにかしらの前処理をしているようには見えませんでした。何か見落としがあるかもしれませんが…

一点、量子化モデルでは非常に荒い推論結果(50~60x30~40ピクセル程度)しか求めておらず、その結果をbilinear補完でで入力サイズに拡大した結果を返しているということがわかったのは収穫です。floatモデルだと、入力画像と同じサイズの出力を直接返します。これは相当計算量がかかっているだろうということは推測できました。

ブラウザで完結させることを考える

Pythonに変換させるアプローチをいったんあきらめ、ブラウザで処理を行う方向に転換しました。ブラウザ上なら、GPUもうまいこと使ってくれるかもしれません。

これに関しては以下の記事が参考になりました。

特に後者の記事は非常に参考になりました。既にやりたいことの半分はできています。行っているのはBodyPixのtoMaskメソッドを使って背景をグレーにしているだけなので、グレーにする代わりに任意の背景画像に置き換えれば済む話です。

ここであらためてオリジナルのBodyPix.toMaskがどういう処理を行っているかを追いかけてみました。結局のところ、単純にImageオブジェクトの全ピクセルへアクセスしているだけだということを突き止めました。
これで十分パフォーマンスが出ているので、自分も単純にforループでセグメンテーション結果と突き合わせながら、前景・背景ピクセルをピックアップするだけで実現できるはずです。

書いたコードの一部を以下に示します。

// segmentation: bodypix.segmentPerson(image)の返り値 (2次元配列)
//   0: 背景, 1: 前景(人体)
// data_img: 前景画像(mediaから取得したwebcam画像)
// data_bg: 背景画像
        function drawToCanvas(canvas, segmentation, img, data_img, data_bg) {
            const ctx = canvas.getContext("2d");
            canvas.width = img.width;
            canvas.height = img.height;
            let width = img.width;
            let height = img.height;
            let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            let pixels = imageData.data;
            for (let y = 0; y < height; y++) {
                for (let x = 0; x < width; x++) {
                    let base = (y * width + x) * 4;
                    let segbase = y * width + x;
                    if (segmentation.data[segbase] == 1) { // is fg
                        pixels[base + 0] = data_img.data[base + 0];
                        pixels[base + 1] = data_img.data[base + 1];
                        pixels[base + 2] = data_img.data[base + 2];
                        pixels[base + 3] = data_img.data[base + 3];
                    } else {
                        pixels[base + 0] = data_bg.data[base + 0];
                        pixels[base + 1] = data_bg.data[base + 1];
                        pixels[base + 2] = data_bg.data[base + 2];
                        pixels[base + 3] = data_bg.data[base + 3];
                    }
                }
            }
            ctx.putImageData(imageData, 0, 0);
        }

こうして出来上がったのが https://knok.github.io/virtbg/ です。
アニメーション処理周りは後者の記事を大いに参考にしています。

OBS Studio + OBS-VirtualCam (Windows)

Windows環境では、OBS-StudioにOBS-VirtualCamプラグインを組み合わせることで、任意のウィンドウ表示領域をDirectShow deviceに見せることができます。

OBS Studioの設定

OBS StudioとOBS-VirtualCamのインストールは完了しているものとします。

新規プロファイルを作成し、ソースの"+"を押し、「ウィンドウキャプチャ」を選択する。
image.png

適当な名前をつける。
image.png

先のページを表示したウィンドウを選択する。適当にリサイズしておき、合成画面だけが表示されるようにしておく。
image.png

適当に移動・リサイズ・クリッピングをして表示領域を拡大しておく。
image.png

メニューの「ツール」から「VirtualCam」を選択。
image.png

ダイアログで「Start」ボタンを押す。
image.png

ブラウザのカメラ設定で「OBS Camera」が選択できることを確認。
image.png

OBS (macOS) Virtual Camera

Mac環境でも同様のプラグインがあります(手元に最近のMac環境がないため未検証)。

obs-v4l2sink

Linux環境ではv4l2loopbackに出力するプラグインがあります。

Debian 10での導入

# 適当な作業ディレクトリに移動
sudo apt install obs-studio libobs-dev
# 必要なkbuildは入れておく
sudo apt install v4l2loopback-dkms v4l2loopback-utils
# この時点でv4l2loopback.koがビルドされる
# プラグインのビルドにソース本体が必要
## deb-srcをapt lineに入れておく必要あり
apt source obs-studio
git clone https://github.com/CatxFish/obs-v4l2sink
cd obs-v4l2sink
mkdir build
cd build
# ここでapt sourceで取得したソースを参照
cmake -DLIBOBS_INCLUDE_DIR=../../obs-studio-22.0.3+dfsg1/libobs -DCMAKE_INSTALL_PREFIX=/opt/obs ..
make
# プラグインディレクトリに直接コピー(作法としては良くない)
sudo cp v4l2sink.so  /usr/lib/x86_64-linux-gnu/obs-plugins
# chromeでの利用にはこのオプションが必要とのこと
sudo modprobe  v4l2loopback exclusive_caps=1
obs
# modprobeしたときに増えた/dev/video%dを指定

まとめ

TensorFlow.js + BodyPixを用いることで、Webブラウザを介してバーチャル背景の合成をする方法を実現しました。OBS StudioとプラグインにOS依存な部分を任せることで、マルチプラットフォームでバーチャル背景を利用することができます。

課題

サンプル動作例 (Windows10, Chrome 81, i5-4670K, GTX-1060, multiplier: 0.75)
y4069-ju7wv.gif

  • このスペックならそれなりにスムーズ
    • multiplier: 0.50でノート(i7-8550U)で確認したところ、体感10fpsを切る程度
22
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
20