Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
連載:C++で始めるLeap Motion開発 ―― タッチUIの先のカタチ ――

連載:C++で始めるLeap Motion開発 ―― タッチUIの先のカタチ ――

初めてのLeap Motion開発

2015年7月3日 改訂 (初版:2013/7/29)

Leap Motion Developer SDKを利用してC++言語でLeapアプリを開発する方法を、サンプルコードを示しながら解説する連載(2015年改訂版)。SDK提供のサンプルコードを基にLeapアプリ開発の基本的な流れを説明する。

Natural Software 中村 薫

 本連載ではLeap Motion Developer SDKを利用してC++言語でLeap Motionのアプリケーションを開発する方法について、サンプルコードを示しながら解説する。本稿の確認OSはWindows 8.1のみだが、ほぼ同じコードでMac OS Xでも記述できる。

 また、本連載でのサンプルコードは次のリンク先で公開しており、WindowsはVisual Studio Express 2013 for Windows Desktopでの動作確認を行い、プロジェクトファイルを含めてすぐに利用できるようにしてある。

 それではさっそくLeap Motionのアプリケーションを動かしてみよう。Leap Motionの基本的なことがらについては、「C#開発者から見たLeap Motion開発のファースト・インプレッション」を参照していただきたい。また、開発環境の整備には下記のリンク先を参考にしていただきたい。

Understanding the C++ Sample Application

 最初のLeap Motionアプリケーションは、先の「C#開発者から見たLeap Motion開発のファースト・インプレッション」のv2版だ。これはLeap Motion SDKで「Sample.cpp」ファイルとして提供されているコードだ。

 今回はこのコードを基にLeap Motionアプリケーションの基本的な流れについて解説する。今回のサンプルコード全体は、次のリンク先で閲覧できる。

プログラムの実行

 このプログラムを実行すると、(以下に示すように)コンソール画面にLeap Motionからのデータが表示される。

実行画面

main関数の実装

 Leap Motionアプリケーションの基本的なmain()関数(本連載では、「()」と記述されているものは「関数」を指す)の実装コードは次のとおりだ。

C++
int main(int argc, char** argv) {
  // Leap Motionのコントローラーおよびイベントを受け取るリスナー(のサブクラス)を作成する
  SampleListener listener;
  Controller controller;

  // イベントを受け取るリスナーを登録する
  controller.addListener(listener);

  // 起動時の引数に "--bg" が設定されていた場合は、
  // バックグラウンドモード(=アプリケーションがアクティブでない場合にもデータを取得する)
  // で動作させる
  if ( argc > 1 && strcmp(argv[1], "--bg") == 0 ) {
      controller.setPolicy(Leap::Controller::POLICY_BACKGROUND_FRAMES);
  }

  //Enterキーが押されるまでLeap Motionの処理を続ける
  std::cout << "Press Enter to quit..." << std::endl;
  std::cin.get();

  // アプリケーションの終了時にはリスナーを削除する
  controller.removeListener(listener);

  return 0;
}
Leap Motionアプリケーションの基本的なmain()関数の実装コード(Sample.cpp)

 Leap Motionからのデータを取得する方法は2つある。1つはここで紹介したイベント駆動で、リスナーのサブクラスを登録し、リスナーで受け取ったイベントでフレームを処理する方式。もう1つはポーリングで、ループ中にLeap::Controller::frame()を呼び出すことでフレームを処理する方式だ。両者はアプリケーションの実装方法によって選択すればよい。

 なお、Leap::Controller::addListener()により複数のリスナーを登録することで、別々のイベントを処理することも可能だ。

リスナーの実装

 SampleListenerクラスはLeap::Listenerクラスをサブクラス化したクラスで、次のように定義されている。SampleListenerクラスではLeap::Listenerクラスで提供されているイベントから必要なものを実装する。

