Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
Leap MotionでWindows 8を操作するアプリの開発[C++]

Leap MotionでWindows 8を操作するアプリの開発[C++]

  • このエントリーをはてなブックマークに追加

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

【obsolete: 旧コンテンツ】 これまでの連載内容の総括として、Leap MotionによりタッチレスでWindows 8を操作できるアプリを作成する。

Natural Software 中村 薫
2013年8月12日
本稿はLeap Motion SDK バージョン1に基づいた記事です。すでにバージョン2の連載記事に改訂しており、その改訂の際に本稿の内容はカットされました。同じLeap Motionデバイスで、最新のSDKバージョン2を利用できますので、最新版SDKをご利用ください。

 最終回*1である今回は、Leap MotionでWindows 8を操作するアプリケーションを作成する。コードはWindows 8のみで動作するアプリケーションとなる(Windows 7以前では動作しない)ことをご了承いただきたい。

  • *1 編集部注: 本連載は、さらに数回、記事コンテンツを追加して延長する予定です。

 ご存じのとおりWindows 8は、従来のWindows OSと比べてタッチUIへの親和性を高めたOSだ。Leap Motionは前回までの解説のとおり、タッチUIを実装しやすいハードウェアおよびソフトウェアとなっている。とすれば、Leap MotionにタッチUIを組み合わせたいと思うだろう。Windows 8にはキーボードやマウス入力と同様にタッチ入力を行うためのAPIが用意されている。それを利用してLeap MotionでWindows 8を操作してみよう。

 全体のコードは次のURLで公開しているので、参照しながら解説を読み進めてほしい(「Touchless」が今回のプロジェクトだ)。

今回のゴール

 次の動画のようなアプリケーションを作成する。Leap Motionのストアでも提供されている「Touchless For Windows」の簡易版だ。

今回作成するアプリの実行例

タッチ・アプリケーションの開発

 Touchlessプロジェクトは次の3つのファイルから成っている。

  • TouchInput.h
  • LeapTouch.h
  • main.cpp

 TouchInput.hファイルは、Windowsのタッチ入力APIであるInjectTouchInput()関数(以下、「()」と記述されているものは「関数」を指す)をラップしたTouchInputクラスを実装している。このクラスを使うことでWindowsのタッチ入力をアプリケーションから発行できるようになる。もちろん汎用的な機能になるため、Kinectなどほかの機器からの入力をタッチ入力に置き換えることも可能だ。

 LeapTouch.hはTouchInputクラスを、Leap Motion Developer SDK(以下、Leap SDKとする)に合わせたラッパー・クラスだ。

 最後のmain.cppはmain関数やLeap SDKのLeap::Listenerを実装している。

 なおInjectTouchInput()は、本題から外れるため解説は行わない。興味がある方は、次のリンク先の記事を参照していただきたい。

SampleListenerクラス

 Leap Motionの処理を担当するSampleListenerクラスの解説を行う。SampleListenerクラスは、Leap::Listenerクラスを継承している、Leap Motionのデータを受け取って処理するクラスだ。

●メンバー変数

 メンバー変数として次の3つが宣言されている。変数「touch」はタッチ入力を行うクラスのオブジェクト。変数「windowWidth」および「windowHeight」はスクリーン(=ディスプレイ)の幅と高さでLeap Motionの位置をスクリーン座標に置き換えるために利用する。

C++
LeapTouch touch;
 
int windowWidth;
int windowHeight;
SampleListenerクラスのメンバー変数
●初期化処理(onInit()関数)

 今回のアプリケーションは、主にバックグラウンドで動作する。Leap SDKの既定の設定では、Leap Motionからのデータはアクティブなウィンドウ(=ウィンドウが選択されている状態)のみに通知される。アプリケーションがバックグラウンドでもデータを受け取れるように、Leap::Controller::setPolicyFlags()の引数に「Leap::Controller::PolicyFlag::POLICY_BACKGROUND_FRAMES」を指定する(次のコードを参照)。

C++
controller.setPolicyFlags( Leap::Controller::PolicyFlag::POLICY_BACKGROUND_FRAMES );
Leap Motionのデータをバックグラウンドでも受け取れるようにする

 次に、スクリーン(=ディスプレイ)の幅と高さを取得する(次のコードを参照)。Leap Motionからのスクリーン座標は、スクリーンに対する位置の割合(「0.0」~「1.0」の範囲)で取得されるので、それを実際のスクリーン座標に置き換える。

