Unity Leap Motion gesture recognition

  Today, I suddenly wanted to check the information about Unity's gesture recognition. Since all the websites collected by the browser have been lost some time ago, I can't trust it, so I plan to put all the information on the blog in the future.

  This article is about gesture recognition by Unity through Leap Motion, but I didn't write it, just read it (because I don't have time...), reprinted from https://blog.csdn.net/u012289636/article/details/ 46883731, the following are all copied and pasted from this link.

 

As a gesture recognition device, Leap Motion has the advantage of accuracy compared to Kniect.

In the development of my graduation project "Scene Robot", Leap Motion's gesture control was an important part. In this way, let's talk about the implementation of gesture recognition using Leap Motion in development and the points that need to be paid attention to.

 

1. Assess the capabilities of Leap Motion

Before setting up the gestures, we had to know how far Leap Motion could do it, lest we find it difficult to achieve after setting the scheme. This evaluation relies on the actual experience of using the device, mainly from three aspects:

1. Visual gesture recognition interface provided by Leap Motion

2. SDK documentation description

3. APP in Leap Store

Basically it can be concluded that:

1. The recognition of Leap Motion can better recognize the horizontal direction or gestures based on the horizontal direction.

2. There will be errors in the recognition of clenched fists or vertical behaviors, which are related to specific gesture behaviors.

3. You shouldn't rely too much on high accuracy, it's true that Leap Motion can detect the millimeter level, but sometimes it will recognize your straight finger as bent, so be prepared for the worst.

 

2. Practical needs

Move, rotate, click buttons, zoom and rotate objects, close programs, pause, the basic functional requirements are like this.

There are some principles:

1. Gestures in the same environment should be close and easily converted. The transition between rotation and movement should be designed to be natural.

2.手势避免冲突,手势过于相似不是什么好事。比如三个伸直的手指和四个伸直的手指不应该被设计成两个手势。当然这不是绝对的,如果你进行一个缓慢的动作并且动作是面向Leap Motion的摄像头,这时候应该相信它,至少要针对这个手势做一个单独的测试。

 

三、考虑基本的数据结构和算法的轮廓

Leap Motion的SDK在第一部分的时候已经浏览过,最起码能知道Leap Motion可以包含的信息,从SDK看来这是非常丰富的,既然设计自己的手势,那么最好不要依赖于SKD开发包的炫酷的手势。很可能,这些手势只是官方用来演示或者炫耀的。自己设计手势的基本数据结构也有另外的好处,比如更换了体感设备,但是功能是相似的,这时候只需要更改获取数据的方式就可以了(从一个SDK更换到另一个SDK),而不要修改算法。

算法的轮廓与基本数据有很大的关系。所以数据结构一定要尽量的精简并且允许修改(可能某个算法占据了决定性因素,但是开始没考虑到)。

 

