Intelligent Technology's Technical Blog

株式会社インテリジェントテクノロジーの技術情報ブログです。

LeapMotionのモーション情報をJavaScriptで取得する仕組み

こんにちは、武村です。

前回の記事で、LeapMotionの情報をJavaScriptで取得するには、
「leap.jsが必要です」
とお伝えしました。今回は、そのleap.jsとアプリ側JavaScriptの関係について書いていきます。

最初に白状しておきますが、調べていくうちに実はleap.jsは必須ではないことが分かりました。申し訳ございません。ただあるとないとでは開発のしやすさに雲泥の差があります。その理由は、この記事を読んでLeapMotionの仕組みを理解すると分かるでしょう。

それではLeapMotionの仕組みについて紹介いたします。


LeapMotionを使うにあたって最初にLeapMotionソフトウェアをPCにインストールしました。あのソフトウェアは一体何者でしょうか?

その正体はUSB接続したLeapMotionからのモーション情報を扱うバックグラウンドプログラムです。バックグラウンドで動くプログラムは、Daemon(WindowsではService)と名付けられているので、以降この記事では「LeapMotionDaemon」と呼びます。

LeapMotionDaemonは、アプリに対してモーション情報を2種類の方法で提供しています。

  • C++で書かれたライブラリを呼び出す
  • WebSocket通信によるJSON形式データの提供

LeapMotionDaemonのライブラリはC++で書かれているので、アプリの開発言語によってモーション情報の取得方法が分かれます。

詳細はコチラLeap Motion Application InterfaceとLeap Motion WebSocket Interfaceを参考にしてください。

今回は開発言語をJavaScriptに絞って、WebSocket通信による方法と、Leap.jsライブラリを使う方法の2通りのモーション情報取得方法を説明します。

1.WebSocket通信による取得

LeapMotionDaemonは、デフォルトでlocalhost(127.0.0.1)のポート6437でWebSocketサーバを立てています。
ws://localhost:6437 と通信を行うと、モーション情報が25~200fps(毎秒フレーム)で返ってきます。LeapMotionコントロールパネルにてトラッキング優先度が、バランス/精度/高速から選べます。それによりフレームスピードが変わってきます。ちなみに私の環境だと、動きがない時で25fps、動きがあるときで98fpsくらいでした。

では試しにChromeブラウザの拡張機能を使ってWebSocketサーバへ簡単に通信してみましょう。
まず、Simple WebSocket Clientをインストールします。するとブラウザのツールバーにSimple WebSocket Clientが追加されますので、起動すると以下のようなタブが開きます。
f:id:IntelligentTechnology:20131126112543p:plain

一番上のServer LocationのURLに

ws://localhost:6437

と入力して[Open]ボタンを押すと、ログがすごい速さで書き出されます。
f:id:IntelligentTechnology:20131126112554p:plain

1フレーム1行ですので、最低でも毎秒25行は書き出されます。
LeapMotionが動きを検知していない時の1フレームの内容は以下の通りです。
f:id:IntelligentTechnology:20131127085655p:plain
このように細かい数値がたくさん含まれています。ここには載せませんが少し動きがあるとこれの3~4倍のモーション情報量になります。この数値などを解析してアプリケーションでの動きに反映させていけばいいわけですが、自分で実装するにはかなり骨が折れます。
そこで通常JavaScriptでアプリを開発するのに使われるのが、「Leap.js」です。

2. Leap.jsを使ってモーション情報を取得する方法

Leap.jsはWebSocketのJSON出力をLeap MotionのネイティブAPIと一貫性のある構造に変換するためのオープンソースJavaScript APIです。

http://hatsune.hatenablog.jp/entry/2013/08/27/161747

つまりLeap.jsがWebSocket通信でデータを取得し解析をしてくれるので、アプリは「ジェスチャ」に対してプログラミングを行う事ができます。

前回作成したサンプルアプリのJavaScriptプログラムを例に、ページが読み込まれてからどういった処理が行われているか段階ごとソースコードを見ていきます。
1.コントローラクラスのインスタンスを作成する
2.作成したコントローラを接続する

