はじめまして。
2012年度入社のニイノミ(@nomi_ryota)と申します。
現在は、お題に一言ボケるスマートフォン向け大爆笑コミュニティサービス『アメーバ大喜利』のフロントエンドを担当しています。
趣味は黄色いもの集めです。


さて、本日は最近(ちょっと前か)話題のフィジカルコンピューティングデバイス『LeapMotion』のJavaScriptを用いた開発について紹介したいと思います。
僕が日頃業務でやっている内容とはちょっと異なるフィジカルコンピューティングのお話ですが、とても面白い世界なのでお付き合い頂ければ幸いです。

LeapMotionとは

LeapMotion(以下Leap)はLeap社が開発した動きやジェスチャーを認識し、コンピュータ操作を行えるデバイスです。
2012年春頃から予約が始まり、今年の7月頃にようやく世界中に発送され利用できるようになりました。
Leapの特徴としては、

 ・手のひらサイズのコンパクトさ
 ・両手の指1本1本すべて検知でき、手の向きも検知できるので仮想的にモノをつかんだりすることが可能
 ・XY軸に加えて、奥行きを表すZ軸を持つ
 ・センサーの反応範囲は約600mm
 ・スキャン速度は、最大290fps(Kinectの約10倍)
 ・C++, Objective-C, Java, Javascriptなど様々な言語で制御可能

が挙げられます。
同様のデバイスにMicrosoft社が開発したKinectがありますが、Kinectと比較したLeapの利点はやはり、

 ・手のひらサイズのコンパクトさ
 ・手のひら、指の細かな情報を認識できる

だと思います。
サイズはKinectの1/7以下と扱いやすい大きさですし、手のひらや指の情報に関してはその位置だけでなく向きや角度、ジェスチャーのパターンまで認識できるので細かな動きを制御するには非常に優れたデバイスです。
イメージ的にKinectは身体全体を使った操作に、Leapは手を使った細かな操作に向いているといった感じでしょうか。

開発準備

Leapを使ったアプリケーションはC++, Objective-C, Java, JavaScriptなど様々な言語で開発可能ですが、今回は比較的扱いやすいJavaScriptを使っていきます。
LeapをPCで使えるようにするには予め専用のソフトウェアをインストールしておく必要があります。
Leapのサイトから手順に沿って行うだけです。非常に簡単です。
JavaScriptでLeapを扱うにはLeapJSというオープンソースライブラリを使用します。LeapJSの使い方は次の項で説明します。

LeapJSについて

Leapはコンピューターにドライバが組み込まれた状態であればポート番号6437でWebSocketサーバが立ち上がるのでそれをJavaScriptで受け取ればJSON形式のトラッキングデータ(手や指の情報)を取得することができます。
これをJavaScriptから簡単に扱えるようにしたのがLeapJSです。
説明するよりもコードを見た方が早いですよね?早速LeapJSを使ってみましょう。

LeapJSを読み込みます。
ダウンロードしたファイル以外にもLeapJSのページにホスティングされているファイルを読み込む方法もあります。


<script src="//js.leapmotion.com/0.2.1/leap.min.js"></script>

LeapJSは「loop」メソッドを使うことでWebSocket経由でトラッキングデータを取得できます。

Leap.loop({enableGestures: true}, function(frame){
  // 処理
});


第1引数にはオブジェクト形式で色々設定可能ですが、ここではジェスチャーの検出設定のみ行います。

インスタンスからイベントバインド形式で書くことも可能です。
以降で紹介するデモではこちらを利用しようと思います。


var controller = new Leap.Controller({enableGestures: true});
controller.on('frame', function(frame) {
  // 処理
});
controller.connect();  // 接続

指先の座標やジェスチャー等のトラッキングデータは、コールバック関数に渡される引数(frame)に格納されます。

例:
 frame.gestures  // ジェスチャーのデータ(配列)
 frame.hands  // 手のデータ(配列)
 frame.fingers  // 指のデータ(配列)

例えば、検出されている指の本数は下記のように取得できます。


Leap.loop({enableGestures: true}, function(frame){
  console.log(frame.fingers.length);
});

