Augmented Reality-Anwendungen mithilfe von JSARToolKit schreiben

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.

Einführung

Dieser Artikel befasst sich mit der Verwendung der JSARToolKit-Bibliothek in Verbindung mit dem WebRTC getUserMedia-API zur Erstellung von Augmented Reality-Anwendungen im Web. Zum Rendern verwende ich aus Leistungsgründen WebGL. Das Endergebnis dieses Artikels ist eine Demo-Anwendung, die ein 3D-Modell auf einen Augmented Reality-Marker in einem Webcam-Video legt.

Das JSARToolKit ist eine Augmented Reality-Bibliothek für JavaScript. Hierbei handelt es sich um eine Open Source-Bibliothek, die im Rahmen der GPL lizenziert ist und das Pendant zum Flash FLARToolKit darstellt, das ich für die Remixing Reality-Demo für Mozilla erstellt habe. Das FLARToolKit gründet auf dem Java NyARToolKit, das wiederum aus dem C ARToolKit entstanden ist. Ein weiter Weg, aber hier sind wir nun.

Das JSARToolKit agiert auf der Basis von Canvas-Elementen. Da es das Bild aus dem Canvas auslesen muss, ist es erforderlich, dass das Bild aus derselben Quelle wie die Seite stammt oder CORS verwendet, um diese Richtlinie zu umgehen. Kurz gesagt: Legen Sie für die crossOrigin-Eigenschaft des Bild- oder Videoelements, das Sie als Textur verwenden möchten, '' oder 'anonymous' fest.

Wenn Sie ein Canvas zur Analyse an das JSARToolKit übergeben, gibt das JSARToolKit eine Liste mit im Bild gefundenen AR-Markern sowie den entsprechenden Transformationsmatrizen zurück. Um ein 3D-Objekt auf einen Marker zu zeichnen, übermitteln Sie die Transformationsmatrix an eine beliebige 3D-Rendering-Bibliothek, die Sie nutzen, damit Ihr Objekt auf der Grundlage dieser Matrix umgewandelt wird. Anschließend zeichnen Sie den Videoframe in Ihre WebGL-Szene und zeichnen das Objekt darüber. Fertig!

Zum Analysieren des Videos mithilfe des JSARToolKit zeichnen Sie das Video auf ein Canvas und übermitteln das Canvas anschließend an das JSARToolKit. Dies wiederholen Sie für jeden Frame und fertig ist das Video-AR-Tracking. Das JSARToolKit ist auf modernen JavaScript-Engines schnell genug, um diesen Vorgang auch für Videoframes im Format 640 x 480 in Echtzeit durchzuführen. Je größer der Videoframe, desto länger dauert der Vorgang jedoch. Eine gute Größe für einen Videoframe ist 320 x 240, wenn Sie jedoch wahrscheinlich kleine oder mehrere Marker verwenden, ist 640 x 480 empfehlenswert.

Demo

Um sich die Webcam-Demo ansehen zu können, muss WebRTC in Ihrem Browser aktiviert sein. In Chrome gehen Sie hierzu zu "about:flags" und aktivieren "MediaStream". Darüber hinaus müssen Sie den folgenden AR-Marker ausdrucken. Sie können auch versuchen, das Marker-Bild auf Ihrem Telefon oder Tablet zu öffnen und in die Webcam zu halten.

AR-Marker

In dieser Demo sehen Sie, was wir erreichen möchten. Dabei wird eine Diashow mithilfe von AR-Markern erstellt. Halten Sie einen Marker in die Kamera, um ein Foto darauf zu platzieren. Bewegen Sie den Marker aus dem Aufnahmebereich der Kamera und halten Sie ihn dann erneut hinein. Wie sie sehen, hat sich das Bild geändert.

AR-Webcam-Demo (falls WebRTC nicht verfügbar ist, finden Sie hier eine Version mit Muster-Videoinhalten)

JSARToolKit einrichten

