HTML5 Rocks

HTML5 Rocks

HTML5 Rocks

Руководства

Создание приложений дополненной реальности с помощью JSARToolKit

Поддерживаемые браузеры:

Opera поддерживается Internet Explorer не поддерживается Safari не поддерживается Firefox не поддерживается Chrome поддерживается
Оставить комментарий

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). Кроме того, необходимо распечатать указанный ниже маркер дополненной реальности. Также вы можете открыть изображение маркера на телефоне или планшетном ПК и вывести его на веб-камеру.

Маркер дополненной реальности

Вот наша демонстрационная программа. Она создает из изображений слайд-шоу с помощью маркеров дополненной реальности. Выведите маркер на веб-камеру, и программа разместит на нем фотографию. Уберите маркер за границы обзора камеры и выведите его снова, и изображение изменится.

Демонстрационная программа дополненной реальности для веб-камеры (если вы не пользуетесь WebRTC, здесь можно найти версию с готовым видео)

Настройка библиотеки 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 – задача непростая, но вполне осуществимая. Я не уверен, что моя демонстрационная программа оптимальна, поэтому с удовольствием ознакомлюсь с более эффективными способами интеграции. Исправления приветствуются!

Ссылки

Если не указано иное, на содержание этой страницы распространяется действие лицензии Creative Commons Attribution 3.0, а на примеры кода – лицензии Apache 2.0.