Viết các ứng dụng thực tế tăng cường bằng JSARToolKit

Ilmari Heikkinen

Giới thiệu

Bài viết này trình bày cách sử dụng thư viện JSARToolKit với API getUserMedia WebRTC để tạo ứng dụng thực tế tăng cường trên web. Để kết xuất, tôi đang sử dụng WebGL do hiệu suất mà công nghệ này mang lại. Kết quả cuối cùng của bài viết này là một ứng dụng minh hoạ đặt mô hình 3D lên trên điểm đánh dấu thực tế tăng cường trong video webcam.

JSARToolKit là một thư viện thực tế tăng cường dành cho JavaScript. Đây là một thư viện nguồn mở được phát hành theo GPL và là một cổng trực tiếp của FLARToolKit Flash mà tôi đã tạo cho bản minh hoạ Remixing Reality của Mozilla. Bản thân FLARToolKit là cổng của NyARToolKit Java, đây là cổng của ARToolKit C. Đã đi một chặng đường dài, nhưng chúng ta đã đến được đây.

JSARToolKit hoạt động trên các phần tử canvas. Vì cần đọc hình ảnh trên canvas, nên hình ảnh cần phải đến từ cùng một nguồn gốc với trang hoặc sử dụng CORS để tránh chính sách về cùng một nguồn gốc. Tóm lại, hãy đặt thuộc tính crossOrigin trên phần tử hình ảnh hoặc video mà bạn muốn sử dụng làm hoạ tiết thành '' hoặc 'anonymous'.

Khi bạn truyền một canvas đến JSARToolKit để phân tích, JSARToolKit sẽ trả về danh sách các điểm đánh dấu AR tìm thấy trong hình ảnh và các ma trận biến đổi tương ứng. Để vẽ một đối tượng 3D trên đầu một điểm đánh dấu, bạn truyền ma trận biến đổi đến bất kỳ thư viện kết xuất 3D nào mà bạn đang sử dụng để đối tượng của bạn được biến đổi bằng ma trận đó. Sau đó, hãy vẽ khung video trong cảnh WebGL và vẽ đối tượng lên trên đó.

Để phân tích video bằng JSARToolKit, hãy vẽ video trên canvas, sau đó truyền canvas đó đến JSARToolKit. Hãy thực hiện việc này cho mọi khung hình để có tính năng theo dõi AR trong video. JSARToolKit đủ nhanh trên các công cụ JavaScript hiện đại để thực hiện việc này theo thời gian thực ngay cả trên các khung hình video 640x480. Tuy nhiên, khung video càng lớn thì thời gian xử lý càng lâu. Kích thước khung hình video phù hợp là 320x240, nhưng nếu bạn dự định sử dụng các điểm đánh dấu nhỏ hoặc nhiều điểm đánh dấu, thì bạn nên sử dụng kích thước 640x480.

Bản minh hoạ

Để xem bản minh hoạ về webcam, bạn cần bật WebRTC trong trình duyệt (trên Chrome, hãy chuyển đến about:flags rồi bật MediaStream). Bạn cũng cần in điểm đánh dấu AR bên dưới. Bạn cũng có thể thử mở hình ảnh điểm đánh dấu trên điện thoại hoặc máy tính bảng và hiển thị hình ảnh đó cho webcam.

Điểm đánh dấu AR.
Máy đánh dấu thực tế tăng cường.

Thiết lập JSARToolKit

API JSARToolKit khá giống với Java, vì vậy, bạn sẽ phải thực hiện một số thao tác để sử dụng API này. Ý tưởng cơ bản là bạn có một đối tượng phát hiện hoạt động trên một đối tượng đường quét. Giữa bộ phát hiện và đường quét là một đối tượng tham số máy ảnh biến đổi toạ độ đường quét thành toạ độ máy ảnh. Để lấy các điểm đánh dấu được phát hiện từ trình phát hiện, bạn lặp lại các điểm đánh dấu đó và sao chép ma trận biến đổi của các điểm đánh dấu đó vào mã của mình.

