🎥

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

2020/11/01に公開

はじめに

最近ついに、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 はオープンソースなんだからそれを使えばウチも出来るだろう!」とか思わないでください(将来的に公開される可能性はありますが)。

(2021/08/21 追記)
MLKit Selfie Segmentation 近いモデルは公開されましたが、Meet で使っているそのものではないようです。

背景ぼかしを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 を上手く使い分けて高速処理を実現しているのでしょうね。

Discussion