1.コントローラクラスのインスタンスを作成する

main.js

$(document).ready(function() {
    // Leapコントローラクラスのインスタンスを作成
    var controller = new Leap.Controller({enableGestures: true});

    /***以下省略***/

コントローラクラスのインスタンスを作成するときに以下の様なleap.js内のコンストラクタ関数が呼ばれます。

leap.js

// コントローラのコンストラクタ関数
var Controller = module.exports = function(opts) {
  var inNode = typeof(process) !== 'undefined' && process.title === 'node';

  opts = _.defaults(opts || {}, {
    inNode: inNode
  });

  /**中略**/

  if (opts.connectionType === undefined) {
    this.connectionType = (this.inBrowser() ? require('./connection/browser') : require('./connection/node'));
  } else {
    this.connectionType = opts.connectionType;
  }
  this.connection = new this.connectionType(opts);  // ここでコネクションを作成している
  this.setupConnectionEvents();
}

下から3行目の

this.connection = new this.connectionType(opts);

でWebSocket通信に必要なホストとポートなどを設定するために、以下のコンストラクタ関数が呼び出しています。

leap.js

var BaseConnection = module.exports = function(opts) {
  this.opts = _.defaults(opts || {}, {
    host : '127.0.0.1',
    enableGestures: false,
    port: 6437,
    enableHeartbeat: true,
    heartbeatInterval: 100,
    requestProtocolVersion: 3
  });
  this.host = this.opts.host;
  this.port = this.opts.port;
  /**中略**/
}

これでコントローラのインスタンスが作成されました。次にコントローラを接続します。

2.作成したコントローラを接続する

main.js

// Leapコントローラを接続(起動)する
controller.connect(); 

以上のメソッドが実行されると、leap.jsの以下のメソッドが呼び出されます。

leap.js

BaseConnection.prototype.connect = function() {
  if (this.socket) return;
  this.socket = this.setupSocket();
  return true;
}
BrowserConnection.prototype.setupSocket = function() {
  var connection = this;
  var socket = new WebSocket(this.getUrl());
  socket.onopen = function() { connection.handleOpen(); };
  socket.onclose = function(data) { connection.handleClose(data['code'], data['reason']); };
  socket.onmessage = function(message) { connection.handleData(message.data) };
  return socket;
}
BaseConnection.prototype.getUrl = function() {
  return "ws://" + this.host + ":" + this.port + "/v" + this.opts.requestProtocolVersion + ".json";
}

以上のコードからLeap.js内でWebSocket通信を行っているのが分かりました。
Controllerインスタンスを作成してジェスチャと状態に対してcallbackを指定しておくと、ジェスチャが起きたときに各モーション情報(距離、座標、指の本数、手の本数など)が渡されます。

前回作成したサンプルアプリではスワイプジェスチャが終了した時に以下の様なcallbackを指定してスワイプジェスチャの距離をもとにスクロールが行われるようにしました。

controller.gesture('swipe').stop(function(g) {

    // 引数にはその動作の一連の情報が配列で入ってくる
    // なので最後の情報がストップ時の情報
    var stopGesture = g.gestures[g.gestures.length - 1];

    // スタート位置とストップ位置を取得する
    var startX = stopGesture.startPosition[0];
    var stopX = stopGesture.position[0];

    // スワイプの距離をスクロール量の基準にする
    // スクロール量が少ないので2倍とする
    position += (startX - stopX) * 2;

    // jQueryのアニメーションを使ってスクロールさせる
    $("#pictures").animate({scrollLeft: position + "px"}, "fast");

});

サンプルでは使っていませんが、その他にも指の本数、手の本数、各指の位置など様々なモーション情報を取得することが可能です。
コチラを参考にしてください。

まとめ

結論として、JavaScriptからはleap.jsを使うのが一般的であると言えます。今後はLeapMotionに限らずこういった次世代デバイスがどんどん出現してくる時代となるでしょう。遅れを取らないためにも新しい物・技術にたくさん触れて、このブログで紹介していきたいです。