C++
auto screen = controller.locatedScreens()[0];
windowWidth = screen.widthPixels();
windowHeight = screen.heightPixels();
スクリーンの幅と高さを取得する

 Windows 8の[スタート]画面やWindowsストア・アプリ、デスクトップと行き来にはチャームを使うことが多いが、チャームの動作が難しい(具体的には、スクリーン右端から左にはじくような操作の)ため、円のジェスチャーで[Windows]キーを押して[スタート]画面に戻るよう実装する。そのため、次のコードのようにして円ジェスチャーの認識を有効にする。

C++
controller.enableGesture( Leap::Gesture::Type::TYPE_CIRCLE );
円ジェスチャーの認識を有効にする

 最後にタッチ入力の初期化を行う(次のコードを参照)。今回は1点のみのタッチ入力を行うが、APIとしては10点までの入力をサポートしている。興味があれば指10本を使った10点マルチタッチができるアプリに改造してほしい。

C++
TouchInput::Initialize();
タッチ入力の初期化を行う
●フレームの更新処理(onFrame()関数)

 まず、更新されたフレームとタッチに利用するポインターを取得する。ポインターが無ければ、このフレームの処理を終了する(次のコードを参照)。

C++
// 新しいフレームを取得する
auto frame = controller.frame();
 
// ポインターを取得する
Leap::PointableList pointables = frame.pointables();
if ( pointables.empty() ) {
  return;
}
新しいフレームとポインターを取得する

 次に、下記のコードを参照してほしい。最初のポインターの位置をタッチの位置とする。ポインターを取得し、InteractionBoxオブジェクトを利用して座標の変換を行う。座標変換された値は、スクリーンに対する位置の割合(「0.0」~「1.0」の範囲)になるので、最初に取得したスクリーンの幅と高さから、実際のスクリーン座標を取得する。タッチ入力に利用できるスクリーン座標は「0」~「スクリーンの幅-1」となっている。スクリーン座標への変換方法は第3回で解説したとおり、いくつかの種類があるので、用途に合わせて選択してほしい。

C++
auto& pointable = pointables[0];
Leap::Vector normalizedPosition = frame.interactionBox()
                                 .normalizePoint( pointable.stabilizedTipPosition() );
int x = (int)min( normalizedPosition.x * windowWidth, windowWidth - 1 );
int y = (int)min( windowHeight - normalizedPosition.y * windowHeight, windowHeight - 1 );
最初のポインターの位置をタッチの位置とする

 タッチする座標が決まったら、第2回で解説したLeap Motionのタッチ入力判定を使って、Windowsにタッチ入力を発行する(次のコードを参照)。

C++
// ホバー
if ( pointable.touchDistance() > 0 &&
   pointable.touchZone() != Leap::Pointable::Zone::ZONE_NONE) {
  touch.hover( x, y );
}
// タッチ
else if( pointable.touchDistance() <= 0 ) {
  touch.touch( x, y );
}
// タッチしてない
else {
  touch.up();
}
タッチ入力を発行する

 最後にジェスチャーの処理を行う。検出したジェスチャーの一覧に円のジェスチャーがあれば、[Windows]キーを押す処理を行う。これによって、円のジェスチャーでスタート画面への行き来ができるようになる。

C++
// 円のジェスチャーで[Windows]キーを押し、[スタート]画面に戻る
for ( auto g : frame.gestures() ) {
  if ( (g.type() == Leap::Gesture::Type::TYPE_CIRCLE) &&
     (g.state() == Leap::Gesture::State::STATE_STOP) ) {
    keybd_event( VK_LWIN, 0, 0, 0 );
    keybd_event( VK_LWIN, 0, KEYEVENTF_KEYUP, 0 );
    break;
  }
}
円のジェスチャーで[Windows]キーを押し、[スタート]画面に戻る

 以上でLeap Motionを利用したタッチ・アプリケーションを実装できた。WindowsのAPIとLeap MotionのSDKは、ともに高度な機能を簡単なAPIで提供してくれているので、少ないコードで実装できる。

まとめ

 実際に動作させてみると、タッチレスというのは思いの外、操作が難しい。Kinectからの流れではあるが、人間の動作というのは外部からの刺激による高速な学習フィードバックが回っていることを感じさせる。Leap Motionのようなデバイスを活用するためには、入力デバイスの進化だけでなく、例えば立体ディスプレイや、非接触で触覚を得られるデバイスといった、出力側のデバイスの進化も必要だろう。これらの出力装置は実際に世の中に出始めており、出力を含めたコンピューティング環境全体が進化することで、タッチUIの先のカタチ、人とコンピューターとの新たな形が見えてくるだろう。

サイトからのお知らせ

Twitterでつぶやこう!