このようにLeapJSを用いることで簡単にトラキングデータを扱うことができます。
ワクワクしますね。

LeapJSで制御してみる

それでは取得したトラッキングデータを色々使ってみましょう。

はじめに、Canvasを用いて手を描いてみます。
人間の手をパーツ別に考えると、手のひらと指に分けることができるのでそれらを別々に描画していきます。
検出されている手のひらは、「frame.hands」で配列として取得できます。
また、その位置は手のひら配列の中の「palmPosition」の中にこちらも配列としてx座標、y座標、z座標が格納されています。

var hand = frame.hands[0],
        posX = hand.palmPosition[0],
        posY = hand.palmPosition[1],
        posZ = hand.palmPosition[2];


指の情報も「frame.pointables」を用いて手のひらと同じ要領で取得します。
手を描画する全コードは以下の通りです。


var controller = new Leap.Controller({enableGestures: true}),
    canvas     = document.getElementById("js_canvas"),
    ctx        = canvas.getContext("2d"),
    winWidth   = window.innerWidth,
    winHeight  = window.innerHeight;
var resetDraw = function(){
    ctx.clearRect(0, 0, winWidth, winHeight);
};
//手の描画
var draw = function(arg, isFinger){
    var _posX = winWidth/2 + arg.posX,
        _posY = winHeight - (arg.posY),
        _radius = (isFinger) ? 30 : 90;
    ctx.fillStyle = "rgba(0,0,0,0.4)";
    ctx.beginPath();
    ctx.arc(_posX, _posY, _radius, 0, Math.PI*2, false);
    ctx.fill();
};
var init = function()
    canvas.width  = winWidth;
    canvas.height = winHeight;
    controller.connect();
};
//トラッキング
controller.on("frame", function(frame){
    resetDraw();
    //検出した手の数だけ実行
    for (var i = 0, _len = frame.hands.length; i < _len; i++) {
        var _hand = frame.hands[i];
        draw({
            posX : _hand.palmPosition[0]*3,
            posY : _hand.palmPosition[1]*3 - 150
        }, false);
    }
    //検出した指の数だけ実行
    for (var i = 0, _len = frame.pointables.length; i < _len; i++) {
        var _pointable = frame.pointables[i];
        draw({
            posX : _pointable.tipPosition[0]*3,
            posY : _pointable.tipPosition[1]*3
        }, true);
    }
});
init();

猫の手のようなかわいい手が描けました!

実際にLeap上で手を動かすと画面上の手の位置も更新されます。
上のコードで使っているのは手のひらと指先の位置だけなのでリアルな人間の手とはほど遠いですが、指の長さを取得する「pointables.length」や指の平均幅を取得する「pointables.width」など使えばもっと人間っぽい手を再現できるかもしれません。

因に、CSS3のtranslate3dプロパティを利用すればCanvasではなくDOM操作で手軽にリアルな3Dの手を描画することもできます。

デモ(LeapMotionが繋がれた状態でご覧下さい)

ジェスチャー

では、次にジェスチャーを取得していきます。

Leapではユーザーの考えられうる特定の動きのパターンがジェスチャーとして予め登録されています。
ジェスチャーを取得するには、予めインスタンス化の際のカスタム設定オブジェクトの「enableGestures」をtrueにしておく必要があります。


var controller = new Leap.Controller({enableGestures: true});

ジェスチャーの種類は、以下の4パターンがあります。

 circle: 指で円を描く動作
 swipe: 水平方向に直線を描く動作
 screenTap: 指を画面方向に素早く動かす動作
 keyTap: 指を下に素早く動かす動作

これらのジェスチャーは「gesture.type」で取得できます。


frame.gestures[0].type;

また、「gesture.state」でジェスチャーの状態(start, update, stop)も取得することができます。
下記はcircleジェスチャーでボックスを回転させるコードです。


