連載:Leap Motion開発入門(C#編)
フレームのいろいろな使い方
Leap Motion公式のサンプルを題材に、さまざまなフレームの扱い方についてコードを交えて解説。※C++編の「Leap SDKのいろいろな使い方」と基本的な内容は同じです。
今回は「Getting Frame Dataサンプル(英語)」を題材にしたさまざまなフレームの扱い方についてコードを交えて解説する。
フレームデータを扱い方
Leap SDKからのデータの取得方法には、次の2つの方式がある。
1Leap.Listener
クラスを継承したクラスを実装し、必要なイベント(の仮想関数)をLeap.Listener
クラスからオーバーライドして通知を受けるコールバック方式
2メインループを実装し、その中で更新されたフレームの取得を行うポーリング方式
実現できることは同じなので、言語やフレームワーク、アプリケーションによって選択してほしい。使い分けの例として次のような考え方を示す。
1コールバック方式
・C++でフレームワークは使わない場合。自分でメインループを実装してもいいが、コードが増えてきたときにクラス化されていた方が何かと実装しやすい
・C#でWPFを使って実装する場合(1)。Leap.Listener
クラスにあるイベントを利用する
2ポーリング方式
・C++でOpenGLを使って実装する場合。OpenGLのDrawのタイミングでデータを取得し、処理する
・C#でWPFを使って実装する場合(2)。C#の「Touch Emulationサンプル(英語)」にもあるように、レンダリング(=CompositionTarget
クラス(System.Windows.Media
名前空間)のRendering
イベント)のタイミングで処理する
・C#でUnityのスクリプトを記述する場合
第1回でも解説したとおり、Leap SDKからコールバック形式でデータを受け取る場合、表1のイベントが発生する。今回は、これらをポーリング方式で実装する場合について解説する。
イベント | 意味 |
---|---|
OnInit() | アプリケーションの初期化時に呼ばれる |
OnConnect() | Leap Motionコントローラーが接続された時に呼ばれる |
OnDisconnect() | Leap Motionコントローラーが抜かれた時に呼ばれる |
OnExit() | アプリケーションの終了時に呼ばれる |
OnFrame() | フレームの更新時に呼ばれる |
OnFocusGained() | アプリケーションのフォーカスが有効になった時に呼ばれる(デフォルトモードでは、このイベント以降、フレームの更新が行われる) |
OnFocusLost() | アプリケーションのフォーカスが無効になった時に呼ばれる(デフォルトモードでは、このイベント以降、フレームの更新が停止する) |
OnDeviceChange() | Leap Motionコントローラーが接続/切断された時に呼ばれる |
OnServiceConnect() | Leap Motionのデータを取得しているWindowsサービスに接続した時に呼ばれる |
OnServiceDisconnect() | Leap Motionのデータを取得しているWindowsサービスに切断された時に呼ばれる |
[イベント]列でOnInit()
のように()
が記述されているものは「メソッド」を指す。以下、()
は同様に「メソッド」を表すものとする。
なお、コールバック方式での実装方法は、次の記事を参照していただきたい。
Leap Motionで発生するイベントを「ポーリング方式」で処理する場合
表1に示す各イベントの処理をポーリング方式で行うために、次のように考える。
OnInit()
およびOnExit()
は、アプリケーションの初期化および終了処理になるので、メインループの前後に行う。
OnFrame()
はフレームの更新処理なので、先ほど説明したように「レンダリングのタイミング」や「自作のメインループ」の中で行う。取得したフレームにはフレームIDがあり、フレームが更新されるとID値がインクリメントされる。前回のフレームIDと異なったときに、新しいフレームと判断して処理する。
次のコードは、ポーリング方式を実現する最もシンプルなコンソールアプリケーションのコードの一部(=Main()
の中身)である。この例では、while
文により無限ループ(=メインループ)を作り、その中でフレームの更新処理を行っている。
Controller leap = new Controller();
long previousFrameId = -1;
// 初期化処理(OnInit()相当)をここに書く
// 無限ループ内で、前回のフレームのIDと比較して新しいフレームを取得する
while ( true ) {
var frame = leap.Frame();
if ( previousFrameId == frame.Id ) {
continue;
}
previousFrameId = frame.Id;
Console.WriteLine( "Frame ID : " + frame.Id );
// フレーム更新処理(OnFrame()相当)をここに書く
}
// 終了処理(OnExit()相当)をここに書く
|
while
文により無限ループにしているが、Leap.Controller.Frame()
により得られるフレームは5ミリ秒(以下、単位は「msec」と表記)に1回更新のペースなので(詳細後述)、スリープ処理を入れなくてもCPUが100%になる心配はない。
それでは、これ以外のイベント、接続状態を表すOnConnect()
およびOnDisconnect()
、Leap Motionサービスとの接続状態を表すOnServiceConnect()
およびOnServiceDisconnect()
、フォーカス状態を表すOnFocusGained()
およびOnFocusLost()
は、どのように実装すればよいのか。
Leap Motionコントローラーとの接続状態はLeap.Controller.IsConnected
プロパティで、Leap Motionサービスとの接続状態はLeap.Controller.IsServiceConnected()
で、取得できる。bool
型の戻り値がtrueであれば接続されている、falseであれば接続されていないことを表す。フォーカス状態も同じようにLeap.Controller.HasFocus
プロパティで取得可能だ。やはりbool
型の戻り値がtrueであればフォーカスが有効、falseであればフォーカスが無効であることを表す。
この状態を利用することで、上記の各種イベント処理と同じ処理を実装可能だ。それぞれの実装イメージは次のとおりだ
Controller leap = new Controller();
bool isPrevConnected = false;
bool isPrevServiceConnected = false;
bool hadPrevFocus = false;
long previousFrameId = -1;
// 初期化処理(OnInit()相当)をここに書く
// 無限ループ内で、前回のフレームのIDと比較して新しいフレームを取得する
while ( true ) {
var frame = leap.Frame();
// Leap Motionコントローラーとの接続状態を確認する
{
bool isCurrentConnected = leap.IsConnected;
if ( isPrevConnected != isCurrentConnected ) {
if ( isCurrentConnected ) {
// Leap Motionコントローラーが接続された(OnConnected()相当)
Console.WriteLine( "Leap Motion connected." );
}
else {
// Leap Motionコントローラーが抜かれた(OnDisconnected()相当)
Console.WriteLine( "Leap Motion disconnected." );
}
}
// 今回の接続状態を保持する
isPrevConnected = isCurrentConnected;
}
// Leap Motionサービスとの接続状態を確認する
{
bool isCurrentServiceConnected = leap.IsServiceConnected();
if ( isPrevServiceConnected != isCurrentServiceConnected ) {
if ( isCurrentServiceConnected ) {
// Leap Motionサービスが接続された(onServiceConnect()相当)
Console.WriteLine( "Leap Motion Service connected." );
}
else {
// Leap Motionサービスが切断された(onServiceDisconnect()相当)
Console.WriteLine( "Leap Motion Service disconnected." );
}
}
isPrevServiceConnected = isCurrentServiceConnected;
}
// フォーカス状態を確認する
{
bool hadCurrentFocus = leap.HasFocus;
if ( hadPrevFocus != hadCurrentFocus ) {
if ( hadCurrentFocus ) {
// アプリケーションのフォーカスが有効になった(OnFocusGained()相当)
Console.WriteLine( "Focus gained." );
}
else {
// アプリケーションのフォーカスが無効になった(OnFocusLost()相当)
Console.WriteLine( "Focus lost." );
}
}
// 今回のフォーカス状態を保持する
hadPrevFocus = hadCurrentFocus;
}
// フレームが更新されていなければ何もしない
{
if ( previousFrameId == frame.Id ) {
continue;
}
previousFrameId = frame.Id;
}
// フレーム更新処理(onFrame()相当)をここに書く
}
// 終了処理(OnExit()相当)をここに書く
|
Leap Motionサービスの接続/切断については、Windowsのコントロールパネルなどから[ローカル サービスの表示]を起動して、「Leap Service」を探そう(図1)。このサービスを停止すると、OnDisconnect()
およびOnServiceDisconnect()
が発生し、サービスを起動するとOnServiceConnect()
およびOnConnect()
が発生する(それぞれ発生順)。
フレームの詳細
フレームについて詳しく解説しよう。
今までは更新されたフレームのみ処理してきた。Leap SDKは、現在のフレームの他に59フレーム前までのフレーム履歴を持っている。これによって、フレームをまたいだ処理(=平滑化や移動量の計算)を簡単に行える。
また、Leap Motionは非常に速い周期でデータを更新するので*1、全てのフレームを更新タイミングで処理することは難しい。履歴を取得できることによって、複数のフレームをまとめて処理することもできる。
- *1 速い場合には200FPS以上(※FPS=1秒間のフレーム数)。つまり、5ミリ秒(msec)に1回更新される速度だ。
過去のフレームを取得するには、Leap.Controller.Frame()
の引数に取得したいフレーム番号を渡す。最新のフレームは0
となりデフォルト引数として設定されている。1フレーム前を取得したいならleap.Frame( 1 )
、10フレーム前を取得したいならleap.Frame( 10 )
という形だ。例として、現在のフレームと、過去5フレームを取得する方法について示す。
Controller leap = new Controller();
long previousFrameId = -1;
while ( true ) {
// 最新のフレームを取得する(leap.Frame( 0 ) と同じ)
var currentFrame = leap.Frame();
if ( previousFrameId == currentFrame.Id ) {
continue;
}
previousFrameId = currentFrame.Id;
// 直前の5フレームを取得する
Console.Write( currentFrame.Id + ", " );
for ( int i = 1; i <= 5; ++i ) {
var previousFrame = leap.Frame( i );
Console.Write( previousFrame.Id + ", " );
}
Console.WriteLine();
}
// 終了処理(onExit()相当)
|
手や指のIDを取得する
手や指にはIDが振られており、追跡している間は同じIDが使われる。
このIDとフレーム履歴を用いることで、フレームをまたいだ処理をシンプルに実装できる。例として「Getting Frame Dataサンプル(英語)」にある「指のIDとフレーム履歴を利用して、10フレームの指の平均座標を求めるコード」を見てみよう。
Controller leap = new Controller();
long previousFrameId = -1;
// 初期化処理(OnInit()相当)をここに書く
// 無限ループ内で、前回のフレームのIDと比較して新しいフレームを取得する
while ( true ) {
var frame = leap.Frame();
if ( previousFrameId == frame.Id ) {
continue;
}
previousFrameId = frame.Id;
// フレーム更新処理(OnFrame()相当)をここに書く
// 最新10フレームを使って、指の位置の平均値を計算する
int count = 0;
Vector average = new Vector ();
// 最初のに検出された指の平均を取得する
Finger fingerToAverage = frame.Fingers[0];
for ( int i = 0; i < 10; i++ ) {
// 指定したフレームの、指定した指IDの、指データを取得する
Finger fingerFromFrame = leap.Frame( i ).Finger( fingerToAverage.Id );
if ( fingerFromFrame.IsValid ) {
// 取得した指データが有効であれば平均値の算出に利用する
average += fingerFromFrame.TipPosition;
count++;
}
average /= count;
}
// 平均値を表示する
Console.WriteLine( average );
}
// 終了処理(OnExit()相当)をここに書く
|
このようにフレームをうまく活用することで、指や手のデータを有効に活用できる。
まとめ
以上、今回はさまざまなフレームの扱い方について説明した。自分の使いたいデータをいかに取得するかがキモであり、それが簡易に実装できるのが、Leap Motion SDKの良いところだ。
1. C#によるLeap Motion v2開発の全体像
Leap Motion Developer SDKを利用してC#でLeap Motionのアプリケーションを開発する方法を解説する連載(2015年改訂版)。今回はC#の開発環境など、開発の基礎を紹介。
2. Leap SDKで指を検出してみよう(Tracking Hands, Fingers, and Tools)
Leap Motionの最大の特長である手・指を検出するには? Leap Motion Developer SDKを活用した開発方法を詳しく解説。※C++編の同名タイトルと基本的な内容は同じです。