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.

Erstellen eines virtuellen Greenscreens mit der Vonage Video API

Zuletzt aktualisiert am August 19, 2025

Lesedauer: 8 Minuten

Einführung

In diesem Artikel wird beschrieben, wie die Vonage Video-API verwendet wird, um Moderatoren mithilfe von Hintergrundersetzung und HTML5-Canvas auf freigegebenen Inhalten zu überlagern und so einen virtuellen Greenscreen zu erstellen.

Sie finden den Code auf GitHub.

Verwendung virtueller grüner Bildschirme zur Rettung von Desktop-Immobilien

Bei Online-Präsentationen müssen sich Vortragende und Teilnehmer den verfügbaren Platz auf dem Desktop mit ihren Präsentationen und anderen Medien teilen. Dies kann dazu führen, dass der Inhalt für viele Teilnehmer zu klein zum Lesen ist. Im obigen Beispiel haben wir die Funktionen der Vonage Video API genutzt, um einen virtuellen Greenscreen zu erstellen, so dass der Präsentator über der Präsentation angezeigt werden kann. Auf diese Weise steht die Präsentation selbst im Mittelpunkt, so dass alle Teilnehmer sie in vollem Umfang sehen können.

Einige anfängliche Hürden

Das Hauptproblem dabei ist, dass Web Real-Time Communication (WebRTC) und Video Codecs in der Regel keine Transparenz unterstützen. Transparente Videos über das Internet zu senden, wird nicht funktionieren. Die Vonage Video-API bietet jedoch einige Funktionen, die helfen können, und eine davon ist die Hintergrundersetzung. Lesen Sie mehr über Filter und Effekte in unserer Dokumentation.

In diesem Tutorial werden wir die Funktion "Hintergrundersetzung" mit einem einfarbigen Hintergrundbild verwenden, um einen virtuellen Greenscreen zu erstellen. So können wir unsere Videotransformatoren tun lassen, was sie tun müssen, um einen sauberen Hintergrund zu erkennen und zu ersetzen. Wir werden ein kleines Bild (16x16) mit der Volltonfarbe RGB(61,180,60) verwenden. Dieses Bild ist im GitHub-Repository für dieses Tutorial enthalten, zusammen mit dem gesamten Code, den Sie benötigen, um das Beispiel auszuführen.

Mit dem unten beschriebenen Verfahren kann der Hintergrund vollständig entfernt werden, so dass das Video beim Rendern über vorhandenen Medien, Webseiten oder anderen Videos angezeigt werden kann.

Voraussetzungen

Bevor wir loslegen, schauen Sie sich bitte das GitHub-Repository damit Sie den Code herunterladen und die Beispiele ausführen können.

Sie benötigen:

  • Einige Erfahrung mit JavaScript und HTML5 Canvas.

  • Erste Erfahrungen mit der Vonage Video API ist wünschenswert, aber nicht unbedingt erforderlich.

  • Eine einfache Videoanwendung ist in diesem Projekt enthalten, fällt aber nicht in den Aufgabenbereich dieses Tutorials. Weitere Beispiele finden Sie in unserem Video Web Samples Repository.

  • Sie benötigen eine funktionierende Kamera und ein Mikrofon, um die Probe durchzuführen.

  • Ein Vonage Video-API-Schlüssel, eine Sitzungs-ID und ein Token. Sie können diese im Dashboard oder über den Video API Playground erstellen. Sie können über Ihr Online-API-Konto darauf zugreifen.

Überblick über die Anwendung

Die Beispielanwendung besteht aus mehreren Komponenten. Eine Zielseite (app.html) mit einem Formular, das die Identifizierung von Benutzern und Rollen für die Anwendung erleichtert. Die Hauptseite der Anwendung (liveroom.html) lädt das Video JavaScript SDK und führt unseren JavaScript-Code aus. In diesem Beispiel wird der Code in Form von ES6-Modulen geladen.

Einrichten des Raums und der Sitzung

Wenn die liveroom.html Seite geladen wird, werden der Name des Benutzers, der Raumname und die Rolle als URL-Parameter in die URL aufgenommen. Diese werden gesammelt und an den Konstruktor der Klasse Liveroom übergeben, um den Raum zu erstellen.

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')

