Heads up! This article discusses APIs that are not yet fully standardized and still in flux. Be cautious when using experimental APIs in your own projects.
Введение
В этой статье рассматривается использование библиотеки JSARToolKit с WebRTC API getUserMedia для создания веб-приложений дополненной реальности. Для обработки я использую технологию WebGL по причине ее высокой эффективности. Конечным результатом является создание демонстрационной программы, которая размещает трехмерную модель на маркере дополненной реальности в видео, снимаемом веб-камерой.
JSARToolKit – это библиотека дополненной реальности для языка JavaScript с открытым исходным кодом, выпущенная по лицензии GPL в виде непосредственного порта Flash-пакета FLARToolKit. Она была создана мной для демонстрационной программы Remixing Reality для браузера Mozilla. Сама библиотека FLARToolKit является портом Java-пакета NyARToolKit, который, в свою очередь, представляет собой порт библиотеки ARToolKit, написанной на языке C. Такая структура может показаться сложной, но теперь мы можем перейти к главному.
JSARToolKit работает с элементами canvas. Поскольку библиотеке требуется считать изображение с объекта canvas, оно должно находиться там же, где и страница, или использовать спецификацию CORS, чтобы обеспечить соответствие политике единого домена. В общих чертах необходимо установить для свойства crossOrigin элемента изображения или видео, используемого в качестве текстуры, значение '' или 'anonymous'.
Когда элемент canvas передается на анализ в библиотеку JSARToolKit, она возвращает список маркеров дополненной реальности, найденных в изображении, а также соответствующих матриц преобразования. Чтобы нарисовать объект поверх маркера, необходимо передать матрицу преобразования в любую библиотеку, используемую для обработки трехмерных моделей, чтобы с ее помощью трансформировать выбранный объект. Затем достаточно нарисовать видеокадр в сцене WebGL и расположить объект поверх него.
Чтобы проанализировать видео с использованием библиотеки JSARToolKit, переместите видео на элемент canvas, а затем передайте canvas в JSARToolKit. Выполняя эти операции для каждого кадра, можно реализовать отслеживание дополненной реальности в видео. Библиотека JSARToolKit на современных платформах JavaScript работает достаточно быстро для того, чтобы выполнять упомянутые операции в режиме реального времени даже для видеокадров с разрешением 640 x 480. Однако чем больше разрешение кадра, тем дольше выполняется его обработка. Оптимальным разрешением является 320 x 240, но если необходимо использовать маленькие маркеры или несколько маркеров, рекомендуется установить 640 x 480.
Демонстрационная программа
Чтобы испытать в действии демонстрационную программу для веб-камеры, необходимо включить WebRTC в браузере (в Chrome откройте страницу about:flags и включите функцию MediaStream). Кроме того, необходимо распечатать указанный ниже маркер дополненной реальности. Также вы можете открыть изображение маркера на телефоне или планшетном ПК и вывести его на веб-камеру.
Вот наша демонстрационная программа. Она создает из изображений слайд-шоу с помощью маркеров дополненной реальности. Выведите маркер на веб-камеру, и программа разместит на нем фотографию. Уберите маркер за границы обзора камеры и выведите его снова, и изображение изменится.
Настройка библиотеки JSARToolKit
API 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 для доступа к веб-камере
Далее я создам элемент видео, обращающийся к изображению с веб-камеры через API WebRTC. Для готовых видеофрагментов достаточно настроить атрибут видео в соответствии с URL видео. Если обнаружение маркеров выполняется на неподвижных кадрах, можно таким же образом использовать элемент изображения.
Поскольку технологии 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.");
}
);
Обнаружение маркеров
Как только детектор будет нормально работать, можно начинать загрузку изображений для обнаружения матриц дополненной реальности. Сначала перенесите изображение на элемент canvas растрового объекта, а затем запустите для него детектор. Детектор вернет число маркеров, найденных в изображении.
// 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);
Последним этапом является их последовательная обработка и получение матриц преобразования. Эти матрицы используются для наложения трехмерных объектов на маркеры.
// 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 элементов, со столбцом перевода в последних четырех элементах). Я не могу подробно описать принцип работы матриц ARToolKit (предполагаю, что используется инвертированная ось Y). Так или иначе, за счет инвертирования знака матрицы JSARToolKit работают так же, как и объекты glMatrix.
Чтобы использовать эту библиотеку вместе с другой (например, Three.js), необходимо написать функцию, преобразующую матрицы ARToolKit в соответствующий формат. Также потребуется выполнить перехват метода FLARParam.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. Сейчас мы узнаем, как использовать в ней выходные данные JSARToolKit. Для этого необходимы три объекта: полноэкранная структура из четырех элементов с расположенным на ней изображением, камера с матрицей перспективы FLARParam и объект с матрицей маркера в качестве результата преобразования. Мы выполним интеграцию в приведенном ниже коде.
Здесь расположена ссылка на действующую демонстрационную версию 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 – задача непростая, но вполне осуществимая. Я не уверен, что моя демонстрационная программа оптимальна, поэтому с удовольствием ознакомлюсь с более эффективными способами интеграции. Исправления приветствуются!