[csharp]  view plain copy
  1. public class HandAndFingersPoint : MonoBehaviour   
  2. {  
  3.     const int BUFFER_MAX=5;  
  4.     Controller m_LeapCtrl;  
  5.   
  6.     <span style="white-space:pre">    </span>public E_HandInAboveView m_AboveView = E_HandInAboveView.None;  
  7.       
  8.     //手指-数据 ,[0]表示左手,[1]表示右手  
  9.     private Dictionary<Finger.FingerType,FingerData>[] m_FingerDatas = new Dictionary<Finger.FingerType, FingerData>[2];  
  10.     //buffer,[0]表示左手,[1]表示右手,[,n](n属于0,3,表示第n次缓存)  
  11.     private Dictionary<Finger.FingerType,FingerData>[,] m_FingerDatasBuffer=new Dictionary<Finger.FingerType, FingerData>[2,BUFFER_MAX];  
  12.     private int m_CurBufIndex=0;  
  13.     //palm 0:左手 和1:右手  
  14.     private PointData[] m_PalmDatas = new PointData[2];  
  15.       
  16.     private readonly PointData m_DefaultPointData = new PointData(Vector.Zero, Vector.Zero);  
  17.         private readonly FingerData m_DefaultFingerData = new FingerData(Vector.Zero,Vector.Zero,Vector.Zero);  

HandAndFingersPoint类中剩下的部分是对数据的填充、清除、刷新等方法。E_HandInAboveView记录哪只手先进入Leap Motion的视野,用于设定优先级。
另外两个基本的数据结构PointData和FingerData:

 

 

[csharp]  view plain copy
  1. //一个手指的数据包含一个指尖点数据和手指根骨的位置数据  
  2. public struct FingerData  
  3. {  
  4.     public PointData m_Point;//指尖的位置和指向  
  5.     public Vector m_Position;//手指根骨的位置,对于拇指来说是Proximal phalanges近端指骨的位置  
  6.   
  7.     public FingerData(PointData pointData, Vector pos)  
  8.     {  
  9.         m_Point = pointData;  
  10.         m_Position = pos;  
  11.     }  
  12.   
  13.     public FingerData(Vector pointPos, Vector pointDir, Vector pos)  
  14.     {  
  15.         m_Point.m_Position = pointPos;  
  16.         m_Point.m_Direction = pointDir;  
  17.         m_Position = pos;  
  18.     }  
  19.   
  20.     public void Set(FingerData fd)  
  21.     {  
  22.     m_Point = fd.m_Point;  
  23.     m_Position = fd.m_Position;  
  24.     }  
  25. }  
  26. //一个点的数据,包括方向和位置  
  27. public struct PointData  
  28. {  
  29.     public Vector m_Position;//位置  
  30.     public Vector m_Direction;//方向  
  31.   
  32.     public PointData(Vector pos,Vector dir)  
  33.     {  
  34.         m_Position = pos;  
  35.         m_Direction = dir;  
  36.     }  
  37.   
  38.     public void Set(PointData pd)  
  39.     {  
  40.         m_Position = pd.m_Position;  
  41.         m_Direction = pd.m_Direction;  
  42.     }  
  43.   
  44.     public void Set(Vector pos,Vector dir)  
  45.     {  
  46.         m_Position = pos;  
  47.         m_Direction = dir;  
  48.     }  
  49. }  
  50.   
  51. //先被看到的手  
  52. public enum E_HandInAboveView  
  53. {  
  54.     None,  
  55.     Left,  
  56.     Right  
  57. }  


基本数据定义好之后,最好确认数据的填充是没问题的,实际通过Frame frame = Leap.Controller.Frame();来获取最新的数据。这时候并不急着写完和基本数据相关的方法,现在最终要的是手势算法的合理性。要判断是否合理,最好先写一个算法。

 

最简单的是伸掌手势,在控制中水平的伸掌用于漫游,垂直的伸掌用于暂停。我发现手掌依赖于手指,而手指包括两个状态——伸直和弯曲。另外,其他的手势,也都是手指的伸直或者弯曲,外加方向的判定累积出各种效果。理所当然的,应该单独写出手指的弯曲和伸直判定算法:

 

[csharp]  view plain copy
  1. /// <summary>  
  2. /// 该方法提供对于单个手指匹配的算法,如伸直,弯曲  
  3. /// 以后可能的改变:对于不同的场景可能要求有所不同,这里的阈值也许会随之改变  
  4. /// </summary>  
  5. public class FingerMatch  
  6. {  
  7.     //弯曲状态的角度阈值  
  8.     static readonly float FingerBendState_Radian = Mathf.PI*4f / 18 ;//40度  
  9.     //伸直状态的角度阈值  
  10.     static readonly float FingerStrightState_Radian = Mathf.PI/12;//15度  
  11.   
  12.     /// <summary>  
  13.     /// 手指伸直的状态,当根骨-指尖的方向和指向的偏差小于阀值时,判定手指为伸直状态。  
  14.     /// 注意无效的方向为零向量,先判定是零向量  
  15.     /// </summary>  
  16.     /// <param name="adjustBorder">对阈值做的微调</param>  
  17.     /// <returns></returns>  
  18.     public static bool StrightState(FingerData fingerData, float adjustBorder=0f)  
  19.     {  
  20.         bool isStright =false;  
  21.         Vector disalDir = fingerData.m_Point.m_Direction;  
  22.         //如果指尖方向为0向量,表示无效的数据  
  23.         if (!disalDir.Equals(Vector.Zero))   
  24.         {  
  25.             Vector fingerDir = fingerData.m_Point.m_Position - fingerData.m_Position;//指尖位置减去指根位置,由指根指向指尖的向量              
  26.             float radian = fingerDir.AngleTo(disalDir);  
  27.               
  28.             if (radian < FingerStrightState_Radian + adjustBorder)  
  29.             {  
  30.                 isStright = true;  
  31.             }  
  32.         }  
  33.         return isStright;  
  34.     }  
  35.   
  36.     /// <summary>  
  37.     /// 判断一根手指是否处于弯曲状态  
  38.     /// </summary>  
  39.     /// <param name="fingerData">需要判定的手指数据</param>  
  40.     /// <param name="bandBorder">弯曲的阈值</param>  
  41.     /// <returns></returns>  
  42.     public static bool BendState(FingerData fingerData, float adjustBorder=0f)//,out float eulerAugle)  
  43.     {  
  44.         bool isBend = false;  
  45.   
  46.         //eulerAugle = -1f;  
  47.         Vector disalDir = fingerData.m_Point.m_Direction;  
  48.         if( !disalDir.Equals(Vector.Zero) )  
  49.         {  
  50.             Vector fingerDir = fingerData.m_Point.m_Position - fingerData.m_Position;//指尖位置减去指根位置,指跟到指尖的向量  
  51.   
  52.             float radian = fingerDir.AngleTo(disalDir);  
  53.             //eulerAugle = radian*180/Mathf.PI;   
  54.             //夹角超过定义的阈值时,认定为弯曲状态  
  55.             if (radian > FingerBendState_Radian + adjustBorder)  
  56.             {  
  57.                 isBend = true;  
  58.             }  
  59.         }  
  60.   
  61.         return isBend;  
  62.     }  
  63.   
  64. }  


上面包含了一个重要的概念——阈值。它是描述到底何种程度算是伸直,何种程度算是弯曲。阈值的确定是需要实际测试来决定的。写到这里也是时候进行一次简单的测试了,毕竟算法的轮廓已经确定。我甚至没写出手掌伸直的判定算法,就确定是可行的。

 

基本数据结构相关的操作——HandAndFingersPoint类:https://github.com/LoranceChen/Leap-Motion-In-Unity3D

该类使用基本数据,在Unity Editor中运行会展示了一个手掌的轮廓,蓝色表示手指的方向,红色表示手指骨根到掌心和指尖的连线,黄色表示掌心到指尖的连线:

 

四、手势实现中简要的概括

其他代码都可以在我的GitHub:Leap Motion In Unity3D仓库中(https://github.com/LoranceChen/Leap-Motion-In-Unity3D)获取,在手势的实现中,也包含了一些小的技巧,比如对于动作的匹配要防止手指的颤抖引起的误差,采用离散的数据取样——每隔一定时间做一次取样。

使用和观察这些脚本的方式:可以把这些脚本放在一个GameObject中,通过Leap Motion会看到脚本的属性在匹配成功时会发生变化。另外,脚本中包含了事件的注册功能,换句话说,外部可以向任意的手势注册一个事件,以便手势完成匹配或者到达某种匹配状态时做一些额外的处理。这些脚本现在并不能直接完成我们的需求,如暂停。我们需要在这些手势状态或者动作上做进一步的限定,如根据掌心的方向设定垂直向前的手掌为暂停,水平的手掌为平移之类的。


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324703490&siteId=291194637