連載:C++で始めるLeap Motion開発 ―― タッチUIの先のカタチ ――
初めてのLeap Motion開発
Leap Motion Developer SDKを利用してC++言語でLeapアプリを開発する方法を、サンプルコードを示しながら解説する連載(2015年改訂版)。SDK提供のサンプルコードを基にLeapアプリ開発の基本的な流れを説明する。
本連載では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()
関数(※本連載では、「()」と記述されているものは「関数」を指す)の実装コードは次のとおりだ。
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からのデータを取得する方法は2つある。1つはここで紹介したイベント駆動で、リスナーのサブクラスを登録し、リスナーで受け取ったイベントでフレームを処理する方式。もう1つはポーリングで、ループ中にLeap::Controller::frame()
を呼び出すことでフレームを処理する方式だ。両者はアプリケーションの実装方法によって選択すればよい。
なお、Leap::Controller::addListener()
により複数のリスナーを登録することで、別々のイベントを処理することも可能だ。
リスナーの実装
SampleListener
クラスはLeap::Listener
クラスをサブクラス化したクラスで、次のように定義されている。SampleListener
クラスではLeap::Listener
クラスで提供されているイベントから必要なものを実装する。
class SampleListener : public Listener |
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種類だ。これらのジェスチャーの動作については、次のイメージだ。
フレーム処理
Leap Motionアプリケーションで一番肝心な部分だ。
なお、以下で説明するコードではonFrame()
の各パラメーターの意味は英語のままにしてある(※WindowsとMacをまたいだときに文字コードの問題があるため)。日本語での意味を知りたい場合には「C#開発者から見たLeap Motion開発のファースト・インプレッション」を参照していただきたい。
フレームの取得
フレーム処理はonFrame()
の呼び出し、またはポーリングで行う。どちらの場合も、(次のコード例のように)Leap::Controller::frame()
を呼び出し、Leap::Frame
クラスのオブジェクトを取得する。このLeap::Frame
オブジェクトがフレームでの全てのデータである。
const Frame frame = controller.frame(); |
Leap::Controller::frame()
は、引数を1つ持ち(デフォルトで「0」)、「0」以外の値を入れると、その数だけ前のフレームを取得できる。最大で59フレーム前まで取得可能だ。
Leap Motionのようなセンサーのアプリケーションを実装する際には、「数フレームを保持して平滑化する」「フレームの履歴からジェスチャーを自作する」など、複数のフレームを利用するケースが多い。Leap Motion Developer SDKではあらかじめそのための機能が提供されているというわけだ。
1つだけ残念なことに、Leap::Frame
オブジェクトをデバッガーで見ても内部の値を直接見ることはできない(次の画面を参照)。値を見るには1つ1つ取得して見るしかないようだ……(これは後述するLeap::Hand
オブジェクトやLeap::Finger
オブジェクトなどについても同様である)。
手の取得
フレームから手の情報を取得する。
手の情報は、Leap::Frame::hands()
で取得し、HandList
オブジェクトが返される。これがフレームで認識されている全ての手の情報だ。
個別の手の情報は、HandList
オブジェクトからインデックス番号で取得できるHand
オブジェクトにある(次のコードを参照)。
HandList
はLeap Motion Developer SDKで定義される独自のクラスだが、iterator
およびbegin()
、end()
を実装しているため、STLライクに書くこともできる(C++11の、範囲に基づくfor文で書くことも可能だ)。これは指を表すFingerList
クラスや、ジェスチャーを表すGestureList
クラスでも同様だ。
for (HandList::const_iterator hl = hands.begin(); hl != hands.end(); ++hl) { // 手を取得する const Hand hand = *hl; ……省略…… } |
Hand
オブジェクトからは、その手に属する指、手の丸みから表される球の半径(sphereRadius()
)、手の位置(palmPosition()
)、手の傾き(direction()
)などの手の情報が取得できる。手の球についてはイメージが湧きづらいので、Leap Motionのサイトにあるイメージを示す。
手の検出数について、両手だけというわけではなく、Leap Motionセンサーの画角に入る手であればいくつでも検出できるようだ。筆者の環境では最大4つの手までを確認している(下の画像は、3つを検出している状態だ)。
後の回で紹介するが、いまのバージョンではLeap Motionのカメラの画像を得ることができる。これによってOculus Riftの目にするといった応用事例が生まれている。
指の取得
指の一覧の取得方法は2通りあり、フレームで検出した指全体をLeap::Frame::fingers()
から取得する方法と、手に属する指をLeap::Hand::fingers()
から取得する方法がある。どちらもLeap::FingerList
オブジェクトという指の集合で返され、Leap::Hand
クラス同様にインデックスやイテレーターでアクセスし、個別の指(=Leap::Finger
オブジェクト)を取得して処理する。Leap::Finger
クラスはLeap::Pointable
クラスから継承され、位置(tipPosition()
)やタッチの状態(touchZone()
)などが取得できる。次のコードはその例である。
// 手に属している指の一覧を取得する const FingerList fingers = hand.fingers(); for (FingerList::const_iterator fl = fingers.begin(); fl != fingers.end(); ++fl) { // 指を取得する const Finger finger = *fl; ……省略…… } |
ジェスチャーの取得
ジェスチャーはLeap::Frame::gestures()
からGestureList
オブジェクトで取得する。手、指と同様に個別のジェスチャーにはインデックスやイテレーターでアクセスし、個別のジェスチャー(=Leap::Gesture
オブジェクト)を取得できる。検出したジェスチャーの種類はLeap::Gesture::type()
で取得でき、詳細な情報はLeap::Gesture
オブジェクトをそれぞれのジェスチャークラス(CircleGesture
/SwipeGesture
/KeyTapGesture
/ScreenTapGesture
)のオブジェクトに変換することで取得する。ジェスチャーの情報は主に、ジェスチャーの状態、位置、方向となる。以下はその実装例だ。
// 検出したジェスチャーの一覧を取得する const GestureList gestures = frame.gestures(); for (int g = 0; g < gestures.count(); ++g) { Gesture gesture = gestures[g]; switch (gesture.type()) { ……省略…… } } |
まとめ
このように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]を参照してください。
1. 【現在、表示中】≫ 初めてのLeap Motion開発
Leap Motion Developer SDKを利用してC++言語でLeapアプリを開発する方法を、サンプルコードを示しながら解説する連載(2015年改訂版)。SDK提供のサンプルコードを基にLeapアプリ開発の基本的な流れを説明する。
2. Leap SDKで指を検出してみよう(Tracking Hands, Fingers, and Tools)
Leap Motionの最大の特長である手・指を検出するには? Leap Motion Developer SDKを活用した開発方法を詳しく解説。
3. Leap Motionでのタッチ操作はどう開発するのか?
Leapアプリのタッチ操作の認識方法と開発方法を説明。今回のサンプルでは、タッチを表現するためのGUIフレームワークとして「Cinder」を利用する。
5. Leap SDKのいろいろな使い方(フレームデータ、イベントなど)
データ取得方式「コールバック」「ポーリング」の選択指針とは? Leap Motionイベントをポーリング方式で処理する方法や、フレーム履歴、手/指IDの取得についても解説。