JSARToolKit를 사용하여 증강 현실 애플리케이션 작성

Ilmari Heikkinen

소개

이 도움말에서는 WebRTC getUserMedia API와 함께 JSARToolKit 라이브러리를 사용하여 웹에서 증강 현실 애플리케이션을 실행하는 방법을 설명합니다. 렌더링의 경우 성능이 향상되어 WebGL을 사용하고 있습니다. 이 도움말의 최종 결과물은 웹캠 동영상의 증강 현실 마커 위에 3D 모델을 배치하는 데모 애플리케이션입니다.

JSARToolKit은 JavaScript용 증강 현실 라이브러리입니다. GPL에 따라 출시된 오픈소스 라이브러리이며 Mozilla 리믹스 현실 데모를 위해 만든 Flash FLARToolKit의 직접 포팅입니다. FLARToolKit 자체는 C ARToolKit의 포팅인 Java NyARToolKit의 포팅입니다. 갈 길은 멀지만 다행히 도착했습니다.

JSARToolKit은 캔버스 요소에서 작동합니다. 캔버스에서 이미지를 읽어야 하므로 이미지가 페이지와 동일한 출처에서 가져와야 하거나 CORS를 사용하여 동일 출처 정책을 우회해야 합니다. 간단히 말해 텍스처로 사용하려는 이미지 또는 동영상 요소의 crossOrigin 속성을 '' 또는 'anonymous'로 설정합니다.

분석을 위해 캔버스를 JSARToolKit에 전달하면 JSARToolKit은 이미지에서 발견된 AR 마커 목록과 해당하는 변환 행렬을 반환합니다. 마커 위에 3D 객체를 그리려면 변환 행렬을 사용 중인 3D 렌더링 라이브러리에 전달하여 객체가 행렬을 사용하여 변환되도록 합니다. 그런 다음 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는 아직 신규 기술이므로 기능 감지를 해야 합니다. 자세한 내용은 에릭 비델만의 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 행렬을 감지할 수 있습니다. 먼저 래스터 객체 캔버스에 이미지를 그린 다음 래스터 객체에서 감지기를 실행합니다. 감지기는 이미지에서 발견된 마커 수를 반환합니다.

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

마지막 단계는 감지된 마커를 반복하고 변환 매트릭스를 가져오는 것입니다. 변환 행렬은 3D 객체를 마커 위에 배치하는 데 사용합니다.

// 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 행렬 (마지막 4개 요소에 변환 열이 있는 16개 요소 FloatArrays)로 복사하는 코드입니다. 마술처럼 작동합니다. 즉, 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 엔진입니다. Three.js에서 JSARToolKit 출력을 사용하는 방법을 살펴보겠습니다. 동영상 이미지가 그려진 전체 화면 쿼드, 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의 기본사항을 살펴봤습니다. 이제 JavaScript로 웹캠을 사용하는 자체 증강 현실 애플리케이션을 빌드할 준비가 되었습니다.

JSARToolKit을 Three.js와 통합하는 것은 약간 번거롭지만 확실히 가능합니다. 데모에서 제대로 하고 있는지 100% 확신할 수 없으므로 더 나은 통합 방법을 알고 계시면 알려주세요. 패치 환영 :)

참조