Написание приложений дополненной реальности с использованием JSARToolKit.

Ilmari Heikkinen

Введение

Эта статья посвящена использованию библиотеки JSARToolKit с API WebRTC getUserMedia для создания приложений дополненной реальности в Интернете. Для рендеринга я использую WebGL из-за повышенной производительности, которую он предлагает. Конечным результатом этой статьи является демонстрационное приложение, которое помещает 3D-модель поверх маркера дополненной реальности в видео с веб-камеры.

JSARToolKit — это библиотека дополненной реальности для JavaScript. Это библиотека с открытым исходным кодом, выпущенная под лицензией GPL, и прямой порт Flash FLARToolKit , который я сделал для демонстрации Mozilla Remixing Reality . FLARToolKit сам по себе является портом Java NyARToolKit , который является портом C ARToolKit . Долгий путь, но мы здесь.

JSARToolKit работает с элементами холста. Поскольку ему необходимо считывать изображение с холста, изображение должно происходить из того же источника, что и страница, или использовать CORS , чтобы обойти политику того же происхождения. Короче говоря, установите для свойства crossOrigin элемента изображения или видео, который вы хотите использовать в качестве текстуры, значение '' или 'anonymous' .

Когда вы передаете холст в JSARToolKit для анализа, JSARToolKit возвращает список маркеров AR, найденных на изображении, и соответствующие матрицы преобразования. Чтобы нарисовать 3D-объект поверх маркера, вы передаете матрицу преобразования в любую используемую вами библиотеку 3D-рендеринга, чтобы ваш объект был преобразован с использованием этой матрицы. Затем нарисуйте видеокадр в сцене WebGL и нарисуйте объект поверх него, и все готово.

Чтобы проанализировать видео с помощью JSARToolKit, нарисуйте видео на холсте, а затем передайте холст в JSARToolKit. Сделайте это для каждого кадра, и вы получите отслеживание видео AR. JSARToolKit достаточно быстр на современных движках JavaScript, чтобы делать это в реальном времени даже с видеокадрами 640x480. Однако чем больше видеокадр, тем больше времени требуется на его обработку. Хороший размер видеокадра — 320x240, но если вы планируете использовать небольшие маркеры или несколько маркеров, предпочтительнее использовать 640x480.

Демо

Чтобы просмотреть демонстрацию веб-камеры, вам необходимо включить WebRTC в вашем браузере (в Chrome перейдите по адресу about:flags и включите MediaStream). Вам также необходимо распечатать маркер AR ниже. Вы также можете попробовать открыть изображение маркера на телефоне или планшете и показать его на веб-камере.

AR-маркер.
AR-маркер.

Настройка JSARToolKit

API JSARToolKit во многом похож на 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 по-прежнему являются новыми технологиями, вам необходимо их обнаружить. Для получения более подробной информации ознакомьтесь со статьей Эрика Бидельмана о захвате аудио и видео в 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 &lt; 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 — популярный 3D-движок JavaScript. Я собираюсь рассказать, как использовать выходные данные JSARToolKit в Three.js. Вам нужны три вещи: полноэкранный квадрат с нарисованным на нем видеоизображением, камера с матрицей перспективы 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&lt;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%, правильно ли я делаю это в своей демо-версии, поэтому, пожалуйста, дайте мне знать, если вы знаете лучший способ добиться интеграции. Патчи приветствуются :)

Рекомендации