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 để triển khai các ứ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à WebGL cung cấp tăng lên. 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 một điểm đánh dấu thực tế tăng cường trong video webcam.

JSARToolKit là 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 của Java, đây là một cổng của ARToolKit C. Rất lâu, nhưng chúng ta đã ở đây.

JSARToolKit hoạt động trên các phần tử canvas. Vì cần đọc hình ảnh bên ngoài canvas, nên hình ảnh phải có cùng nguồn gốc với trang hoặc sử dụng CORS để tuân thủ chính sách cùng 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 chuyể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 có trong hình ảnh và các ma trận biến đổi tương ứng. Để vẽ đối tượng 3D lên trên điểm đánh dấu, bạn phải chuyển ma trận biến đổi sang 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 hình video trong cảnh WebGL của bạn và vẽ đối tượng trên ảnh đó và thế là xong.

Để phân tích video bằng JSARToolKit, hãy vẽ video đó trên canvas, sau đó chuyển canvas đó đến JSARToolKit. Hãy làm việc này cho mọi khung hình và bạn sẽ có tính năng theo dõi video thực tế tăng cường. 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 khung hình video 640x480. Tuy nhiên, khung hình video càng lớn thì càng mất nhiều thời gian để xử lý. Kích thước khung hình video tốt là 320x240, nhưng nếu bạn muốn sử dụng các điểm đánh dấu nhỏ hoặc nhiều điểm đánh dấu, bạn nên sử dụng 640x480.

Bản minh hoạ

Để xem bản minh hoạ webcam, bạn cần bật WebRTC trong trình duyệt (trên Chrome, hãy truy cập vào 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à đưa nó cho webcam.

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

Thiết lập JSARToolKit

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

Bước đầu tiên là tạo đối tượng đường quét, đối tượng tham 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 webcam

Tiếp theo, tôi sẽ tạo một phần tử video sẽ nhận video 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 là URL của video. Nếu bạn đang phát hiện điểm đánh dấu từ các 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à các công nghệ mới nổi nên bạn cần phải phát hiện tính năng của các công nghệ này. Để biết thêm chi tiết, hãy xem bài viết của Eric Bidelman về Thu thập âm thanh và video 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 bình thường, 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, vẽ hình ảnh vào 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 sẽ trả về số điểm đánh dấu có 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. Bạn sẽ sử dụng các ma trận biến đổi để đặt các đối tượng 3D lên trên đ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

Đây là mã để sao chép các ma trận JSARToolKit sang 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). Nó hoạt động kỳ diệu (đọc: Tôi không biết cách thiết lập ma trận ARToolKit. Tôi đoán là trục Y đảo ngược.) Dù sao thì voodoo đảo ngược dấu hiệu này 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 ma trận ARToolKit thành định dạng ma trận của thư viện. Bạn cũng cần kết nối với phương thức FLARParam.copyCameraMatrix. Phương thức copyCameraMatrix ghi ma trận phối cảnh FLARParam thành 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ụ JavaScript 3D phổ biến. Tôi sẽ tìm hiểu cách sử dụng đầu ra JSARToolKit trong Three.js. Bạn cần ba thứ: một 4 khung hình toàn màn hình với hình ảnh video được vẽ trê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 biến đổi. Tôi sẽ hướng dẫn bạn về quy trình tích hợp trong đoạn mã bên dưới.

// 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 khái niệm cơ bản về JSARToolKit. Giờ đây, bạn đã sẵn sàng xây dựng các ứng dụng thực tế tăng cường với JavaScript của riêng mình bằng webcam.

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

Tài liệu tham khảo