Bước đầu tiên là tạo đối tượng đường quét, đối tượng thông số máy ảnh và đối tượng trình phát hiện.

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

Sử dụng getUserMedia để truy cập vào webcam

Tiếp theo, tôi sẽ tạo một phần tử video nhận video từ webcam thông qua API WebRTC. Đối với video được quay trước, bạn chỉ cần đặt thuộc tính nguồn của video thành URL video. Nếu đang phát hiện điểm đánh dấu từ hình ảnh tĩnh, bạn có thể sử dụng phần tử hình ảnh theo cách tương tự.

Vì WebRTC và getUserMedia vẫn là những công nghệ mới nổi, nên bạn cần phát hiện các công nghệ này. Để biết thêm thông tin chi tiết, hãy xem bài viết của Eric Bidelman về Quay video và âm thanh trong 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.");
  }
);

Phát hiện điểm đánh dấu

Sau khi trình phát hiện hoạt động ổn định, chúng ta có thể bắt đầu cung cấp hình ảnh cho trình phát hiện để phát hiện ma trận AR. Trước tiên, hãy vẽ hình ảnh lên canvas đối tượng đường quét, sau đó chạy trình phát hiện trên đối tượng đường quét. Trình phát hiện trả về số lượng điểm đánh dấu tìm thấy trong hình ảnh.

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

Bước cuối cùng là lặp lại các điểm đánh dấu đã phát hiện và lấy ma trận biến đổi của các điểm đánh dấu đó. Bạn sử dụng ma trận biến đổi để đặt các đối tượng 3D lên trên các điểm đánh dấu.

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

Ánh xạ ma trận

Dưới đây là mã để sao chép các ma trận JSARToolKit sang các ma trận glMatrix (là FloatArrays gồm 16 phần tử với cột dịch trong 4 phần tử cuối cùng). Cách này hoạt động một cách kỳ diệu (tức là tôi không biết cách thiết lập ma trận ARToolKit. Tôi đoán là trục Y bị đảo ngược.) Dù sao, chút ma thuật đảo ngược dấu này cũng khiến ma trận JSARToolKit hoạt động giống như glMatrix.

Để sử dụng thư viện này với một thư viện khác, chẳng hạn như Three.js, bạn cần viết một hàm chuyển đổi các ma trận ARToolKit thành định dạng ma trận của thư viện. Bạn cũng cần nối vào phương thức FLARParam.copyCameraMatrix. Phương thức copyCameraMatrix ghi ma trận phối cảnh FLARParam vào ma trận kiểu 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;
}

Tích hợp Three.js

Three.js là một công cụ 3D JavaScript phổ biến. Tôi sẽ hướng dẫn cách sử dụng đầu ra JSARToolKit trong Three.js. Bạn cần có 3 thứ: một hình tứ giác toàn màn hình có hình ảnh video được vẽ lên đó, một máy ảnh có ma trận phối cảnh FLARParam và một đối tượng có ma trận điểm đánh dấu làm phép biến đổi. Tôi sẽ hướng dẫn bạn cách tích hợp trong mã dưới đây.

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

Tóm tắt

Trong bài viết này, chúng ta đã tìm hiểu các kiến thức cơ bản về JSARToolKit. Giờ đây, bạn đã sẵn sàng tạo ứng dụng thực tế tăng cường bằng JavaScript sử dụng webcam của riêng mình.

Việc tích hợp JSARToolKit với Three.js có chút phiền phức, nhưng chắc chắn là có thể. Tôi không chắc chắn 100% liệu mình có thực hiện đúng trong bản minh hoạ hay không. Vì vậy, vui lòng cho tôi biết nếu bạn biết cách tích hợp hiệu quả hơn. Chúng tôi hoan nghênh các bản vá :)

Tài liệu tham khảo