การเขียนแอปพลิเคชัน Augmented Reality โดยใช้ JSARToolKit

บทนำ

บทความนี้เกี่ยวข้องกับการใช้ไลบรารี JSARToolKit กับ WebRTC getUserMedia API เพื่อสร้างแอปพลิเคชันความจริงเสริมบนเว็บ เราใช้ WebGL ในการเรนเดอร์เนื่องจากมีประสิทธิภาพมากขึ้น ผลลัพธ์สุดท้ายของบทความนี้คือแอปพลิเคชันสาธิตที่วางโมเดล 3 มิติไว้บนเครื่องหมาย Augmented Reality ในวิดีโอจากเว็บแคม

JSARToolKit เป็นไลบรารี Augmented Reality สําหรับ JavaScript ซึ่งเป็นไลบรารีโอเพนซอร์สที่เผยแพร่ภายใต้ GPL และพอร์ตโดยตรงของ FLARToolKit ของ Flash ที่เราสร้างขึ้นสำหรับการสาธิตการรีมิกซ์ความเป็นจริงของ Mozilla FLARToolKit เองก็เป็นพอร์ตของ NyARToolKit เวอร์ชัน Java ซึ่งเป็นพอร์ตของ ARToolKit เวอร์ชัน C เส้นทางนี้ยาวไกล แต่เรามาถึงแล้ว

JSARToolKit ทำงานกับองค์ประกอบ Canvas เนื่องจากต้องอ่านรูปภาพจากผืนผ้าใบ รูปภาพจึงต้องมาจากแหล่งที่มาเดียวกับหน้าเว็บหรือใช้ CORS เพื่อหลีกเลี่ยงนโยบายแหล่งที่มาเดียวกัน กล่าวโดยย่อคือ ให้ตั้งค่าพร็อพเพอร์ตี้ crossOrigin ในองค์ประกอบรูปภาพหรือวิดีโอที่ต้องการใช้เป็นพื้นผิวเป็น '' หรือ 'anonymous'

เมื่อคุณส่งภาพพิมพ์แคนวาสไปยัง JSARToolKit เพื่อการวิเคราะห์ JSARToolKit จะแสดงรายการเครื่องหมาย AR ที่พบในรูปภาพและเมทริกซ์การเปลี่ยนรูปแบบที่เกี่ยวข้อง หากต้องการวาดวัตถุ 3 มิติบนเครื่องหมาย ให้ส่งเมทริกซ์การเปลี่ยนรูปแบบไปยังคลังการแสดงผล 3 มิติที่คุณใช้อยู่เพื่อให้ระบบเปลี่ยนรูปแบบวัตถุโดยใช้เมทริกซ์ จากนั้นวาดเฟรมวิดีโอในฉาก WebGL และวาดวัตถุทับบนเฟรมวิดีโอดังกล่าว

หากต้องการวิเคราะห์วิดีโอโดยใช้ JSARToolKit ให้วาดวิดีโอบนผืนผ้าใบ แล้วส่งผืนผ้าใบไปยัง JSARToolKit ทำเช่นนี้กับทุกเฟรม คุณก็จะได้การติดตาม AR ของวิดีโอ JSARToolKit ทำงานได้เร็วพอในเครื่องมือ JavaScript สมัยใหม่ที่จะทำสิ่งนี้แบบเรียลไทม์ได้แม้ในเฟรมวิดีโอขนาด 640x480 อย่างไรก็ตาม ยิ่งเฟรมวิดีโอมีขนาดใหญ่เท่าใด ก็ยิ่งใช้เวลาในการประมวลผลนานขึ้นเท่านั้น ขนาดเฟรมวิดีโอที่เหมาะสมคือ 320x240 แต่หากต้องการใช้เครื่องหมายขนาดเล็กหรือเครื่องหมายหลายรายการ ก็ควรใช้ขนาด 640x480

สาธิต

หากต้องการดูการสาธิตเว็บแคม คุณต้องเปิดใช้ WebRTC ในเบราว์เซอร์ (ใน Chrome ให้ไปที่ about:flags แล้วเปิดใช้ MediaStream) นอกจากนี้ คุณยังต้องพิมพ์เครื่องหมาย AR ด้านล่างด้วย หรือจะลองเปิดรูปเครื่องหมายในโทรศัพท์หรือแท็บเล็ตแล้วแสดงต่อเว็บแคมก็ได้

เครื่องหมาย AR
เครื่องหมาย AR

การตั้งค่า JSARToolKit

JSARToolKit API มีลักษณะคล้ายกับ Java คุณจึงอาจต้องปรับให้เข้ากับการใช้งาน แนวคิดพื้นฐานคือคุณมีออบเจ็กต์เครื่องตรวจจับที่ทำงานกับออบเจ็กต์แรสเตอร์ ระหว่างเครื่องตรวจจับกับแรสเตอร์คือออบเจ็กต์พารามิเตอร์กล้องที่จะเปลี่ยนพิกัดแรสเตอร์เป็นพิกัดกล้อง หากต้องการรับเครื่องหมายที่ตรวจพบจากเครื่องตรวจจับ ให้วนซ้ำเครื่องหมายเหล่านั้นและคัดลอกเมทริกซ์การเปลี่ยนรูปแบบไปยังโค้ด

