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

เกริ่นนำ

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

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

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

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

ในการวิเคราะห์วิดีโอโดยใช้ JSARToolKit ให้วาดวิดีโอในผืนผ้าใบ แล้วส่ง Canvas ไปยัง 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 (ซึ่งก็คือ 16 องค์ประกอบ FloatArrays พร้อมคอลัมน์การแปลใน 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 เป็นเครื่องมือ JavaScript 3D ยอดนิยม ผมจะอธิบายวิธีใช้เอาต์พุต 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% ว่าตัวเองทำได้ถูกต้องในการสาธิตหรือไม่ ดังนั้นโปรดแจ้งให้เราทราบหากคุณรู้จักวิธีการผสานรวมที่ดีกว่า เรายินดีรับแพตช์ :)

รายการอ้างอิง