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

Ilmari Heikkinen

소개

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

JSARToolKit는 자바스크립트용 증강 현실 라이브러리입니다. GPL에 따라 출시된 오픈소스 라이브러리이며 Mozilla Remixing Reality 데모용으로 만든 Flash FLARToolKit를 직접 포팅할 수 있습니다. FLARToolKit 자체는 C ARToolKit의 포트인 자바 NyARToolKit 포트입니다. 먼 길이지만 여기까지 왔네요.

JSARToolKit는 캔버스 요소에서 작동합니다. 캔버스 밖에서 이미지를 읽어야 하므로 이미지의 출처가 페이지와 동일해야 하거나 CORS를 사용하여 동일 출처 정책을 적용해야 합니다. 요약하면 텍스처로 사용할 이미지 또는 동영상 요소의 crossOrigin 속성을 '' 또는 'anonymous'로 설정하세요.

분석을 위해 캔버스를 JSARToolKit에 전달하면 JSARToolKit는 이미지에서 찾은 AR 마커 목록과 해당 변환 행렬을 반환합니다. 마커 위에 3D 객체를 그리려면 사용 중인 모든 3D 렌더링 라이브러리에 변환 매트릭스를 전달하여 객체가 매트릭스를 사용하여 변환되도록 합니다. 그런 다음 WebGL 장면에서 동영상 프레임을 그리고 그 위에 객체를 그리면 됩니다.

JSARToolKit를 사용하여 동영상을 분석하려면 캔버스에 동영상을 그린 다음 캔버스를 JSARToolKit에 전달합니다. 모든 프레임에 대해 이 작업을 수행하고 동영상 AR 추적이 가능합니다. JSARToolKit는 최신 자바스크립트 엔진에서 충분히 빠르게 실행되어 640x480 동영상 프레임에서도 실시간으로 이 작업을 수행할 수 있습니다. 하지만 동영상 프레임이 클수록 처리하는 데 시간이 오래 걸립니다. 적절한 동영상 프레임 크기는 320x240이지만 작은 마커나 여러 개의 마커를 사용하려는 경우 640x480을 사용하는 것이 좋습니다.

데모

웹캠 데모를 보려면 브라우저에서 WebRTC를 사용하도록 설정해야 합니다 (Chrome의 경우 about:flags로 이동하여 MediaStream을 사용하도록 설정). 아래의 AR 마커도 출력해야 합니다. 휴대전화나 태블릿에서 마커 이미지를 열고 웹캠에 표시할 수도 있습니다.

AR 마커
AR 마커

JSARToolKit 설정

JSARToolKit API는 자바와 유사하므로 이를 사용하려면 몇 가지 곡을 변형해야 합니다. 기본 개념은 래스터 객체에서 작동하는 감지기 객체가 있다는 것입니다. 감지기와 래스터 사이에는 래스터 좌표를 카메라 좌표로 변환하는 카메라 매개변수 객체가 있습니다. 인식기에서 감지된 마커를 가져오려면 해당 마커를 반복하고 변환 행렬을 코드에 복사합니다.

첫 번째 단계는 래스터 객체, 카메라 매개변수 객체, 감지기 객체를 만드는 것입니다.

// 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 행렬을 감지할 수 있습니다. 먼저 래스터 객체 캔버스에 이미지를 그린 다음 래스터 객체에서 감지기를 실행합니다. 인식기는 이미지에서 찾은 마커의 수를 반환합니다.

// 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% 확신할 수는 없습니다. 통합을 달성하는 더 좋은 방법이 있으면 알려주시기 바랍니다. 패치 사용 가능 :)

참조