Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
Leap Motionの座標系の変換とスクリーンを理解する[C++]

Leap Motionの座標系の変換とスクリーンを理解する[C++]

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

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

【obsolete: 旧コンテンツ】 Leap Motionからのタッチ入力を実装するための前準備として、Leap Motionの座標系の変換やスクリーンについて解説する。

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

 前回は、Leap Motionを利用してタッチ入力を認識するアプリケーションを作成した。次回の最終回では、Leap Motionからのタッチ入力をWindows 8のタッチ入力として認識させる。今回はその準備として、Leap Motionの座標系の変換やスクリーンについて解説する。

 今回のサンプル・コードは前回のコードに加える形になっている。サンプル・コードは次のリンク先で公開している。Windows環境ではVisual Studio Express 2012 for Windows Desktopで、Mac OS X環境ではXcode 4.6.3での動作確認を行い、プロジェクト・ファイルを含めてすぐに利用できるようにしてある。

Leap Motion上で認識されるスクリーン

 座標系を解説する前に、Leap Motionが認識しているスクリーン(=ディスプレイ)について知る必要がある。

 Leap::ControllerクラスにはlocatedScreens()関数(以降、「()」と記述されているものは「関数」を指す)というスクリーン一覧(=Leap::ScreenListオブジェクト)を取得する機能がある。ちなみに、同様の機能を持つ関数としてcalibratedScreens()があるが、これは“Deprecated”(=推奨されない)とされ、「locatedScreens()を利用するように」と記載されている。

 次のようなコードで、スクリーン情報を表示できる。

C++
std::stringstream ss;
ss << "Screen Information" << std::endl;
for ( auto screen : leap.locatedScreens() ) {
ss << screen.widthPixels() << ", " << screen.heightPixels() << std::endl;
ss << "Horizontal axis: " << screen.horizontalAxis() << std::endl;
ss << "Vertical axis: " << screen.verticalAxis() << std::endl;
ss << "Bottom left corner: " << screen.bottomLeftCorner() << std::endl;
}
gl::drawString( ss.str(), Vec2f( 0, 0 ), ColorA( 0, 0, 0, 1 ) );
スクリーン情報を表示する(main.cpp)

このコードを実際に試したい場合は、前回のコードのdraw関数の最後にこれを追記すればよい。

 Leap::ScreenListクラスは、個別のスクリーン情報を持つLeap::Screenクラスのイテレーターを持ち、これをfor文で回すことで、スクリーン情報を表示できる。上記のコードではlocatedScreens()以外に5つの座標に関する関数が使われている。それぞれの意味を次に示す。

  • widthPixels() : スクリーンの幅(ピクセル単位)
  • heightPixels() : スクリーンの高さ(ピクセル単位)
  • horizontalAxis() : スクリーンの物理的な幅(Leap Motionコントローラーを中心とした物理的な位置。ミリメートル単位)
  • verticalAxis() : スクリーンの物理的な高さ(Leap Motionコントローラーを中心とした物理的な位置。ミリメートル単位)
  • bottomLeftCorner() : スクリーンの左下の位置座標(Leap Motionコントローラーを中心とした物理的な位置。ミリメートル単位)

 このようにLeap Motionで利用されている座標の単位は2つあり、ピクセル単位(以降、px)とミリメートル単位(以降、mm)となる。

 例えば筆者が利用している1920×1080のディスプレイを持つWindowsマシンおよび、1366×768のディスプレイを持つMacBook Air 11インチで上記のコードを動作すると、次のような結果になる。

Screen Information
1920, 1080
Horizontal axis: (400, 0, 0)
Vertical axis: (0, 250, 0)
Bottom left corner: (-200, 50, -200)
スクリーン情報を表示するコードを、1920×1080のディスプレイを持つWindowsマシンで動作させた例
Screen Information
1366, 768
Horizontal axis: (400, 0, 0)
Vertical axis: (0, 250, 0)
Bottom left corner: (-200, 50, -200)
スクリーン情報を表示するコードを、1366×768のディスプレイを持つMacBook Air 11インチで動作させた例

 例えばWindowsマシンの出力結果を見ると、widthPixels()およびheightPixels()は、「幅: 1920」「高さ: 1080」(ピクセル単位)というスクリーン・サイズの値を返している。horizontalAxis()およびverticalAxis()の出力を見ると、スクリーンの物理的な幅と高さは「幅: 400mm」「高さ: 250mm」となっている。

 bottomLeftCorner()が返す値には特に注意が必要だ。繰り返しになるが、この値はLeap Motionを中心としてスクリーンの左下の位置を表す。Leap Motionの座標系は右手座標系であり、それぞれの方向は次のようになっている。上の2つの出力結果では「(-200, 50, -200)」となっているため、「左: 200mm」「上: 50mm」「奥: 200mm」の位置ということなる。

