はつねの日記

Kinect, Windows 10 UWP, Windows Azure, IoT, 電子工作

Understanding the C# Sample Applicationの日本語訳

Leap Motionの日本語情報は非常に限られているので公式ドキュメントをざっくり日本語訳にしました。誤訳などあったらお知らせください。

本エントリはhttps://developer.leapmotion.com/documentation/Languages/CSharpandUnity/Guides/Sample_CSharp_Tutorial.htmlの日本語訳となります。

Understanding the C# Sample Application

Topics:

Overview
Creating a Controller object
Subclassing the Listener class
Getting a Frame of data
Getting Gestures
Running the sample

Leap SDKフォルダ配下に、この資料で使う次のファイルがあります。

  • LeapSDK/samples/Sample.cs ? C# sample application
  • Windows:
    • LeapSDK/lib/LeapCSharp.NET3.5.dll ? Leap C# library for .NET 3.5
    • LeapSDK/lib/LeapCSharp.NET4.0.dll ? Leap C# library for .NET 4.0
    • LeapSDK/lib/x86/LeapCSharp.dll ? 32-bit Leap C# library for Windows
    • LeapSDK/lib/x64/LeapCSharp.dll ? 64-bit Leap C# library for Windows
    • LeapSDK/lib/x86/Leap.dll ? 32-bit Leap library for Windows
    • LeapSDK/lib/x64/Leap.dll ? 64-bit Leap library for Windows
  • Mac (Mono):
    • LeapSDK/lib/LeapCSharp.NET3.5.dll ? Leap C# library for .NET 3.5
    • LeapSDK/lib/LeapCSharp.NET4.0.dll ? Leap C# library for .NET 4.0
    • LeapSDK/lib/libLeapCSharp.dylib ? Leap C# library for Mac
    • LeapSDK/lib/libLeap.dylib ? Leap library for Mac

Leap APIリファレンスは、LeapSDK/docs/API_Reference/annotated.html にインストールされます。

Overview

一言で言えば、Leap Motionトラッキングデバイスが検出され、手とその検出範囲内にある指を追跡します。Leapは、1度に1フレームのデータをキャプチャします。アプリケーションからこのデータにアクセスするにはLeap APIを使います。

サンプル アプリケーションはLeap API を使用して、Leapからのフレームイベントを取得する方法と、各フレームの手や指のデータにアクセスする手段を提供します。Sample.csはコマンドラインアプリケーションとなっており、検出された手や指を標準出力に出力します。

サンプルアプリケーションでは、Leap APIの主要なクラスの大部分を使用しています。

  • Controller ? Leapとアプリケーション間のインターフェース
  • Listener ? Leapによって送出されたイベントを処理するために使用
  • Frame ? 手と指のトラッキングデータのセットが含まれています
  • Hand ? 検出された手のトラッキングデータが含まれています
  • Finger ? 検出された指のトラッキングデータが含まれています
  • Vector ? 3Dの位置または方向ベクトルを表します
  • Gesture ? 認識できたジェスチャーを表します

これらのクラスの詳細についてはLeap APIのリファレンスを参照してください。

Creating a Controller object

Controllerクラスは、Leapとアプリケーションの間の主要なインターフェイスを提供します。Controllerオブジェクトを作成すると実行中のLeapソフトウェアに接続して、Frameオブジェクトを通じて手のトラッキングデータが利用可能になります。Frameオブジェクトは、Controllerオブジェクトをインスタンス化して、Controller.Frame メソッドを呼び出すことによってアクセスできます。

アプリケーションからはループ処理によりController.Frameを呼び出して使います。ループ処理により定期的にポーリングする以外に、Controllerオブジェクトにリスナーを追加する方法もあります。Controllerオブジェクトはトラッキングデータの新たしいフレームが利用できるとき(もしくは他のLeapイベント発生時に)はListenerサブクラスで定義されているコールバックメソッドを呼び出します。

サンプルアプリケーションでは、MainメソッドでControllerオブジェクトを作成し、Controller.AddListenerメソッドでListenerのサブクラスインスタンスを追加します。

VB.NET
Class Sample
    Public Sub Main()
        ' Create a sample listener and controller
        Dim listener As new SampleListener
        Dim _controller As new Controller

        ' Have the sample listener receive events from the controller
        _controller.AddListener(listener);

        ' Keep this process running until Enter is pressed
        Console.WriteLine("Press Enter to quit...");
        Console.ReadLine();

        ' Remove the sample listener when done
        _controller.RemoveListener(listener)
        _controller.Dispose()
    End Sub
