Amazon Chime SDKとBodyPixでビデオ会議システムにバーチャル背景を実装した話。 

` この記事はこちらでも紹介しています。 `
https://cloud.flect.co.jp/entry/2020/05/20/101615


こんにちは。前回、Amazon Chime SDKについてご紹介しました。
https://cloud.flect.co.jp/entry/2020/05/13/103634

今回は、引き続きAmazon Chime SDKに関連して、自分でVirtual背景を作る方法をご紹介します。

Virtual背景とは、背景を他の画像に置き換える機能のことで、主に相手に自分の部屋を見せたくないなーというときに使うものです。
世の中のビデオ会議ソフトでZoomやMS Teamsは Virtual背景をサポートしていますが、Amazon ChimeやAgora.io、Twillioなどでは提供されていないようです。

今回は、なければ作ればいいの精神で、Amazon Chime SDKを用いたビデオ会議システムでVirtual背景を自分で作ってみようと思います。
なお、Twillioでも以下で説明するやり方でできるはずです。ドキュメントを読む限りは。（未検証）

作成したVirtual背景はこんな感じで動きます。

![virtualBG.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365991/adb72a23-e99e-6b5d-0b0d-04bee2b4dc20.gif)

### Amazon Chime SDK for Javascriptの機能とMediaStream
Amazon Chime SDK for Javascriptは、相手にシェアする映像(音声を含む)を指定するメソッドを、次の映像の種類ごとに提供しています。

- カメラの映像
- 画面キャプチャ
- 動画

それぞれの入り口のメソッドは異なりますが内部は似たような動きをしており、処理の途中からMediaStreamを生成してそこから映像データを読み出して転送するという流れになります。
MediaStreamについては[ここ](https://developer.mozilla.org/ja/docs/Web/API/MediaStream)とかで詳しく述べられているので、興味のある方はご参照ください。

公式のチュートリアルや多くのAmazon Chime SDKのデモでは、カメラの映像をシェアするときに、Amazon Chime SDKのメソッドがリストアップするカメラデバイスのIDから、一つのカメラデバイスのIDを入力元として選ぶという処理をしています。
しかし、SDKのドキュメントにも記載のとおり、実は同じメソッドでMediaStreamをそのまま渡すこともできるようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365991/02d196ff-d165-76ce-9359-154b82f65664.png)

https://aws.github.io/amazon-chime-sdk-js/interfaces/audiovideofacade.html#choosevideoinputdevice

つまり、実はAmazon Chime SDKではMediaStreamさえ取得できれば何でも(?)シェアできるということです。すばらしく柔軟な作りです。この事実だけでも活用のアイデアが湧きまくり!!なのですが、今回はこれを前提にVirtual背景を実現する方法について説明していきます。