Leap Motionの座標系(Leap Motion SDKのAPIドキュメントから引用)
Leap Motionの座標系(Leap Motion SDKのAPIドキュメントから引用)

さまざまな座標変換

 ここから本題に入る。Leap Motionから取得できる座標の変換方法は下記の3種類がある。

 (1)InteractionBoxオブジェクトを利用した座標変換
 (2)インターセクション・ポイント(Intersection Point)の座標変換
 (3)プロジェクション・ポイント(Projection Point)の座標変換

 座標の種類は、先に解説した「ピクセル単位」および「ミリメートル単位」のものになる。「ピクセル単位」の座標系に変換する場合は、指をどのように認識するかによって、「(1)(前回紹介した)インタラクション・ボックスを使用した座標変換」と「(2)インターセクション・ポイントの座標変換」のうち、適切な手段を選択する。「ミリメートル単位」の座標系に変換する場合は、「(3)プロジェクション・ポイント」だ。

 それぞれについて詳しく説明しよう。

(1)InteractionBoxオブジェクトを利用した座標変換

 InteractionBoxオブジェクトを使えば、指の位置をピクセル座標系に変換し、指がスクリーンのどの位置にあるかという値を取得できる。次のコードはその利用例である。

C++
Leap::Vector normalizedPosition = iBox.normalizePoint(pointable.stabilizedTipPosition());
 
float x = normalizedPosition.x * windowWidth;
float y = windowHeight - normalizedPosition.y * windowHeight;
InteractionBoxオブジェクトを利用した座標変換

(2)インターセクション・ポイント(Intersection Point)の座標変換

 Leap::Screen::intersect()関数を使えば、指の位置に加えて指の方向を認識されて、指が向いている先のスクリーン・パネルと交差する点の座標(=インターセクション・ポイント)を取得できる。次の図の「A」と「B」はスクリーンと交差している緑色の○がインターセクション・ポイントになる。

インターセクション・ポイント(Leap Motion SDKのAPIドキュメントから引用)
インターセクション・ポイント(Leap Motion SDKのAPIドキュメントから引用)

ポインターAは、物理スクリーン領域外にあるスクリーン・パネルとのインターセクション。

ポインターBは、スクリーンとの直接的なインターセクション。

ポインターCは、スクリーン・パネルと交差しない。

 次のコードは、インターセクション・ポイントの座標変換を行う例だ。

C++
Leap::Vector normalizedCoordinates = screen.intersect(pointable, true);
 
float x = normalizedCoordinates.x * windowWidth;
float y = windowHeight - normalizedCoordinates.y * windowHeight;
Leap::Screen::intersect()を利用したインターセクション・ポイントの座標変換

 「(1)(前回紹介した)インタラクション・ボックスを使用した座標変換」と「(2)インターセクション・ポイントの座標変換」のどちらも、次の図のように左下を原点とする座標となっている。実行して座標を確認してみると、指を正面に向けているときは両方の座標がほぼ同じ位置を差し、指を上下左右に動かしてみると、両方の座標は変化する。特にインターセクション・ポイントは指先方向を見るため、手の位置を固定しても指の動きだけで座標が大きく変化することが分かるだろう。

Leap Motionのスクリーン座標系(Leap Motion SDKのAPIドキュメントから引用)

(3)プロジェクション・ポイント(Projection Point)の座標変換

 プロジェクション・ポイントでは、次の図に示すように、スクリーン・パネル上に投影された指の点を「ミリメートル単位」の座標に変換する。

プロジェクション・ポイント(Leap Motion SDKのAPIドキュメントから引用)
プロジェクション・ポイント(Leap Motion SDKのAPIドキュメントから引用)

ポイントAは、スクリーンに直接、投影されている。

ポイントBは、物理スクリーン領域外にあるスクリーン・パネルに投影されている。

 プロジェクション・ポイントは次のコードのように利用する。

C++
Leap::Vector screenProjection = screen.project(pointable.tipPosition(), false);
 
float x = screenProjection.x;
float y = screenProjection.y;
Leap::Screen::project()を利用したプロジェクション・ポイントの座標変換

 こちらは原点がLeap Motionの位置になるため、一般的なLeap Motionをスクリーンの中心に置いた場合、X座標の「0」がスクリーンの中心、Y座標の値がLeap Motionからの高さの位置になる。

まとめ

 このようにLeap Motionで変換できる座標系および手・指の扱いにはいくつかの種類がある。目的のアプリケーションに最適な動きを採用していただきたい。

 次回は最終回として、Leap Motionの動きをWindows 8にタッチとして入力することで、Leap MotionでWindows 8を操作するアプリケーションの開発について解説する。

サイトからのお知らせ

Twitterでつぶやこう!