はつねの日記

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

Leap Motion - Getting Frame Data の日本語訳

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

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

Getting Frame Data

一連のスナップが、フレームと呼んだように、Leap Motion APIはあなたのアプリケーションにモーショントラッキングデータを提示します。 追跡データの各フレームはそのスナップに検出された各実体の測定値と他の情報を含んでいます。 この記事はLeap MotionコントローラからFrameオブジェクトの取得方法の詳細について記載しています。

Topics:
  • Overview
  • Getting Frames by Polling
  • Getting Frames with Callbacks
  • Getting Data from a Frame
  • Using IDs to track entities across frames

Overview

Controllerオブジェクトからトラッキングデータを含むFrameオブジェクトを取得してください。 ControllerクラスのFrameメソッドを使用することで処理する準備ができているときはいつも、Frameオブジェクトを生成可能です。

VB.NET
    If controller.IsConnected Then            'controller is a Controller object
        Frame frame = controller.Frame()      'The latest frame
        Frame previous = controller.Frame(1); 'The previous frame
    End If
C#
    if (controller.IsConnected) //controller is a Controller object
    {
        Frame frame = controller.Frame(); //The latest frame
        Frame previous = controller.Frame(1); //The previous frame
    }

Frameメソッドはパラメタとして履歴番号(=1が現在フレームの直前フレーム)を指定できます。通常、最後の60個のフレームが履歴バッファで維持されています。

Getting Frames by Polling

アプリケーションが違和感のないフレームレートでループ処理を実装している時は、Controllerオブジェクトのフレームをポーリング取得するのが、最も簡単で良い結果につながります。アプリケーションがデータのフレームを処理する準備ができている間だけControllerのFrameメソッドを呼ぶだけで済みます。

ポーリングを使用する場合、もしアプリケーションのフレーム レートがLeapのフレーム レートを超えている場合は同じフレームが返ってくる場合があったり、Leapフレーム レートがアプリケーションのフレーム レートを超えている場合はフレームを取りこぼす可能性があります。

多くの場合、例えば、手の動きへの応答で画面上のオブジェクトを移動するような場合、同じフレームを連続して取得しても滑らかな動きが実現できます(アプリケーションの全体的なフレーム レートが十分に高いと仮定)。

フレームがすでに処理されているかどうかを判定するには、最後に処理したフレームのIDを保存しておいて現在のフレームのIDと比較します。

VB.NET
    Private lastFrameID As Integer = 0

    Private Sub ProcessFrame(_frame As Frame)
        If _frame.Id = lastFrameID Then return
        '...
        lastFrameID = _frame.Id
    End Sub
C#
    Int64 lastFrameID = 0;

    void processFrame( Frame frame )
    {
        if(frame.Id == lastFrameID) return;
        //...
        lastFrameID = frame.Id;
    }

アプリがフレームをスキップしてしまった場合は、履歴バッファからスキップしたフレームを処理するために履歴番号付のFrameメソッドを使用します。

VB.NET
    Private lastFrameID As Integer = 0

    Private Sub NextFrame(_controller As Controller)
        Dim currentID As Integer = _controller.Frame().Id
        For history As Integer = 0 To history < currentID - lastProcessedFrameID - 1
            ProcessFrame(_controller.Frame(history))
        Next
        lastProcessedFrameID = currentID
    End Sub

    Private Sub ProcessNextFrame(_frame As Frame)
        If _frame.IsValid Then
            '...
        End If
    End Sub
C#
    Int64 lastProcessedFrameID = 0;

    void nextFrame(Controller controller)
    {
        Int64 currentID = controller.Frame().Id;
        for( int history = 0; history < currentID - lastProcessedFrameID; history++)
        {
            processFrame(controller.Frame(history));
        }
        lastProcessedFrameID = currentID;
    }

    void processNextFrame(Frame frame)
    {
        if(frame.IsValid)
        {
            //...
        }
    }

Getting Frames with Callbacks

Leap Motion Controllerのフレームレートでフレームを取得する方法としてListenerオブジェクトを使う方法があります。Controllerオブジェクトは、新しいフレームが使用できるときにListenerのonFrameコールバック関数を呼び出します。OnFrameハンドラではFrameオブジェクト自体を呼び出すControllerのFrameメソッドを呼び出すことができます。

リスナーコールバックは独立したスレッドとして呼び出されるマルチスレッドコールバックのためポーリングよりも複雑な処理が必要です。複数のスレッドによりアクセスされるデータは、スレッドセーフな方法で処理されることを確認する必要があります。

