Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
特集:コードで理解するLeapアプリ開発の概要

特集:コードで理解するLeapアプリ開発の概要

C#開発者から見たLeap Motion開発のファースト・インプレッション

2013年7月23日

手や指の動きを読み取って、さまざまな処理を行うアプリを作成できる「Leap Motion」の一般販売がついに開始された。その開発はどのようなものなのか? SDKに含まれる最も基本的なソース・コードを眺めてみよう。

デジタルアドバンテージ 一色 政彦
  • このエントリーをはてなブックマークに追加

※2013/07/29追記: C++言語向けの連載はこちらです。
※2013/08/29追記: VB言語向けの連載はこちらです。

 2013年7月22日(米国時間)、ついにLeap Motionの一般販売が開始された。Leap Motionとは、簡単にいえばKinectの機能を手と指だけ(+手に関連付けられた道具。例えばペンなど)に絞ったデバイスである。そのため、デバイスはフリスクのケース程度の大きさで(高さ:1.27cm、横幅:3cm、奥行き:7.62cm、重さ:45.4g)、価格も(執筆時点で)8200円程度(税金や送料を含まない場合)とお手頃だ。

 その内容や可能性は、言葉で説明するよりも、実際に動いている映像を見た方が早いだろう(Leap Motionをあまりよく知らないという方は、ぜひ次の動画を再生してほしい)。

Introducing the Leap Motion

 昨日から世界中で「製品が届いた!」という喜びの声が上がっている。筆者は実際にはまだ手にしていないが、その開発SDKは入手できる状態なので、さっそく「そのSDKが提供する最も基本的なサンプル・コードを見て、Leap Motionアプリケーション(以降、「Leapアプリ」)の開発がどのようなものか」を調べてみた。本稿では、その内容を紹介する。なお、このSDK使うと、手/指/道具の動きの情報(=モーション・キャプチャ情報)を取得したりできる。

SDKのダウンロード

 下記のリンク先から、Windows/Mac OS X/Linux向けにSDKをダウンロードできる。実際にダウンロードするには、事前にサインアップが必要になる(サインアップは無料で行える)。

 本稿では、Windows用のSDKをダウンロードした。

対応している開発言語について

 SDKが提供するLeap Motion APIライブラリでは、

  • C++
  • C#
  • Java
  • Python
  • Objective-C
  • JavaScript

と数多くの開発言語に対応している。

 本稿では、筆者が最も得意なC#を用いる(SDKは、.NET Framework 3.5および4.0に対応。.NET互換の「Mono」にも対応している。また、Unity Pro用のプラグインも提供している)。また、本稿で紹介するサンプル・コードは、SDKに含まれている「\LeapSDK\samples\Sample.cs」である。詳しいコード内容を知りたい場合は、ぜひSDKをダウンロードして参照してほしい。

 それでは、さっそくLeapアプリの最も基本的なサンプル・コードを見ていこう(なお、以降で単に「Leap」と表記している場合は、「Leap Motion」を指す)。