C++
class SampleListener : public Listener
SampleListenerクラスの定義箇所(Sample.cpp)

 Leap::Listenerクラスでは、下記のイベント処理関数が提供されている。

  • virtual void onInit(const Controller&);
  • virtual void onConnect(const Controller&);
  • virtual void onDisconnect(const Controller&);
  • virtual void onExit(const Controller&);
  • virtual void onFrame(const Controller&);
  • virtual void onFocusGained(const Controller&);
  • virtual void onFocusLost(const Controller&);
  • virtual void onDeviceChange(const Controller&);
  • virtual void onServiceConnect(const Controller&);
  • virtual void onServiceDisconnect(const Controller&);
onInit()

 リスナーの初期化処理を行う。Leap::Controller::addListener()でリスナーを追加したときに呼び出される。

onConnect()

 Leap::ControllerクラスがLeap Motionセンサーと接続されたときに呼び出される。アプリケーション起動時にLeap Motionセンサーが接続されていない場合には、Leap Motionセンサーが接続されたときに呼ばれる。1台のPCに複数のLeap Motionセンサーを接続した場合には、先に接続された方が優先され、後に接続された方は特に通知もされず利用できないようだ。

onDisconnect()

 Leap::ControllerクラスがLeap Motionセンサーから切断されたときに呼び出される。Leap Motionセンサーが物理的に引き抜かれた場合にも呼び出される。

onExit()

 リスナーの終了処理を行う。Leap::Controller::removeListener()でリスナーを削除したときに呼び出される。

onFrame()

 フレームのデータが更新されたときに呼び出される。Leap Motionセンサーのデータは全てここで処理される。

onFocusGained()およびonFocusLost()

 既定ではLeap Motionのフレームデータはアプリケーションウィンドウがアクティブであるとき(=最前面にあるとき)のみ通知される。onFocusGained()はアプリケーションがアクティブになったことを通知し、onFocusLost()はアプリケーションがアクティブでなくなったことを通知する。

 通常のアプリケーションでは問題にならないが、タッチ入力をエミュレートする場合には、アプリケーションがアクティブでない場合がほとんどである(タッチ入力で別のアプリケーションを操作するため)。この設定はLeap::Controller::setPolicyFlags()で変更できる。setPolicyFlags()の引数にはPolicyFlag列挙体を与える。PolicyFlag列挙体には「POLICY_DEFAULT」と「POLICY_BACKGROUND_FRAMES」の2つが定義されている。POLICY_DEFAULTは既定のポリシーでアプリケーションがアクティブであるときのみフレームの更新が通知される。POLIC_BACKGROUND_FRAMESはバックグラウンド、つまりアプリケーションがアクティブではない状態でもフレームの更新が通知される。

onDeviceChange()

 Leap Motionコントローラーの接続/切断が通知される。センサーが接続されているかどうかの判別に利用できる。

onServiceConnect()およびonServiceDisconnect()

 Leap Motionのデータを取得しているWindowsサービスとの接続/切断が通知される。onServiceDisconnect()が通知された場合には、何らかの原因でLeap Motionのデータが取得できなくなったので、サービスやOSの再起動が必要になる。

ジェスチャーの有効化

 Leap Motionからのデータは自動的に取得されるが、ジェスチャーの認識については事前に有効化したジェスチャーのみが検出される。

 本コード内でその実装はonConnect()でなされており、Leap::Controller::enableGesture()で有効にするジェスチャーを指定する。ジェスチャーは、

  • TYPE_SWIPE
  • TYPE_CIRCLE
  • TYPE_SCREEN_TAP
  • TYPE_KEY_TAP

の4種類だ。これらのジェスチャーの動作については、次のイメージだ。

4種類のジェスチャー(Leap Motion SDKのAPIドキュメントから引用)
4種類のジェスチャー(Leap Motion SDKのAPIドキュメントから引用)

