Wie GWT Augmented Reality in Ihrem Browser freischaltet
Veröffentlicht: 2022-03-11In unserem vorherigen Beitrag zum GWT Web Toolkit haben wir die Stärken und Eigenschaften von GWT besprochen, das es uns, um an die allgemeine Idee zu erinnern, erlaubt, Java-Quellcode in JavaScript zu transpilieren und Java und JavaScript-Bibliotheken nahtlos zu mischen. Wir haben festgestellt, dass das von GWT generierte JavaScript dramatisch optimiert ist.
Im heutigen Beitrag möchten wir etwas tiefer gehen und das GWT Toolkit in Aktion sehen. Wir werden demonstrieren, wie wir GWT nutzen können, um eine besondere Anwendung zu erstellen: eine Augmented Reality (AR)-Webanwendung, die in Echtzeit vollständig in JavaScript im Browser ausgeführt wird.
In diesem Artikel konzentrieren wir uns darauf, wie GWT uns die Möglichkeit gibt, einfach mit vielen JavaScript-APIs wie WebRTC und WebGL zu interagieren, und uns ermöglicht, eine große Java-Bibliothek, NyARToolkit, zu nutzen, die nie für die Verwendung im Browser vorgesehen war. Wir werden zeigen, wie GWT es meinem Team und mir bei Jooink ermöglicht hat, all diese Teile zusammenzufügen, um unser Lieblingsprojekt Picshare zu erstellen, eine markerbasierte AR-Anwendung, die Sie jetzt in Ihrem Browser ausprobieren können.
Dieser Beitrag ist keine umfassende exemplarische Vorgehensweise zum Erstellen der Anwendung, sondern zeigt vielmehr die Verwendung von GWT, um scheinbar überwältigende Herausforderungen mit Leichtigkeit zu meistern.
Projektübersicht: Von der Realität zur erweiterten Realität
Picshare verwendet Marker-basierte Augmented Reality. Diese Art von AR-Anwendung durchsucht die Szene nach einer Markierung : einem bestimmten, leicht erkennbaren geometrischen Muster wie diesem. Der Marker gibt Auskunft über die Position und Ausrichtung des markierten Objekts, sodass die Software zusätzliche 3D-Szenerien realistisch in das Bild projizieren kann. Die grundlegenden Schritte in diesem Prozess sind:
- Zugriff auf die Kamera: Beim Umgang mit nativen Desktop-Anwendungen bietet das Betriebssystem E/A-Zugriff auf einen Großteil der Hardware des Geräts. Anders verhält es sich mit Webanwendungen. Browser wurden als eine Art „Sandbox“ für aus dem Internet heruntergeladenen JavaScript-Code entwickelt und waren ursprünglich nicht dazu gedacht, Websites die Interaktion mit der meisten Gerätehardware zu ermöglichen. WebRTC durchbricht diese Barriere mit den Medienerfassungsfunktionen von HTML5 und ermöglicht dem Browser unter anderem den Zugriff auf die Gerätekamera und ihren Stream.
- Analysieren Sie den Videostream: Wir haben den Videostream … was nun? Wir müssen jeden Frame analysieren, um Markierungen zu erkennen, und die Position der Markierung in der rekonstruierten 3D-Welt berechnen. Diese komplexe Aufgabe ist das Geschäft von NyARToolkit.
- Erweitern Sie das Video: Schließlich möchten wir das Originalvideo mit hinzugefügten synthetischen 3D-Objekten anzeigen. Wir verwenden WebGL, um die endgültige, erweiterte Szene auf die Webseite zu zeichnen.
Nutzung der APIs von HTML5 mit GWT
Die Verwendung von JavaScript-APIs wie WebGL und WebRTC ermöglicht unerwartete und ungewöhnliche Interaktionen zwischen dem Browser und dem Benutzer.
Beispielsweise ermöglicht WebGL hardwarebeschleunigte Grafiken und ermöglicht der JavaScript-Engine mit Hilfe der typisierten Array-Spezifikation, Zahlenverarbeitung mit nahezu nativer Leistung auszuführen. In ähnlicher Weise kann der Browser mit WebRTC direkt von der Computerhardware auf Videostreams (und andere Datenströme) zugreifen.
WebGL und WebRTC sind beides JavaScript-Bibliotheken, die in den Webbrowser integriert werden müssen. Die meisten modernen HTML5-Browser bieten zumindest teilweise Unterstützung für beide APIs (wie Sie hier und hier sehen können). Aber wie können wir diese Tools in GWT nutzen, das in Java geschrieben ist? Wie im vorherigen Beitrag besprochen, macht die Interoperabilitätsschicht von GWT, JsInterop (offiziell in GWT 2.8 veröffentlicht), dies zu einem Kinderspiel.
Die Verwendung von JsInterop mit GWT 2.8 ist so einfach wie das Hinzufügen von -generateJsInteropExports
als Argument zum Compiler. Die verfügbaren Anmerkungen sind im Paket jsinterop.annotations
definiert, gebündelt in gwt-user.jar
.
WebRTC
Beispielsweise wird die Verwendung von getUserMedia
von WebRTC auf Chrome mit GWT mit minimalem Programmieraufwand so einfach wie das Schreiben:
Navigator.webkitGetUserMedia( configs, stream -> video.setSrc( URL.createObjectURL(stream) ), e -> Window.alert("Error: " + e) );
Wobei die Klasse Navigator
wie folgt definiert werden kann:
@JsType(namespace = JsPackage.GLOBAL, isNative = true, name="navigator") final static class Navigator { public static native void webkitGetUserMedia( Configs configs, SuccessCallback success, ErrorCallback error); }
Interessant ist die Definition der Schnittstellen SuccessCallback
und ErrorCallback
, die beide oben durch den Lambda-Ausdruck implementiert und in Java mithilfe der Annotation @JsFunction
definiert wurden:
@JsFunction public interface SuccessCallback { public void onMediaSuccess(MediaStream stream); } @JsFunction public interface ErrorCallback { public void onError(DomException error); }
Schließlich ist die Definition der Klasse URL
fast identisch mit der von Navigator
, und ähnlich kann die Klasse Configs
definiert werden durch:
@JsType(namespace = JsPackage.GLOBAL, isNative = true, name="Object") public static class Configs { @JsProperty public native void setVideo(boolean getVideo); }
Die eigentliche Implementierung all dieser Funktionalitäten findet in der JavaScript-Engine des Browsers statt.
Den obigen Code finden Sie hier auf GitHub.
In diesem Beispiel wird der Einfachheit halber die veraltete API navigator.getUserMedia()
verwendet, da sie die einzige ist, die ohne Polyfilling in der aktuellen stabilen Version von Chrome funktioniert. In einer Produktionsanwendung können wir adapter.js verwenden, um über die neuere navigator.mediaDevices.getUserMedia()
-API einheitlich in allen Browsern auf den Stream zuzugreifen, aber dies würde den Rahmen der vorliegenden Diskussion sprengen.
WebGL
Die Verwendung von WebGL von GWT unterscheidet sich nicht wesentlich von der Verwendung von WebRTC, ist jedoch aufgrund der inhärenten Komplexität des OpenGL-Standards etwas mühsamer.
Unser Ansatz hier spiegelt den im vorherigen Abschnitt verfolgten wider. Das Ergebnis des Umbruchs ist in der in Picshare verwendeten GWT-WebGL-Implementierung zu sehen, die hier zu finden ist, und ein Beispiel für die von GWT erzeugten Ergebnisse finden Sie hier.
Das Aktivieren von WebGL allein gibt uns nicht wirklich 3D-Grafikfähigkeiten. Wie Gregg Tavares schreibt:
Was viele Leute nicht wissen, ist, dass WebGL eigentlich eine 2D-API ist, keine 3D-API.
Die 3D-Arithmetik muss von einem anderen Code ausgeführt und für WebGL in das 2D-Bild umgewandelt werden. Es gibt einige gute GWT-Bibliotheken für 3D-WebGL-Grafiken. Mein Favorit ist Parallax, aber für die erste Version von Picshare sind wir einem eher „Do-it-yourself“-Pfad gefolgt und haben eine kleine Bibliothek zum Rendern einfacher 3D-Netze geschrieben. Mit der Bibliothek können wir eine perspektivische Kamera definieren und eine Objektszene verwalten. Schauen Sie es sich gerne hier an.
Kompilieren von Java-Bibliotheken von Drittanbietern mit GWT
NyARToolkit ist eine reine Java-Portierung von ARToolKit, einer Softwarebibliothek zum Erstellen von Augmented-Reality-Anwendungen. Der Port wurde von den japanischen Entwicklern bei Nyatla geschrieben. Obwohl sich das ursprüngliche ARToolKit und die Nyatla-Version seit der ursprünglichen Portierung etwas voneinander entfernt haben, wird das NyARToolkit immer noch aktiv gepflegt und verbessert.
Markerbasierte AR ist ein Spezialgebiet und erfordert Kompetenzen in Computer Vision, digitaler Bildverarbeitung und Mathematik, wie hier deutlich wird:
Reproduziert aus der ARToolKit-Dokumentation.
Reproduziert aus der ARToolKit-Dokumentation.
Alle vom Toolkit verwendeten Algorithmen sind dokumentiert und gut verstanden, aber das Neuschreiben von Grund auf ist ein langer und fehleranfälliger Prozess, daher ist es vorzuziehen, ein vorhandenes, bewährtes Toolkit wie ARToolKit zu verwenden. Leider ist bei der Ausrichtung auf das Internet so etwas nicht verfügbar. Die leistungsstärksten, fortschrittlichsten Toolkits haben keine Implementierung in JavaScript, einer Sprache, die hauptsächlich zum Bearbeiten von HTML-Dokumenten und -Daten verwendet wird. Hier beweist GWT seine unschätzbare Stärke, indem es uns ermöglicht, NyARToolkit einfach in JavaScript zu transpilieren und es mit sehr wenig Aufwand in einer Webanwendung zu verwenden.
Kompilieren mit GWT
Da ein GWT-Projekt im Wesentlichen ein Java-Projekt ist, besteht die Verwendung von NyARToolkit lediglich darin, die Quelldateien in Ihren Quellpfad zu importieren. Beachten Sie jedoch, dass Sie, da die Transpilation von GWT-Code in JavaScript auf Quellcodeebene erfolgt, die Quellen von NyARToolkit benötigen und nicht nur ein JAR mit den kompilierten Klassen.
Die von Picshare verwendete Bibliothek finden Sie hier. Es ist nur von den Paketen abhängig, die sich in lib/src
und lib/src.markersystem
aus dem hier archivierten NyARToolkit-Build befinden. Wir müssen diese Pakete kopieren und in unser GWT-Projekt importieren.

