Desarrollo de aplicaciones de realidad aumentada con 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.

Introducción

En este artículo encontrarás información sobre cómo utilizar la biblioteca JSARToolKit con el API getUserMedia de WebRTC para crear aplicaciones de realidad aumentada en la Web. Para llevar a cabo la representación, utilizo WebGL debido al gran rendimiento que ofrece. El objetivo de este artículo es crear una aplicación de demostración que integre un modelo 3D en un marcador de realidad aumentada en un vídeo de cámara web.

JSARToolKit es una biblioteca de realidad aumentada para JavaScript. También se considera una biblioteca de software libre publicada bajo la Licencia Pública GNU y un puerto directo de FLARToolKit para Flash que creé para la demo de realidad aumentada con aceleración gráfica de Mozilla. FLARToolKit en sí es un puerto de NyARToolKit de Java, que a su vez es puerto de ARToolKit C. Después de esta larga explicación, ahora sí comenzamos.

JSARToolKit opera en elementos canvas. Debido a que necesita leer la imagen fuera del elemento canvas, es necesario que esta proceda del mismo origen que la página o que utilice la tecnología CORS para obtener una política con aproximadamente el mismo origen. En resumen, establece la propiedad crossOrigin del elemento de vídeo o imagen que quieras utilizar como textura para '' o 'anonymous'.

Cuando transfieres un elemento canvas a JSARToolKit para analizarlo, JSARToolKit devuelve una lista de marcadores de realidad aumentada que se encuentran en la imagen y las matrices de transformación correspondientes. Para dibujar un objeto 3D en un marcador, transfiere la matriz de transformación a la librería de representación 3D que utilices, de manera que el objeto se transforme utilizando la matriz. A continuación, dibuja el fotograma de vídeo en la escena WebGL, dibuja el objeto en ella y lo tendrás preparado.

Para analizar un vídeo mediante JSARToolKit, dibuja el vídeo en un elemento canvas y, a continuación, transfiere el elemento canvas a JSARToolKit. Realiza esta acción para todos los fotogramas y obtendrás el tracking de realidad aumentada. JSARToolKit funciona con la suficiente rapidez en motores JavaScript modernos como para realizar este proceso en tiempo real incluso en fotogramas de vídeo de 640x480. Sin embargo, cuanto mayor sea el fotograma de vídeo, más tiempo se tardará en completar el proceso. Un tamaño adecuado para un fotograma de vídeo sería 320x240, pero si tienes previsto utilizar marcadores pequeños o varios marcadores, es preferible utilizar un tamaño de 640x480.

Demo

Para visualizar la demo de cámara web, debes tener habilitado WebRTC en tu navegador (en Chrome, ve a la página about:flags y habilita MediaStream). También debes imprimir el marcador de realidad aumentada que se indica a continuación. Puedes probar a abrir la imagen del marcador en tu teléfono o tablet y mostrarla a la cámara web.

Marcador de realidad aumentada

A continuación, se muestra una demo de lo que intentamos realizar. En esta demo, se crea una presentación de imágenes mediante los marcadores de realidad aumentada. Muestra un marcador a la cámara y esta colocará una foto en él. Mueve el marcador fuera de la vista de la cámara y muéstralo de nuevo, verás que la imagen ha cambiado.

Demo de realidad aumentada mediante cámara web (si no tienes WebRTC, aquí te proporcionamos una versión con vídeo integrado previamente)

Cómo configurar JSARToolKit

El API JSARToolKit es bastante similar a Java, por lo que tendrás que hacer algunas operaciones adicionales para utilizarla. La idea básica es que dispones de un objeto detector que opera en un objeto ráster. Entre el detector y el ráster hay un objeto parámetro de cámara que transforma las coordenadas en coordenadas de cámara. Para obtener los marcadores detectados del detector, debes repetir el proceso en cada uno de ellos y copiar sus matrices de transformación en tu código.

El primer paso es crear el objeto ráster, el objeto parámetro de cámara y el objeto detector.

// 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);

Cómo utilizar getUserMedia para acceder a la cámara web

A continuación, crearemos un elemento de vídeo que obtenga vídeo de cámara web a través de las API de WebRTC. Para vídeos grabados previamente, solo tienes que establecer el atributo de origen del vídeo en la URL del vídeo. Si vas a realizar la detección de marcadores a partir de imágenes estáticas, puedes utilizar un elemento de imagen casi de la misma forma.

Debido a que WebRTC y getUserMedia todavía son tecnologías emergentes, debes detectar sus funciones. Para obtener más información, consulta el artículo de Eric Bidelman sobre cómo capturar audio y vídeo en 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.");
  }
);

Cómo detectar marcadores

Una vez tengamos el detector funcionando adecuadamente, podremos comenzar a proporcionarle imágenes para detectar matrices de realidad aumentada. En primer lugar, dibuja la imagen en el elemento canvas del objeto ráster y, a continuación, ejecuta el detector en el objeto ráster. El detector devolverá el número de marcadores que encuentre en la imagen.

// 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);

El último paso es repetir este proceso en los marcadores detectados y obtener sus matrices de transformación. Utiliza las matrices de transformación para poner objetos 3D en los marcadores.

// 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);
}

Cómo asignar matrices

A continuación, se indica el código para copiar matrices JSARToolKit en matrices glMatrix (que son FloatArrays de 16 elementos con la columna de traducción en los cuatro últimos elementos). Funciona como por arte de magia (es decir, no sé cómo se configuran las matrices ARToolKit. Supongo que mediante el eje Y invertido). En cualquier caso, este pequeño truco de magia por el que se invierten los signos hace que la matriz JSARToolKit funcione de la misma forma que una matriz glMatrix.

Para utilizar la biblioteca con otra biblioteca, como Three.js, debes escribir una función que pase las matrices ARToolKit al formato de matriz de la biblioteca. También debes añadir el método FLARParam.copyCameraMatrix. El método copyCameraMatrix escribe la matriz de perspectiva FLARParam en una matriz de estilo 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;
}

Integración de Three.js

Three.js es un conocido motor 3D de JavaScript. A continuación, se explicará cómo utilizar el resultado de JSARToolKit en Three.js. Necesitas tres cosas: un conjunto cuádruple de pantalla en el que esté dibujada la imagen de vídeo, una cámara con la matriz de perspectiva FLARParam y un objeto con la matriz de marcador para la transformación. En el código que se indica a continuación, se explica el proceso de integración.

Aquí puedes consultar un enlace a la demo Three.js donde se muestra su funcionamiento. Se han habilitado los mensajes de depuración, por lo que podrás ver parte del funcionamiento interno de la biblioteca 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);
}

Resumen

En este artículo se describen los aspectos básicos de JSARToolKit. Ahora ya estás preparado para crear tus propias aplicaciones de realidad aumentada mediante cámara web con JavaScript.

La integración de JSARToolKit en Three.js es un poco engorrosa, pero perfectamente posible. No estoy completamente seguro de si he seguido los pasos correctos en mi demo; por ello, si conoces una forma más adecuada de realizar la integración, házmelo saber. Se admiten correcciones :).

Referencias

Comments

0