zoukankan      html  css  js  c++  java
  • Unity3D中使用Leap Motion进行手势控制

    Leap Motion作为一款手势识别设备,相比于Kniect,长处在于准确度。

    在我的毕业设计《场景漫游器》的开发中。Leap Motion的手势控制作为重要的一个环节。以此,谈谈开发中使用Leap Motion进行手势识别的实现方式以及须要注意的地方。


    一、对Leap Motion的能力进行评估

    在设定手势之前。我们必须知道Leap Motion能做到哪种程度,以免在设定方案之后发现非常难实现。

    这个评估依靠实际对设备的使用体验。主要从三个方面:

    1.Leap Motion提供的可视化的手势识别界面

    2.SDK文档说明

    3.Leap商店中的APP

    基本能够的得出:

    1.Leap Motion的识别对于水平方向或者以水平方向为基础手势可以较好的识别。

    2.对于握拳或者垂直的行为识别会出现误差。这样的误差和详细的手势行为有关。

    3.不应该过分依赖高准确度,Leap Motion能检測到毫米级别是没错的,可是有时候会把你伸直的手指识别成弯曲的。所以要做好最坏的打算。


    二、实际的须要

    移动、旋转、点击button、缩放和旋转物体、关闭程序、暂停,主要的功能需求是这样。

    有一些原则:

    1.同样环境下的手势应该接近和方便的转换。旋转和移动的之间的转换应该设计的非常自然。

    2.手势避免冲突,手势过于相似不是什么好事。

    比方三个伸直的手指和四个伸直的手指不应该被设计成两个手势。当然这不是绝对的。假设你进行一个缓慢的动作而且动作是面向Leap Motion的摄像头,这时候应该相信它。至少要针对这个手势做一个单独的測试。


    三、考虑主要的数据结构和算法的轮廓

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

    算法的轮廓与基本数据有非常大的关系。所以数据结构一定要尽量的精简而且同意改动(可能某个算法占领了决定性因素,可是開始没考虑到)。

    public class HandAndFingersPoint : MonoBehaviour 
    {
    	const int BUFFER_MAX=5;
    	Controller m_LeapCtrl;
    
        <span style="white-space:pre">	</span>public E_HandInAboveView m_AboveView = E_HandInAboveView.None;
        
    	//手指-数据 ,[0]表示左手,[1]表示右手
    	private Dictionary<Finger.FingerType,FingerData>[] m_FingerDatas = new Dictionary<Finger.FingerType, FingerData>[2];
    	//buffer,[0]表示左手,[1]表示右手,[,n](n属于0,3。表示第n次缓存)
    	private Dictionary<Finger.FingerType,FingerData>[,] m_FingerDatasBuffer=new Dictionary<Finger.FingerType, FingerData>[2,BUFFER_MAX];
    	private int m_CurBufIndex=0;
    	//palm 0:左手 和1:右手
    	private PointData[] m_PalmDatas = new PointData[2];
    	
    	private readonly PointData m_DefaultPointData = new PointData(Vector.Zero, Vector.Zero);
            private readonly FingerData m_DefaultFingerData = new FingerData(Vector.Zero,Vector.Zero,Vector.Zero);
    HandAndFingersPoint类中剩下的部分是对数据的填充、清除、刷新等方法。E_HandInAboveView记录哪仅仅手先进入Leap Motion的视野。用于设定优先级。
    另外两个主要的数据结构PointData和FingerData:

    //一个手指的数据包括一个指尖点数据和手指根骨的位置数据
    public struct FingerData
    {
        public PointData m_Point;//指尖的位置和指向
        public Vector m_Position;//手指根骨的位置,对于拇指来说是Proximal phalanges近端指骨的位置
    
        public FingerData(PointData pointData, Vector pos)
        {
            m_Point = pointData;
            m_Position = pos;
        }
    
        public FingerData(Vector pointPos, Vector pointDir, Vector pos)
        {
            m_Point.m_Position = pointPos;
            m_Point.m_Direction = pointDir;
            m_Position = pos;
        }
    
        public void Set(FingerData fd)
        {
    	m_Point = fd.m_Point;
    	m_Position = fd.m_Position;
        }
    }
    //一个点的数据,包括方向和位置
    public struct PointData
    {
        public Vector m_Position;//位置
        public Vector m_Direction;//方向
    
    	public PointData(Vector pos,Vector dir)
    	{
    		m_Position = pos;
    		m_Direction = dir;
    	}
    
    	public void Set(PointData pd)
    	{
    		m_Position = pd.m_Position;
    		m_Direction = pd.m_Direction;
    	}
    
    	public void Set(Vector pos,Vector dir)
    	{
    		m_Position = pos;
    		m_Direction = dir;
    	}
    }
    
    //先被看到的手
    public enum E_HandInAboveView
    {
        None,
        Left,
        Right
    }

    基本数据定义好之后,最好确认数据的填充是没问题的。实际通过Frame frame = Leap.Controller.Frame();来获取最新的数据。

    这时候并不急着写完和基本数据相关的方法。如今终于要的是手势算法的合理性。要推断是否合理,最好先写一个算法。

    最简单的是伸掌手势,在控制中水平的伸掌用于漫游,垂直的伸掌用于暂停。我发现手掌依赖于手指,而手指包含两个状态——伸直和弯曲。

    另外,其它的手势,也都是手指的伸直或者弯曲,外加方向的判定累积出各种效果。理所当然的,应该单独写出手指的弯曲和伸直判定算法:

    /// <summary>
    /// 该方法提供对于单个手指匹配的算法,如伸直。弯曲
    /// 以后可能的改变:对于不同的场景可能要求有所不同。这里的阈值或许会随之改变
    /// </summary>
    public class FingerMatch
    {
    	//弯曲状态的角度阈值
    	static readonly float FingerBendState_Radian = Mathf.PI*4f / 18 ;//40度
    	//伸直状态的角度阈值
    	static readonly float FingerStrightState_Radian = Mathf.PI/12;//15度
    
    	/// <summary>
    	/// 手指伸直的状态,当根骨-指尖的方向和指向的偏差小于阀值时,判定手指为伸直状态。
    	/// 注意无效的方向为零向量。先判定是零向量
    	/// </summary>
    	/// <param name="adjustBorder">对阈值做的微调</param>
    	/// <returns></returns>
    	public static bool StrightState(FingerData fingerData, float adjustBorder=0f)
    	{
    		bool isStright =false;
    		Vector disalDir = fingerData.m_Point.m_Direction;
    		//假设指尖方向为0向量,表示无效的数据
    		if (!disalDir.Equals(Vector.Zero)) 
    		{
    			Vector fingerDir = fingerData.m_Point.m_Position - fingerData.m_Position;//指尖位置减去指根位置,由指根指向指尖的向量	        
    			float radian = fingerDir.AngleTo(disalDir);
    			
    			if (radian < FingerStrightState_Radian + adjustBorder)
    			{
    				isStright = true;
    			}
    		}
    		return isStright;
    	}
    
    	/// <summary>
    	/// 推断一根手指是否处于弯曲状态
    	/// </summary>
    	/// <param name="fingerData">须要判定的手指数据</param>
    	/// <param name="bandBorder">弯曲的阈值</param>
    	/// <returns></returns>
    	public static bool BendState(FingerData fingerData, float adjustBorder=0f)//,out float eulerAugle)
    	{
    		bool isBend = false;
    
    		//eulerAugle = -1f;
    		Vector disalDir = fingerData.m_Point.m_Direction;
    		if( !disalDir.Equals(Vector.Zero) )
    		{
    			Vector fingerDir = fingerData.m_Point.m_Position - fingerData.m_Position;//指尖位置减去指根位置,指跟到指尖的向量
    
    			float radian = fingerDir.AngleTo(disalDir);
    			//eulerAugle = radian*180/Mathf.PI;	
    			//夹角超过定义的阈值时,认定为弯曲状态
    			if (radian > FingerBendState_Radian + adjustBorder)
    			{
    				isBend = true;
    			}
    		}
    
    		return isBend;
    	}
    
    }
    

    上面包括了一个重要的概念——阈值。它是描写叙述究竟何种程度算是伸直,何种程度算是弯曲。阈值的确定是须要实际測试来决定的。

    写到这里也是时候进行一次简单的測试了,毕竟算法的轮廓已经确定。我甚至没写出手掌伸直的判定算法。就确定是可行的。

    基本数据结构相关的操作——HandAndFingersPoint类:源码GitHub链接

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



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

    其它代码都能够在我的GitHub:Leap Motion In Unity3D仓库中获取。在手势的实现中,也包括了一些小的技巧。比方对于动作的匹配要防止手指的颤抖引起的误差。採用离散的数据取样——每隔一定时间做一次取样。

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


  • 相关阅读:
    UVA 10618 Tango Tango Insurrection
    UVA 10118 Free Candies
    HDU 1024 Max Sum Plus Plus
    POJ 1984 Navigation Nightmare
    CODEVS 3546 矩阵链乘法
    UVA 1625 Color Length
    UVA 1347 Tour
    UVA 437 The Tower of Babylon
    UVA 1622 Robot
    UVA127-"Accordian" Patience(模拟)
  • 原文地址:https://www.cnblogs.com/yxwkf/p/5372309.html
Copyright © 2011-2022 走看看