Wir sollten diese Pakete von Drittanbietern von unserer eigenen Implementierung getrennt halten, aber um mit der „GWT-isierung“ von NyARToolkit fortzufahren, müssen wir eine XML-Konfigurationsdatei bereitstellen, die den GWT-Compiler darüber informiert, wo er nach Quellen suchen soll. Im Paket jp.nyatla.nyartoolkit
fügen wir die Datei NyARToolkit.gwt.xml
<module> <source path="core" /> <source path="detector" /> <source path="nyidmarker" /> <source path="processor" /> <source path="psarplaycard" /> <source path="markersystem" /> </module>
Jetzt erstellen wir in unserem com.jooink.gwt.nyartoolkit
die Hauptkonfigurationsdatei GWT_NyARToolKit.gwt.xml
und weisen den Compiler an, die Quelle von Nyatla in den Klassenpfad aufzunehmen, indem er von seiner XML-Datei erbt:
<inherits name='jp.nyatla.nyartoolkit.NyARToolkit'/>
Eigentlich ganz einfach. In den meisten Fällen wäre dies alles, was nötig ist, aber leider sind wir noch nicht fertig. Wenn wir zu diesem Zeitpunkt versuchen, über den Super Dev Mode zu kompilieren oder auszuführen, stoßen wir überraschenderweise auf einen Fehler, der besagt:
No source code is available for type java.io.InputStream; did you forget to inherit a required module?
Der Grund dafür ist, dass NyARToolkit (dh eine für Java-Projekte vorgesehene Java-Bibliothek) Klassen der JRE verwendet, die von der emulierten JRE des GWT nicht unterstützt werden. Wir haben dies kurz im vorherigen Beitrag besprochen.
In diesem Fall liegt das Problem bei InputStream
und verwandten IO-Klassen. Zufällig müssen wir die meisten dieser Klassen nicht einmal verwenden, aber wir müssen dem Compiler einige Implementierungen bereitstellen. Nun, wir könnten eine Menge Zeit investieren, um diese Referenzen manuell aus der NyARToolkit-Quelle zu entfernen, aber das wäre verrückt. GWT bietet uns eine bessere Lösung: Stellen Sie unsere eigenen Implementierungen der nicht unterstützten Klassen über das XML-Tag <super-source>
bereit.
<super-source>
Wie in der offiziellen Dokumentation beschrieben:
Das Tag
<super-source>
weist den Compiler an, einen Quellpfad neu zu rooten. Dies ist nützlich, wenn Sie eine vorhandene Java-API für ein GWT-Projekt wiederverwenden möchten, aber die Originalquelle nicht verfügbar oder nicht übersetzbar ist. Ein häufiger Grund dafür ist die Emulation eines Teils der JRE, der nicht von GWT implementiert wurde.
<super-source>
ist also genau das, was wir brauchen.
Wir können im GWT-Projekt ein jre
Verzeichnis erstellen, in dem wir unsere Implementierungen für die Klassen ablegen können, die uns Probleme bereiten:
java.io.FileInputStream java.io.InputStream java.io.InputStreamReader java.io.StreamTokenizer java.lang.reflect.Array java.nio.ByteBuffer java.nio.ByteOrder
Alle diese, außer java.lang.reflect.Array
, sind wirklich unbenutzt, also brauchen wir nur dumme Implementierungen. Unser FileInputStream
liest sich beispielsweise wie folgt:
package java.io; import java.io.InputStream; import com.google.gwt.user.client.Window; public class FileInputStream extends InputStream { public FileInputStream(String filename) { Window.alert("WARNING, FileInputStream created with filename: " + filename ); } @Override public int read() { return 0; } }
Die Window.alert
Anweisung im Konstruktor ist während der Entwicklung hilfreich. Obwohl wir in der Lage sein müssen, die Klasse zu kompilieren, möchten wir sicherstellen, dass wir sie nie wirklich verwenden, damit wir gewarnt werden, falls die Klasse versehentlich verwendet wird.
java.lang.reflect.Array
wird tatsächlich von dem von uns benötigten Code verwendet, daher ist eine nicht ganz dumme Implementierung erforderlich. Das ist unser Code:
package java.lang.reflect; import jp.nyatla.nyartoolkit.core.labeling.rlelabeling.NyARRleLabelFragmentInfo; import jp.nyatla.nyartoolkit.markersystem.utils.SquareStack; import com.google.gwt.user.client.Window; public class Array { public static <T> Object newInstance(Class<T> c, int n) { if( NyARRleLabelFragmentInfo.class.equals(c)) return new NyARRleLabelFragmentInfo[n]; else if(SquareStack.Item.class.equals(c)) return new SquareStack.Item[n]; else Window.alert("Creating array of size " + n + " of " + c.toString()); return null; } }
Wenn wir jetzt <super-source path="jre"/>
in die GWT_NyARToolkit.gwt.xml
, können wir NyARToolkit sicher kompilieren und in unserem Projekt verwenden!
Alles zusammenkleben mit GWT
Jetzt sind wir in der Lage, Folgendes zu haben:
- WebRTC, eine Technologie, die in der Lage ist, einen Stream von der Webcam zu nehmen und ihn in einem
<video>
-Tag anzuzeigen. - WebGL, eine Technologie, die in der Lage ist, hardwarebeschleunigte Grafiken in einem HTML-
<canvas>
zu manipulieren. - NyARToolkit, eine Java-Bibliothek, die in der Lage ist, ein Bild (als ein Array von Pixeln) aufzunehmen, nach einem Marker zu suchen und uns, falls gefunden, eine Transformationsmatrix zu geben, die die Position des Markers im 3D-Raum vollständig definiert.
Die Herausforderung besteht nun darin, all diese Technologien miteinander zu integrieren.
Wir werden nicht näher darauf eingehen, wie dies zu erreichen ist, aber die Grundidee besteht darin, die Videobilder als Hintergrund unserer Szene zu verwenden (eine Textur, die auf die „entfernte“ Ebene im obigen Bild angewendet wird) und eine 3D-Datenstruktur aufzubauen So können wir dieses Bild mithilfe der Ergebnisse von NyARToolkit in den Raum projizieren.
Diese Konstruktion gibt uns die richtige Struktur, um mit der NyARToolkit-Bibliothek zur Markierungserkennung zu interagieren und das 3D-Modell über der Kameraszene zu zeichnen.
Den Kamerastream nutzbar zu machen ist etwas knifflig. Videodaten können nur in ein <video>
-Element gezogen werden. Das HTML5-Element <video>
ist undurchsichtig und ermöglicht es uns nicht, die Bilddaten direkt zu extrahieren, sodass wir gezwungen sind, das Video auf eine zwischengeschaltete <canvas>
zu kopieren, die Bilddaten zu extrahieren, sie in ein Array von Pixeln umzuwandeln und schließlich schieben Sie es in die Sensor.update()
Methode von NyARToolkit. Dann kann NyARToolkit den Marker im Bild identifizieren und eine Transformationsmatrix zurückgeben, die seiner Position in unserem 3D-Raum entspricht.
Mit diesen Elementen können wir im Live-Videostream ein synthetisches Objekt genau über dem Marker in 3D platzieren! Dank der hohen Leistung von GWT verfügen wir über reichlich Rechenressourcen, sodass wir sogar einige Videoeffekte wie Sepia oder Unschärfe auf die Leinwand anwenden können, bevor wir sie als Hintergrund für die WebGL-Szene verwenden.
Der folgende gekürzte Code beschreibt den Kern des Prozesses:
// given a <canvas> drawing context with appropriate width and height // and a <video> where the mediastream is drawn ... // for each video frame // draw the video frame on the canvas ctx.drawImage(video, 0, 0, w, h); // extract image data from the canvas ImageData capt = ctx.getImageData(0, 0, w, h); // convert the image data in a format acceptable by NyARToolkit ImageDataRaster input = new ImageDataRaster(capt); // push the image in to a NyARSensor sensor.update(input); // update the NyARMarkerSystem with the sensor nyar.update(sensor); // the NyARMarkerSystem contains information about the marker patterns and is able to detect them. // After the call to update, all the markers are detected and we can get information for each // marker that was found. if( nyar.isExistMarker( marker_id ) ) { NyARDoubleMatrix44 m = nyar.getMarkerMatrix(marker_id); // m is now the matrix representing the pose (position and orientation) of // the marker in the scene, so we can use it to superimpose an object of // our choice ... } ...
Mit dieser Technik können wir Ergebnisse wie diese erzeugen:
Dies ist der Prozess, den wir verwendet haben, um Picshare zu erstellen, bei dem Sie eingeladen werden, einen Marker auszudrucken oder auf Ihrem Handy anzuzeigen und mit Marker-basiertem AR in Ihrem Browser zu spielen. Genießen!
Schlussbemerkungen
Picshare ist ein langfristiges Lieblingsprojekt für uns bei Jooink. Die erste Implementierung liegt einige Jahre zurück, und schon damals war sie schnell genug, um zu beeindrucken. Unter diesem Link können Sie eines unserer früheren Experimente sehen, das 2012 zusammengestellt und nie berührt wurde. Beachten Sie, dass es im Beispiel nur ein <video>
gibt. Die anderen beiden Fenster sind <canvas>
-Elemente, die die Ergebnisse der Verarbeitung anzeigen.
GWT war sogar 2012 leistungsfähig genug. Mit der Veröffentlichung von GWT 2.8 haben wir mit JsInterop eine stark verbesserte Interoperabilitätsebene erhalten, die die Leistung weiter steigert. Außerdem haben wir zur Feier vieler eine viel bessere Entwicklungs- und Debugging-Umgebung erhalten, den Super-Dev-Modus. Ach ja, und Java 8-Unterstützung.
Wir freuen uns auf GWT 3.0!