Greenscreen showing three participants on the screen. A participant with a blue shirt on the left, a participant with a black t-shirt at the top right, and a purple robot at the bottom right.

Vonage Video APIを使用してバーチャル・グリーン・スクリーンを作成する方法

最終更新日 August 19, 2025

所要時間:2 分

はじめに

この記事では、Vonage Video API を使用して、背景置換と HTML5 canvas を使用して共有コンテンツにプレゼンターをオーバーレイし、仮想グリーンスクリーンを作成する方法について説明します。

コードは GitHub.

Speaker shows in front of the background without the greenscale background.Presentation Example

バーチャル・グリーン・スクリーンを使ってデスクトップの不動産を節約する

オンラインでプレゼンテーションを行う場合、プレゼンターと参加者は、利用可能なデスクトップスペースをプレゼンテーションやその他のメディアで共有する必要があります。そのため、多くの参加者にとっては、コンテンツが小さすぎて読めないことがよくあります。上の例では、Vonage Video APIの機能を使ってバーチャル・グリーン・スクリーンを作成し、プレゼンターをプレゼンテーションの上に表示しています。これにより、プレゼンテーションそのものが主役となり、すべての人がそれを完全に見ることができます。

最初のハードル

このようなことを実現するための主な問題は、Webリアルタイム通信(WebRTC)とVideoコーデックが透過性をサポートしない傾向があることだ。インターネット上で透過ビデオを送信することはうまくいかない。しかし、Vonage Video APIはいくつかの機能を提供しており、その一つがBackground Replacementです。詳しくは フィルタとエフェクトをご覧ください。

このチュートリアルで行うのは、単色の背景画像を使った背景置換機能を使って、バーチャルなグリーンスクリーンを作成することです。これにより、Video Transformersに必要な処理をさせて、きれいな背景の検出と置換を行うことができます。ここでは、RGB(61,180,60)の単色の小さな画像(16x16)を使用します。 この画像は、このチュートリアルの GitHub リポジトリに含まれています。サンプルを実行するのに必要なコードもすべて含まれています。

A green squaregreenscale background以下のプロセスを使用すると、背景を完全に削除して、レンダリング時に既存のメディア、ウェブページ、または他のビデオの上にビデオを表示することができます。

A workflow that read "How it works" there's a ballon that starts from an image of a video, followed by background replacement, followed by a picture of the speaker showing a screen scale background followed by Video API Session, followed to the same speaker picture with a greenscale background, followed by green screen processor subscriber followed by the picture of the speaker image added to the front layer without a green background.Application Workflow

前提条件

始める前に GitHubリポジトリをチェックして、コードをダウンロードしてサンプルを実行できるようにしてください。

必要なもの

  • JavaScriptとHTML5 Canvasの使用経験。

  • 過去に Video APIの経験があることが望ましいですが、必須ではありません。

  • このプロジェクトには基本的なビデオアプリケーションが含まれていますが、このチュートリアルの範囲外です。その他のサンプルは ビデオ・ウェブ・サンプル・リポジトリ.

  • サンプルを実行するには、機能するカメラとマイクが必要です。

  • Video APIキー、セッションID、トークン。これらはダッシュボードまたは Video API プレイグラウンドで作成できます。オンライン API アカウントからアクセスできます。

アプリケーション概要

サンプルアプリケーションはいくつかのコンポーネントで構成されています。ランディングページ(app.html) には、アプリケーションで使用するユーザとロールを特定するためのフォームがあります。メイン アプリケーション ページ (liveroom.html) は Video JavaScript SDK をロードし、JavaScript コードを実行します。このサンプルでは、コードは ES6 モジュールとして読み込まれます。

部屋とセッションのセットアップ

ページが liveroom.htmlページがロードされるとき、ユーザーの名前、部屋名、およびロールはURLパラメータとしてURLに含まれます。これらは収集され Liveroomクラスのコンストラクタに渡されます。

let urlRoomName = new URLSearchParams(window.location.search).get('roomName')

let urlUserName = new URLSearchParams(window.location.search).get('userName')

let urlUserRole = new URLSearchParams(window.location.search).get('userRole')

パラメータを設定し、それらが有効であることを確認したら、部屋を作成することができます。

const liveroom = new Liveroom(urlRoomName, urlUserName, urlUserRole)