var controller   = new Leap.Controller({enableGestures: true}),
    winWidth     = window.innerWidth,
    winHeight    = window.innerHeight,
    canvas       = document.getElementById("js_canvas"),
    ctx          = canvas.getContext("2d"),
    drawRectAble = true,
    boxSize      = 200,
    gesType      = null,
    rotateAngle  = 15,
    rotateCount  = 0,
    currentDown  = 0,
    downState    = null,
    downCount    = 0,
    upCount      = 0;
//ボックス
var boxObj = {
    x : winWidth/2 - boxSize/2,
    y : winHeight/2 - boxSize/2,
    size : boxSize
};
var resetDraw = function(){
    drawRectAble  = true;
    ctx.fillStyle = "rgba(100, 100, 100, 1)";
    if (!gesType) {
        ctx.clearRect(0, 0, winWidth, winHeight);
        ctx.fillRect(boxObj.x, boxObj.y, boxObj.size, boxObj.size);
    }
    //回転処理
    else if (gesType == "circle") {
        ctx.clearRect(0, 0, winWidth, winHeight);
        ctx.fillRect(boxObj.x, boxObj.y, boxObj.size, boxObj.size);
        ctx.translate(winWidth/2, winHeight/2);
        ctx.rotate(rotateAngle/180*Math.PI);
        ctx.translate(-1*winWidth/2, -1*winHeight/2);
        rotateCount++;
        if (rotateAngle*rotateCount >= 360) {
            gesType = null;
            rotateCount = 0;
        }
    }
};
//衝突&ジェスチャー判定
var checkGesture = function(_gesture, _posX, _posY){
    if (_gesture && _posX > boxObj.x && _posY > boxObj.y && _posX < boxObj.x+boxObj.size && _posY < boxObj.y+boxObj.size) {
        //circle
        if(_gesture.type == "circle" && _gesture.state == "start" && !gesType) {
            gesType = _gesture.type;
        }
    }
};
//描画
var draw = function(arg){
    var _posX = winWidth/2 + arg.posX,
        _posY = winHeight - (arg.posY);
    checkGesture(arg.gesture, _posX, _posY);
    //指
    ctx.fillStyle = "rgba(255,255,255,0.4)";
    ctx.beginPath();
    ctx.arc(_posX, _posY, 10, 0, Math.PI*2, false);
    ctx.fill();
};
var init = function(){
    canvas.width  = winWidth;
    canvas.height = winHeight;
    controller.connect();  //接続
};
//トラッキング
controller.on("frame", function(frame){
    resetDraw();
    //検出した指の数だけ実行
    for (var i = 0, _len = frame.pointables.length; i < _len; i++) {
        var _pointable = frame.pointables[i];
        draw(_obj = {
            posX : _pointable.tipPosition[0]*3,
            posY : _pointable.tipPosition[1]*3,
            posZ : _pointable.tipPosition[2]*3,
            gesture : (frame.gestures[0]) ? frame.gestures[0] : null
        });
    }
});
init();

実行結果は画像ではわかりづらいので動画でご覧下さい。



ちゃんとcircleジェスチャーを認識してくれました。

最後に認識するジェスチャーの種類を増やし、ジェスチャーごとにアクションを変えてみます。

 circle:ボックス回転
 keyTap:ボックス降下
 screenTap:背景色変更



結構な精度でそれぞれのジェスチャーを認識してくれてます。(動きが地味ですが笑)
ただ、keyTapは下げた指をしっかり上まで戻さないとジェスチャーとして認識されなかったので、その辺の調整は必要かもしれません。

このように予め用意されているジェスチャーを用いれば簡単に画面を操作することが可能です。
マウスを使わなくてもWebサイトの閲覧ぐらいはできそうですね。

最後に

最後までお付き合い頂きありがとうございました。
今回はJavaScriptを用いたLeapMotionの制御について紹介させて頂きました。
いかがでしたか?
フィジカルコンピューティング、面白いですよね。
まだまだ仕事として扱うには難しいかもしれませんが、これからの未来を担う可能性のある分野だと僕は思っています。
マイノリティ・リポート的な未来が来るのもそう遠くはないかもしれませんね。
興味が湧いた方、是非トライしてみて下さい!

大爆笑コミュニティサービスの『アメーバ大喜利』もよろしくね♪