javascriptでのAR

f:id:haru-komugi:20140713223152j:plain
javascriptでのARをしてみたのでちょっとメモ。

サンプルはこちら
http://moeten.info/js/20140713_arTest/

上記ページをスマートフォンChromeなどで表示して、下のマーカーを写すと認識されます。
f:id:haru-komugi:20140713223417p:plain

ソースコードはこちら
index.html(ほとんどjs-aruco - JavaScript library for Augmented Reality applications - Google Project Hostingのソースのままです)

<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no;" />
  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
  <title></title>
  <script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
  <script type="text/javascript" src="lib/jkl-dumper.js"></script>
  <script type="text/javascript" src="lib/polyfill.js"></script>
  <script type="text/javascript" src="lib/cv.js"></script>
  <script type="text/javascript" src="lib/aruco.js"></script>
  <script>

    $(function(){

      var video, canvas, context, imageData, detector;
      var cameraData = [];

      function onLoad(){

        //
        getCameraInfo();

      }

      //
      function getCameraInfo(){

        //カメラの情報を取得
        MediaStreamTrack.getSources(function(data){

          //カメラ情報を取得して、出力する
          var strCamera = "";
          var len = data.length;
          for( var i = 0 ; i < len ; i ++ ){
            if( data[i].kind == "video" ){
              cameraData.push(data[i]);
            }
          }
          if( cameraData.length == 0 ){
            alert("カメラが見つかりません");
            return;
          }

          //カメラをセット
          setCamera();

        });

      }

      //カメラをセット
      //カメラを取得・切り替える
      var cnt = 0;
      var localStream = null;
      function setCamera(){

        //カメラを順番に切り替える
        cnt++;
        if( cnt == cameraData.length ){
          cnt = 0;
        }
        if( localStream ){
          localStream.stop();
        }
        navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
        if (navigator.getUserMedia){

          function successCallback(stream){

            localStream = stream;

            video = document.getElementById("video");

            if (window.webkitURL) {
              video.src = window.webkitURL.createObjectURL(stream);
            } else if (video.mozSrcObject !== undefined) {
              video.mozSrcObject = stream;
            } else {
              video.src = stream;
            }

            canvas = document.getElementById("canvas");
            context = canvas.getContext("2d");
            canvas.width = $("#video").width();//parseInt(canvas.style.width);
            canvas.height = $("#video").width();//parseInt(canvas.style.height);

          }

          function errorCallback(error){
          }

          navigator.getUserMedia({
            video: {
              optional: [{sourceId: cameraData[cnt].id }] //カメラIDを直接指定する
            }
          }, successCallback, errorCallback);

          detector = new AR.Detector();

          requestAnimationFrame(tick);
        }

      }

      function tick(){
        requestAnimationFrame(tick);

        if (video.readyState === video.HAVE_ENOUGH_DATA){
          snapshot();

          var markers = detector.detect(imageData);
          drawCorners(markers);
          drawId(markers);
        }
      }

      function snapshot(){
        context.drawImage(video, 0, 0, canvas.width, canvas.height);
        imageData = context.getImageData(0, 0, canvas.width, canvas.height);
      }

      function drawCorners(markers){
        var corners, corner, i, j;

        context.lineWidth = 3;

        for (i = 0; i !== markers.length; ++ i){
          corners = markers[i].corners;

          context.strokeStyle = "red";
          context.beginPath();

          for (j = 0; j !== corners.length; ++ j){
            corner = corners[j];
            context.moveTo(corner.x, corner.y);
            corner = corners[(j + 1) % corners.length];
            context.lineTo(corner.x, corner.y);
          }

          context.stroke();
          context.closePath();

          context.strokeStyle = "green";
          context.strokeRect(corners[0].x - 2, corners[0].y - 2, 4, 4);
        }
      }

      function drawId(markers){
        var corners, corner, x, y, i, j;

        context.strokeStyle = "blue";
        context.lineWidth = 1;

        for (i = 0; i !== markers.length; ++ i){

          //$("#result").html("マーカーが" + markers.length + "個見つかりました。");

          var dumper = new JKL.Dumper();
          $("#result").html( dumper.dump( markers ) );

          corners = markers[i].corners;

          x = Infinity;
          y = Infinity;

          for (j = 0; j !== corners.length; ++ j){
            corner = corners[j];

            x = Math.min(x, corner.x);
            y = Math.min(y, corner.y);
          }

          context.strokeText(markers[i].id, x, y)
        }
      }

      //カメラ切り替えボタンクリックイベント
      $("#changeButton").bind("click",function(){
        setCamera();
      });

      onLoad();

    });
  </script>
  <style type="text/css">
    *{
      padding: 0;
      margin: 0;
    }
    body{
      text-align: center;
    }
    #video{
      width: 100%;
      height: 100%;
      margin: 10px;
      display: none;
    }
    #changeButton{
      background: #7abcff;
      background: -moz-linear-gradient(top, #7abcff 0%, #60abf8 44%, #4096ee 100%);
      background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#7abcff), color-stop(44%,#60abf8), color-stop(100%,#4096ee));
      background: -webkit-linear-gradient(top, #7abcff 0%,#60abf8 44%,#4096ee 100%);
      background: -o-linear-gradient(top, #7abcff 0%,#60abf8 44%,#4096ee 100%);
      background: -ms-linear-gradient(top, #7abcff 0%,#60abf8 44%,#4096ee 100%);
      background: linear-gradient(to bottom, #7abcff 0%,#60abf8 44%,#4096ee 100%);
      filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#7abcff', endColorstr='#4096ee',GradientType=0 );
      padding: 5px 12px;
      font-weight: bold;
      border: 1px solid blue;
      font-size: 16px;
      text-shadow: 0 0 3px #fff;
      border-radius: 4px;
      margin-bottom: 13px;
      margin-top: 13px;
    }
    #result{
      font-size: 12px;;
      color: gray;
      border:1px solid #aeaeae;
      border-radius: 5px;
      padding: 10px;
      margin-bottom: 13px;
      background: rgb(255, 251, 242);
    }
  </style>
</head>
<body>
<video id="video" autoplay="true"></video>
<canvas id="canvas"></canvas>
<button id="changeButton">カメラ切り替え</button>
<div id="result"></div>
</body>
</html>

速度もほぼリアルタイムで認識するので、いろいろとできそうです。