この Liveroomオブジェクトは最初にサーバーからセッションのクレデンシャルを取得するために呼び出しを行います。物事をシンプルにするために、現在のフォルダから有効なセッション認証情報を含む config.jsonファイルを取得するように設定しました。サンプルを実行する前に、このファイルにセッションID、APIキー、トークンを入力する必要があります。

// config.json

{

   "apiKey": "your api key",

   "sessionId": "your session id",

   "token": "your token"

}

Liveroomオブジェクトもインスタンス化する。 Displayオブジェクトをインスタンス化する。

this.display = new Display()

この Display クラスは表示領域の幅と高さを管理し、アプリケーション・ウィンドウのサイズが変更されたときにこれらの属性が更新されるようにリスナーを追加します。このクラスのコードは display.jsファイルにあります。.

Video API セッションの接続

アプリケーションが認証情報を得ると Liveroomクラスは、メディアを管理し、vonage セッションに接続する Video オブジェクトを作成します。

this.getVideoCredentials(this.roomName)

       .then(()=>{

           this.video = new Video(this.sessionCredentials, {})

           return this.video.connectSession()

       })

       .then(()=>{

           console.log(`User Role: ${this.userRole}`)

           if(this.userRole == 'composer'){

               this.display.enableComposerMode()

               this.composer = true

           }

           if(this.userRole != 'viewer'){

               this.video.publishCamera(this.userRole)

           }

           if(this.userRole == 'presenter'){

               this.video.publishScreen()

           }

           this.updateDisplay(this.video.participants, this.video.mainstage)

       })

       .catch((error)=>{

           console.log(error)

       })

必要な情報とオブジェクトがすべて揃うまで何も起動しないように、プロミスを使用している。まず、認証情報を収集し、Videoクラスのコンストラクタに渡します。 次に connectSession()関数を呼び出し、Video JavaScript SDK をセッション ID に接続します。これが完了すると、渡されたユーザー・ロールに基づいて適切なメンバ関数を起動します。

メディアの送受信

前にも述べたように、WebRTC経由で透明度を送信することはできないので、背景をグリーン・スクリーン効果に置き換える必要がある。これは publishCamera()関数で処理します。この関数は新しい CameraPublisher オブジェクトを作成し、以下の設定を使ってパブリッシャーを作成します:

 let publisherOptions = {

           showControls: false,

           videoFilter: {

               type: "backgroundReplacement",

               backgroundImgUrl: "/images/greenscreen.png" // r:61 g:180 b:60    #3db43c

           }

       }

これはSDKに背景をgreenscreen.png画像に置き換えるよう指示します。この画像は拡大縮小され、1色しかないので、ファイルは16x16ピクセルしかありません。

パブリッシャーを作成する際に、Video を追加する HTML DOM オブジェクトを渡します。この場合、DIV オブジェクトは CameraPublisherクラスのメンバーである DIV オブジェクトがあります。この DIV オブジェクトは CSS で隠されます。 display: none;を使用して非表示にします。

また、Videoの出力を透過して受け取るために、HTML5 Canvasオブジェクトも作成します。これもオブジェクトのメンバで、名前は outputCanvas.

  this.publisher = OT.initPublisher(this.publisherDiv, publisherOptions, (event)=>{

           this.videoElement = this.publisherDiv.querySelector("video")

           this.videoElement.onloadeddata = (event)=>{

               console.log("Video Loaded Data")

               this.renderer = new VideoRenderer(this.videoElement, this.outputCanvas)

               this.renderer.processFrames()

               this.session.publish(this.publisher)

           }

       })

このコードでは、関数 OT.initPublisher()関数にも関数を渡しています。この関数が実行されると、Video 要素が DIV に追加されたことになります。つまり querySelector()関数を使用してそれを見つけ、別のコールバック関数を追加します。これは、Video メディアの準備ができたときに実行され、インスタンスを作成します。 VideoRendererインスタンスを作成します。

Video 要素と書き込む canvas を取得したら、このコードで新しい VideoRenderer オブジェクトを作成します。

メディアを受け取るには、同じプロセスを CameraSubscriber()クラスの別のインスタンスを作成します。 VideoRendererクラスのインスタンスを作成します。

ビデオのレンダリング

