連載:C++で始めるLeap Motion開発 ―― タッチUIの先のカタチ ――
Leap Motionでのタッチ操作はどう開発するのか?
Leapアプリのタッチ操作の認識方法と開発方法を説明。今回のサンプルでは、タッチを表現するためのGUIフレームワークとして「Cinder」を利用する。
第1回および第2回でLeap Motionを利用したアプリケーションの基本的な開発方法および手指の検出について解説した。今回は、よりLeap Motionらしいアプリケーションとして、タッチ操作を扱う。
サンプルコードは、Leap Motionの開発者サイトにある「Touch Emulation」を利用する。また、タッチを表現するためのGUIフレームワークとして「Cinder」(シンダー)と呼ばれるフレームワークを利用する。CinderはC++言語でのアプリケーション開発をより簡単にするフレームワークで、同様のフレームワークにopenFrameworksがある。Leap Motionから提供されるサンプルやデモにはCinderを使って実装されているものが多くある。
今回のサンプルコードも次のリンク先で公開している。WindowsはVisual Studio Express 2013 for Windows Desktopでの動作確認を行い、プロジェクトファイルを含めてすぐに利用できるようにしてある。Cinderについては、下記のリンク先の手順を参考に別途ダウンロードしてほしい。
Touch Emulation
今回は「Touch Emulation」のコードを基に解説する。
このプログラムを実行し、Leap Motion上に手をかざして指を後ろから前に動かすと緑の丸●が現れ、さらに前に動かすと赤●に変わる。丸の色は緑がホバー状態を表し、赤に変わった位置がタッチを表す。Leap Motionのような非接触のユーザーインターフェースをタッチに利用する場合には、タッチしようとする位置が分かりづらいため、ホバーを利用してタッチしようとしている位置をユーザーに教えてあげることで操作に対するストレスが軽減される。以下の画面は、サンプルの実行例である。
タッチの検出は第1回で紹介したScreenTapGesture
クラスでも可能だが、ScreenTapGesture
は(筆者の感覚で)認識が難しいことや、瞬間的なイベントであるため、事前のホバー状態、事後のタッチし続けている状態といった一連のタッチ操作を検出できないことから、Leap Motionを利用したタッチ操作には、今回紹介するLeap::Pointable
クラスを使うことが望ましいと考えている。
Leap Motionを利用したタッチの認識
まずはLeap Motionがどのようにタッチを認識しているかを解説する。
次の図が動作イメージであり、「仮想的なタッチパネル(The virtual touch surface)を持っている」と考えると分かりやすいだろう。仮想的なタッチパネルの前後に状態を判定する空間があり、手前側が「ホバー状態(hovering)」、奥側が「タッチ状態(touching)」を表す。空間の範囲は前後1
~-1
となっているが正確な単位については確認できていない。
コードの解説
本サンプルコードでの注目点はvoid TouchPointsApp::draw()
という関数の中身のみとなり、ここに全てが実装されている。第1回ではListener
クラスを作成してコールバックの形式でフレーム処理を実装した。Leap Motionのフレーム処理には、コールバック形式とポーリング形式の2つがあるが、今回はCinderのループに合わせてポーリング形式でフレームを取得している。
フレームデータを取得する
フレームデータとして、このフレームでのLeap::PointableList
オブジェクトおよびLeap::InteractionBox
オブジェクトを取得する。
Leap::PointableList pointables = leap.frame().pointables();
Leap::InteractionBox iBox = leap.frame().interactionBox();
|
Leap::PointableList
オブジェクトは、名前の通りLeap::Pointable
オブジェクトのリストであり、指やツール(=棒状のもの)をひとまとめにして表している。Leap::Finger
クラスおよびLeap::Tool
クラスは、Leap::Pointable
クラスを継承している。ここではタッチの位置を取得するためにPointableList
オブジェクトを利用する。
Leap::InteractionBox
オブジェクト(=インタラクションボックス)は、Leap Motionで認識できる可動範囲となり、図3のイメージだ。InteractionBox
オブジェクトを利用することで指やツールの位置を実際のディスプレイの座標系に変換できる。
ポイントされた座標を取得する
PointableList
オブジェクトをループで順に処理する。その処理コードは、次のようになっている。
for( int p = 0; p < pointables.count(); p++ ) {
……省略……
Leap::Vector normalizedPosition =
iBox.normalizePoint( pointable.stabilizedTipPosition() );
float x = normalizedPosition.x * windowWidth;
float y = windowHeight - normalizedPosition.y * windowHeight;
……省略……
}
|
Leap::InteractionBox::normalizePoint()
関数にLeap::Pointable::stabilizedTipPosition()
の戻り値を渡してポインターの画面上の位置を取得する。取得した画面上の位置はLeap::Vector
型となり、画面サイズ上でのポインターの位置が0.0
~1.0
の間で表される。これを実際の画面サイズで割り出すが、Leap Motionのスクリーン座標系の原点が左下であるため(図4を参照)、Y座標は高さの値から算出した値を引くことで、左上を原点とする座標にしている。
タッチの状態に合わせた処理を行う
続けてループ内で、タッチの状態に合わせた処理を実施する。この際、タッチの状態はLeap::Pointable::touchDistance()
およびLeap::Pointable::touchZone()
で取得する。
今回のサンプルコードでは、(リスト3のように)取得された状態と座標によって表示される丸の色および位置を変えている。ホバーはタッチされていないが指を認識している状態で、「指を押し込むと、ここがタッチされる」という場所を示すものだ。
// ホバー状態
if ( (pointable.touchDistance() > 0) &&
(pointable.touchZone() != Leap::Pointable::Zone::ZONE_NONE) ) {
gl::color(0, 1, 0, 1 - pointable.touchDistance());
}
// タッチ状態
else if ( pointable.touchDistance() <= 0 ) {
gl::color(1, 0, 0, -pointable.touchDistance());
}
// タッチ対象外
else {
gl::color(0, 0, 1, .05);
}
|
touchDistance()
は下の図の+1
~0
~-1
の値を返す。また、touchZone()
はLeap::Pointable::Zone
型の値を返し、この値は「ホバー中(ZONE_HOVERING)」「タッチ中(ZONE_TOUCHING)」「なし(ZONE_NONE)」、いずれかの状態を指す。
touchDistance()
およびtouchZone()
の状態は同期しており、次のようになっている(以下の説明を読んで、上の図を見直すとよりよく理解できるだろう)。
touchDistance()
が「1」であれば、touchZone()
は「ZONE_NONE」touchDistance()
が「1」以下、かつ「0」より大きい値であれば、touchZone()
は「ZONE_HOVERING」touchDistance()
が「0」以下、かつ「-1」より大きい値であれば、touchZone()
は「ZONE_TOUCHING」
伸びている指やツールのみを検出させる。
「これは快適! Leap Motion v2で格段に良くなったSkeletal Tracking機能」という記事でLeap Motion SDK v2について解説した。
v2では指を開いても閉じても5本の指を検出し続けるようになった。これによって、指を閉じた(=グーの状態)で手を突き出してもタッチとして認識してしまう。これは多くの場合、意図しない動作だろう。そこで伸びている指およびツールのみをタッチとして認識するようにさせる。
先の記事でも解説しているが、Leap::Pointable::isExtended()
という関数で、伸びているかどうかの状態を取得できる。これを使って次のようにコードを追加することで、意図する動作に変更できる。なお、Leap::Finger
クラス(指)およびLeap::Tool
クラス(ツール)は Leap::Pointable
クラスを継承しているため、同様にisExtended()
を使うことができる。
for( int p = 0; p < pointables.count(); p++ ) {
Leap::Pointable pointable = pointables[p];
// ここから追加
// 伸びている指、ツール以外は無視する
if ( !pointable.isExtended() ) {
continue;
}
// ここまで追加
……省略……
}
|
これによって、人差し指1本だけを伸ばしたタッチでは1つの円のみが表示される(図6)。先ほど追加したコードを入れたり外したりして、動作の違いを確認するとよい。
まとめ
以上で今回の解説は終了だ。モーション・センサー・デバイスのSDKでは、ユーザーインターフェースへの活用が多いため、デバイスの座標系からスクリーンの座標系に変換する実装を多く行う。Leap Motion Developer SDKではこの座標系変換についても、より簡単に行える。実装者はLeap Motionの視野角を含めた見え方と、それをスクリーンに投影する際の変換イメージを持つことで、より簡単に扱えるだろう。
※以下では、本稿の前後を合わせて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の取得についても解説。