ขั้นตอนแรกคือการสร้างออบเจ็กต์แรสเตอร์ ออบเจ็กต์พารามิเตอร์กล้อง และออบเจ็กต์เครื่องตรวจจับ

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

การใช้ getUserMedia เพื่อเข้าถึงเว็บแคม

ต่อไป เราจะสร้างองค์ประกอบวิดีโอที่รับวิดีโอจากเว็บแคมผ่าน WebRTC API สำหรับวิดีโอที่บันทึกไว้ล่วงหน้า ให้ตั้งค่าแอตทริบิวต์แหล่งที่มาของวิดีโอเป็น URL ของวิดีโอ หากตรวจหาเครื่องหมายจากภาพนิ่ง คุณจะใช้องค์ประกอบรูปภาพในลักษณะเดียวกันได้

เนื่องจาก WebRTC และ getUserMedia ยังเป็นเทคโนโลยีใหม่ คุณจึงต้องตรวจหาฟีเจอร์เหล่านี้ ดูรายละเอียดเพิ่มเติมได้ที่บทความของ Eric Bidelman เรื่องการบันทึกเสียงและวิดีโอใน 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.");
  }
);

การตรวจหาเครื่องหมาย

เมื่อเครื่องตรวจจับทำงานได้อย่างถูกต้องแล้ว เราจะเริ่มป้อนรูปภาพให้เพื่อตรวจหาเมตริก AR ก่อนอื่นให้วาดรูปภาพลงใน Canvas ของออบเจ็กต์แรสเตอร์ จากนั้นเรียกใช้เครื่องตรวจจับบนออบเจ็กต์แรสเตอร์ ตัวตรวจจับจะแสดงจํานวนเครื่องหมายที่พบในรูปภาพ

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

ขั้นตอนสุดท้ายคือ วนดูเครื่องหมายที่ตรวจพบและรับเมทริกซ์การเปลี่ยนรูปแบบ คุณใช้เมทริกซ์การเปลี่ยนรูปแบบเพื่อวางวัตถุ 3 มิติไว้บนเครื่องหมาย

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

การแมปเมตริกซ์

ต่อไปนี้คือโค้ดสําหรับคัดลอกเมทริกซ์ JSARToolKit ไปยังเมทริกซ์ glMatrix (ซึ่งเป็น FloatArrays 16 องค์ประกอบที่มีคอลัมน์การแปลในองค์ประกอบ 4 รายการสุดท้าย) ทำงานด้วยเวทมนตร์ (อ่านว่า ฉันไม่รู้วิธีตั้งค่าเมทริกซ์ ARToolKit เราเดาว่าแกน Y กลับหัว) อย่างไรก็ตาม การเปลี่ยนเครื่องหมายของบิตนี้ทําให้เมทริกซ์ JSARToolKit ทํางานเหมือนกับ glMatrix

หากต้องการใช้ไลบรารีนี้กับไลบรารีอื่น เช่น Three.js คุณต้องเขียนฟังก์ชันที่แปลงเมทริกซ์ ARToolKit เป็นรูปแบบเมทริกซ์ของไลบรารี นอกจากนี้ คุณยังต้องเชื่อมต่อกับเมธอด FLARParam.copyCameraMatrix ด้วย เมธอด copyCameraMatrix จะเขียนเมทริกซ์มุมมอง FLARParam ลงในเมทริกซ์สไตล์ 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;
}

การผสานรวม Three.js

Three.js เป็นเครื่องมือ 3 มิติยอดนิยมของ JavaScript เราจะอธิบายวิธีใช้เอาต์พุต JSARToolKit ใน Three.js คุณต้องมี 3 อย่าง ได้แก่ รูปสี่เหลี่ยมจัตุรัสแบบเต็มหน้าจอที่มีภาพวิดีโอวาดอยู่ กล้องที่มีเมทริกซ์มุมมอง FLARParam และออบเจ็กต์ที่มีเมทริกซ์เครื่องหมายเป็นการเปลี่ยนรูปแบบ เราจะอธิบายการผสานรวมในโค้ดด้านล่าง

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

สรุป

ในบทความนี้ เราได้อธิบายข้อมูลเบื้องต้นเกี่ยวกับ JSARToolKit ตอนนี้คุณก็พร้อมสร้างแอปพลิเคชัน Augmented Reality ที่ใช้เว็บแคมของคุณเองด้วย JavaScript แล้ว

การผสานรวม JSARToolKit กับ Three.js นั้นค่อนข้างยุ่งยาก แต่ก็เป็นไปได้ เราไม่แน่ใจ 100% ว่าทําถูกในเดโมหรือไม่ โปรดแจ้งให้เราทราบหากคุณมีวิธีผสานรวมที่ดีกว่า ยินดีรับการแก้ไข :)

ข้อมูลอ้างอิง