機械学習モデルBodyPixを使ってAmazon Chime SDKのビデオ会議の背景をぼかしてみた(後編)

2020.11.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX事業本部の若槻です。

前編の記事では、Amazon Chime SDKのビデオ会議の背景ぼかしをBodyPixによって行えることを簡単に確認しました。

今回の後編では、もう少し実用的な実装として、BodyPixによる「背景ぼかし機能」をデモアプリに追加してみました。

アウトプット

AWS公式が提供しているAmazon Chime SDKのデモアプリのローカルデモに「背景ぼかし機能」を追加しました。

映像入力メニューのドロップダウンリストに追加された[Bokeh BackGround]を選択すると、カメラ映像の背景をぼかすことができるようにしました。 image

背景ぼかし機能を使用している様子です。通常のカメラ映像の入力では人物の背景が鮮明に見えていますが、背景ぼかし機能を使うことにより背景のみぼかせています。 bodypix-amazon-chime-demo

ソースコードはGitHubにアップロードしてあります。AWSの元のRepositoryをフォークして、今回の変更のブランチを切っています。

背景ぼかし機能(デモアプリ)の使い方

次のコマンドを実行し、GitHubからソースコードをcloneして今回の変更のブランチをcheckoutします。

% git clone https://github.com/cm-rwakatsuki/amazon-chime-sdk-js.git
% git fetch origin implementing-video-background-bluring-with-bodypix
% git checkout implementing-video-background-bluring-with-bodypix

AWSの認証情報の設定がまだの場合は設定してください。(今回使用する/demos/browserのデモアプリはローカルで動作するため、バックエンドの構築は必要ありませんが、会議利用時にアプリが会議の作成、削除、出席者の作成をするためにAmazon Chime APIにアクセスするため、その際にAWSの認証情報が必要となります。)

次のコマンドを実行し、demos/browser配下のデモアプリをローカルで起動します。合わせて依存関係のインストールも行われます。

% cd demos/browser
% npm run start

アプリの起動に成功したら、ブラウザでhttp://127.0.0.1:8080/にアクセスします。

会議参加画面でタイトルが開きます。参加者名を指定し、[Continue]をクリックします。 image

デバイス準備画面が開きます。[Join]をクリックして会議に参加します。 image

会議画面が開きます。カメラボタンをクリックして映像入力を開始し、ドロップダウンリストから[Bokeh BackGround]を選択すると、カメラ映像の背景をぼかすことができます。 image

実装の変更の解説

今回の機能を実現するために元のソースコードに対して行った変更について解説します。変更内容自体は下記のComparingページからも確認できます。

BodyPixの利用に必要なパッケージの導入

ブラウザでBodyPixを利用する場合は、下記の4つのパッケージのインストールが必要となります。@tensorflow-models/body-pixがBodyPix本体、他の3つはJavaScriptでBodyPix(TensorFlow)を使用する場合に必要となります。

demos/browser/package.json

{
  "dependencies": {
    "@tensorflow-models/body-pix": "^2.0.5",
    "@tensorflow/tfjs": "^2.7.0",
    "@tensorflow/tfjs-converter": "^2.7.0",
    "@tensorflow/tfjs-core": "^2.7.0",
    //other packages
  },
  //other definitions
}

またデモアプリのJavaScriptコードとなるmeetingV2.tsに以下の記述を追加し、前述のパッケージがアプリ上で使用できるようにします。ただし@tensorflow/tfjsはimportは必要ですが処理内で明示的に指定しての使用はしません。よってunuseエラー抑制のためにconsole.logでログ出力のためだけに指定するようにしています。

demos/browser/app/meetingV2/meetingV2.ts

import * as bodyPix from '@tensorflow-models/body-pix';
import * as tf from '@tensorflow/tfjs';
console.log('Using TensorFlow backend: ', tf.getBackend());

SourceMapのロード失敗エラーの抑制

BodyPixを導入したデモアプリを起動したところコンソールで下記のようなエラーが継続的に出力されるようになりました。

image

DevTools failed to load SourceMap: Could not load content for webpack://app_meetingV2/node_modules/@tensorflow/tfjs/dist/version.js.map: HTTP error: status code 404, net::ERR_UNKNOWN_URL_SCHEME

次の記事によるとこのエラーはSourceMap Loaderを導入することにより抑制できるとのことです。

記事に従いdevDependenciesとしてsource-map-loaderを追加し、webpack.config.jsに記述を追加したらエラーを抑制できました。

demos/browser/package.json

{
  "devDependencies": {
    "source-map-loader": "^1.1.2",
    //other packages
  },
  //other definitions
}

demos/browser/webpack.config.js

module.exports = env => {
  return {
    module: {
      rules: [
        {
          test: /\.js$/,
          enforce: 'pre',
          use: ['source-map-loader'],
        },
        //other rules
      ],
    },
    //other definitions
  };
};

背景ぼかし処理

今回、背景ぼかし処理および処理映像のビデオタイルへの出力は次のようにして実現させています。

  1. カメラで撮影した映像をHTMLVideoElementに描画する
  2. HTMLVideoElementに描画された動画に対してBodyPixで背景ぼかし処理を行う
  3. ぼかし処理した映像をHTMLCanvasElementに描画する
  4. HTMLCanvasElementから取得したMediaStreamをchooseVideoInputDevice()(ビデオタイルに出力する映像・音声を指定するAmazon Chime SDKのメソッド)の入力とする

