
シェア:
リチャードはVonageのシニアVideo APIサポートエンジニアで、製造業、電気通信、ネットワークにおける製品、サポート、エンジニアリングの職務で25年以上の経験があります。
Vonage Video APIを使用してバーチャル・グリーン・スクリーンを作成する方法
所要時間:2 分
はじめに
この記事では、Vonage Video API を使用して、背景置換と HTML5 canvas を使用して共有コンテンツにプレゼンターをオーバーレイし、仮想グリーンスクリーンを作成する方法について説明します。
コードは GitHub.
Presentation Example
バーチャル・グリーン・スクリーンを使ってデスクトップの不動産を節約する
オンラインでプレゼンテーションを行う場合、プレゼンターと参加者は、利用可能なデスクトップスペースをプレゼンテーションやその他のメディアで共有する必要があります。そのため、多くの参加者にとっては、コンテンツが小さすぎて読めないことがよくあります。上の例では、Vonage Video APIの機能を使ってバーチャル・グリーン・スクリーンを作成し、プレゼンターをプレゼンテーションの上に表示しています。これにより、プレゼンテーションそのものが主役となり、すべての人がそれを完全に見ることができます。
最初のハードル
このようなことを実現するための主な問題は、Webリアルタイム通信(WebRTC)とVideoコーデックが透過性をサポートしない傾向があることだ。インターネット上で透過ビデオを送信することはうまくいかない。しかし、Vonage Video APIはいくつかの機能を提供しており、その一つがBackground Replacementです。詳しくは フィルタとエフェクトをご覧ください。
このチュートリアルで行うのは、単色の背景画像を使った背景置換機能を使って、バーチャルなグリーンスクリーンを作成することです。これにより、Video Transformersに必要な処理をさせて、きれいな背景の検出と置換を行うことができます。ここでは、RGB(61,180,60)の単色の小さな画像(16x16)を使用します。 この画像は、このチュートリアルの GitHub リポジトリに含まれています。サンプルを実行するのに必要なコードもすべて含まれています。
greenscale 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人だけです。
Multiple Speaker
非アクティブなスピーカーをフェードアウトしたり削除したりすることで、体験の質を損なうことなく、スピーカーの数や共有スクリーンやプレゼンテーションの解像度を増やすことができます。
サンプルを実行する
提供されたconfig.jsonファイルにAPIキー、セッションID、トークンを入力する必要があります。Nodeを使用している場合は、クローンしたリポジトリのルートから以下のコマンドを実行できます:
npm install
npm run serve次に、ブラウザを開き、次のサイトに移動します。 http://127.0.0.1:3000.あなたが話すと、マイクがあなたの声を拾い、ビデオフィードが有効になるはずです。
結論
このチュートリアルは以上です。スライドショーを見せているときも、スプレッドシートを編集しているときも、Videoを見ているときも、カメラフィードはあなたが話しているときだけ表示され、画面上に余計なスペースを取ることはありません。興味を持っていただけたでしょうか?その他の実装や機能強化には、以下のようなものが考えられます:
パブリッシャー向けビデオ背景
ウォッチパーティー・アプリケーション
この投稿の この記事の完全なコードはGitHubにあります。.
ご質問がある場合、またはあなたが作っているものを共有したい場合は、こちらをクリックしてください。
会話に参加する VonageコミュニティSlack
登録する 開発者ニュースレター
フォローする X(旧ツイッター)最新情報
チュートリアルを見る YouTubeチャンネル
LinkedInの LinkedIn の Vonage デベロッパーページ
最新の開発者向けニュース、ヒント、イベント情報をお届けします。