VideoRendererクラスは、この機能を実現するための最後のピースだ。このクラスは、入力 <video>要素と出力 <canvas>HTML5 要素を受け取ります。有効にすると、VIDEO要素からVideoフレームをコピーします。緑色の背景とともに内部の CANVAS (") に貼り付けます。builderCanvas")に貼り付け、各 Video フレームのピクセル データにアクセスできるようにします。

それでは、VideoRendererが何をするのか、どのように動作するのかを詳しく見ていこう。

constructor(sourcevideo, outputcanvas){

       let randomIcon = (Math.floor(Math.random()*14)+1) + '.png'

       this.imageIcon = new Image()

       this.imageIcon.src = /images/${randomIcon}

       console.log('Random icon for user: ', this.imageIcon.src)

      

       this.targetFPS = 30

       this.videoElement = sourcevideo

       this.width = sourcevideo.videoWidth

       this.height = sourcevideo.videoHeight

       this.outputCanvas = outputcanvas

       this.builderCanvas = document.createElement("canvas")

       this.enabled = false

   }

クラスのコンストラクタは、ビルダー・キャンバスをセットアップし、ビデオが無効になっている場合にビデオフィードをランダムな画像に置き換える関数をいくつか含んでいる。

次に、Videoレンダラーを有効または無効にする2つの短い関数があります。これは、表示されていない参加者やミュートされている参加者のリソースを節約するのに役立ちます。

   enable(){

       if(!this.enabled){

          this.enabled = true

           this.processFrames()

       }      

   }

   disable(){

       this.enabled = false

   }

次に processFrames()関数があります。これはレンダラーが有効になったときに初めて呼び出され、その後 requestAnimationFrame()を使用して再度呼び出されます。このようにアニメーションフレームを要求することで、フレームが表示されていないときのリソースの使用量を減らすことができます。

processFrames 関数は、レンダラーが有効になっていない場合は終了します。これにより、レンダラーが完全に無効になります。

   processFrames() {

      if(!this.enabled) return

}

次に、すべてのキャンバスが同じ寸法になるように、Video 要素の幅と高さをキャプチャします。また、Video 要素へのポインタとして sourceImage 変数を作成します。Video が有効でない場合、sourceImage にはコンストラクタで選択された imageIcon が設定されます。

this.width = this.videoElement.videoWidth

       this.height = this.videoElement.videoHeight

       let sourceImage = this.videoElement

      

       if(!this.videoElement.srcObject.getVideoTracks()[0].enabled){

           sourceImage = this.imageIcon

           this.width = sourceImage.width

           this.height = sourceImage.height

       }

ビルダーのキャンバス幅が設定され、キャンバスコンテキストが作成される。このコンテキスト builderCtxによって、canvas に対する読み書きが可能になる。まずcanvasをクリアし、sourceImageをbuilderCanvasに描画する。

       this.builderCanvas.width = this.width

       this.builderCanvas.height = this.height

       let builderCtx = this.builderCanvas.getContext("2d")

       builderCtx.clearRect(0,0,this.width, this.height)

       builderCtx.drawImage(sourceImage, 0, 0, this.width, this.height)

builderCanvasそして contextのセットアップが完了したら outputCanvas.幅と高さがソース・メディアと一致していることを確認します。

注意:これはCSSの表示幅と高さを変更するものではありません。内部キャンバスの幅と高さを変更するだけです。DOM は外部ノードに合わせて内部キャンバスを拡大縮小します。

       this.outputCanvas.width = this.width

       this.outputCanvas.height = this.height

       let outputCtx = this.outputCanvas.getContext("2d")

これでビルダーとアウトプットのコンテキストができた。次に、ビルダー・コンテキストから ImageDataオブジェクトを作成する。これにより、canvas 内のピクセルレベルのデータに ImageData.data配列を通して、キャンバス内のピクセルレベルのデータにアクセスできるようになります。これは平坦な1次元配列で、各ピクセルの赤、緑、青、アルファ属性が順次表示されます。

var imgdata = builderCtx.getImageData(0, 0, this.width, this.height);

var pix = imgdata.data;