Das JSARToolKit-API ist im Java-Stil gehalten. Daher müssen Sie zunächst einige Verzerrungen vornehmen, um es verwenden zu können. Die Grundidee ist, dass Sie ein Detektorobjekt haben, das als Rasterobjekt fungiert. Zwischen Detektor und Raster befindet sich ein Kameraparameterobjekt, das Rasterkoordinaten in Kamerakoordinaten umwandelt. Um die gefundenen Marker vom Detektor abzurufen, iterieren Sie darüber und kopieren die 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-Videoinhalte über die WebRTC-APIs erhält. Bei vorab aufgezeichneten Videos geben Sie einfach die Video-URL als Quellattribut des Videos an. Falls Sie eine Markererkennung auf der Basis von Standbildern durchführen, folgt die Verwendung eines Bildelements nahezu demselben Prinzip.

Da es sich bei WebRTC und getUserMedia um noch junge Technologien handelt, ist eine Funktionserkennung erforderlich. Weitere Informationen finden Sie im Artikel von Eric Bidelman zur Aufnahme von Audio- und Videoinhalten in 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.");
  }
);

Marker finden

Sobald der Detektor ordnungsgemäß funktioniert, können wir Bilder einspeisen, um AR-Matrizen zu finden. Zeichnen Sie das Bild zunächst auf das Rasterobjekt-Canvas und führen Sie anschließend den Detektor auf dem Rasterobjekt aus. Der Detektor gibt die Anzahl der im Bild gefundenen Marker 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);

Im letzten Schritt iterieren wir über die gefundenen Marker, um ihre Transformationsmatrizen zu erhalten. Mithilfe der Transformationsmatrizen platzieren Sie dann die 3D-Objekte auf den Markern.

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

Matrix-Zuordnung

Dies ist der Code, um JSARToolKit-Matrizen in glMatrix-Matrizen zu übertragen. Hierbei handelt es sich um FloatArrays bestehend aus 16 Elementen, wobei sich die Umrechnungsspalte aus den letzten 4 Elementen zusammensetzt. Es ist wie Magie (Ich weiß nicht, wie ARToolKit-Matrizen aufgebaut sind. Ich tippe auf eine umgekehrte y-Achse.) Auf jeden Fall funktioniert eine JSARToolKit-Matrix durch diese kleine magische Zeichenumkehrung genauso wie eine glMatrix.

Um die Bibliothek zusammen mit einer anderen Bibliothek wie Three.js zu verwenden, müssen Sie eine Funktion schreiben, die ARToolKit-Matrizen in das Matrixformat der Bibliothek umwandelt. Darüber hinaus müssen Sie auf die FLARParam.copyCameraMatrix-Methode zurückgreifen. Die copyCameraMatrix-Methode schreibt die FLARParam-Perspektivmatrix in eine Matrix im glMatrix-Stil um.

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

Integration von Three.js

Three.js ist eine beliebte JavaScript-3D-Engine. Ich zeige Ihnen jetzt, wie Sie die JSARToolKit-Ausgabe in Three.js verwenden können. Dazu benötigen Sie 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ühre ich Sie durch die Integration.

Hier finden Sie einen Link zur Three.js-Demo. Die Debugging-Ausgabe ist aktiviert, sodass Sie einen Einblick in die JSARToolKit-Bibliothek erhalten.

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

Zusammenfassung

In diesem Artikel habe ich Ihnen die Grundlagen des JSARToolKit nähergebracht. Nun können Sie Ihre eigenen webcambasierten Augmented Reality-Anwendungen mit JavaScript erstellen.

Die Integration des JSARToolKit mit Three.js ist ein wenig mühsam, aber sicher möglich. Ich bin mir nicht absolut sicher, ob die Vorgehensweise in meiner Demo richtig ist, falls Sie also einen besseren Weg im Hinblick auf die Integration kennen, lassen Sie es mich wissen. Patches sind willkommen :)

Referenzen

Comments

0