Augmented-Reality-Anwendungen mit dem JSARToolKit schreiben

Ilmari Heikkinen

Einleitung

In diesem Artikel geht es um die Verwendung der JSARToolKit-Bibliothek mit der getUserMedia API WebRTC für Augmented Reality-Anwendungen im Web. Zum Rendern verwende ich aufgrund der besseren Leistung WebGL. Das Endergebnis dieses Artikels ist eine Demo-App, die ein 3D-Modell auf eine Augmented-Reality-Markierung in einem Webcam-Video setzt.

Das JSARToolKit ist eine Augmented-Reality-Bibliothek für JavaScript. Dabei handelt es sich um eine Open-Source-Bibliothek, die unter der GPL veröffentlicht wird und die direkt vom FLARToolKit für Flash unterstützt wird, das ich für die Remixing Reality-Demo von Mozilla erstellt habe. Das FLARToolKit selbst ist der Port des Java NyARToolKit, das wiederum ein Port des C ARToolKit ist. Ein weiter Weg, aber hier sind wir.

Das JSARToolKit agiert auf Canvas-Elementen. Da das Bild aus dem Canvas gelesen werden muss, muss das Bild aus demselben Ursprung wie die Seite stammen oder CORS verwenden, um diese Richtlinie zu umgehen. Kurz gesagt: Legen Sie die crossOrigin-Eigenschaft des Bild- oder Videoelements, das Sie als Textur verwenden möchten, auf '' oder 'anonymous' fest.

Wenn Sie ein Canvas zur Analyse an das JSARToolKit übergeben, gibt das JSARToolKit eine Liste mit den im Bild gefundenen AR-Markierungen und den entsprechenden Transformationsmatrizen zurück. Um ein 3D-Objekt auf einem Marker zu zeichnen, übergeben Sie die Transformationsmatrix an die von Ihnen verwendete 3D-Rendering-Bibliothek, sodass Ihr Objekt entsprechend dieser Matrix transformiert wird. Zeichnen Sie dann den Videoframe in Ihre WebGL-Szene und zeichnen Sie das Objekt darüber. Fertig!

Um Videos mithilfe des JSARToolKit zu analysieren, zeichnen Sie das Video auf ein Canvas und übergeben Sie es dann an das JSARToolKit. Führen Sie dies für jeden Frame durch und Sie haben ein AR-Video-Tracking. Das JSARToolKit ist auf modernen JavaScript-Engines schnell genug, um dies in Echtzeit selbst bei Videoframes von 640 x 480 zu tun. Je größer der Videoframe, desto länger dauert die Verarbeitung jedoch. Eine gute Größe eines Videoframes ist 320 x 240, wenn Sie jedoch kleine oder mehrere Markierungen verwenden, ist 640 x 480 die bessere Wahl.

Demo

Um sich die Webcam-Demo ansehen zu können, muss WebRTC in Ihrem Browser aktiviert sein. In Chrome müssen Sie hierzu „about:flags“ aufrufen und MediaStream aktivieren. Außerdem müssen Sie den unten stehenden AR-Marker ausdrucken. Sie können auch versuchen, das Markierungsbild auf Ihrem Smartphone oder Tablet zu öffnen und in die Webcam zu zeigen.

AR-Markierung
AR-Markierung.

JSARToolKit einrichten

Das JSARToolKit-API ist im Java-Stil gehalten, daher sind einige Verzerrungen erforderlich, um es verwenden zu können. Die Grundidee ist, dass Sie ein Detektorobjekt haben, das auf einem Rasterobjekt basiert. Zwischen Detektor und Raster befindet sich ein Kameraparameterobjekt, das Rasterkoordinaten in Kamerakoordinaten umwandelt. Um die erkannten Marker vom Detektor zu erhalten, iterieren Sie über sie und kopieren ihre Transformationsmatrizen in Ihren Code.

Im ersten Schritt erstellen Sie das Rasterobjekt, das Kameraparameterobjekt und das Detektorobjekt.

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

Mit getUserMedia auf die Webcam zugreifen

Als Nächstes erstelle ich ein Videoelement, das Webcam-Videos über die WebRTC-APIs sendet. Bei vorab aufgezeichneten Videos geben Sie einfach die Video-URL als Quellattribut des Videos an. Bei der Markierungserkennung auf Grundlage von Standbildern können Sie ein Bildelement ähnlich verwenden.

Da es sich bei WebRTC und getUserMedia noch um neue Technologien handelt, müssen Sie eine Funktionserkennung vornehmen. Weitere Informationen finden Sie im Artikel Aufnahmen von Audio und Video in HTML5 von Eric Bidelman.

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.");
  }
);

Markierungen erkennen

Sobald der Detektor funktioniert, können wir Bilder einspeisen, um AR-Matrizen zu erkennen. Zeichnen Sie das Bild zuerst auf das Rasterobjekt-Canvas und führen Sie dann den Detektor auf dem Rasterobjekt aus. Der Detektor gibt die Anzahl der im Bild gefundenen Markierungen zurück.

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

Der letzte Schritt besteht darin, über die erkannten Marker zu iterieren und ihre Transformationsmatrizen zu erhalten. Mithilfe der Transformationsmatrizen legen Sie 3D-Objekte auf die Markierungen.

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

Matrixzuordnung

Dies ist der Code für das Kopieren von JSARToolKit-Matrizen in glMatrix-Matrizen (FloatArrays mit 16 Elementen und der Übersetzungsspalte in den letzten vier Elementen). Es ist wie Magie (Ich weiß nicht, wie ARToolKit-Matrizen aufgebaut sind. umgekehrte Y-Achse ist meine Vermutung.) Wie auch immer, dieses kleine Voodoo zur Vorzeichenumkehr sorgt dafür, dass eine JSARToolKit-Matrix genauso funktioniert wie eine glMatrix.

Um die Bibliothek mit einer anderen Bibliothek wie Three.js zu verwenden, müssen Sie eine Funktion schreiben, die die ARToolKit-Matrizen in das Matrixformat der Bibliothek konvertiert. Sie müssen sich auch in die Methode FLARParam.copyCameraMatrix einarbeiten. Die copyCameraMatrix-Methode schreibt die FLARParam-Perspektivmatrix in eine Matrix im glMatrix-Stil.

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-Integration

Three.js ist eine beliebte JavaScript-3D-Engine. Ich zeige Ihnen jetzt, wie Sie die JSARToolKit-Ausgabe in Three.js verwenden. Sie benötigen drei Dinge: ein Vollbild-Quad mit dem Videobild darauf, eine Kamera mit der FLARParam-Perspektivmatrix und ein Objekt mit Markermatrix als dessen Transformation. Im folgenden Code führen wir Sie durch die Integration.

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

Zusammenfassung

In diesem Artikel haben wir die Grundlagen des JSARToolKit erläutert. Jetzt sind Sie bereit, mithilfe von JavaScript Ihre eigenen Augmented-Reality-Anwendungen zu erstellen, die eine Webcam verwenden.

Die Integration des JSARToolKit in Three.js ist ein wenig mühsam, aber sicher möglich. Ich bin mir nicht zu 100% sicher, ob ich das in meiner Demo richtig mache, also lassen Sie mich wissen, wenn Sie eine bessere Methode für die Integration kennen. Patches sind willkommen

Verweise