Sobald wir die Parameter haben und überprüft haben, dass sie gültig und ausgefüllt sind, können wir den Raum erstellen.

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

Das Liveroom Objekt ruft zunächst die Sitzungsdaten von einem Server ab. Der Einfachheit halber haben wir dies so eingestellt, dass eine config.json-Datei aus dem aktuellen Ordner abgerufen wird, die gültige Sitzungsdaten enthält. Sie müssen Ihre Sitzungs-ID, Ihren API-Schlüssel und Ihr Token in diese Datei eingeben, bevor Sie das Beispiel ausführen.

// config.json

{

   "apiKey": "your api key",

   "sessionId": "your session id",

   "token": "your token"

}

Der Liveroom instanziiert auch ein Display-Objekt.

this.display = new Display()

Die Klasse Display verwaltet die Breite und Höhe des Anzeigebereichs und fügt einen Listener hinzu, um sicherzustellen, dass diese Attribute aktualisiert werden, wenn die Größe des Anwendungsfensters geändert wird. Der Code für diese Klasse befindet sich in der Datei display.js-Datei.

Verbinden Sie die Vonage Video-API-Sitzung

Sobald die Anwendung die Anmeldedaten hat, erstellt die Liveroom Klasse ein Video-Objekt, das die Medien verwaltet und eine Verbindung mit der Vonage-Sitzung herstellt.

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)

       })

Wir verwenden Versprechen, um sicherzustellen, dass wir nichts starten, bevor nicht alle erforderlichen Informationen und Objekte vorhanden sind. Zunächst werden die Anmeldeinformationen gesammelt und an den Konstruktor der Klasse Video übergeben. Dieser ruft dann die connectSession() Funktion auf, die das Video JavaScript SDK mit der Sitzungs-ID verbindet. Sobald dies geschehen ist, werden die entsprechenden Mitgliedsfunktionen auf der Grundlage der übergebenen Benutzerrolle gestartet.

Senden und Empfangen von Medien

Wie wir bereits erwähnt haben, können wir keine Transparenz über WebRTC senden, also müssen wir den Hintergrund durch den Green-Screen-Effekt ersetzen. Dies wird von der publishCamera() Funktion. Diese Funktion erstellt ein neues CameraPublisher-Objekt, das die folgenden Einstellungen verwendet, um den Publisher zu erstellen:

 let publisherOptions = {

           showControls: false,

           videoFilter: {

               type: "backgroundReplacement",

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

           }

       }

Dies weist das SDK an, den Hintergrund durch unser Bild greenscreen.png zu ersetzen. Da es gestreckt und skaliert wird und nur eine Farbe hat, ist die Datei nur 16x16 Pixel groß.

Beim Erstellen des Publishers übergeben wir das HTML-DOM-Objekt, an das das Video angehängt werden soll. In diesem Fall handelt es sich um ein DIV-Objekt, das Mitglied der Klasse CameraPublisher ist. Dieses DIV-Objekt wird mit CSS unsichtbar gemacht display: none; ausgeblendet, da es das unbearbeitete Video enthalten wird.

Wir erstellen auch ein HTML5 Canvas-Objekt, um die Ausgabe des Videos mit Transparenz zu erhalten. Dieses ist ebenfalls ein Mitglied des Objekts und heißt 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)

           }

       })

In diesem Code übergeben wir auch eine Funktion an den Callback für die OT.initPublisher() Funktion. Wenn dieser ausgeführt wird, wissen wir, dass das Videoelement zu unserem DIV hinzugefügt wurde. Das heißt, wir können die querySelector() Funktion verwenden, um es zu finden und eine weitere Callback-Funktion hinzuzufügen. Diese wird ausgeführt, wenn das Videomedium bereit ist, und erstellt die VideoRenderer-Instanz.

Sobald wir das Videoelement und eine Leinwand haben, in die wir schreiben können, erstellt dieser Code ein neues VideoRenderer-Objekt.

