Augmented-Reality-Anwendungen mit dem JSARToolKit schreiben

Ilmari Heikkinen

Einführung

In diesem Artikel erfahren Sie, wie Sie die JSARToolKit-Bibliothek mit der getUserMedia API von WebRTC verwenden, um Augmented-Reality-Anwendungen im Web zu erstellen. Für das Rendering verwende ich WebGL, da es eine höhere Leistung bietet. Das Endergebnis dieses Artikels ist eine Demoanwendung, die ein 3D‑Modell in einem Webcam-Video über einer Augmented-Reality-Markierung platziert.

JSARToolKit ist eine AR-Bibliothek für JavaScript. Es ist eine Open-Source-Bibliothek, die unter der GPL veröffentlicht wurde, und ein direkter Port des Flash-FLARToolKit, das ich für die Remixing Reality-Demo von Mozilla erstellt habe. FLARToolKit selbst ist eine Portierung des Java-NyARToolKit, das wiederum eine Portierung des C-ARToolKit ist. Es war ein langer Weg, aber wir sind da.

JSARToolKit arbeitet mit Canvas-Elementen. Da das Bild vom Canvas gelesen werden muss, muss es aus derselben Quelle wie die Seite stammen oder CORS verwendet werden, um die Same-Origin-Richtlinie zu umgehen. Kurz gesagt: Legen Sie für das Bild- oder Videoelement, das Sie als Textur verwenden möchten, die crossOrigin-Eigenschaft auf '' oder 'anonymous' fest.

Wenn Sie einen Canvas zur Analyse an JSARToolKit übergeben, gibt JSARToolKit eine Liste der AR-Markierungen im Bild und die entsprechenden Transformationsmatrizen zurück. Wenn Sie ein 3D‑Objekt auf einer Markierung zeichnen möchten, übergeben Sie die Transformationsmatrix an die von Ihnen verwendete 3D‑Rendering-Bibliothek, damit Ihr Objekt mithilfe der Matrix transformiert wird. Zeichnen Sie dann den Videoframe in Ihrer WebGL-Szene und das Objekt darüber.

Wenn Sie ein Video mit dem JSARToolKit analysieren möchten, zeichnen Sie das Video auf einem Canvas und übergeben Sie den Canvas dann an das JSARToolKit. Wenn Sie das für jeden Frame tun, erhalten Sie AR-Tracking in Videos. JSARToolKit ist auf modernen JavaScript-Engines schnell genug, um dies auch bei 640 × 480 Pixel großen Videoframes in Echtzeit zu tun. Je größer der Videoframe ist, desto länger dauert die Verarbeitung. Eine gute Videoframe-Größe ist 320 × 240. Wenn Sie jedoch kleine oder mehrere Markierungen verwenden möchten, ist 640 × 480 vorzuziehen.

Demo

Damit Sie die Webcam-Demo sehen können, muss WebRTC in Ihrem Browser aktiviert sein. In Chrome können Sie unter about:flags MediaStream aktivieren. Außerdem müssen Sie die AR-Markierung unten ausdrucken. Sie können auch versuchen, das Markierungsbild auf Ihrem Smartphone oder Tablet zu öffnen und es der Webcam zu zeigen.

AR-Markierung
AR-Markierung.

JSARToolKit einrichten

Die JSARToolKit API ist ziemlich Java-ähnlich, sodass Sie einige Umwege gehen müssen, um sie zu verwenden. Die Grundidee ist, dass Sie ein Detektorobjekt haben, das auf einem Rasterobjekt ausgeführt wird. Zwischen dem Detektor und dem Raster befindet sich ein Kameraparameterobjekt, das Rasterkoordinaten in Kamerakoordinaten umwandelt. Um die erkannten Markierungen vom Detector abzurufen, iterieren Sie sie und kopieren ihre Transformationsmatrizen in Ihren Code.

Als Erstes 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);

Über getUserMedia auf die Webcam zugreifen

Als Nächstes erstelle ich ein Videoelement, das Webcam-Videos über die WebRTC APIs empfängt. Bei vorab aufgezeichneten Videos müssen Sie nur das Quellattribut des Videos auf die Video-URL festlegen. Wenn Sie Markierungen in Standbildern erkennen möchten, können Sie ein Bildelement auf ähnliche Weise verwenden.

Da WebRTC und getUserMedia noch neue Technologien sind, müssen Sie sie erkennen. Weitere Informationen finden Sie im Artikel Audio- und Videoaufnahmen 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 einwandfrei funktioniert, können wir ihm Bilder zur Erkennung von AR-Matrizen zuführen. Zeichnen Sie zuerst das Bild auf das Rasterobjekt-Canvas und führen Sie dann den Detector auf dem Rasterobjekt aus. Der Detector gibt die Anzahl der Markierungen zurück, die im Bild gefunden wurden.

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

Im letzten Schritt werden die erkannten Markierungen durchgegangen und ihre Transformationsmatrizen abgerufen. Mit den Transformationsmatrizen können Sie 3D-Objekte über die Markierungen legen.

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

Hier ist der Code zum Kopieren von JSARToolKit-Matrizen in glMatrix-Matrizen (FloatArrays mit 16 Elementen mit der Translationsspalte in den letzten vier Elementen). Es funktioniert magisch (d. h. ich weiß nicht, wie ARToolKit-Matrizen eingerichtet werden). Ich vermute, dass die Y-Achse invertiert ist.) Auf jeden Fall funktioniert eine JSARToolKit-Matrix mit diesem kleinen Zauber zur Umkehrung des Vorzeichens genauso wie eine glMatrix.

Wenn Sie die Bibliothek mit einer anderen Bibliothek wie Three.js verwenden möchten, müssen Sie eine Funktion schreiben, die die ARToolKit-Matrizen in das Matrixformat der Bibliothek konvertiert. Außerdem müssen Sie die Methode FLARParam.copyCameraMatrix verknüpfen. Die Methode „copyCameraMatrix“ schreibt die FLARParam-Perspektivmatrix in eine glMatrix-Matrix.

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, wie Sie die JSARToolKit-Ausgabe in Three.js verwenden. Sie benötigen drei Dinge: ein Vollbild-Quad mit dem darauf gezeichneten Videobild, eine Kamera mit der FLARParam-Perspektivmatrix und ein Objekt mit der Markierungsmatrix als Transformation. Im Code unten zeige ich Ihnen 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 von JSARToolKit behandelt. Jetzt können Sie mit JavaScript eigene Augmented-Reality-Anwendungen mit Webcam erstellen.

Die Integration von JSARToolKit in Three.js ist etwas mühsam, aber durchaus möglich. Ich bin mir nicht sicher, ob ich das in meiner Demo richtig mache. Bitte lassen Sie es mich wissen, falls Sie eine bessere Möglichkeit zur Integration kennen. Korrekturen sind willkommen :)

Verweise