休暇中、Leap Motionセンサーが私の手に落ちました。 私は彼と長い間一緒に仕事をしたかった
かつて、約10年前、私が男子学生で何もしなかったとき、私はIgromania雑誌を購入しました。この雑誌には、あらゆる種類のゲームとシェアウェアソフトウェアが入ったディスクが付属しています。 そして、この雑誌には、有用なソフトウェアに関する見出しがありました。 プログラムの1つはSymbol Commanderであることが判明しました。これは、マウスの動きを記録し、記録された動きを認識し、認識された場合にこの動きに割り当てられたアクションを実行できるユーティリティです。
現在、近接センサー(Leap Motion、Microsoft Kinect、PrimeSence Carmine)の開発により、それらの1つに対して同様の機能を繰り返すというアイデアが生まれました。 選択はLeap Motionにかかった。
それでは、カスタムジェスチャを処理するには何が必要ですか? ジェスチャプレゼンテーションモデルとデータプロセッサ。 作業のスキームは次のようになります。
したがって、開発は次の段階に分けることができます。
1.ジェスチャー記述モデルの設計
2.記述されたジェスチャのプロセッサ認識の実装
3. OSのジェスチャーとコマンドの一致を実装する
4.ジェスチャーを記録し、コマンドを割り当てる機能のUI。
モデルから始めましょう。
公式のLeap Motion SDKには、事前定義された一連のジェスチャーが用意されています。 これを行うには、次の値を含むGestureType列挙があります。
GestureType.TYPE_CIRCLE GestureType.TYPE_KEY_TAP GestureType.TYPE_SCREEN_TAP GestureType.TYPE_SWIPE
私は自分用にカスタムジェスチャを処理するタスクを設定しているため、ジェスチャを記述するモデルは自分のものになります。
それでは、Leap Motionのジェスチャーとは何ですか?
SDKは、各指の現在の状態の説明のコレクションを含むFingerListを取得できるフレームを提供します。 したがって、スターターの場合、最も抵抗の少ない経路をたどり、ジェスチャーを、軸の1つ(XYZ)に沿った方向の1つに沿った指の動きのセットと見なすことにしました(指座標の対応するコンポーネントは増減します)。
したがって、各ジェスチャーは、以下で構成されるプリミティブのセットによって記述されます。
1.指の動きの軸
2.道順
3.ジェスチャー内のプリミティブの実行順序
4.プリミティブジェスチャを実行する必要があるフレームの数。
ジェスチャーの場合、次も設定する必要があります。
1.名前
2.このジェスチャを実行する指の人差し指。
このバージョンのジェスチャーは、XML形式で記述されます。 たとえば、よく知られているタップジェスチャ(指で「クリック」)のXML記述を示します。
<Gesture> <GestureName>Tap</GestureName> <FingerIndex>1</FingerIndex> <Primitives> <Primitive> <Axis>Z</Axis> <Direction>-1</Direction> <Order>0</Order> <FramesCount>10</FramesCount> </Primitive> <Primitive> <Axis>Z</Axis> <Direction>1</Direction> <Order>1</Order> <FramesCount>10</FramesCount> </Primitive> </Primitives> </Gesture>
このフラグメントは、2つのプリミティブで構成されるタップジェスチャを設定します。10フレームにわたって指を下げ、それに応じて指を上げます。
認識ライブラリのこのモデルについて説明します。
public class Primitive { [XmlElement(ElementName = "Axis", Type = typeof(Axis))] // public Axis Axis { get; set; } [XmlElement(ElementName = "Direction")] //: +1 -> public int Direction { get; set; } // -1 -> [XmlElement(ElementName = "Order", IsNullable = true)] // public int? Order { get; set; } [XmlElement(ElementName = "FramesCount")] // public int FramesCount { get; set; } } public enum Axis { [XmlEnum("X")] X, [XmlEnum("Y")] Y, [XmlEnum("Z")] Z }; public class Gesture { [XmlElement(ElementName = "GestureIndex")] // public int GestureIndex { get; set; } [XmlElement(ElementName = "GestureName")] // public string GestureName { get; set; } [XmlElement(ElementName = "FingerIndex")] // public int FingerIndex { get; set; } [XmlElement(ElementName = "PrimitivesCount")] // public int PrimitivesCount { get; set; } [XmlArray(ElementName = "Primitives")] // public Primitive[] Primitives { get; set; } }
OK、モデルの準備ができました。 認識プロセッサに移りましょう。
認識とは何ですか? 各フレームで指の現在の状態を取得できることを考えると、認識は、指の状態が所定の期間にわたって指定された基準に準拠しているかどうかのチェックです 。
したがって、Leap.Listenerから継承したクラスを作成し、その中のOnFrameメソッドを再定義します。
public override void OnFrame(Leap.Controller ctrl) { Leap.Frame frame = ctrl.Frame(); currentFrameTime = frame.Timestamp; frameTimeChange = currentFrameTime - previousFrameTime; if (frameTimeChange > FRAME_INTERVAL) { foreach (Gesture gesture in _registry.Gestures) { Task.Factory.StartNew(() => { Leap.Finger finger = frame.Fingers[gesture.FingerIndex]; CheckFinger(gesture, finger); }); } previousFrameTime = currentFrameTime; } }
ここでは、FRAME_INTERVALに等しい期間に一度、指の状態を確認します。 テストの場合、FRAME_INTERVAL = 5000(処理されたフレーム間のマイクロ秒数)。
コードから、認識がCheckFingerメソッドで実装されていることは明らかです。 このメソッドのパラメーターは現在チェックされているジェスチャーであり、Leap.Fingerは指の現在の状態を表すオブジェクトです。
認識はどのように機能しますか?
方向を制御するための座標のコンテナー、ジェスチャーを認識するための条件を満たす指の位置のフレームコンテナー、ジェスチャーの実行を制御するための認識されるプリミティブのカウンターのコンテナーの3つのコンテナーを作成することにしました。
このように:
public void CheckFinger(Gesture gesture, Leap.Finger finger) { int recognitionValue = _recognized.ElementAt(gesture.GestureIndex); Primitive primitive = gesture.Primitives[recognitionValue]; CheckDirection(gesture.GestureIndex, primitive, finger); CheckGesture(gesture); } public void CheckDirection(int gestureIndex, Primitive primitive, Leap.Finger finger) { float pointCoordinates = float.NaN; switch(primitive.Axis) { case Axis.X: pointCoordinates = finger.TipPosition.x; break; case Axis.Y: pointCoordinates = finger.TipPosition.y; break; case Axis.Z: pointCoordinates = finger.TipPosition.z; break; } if (_coordinates[gestureIndex] == INIT_COUNTER) _coordinates[gestureIndex] = pointCoordinates; else { switch (primitive.Direction) { case 1: if (_coordinates[gestureIndex] < pointCoordinates) { _coordinates[gestureIndex] = pointCoordinates; _number[gestureIndex]++; } else _coordinates[gestureIndex] = INIT_COORDINATES; break; case -1: if (_coordinates[gestureIndex] > pointCoordinates) { _coordinates[gestureIndex] = pointCoordinates; _number[gestureIndex]++; } else _coordinates[gestureIndex] = INIT_COORDINATES; break; } } if(_number[gestureIndex] == primitive.FramesCount) { _number[gestureIndex] = INIT_COUNTER; _recognized[gestureIndex]++; } } public void CheckGesture(Gesture gesture) { if(_recognized[gesture.GestureIndex] == (gesture.PrimitivesCount - 1)) { FireEvent(gesture); _recognized[gesture.GestureIndex] = INIT_COUNTER; } }
現時点では、ジェスチャータップ(指タップ)とラウンド(円運動)が説明されています。
次のステップは次のとおりです。
1.認識の安定化(はい、今では安定していません。オプションを検討しています)。
2.ユーザーの通常の操作のためのUIアプリケーションの実装。
githubで入手可能なソースコード