End Class
C#
class Sample
{
    public static void Main()
    {
        // Create a sample listener and controller
        SampleListener listener = new SampleListener();
        Controller controller = new Controller();

        // Have the sample listener receive events from the controller
        controller.AddListener(listener);

        // Keep this process running until Enter is pressed
        Console.WriteLine("Press Enter to quit...");
        Console.ReadLine();

        // Remove the sample listener when done
        controller.RemoveListener(listener);
        controller.Dispose();
    }
}

[ENTER]キーを押すまでサンプルアプリケーションは十恋し続けます。実行している間、Leapイベントが発生したときはControllerが適切なリスナーコールバックを呼び出します。上記のサンプルコードではSampleListenerクラスがリスナークラスの独自サブクラスになります。次はそのサブクラスの実装をみてみましょう。

Subclassing the Listener class

サンプル アプリケーションでは、リスナーのサブクラスであるSampleListenerでLeapからのイベントを処理するコールバックメソッドを実装しています。

イベントは次のとおりです。

  • OnInit ? Listenerが登録されているControllerオブジェクトの初期化時のイベント。一度だけ発生します。
  • OnConnect ? Leapに接続し、モーショントラッキングデータのフレームの送信開始の準備ができたときのイベント
  • OnDisconnect ? Leapデバイスを外したりLeapソフトウェアを落としたときなど、ControllerオブジェクトがLeapから切り離されたときのイベント
  • OnExit ? ControllerオブジェクトからListenerが切り離された時のイベント
  • OnFrame ? モーショントラッ キングデータの新しいフレームが使用できるときのイベント

サンプルアプリケーションでは、 3つのライフサイクルイベントコールバック(OnInit OnDisconnect、OnExit)が発生すると標準出力にメッセージを出力します。OnConnect とOnFrameのイベント発生時にはメッセージ出力以外の処理もしています。 OnConnectコールバックで呼び出された関数の中では、認識したいジェスチャを使用可能にしています。OnFrameコールバックで呼び出された関数の中では、モーショントラッ キングデータの最新のフレームの内容を標準出力にメッセージ出力します。

Getting a Frame of data

Leapは、モーショントラッキングデータの新しいフレームを生成するときに、OnFrameコールバックメソッドを呼び出します。Controller.Frame()メソッドを呼び出すことによって、新たなデータにアクセスすることができます (Controllerオブジェクトへの参照がパラメータとしてコールバックに渡されます)。Frameオブジェクトには、ID、タイムスタンプおよび検出された手に対するHandオブジェクトを含むリストから構成されています。

サンプルアプリケーションのOnFrame実装の次の部分は、Controllerオブジェクトから最新のFrameオブジェクトを取得し、フレームID、タイムスタンプ、検出した手の数、検出した指の数、検出したツールの数を出力しています。

VB.NET
' Get the most recent frame and report some basic information
Dim _frame As Frame = _controller.Frame()
SafeWriteLine("Frame id: " + _frame.Id
            & ", timestamp: " & _frame.Timestamp
            & ", hands: " & _frame.Hands.Count
            & ", fingers: " & _frame.Fingers.Count
            & ", tools: " & _frame.Tools.Count)
C#
// Get the most recent frame and report some basic information
Frame frame = controller.Frame();
SafeWriteLine("Frame id: " + frame.Id
            + ", timestamp: " + frame.Timestamp
            + ", hands: " + frame.Hands.Count
            + ", fingers: " + frame.Fingers.Count
            + ", tools: " + frame.Tools.Count);

 

次のコードは、手が検出できたかをチェックし、検出できた場合はリストの最初の手を取得しています。

VB.NET
If (Not _frame.Hands.Empty) Then
    ' Get the first hand
    Dim _hand As Hand = _frame.Hands(0)