1、2、3の処理は次の3つのメソッドで行います。

  • setupCamera():メディアデバイス(カメラのみ)による入力処理を開始
  • segmentBody():HTMLVideoElementから取得した映像に対し、BodyPixによる人物領域の取得、人物の背景のボケ加工を行い、HTMLCanvasElementに描画する処理を連続的に行う
  • startDrawBokehEffect():HTMLVideoElementの作成、HTMLCanvasElementの作成とDOMへの追加(追加しない場合は処理が正常に動作しませんでした)、setupCamera()およびstartDrawBokehEffect()の開始を行う

demos/browser/app/meetingV2/meetingV2.ts

  async setupCamera(videoElement: any) {
    const stream = await navigator.mediaDevices.getUserMedia({
      video: {
        width: 1280,
        height: 720,
      },
      audio: false,
    });
    videoElement.srcObject = stream;
    return new Promise(resolve => {
      videoElement.onloadedmetadata = () => {
        videoElement.play();
        resolve();
      };
    });
  }

  segmentBody(input: HTMLVideoElement, output: HTMLCanvasElement, bodypixnet: bodyPix.BodyPix) {
    async function renderFrame() {
      const segmentation = await bodypixnet.segmentPerson(input);
      const backgroundBlurAmount = 7;
      const edgeBlurAmount = 3;
      const flipHorizontal = false;
      bodyPix.drawBokehEffect(
        output,
        input,
        segmentation,
        backgroundBlurAmount,
        edgeBlurAmount,
        flipHorizontal
      );
      if (document.getElementById('blurredCanvasElement')) {
        requestAnimationFrame(renderFrame);
      }
    }
    renderFrame();
  }

  async startDrawBokehEffect() {
    const input = document.createElement('video') as HTMLVideoElement;
    input.width = 1280;
    input.height = 720;
    input.muted = true;
    input.autoplay = true;
    input.setAttribute('id', 'inputVideoElement');
    if (input.srcObject) {
      input.srcObject = null;
    } else {
      const output = document.createElement('canvas') as HTMLCanvasElement;
      output.width = 1280;
      output.height = 720;
      output.setAttribute('id', 'blurredCanvasElement');
      output.setAttribute('style', 'display:none');
      document.body.appendChild(output);
      await this.setupCamera(input);
      const bodypixnet: bodyPix.BodyPix = await bodyPix.load();
      this.segmentBody(input, output, bodypixnet);
    }
  }
}

本部分の実装は下記ブログの内容をとても参考にさせて頂きました。

背景ぼかし処理とビデオタイルへの入力の開始トリガー

ドロップダウンリストのメニューを定義するpopulateVideoInputList()で、カメラ映像の背景ぼかしを開始するメニューとしてBokeh BackGroundを追加します。

demos/browser/app/meetingV2/meetingV2.ts

  async populateVideoInputList(): Promise<void> {
    const genericName = 'Camera';
    const additionalDevices = ['None', 'Blue', 'SMPTE Color Bars', 'Bokeh BackGround'];
    //other processes

videoInputSelectionToDevice()メソッドはドロップダウンリストで選択されたメニューに従い、画像、メディアデバイスIDまたはMediaStreamが返すメソッドです。Bokeh BackGroundが選択されると、startDrawBokehEffect()を実行してHTMLCanvasElementへの背景ぼかし映像の描画を開始し、HTMLCanvasElementからcaptureStream()によりMediaStreamを取得して戻り値とするようにしています。このメソッドの戻り値は呼び出し元でchooseVideoInputDevice()に渡されます。

demos/browser/app/meetingV2/meetingV2.ts

  private videoInputSelectionToDevice(value: string): Device {
    if (this.isRecorder() || this.isBroadcaster()) {
      return null;
    }
    if (value === 'Blue') {
      return DefaultDeviceController.synthesizeVideoDevice('blue');
    } else if (value === 'SMPTE Color Bars') {
      return DefaultDeviceController.synthesizeVideoDevice('smpte');
    } else if (value === 'None') {
      return null;
    } else if (value === 'Bokeh BackGround') {
      this.startDrawBokehEffect();
      interface CanvasElement extends HTMLCanvasElement {
        captureStream(frameRate?: number): MediaStream;
      }
      return (<CanvasElement>document.getElementById('blurredCanvasElement')).captureStream();
    }
    return value;
  }

中間要素の削除

映像出力が選択され直された際に中間要素(HTMLVideoElement、HTMLCanvasElement)を削除するようにしています。segmentBody()ではinputVideoElementが存在しない場合にぼかし処理・結果の描画を停止するようにしています。

demos/browser/app/meetingV2/meetingV2.ts

  removeIntermediateElement(): void {
    const blurredCanvasElement = document.getElementById('blurredCanvasElement');
    if (blurredCanvasElement) {
      blurredCanvasElement.remove();
      console.log('blurredCanvasElement removed');
    }
    const inputVideoElement = document.getElementById('inputVideoElement');
    if (inputVideoElement) {
      inputVideoElement.remove();
      console.log('inputVideoElement removed');
    }
  }

demos/browser/app/meetingV2/meetingV2.ts

    const buttonVideo = document.getElementById('button-camera');
    buttonVideo.addEventListener('click', _e => {
      this.removeIntermediateElement();
      //other processes

demos/browser/app/meetingV2/meetingV2.ts

  async openVideoInputFromSelection(selection: string | null, showPreview: boolean): Promise<void> {
    this.removeIntermediateElement();
    //other processes

おわりに

BodyPixによる「背景ぼかし機能」をAmazon Chime SDKのデモアプリに追加してみました

今回の実装を行うにあたりAmazon Chime SDkだけでなくHTML5でのメディアの取扱いについての理解も深まりました。

また背景ぼかし機能に関しては今回次の実装ができなかったので、次回以降やってみたいと思います。

  • デバイス準備画面でのプレビュー映像での背景ぼかしの利用
  • 映像出力の開始を背景ぼかしを利用した状態で行う

参考

以上