この関数は、配列内のすべてのピクセルを繰り返し処理し、それぞれのピクセルが背景の緑と一致するかどうかを判断する必要がある。これには adjustPixel()関数を使います。

 for (var i = 0, n = pix.length; i < n; i += 4) {

           let r = pix[i]

           let g = pix[i+1]

           let b = pix[i+2]

           let a = pix[i+3]

           let newColor = this.adjustPixel(r,g,b,a)

           pix[i] = newColor.r

           pix[i+1] = newColor.g

           pix[i+2] = newColor.b

           pix[i+3] = newColor.a      

       }

のすべてのピクセルを更新したら imgdataのピクセルがすべて更新されたら、ビルダー・コンテキストをクリアして imgdataを canvas に戻します。そして、この非表示のキャンバスから可視の outputCanvasコンテキストに描画できる。この関数は requestAnimationFrame()では、この関数は1秒間に30回から60回実行されます。

       builderCtx.clearRect(0,0,this.width,this.height)

       builderCtx.putImageData(imgdata, 0, 0)

       outputCtx.drawImage(this.builderCanvas, 0, 0, this.width, this.height)

       this.animationFrameId = window.requestAnimationFrame(()=>{this.processFrames()})

   }

ピクセルの不透明度を設定する

の機能 adjustPixel()クラスの VideoRendererクラスの関数は、独立した赤、緑、青、アルファ値を持つ色を受け取る。そして、r、g、b、aの4つのメンバーを持つオブジェクトを返す。 outputオブジェクトを生成します。

adjustPixel(r,g,b,a){

  let c = {r: r, g: g, b: b, a: a}
}

その後、この関数は赤と青の値の平均を取り、rbとして保存する。

       let rb = (r + b) / 2

次のコード行は、ピクセルを透明にするかどうかを決定する。まず withinRange()関数を使用して、赤と青の差が40より小さいかどうかを調べます。もし赤と青の値が異なっていれば、その色は緑にはならない。

引数の2番目の部分は、緑の値が赤と青の平均より少なくとも20多いかどうかをチェックする。これは、16x16のオリジナル画像に最もマッチする、多くの緑の色合いと色相をカバーする。

if((this.withinRange(r, b, 40) && g - rb > 20)){

           c.a = 0

       }

       return c

   }

   withinRange(val1, val2, range){

       let diff = val1 - val2

       if(diff < 0) diff = diff * -1

       if(diff < range){

           return true

       } else {

           return  false

       }

   }

マルチスピーカーとフェードアウト

下のスクリーンショットでは、現在発言していない人をフェードアウトさせ、すべての人を左下に整列させることで、複数の発言者に対応している例を見ることができます。この時点で、通話には3人の話者がいますが、一度に発言できるのは1人だけです。

The image shows multiple speakers layered in front of the screen presentation without a greenscale backgroundMultiple Speaker

非アクティブなスピーカーをフェードアウトしたり削除したりすることで、体験の質を損なうことなく、スピーカーの数や共有スクリーンやプレゼンテーションの解像度を増やすことができます。

サンプルを実行する

提供されたconfig.jsonファイルにAPIキー、セッションID、トークンを入力する必要があります。Nodeを使用している場合は、クローンしたリポジトリのルートから以下のコマンドを実行できます:

npm install 

npm run serve

次に、ブラウザを開き、次のサイトに移動します。 http://127.0.0.1:3000.あなたが話すと、マイクがあなたの声を拾い、ビデオフィードが有効になるはずです。

結論

このチュートリアルは以上です。スライドショーを見せているときも、スプレッドシートを編集しているときも、Videoを見ているときも、カメラフィードはあなたが話しているときだけ表示され、画面上に余計なスペースを取ることはありません。興味を持っていただけたでしょうか?その他の実装や機能強化には、以下のようなものが考えられます:

  • パブリッシャー向けビデオ背景

  • ウォッチパーティー・アプリケーション

この投稿の この記事の完全なコードはGitHubにあります。.

ご質問がある場合、またはあなたが作っているものを共有したい場合は、こちらをクリックしてください。

最新の開発者向けニュース、ヒント、イベント情報をお届けします。

シェア:

https://a.storyblok.com/f/270183/689x689/2b79e61b9f/richard-sabbarton.png
Richard SabbartonシニアビデオAPIサポートエンジニア

リチャードはVonageのシニアVideo APIサポートエンジニアで、製造業、電気通信、ネットワークにおける製品、サポート、エンジニアリングの職務で25年以上の経験があります。