GWT 如何在您的浏览器中解锁增强现实

已发表: 2022-03-11

在我们之前关于 GWT Web Toolkit 的文章中,我们讨论了 GWT 的优势和特点,回想一下大体的想法,它让我们可以将 Java 源代码转换为 JavaScript 并无缝混合 Java 和 JavaScript 库。 我们注意到 GWT 生成的 JavaScript 得到了极大的优化。

在今天的博文中,我们希望更深入地了解 GWT 工具包的实际应用。 我们将演示如何利用 GWT 构建一个特殊的应用程序:一个实时运行的增强现实 (AR) Web 应用程序,完全使用 JavaScript,在浏览器中。

浏览器中的增强现实?这比你想象的要容易。

在本文中,我们将重点介绍 GWT 如何使我们能够轻松地与许多 JavaScript API(例如 WebRTC 和 WebGL)交互,并允许我们利用从未打算在浏览器中使用的大型 Java 库 NyARToolkit。 我们将展示 GWT 如何让我和我在 Jooink 的团队将所有这些部分放在一起创建我们的宠物项目Picshare ,这是一个基于标记的 AR 应用程序,您现在可以在浏览器中试用。

这篇文章不会全面介绍如何构建应用程序,而是展示如何使用 GWT 轻松克服看似压倒性的挑战。

项目概述:从现实到增强现实

使用 GWT、WebRTC、WebGL 和 ARToolKit 在浏览器中实现基于标记的增强现实的管道。

Picshare使用基于标记的增强现实。 这种类型的 AR 应用程序会在场景中搜索标记:一种特定的、易于识别的几何图案,就像这样。 标记提供有关标记对象的位置和方向的信息,允许软件以逼真的方式将额外的 3D 风景投影到图像中。 这个过程的基本步骤是:

  • 访问摄像头:在处理本地桌面应用程序时,操作系统提供对设备大部分硬件的 I/O 访问。 当我们处理 Web 应用程序时,情况就不一样了。 浏览器被构建为从网络下载的 JavaScript 代码的“沙箱”,最初并不打算让网站与大多数设备硬件进行交互。 WebRTC 使用 HTML5s 媒体捕获功能突破了这一障碍,使浏览器能够访问设备摄像头及其流等。
  • 分析视频流:我们有视频流……现在怎么办? 我们必须分析每一帧以检测标记,并计算标记在重建的 3D 世界中的位置。 这项复杂的任务是 NyARToolkit 的业务。
  • 增强视频:最后,我们要显示添加了合成 3D 对象的原始视频。 我们使用 WebGL 将最终的增强场景绘制到网页上。

利用 GWT 充分利用 HTML5 的 API

使用 WebGL 和 WebRTC 等 JavaScript API 可以在浏览器和用户之间实现意外和不寻常的交互。

例如,WebGL 允许硬件加速图形,并在类型化数组规范的帮助下,使 JavaScript 引擎能够以几乎原生的性能执行数字运算。 同样,使用 WebRTC,浏览器能够直接从计算机硬件访问视频(和其他数据)流。

WebGL 和 WebRTC 都是必须内置到 Web 浏览器中的 JavaScript 库。 大多数现代 HTML5 浏览器都至少部分支持这两种 API(如您在此处和此处所见)。 但是我们如何在用 Java 编写的 GWT 中利用这些工具呢? 正如上一篇文章中所讨论的,GWT 的互操作层 JsInterop(在 GWT 2.8 中正式发布)使这变得轻而易举。

在 GWT 2.8 中使用 JsInterop 就像将-generateJsInteropExports作为参数添加到编译器一样简单。 可用的注释在包jsinterop.annotations中定义,捆绑在gwt-user.jar中。

网络RTC

例如,通过最少的编码工作,在带有 GWT 的 Chrome 上使用 WebRTC 的getUserMedia变得像编写一样简单:

 Navigator.webkitGetUserMedia( configs, stream -> video.setSrc( URL.createObjectURL(stream) ), e -> Window.alert("Error: " + e) );

其中类Navigator可以定义如下:

 @JsType(namespace = JsPackage.GLOBAL, isNative = true, name="navigator") final static class Navigator { public static native void webkitGetUserMedia( Configs configs, SuccessCallback success, ErrorCallback error); }

有趣的是SuccessCallbackErrorCallback接口的定义,它们都由上面的 lambda 表达式实现,并通过@JsFunction注释在 Java 中定义:

 @JsFunction public interface SuccessCallback { public void onMediaSuccess(MediaStream stream); } @JsFunction public interface ErrorCallback { public void onError(DomException error); }