Um Medien zu empfangen, folgen wir dem gleichen Prozess in der CameraSubscriber() Klasse und erstellen eine weitere Instanz der Klasse VideoRenderer, um das empfangene Video zu rendern.

Rendering des Videos

Die Klasse VideoRenderer ist der letzte Baustein, damit dies funktioniert. Sie nimmt ein Eingabe <video> Element und ein Ausgabe <canvas> HTML5-Element. Wenn sie aktiviert ist, kopiert sie das Videobild aus dem VIDEO-Element. Sie fügt es mit einem grünen Hintergrund in eine interne CANVAS ("builderCanvas") ein, so dass wir auf die Pixeldaten für jedes Videobild zugreifen können.

Schauen wir uns genauer an, was der VideoRenderer macht und wie er funktioniert.

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

   }

Der Konstruktor für die Klasse richtet unsere Builder-Leinwand ein und enthält einige Funktionen, um den Video-Feed durch ein zufälliges Bild zu ersetzen, wenn das Video deaktiviert ist.

Als Nächstes haben wir zwei kurze Funktionen, die den Video-Renderer einfach aktivieren und deaktivieren. Dies hilft, Ressourcen für Teilnehmer zu sparen, die nicht angezeigt werden oder stummgeschaltet sind.

   enable(){

       if(!this.enabled){

          this.enabled = true

           this.processFrames()

       }      

   }

   disable(){

       this.enabled = false

   }

Als nächstes haben wir die processFrames() Funktion. Diese wird zum ersten Mal aufgerufen, wenn der Renderer aktiviert wird, und verwendet dann requestAnimationFrame() um sich selbst erneut aufzurufen. Durch die Anforderung eines Animationsrahmens auf diese Weise wird die Ressourcennutzung reduziert, wenn der Rahmen nicht angezeigt wird.

Die Funktion processFrames wird beendet, wenn der Renderer nicht aktiviert ist. Dadurch wird der Renderer vollständig deaktiviert.

   processFrames() {

      if(!this.enabled) return

}

Als Nächstes werden die Breite und Höhe des Videoelements erfasst, um sicherzustellen, dass alle unsere Leinwände die gleichen Abmessungen haben. Es wird auch eine Variable sourceImage als Zeiger auf das Videoelement erstellt. Wenn das Video nicht aktiviert ist, wird das sourceImage auf das vom Konstruktor ausgewählte imageIcon gesetzt.

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

       }

Die Leinwandbreite für den Builder wird festgelegt, und der Leinwandkontext wird erstellt. Dieser Kontext builderCtx ermöglicht uns das Lesen und Schreiben auf der Leinwand. Zuerst löschen wir ihn, und dann zeichnen wir unser sourceImage auf die 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)

Mit den builderCanvas und context geht es dann weiter mit der outputCanvas. Wir stellen sicher, dass die Breite und Höhe mit unserem Quellmedium übereinstimmen.

HINWEIS: Dies ändert nicht die Anzeigebreite und -höhe in CSS. Es werden nur die Breite und Höhe des internen Canvas geändert. Das DOM skaliert den internen Canvas so, dass er mit dem externen Knoten übereinstimmt.

       this.outputCanvas.width = this.width

       this.outputCanvas.height = this.height

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

Wir haben jetzt unseren Ersteller- und Ausgabekontext eingerichtet. Um fortzufahren, erstellen wir ein ImageData Objekt aus dem Builder-Kontext. Dies ermöglicht den Zugriff auf die Daten auf Pixelebene innerhalb der Leinwand über das Array ImageData.data. Dabei handelt es sich um ein flaches, eindimensionales Array, das die Rot-, Grün-, Blau- und Alpha-Attribute für jedes Pixel enthält und nacheinander dargestellt wird.

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

var pix = imgdata.data;

Unsere Funktion muss all diese Pixel im Array durchlaufen und jedes einzelne bewerten, um festzustellen, ob es mit dem Grün des Hintergrunds übereinstimmt. Dazu wird die Funktion adjustPixel() Funktion, die wir in Kürze behandeln werden.

 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      

       }