フレーム処理

 Leap Motionアプリケーションで一番肝心な部分だ。

 なお、以下で説明するコードではonFrame()の各パラメーターの意味は英語のままにしてある(WindowsとMacをまたいだときに文字コードの問題があるため)。日本語での意味を知りたい場合には「C#開発者から見たLeap Motion開発のファースト・インプレッション」を参照していただきたい。

フレームの取得

 フレーム処理はonFrame()の呼び出し、またはポーリングで行う。どちらの場合も、(次のコード例のように)Leap::Controller::frame()を呼び出し、Leap::Frameクラスのオブジェクトを取得する。このLeap::Frameオブジェクトがフレームでの全てのデータである。

C++
const Frame frame = controller.frame();
Leap::Frameオブジェクトを取得するコード(Sample.cpp)

 Leap::Controller::frame()は、引数を1つ持ち(デフォルトで「0」)、「0」以外の値を入れると、その数だけ前のフレームを取得できる。最大で59フレーム前まで取得可能だ。

 Leap Motionのようなセンサーのアプリケーションを実装する際には、「数フレームを保持して平滑化する」「フレームの履歴からジェスチャーを自作する」など、複数のフレームを利用するケースが多い。Leap Motion Developer SDKではあらかじめそのための機能が提供されているというわけだ。

 1つだけ残念なことに、Leap::Frameオブジェクトをデバッガーで見ても内部の値を直接見ることはできない(次の画面を参照)。値を見るには1つ1つ取得して見るしかないようだ……(これは後述するLeap::HandオブジェクトやLeap::Fingerオブジェクトなどについても同様である)。

Leap::Frameオブジェクトをデバッガーで見る
手の取得

 フレームから手の情報を取得する。

 手の情報は、Leap::Frame::hands()で取得し、HandListオブジェクトが返される。これがフレームで認識されている全ての手の情報だ。

 個別の手の情報は、HandListオブジェクトからインデックス番号で取得できるHandオブジェクトにある(次のコードを参照)。

 HandListはLeap Motion Developer SDKで定義される独自のクラスだが、iteratorおよびbegin()end()を実装しているため、STLライクに書くこともできる(C++11の、範囲に基づくfor文で書くことも可能だ)。これは指を表すFingerListクラスや、ジェスチャーを表すGestureListクラスでも同様だ。

C++
for (HandList::const_iterator hl = hands.begin(); hl != hands.end(); ++hl) {
  // 手を取得する
  const Hand hand = *hl;

  ……省略……
}
手の取得(Sample.cpp)

 Handオブジェクトからは、その手に属する指、手の丸みから表される球の半径(sphereRadius())、手の位置(palmPosition())、手の傾き(direction())などの手の情報が取得できる。手の球についてはイメージが湧きづらいので、Leap Motionのサイトにあるイメージを示す。

手の球(Leap Motion SDKのAPIドキュメントから引用)

 手の検出数について、両手だけというわけではなく、Leap Motionセンサーの画角に入る手であればいくつでも検出できるようだ。筆者の環境では最大4つの手までを確認している(下の画像は、3つを検出している状態だ)。
後の回で紹介するが、いまのバージョンではLeap Motionのカメラの画像を得ることができる。これによってOculus Riftの目にするといった応用事例が生まれている。

手を3つ以上検出できる例
指の取得

 指の一覧の取得方法は2通りあり、フレームで検出した指全体をLeap::Frame::fingers()から取得する方法と、手に属する指をLeap::Hand::fingers()から取得する方法がある。どちらもLeap::FingerListオブジェクトという指の集合で返され、Leap::Handクラス同様にインデックスやイテレーターでアクセスし、個別の指(=Leap::Fingerオブジェクト)を取得して処理する。Leap::FingerクラスはLeap::Pointableクラスから継承され、位置(tipPosition())やタッチの状態(touchZone())などが取得できる。次のコードはその例である。

