使用 JSARToolKit 编写扩增实境应用

HTML5 Rocks

This article discusses APIs that are not yet fully standardized and still in flux. Be cautious when using experimental APIs in your own projects.

简介

本文介绍了如何将 WebRTC getUserMedia API 和 JSARToolKit 库配合使用,以便在网络上运行增强现实应用。我会使用 WebGL 进行呈现,因为这可以提高性能。本文的最终结果是一款演示应用,该应用可在网络摄像头视频中的增强现实标记上显示一个 3D 模型。

JSARToolKit 是 JavaScript 增强现实库。这是一个根据 GPL 发布的开放源代码库,是我为 Mozilla 混合现实演示从 Flash FLARToolKit 直接移植而来。FLARToolKit 本身则移植自 Java NyARToolKit,而 Java NyARToolKit 又移植自 C ARToolKit。过程很复杂,但最后我们有了这个库。

JSARToolKit 要运行在画布元素上。由于该库需要从画布读取图片,因此图片需要与网页来源相同,或使用 CORS 避开同源政策。简而言之,在您要用作纹理的图片或视频元素上,将 crossOrigin 属性设置为 '''anonymous'

将画布传递给 JSARToolKit 进行分析后,JSARToolKit 会返回在图片和相应转换矩阵中发现的 AR 标记的列表。要在标记上绘制 3D 对象,请将转换矩阵传递给您使用的任何 3D 呈现库,以便通过矩阵转换对象。然后在 WebGL 场景中绘制视频帧,并在视频帧上绘制对象。准备完毕!

要使用 JSARToolKit 分析视频,请在画布上绘制视频,然后将画布传递给 JSARToolKit。在每一帧上都执行此操作后,视频增强现实跟踪就设置好了。在最新的 JavaScript 引擎上,JSARToolKit 的处理速度甚至足以在 640x480 的视频帧上实时执行此操作。但是,视频帧越大,处理所需的时间就越长。比较合适的视频帧尺寸为 320x240,但如果您想使用较小的标记或多个标记,那么最好使用 640x480。

演示

要查看网络摄像头演示,您需要在浏览器中启用 WebRTC(在 Chrome 浏览器中,请访问 about:flags 并启用 MediaStream)。您还需要打印出下方的增强现实标记。您也可以尝试在手机或平板电脑上打开标记图片,并将其置于网络摄像头的视野中。

增强现实标记。

以下就是我们的目标演示。此演示使用增强现实标记创建了图片幻灯片演示。将标记置于摄像头的视野中,系统就会在标记上显示一张照片。将标记移出摄像头的视野,然后再移入,图片就会变化。

增强现实网络摄像头演示(如果您没有 WebRTC,此处提供了一个预先录制的视频

设置 JSARToolKit

JSARToolKit API 与 Java 很类似,因此您需要在使用时注意区分。基本概念就是,检测器对象运行在光栅对象上。摄像头参数对象位于检测器和光栅之间,用于将光栅坐标转换成摄像头坐标。要从检测器中获取检测到的标记,您应遍历这些标记,并将它们的转换矩阵复制到代码中。

第一步是创建光栅对象、摄像头参数对象和检测器对象。

// Create a RGB raster object for the 2D canvas.
// JSARToolKit uses raster objects to read image data.
// Note that you need to set canvas.changed = true on every frame.
var raster = new NyARRgbRaster_Canvas2D(canvas);

// FLARParam is the thing used by FLARToolKit to set camera parameters.
// Here we create a FLARParam for images with 320x240 pixel dimensions.
var param = new FLARParam(320, 240);

// The FLARMultiIdMarkerDetector is the actual detection engine for marker detection.
// It detects multiple ID markers. ID markers are special markers that encode a number.
var detector = new FLARMultiIdMarkerDetector(param, 120);

// For tracking video set continue mode to true. In continue mode, the detector
// tracks markers across multiple frames.
detector.setContinueMode(true);

// Copy the camera perspective matrix from the FLARParam to the WebGL library camera matrix.
// The second and third parameters determine the zNear and zFar planes for the perspective matrix.
param.copyCameraMatrix(display.camera.perspectiveMatrix, 10, 10000);

使用 getUserMedia 访问网络摄像头

接着,我要创建通过 WebRTC API 获取网络摄像头视频的视频元素。如果视频是预先录制的,只需将来源属性设置为视频网址即可。如果是在静态图片上检测标记,您就可以通过大体类似的方式使用图片元素。

由于 WebRTC 和 getUserMedia 仍属于新兴技术,因此您需要了解它们的功能。有关详情,请参阅埃里克·比德尔曼 (Eric Bidelman) 有关在 HTML5 中捕获音频和视频的文章。

var video = document.createElement('video');
video.width = 320;
video.height = 240;

var getUserMedia = function(t, onsuccess, onerror) {
  if (navigator.getUserMedia) {
    return navigator.getUserMedia(t, onsuccess, onerror);
  } else if (navigator.webkitGetUserMedia) {
    return navigator.webkitGetUserMedia(t, onsuccess, onerror);
  } else if (navigator.mozGetUserMedia) {
    return navigator.mozGetUserMedia(t, onsuccess, onerror);
  } else if (navigator.msGetUserMedia) {
    return navigator.msGetUserMedia(t, onsuccess, onerror);
  } else {
    onerror(new Error("No getUserMedia implementation found."));
  }
};

var URL = window.URL || window.webkitURL;
var createObjectURL = URL.createObjectURL || webkitURL.createObjectURL;
if (!createObjectURL) {
  throw new Error("URL.createObjectURL not found.");
}

getUserMedia({'video': true},
  function(stream) {
    var url = createObjectURL(stream);
    video.src = url;
  },
  function(error) {
    alert("Couldn't access webcam.");
  }
);

检测标记

检测器运行正常后,我们就可以开始向它馈入要检测增强现实矩阵的图片了。先将图片绘制到光栅对象画布上,然后在光栅对象上运行检测器。检测器会返回在图片中找到的标记数量。

// Draw the video frame to the raster canvas, scaled to 320x240.
canvas.getContext('2d').drawImage(video, 0, 0, 320, 240);

// Tell the raster object that the underlying canvas has changed.
canvas.changed = true;

// Do marker detection by using the detector object on the raster object.
// The threshold parameter determines the threshold value
// for turning the video frame into a 1-bit black-and-white image.
//
var markerCount = detector.detectMarkerLite(raster, threshold);

最后一步是遍历检测到的标记并获取它们的转换矩阵。使用转换矩阵将 3D 对象显示在标记上。

// Create a NyARTransMatResult object for getting the marker translation matrices.
var resultMat = new NyARTransMatResult();

var markers = {};

// Go through the detected markers and get their IDs and transformation matrices.
for (var idx = 0; idx < markerCount; idx++) {
  // Get the ID marker data for the current marker.
  // ID markers are special kind of markers that encode a number.
  // The bytes for the number are in the ID marker data.
  var id = detector.getIdMarkerData(idx);

  // Read bytes from the id packet.
  var currId = -1;
  // This code handles only 32-bit numbers or shorter.
  if (id.packetLength <= 4) {
    currId = 0;
    for (var i = 0; i < id.packetLength; i++) {
      currId = (currId << 8) | id.getPacketData(i);
    }
  }

  // If this is a new id, let's start tracking it.
  if (markers[currId] == null) {
    markers[currId] = {};
  }
  // Get the transformation matrix for the detected marker.
  detector.getTransformMatrix(idx, resultMat);

  // Copy the result matrix into our marker tracker object.
  markers[currId].transform = Object.asCopy(resultMat);
}

矩阵映射

以下代码用于将 JSARToolKit 矩阵复制转换成 glMatrix 矩阵(这些矩阵是 16 个元素的 FloatArrays,其中最后四个元素是转换列)。这段代码十分奇妙(也就是说,我不知道 ARToolKit 矩阵是如何设置的。可能是反转 Y 轴)。无论如何,这个符号反转技巧让 JSARToolKit 矩阵的运行效果与 glMatrix 相同。

要将该库与 Three.js 等其他库配合使用,您需要编写一个函数,用于将 ARToolKit 矩阵转换成相应库的矩阵格式。您还需要结合使用 FLARParam.copyCameraMatrix 方法。copyCameraMatrix 方法用于将 FLARParam 透视矩阵编写成 glMatrix 类型的矩阵。

function copyMarkerMatrix(arMat, glMat) {
  glMat[0] = arMat.m00;
  glMat[1] = -arMat.m10;
  glMat[2] = arMat.m20;
  glMat[3] = 0;
  glMat[4] = arMat.m01;
  glMat[5] = -arMat.m11;
  glMat[6] = arMat.m21;
  glMat[7] = 0;
  glMat[8] = -arMat.m02;
  glMat[9] = arMat.m12;
  glMat[10] = -arMat.m22;
  glMat[11] = 0;
  glMat[12] = arMat.m03;
  glMat[13] = -arMat.m13;
  glMat[14] = arMat.m23;
  glMat[15] = 1;
}

Three.js 集成

Three.js 是一款广受欢迎的 JavaScript 3D 引擎。我会向您介绍在 Three.js 中使用 JSARToolKit 输出的方法。您需要满足三个条件:上面绘制有视频图片的全屏四边形、支持 FLARParam 透视矩阵的摄像头,以及带有作为转换矩阵的标记矩阵的对象。我会通过以下代码向您介绍 Three.js 集成。

此连接指向有效的 Three.js 演示。该演示启动了调试输出,以便您查看 JSARToolKit 库的一些内部运行情况。

// I'm going to use a glMatrix-style matrix as an intermediary.
// So the first step is to create a function to convert a glMatrix matrix into a Three.js Matrix4.
THREE.Matrix4.prototype.setFromArray = function(m) {
  return this.set(
    m[0], m[4], m[8], m[12],
    m[1], m[5], m[9], m[13],
    m[2], m[6], m[10], m[14],
    m[3], m[7], m[11], m[15]
  );
};

// glMatrix matrices are flat arrays.
var tmp = new Float32Array(16);

// Create a camera and a marker root object for your Three.js scene.
var camera = new THREE.Camera();
scene.add(camera);

var markerRoot = new THREE.Object3D();
markerRoot.matrixAutoUpdate = false;

// Add the marker models and suchlike into your marker root object.
var cube = new THREE.Mesh(
  new THREE.CubeGeometry(100,100,100),
  new THREE.MeshBasicMaterial({color: 0xff00ff})
);
cube.position.z = -50;
markerRoot.add(cube);

// Add the marker root to your scene.
scene.add(markerRoot);

// Next we need to make the Three.js camera use the FLARParam matrix.
param.copyCameraMatrix(tmp, 10, 10000);
camera.projectionMatrix.setFromArray(tmp);


// To display the video, first create a texture from it.
var videoTex = new THREE.Texture(videoCanvas);

// Then create a plane textured with the video.
var plane = new THREE.Mesh(
  new THREE.PlaneGeometry(2, 2, 0),
  new THREE.MeshBasicMaterial({map: videoTex})
);

// The video plane shouldn't care about the z-buffer.
plane.material.depthTest = false;
plane.material.depthWrite = false;

// Create a camera and a scene for the video plane and
// add the camera and the video plane to the scene.
var videoCam = new THREE.Camera();
var videoScene = new THREE.Scene();
videoScene.add(plane);
videoScene.add(videoCam);

...

// On every frame do the following:
function tick() {
  // Draw the video frame to the canvas.
  videoCanvas.getContext('2d').drawImage(video, 0, 0);
  canvas.getContext('2d').drawImage(videoCanvas, 0, 0, canvas.width, canvas.height);

  // Tell JSARToolKit that the canvas has changed.
  canvas.changed = true;

  // Update the video texture.
  videoTex.needsUpdate = true;

  // Detect the markers in the video frame.
  var markerCount = detector.detectMarkerLite(raster, threshold);
  for (var i=0; i<markerCount; i++) {
    // Get the marker matrix into the result matrix.
    detector.getTransformMatrix(i, resultMat);

    // Copy the marker matrix to the tmp matrix.
    copyMarkerMatrix(resultMat, tmp);

    // Copy the marker matrix over to your marker root object.
    markerRoot.matrix.setFromArray(tmp);
  }

  // Render the scene.
  renderer.autoClear = false;
  renderer.clear();
  renderer.render(videoScene, videoCam);
  renderer.render(scene, camera);
}

摘要

在本文中,我们介绍了有关 JSARToolKit 的基本信息。现在,您可以通过 JavaScript 使用网络摄像头构建自己的增强现实应用了。

将 JSARToolKit 与 Three.js 集成有些麻烦,但却是完全可行的。我无法 100% 确定此演示中的操作正确与否,因此,如果您知道更好的实现集成的方法,请告诉我。欢迎修改指正 :)

参考资料

Comments

0