Sobald alle Pixel in unserem imgdata aktualisiert worden sind, löschen wir den Builder-Kontext und schieben die imgdata zurück in den Canvas. Wir können dann von dieser versteckten Leinwand in unseren sichtbaren outputCanvas Kontext zeichnen. Mit requestAnimationFrame()wird diese Funktion zwischen 30 und 60 Mal pro Sekunde ausgeführt.

       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()})

   }

Pixel-Deckkraft einstellen

Die Funktion adjustPixel() Funktion der Klasse VideoRenderer akzeptiert eine Farbe mit unabhängigen Rot-, Grün-, Blau- und Alpha-Werten. Sie gibt dann ein Objekt mit vier Mitgliedern zurück, r, g, b und a. Die erste Zeile erzeugt ein Ausgabeobjekt.

adjustPixel(r,g,b,a){

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

Die Funktion bildet dann einen Durchschnitt aus den roten und blauen Werten und speichert ihn als rb.

       let rb = (r + b) / 2

Die nächste Codezeile bestimmt, ob das Pixel transparent sein soll oder nicht. Sie verwendet zunächst die withinRange() Funktion, um festzustellen, ob der Unterschied zwischen Rot und Blau weniger als 40 beträgt. Wenn Rot und Blau ungleiche Werte haben, wird die Farbe nicht grün sein.

Der zweite Teil des Arguments prüft, ob der Wert für Grün mindestens 20 mehr beträgt als der Durchschnitt von Rot und Blau. Dies deckt eine Reihe von Grüntönen und Schattierungen ab, die am besten zu unserem ursprünglichen 16x16-Bild passen.

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

       }

   }

Mehrere Lautsprecher und Ausblendung

In der Abbildung unten sehen Sie ein Beispiel dafür, wie mehrere Sprecher untergebracht wurden, indem diejenigen, die gerade nicht sprechen, ausgeblendet werden und alle unten links ausgerichtet bleiben. Zu diesem Zeitpunkt gibt es drei Sprecher in dem Anruf, aber nur eine Person kann gleichzeitig sprechen.

Durch Ausblenden und Entfernen inaktiver Sprecher kann die Anzahl der Sprecher und die Auflösung des gemeinsamen Bildschirms oder der Präsentation erhöht werden, ohne dass die Qualität des Erlebnisses beeinträchtigt wird.

Die Probe ausführen

Sie müssen Ihren API-Schlüssel, Ihre Sitzungs-ID und Ihr Token in die bereitgestellte config.json-Datei eingeben und können diese Dateien dann auf Ihrem bevorzugten Webserver bereitstellen. Wenn Sie Node verwenden, können Sie die folgenden Befehle aus dem Stammverzeichnis des geklonten Repositorys ausführen:

npm install 

npm run serve

Öffnen Sie dann einfach Ihren Browser und navigieren Sie zu http://127.0.0.1:3000. Wenn Sie sprechen, sollte das Mikrofon Ihre Stimme aufnehmen und Ihr Video-Feed aktivieren.

Schlussfolgerung

Das war's für dieses Lernprogramm. Ganz gleich, ob Sie eine Diashow präsentieren, eine Tabelle bearbeiten oder ein Video ansehen, die Kamera wird nur angezeigt, wenn Sie sprechen, und nimmt keinen zusätzlichen Platz auf dem Bildschirm ein. Ich hoffe, Sie haben es interessant gefunden. Weitere Implementierungen und Erweiterungen könnten sein:

  • Video-Hintergründe für Verlage

  • Watchparty-Anwendungen

Der vollständige Code für diesen Beitrag kann auf GitHub gefunden werden.

Beteiligen Sie sich am Gespräch auf unseremVonage Community Slack oder senden Sie uns eine Nachricht auf X, früher bekannt als Twitter.

Share:

https://a.storyblok.com/f/270183/689x689/2b79e61b9f/richard-sabbarton.png
Richard SabbartonSenior Video API Support Ingenieur

Richard ist Senior Video API Support Engineer bei Vonage und verfügt über mehr als 25 Jahre Erfahrung in den Bereichen Produkt, Support und Technik in der Fertigung, Telekommunikation und im Netzwerkbereich.