Amazon Chime SDKでセンターフレームを作る
こんにちは。
Amazon Chime SDK for Javascriptのバージョンが3.x系になって久しいですが、私がOSSで公開しているデモも漸くv3.xに対応させることができました。v2.x系からv3.x系への移行の手順は公式のドキュメントに記載されていますので、これから移行を考えておられる方はそちらを参考にされるとよいと思います。
今回はSDKの移行に伴い、デモに簡易版センターフレーム(Center Stage)を追加したので、その実装方法についてご紹介したいと思います。
つくったものはこんな感じのものです。
簡易版センターフレーム
センターフレームとは、最近のipad proのカメラの機能で、被写体の顔を認識して自動追従する機能です。ZoomやFaceTimeはこのセンターフレームを使用した機能を提供してます(Zoom, Facetime)。詳細は不明ですが、このセンターフレームは被写体の位置に応じてズームしたりフォーカスを合わせるなどipadのカメラの機能と連携して実現されていると推測されます。公式のリンク先では少しわかりにくいですが、gizmodoのこのページを見るとかなり高速、高画質な映像を撮影できているのがわかります。
今回は、普通のWebカメラと機械学習モデルを用いて簡易版のセンターフレーム機能を作成してみます。具体的には、Amazon Chime SDK for Javascriptのアプリの中で、Webカメラの映像から顔を検出し、その周辺をトリミングして送信する機能となります。オリジナルのセンターフレームと異なりズームやフォーカスなどのカメラの機能とは連携できないため、速度や画質はオリジナルに及ばないものの、ブラウザ上で特別なハードウェアが不要なのが利点です。
BlazeFace
顔の検出には機械学習のモデルを用います。Mediapipeが軽量で高精度なBlazeFaceというモデルを提供しています。このモデルは下の図のようにカメラで写された映像に対しリアルタイムで顔の位置を検出することができます。下の図では、検出した顔に赤い枠をつけていますが、簡易版センターフレームでは赤い枠より少し大きな範囲でトリミングすることになります。
Ref:mediapipe BlazeFace
このモデルはとても軽量でWebGLで動かすよりCPU(WASM+SIMD)で動かす方が高速に処理できることで知られています(ref)。
Ref:TensorFlow Blog
なお、BlazeFaceと他の類似モデルの処理時間を比較しておられる方がいます。このデータを見てもBlazeFaceがかなり高速に処理できていることがわかります。
Amazon Chime SDK for Javascriptアプリへの組み込み
Amazon Chime SDK for Javascriptでは、Webカメラで撮影した映像をサーバ[1]に転送する前に編集するAPIを提供しています。Video Processing APIです。今回の簡易版センターフレームではこのAPIを用いて映像転送前に顔のトリミングを行います。Video Processing APIの詳細は公式のドキュメントや、以前の私の記事を参照してください。
前述の通り、BlazeFaceはWebGLを使用するよりもCPU(WASM+SIMD)で動かした方が高速に動くようです。一般的に、ブラウザで動くアプリケーションにおいてはメインスレッド(のCPU)で大量の処理をしない方が良いとされます。今回もBlazeFaceはWebworkerで動かしたいと思います。
下図は、ざっくりの構成図と処理の流れです。
(1) Webカメラで映像をキャプチャします。
(2) Amazon Chime SDK for Javascriptのデモの中でVideo Processor APIの処理を呼び出します。この時、Video Processor APIの内部処理(VideoFrameProcessor)では、Main ThreadからWebWorkerに処理をリクエストします。WebWorker上でBalzeFaceを用いてユーザの顔の位置を特定し、Main Threadに応答します。
(3) Main Threadは顔のトリミングを行います。
(4) トリミングした映像をAmazon Chimeのサーバに送信します。
実装
BlazeFaceをWebWorkerで動かすモジュール
BlazeFaceをWebWorkerで動かすためにはいろいろとおまじないを実装する必要があり少し面倒です。手前味噌ではありますが、npmでBlazeFaceをWebWorkerで動かすモジュールを公開していますので、今回はこれを利用しましょう。なお、このモジュールのソースコードとデモも公開しているので興味を持たれましたらリポジトリをご確認ください。
ここで提供しているデモでも、センターフレームと同じような動きをさせることができます。デモページの右側の"application mode"のプルダウンメニューから"tracking"を選択してみてください。下のような映像が表示されると思います。左がオリジナルの映像、右側が簡易版センターフレームで顔を切り出した映像になります。なかなかよさそうですね。
ついでに、backend-selectorというプルダウンでWebGLとWasmを切り替えてみてください。左上にフレームごとの処理時間が表示されますが、Wasmの方が高速に処理されていることがわかると思います[2]。
Amazon Chime SDK for Javascript上での実装
この機能をアプリケーションに組み込むには、上記の構成で説明した通りVideo Processor APIのVideoFrameProcessorに処理を実装することになります。上記のnpmモジュールでは、顔の検出とトリミングの位置を算出するメソッドが提供されているので、これらを使用することでとても簡単に実装することができます。
VideoFrameProcessorは、映像の処理を行うprocess
メソッドを実装する必要があります。このメソッドで使用する各種メンバーを初期化しておきます。managerが上記のnpm モジュールです(1)。configとparamsでmanagerの挙動を設定します(2)。今回は、webworker上のwasmで動かしたいので、backendTypeをwasmに、processOnLocalをfalseに設定しています(3)。
private targetCanvas: HTMLCanvasElement = document.createElement('canvas');
private targetCanvasCtx: CanvasRenderingContext2D = this.targetCanvas.getContext('2d')!;
private canvasVideoFrameBuffer = new CanvasVideoFrameBuffer(this.targetCanvas);
private manager = new BlazefaceWorkerManager() // <--- (1)
private config = generateBlazefaceDefaultConfig(); // <---(2)
private params = generateDefaultBlazefaceParams(); // <---(2)
constructor() {
this.config.backendType = BackendTypes.wasm // <---(3)
this.config.processOnLocal = false // <---(3)
this.manager.init(this.config)
}
process
メソッド自体は次のようになります。npmモジュールのpredictメソッドで顔の位置を特定します(1)。特定された顔の位置からトリミングする矩形を算出します(2)。トリミングする矩形を出力用のイメージバッファに書き込み(3)、呼び出し元に返します。
process = async (buffers: VideoFrameBuffer[]): Promise<VideoFrameBuffer[]> => {
if (!buffers[0]) {
return buffers;
}
const canvas = buffers[0].asCanvasElement!()
if (!canvas) {
return buffers;
}
const prediction = await this.manager.predict(this.params, canvas as HTMLCanvasElement) // <--- (1)
const trackingArea = this.manager.fitCroppedArea(prediction, canvas.width, canvas.height, this.params.processWidth, this.params.processHeight, canvas.width, canvas.height, 1, 0.4, 0, 0); // <--- (2)
this.targetCanvasCtx.clearRect(0, 0, this.targetCanvas.width, this.targetCanvas.height)
this.targetCanvasCtx.drawImage(canvas, trackingArea.xmin, trackingArea.ymin, trackingArea.width, trackingArea.height, 0, 0, this.targetCanvas.width, this.targetCanvas.height); // <--- (3)
buffers[0] = this.canvasVideoFrameBuffer;
return buffers;
}
あとは、VideoFrameProcessorに必要な後処理を定義するdestroy
メソッドを実装すれば完了です。Video Processor APIにVideoFrameProcessorを登録する方法などは公式のドキュメントをご確認ください。後述するデモのリポジトリでも実装を公開しているので、より具体的にはそちらをご覧ください。
デモ
ここでご紹介した簡易版センターフレームを実装したデモを下記のリポジトリで公開しています。セットアップの方法はリポジトリをご確認ください。
今後改変の可能性もあるので、お試しになる場合は次のタグでクローンするようにしてください。
git clone https://github.com/w-okada/flect-chime-sdk-demo.git -b center_stage --depth 1
セットアップ方法はリポジトリのreadmeに記載しています。
センターフレームを使用する場合はヘッダのギアアイコンでデバイス設定を開き、カメラ設定でCenter Stageを有効にします。すると次の映像のように動くと思います。左のブラウザからAmazon Chimeのバックエンドを通して右のブラウザに映像を転送しています。悪くないかなと思います。
まとめ
今回はAmazon Chime SDK for Javascriptのアプリケーションに簡易版センターフレーム機能を組み込む方法をご紹介しました。オリジナルと比較すると劣る部分もありますが、ブラウザ上で特殊なデバイスを使わずに新しいUXを実現を実現する一助になるのではないかと期待しています。例えば、全参加者の顔をサイズをズームアップでそろえてタイル状に並べるとかできそうですよね。
謝辞
人物の動画、背景画像はこちらのサイトの画像を使わせていただきました。 https://pixabay.com/ja/ https://www.irasutoya.com/
Disclaimer
本ブログのソフトウェアの使用または使用不能により生じたいかなる直接損害・間接損害・波及的損害・結果的損害 または特別損害についても、一切責任を負いません。
Discussion