最も基本的なLeapアプリのサンプル・コード(C#)

 本稿で紹介するLeapアプリは、Leapから受け取ったフレーム情報(詳細後述)やジェスチャー情報(詳細後述)をコンソール出力するだけのシンプルなアプリだ。前述のとおり、筆者は実機を持っておらず、実行しても何も情報は出力されないので、実行画面はキャプチャしていない。ご了承いただきたい。

Mainメソッドの実装

 まずは、最初に呼び出されるMainメソッドから見ていこう。下記のコードのようになっている(本稿では、コードの一部を日本語に翻訳している)。

C#
using System;
using Leap;
 
class Sample
{
  public static void Main ()
  {
    // サンプルのリスナーとコントローラーを作成
    SampleListener listener = new SampleListener ();
    Controller controller = new Controller ();
 
    // サンプルのリスナーが、コントローラーからイベントを受け取るよう設定
    controller.AddListener (listener);
 
    // [Enter]キーが押されるまで、このプロセスの実行を続ける
    Console.WriteLine ("[Enter]キーを押すと終了...");
    Console.ReadLine ();
 
    // 実行終了時には、サンプル・リスナーを削除
    controller.RemoveListener (listener);
    controller.Dispose ();
  }
}
Mainメソッドのコード内容

本アプリを実行するには、SDKの「\LeapSDK\lib\x86」/「\LeapSDK\lib\x64」フォルダの中身を、プロジェクト「bin\Debug」/「bin\Release」フォルダにコピーする必要がある。

 上記のコードを理解するのは、(日本語でコメントも記述しているので)大して難しくないだろう。本稿では、コード内容の詳しい説明は割愛する。その代わりに、ライブラリが提供する機能やクラスの、基本的な概念や使い方について簡単に紹介する。

 上記のコードで重要なのは、「コントローラー(Controller)」と「リスナー(Listener)」という概念である。

リスナーの実装

 上記のコード例では、SampleListenerクラスがリスナーに当たるが、このクラスは次のように定義されている(一部省略)。

C#
class SampleListener : Listener
{
  private Object thisLock = new Object();
 
  private void SafeWriteLine(String line)
  {
    lock (thisLock)
    {
      Console.WriteLine(line);  // コンソール出力
    }
  }
 
  public override void OnInit(Controller controller)
  {
    SafeWriteLine("Initialized");
  }
 
  public override void OnConnect(Controller controller)
  {
    SafeWriteLine("Connected");
    ……省略……
  }
 
  public override void OnDisconnect(Controller controller)
  {
    // 注意: デバッガー内で実行されているときは呼び出されない
    SafeWriteLine("Disconnected");
  }
 
  public override void OnExit(Controller controller)
  {
    SafeWriteLine("Exited");
  }
 
  public override void OnFrame(Controller controller)
  {
    ……省略……
  }
}
SampleListenerクラスのコード内容(一部省略)

 このコードを見ると、5つの仮想メソッドがオーバーライドされているのが分かる。それぞれのメソッドは、下記のようなタイミングで呼び出されることになる。

  • OnInitメソッド: リスナーの登録先のコントローラーが初期化されたときに、1回だけ
  • OnConnectメソッド: コントローラーがLeapに接続して、モーション・トラッキング・データ(motion tracking data)のフレーム(frames。詳細後述)を送信開始する準備が整ったとき
  • OnDisconnectメソッド: コントローラーがLeapから接続解除した場合(例えば、Leapデバイスが取り外されたり、Leapソフトウェアが強制終了したりした場合)
  • OnExitメソッド: リスナーがコントローラーから削除されたとき(もしくは、コントローラーが破棄されたとき)
  • OnFrameメソッド: モーション・トラッキング・データの新しいフレームが利用可能になったとき

 ちなみに、このほかにもOnFocusGainedメソッド(=アプリが前面になったときに発生)やOnFocusLostメソッド(=アプリが前面ではなくなったときに発生)もオーバーライドできる。

 上記のコード例では、OnConnectメソッドとOnFrameメソッドの中身を省略している。以下では、それぞれの省略した部分の内容について説明しよう。

OnConnectメソッドの実装内容

 OnConnectメソッドの中は下記のようになっている。

C#
public override void OnConnect(Controller controller)
{
  SafeWriteLine("Connected");
  controller.EnableGesture(Gesture.GestureType.TYPECIRCLE);
  controller.EnableGesture(Gesture.GestureType.TYPEKEYTAP);
  controller.EnableGesture(Gesture.GestureType.TYPESCREENTAP);
  controller.EnableGesture(Gesture.GestureType.TYPESWIPE);
}
OnConnectメソッドの実装内容

 このコードの意味は、書かれている英語の内容どおり、ジェスチャー(Gesture)を有効(Enable)にしているだけである。それぞれを有効にすることで、実際にLeapから各ジェスチャーを取得できるようになる(必要なものだけを有効にすればよい)。

 そのジェスチャーには、4つの種類(Type)があることが分かる。それぞれの意味は下記のとおりだ。

  • サークル(TYPECIRCLE): 指で円を描く動作
  • キー・タップ(TYPEKEYTAP): 指で(あたかもキーを押しているように)「下方向」にタップする動作
  • スクリーン・タップ(TYPESCREENTAP): 指で(あたかもスクリーンを押しているように)「前方向」にタップする動作
  • スワイプ(TYPESWIPE): 指を伸ばした状態の手で直線を描く動作

 これらの動作は言葉だけでは伝わりにくいので、Leap Motion SDKのAPIドキュメントから、各動作の絵を引用しておこう(次の画像)。

4種類のジェスチャー
4種類のジェスチャー(Leap Motion SDKのAPIドキュメントから4つの画像をまとめて引用)

OnFrameメソッドの実装内容

 有効になった各ジェスチャーのデータは、フレームから取得できる。フレームとは、手や指のトラッキング・データのセットを格納した、Frameクラス(Leap名前空間)のオブジェクトのことだ。

 前述したように、新しいフレームが発生するたびにOnFrameメソッドが呼ばれる。従って、そのOnFrameメソッド内で、ジェスチャー情報やフレーム情報をFrameオブジェクトから取得し、その情報を活用してアプリ独自の処理を実装すれば、さまざまな独自の機能が実現できるというわけだ。

 OnFrameメソッドは少し長いので、「フレーム情報の取得」と「ジェスチャー情報の取得」の2つに分けてコード内容を紹介しよう。

フレーム情報の取得

 まずは、フレーム情報を取得して独自の処理を実装している部分のコードを示す。

C#
public override void OnFrame(Controller controller)
{
  // 最新のフレームを取得して、基本情報をレポートする
  Frame frame = controller.Frame();
 
  SafeWriteLine("フレームID: " + frame.Id
        + ", タイムスタンプ: " + frame.Timestamp
        + ", 手の数: " + frame.Hands.Count
        + ", 指の数: " + frame.Fingers.Count
        + ", 道具の数: " + frame.Tools.Count
        + ", ジェスチャーの数: " + frame.Gestures().Count);
 
  if (!frame.Hands.Empty)
  {
    // 1つ目の手を取得
    Hand hand = frame.Hands[0];
 
    // 手に指があるかチェック
    FingerList fingers = hand.Fingers;
    if (!fingers.Empty)
    {
      // 手の指先の平均的な位置を計算
      Vector avgPos = Vector.Zero;
      foreach (Finger finger in fingers)
      {
        avgPos += finger.TipPosition;
      }
      avgPos /= fingers.Count;
      SafeWriteLine("手には、" + fingers.Count
        + "本の指があり、指先の平均的な位置は: " + avgPos);
    }
 
    // 手の球半径と手のひらの位置を取得
    SafeWriteLine("手の球半径: " + hand.SphereRadius.ToString("n2")
          + " mm, 手のひらの位置: " + hand.PalmPosition);
 
    // 手のひらの法線ベクトルと(手のひらから指までの)方向を取得
    Vector normal = hand.PalmNormal;
    Vector direction = hand.Direction;
 
    // 手のピッチとロールとヨー角を計算
    SafeWriteLine("手のピッチ: " + direction.Pitch * 180.0f / (float)Math.PI + " 度, "
          + "ロール: " + normal.Roll * 180.0f / (float)Math.PI + " 度, "
          + "ヨー角: " + direction.Yaw * 180.0f / (float)Math.PI + " 度");
  }
    ……省略……
}
フレーム情報を取得して独自の処理を実装している部分のコード

 このコードもコメントが多く入っており、大体の実装内容は理解できると思われるので、説明は割愛する。新出のクラスには以下のようなものがある(いずれもLeap名前空間に所属)。

  • Handクラス 検出された手の物理的な特徴をレポート
  • HandListクラス Handオブジェクトのリスト。FrameオブジェクトのHandsプロパティで取得できる
  • Fingerクラス トラッキングしている指を表現
  • FingerListクラス Fingerオブジェクトのリスト。FrameオブジェクトのFingersプロパティで取得できる
  • Toolクラス トラッキングしている道具(例えばペンなど)を表現
  • ToolListクラス Toolオブジェクトのリスト。FrameオブジェクトのToolsプロパティで取得できる
  • Gestureクラス 認識されたユーザーの動きを表現
  • GestureListクラス Gestureオブジェクトのリスト。FrameオブジェクトのGesturesメソッドで取得できる
  • Vector構造体 3次元ベクトル情報を表現
ジェスチャー情報の取得

 次に、ジェスチャー情報を取得して独自の処理を実装している部分のコードを示す。

C#
public override void OnFrame(Controller controller)
{
    ……省略……
 
  // 全ジェスチャーを取得して、個別に処理する
  GestureList gestures = frame.Gestures();
  for (int i = 0; i < gestures.Count; i++)
  {
    Gesture gesture = gestures[i];
 
    switch (gesture.Type)
    {
      case Gesture.GestureType.TYPECIRCLE:
        CircleGesture circle = new CircleGesture(gesture);
 
        // 回転方向を計算
        String clockwiseness;
        if (circle.Pointable.Direction.AngleTo(circle.Normal) <= Math.PI / 4)
        {
          // 角度が90度以下なら、時計回り
          clockwiseness = "時計回り";
        }
        else
        {
          clockwiseness = "反時計回り";
        }
 
        float sweptAngle = 0;
 
        // 最後のフレームから回った角度を計算
        if (circle.State != Gesture.GestureState.STATESTART)
        {
          CircleGesture previousUpdate = new CircleGesture(controller.Frame(1).Gesture(circle.Id));
          sweptAngle = (circle.Progress - previousUpdate.Progress) * 360;
        }
 
        SafeWriteLine("サークルID: " + circle.Id
                 + ", " + circle.State
                 + ", 進度: " + circle.Progress
                 + ", 半径: " + circle.Radius
                 + ", 角度: " + sweptAngle
                 + ", " + clockwiseness);
        break;
 
      case Gesture.GestureType.TYPESWIPE:
        SwipeGesture swipe = new SwipeGesture(gesture);
        SafeWriteLine("スワイプID: " + swipe.Id
                 + ", " + swipe.State
                 + ", 位置: " + swipe.Position
                 + ", 方向: " + swipe.Direction
                 + ", スピード: " + swipe.Speed);
        break;
 
      case Gesture.GestureType.TYPEKEYTAP:
        KeyTapGesture keytap = new KeyTapGesture(gesture);
        SafeWriteLine("キー・タップID: " + keytap.Id
                 + ", " + keytap.State
                 + ", 位置: " + keytap.Position
                 + ", 方向: " + keytap.Direction);
        break;
 
      case Gesture.GestureType.TYPESCREENTAP:
        ScreenTapGesture screentap = new ScreenTapGesture(gesture);
        SafeWriteLine("スクリーン・タップID: " + screentap.Id
                 + ", " + screentap.State
                 + ", 位置: " + screentap.Position
                 + ", 方向: " + screentap.Direction);
        break;
 
      default:
        SafeWriteLine("未知のジェスチャー・タイプです。");
        break;
    }
  }
 
  if (!frame.Hands.Empty || !frame.Gestures().Empty)
  {
    SafeWriteLine("");
  }
}
ジェスチャー情報を取得して独自の処理を実装している部分のコード

 前掲のコードで4種類のジェスチャーを有効にしたが、上記のコードではそれらのジェスチャー種別を判別して、それに基づき下記の4種類いずれかのジェスチャー・オブジェクトを取得している(いずれもLeap名前空間に所属)。

 ここでも詳しいロジックの説明は割愛するが、不明なクラスやプロパティなどがある場合は、APIドキュメント(英語)を参照してほしい。

 以上、Leapアプリの最も基本的なサンプルC#コードを一通り見た。本稿のような短い内容でもSDKに用意されている大半のクラスは登場したので、Leap Motion APIが非常にシンプルで基礎的なライブラリであることが分かる。

 (コードを読んだだけだが)「Leap開発で難しい」と感じたのは、やはり「ジェスチャー情報をどう調理して独自の機能を実装していくか」というところだろう。その部分が開発者の腕の見せどころなのだが、少なからず数学的な素養が必要になってしまうので、特に数学が苦手な筆者のような開発者は大変だろう。しかし、「そのような困難を乗り越えてでもチャレンジする価値が、Leap開発にはある」と筆者は考えている。

 Leap Motion向けにどのようなアプリが開発できるかは現時点で未知数だ。だからこそ、世の中を驚かせるような、誰も見たことがない面白いアプリが開発できる可能性もある。今後、どんなLeapアプリが出てくるか、本当に楽しみである。

Leap Motionの記事を募集しています!

 Leap Motionの解説記事を執筆したい方、開発言語は問いませんので(特にJavaScriptは歓迎)、ぜひお問い合わせに示すメール・アドレス宛に一度ご連絡ください。その際、ご希望のタイトルや連載回数などもお知らせください。よろしくお願いします。

サイトからのお知らせ

Twitterでつぶやこう!