ブラウザ上で顔入れ替えと背景入れ替えをするアプリを作った。
(noteに投稿した記事をこちらに引っ越しさせました。note)
はじめに
いきなりですが、私はPCやスマホに、たくさんのアプリケーションをインストールするのがあまり好きじゃないです。管理が面倒だし、機種変のときに再インストールをするのも面倒なので。
ということで、なるべくブラウザで完結するアプリを実現できないか?ということを目指して頑張ってみています。その一つとして、Amazonが提供しているChime SDKというビデオ会議システムのSDKを持ちいてブラウザ上でビデオ会議システムを構築するようなプロジェクトを進めています。
今回の話は、その中で使われるバーチャル背景機能と顔入れ替え機能の実現についてになります。これらの機能の実現にAIの処理を用いて行うのですが、AIの処理は比較的重い処理になるので、Webworkerを使用して実現しました、という話になります。
最終的に作成するのはこんな感じのもの。
今回やりたいこと
ビデオ会議では、プライバシー保護の観点から、背景を別の画像に入れ替えるバーチャル背景機能が使われることが多いと思います。 また、youtubeなどの動画サイトでは、話者を別のアバターに入れ替えるということもよく行われています(FaceRigとか)。 今回は、この2つを同時にブラウザ上で行ってみます。具体的には、バーチャル背景を行いつつ、話者の別の顔に入れ替えてみます。 また、少し唐突ですが説明の都合上(*1)、手の動きの検出も併せて実施します。
(*1) 後で理由を示します。
基本要素
今回やりたいことをブラウザ上で行うためには、AIを用いて、次のことを行う必要があります。
- バーチャル背景:人物と背景の識別
- 顔の入れ替え:目、鼻、口といった顔のランドマーク検出
- 手の検出:手の検出
これらを行うためのトレーニング済みAIモデルは下記のサイトで提供されています。
今回は、これらのトレーニング済みAIモデルを活用して機能実現をしていきたいと思います。
課題
・・・と、意気込んでみましたが、実は課題が2つあります。
1つ目は各AIモデルが前提とするTensorflowjsのバージョンの違いです。 Bodypixはtensorflowjsの1.x系が前提となっています。FacemeshとHandposeは2.x系が前提となっています。 tensorflowjsはバージョン間でAPIの名前やパッケージ名が変更になっているものがあります。 この影響で、同時に異なるバージョンを前提とするAIモデルを動かそうとするとどちらか一方がAPI呼び出しに失敗します。 例えば、下記のような例外が発生します。 もしかしたら、うまい回避策があるのかもしれませんが、私が調べた範囲では回避方法は見つけられませんでした。 (どなたかご存知でしたら教えていただけると幸いです。)
2つ目は処理時間の問題です。 AIの処理は比較的重い処理なので、複数の処理を単一のスレッドで実行するとどうしてもfpsが悪化します。
解決方法
ということで、最初の方にネタバレしていますが、今回はwebworkerを用いてこれらの課題を解決します。 webworkerで実行される処理は独立したプロセスで動きますので、プロセス間で異なるバージョンのtensorflowjsモジュールをロードできます。 また、それぞれのプロセスは並列して実行されますので、リソースの空き具合にもよりますが、fpsの悪化を抑えることが期待できます。
デモアプリの作成と動作検証
それでは、期待通りの動作をするか、デモアプリを作成して検証してみましょう。
ざっくりと構成は次のようにします。 ウェブカメラから取得した各フレームに対し、BodyPix, Facemesh、HandPoseを用いて、人物の位置、顔の位置、手の位置を取得します。 また、予め入れ替える顔をFacemeshを用いて顔の位置を取得しておきます。 これらの各パーツの位置情報を用いて、各イメージ(ウェブカメラのフレーム、背景画像、入れ替え先の画像)をマージして最終的な画像を生成します。 この中のAIの処理(BodyPix, Facemesh, HandPose)はWebworkerを用いてバックグラウンドで処理を行うようにします。
では、作成したデモを動かしてみます。 次のように、異なるバージョンのtensorflowjsを前提としているAIモデル(Bodypix, Facemesh, HandPose)も動かすことができていることがわかります。
こちらのURLで実際に触っていただけます。(chromeのみ。safariは未サポート)
性能検証
それでは、fpsの方は改善されているでしょうか?これも検証してみましょう。ただ、残念ながら、上記のとおりbodypixとその他のモデルは同一スレッド上で動かすことができません。なので、今回はbodypixの代わりに画像をasciiart化する処理を入れてみます。なお、これが今回唐突に手の検出を行っている理由です。本来は、BodyPixとFacemeshのみでよかったのですが、単一スレッドで2つを動かした場合と、webworkerで異なるプロセスに分離した場合のFPS比較がしたかったのですが、両者を単一スレッドで動かすことができなかったため、代わりにFacemeshとHandPoseを同時に動かすことにしました。
次の映像は私のメインマシン(core i9-9900KF, GeForce GTX 1660)で動かしたときのものです。上がwebworkerを使った場合です。下がwebworkerを使わなかった場合です。webworkerを使わなかった場合は12fps程度ですが、webworkerを使ったときは17fps程度出ています。
参考までに、ThinkpadとMacBookで動かした場合のFPSも載せておきます。 なお、MacBookはChromeで動かしています。
このデモも次のURLで実際に触っていただけます。(chromeのみ。safariは未サポート)
まとめ
以上のとおり、webworkerを用いることで複数の重いAIモデル、tensorflowjsのバージョンが異なるAIモデルを同時にブラウザ上で動かすことができました。 今回のデモは単一のブラウザ上で動くものでしたが、画像処理後の画像はHTMLCanvasElement上に描画していますので、 以前ご紹介した方法(こちら)で簡単にAmazon Chime SDKを用いたビデオ会議システムに組み込むことができます。
ただ、webworkerを使用するのはそれなりの前提知識が必要となりますので、今回使用した処理については簡単に導入できるようにnpm化してあります。 ソースや使い方は次のリポジトリに置いてあります。また、今回の動作検証に用いたデモのソースもおいてあります。ぜひご活用ください。
参考
顔入れ替えのやり方はこちらを参考にさせていただきました。
本ページのデモで使用している顔はこちらのサイトで生成した画像を使用しています。 (リンク先のデモは別画像を用いています。)
Discussion