最后,类URL的定义几乎与Navigator的定义相同,类似地, Configs类可以定义为:

 @JsType(namespace = JsPackage.GLOBAL, isNative = true, name="Object") public static class Configs { @JsProperty public native void setVideo(boolean getVideo); }

所有这些功能的实际实现都发生在浏览器的 JavaScript 引擎中。

您可以在 GitHub 上找到上述代码。

在此示例中,为简单起见,使用了已弃用的navigator.getUserMedia() API,因为它是唯一一个在当前稳定版本的 Chrome 上无需 polyfill 即可工作的 API。 在生产应用程序中,我们可以使用 adapter.js 通过更新的navigator.mediaDevices.getUserMedia() API 访问流,在所有浏览器中统一,但这超出了当前讨论的范围。

WebGL

从 GWT 中使用 WebGL 与使用 WebRTC 并没有太大区别,但由于 OpenGL 标准的内在复杂性,它有点乏味。

我们这里的方法反映了上一节中遵循的方法。 包装的结果可以在Picshare中使用的 GWT WebGL 实现中看到,可以在这里找到,GWT 产生的结果示例可以在这里找到。

单独启用 WebGL 并没有真正为我们提供 3D 图形功能。 正如格雷格·塔瓦雷斯所写:

很多人不知道的是,WebGL 实际上是一个 2D API,而不是 3D API。

3D 运算必须由其他代码执行,并转换为 WebGL 的 2D 图像。 有一些用于 3D WebGL 图形的优秀 GWT 库。 我最喜欢的是 Parallax,但对于Picshare的第一个版本,我们遵循了更“自己动手”的路径,编写了一个用于渲染简单 3D 网格的小型库。 该库允许我们定义透视相机并管理对象场景。 随意检查一下,在这里。

使用 GWT 编译第三方 Java 库

NyARToolkit 是 ARToolKit 的纯 Java 端口,ARToolKit 是一个用于构建增强现实应用程序的软件库。 该端口是由 Nyatla 的日本开发人员编写的。 虽然最初的 ARToolKit 和 Nyatla 版本与最初的移植版本相比有所不同,但 NyARToolkit 仍然在积极维护和改进。

基于标记的 AR 是一个专业领域,需要计算机视觉、数字图像处理和数学方面的能力,如下所示:

使用 ARToolKit 进行基于标记的增强现实图像分析。

转载自 ARToolKit 文档。

带有 ARToolKit 的基于标记的增强现实管道。

转载自 ARToolKit 文档。

该工具包使用的所有算法都有文档记录且易于理解,但从头开始重写它们是一个漫长且容易出错的过程,因此最好使用现有的、经过验证的工具包,例如 ARToolKit。 不幸的是,当定位到网络时,没有这样的东西可用。 大多数功能强大的高级工具包都没有在 JavaScript 中实现,这种语言主要用于处理 HTML 文档和数据。 这就是 GWT 证明其无价优势的地方,使我们能够简单地将 NyARToolkit 转换为 JavaScript,并在 Web 应用程序中轻松使用它。

使用 GWT 编译

由于 GWT 项目本质上是一个 Java 项目,因此使用 NyARToolkit 只需在源路径中导入源文件即可。 但是,请注意,由于将 GWT 代码转换为 JavaScript 是在源代码级别完成的,因此您需要 NyARToolkit 的源代码,而不仅仅是包含已编译类的 JAR。

Picshare使用的库可以在这里找到。 它仅依赖于此处存档的 NyARToolkit 构建中的lib/srclib/src.markersystem中的包。 我们必须将这些包复制并导入到我们的 GWT 项目中。

我们应该将这些第三方包与我们自己的实现分开,但要继续 NyARToolkit 的“GWT 化”,我们必须提供一个 XML 配置文件,通知 GWT 编译器在哪里寻找源代码。 在包jp.nyatla.nyartoolkit中,我们添加文件NyARToolkit.gwt.xml

 <module> <source path="core" /> <source path="detector" /> <source path="nyidmarker" /> <source path="processor" /> <source path="psarplaycard" /> <source path="markersystem" /> </module>

现在,在我们的主包com.jooink.gwt.nyartoolkit中,我们创建主配置文件GWT_NyARToolKit.gwt.xml ,并指示编译器通过从其 XML 文件继承将 Nyatla 的源代码包含在类路径中:

 <inherits name='jp.nyatla.nyartoolkit.NyARToolkit'/>

其实很简单。 在大多数情况下,这就是所有需要的,但不幸的是我们还没有完成。 如果我们在这个阶段尝试通过 Super Dev Mode 编译或执行,我们会遇到一个错误说明,非常令人惊讶:

 No source code is available for type java.io.InputStream; did you forget to inherit a required module?