次の例はデータの新しいフレームの処理を最小限のリスナーサブクラスで定義しています。

VB.NET
    Class FrameListener
        Inherits Listener

        Sub OnFrame(_controller As Controller)
        {
            Dim _frame As Frame = _controller.Frame()    'The latest frame
            Dim previous As Frame = _controller.Frame(1) 'The previous frame
            '...
        }
    End Class
C#
    class FrameListener : Listener
    {
        void onFrame(Controller controller)
        {
            Frame frame = controller.Frame(); //The latest frame
            Frame previous = controller.Frame(1); //The previous frame
            //...
        }
    };

このようにListenerオブジェクトを使ってトラッキングデータを取得する場合でも、他の処理部分はポーリングしたときと同様です。

注意:リスナーコールバックを使用する場合にも、フレームをスキップすることが可能です。onFrameコールバック関数が完了までに長時間かかる場合、履歴に次のフレームが追加されonFrameコールバックがスキップされます。時間内にフレームの処理が完了しないことが頻発するとLeapソフトウェアはそのフレームを破棄して履歴には追加しません。この問題は処理タスクが多すぎるなどのリソース不足時に発生します。

Getting Data from a Frame

Frameクラスは、フレーム内のデータへのアクセスを提供するいくつかのメンバを用意しています。

例えば、Leap Motion Systemによってトラッキングされた基本的なオブジェクトを取得する方法は次のようになります。

VB.NET
        Dim _controller As new Controller
        ' wait until Controller.isConnected() evaluates to true
        '...

        Dim _frame As Frame = _controller.Frame
        HandList hands = _frame.Hands
        PointableList pointables = _frame.Pointables
        FingerList fingers = _frame.Fingers
        ToolList tools = _frame.Tools
C#
        Controller controller = new Controller();
        // wait until Controller.isConnected() evaluates to true
        //...

        Frame frame = controller.Frame();
        HandList hands = frame.Hands;
        PointableList pointables = frame.Pointables;
        FingerList fingers = frame.Fingers;
        ToolList tools = frame.Tools;

Frameオブジェクトによって返されるオブジェクトは、すべて読み取り専用です。スレッドセーフな作りになっているため、安全にそれらを格納し、あとで利用することができます。内部的にはオブジェクトはC++ Boostライブラリの共有ポインタクラスを使って実装されています。

Using IDs to track entities across frames

もし、別フレームのエンティティIDがある場合、IDを指定して現在のフレームから該当するエンティティオブジェクトを取得できます。

VB.NET
        Dim _hand As Hand = _frame.Hand(handID)
        Dim _pointable As Pointable = _frame.Pointable(pointableID)
        Dim _finger As Finger = _frame.Finger (fingerID)
        Dim _tool As Tool = _frame.Tool(toolID)
C#
        Hand hand = frame.Hand(handID);
        Pointable pointable = frame.Pointable(pointableID);
        Finger finger = frame.Finger (fingerID);
        Tool tool = frame.Tool(toolID);

同じIDを持つオブジェクトが見つからない(おそらく手や指がLeapの検出範囲から外れるなどした場合)ときは、特別な無効オブジェクトが返却されます。無効オブジェクトは、適切なクラスのインスタンスですが、メンバー値はゼロ、ベクトル値はゼロ、もしくはIsValidプロパティ値がTrueを返します。

無効オブジェクトが発生したときの対処ですが、例えば次のコードのように複数のフレームで指の先端の位置を平均を求めて補完する方法などがあります。

VB.NET
        'Average a finger position for the last 10 frames
        Dim count As Integer = 0
        Dim average As new Vector()
        Dim fingerToAverage As Finger = frame.Fingers(0)
        For i As Integer = 0 To i < 10 - 1
            Dim fingerFromFrame As Finger = _controller.Frame(i).Finger(fingerToAverage.Id)
            if (fingerFromFrame.IsValid) Then
                average += fingerFromFrame.TipPosition
                count++
            End If
        Next
        average /= count
C#
        //Average a finger position for the last 10 frames
        int count = 0;
        Vector average = new Vector();
        Finger fingerToAverage = frame.Fingers[0];
        for (int i = 0; i < 10; i++) {
            Finger fingerFromFrame = controller.Frame(i).Finger(fingerToAverage.Id);
            if (fingerFromFrame.IsValid) {
                average += fingerFromFrame.TipPosition;
                count++;
            }
        }
        average /= count;

無効オブジェクトを使用しない場合、Fingerオブジェクトをチェックする前に各Frameオブジェクトを確認する必要があります。