なぜGoogle Meetの背景ぼかしが最強なのか(一般公開版)

公開:2020/10/31
更新:2020/11/24
13 min読了の目安(約11800字IDEAアイデア記事

はじめに

最近ついに、Google Meetに背景ぼかし機能が利用可能になりましたよね。日本語だとインプレスのケータイ Watchの記事などで紹介されてます。確か2020年9月末前後で順次リリースされていたと思います。 このときは「背景ぼかし」の機能しかなかったのですが、最近(私が気づいたのは2020/10/30)更にアップデートされて、「背景差し替え」機能が付いて、ぼかし機能もぼかし効果が強弱2つから選べるようになりました。まだ日本語のニュース記事は見てないですが、Googleによるアップデートの発表はちゃんとされています。

そして、Google AI BlogでBackground Features in Google Meet, Powered by Web MLという記事が公開されていて、実装についての解説がされました。

この記事はその解説記事を更に解説する・・・ような感じの記事です。元々社内Kibelaにポエムとして社内事情も含めて投稿していたのですが、解説記事が出たことを機に、全面的に改訂にしてZennに投稿します。

ちなみにKibelaにポエムを書いたのも元々はこのツイートからの一連のやりとりがきっかけです。ここでも言ってるように、Meetの背景ぼかしは本当に凄いんです。本当に本当に凄いんです。

解説記事が解説しているバージョン

先ほど「更にアップデートされて」と書きましたが、解説記事はアップデート前のバージョンの内容でしょうか、後のバージョンでしょうか?

記事を読めば分かりますが、「背景差し替え」機能についての言及があるので、アップデート後のバージョンについて解説したものだと思います。

背景ぼかしに必要な機械学習タスク

ディープラーニングを含む機械学習では「何をやりたいか」というのを「タスク」と呼びます。「この画像に写ってるものは何ですか?」という質問に犬・猫・ヨット・・・など+その他、の中から答えるというタスクであれば「画像分類タスク」です。

入力についても画像・音声・文章など色々ありますし、何をするかは画像に限っても

  • 顔のあるバウンディングボックス(正立矩形)を検出する顔検出タスク
  • 物体の種類とバウンディングボックスを検出する物体検出タスク
  • 物体の種類とピクセル単位の位置を検出するセグメンテーションタスク

などなど、様々なタスクがあります。

背景ぼかしに必要なのはこのうち、セグメンテーションタスクになります。検出対象は人間だけに限っても良いですし、どんな物体でもOKというものでも構いません。ともかく相手に見せたい前景と見せたくない背景を分離することが大事なのです。

ちなみにどんな物体でも検出するセグメンテーションのモデルとして最近話題になったのがU^-Netというもので、こんな感じに検出します。

U-2-Net検出例

Google Meetの背景ぼかし以前

皆さんご存じの通り、Zoomには以前からバーチャル背景機能として、同種の機能が実装されていました。また、SnapCameraXSplit VCamのように仮想カメラデバイスとして動作するアプリケーションもありました。

ただし、これらはいずれもネイティブアプリケーションであるからこそ、PCが持っているリソース(特にGPU)をフル活用できるわけです。欠点としてはインストール作業(モノによっては管理者権限)が必要だということがあります。

ブラウザ単体でどうにかしたい、というときは同じくGoogleのオープンソースプロダクトのTensorFlow.jsBodyPixモデルを使うことで、一応出来なくはない、という状況でした。ちなみにBodyPixは単一または複数の人物だけを検出するセグメンテーションの実装です。

しかし、実際にプロダクトに組み込もうとすると、例えば検出精度とパフォーマンスのトレードオフをパフォーマンス側に相当倒して、更に様々なパフォーマンス向上の工夫をしても、なかなか実用レベルのパフォーマンスが得られないとか他にも様々な課題がありました。

そういう状況だったからこそ、Google Meetのブラウザで完結する背景ぼかし機能は衝撃的でした。ざっくり言うとBodyPixベースだとVGAとか360pくらいの解像度で色々諦めれば何とか使えるくらいのCPU負荷かな、というくらいだったものが、Google Meetは720pで精度も十分な背景ぼかしを同等のCPU負荷でした。360pと720pというのは画像の縦ピクセル数なので、データ量で言えば4倍のサイズになります。つまり、雑に言って4倍くらい高速なのです。ちなみにこれはファーストリリースのときのパフォーマンスで、アップデート後は更に高速になっているようです。ただし、アップデート後は検出精度が悪化している感じがするので、今後のアップデートに期待しています。

前段

さて、解説記事に対してセクションごとに解説していきましょう。まずはセクションタイトルのない前段の部分です。ここは雑な訳にちょっと解説を追加した感じ書いていきます。

Web会議の重要性は増していて、プライバシー保護とか色々な課題がありますよね。その一助としてGoogle Meetは背景ぼかしと背景差し替えをリリースしました。

同じような機能は他のツールもありますけど(例えばSnap Cameraとか、Zoomアプリのように)追加のソフトウエアが必要だったりしますよね。Meetの場合は追加のソフトウエアなしにブラウザだけで実現できます。

それはMediaPipeというフレームワークを使っていて、レンダリングにはWebGLを使って、推論実行にはXNNPACKTensorFlow Liteを使うことで、クライアント側のデバイスのブラウザ上で効率的にリアルタイムでの実行を可能にしました。

後で書いてあることですが、MediaPipeはWebAssembly(WASM)として実行できる、特にWebAssembly SIMDも活用しているという点がパフォーマンスへの影響が大きいと思います。

機械学習ソリューションの概要

MediaPipeというフレームワーク

まずはMediaPipeというのが何者か、もうちょっと解説しましょう。MediaPipeというのはGoogleの開発したオープンソースのクロスプラットフォーム・カスタム可能な機械学習実行環境で、特にライブ/ストリーミング映像をターゲットにしています。

ライブ/ストリーミング映像がターゲットというのはどういうことかというと、一枚一枚の画像ではなく、秒間30枚とかの画像フレーム
を連続して処理していくということです。典型的なのはWebカメラの映像ですね。そして、画像が複数あるのではなく、映像ストリームということは直前に処理した画像は南の島の画像だったけど、次のフレームの画像は雪山だ、というようなことがほぼ無い、ということです(編集された動画が対象の場合にはあり得ますが)。映像解析ソリューションによってはその性質を使うことがあります。例えば、直前に処理した画像にある顔1と顔2が次の画像でどこに行ったかを追跡する「トラッキング」という技術などがメジャーですね。他にも上手にこの性質を使って処理を省略して軽量化したりすることなども考えられます。

MediaPipeでは処理のパイプラインをグラフ構造で表現します。下図の左側がグラフの視覚表現で、右側がそのグラフの定義です。Google Meetのパイプラインではなく、単なるグラフ構造の例なので、そこはご了承ください。

グラフの例(from公式サイト)

一番上の黄色い要素が入力ソースで、Webカメラとかの映像を出力するデバイスということになります。そして下の方にある黄色い要素が結果出力する要素です。画面だったりファイルだったり色々あるでしょう。

そして他の白背景の要素が何かしらの処理を行う要素です。

  • 画像のリサイズなどの前処理
  • 物体検出などの推論の実行
  • トラッキングなどの実行
  • バウンディングボックスの描画などの可視化処理
  • 背景ぼかし/差し替えなどの画像加工処理
  • フレーム間引きやトラッキングで前フレームのデータを渡すなどの実行制御処理

など、様々な要素があり、本体と一緒に提供されているものもあれば、自分で開発することもできます(言語はC++になりますけどね)。

Meetで使っている要素はほとんどが独自に開発されたものでオープンソースのMediaPipeの中には入っていないので、くれぐれも「MediaPipeはオープンソースなんだからそれを使えばウチも出来るだろう!」とか思わないでください(将来的に公開されるかもしれないですけど)。

背景ぼかしをMediaPipeのWASMで使うことの利点

さて、このMediaPipeのパイプラインはWASMとしてビルドすることが出来ます。記事ではここでWASMがバイナリコードで高速に実行できるとかChrome 84で導入されたSIMDで速くなるなどの特徴を持っていると書いています。もちろん、そういう特徴で高速に実行出来ることも利点なのですが、BodyPix実装と比べた時の一番の違いはランタイム環境をまたぐ回数が激減していることだと私は思っています。

解説記事中の下図を見ると分かるように、1フレームの処理でランタイム環境が変わるのは入力・出力の1回ずつで、後はすべてWASMの中で完結しています。

解説記事中の図

雑にシーケンス図みたいなものを書くとBodyPixの場合はこんな感じになります。
BodyPixの場合

実際にはTensorflow.jsやopencv.jsの呼び出しは1回ずつではなく、細かな機能を何度も呼ばれると思ってください。ただし、呼び出しごとに画像データを丸々送るわけではないので、そこまで重くはないですが、やはりランタイムが違う環境の関数等を呼び出すというのはそれなりにオーバーヘッドがあるものです。あと、BodyPixの場合はぼかし処理なら内部でJavascript処理やブラウザの画像レンダリング処理を変な風に使ってたりするのでopencv.jsは呼び出さずに済ませることもできますが、背景差し替えなどをちゃんと作ろうとするとやっぱり必要になってくると思います。

同じようにMeetの場合のシーケンス図を(解説記事が出る前の理解で)書くとこんな感じです。
Meetの場合

上でも述べたように重い受け渡し処理が激減しているというのが分かると思います。

セグメンテーションモデル

次は、実際に使っているMLモデルの話ですが、実は私はCNN周りの話が得意ではなくてあまり詳しいことは分からないです。まあ、MobileNetV3-small というのは精度とパフォーマンスのトレードオフの中では割と良い選択なのではないでしょうか(にわか知識)。モデルサイズが約400KBと小さいというのはかなり軽量で、Web会議を始めるときにダウンロードする(キャッシュがあるとはいえ)ときのユーザ体験にも大きく貢献していると思います。

口述のModel Cardによれば入力テンソルは256x144x3(full model)または160x96x3(light model)だそうです。念のために説明しておくと入力テンソルは幅x高さxRGBということです。チャンネル順序はRGBの順番、値は[0.0, 1.0]、つまり0.0以上1.0以下(境界を含む)の浮動小数点数です。

出力は256x144x2または160x96x2です。チャンネル0が背景っぽさ、チャンネル1が人物っぽさを[MIN_FLOAT, MAX_FLOAT]で表したものです。ユーザ側は後段でこの2チャンネルに対してsoftmax関数を適用します。

検出対象

解説記事では「人と背景を分離する」という説明ですし、Model Cardでも「人と背景を分離する」と書かれているのですが、現時点の実装は一般物体の前景と背景を分離するようになっているように思えます。

一例として、XSplit VCamで使えるフリー素材を使ってこんな画像をカメラ入力になるようにしてみました(カメラレンズに蓋をして何も映らないようにすると、XSplit VCamで差し替えた背景だけがカメラ入力になります)。

元々背景にボケ効果が強くかかっているので、背景差し替え処理をかけてみましょう。

ご覧の通りを前景として認識しています。試してみるとわかりますが、他にも色々な物体を前景として認識します。

前景として認識できる物体種が増えたせいなのか、(少なくとも今のバージョンは)人物の輪郭検出精度が悪化しており、特に背景であるべきところを前景として認識することが多い印象です。

効果をレンダリングする

Refinement処理

さて、セグメンテーションの推論処理が終わったら次はRefinement処理です。

機械学習全般に言えることですが、どんなにがんばっても完璧な出力を得られることはありません。セグメンテーションタスクでいえば誤検出してしまうこともありますし、検出は成功していてもピクセル単位まで正確ということはほぼあり得ません。また、単純に人間が画像として見るときにカクカクしていると見ていて違和感を感じることがあります。そこで、境界の輪郭をおおむね維持しつつノイズ除去とスムージングを同時に行うバイラテラルフィルタを更に派生させたジョイントバイラテラルフィルタというフィルタ処理をかけます。

ジョイントバイラテラルフィルタについては名古屋工業大学の福嶋先生のスライドを見るのが一番でしょう。バイラテラルフィルタといえば福嶋先生です。覚えておいてください。このスライド自体はジョイントバイラテラルフィルタを詳しく解説したりするものではなく、別のフィルタの話が本論で、関連研究としてジョイントバイラテラルフィルタとその応用についてさらっと紹介されているだけなので、アカデミックなスライドだからと必要以上に身構える必要はありません。

このスライドの9ページの図が概念図です。同じものを写した性質の異なる2つの画像を使うフィルタ処理・・・というのが端的な説明でしょうか。そして、17ページで紹介されているのが正に今回の用途と全く同じ話です。Guidanceとなるカラーの入力画像、精度が完全ではない2値マスクの2つからGuided Filterが得られると説明されています。つまり、マスクに対してフィルタをかける際に元画像の情報を活用することで、小さなネットワークで作った荒いマスクを補間し、高精度な前景・背景分離を実現しているわけです。

背景ぼかし処理

さて、最後は加工処理です。シェーダーとして表現されているのでレンダリング処理とセットと言ってもいいかもしれません。加工処理は今のところ背景ぼかしと背景差し替え2種類あって、それぞれ処理が異なるので分けて書きます。まずは背景ぼかし処理です。

ぼかしシェーダーはボケ効果を模倣します・・・日本語で言うと何だそれ? って文章ですね(笑)
この場合の ボケ効果(bokeh effect) というのは光とレンズの屈折などの光学的な意味でぼんやりする効果の意味だと思います。参考記事

それに対して ぼかし(blur) というのは加工処理でぼんやりさせることを意味している感じ・・・でしょうか。有名なところでガウシアンフィルタ(ガウシアンブラー)などのフィルタ処理があります。ぼけ効果は物体側で点だったものがセンサ面での像で円形になる光学的な性質(Circle of Confusion, CoC, 錯乱円)によるものなので、 ガウシアンブラーなどとは微妙に違う・・・のかな、すみません、この辺は自信ないです。前景画像の成分が背景に漏れ出さないように、CoCの半径によって重み付けされたものになるよう、独自のフィルタを実装しました。実行効率のためにぼかし処理は低解像度で実行されて、前景と合成される際に元の解像度になります。

前景が成分が背景に漏れ出さないというのはどういうことかをざっと説明します。

まず前景が肌色、背景が市松模様の画像を用意しました。

単純にガウシアンブラーをかけるとこうなります。

そこに前景を合成するとこうなります。円の周囲がぼんやりと明るく肌色よりになってることが見えますでしょうか。前景・背景合わせてブラー効果をかけたのでこうなるのは当然といえば当然です。これはハロー効果(ハロー=後光)と言われることがあります。

本当はこのようになって欲しいわけです。この絵はズルをして背景だけでブラー処理をかけてそこに前景を合成したものですが・・・

詳細な説明はないものの、このような効果が得られるフィルタなのだと思います。セパラブルフィルタとしているとあるので、実行時のパフォーマンスにもかなり気を使っていると思われます。

背景差し替え処理

背景差し替え処理も単にマスクの0/1でどっちの画像の画素値を取るか選べば良いというものではありません。そんな単純な処理では合成部分の輪郭でギャップが激しくなって違和感のある画像にしかなりません。

背景差し替え処理においてはlight wrappingという合成技法を使っています。この技法は差し替え後の背景の光が前景の要素にこぼれるような効果を持ち、マスク領域の輪郭のエッジを和らげ、没入感のある合成とします。また、特に前景と差し替え後の背景のコントラスト差が大きいときにおこるハロー効果を軽減することにもなります。

パフォーマンス評価

解説記事ではMacBook ProとChromeBookという2つのデバイスでの処理速度などを評価していますが、他の手法とかとの比較があるわけでもないので、正直あまり・・・
多分ほぼすべての個人GoogleアカウントでもMeetは試せるので、各自が自分のマシンで動かしてみてください。
ただし、さすがにRaspberry Pi 4でRaspberry Pi OSのChromiumを使ったときには背景処理のメニュー自体が出てきませんでした。

あと、MediaPipeのソリューションには大体付いてるModel Cardというものが公開されています。その中で17の地域で肌の色や性別の異なる評価データ

解説記事の結論

ブラウザ上での背景ぼかし・背景差し替えを導入したことで、MLモデルとOpenGL シェーダがWebでも効率的に実行できることを示しました。この機能は低消費電力・低性能デバイスでもリアルタイム実行可能なパフォーマンスを得ました。

私の感想

Googleの中の人が書いた論文で "Hidden technical debt in machine learning systems" という論文があります。

日本語で解説したスライドは以下で、5ページの図において、「実世界の機械学習システムの中で機械学習のコアっていうのは真ん中の黒塗りにされたわずかな部分だけなんですよ。その周りに様々なコンポーネントがあります」みたいなことを言っています。

今回解説したのはこのコア部分とそのほんのちょっと外周にある背景処理特有の古典的画像処理の頃から研究され続けてきた問題に対してブラウザ上で実行するためにどう対処するか、という部分だけです。それだけでもこんなに様々なことをきちんと考慮していかないといけない上に、上図が示す更に周りの部分もあるわけです。

幸いなことに人物検出というのは時間の経過にともなう変化が比較的少ない分野かもしれません。例えば緑色や青色の肌をした人種が急激に増えた、とか耳が長くなっていった、とかの変化はほとんど考慮しなくて良いと思います。しかし、そうはいっても最近の情勢のように急激にマスクをする人が増えた、というようなことも起こるわけなので、モデルやデータセットのアップデートは必要です。そこまで含めたトータルのシステムを用意するのはとてもとても大変です。

ただ、こうやってコアとなるネットワーク構造だけでなく、ブラウザで実行するための仕組みをGoogleが全力で実装していくと、ここまでのパフォーマンス差が出るんだなぁ・・・というのは衝撃的でした。これに追いつくのはほぼ無理ですが、何らかのブラウザ上で動作するMLシステムを作るときには参考になることが沢山あります。パフォーマンスを徹底的に意識するのはここまでの範囲で、あとの周囲の要素は最悪お金で計算能力などのリソースを買えばある程度のレベルまでは何とかなるかと(笑)

とはいえ、ここまでやってもやっぱりブラウザ上で実行するという制約は厳しく、ネイティブアプリで好き放題できるXSplit VCamはもっともっと低負荷で動きます。未登録でもちょっとロゴが入る程度ですし、ヨドバシで4,290円で買えばライセンスキーが手に入ります。ソースネクストに登録するのがちょっと・・・という人は直接VCamをアクティベートできるライセンスキーも入っているのでご安心を。Windowsならばこれを買っておけば間違いないです。Mac版も開発中ということではあるので、公式サイトにメールアドレスを登録しておきましょう。

というわけで、Google Meetが最強と言いながらなぜかXSplit VCamを宣伝してこの記事を終わります。

※著者はGoogle、ソースネクスト、SplitmediaLabs(XSplit VCamの開発元)、ヨドバシカメラのいずれにも所属しておりません。

2020/11/25 追記

MediaPipeのグラフを見直していて気付いたことがあります。

グラフの例(from公式サイト)

IMAGE_GPU のようにGPUと末尾についた端子(?)はGPU上のデータを指していると思われます。ということは、結構な部分をGPUのまま処理してますね。特にMediaPipe Hair Segmentation のデモが顕著です。推論結果からマスク画像を作る部分だけがCPUで、それをGPUに送りなおして色置換をやっているようです。Meetの背景ぼかし・差し替えでも同じようにGPUとCPUを上手く使い分けて高速処理を実現しているのでしょうね。