简介
本文介绍了如何将 JSARToolKit 库与 WebRTC getUserMedia API 结合使用,以在网页上运行增强现实应用。我之所以使用 WebGL 进行渲染,是因为它提高了性能。本文的最终结果是一款演示应用,它可以将 3D 模型置于摄像头视频的增强现实标记之上。
JSARToolKit 是 JavaScript 的增强现实库。它是按照 GPL 发布的开源库,是我为 Mozilla 混剪现实演示构建的 Flash FLARToolKit 的直接移植。FLARToolKit 本身是 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)。您还需要打印出下方的 AR 标记。您也可以尝试在手机或平板电脑上打开标记图像,并展示给摄像头。
设置 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.");
}
);
检测标记
检测器运行正常后,我们就可以开始向其馈送图像,以检测 AR 矩阵。先将图片绘制到光栅对象画布上,然后在光栅对象上运行检测器。检测器会返回在图片中找到的标记数量。
// 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 透视矩阵的相机,以及带有作为转换矩阵的标记矩阵的对象。我将通过以下代码向您介绍相关集成。
// 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% 确定我在演示中的操作是否正确,因此如果您知道更好的实现集成的方法,请告诉我。欢迎提供补丁 :)