原因是 NyARToolkit(即用于 Java 项目的 Java 库)使用 GWT 的 Emulated JRE 不支持的 JRE 类。 我们在上一篇文章中简要讨论了这一点。

在这种情况下,问题在于InputStream和相关的 IO 类。 碰巧的是,我们甚至不需要使用大多数这些类,但我们需要为编译器提供一些实现。 好吧,我们可以投入大量时间从 NyARToolkit 源中手动删除这些引用,但这太疯狂了。 GWT 为我们提供了更好的解决方案:通过<super-source> XML 标记提供我们自己的不受支持类的实现。

<super-source>

如官方文档中所述:

<super-source>标签指示编译器重新根源路径。 这对于您希望为 GWT 项目重用现有 Java API 但原始源不可用或不可翻译的情况很有用。 这样做的一个常见原因是模拟 GWT 未实现的部分 JRE。

所以<super-source>正是我们所需要的。

我们可以在 GWT 项目中创建一个jre目录,我们可以在其中放置导致我们出现问题的类的实现:

 java.io.FileInputStream java.io.InputStream java.io.InputStreamReader java.io.StreamTokenizer java.lang.reflect.Array java.nio.ByteBuffer java.nio.ByteOrder

除了java.lang.reflect.Array之外,所有这些都没有真正使用,所以我们只需要愚蠢的实现。 例如,我们的FileInputStream如下所示:

 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; } }

构造函数中的Window.alert语句在开发过程中很有用。 尽管我们必须能够编译该类,但我们希望确保我们永远不会真正使用它,因此这会提醒我们以防无意中使用了该类。

java.lang.reflect.Array实际上是由我们需要的代码使用的,因此需要一个不完全愚蠢的实现。 这是我们的代码:

 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; } }

现在,如果我们将<super-source path="jre"/>放在GWT_NyARToolkit.gwt.xml模块文件中,我们就可以在我们的项目中安全地编译和使用 NyARToolkit!

将所有内容与 GWT 粘合在一起

现在我们处于以下位置:

  • WebRTC,一种能够从网络摄像头获取流并将其显示在<video>标签中的技术。
  • WebGL,一种能够在 HTML <canvas>中操作硬件加速图形的技术。
  • NyARToolkit,一个 Java 库,能够获取图像(作为像素数组),搜索标记,如果找到,则为我们提供一个完全定义标记在 3D 空间中的位置的变换矩阵。

现在的挑战是将所有这些技术集成在一起。

将 3D 空间投影到相机上。

我们不会深入探讨如何实现这一点,但基本思想是使用视频图像作为我们场景的背景(上图中应用到“远”平面的纹理)并构建 3D 数据结构允许我们使用 NyARToolkit 的结果将该图像投影到太空中。

这种结构为我们提供了与 NyARToolkit 的标记识别库交互的正确结构,并在相机场景的顶部绘制 3D 模型。

使相机流可用有点棘手。 视频数据只能绘制到<video>元素。 HTML5 <video>元素是不透明的,不允许我们直接提取图像数据,所以我们不得不将视频复制到中间的<canvas>中,提取图像数据,将其转换为像素数组,最后将其推送到 NyARToolkit 的Sensor.update()方法。 然后 NyARToolkit 可以完成识别图像中标记的工作,并返回一个与它在我们的 3D 空间中的位置相对应的变换矩阵。

使用这些元素,我们可以在实时视频流中以 3D 形式将合成对象精确地配置在标记上! 由于 GWT 的高性能,我们拥有大量的计算资源,因此我们甚至可以在画布上应用一些视频效果,例如棕褐色或模糊,然后将其用作 WebGL 场景的背景。

以下精简代码描述了该过程的核心:

 // 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 ... } ...

使用这种技术,我们可以生成如下结果:

Picshare 浏览器内增强现实应用程序的结果。

Picshare 浏览器内具有多个标记的增强现实应用程序的结果。

这是我们用来创建Picshare的过程,邀请您打印标记或将其显示在您的手机上,并在您的浏览器中使用基于标记的 AR。 享受!

最后的话

Picshare是我们在 Jooink 的长期宠物项目。 第一次实施可以追溯到几年前,即便如此,它的速度也足以令人印象深刻。 在此链接中,您可以看到我们早期的实验之一,该实验于 2012 年编译,从未接触过。 请注意,在示例中只有一个<video> 。 其他两个窗口是显示处理结果的<canvas>元素。

即使在 2012 年,GWT 也足够强大。随着 GWT 2.8 的发布,我们获得了与 JsInterop 的互操作性层的大幅改进,进一步提升了性能。 此外,为了庆祝许多人,我们还获得了一个更好的开发和调试环境,超级开发模式。 哦,是的,还有 Java 8 支持。

我们期待 GWT 3.0!