C#
if (!frame.Hands.Empty)
{
    // Get the first hand
    Hand hand = frame.Hands[0];

 

Handオブジェクトは、ID、手の物理的特性を表すプロパティおよびFingerオブジェクトリストが含まれています。各Fingerオブジェクトは、IDと指の特性を表すプロパティが含まれています。

次のコードはHandオブジェクトからFingerオブジェクトリストを取り出して、指の先端の平均的な位置を標準出力に出力します。

VB.NET
' Check if the hand has any fingers
Dim fingers As FingerList = _hand.Fingers
If (Not fingers.Empty) Then
    ' Calculate the hand's average finger tip position
    Dim avgPos As Vector = Vector.Zero
    For Each _finger In fingers
        avgPos += _finger.TipPosition
    Next
    avgPos /= fingers.Count
    SafeWriteLine("Hand has " & fingers.Count
                & " fingers, average finger tip position: " & avgPos)
End If
C#
// Check if the hand has any fingers
FingerList fingers = hand.Fingers;
if (!fingers.Empty)
{
    // Calculate the hand's average finger tip position
    Vector avgPos = Vector.Zero;
    foreach (Finger finger in fingers)
    {
        avgPos += finger.TipPosition;
    }
    avgPos /= fingers.Count;
    SafeWriteLine("Hand has " + fingers.Count
                + " fingers, average finger tip position: " + avgPos);
}

 

次に、手の平が形作っている球体の半径と手の平の位置を出力します。

VB.NET
' Get the hand's sphere radius and palm position
SafeWriteLine("Hand sphere radius: " & _hand.SphereRadius.ToString("n2") _
            & " mm, palm position: " & hand.PalmPosition)
C#
// Get the hand's sphere radius and palm position
SafeWriteLine("Hand sphere radius: " + hand.SphereRadius.ToString("n2")
            + " mm, palm position: " + hand.PalmPosition);

 

最後に、OnFrameメソッドは、手の法線ベクトルと方向ベクトルから手のピッチ、ロール、ヨー角を算出するベクトルを使用します。角度はラジアンから度に変換されます。

VB.NET
' Get the hand's normal vector and direction
Dim normal As Vector = _hand.PalmNormal
Dim direction As Vector = _hand.Direction

' Calculate the hand's pitch, roll, and yaw angles
SafeWriteLine("Hand pitch: " & direction.Pitch * 180.0F / CType(Math.PI, Single) & " degrees, " _
            & "roll: " & normal.Roll * 180.0F / CType(Math.PI, Single) & " degrees, " _
            & "yaw: " & direction.Yaw * 180.0F / CType(Math.PI, Single) & " degrees\n")
C#
// Get the hand's normal vector and direction
Vector normal = hand.PalmNormal;
Vector direction = hand.Direction;

// Calculate the hand's pitch, roll, and yaw angles
SafeWriteLine("Hand pitch: " + direction.Pitch * 180.0f / (float)Math.PI  + " degrees, "
            + "roll: " + normal.Roll * 180.0f / (float)Math.PI + " degrees, "
            + "yaw: " + direction.Yaw * 180.0f / (float)Math.PI + " degrees\n");

Getting Gestures

 

Leapからジェスチャーを受け取るには、最初に認識するジェスチャーを有効にする必要があります。Leapと接続したControllerオブジェクト(IsConnectedがTrue)は、いつでもジェスチャー認識を有効にできます。サンプルプログラムでは、OnConnect()コールバック関数の中でEnableGesture()Controllerクラスで定義されたメソッドを使用して認識したいすべてジェスチャを有効にしています。

VB.NET
Public Overrides Sub OnConnect(_controller As Controller)
    SafeWriteLine("Connected")
    _controller.EnableGesture(Gesture.GestureType.TYPECIRCLE)
    _controller.EnableGesture(Gesture.GestureType.TYPEKEYTAP)
    _controller.EnableGesture(Gesture.GestureType.TYPESCREENTAP)
    _controller.EnableGesture(Gesture.GestureType.TYPESWIPE)
End Sub
C#
public override void OnConnect (Controller controller)
{
        SafeWriteLine ("Connected");
        controller.EnableGesture (Gesture.GestureType.TYPECIRCLE);
        controller.EnableGesture (Gesture.GestureType.TYPEKEYTAP);
        controller.EnableGesture (Gesture.GestureType.TYPESCREENTAP);
        controller.EnableGesture (Gesture.GestureType.TYPESWIPE);
}

Leapは、Frameオブジェクトにおけるジェスチャーリストにジェスチャーの動きを定義したGestureオブジェクトを追加します。 サンプル・アプリケーションのOnFrame()イベントでは、検出されたジェスチャーに合わせた情報を標準出力に出力しています。

Gesture APIは、Gestureクラスをベースにして個々のジェスチャーを表すクラスに拡張されています。ジェスチャーリスト内のオブジェクトは、ジェスチャーインスタンスですので、あなたは正しいサブクラスのインスタンスにジェスチャーインスタンスを変換する必要があります。キャストはサポートされない代わりに各サブクラスは変換を実行するコンストラクターを提供します。例えば、例えば、円のジェスチャーは、以下のコードでCircleGestureインスタンスに変換できます。

VB.NET
CircleGesture circle = new CircleGesture (gesture)
C#
CircleGesture circle = new CircleGesture (gesture)

 

間違えているサブクラスにジェスチャインスタンスを変換する場合、コンストラクター関数は無効なジェスチャオブジェクトを返します。

ジェスチャーでは以前のフレームと現在のフレームでジェスチャーの特性を比較すると便利な場合があります。例えば、円のジェスチャーには円を何回描いたかをカウントするProgressプロパティがあります。今回のフレームの円のジェスチャーのProgressから前回のフレームの円のジェスチャーを引くことで1フレームの回数が取得できます。

VB.NET
' Calculate angle swept since last frame
Dim sweptAngle As Single = 0
If (circle.State <> Gesture.GestureState.STATESTART) Then
    Dim previousUpdate As New CircleGesture(_controller.Frame(1).Gesture(circle.Id))
    sweptAngle = (circle.Progress - previousUpdate.Progress) * 360
End If
C#
// Calculate angle swept since last frame
float sweptAngle = 0;
if (circle.State != Gesture.GestureState.STATESTART) {
    CircleGesture previousUpdate = new CircleGesture (controller.Frame (1).Gesture (circle.Id));
    sweptAngle = (circle.Progress - previousUpdate.Progress) * 360;
}

 

ジェスチャーループの完全なコードは次の通りです。

VB.NET
' Get gestures
Dim gestures As GestureList = _frame.Gestures()
For i As Integer = 0 To gestures.Count - 1
    Dim _gesture As Gesture = gestures(i)

    Select Case _gesture.Type
        Case Gesture.GestureType.TYPECIRCLE
            Dim circle As New CircleGesture(_gesture)

            ' Calculate clock direction using the angle between circle normal and pointable
            Dim clockwiseness As String
            If (circle.Pointable.Direction.AngleTo(circle.Normal) <= Math.PI / 4) Then
                'Clockwise if angle is less than 90 degrees
                clockwiseness = "clockwise"
            Else
                clockwiseness = "counterclockwise"
            End If

            Dim sweptAngle As Single = 0

            ' Calculate angle swept since last frame
            If (circle.State <> Gesture.GestureState.STATESTART) Then
                Dim previousUpdate As New CircleGesture(_controller.Frame(1).Gesture(circle.Id))
                sweptAngle = (circle.Progress - previousUpdate.Progress) * 360
            End If

            SafeWriteLine("Circle id: " & circle.Id _
                        & ", " & circle.State _
                        & ", progress: " & circle.Progress _
                        & ", radius: " & circle.Radius _
                        & ", angle: " & sweptAngle _
                        & ", " & clockwiseness)
        Case Gesture.GestureType.TYPESWIPE
            Dim swipe As New SwipeGesture(_gesture)
            SafeWriteLine("Swipe id: " & swipe.Id _
                        & ", " & swipe.State _
                        & ", position: " & swipe.Position.ToString _
                        & ", direction: " & swipe.Direction.ToString _
                        & ", speed: " & swipe.Speed)
        Case Gesture.GestureType.TYPEKEYTAP
            Dim keytap As New KeyTapGesture(_gesture)
            SafeWriteLine("Tap id: " & keytap.Id _
                        & ", " & keytap.State _
                        & ", position: " & keytap.Position.ToString _
                        & ", direction: " & keytap.Direction.ToString)
        Case Gesture.GestureType.TYPESCREENTAP
            Dim screentap As New ScreenTapGesture(_gesture)
            SafeWriteLine("Tap id: " & screentap.Id _
                        & ", " & screentap.State _
                        & ", position: " & screentap.Position.ToString _
                        & ", direction: " & screentap.Direction.ToString)
        Case Else
            SafeWriteLine("Unknown gesture type.")
    End Select
Next
C#
// Get gestures
GestureList gestures = frame.Gestures ();
for (int i = 0; i < gestures.Count; i++) {
        Gesture gesture = gestures [i];

        switch (gesture.Type) {
        case Gesture.GestureType.TYPECIRCLE:
                CircleGesture circle = new CircleGesture (gesture);

    // Calculate clock direction using the angle between circle normal and pointable
                String clockwiseness;
                if (circle.Pointable.Direction.AngleTo (circle.Normal) <= Math.PI / 4) {
                        //Clockwise if angle is less than 90 degrees
                        clockwiseness = "clockwise";
                } else {
                        clockwiseness = "counterclockwise";
                }

                float sweptAngle = 0;

    // Calculate angle swept since last frame
                if (circle.State != Gesture.GestureState.STATESTART) {
                        CircleGesture previousUpdate = new CircleGesture (controller.Frame (1).Gesture (circle.Id));
                        sweptAngle = (circle.Progress - previousUpdate.Progress) * 360;
                }

                SafeWriteLine ("Circle id: " + circle.Id
               + ", " + circle.State
               + ", progress: " + circle.Progress
               + ", radius: " + circle.Radius
               + ", angle: " + sweptAngle
               + ", " + clockwiseness);
                break;
        case Gesture.GestureType.TYPESWIPE:
                SwipeGesture swipe = new SwipeGesture (gesture);
                SafeWriteLine ("Swipe id: " + swipe.Id
               + ", " + swipe.State
               + ", position: " + swipe.Position
               + ", direction: " + swipe.Direction
               + ", speed: " + swipe.Speed);
                break;
        case Gesture.GestureType.TYPEKEYTAP:
                KeyTapGesture keytap = new KeyTapGesture (gesture);
                SafeWriteLine ("Tap id: " + keytap.Id
               + ", " + keytap.State
               + ", position: " + keytap.Position
               + ", direction: " + keytap.Direction);
                break;
        case Gesture.GestureType.TYPESCREENTAP:
                ScreenTapGesture screentap = new ScreenTapGesture (gesture);
                SafeWriteLine ("Tap id: " + screentap.Id
               + ", " + screentap.State
               + ", position: " + screentap.Position
               + ", direction: " + screentap.Direction);
                break;
        default:
                SafeWriteLine ("Unknown gesture type.");
                break;
        }
}

Running the sample

To run the sample application:

  1. サンプルのコンパイル
    • dows では、現在のディレクトリにある Sample.cs と LeapCSharp.NET3.5.dll または LeapCSharp.NET4.0.dll を確認します。(を使用している .NET framework の適切なライブラリ参照を使用して) コマンド ライン プロンプトで次のコマンドを実行します。

      csc /reference:LeapCSharp.NET4.0.dll /platform:x86 /target:exe Sample.cs

      注: .NET framework の適切なバージョンから「csc」コンパイラを使用します。

       

    • Mac では、c# プログラムをコンパイルする、Mono プロジェクトを使用してできます。現在のディレクトリにある Sample.cs と LeapCSharp.NET3.5.dll または LeapCSharp.NET4.0.dll を確認します。
      • .NET 3.5 framework

        gmcs /reference:LeapCSharp.NET3.5.dll /platform:x86 /target:exe Sample.cs

         

      • .NET 4.0 framework

        dmcs /reference:LeapCSharp.NET4.0.dll /platform:x86 /target:exe Sample.cs

  2. LeapデバイスをUSBに繋ぎ、自分の前に設置
  3. Leapソフトウェアをインストールしていなかったらインストール
  4. Leapソフトウェアをスタート。プロンプトが表示されたら登録したメールアドレスとパスワードを入力します。準備ができたらタスクバーの通知領域に表示されたアイコンが緑色になります。
  5. サンプルアプリの実行
    • Windowsでは、Sample.exe、LeapCSharp.dll、Leap.dll、LeapCSharp.NET3.5.dllまたはLeapCSharp.NET4.0.dllを同じディレクトリに配置します。32ビットのプロジェクトはlib\x86ディレクトリのライブラリを使用し、64ビットプロジェクトはlib\x64ディレクトリのライブラリを使用します。コマンドラインプロンプトで次のコマンドを実行します。

      Sample.exe

      Visual Studioでコンパイルする場合は、プロジェクトでlib\LeapCSharp.NET4.0.dllを参照設定し、ビルドターゲットをx86にした場合はlib\x86\LeapCSharp.dllとlib\x86\Leap.dllを取り込み、コンパイルアクション「なし」、コピーアクション「常にコピーする」と各ファイルのプロパティで設定しておくと便利です。なお、ターゲットフレームワークを4.5としても動作します。
      image

      なお、ビルドターゲットをx64にした場合はlib\x64\LeapCSharp.dllとlib\x64\Leap.dllを取り込みます。

       

    • MacではSample.exe、 libLeapCSharp.dylib、 libLeap.dylib、LeapCSharp.NET3.5.dllまたはLeapCSharp.NET4.0.dllを同じディレクトリに配置し、ターミナルウィンドウで次のコマンドを実行して確認してください。

      mono Sample.exe

サンプルアプリケーションを実行すると、初期化時とLeap接続時に「Initialized」と「Connected」が標準出力に出力されます。OnFrameのイベントが検出されると標準出力にフレーム情報が出力されます。Leapの上に手をかざすと指と手の平の位置情報が出力されます。

このサンプルでLeapからのモーショントラックングデータの取得方法が理解できたら、Leapを使ったC#アプリケーションの開発を始めることができます。