C++
// 手に属している指の一覧を取得する
const FingerList fingers = hand.fingers();
for (FingerList::const_iterator fl = fingers.begin(); fl != fingers.end(); ++fl) {
  // 指を取得する
  const Finger finger = *fl;

  ……省略……
}
指の取得(Sample.cpp)
ジェスチャーの取得

 ジェスチャーはLeap::Frame::gestures()からGestureListオブジェクトで取得する。手、指と同様に個別のジェスチャーにはインデックスやイテレーターでアクセスし、個別のジェスチャー(=Leap::Gestureオブジェクト)を取得できる。検出したジェスチャーの種類はLeap::Gesture::type()で取得でき、詳細な情報はLeap::Gestureオブジェクトをそれぞれのジェスチャークラス(CircleGestureSwipeGestureKeyTapGestureScreenTapGesture)のオブジェクトに変換することで取得する。ジェスチャーの情報は主に、ジェスチャーの状態、位置、方向となる。以下はその実装例だ。

C++
// 検出したジェスチャーの一覧を取得する
const GestureList gestures = frame.gestures();
for (int g = 0; g < gestures.count(); ++g) {
  Gesture gesture = gestures[g];
 
  switch (gesture.type()) {
    ……省略……
  }
}
ジェスチャーの取得(Sample.cpp)

まとめ

 このようにLeap Motion Developer SDKの肝はフレーム処理であり、フレーム処理も手、指、ジェスチャーとほぼ同じ考え方でデータを処理できるため、コツをつかむと簡単に処理できるだろう。C#のコードと比べてもらうと分かるが、言語間の書き方も大きな差がないため、アプリケーションの目的による言語選択の幅を広げやすいだろう。

 また、Leap Motion Developer SDKではLeap MotionがUI操作を主な機能にしているため、UI操作を行ううえでの機能がさまざま実装されている。特にフレームの履歴やタッチ状態など、類似のSDK(例えばKinect for Windows SDKやIntel RealSense SDK)では自分で実装しなければならない機能が提供されていることが大きい。これによって、アプリケーションの開発効率が上がることだろう。

次回

 次回は手や指の検出方法について解説する。

※以下では、本稿の前後を合わせて5回分(第1回~第5回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

連載:C++で始めるLeap Motion開発 ―― タッチUIの先のカタチ ――
1. 【現在、表示中】≫ 初めてのLeap Motion開発

Leap Motion Developer SDKを利用してC++言語でLeapアプリを開発する方法を、サンプルコードを示しながら解説する連載(2015年改訂版)。SDK提供のサンプルコードを基にLeapアプリ開発の基本的な流れを説明する。

連載:C++で始めるLeap Motion開発 ―― タッチUIの先のカタチ ――
2. Leap SDKで指を検出してみよう(Tracking Hands, Fingers, and Tools)

Leap Motionの最大の特長である手・指を検出するには? Leap Motion Developer SDKを活用した開発方法を詳しく解説。

連載:C++で始めるLeap Motion開発 ―― タッチUIの先のカタチ ――
3. Leap Motionでのタッチ操作はどう開発するのか?

Leapアプリのタッチ操作の認識方法と開発方法を説明。今回のサンプルでは、タッチを表現するためのGUIフレームワークとして「Cinder」を利用する。

連載:C++で始めるLeap Motion開発 ―― タッチUIの先のカタチ ――
4. Leap Motionのカメラ画像を取得する

Leapアプリのカメラ画像の取得方法を説明。今回のサンプルでは、タッチを表現するためのGUIフレームワークとして「Cinder」を利用する。

連載:C++で始めるLeap Motion開発 ―― タッチUIの先のカタチ ――
5. Leap SDKのいろいろな使い方(フレームデータ、イベントなど)

データ取得方式「コールバック」「ポーリング」の選択指針とは? Leap Motionイベントをポーリング方式で処理する方法や、フレーム履歴、手/指IDの取得についても解説。

サイトからのお知らせ