### BodyPix
ところで、Virtual背景を実現するためには人物と背景を識別する必要があります。
(一時期、ZoomのVirtual背景は[背景差分](https://ja.wikipedia.org/wiki/%E8%83%8C%E6%99%AF%E5%B7%AE%E5%88%86)で実現しているという噂も聞きましたが、背景差分だけじゃできないですよね。。。多分。)
このような人物と背景の識別するJSライブラリとして、Googleがとても高速で高性能なものを提供しています。BodyPixです。

https://github.com/tensorflow/tfjs-models/tree/master/body-pix


BodyPixは、ImageDataや、HTMLCanvasElement, HTMLImageElementなどをインプットとして与えれば
マスク画像を作ってくれます。これを用いれば、人物と背景をピクセル毎に判別できそうですね。

### バーチャル背景の実装
ということで、ここまでの要素でこんな実現方式が考えられます。

- まず、カメラから取得した画像をHTMLCanvasElementに描画します。
- そして、このHTMLCanvasElementをBodyPixに入力して人物と背景を識別したマスク画像を生成します。
- カメラから取得した画像に対し背景の部分を別のイメージのデータに置き換えてHTMLCanvasElementに描画します。
- このHTMLCanvasElementからMediaStreamを取得して、Amazon Chime SDKのchooseVideoInputDeviceに設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365991/88cd8c87-218c-4af8-3bd2-48307eb09872.png)

### カメラ映像と背景の合成
全体像はここまでで説明終わりなのですが、実際カメラから取得した画像と背景の合成はどうやってやるの？と言う疑問もあるかと思います。
これは地道にピクセル単位でデータの置き換えを行います。（もっと賢いやり方があれば教えてください。。。）
HTMLCanvasElementからはImageDataが取得できますが、このImageDataは生のピクセル毎の情報を格納しています。
なので、カメラから取得した画像、BodyPixで作成したマスク画像、背景用のイメージ画像を、同じサイズに整形すれば、それぞれ同じピクセルを左上から順番に走査していくことができます。

ざっくりこんな感じです。
マスク画像(maskedImage)のピクセルが#ffffffffの場合は背景画像(bgImageData)でカメラの画像(pixelData)を置き換えるというようなことを順番に行います。

```
for (let rowIndex = 0; rowIndex < maskedImage.height; rowIndex++) {
    for (let colIndex = 0; colIndex < maskedImage.width; colIndex++) {
    const pix_offset = ((rowIndex * maskedImage.width) + colIndex) * 4
    if (maskedImage.data[pix_offset] === 255 &&
        maskedImage.data[pix_offset + 1] === 255 &&
        maskedImage.data[pix_offset + 2] === 255 &&
        maskedImage.data[pix_offset + 3] === 255
    ) {
        pixelData[pix_offset] = bgImageData.data[pix_offset]
        pixelData[pix_offset + 1] = bgImageData.data[pix_offset + 1]
        pixelData[pix_offset + 2] = bgImageData.data[pix_offset + 2]
        pixelData[pix_offset + 3] = bgImageData.data[pix_offset + 3]
    } else {
        pixelData[pix_offset] = maskedImage.data[pix_offset]
        pixelData[pix_offset + 1] = maskedImage.data[pix_offset + 1]
        pixelData[pix_offset + 2] = maskedImage.data[pix_offset + 2]
        pixelData[pix_offset + 3] = maskedImage.data[pix_offset + 3]
    }
    }
}
```

### 動作デモ
ということで、このロジックを組み込んだサイトをHerokuにあげておきました。
動作を確認してみてください。
Zoomなどのvirtual背景より少しレスポンスが遅い感じもしますが、Webブラウザ上で動かしている割には十分実用的なものになっていると思います。
なお、BodyPix自体は、精度と速度のトレードオフをチューニングできるようになっています。
ユースケースに適したパラメータを見つけてみてください。

https://virtual-background-bodypix.herokuapp.com/index.html




### Amazon Chime Meetingのテストベッド
また、ビデオ会議を使った新機能のテストベッドとして会議室システムを作成しています。
下記のリポジトリに公開しています。
こちらは、バーチャル背景を利用できるビデオ会議に加えてテキストチャットやスタンプの送信などもできるようになっています。
また、AWSのLamdaとDynamoDBを用いることでサーバレスの環境を実現しています。興味があればアクセスしてみてください。


https://github.com/FLECT-DEV-TEAM/FLECT_Amazon_Chime_Meeting


### 最後に
今回は、Amazon Chime SDKを用いるビデオ会議システムでVirtual背景を実現する方法をご紹介しました。
コロナ禍の中で自宅からビデオ会議に参加するというケースも増えており、
ビデオ会議の相手に家の中など背景を見せたくない、というのはごく自然に生まれる要求だと思います。
Amazon Chimeでvirtual背景が提供されていない理由はわかりませんが、近いうちに提供されてもおかしくないと考えています。
その場合は、今回紹介したようにVirtual背景の機能を独自で実装する必要はなくなります。
ただ、今回示したとおり、Amazon Chime SDKではユーザ間でシェアするものはMediaStreamであると抽象化することにより高い拡張性を実現しています。
どのような映像を共有するかはアイデア次第ですので、いろいろ考えてみると面白いかもしれません。

ちなみに、以前ご紹介した、ZoomやMS Temasでカメラ画像をアニメ化する方法、も仮想ビデオデバイスを使わずに実現できると思います（メモリが足りないかも）。


https://cloud.flect.co.jp/entry/2020/04/02/114756

次回は、またAmazon Chimeで遊ぶか、以前紹介したマルチバーコードリーダの技術的な内容をご紹介するかをしようと思ってます。
では。