Menulis aplikasi augmented reality menggunakan JSARToolKit

Ilmari Heikkinen

Pengantar

Artikel ini membahas penggunaan library JSARToolKit dengan getUserMedia API WebRTC untuk melakukan aplikasi augmented reality di web. Untuk rendering, saya menggunakan WebGL karena peningkatan kinerja yang ditawarkannya. Hasil akhir artikel ini adalah aplikasi demo yang menempatkan model 3D di atas penanda augmented reality di video webcam.

JSARToolKit adalah library augmented reality untuk JavaScript. Ini adalah library open source yang dirilis di bawah GPL dan port langsung dari FLARToolKit Flash yang saya buat untuk demo Realitas Remixing Mozilla. FLARToolKit sendiri adalah port dari NyARToolKit Java, yang merupakan port dari ARToolKit C. Masih sangat panjang, tapi ini di sini.

JSARToolKit beroperasi pada elemen kanvas. Karena perlu membaca gambar di luar kanvas, gambar harus berasal dari asal yang sama dengan halaman atau menggunakan CORS untuk memahami kebijakan asal yang sama. Singkatnya, tetapkan properti crossOrigin pada elemen gambar atau video yang ingin Anda gunakan sebagai tekstur ke '' atau 'anonymous'.

Ketika Anda meneruskan kanvas ke JSARToolKit untuk analisis, JSARToolKit menampilkan daftar penanda AR yang ditemukan dalam gambar dan matriks transformasi yang sesuai. Untuk menggambar objek 3D di atas penanda, Anda meneruskan matriks transformasi ke library rendering 3D apa pun yang digunakan sehingga objek Anda diubah menggunakan matriks. Kemudian, gambar bingkai video di adegan WebGL Anda dan gambar objek di atasnya dan Anda pun siap.

Untuk menganalisis video menggunakan JSARToolKit, gambar video di atas kanvas, lalu berikan kanvas ke JSARToolKit. Lakukan ini untuk setiap frame dan Anda akan mendapatkan pelacakan AR video. JSARToolKit cukup cepat pada mesin JavaScript modern untuk melakukan ini secara realtime bahkan pada frame video 640x480. Namun, makin besar frame video, makin lama waktu yang dibutuhkan untuk memprosesnya. Ukuran frame video yang baik adalah 320x240, tetapi jika Anda ingin menggunakan penanda kecil atau banyak penanda, sebaiknya gunakan 640x480.

Demo

Untuk melihat demo webcam, Anda harus mengaktifkan WebRTC di browser (di Chrome, buka about:flags dan aktifkan MediaStream). Anda juga harus mencetak penanda AR di bawah ini. Anda juga dapat mencoba membuka gambar penanda di ponsel atau tablet dan menampilkannya di webcam.

Penanda AR.
Penanda AR.

Menyiapkan JSARToolKit

JSARToolKit API cukup mirip Java, jadi Anda harus melakukan beberapa penyesuaian untuk menggunakannya. Ide dasarnya adalah Anda memiliki objek detektor yang beroperasi pada objek raster. Di antara detektor dan raster terdapat objek parameter kamera yang mengubah koordinat raster menjadi koordinat kamera. Untuk mendapatkan penanda yang terdeteksi dari detektor, Anda harus melakukan iterasi pada penanda tersebut dan menyalin matriks transformasinya ke kode Anda.

Langkah pertama adalah membuat objek raster, objek parameter kamera, dan objek detektor.

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

Menggunakan getUserMedia untuk mengakses webcam

Selanjutnya, saya akan membuat elemen video yang mendapatkan video webcam melalui API WebRTC. Untuk video yang direkam sebelumnya, cukup setel atribut sumber video ke URL video. Jika Anda melakukan deteksi penanda dari gambar diam, Anda dapat menggunakan elemen gambar dengan cara yang sama.

Karena WebRTC dan getUserMedia masih merupakan teknologi baru yang berkembang, Anda harus mendeteksi fitur tersebut. Untuk detail selengkapnya, lihat artikel Eric Bidelman tentang Merekam Audio & Video dalam 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.");
  }
);

Mendeteksi penanda

Setelah memastikan detektor berjalan dengan baik, kita dapat mulai memberinya gambar untuk mendeteksi matriks AR. Pertama, gambar gambar ke kanvas objek raster, lalu jalankan detektor pada objek raster. Pendeteksi menampilkan jumlah penanda yang ditemukan dalam gambar.

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

Langkah terakhir adalah melakukan iterasi melalui penanda yang terdeteksi dan mendapatkan matriks transformasinya. Anda menggunakan matriks transformasi untuk meletakkan objek 3D di atas penanda.

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

Pemetaan matriks

Berikut adalah kode untuk menyalin matriks JSARToolKit ke matriks glMatrix (yaitu FloatArrays 16 elemen dengan kolom terjemahan di empat elemen terakhir). Metode ini bekerja secara sulap (baca: Saya tidak tahu bagaimana matriks ARToolKit disiapkan. Sumbu Y terbalik adalah tebakan saya.) Bagaimanapun, potongan voodoo pembalik tanda ini membuat matriks JSARToolKit bekerja sama seperti glMatrix.

Untuk menggunakan library dengan library lain, seperti Three.js, Anda perlu menulis fungsi yang mengonversi matriks ARToolKit ke format matriks library. Anda juga perlu mengaitkan ke metode FLARParam.copyCameraMatrix. Metode copyCameraMatrix menulis matriks perspektif FLARParam ke dalam matriks bergaya 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;
}

Integrasi Three.js

Three.js adalah mesin 3D JavaScript yang populer. Saya akan membahas cara menggunakan {i>output<i} JSARToolKit di Three.js. Anda memerlukan tiga hal: segi empat layar penuh dengan gambar video yang digambar di atasnya, kamera dengan matriks perspektif FLARParam, dan objek dengan matriks penanda sebagai transformasinya. Saya akan memandu Anda melalui integrasi pada kode di bawah.

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

Ringkasan

Dalam artikel ini, kita membahas dasar-dasar JSARToolKit. Kini Anda siap untuk membangun webcam menggunakan aplikasi augmented reality dengan JavaScript.

Mengintegrasikan JSARToolKit dengan Three.js sedikit merepotkan, tetapi hal itu tentu saja dapat dilakukan. Saya tidak yakin 100% apakah saya melakukannya dengan benar dalam demo saya, jadi beri tahu saya jika Anda mengetahui cara yang lebih baik untuk mencapai integrasi. Anda boleh menggunakan patch :)

Referensi