Kinect 开发 —— 手势识别(上)

像点击(clicks)是GUI平台的核心,轻点(taps)是触摸平台的核心那样,手势(gestures)是Kinect应用程序的核心算法


关于手势的定义的中心在于手势可以用来交流,手势的意义在于讲述而不是执行数组

在人机交互领域,手势一般被做为传达一些简单的指令而不是交流某些事实、描述问题或者陈述想法网络

使用手势操做电脑一般是命令式的,这一般不是人们使用手势的目的。例如,挥手(wave)这一动做,在现实世界中一般是打招呼的一种方式,可是这种打招呼的方式在人机交互中却不太经常使用。一般第一次写程序一般会显示“hello”,但咱们对和电脑打招呼并不感兴趣。数据结构

    可是,在一个繁忙的餐馆,挥手这一手势可能就有不一样的含义了。当向服务员招收时,多是要引发服务员注意,须要他们提供服务。在计算机中,要引发计算机注意有时候也有其特殊意义,好比,计算机休眠时,通常都会敲击键盘或者移动鼠标来唤醒,以提醒计算机“注意”。当使用Kinect时,可使用更加直观的方式,就行少数派报告阿汤哥那样,抬起双手,或者简单的朝计算机挥挥手,计算机就会从休眠状态唤醒。机器学习

咱们想要使用的任何Kinect手势必须基于应用程序的用户 和应用程序的设计和开发者之间就某种手势表明的含义达成一致。ide


天然用户界面是一系列技术的合计,他包括:语音识别,多点触控以及相似Kinect的动感交互界面,他和Windows和Macs操做系统中鼠标和键盘交互这种很常见图形交互界面不一样函数

NUI界面的设计充分利用了用户预先就会的技能,用户和UI进行交互感到很天然,使得他们甚至忘了是从哪里学到这些和UI进行交互所需的技能的性能

天然用户界面的 依赖于先验知识和不须要媒介的交互这两个特征是每一种NUI界面的共同特征学习

图形用户界面中按钮的一个一般的特征是他提供了一个悬浮状态来指示用户光标已经悬停在的按钮上方的正确位置。这种悬浮状态将点(click)这个动做离散开来。悬浮状态能够为按钮提供一些额外的信息。当将按钮移植到触摸屏交互界面时,按钮不能提供悬浮状态。触摸屏界面只能响应触摸。所以,和电脑上的图像用户界面相比,按钮只能提供“击”(click)操做,而没有“点”(point)的能力。测试

基于Kinect的图形界面中,按钮的行为和触摸界面中的恰好相反,他只提供了悬浮(hover)的“点”(point)的能力,没有“击”(click)的能力。按钮这种更令用户体验设计者感到沮丧的弱点,在过去的几年里,迫使设计者不断的对Kinect界面上的按钮进行改进,以提供更多巧妙的方式来点击视觉元素。这些改进包括:悬停在按钮上一段时间、将手掌向外推(笨拙地模仿点击一个按钮的行为)

人们一般将天然交互界面划分为三类:语音交互界面,触摸交互界面和手势交互界面。


在手势交互界面中,纯粹的手势,姿式和追踪以及他们之间的组合构成了交互的基本术语。对于Kinect来讲,目前可使用的有8个通用的手势:挥手(wave),悬浮按钮(hover button),磁吸按钮(magnet button),推按钮(push button),磁吸幻灯片(magnetic slide),通用暂停(universal pause),垂直滚动条(vertical scrolling)和滑动(swipping)。

在交互设计中,易用性有两个方面:可用(affordance)和反馈(feedback)。反馈就是说用户知道当前正在进行的操做。在网页中,点击按钮会看到按钮有一点偏移,这就表示交互成功。鼠标按键按下时的声音在某种意义上也是一种反馈,他表示鼠标在工做。

若是说反馈发生在操做进行中或者以后,那么可用性(affordance)就发生在操做以前了。可用性就是一种提示或者引导,告诉用户某一个可视化元素是能够交互的,指示用户该元素的用处。在GUI交互界面中,按钮是可以最好的完成这些理念的元素。按钮经过文字或者图标提示来执行一些函数操做。GUI界面上的按钮经过悬浮状态能够提示用户其用途。

以上基本上都是废话 ~~ 下面是具体的手势识别实现


有三种基本的方法能够用来识别手势:基于算法,基于神经网络和基于手势样本库。每一种方法都有其优缺点。开发者具体采用那种方法取决与待识别的手势、项目需求,开发时间以及开发水平。基于算法的手势识别相对简单容易实现,基于神经网络和手势样本库则有些复杂。

使用算法的基本流程就是定义处理规则和条件,这些处理规则和条件必须符合处理结果的要求。在手势识别中,这种算法的结果要求是一个二值型对象,某一手势要么符合预约的手势要么不符合。可是,最简单直接的方法也有其缺点。算法的简单性限制了其能识别到的手势的类别。对于挥手(wave)识别较好的算法不可以识别扔(throw)和摆(swing)动做。前者动做相对简单和规整,后者则更加细微且多变。

算法还有一个内在的扩展性问题。虽然一些代码能够重用,可是每一种手势必须使用定制的算法来进行识别。随着新的手势识别算法加入类库,类库的大小会迅速增长。这就对程序的性能产生影响,由于须要使用不少算法来对某一个手势进行识别以判断该手势的类型。

最后,每个手势识别算法须要不一样的参数,例如时间间隔和阈值。尤为是在依据流程识别特定的手势的时候这一点显得尤为明显。开发者须要不断测试和实验觉得每一种算法肯定合适的参数值。这自己是一个有挑战也很乏味的工做。然而每一种手势的识别有着本身特殊的问题。

 

例如跳跃手势,跳跃手势就是用户短暂的跳起来,脚离开地面。这个定义不可以提供足够的信息来识别这一动做。咋一看,这个动做彷佛足够简单,使得可使用算法来进行识别。首先,考虑到有不少种不一样形式的跳跃:基本跳跃(basic jumping)、 跨栏(hurdling)、 跳远(long jumping)、 跳跃(hopping),等等。可是这里有一个大的问题就是,因为受到Kinect视场区域的限制,不可能老是可以探测到地板的位置,这使得脚部什么时候离开地板很难肯定。想象一下,用户在膝盖到下蹲点处弯下,而后跳起来。手势识别引擎应该认为这是一个手势仍是多个手势:下蹲或 下蹲跳起或者是跳起?若是用户在蹲下的时间和跳跃的时间相比过长,那么这一手势可能应被识别为下蹲而不是跳跃。这一姿式很难定义清楚,使得不可以经过定义一些算法来进行识别,同时这些算法因为须要定义过多的规则和条件而变得难以管理和不稳定。使用对或错的二值策略来识别用户手势的算法太简单和不够健壮,不可以很好的识别出相似跳跃,下蹲等动做。

神经网络的组织和判断是基于统计和几率的,所以使得像识别手势这些过程变得容易控制。基于什么网络的手势识别引擎对于下蹲而后跳跃动做,80%的几率判断为跳跃,10%会断定为下蹲。

除了可以识别复杂和精细的手势,神经网络方法还能解决基于算法手势识别存在的扩展性问题。神经网络包含不少神经元,每个神经元是一个好的算法,可以用来判断手势的细微部分的运动。在神经网络中,许多手势能够共享神经元。可是每一中手势识别有着独特的神经元的组合。并且,神经元具备高效的数据结构来处理信息。这使得在识别手势时具备很高的效率。

和基于算法的方法相比,神经网络依赖大量的参数来能获得精确的结果。参数的个数随着神经元的个数增加。每个神经元能够用来识别多个手势,每个神经远的参数的变化都会影响其余节点的识别结果。配置和调整这些参数是一项艺术,须要经验,并无特定的规则可循。然而,当神经网络配对机器学习过程当中手动调整参数,随着时间的推移,系统的识别精度会随之提升。

 

基于样本或者基于模版的手势识别系统可以将人的手势和已知的手势相匹配。用户的手势在模板库中已经规范化了,使得可以用来计算手势的匹配精度。有两种样本识别方法,一种是存储一系列的点,另外一种方法是使用相似的Kinect SDK中的骨骼追踪系统。在后面的那个方法中,系统中包含一系列骨骼数据和景深帧数据,可以使用统计方法对产生的影像帧数据进行匹配以识别出已知的帧数据来。

这种手势识别方法高度依赖于机器学习。识别引擎会记录,处理,和重用当前帧数据,因此随着时间的推移,手势识别精度会逐步提升。系统可以更好的识别出你想要表达的具体手势。这种方法可以比较容易的识别出新的手势,并且较其余两种方法可以更好的处理比较复杂的手势。可是创建这样一个系统也不容易。首先,系统依赖于大量的样本数据。数据越多,识别精度越高。因此系统须要大量的存储资源和CPU时间的来进行查找和匹配。其次系统须要不一样高度,不一样胖瘦,不一样穿着(穿着会影响景深数据提取身体轮廓)的样原本进行某一个手势。


识别常见的手势

选择手势识别的方法一般是依赖于项目的须要。若是项目只须要识别几个简单的手势,那么使用基于算法或者基于神经网络的手势识别就足够了。对于其余类型的项目,若是有兴趣的话能够投入时间来创建可复用的手势识别引擎,或者使用一些人家已经写好的识别算法

 

不论选择哪一种手势识别的方法,都必须考虑手势的变化范围。系统必须具备灵活性,并容许某一个手势有某个范围内的变更。技巧就是对于某一手势,让尽量多的人来作,而后试图标准化这一手势。手势识别一个比较好的方式就是关注手势最核心的部分而不是哪些外在的细枝末节。

 

    挥手是最简单最基本的手势。使用算法方法可以很容易识别这一手势,可是以前讲到的任何方法也可以使用。虽然挥手是一个很简单的手势,可是如何使用代码来识别这一手势呢?读者能够在镜子前作向本身挥手,而后仔细观察手的运动,尤为注意观察手和胳膊之间的关系。继续观察手和胳膊之间的关系,而后观察在作这个手势事身体的整个姿式。有些人保持身体和胳膊的不动,使用手腕左右移动来挥手。有些人保持身体和胳膊不动使用手腕先后移动来挥手。能够经过观察这些姿式来了解其余各类不一样挥手的方式。

XBOX中的挥手动做定义为:从胳膊开始到肘部弯曲。用户以胳膊肘为焦点来回移动前臂,移动平面和肩部在一个平面上,而且胳膊和地面保持平行,在手势的中部(下图1),前臂垂直于后臂和地面。

image

从图中能够观察得出一些规律,第一个规律就是,手和手腕都是在肘部和肩部之上的,这也是大可能是挥手动做的特征。这也是咱们识别挥手这一手势的第一个标准。第一幅图展现了挥手这一姿式的中间位置,前臂和后臂垂直。若是用户手臂改变了这种关系,前臂在垂直线左边或者右边,咱们则认为这是该手势的一个片断。对于挥手这一姿式,每个姿式片断必须来回重复屡次,不然就不是一个完整的手势。这一运动规律就是咱们的第二个准则:当某一手势是挥手时,手或者手腕,必须在中间姿式的左右来回重复特定的次数。使用这两点经过观察获得的规律,咱们能够经过算法创建算法准则,来识别挥动手势了。

算法经过计算手离开中间姿式区域的次数。中间区域是一个以胳膊肘为原点并给予必定阈值的区域。算法也须要用户在必定的时间段内完成这个手势,不然识别就会失败。这里定义的挥动手势识别算法只是一个单独的算法,不包含在一个多层的手势识别系统内。算法维护自身的状态,并在识别完成时以事件形式告知用户识别结果。挥动识别监视多个用户以及两双手的挥动手势。识别算法计算新产生的每一帧骨骼数据,所以必须记录这些识别的状态。

 

下面的代码展现了记录手势识别状态的两个枚举和一个结构。第一个名为WavePosition的枚举用来定义手在挥手这一动做中的不一样位置。手势识别类使用WaveGestureState枚举来追踪每个用户的手的状态。WaveGestureTracker结构用来保存手势识别中所须要的数据。他有一个Reset方法,当用户的手达不到挥手这一手势的基本动做条件时,好比当手在胳膊肘如下时,可调用Reset方法来重置手势识别中所用到的数据。

private enum WavePosition
{
    None = 0,
    Left = 1,
    Right = 2,
    Neutral = 3
}

private enum WaveGestureState
{
    None = 0,
    Success = 1,
    Failure = 2,
    InProgress = 3
}

private struct WaveGestureTracker
{
    public int IterationCount;
    public WaveGestureState State;
    public long Timestamp;
    public WavePosition StartPosition;
    public WavePosition CurrentPosition;

    public void Reset()
    {
        IterationCount = 0;
        State = WaveGestureState.None;
        Timestamp = 0;
        StartPosition = WavePosition.None;
        CurrentPosition = WavePosition.None;
    }
}

 

下面代码显示了手势识别类的最基本结构:它定义了五个常量:中间区域阈值,手势动做持续时间,手势离开中间区域左右移动次数,以及左手和右手标识常量。这些常量应该做为配置文件的配置项存储,在这里为了简便,因此以常量声明。WaveGestureTracker数组保存每个可能的游戏者的双手的手势的识别结果。当挥手这一手势探测到了以后,触发GestureDetected事件。

当主程序接收到一个新的数据帧时,就调用WaveGesture的Update方法。该方法循环遍历每个用户的骨骼数据帧,而后调用TrackWave方法对左右手进行挥手姿式识别。当骨骼数据不在追踪状态时,重置手势识别状态。

public class WaveGesture
{
    private const float WAVE_THRESHOLD = 0.1f;
    private const int WAVE_MOVEMENT_TIMEOUT = 5000;
    private const int LEFT_HAND = 0;
    private const int RIGHT_HAND = 1;
    private const int REQUIRED_ITERATIONS = 4;

    private WaveGestureTracker[,] _PlayerWaveTracker = new WaveGestureTracker[6, 2];

    public event EventHandler GestureDetected;

    public void Update(Skeleton[] skeletons, long frameTimestamp)
    {
        if (skeletons != null)
        {
            Skeleton skeleton;

            for (int i = 0; i < skeletons.Length; i++)
            {
                skeleton = skeletons[i];

                if (skeleton.TrackingState != SkeletonTrackingState.NotTracked)
                {
                    TrackWave(skeleton, true, ref this._PlayerWaveTracker[i, LEFT_HAND], frameTimestamp);
                    TrackWave(skeleton, false, ref this._PlayerWaveTracker[i, RIGHT_HAND], frameTimestamp);
                }
                else
                {
                    this._PlayerWaveTracker[i, LEFT_HAND].Reset();
                    this._PlayerWaveTracker[i, RIGHT_HAND].Reset();
                }
            }
        }
    }
}

下面的代码是挥手姿式识别的主要逻辑方法TrackWave的主体部分。它验证咱们先前定义的构成挥手姿式的条件,并更新手势识别的状态。方法识别左手或者右手的手势,第一个条件是验证,手和肘关节点是否处于追踪状态。若是这两个关节点信息不可用,则重置追踪状态,不然进行下一步的验证。

    若是姿式持续时间超过阈值且尚未进入到下一步骤,在姿式追踪超时,重置追踪数据。下一个验证手部关节点是否在肘关节点之上。若是不是,则根据当前的追踪状态,挥手姿式识别失败或者重置识别条件。若是手部关节点在Y轴上且高于肘部关节点,方法继续判断手在Y轴上相对于肘关节的位置。调用UpdatePosition方法并传入合适的手关节点所处的位置。更新手关节点位置以后,最后判判定义的重复次数是否知足,若是知足这些条件,挥手这一手势识别成功,触发GetstureDetected事件。

private void TrackWave(Skeleton skeleton, bool isLeft, ref WaveGestureTracker tracker, long timestamp)
{
    JointType handJointId = (isLeft) ? JointType.HandLeft : JointType.HandRight;
    JointType elbowJointId = (isLeft) ? JointType.ElbowLeft : JointType.ElbowRight;
    Joint hand = skeleton.Joints[handJointId];
    Joint elbow = skeleton.Joints[elbowJointId];


    if (hand.TrackingState != JointTrackingState.NotTracked && elbow.TrackingState != JointTrackingState.NotTracked)
    {
        if (tracker.State == WaveGestureState.InProgress && tracker.Timestamp + WAVE_MOVEMENT_TIMEOUT < timestamp)
        {
            tracker.UpdateState(WaveGestureState.Failure, timestamp);
            System.Diagnostics.Debug.WriteLine("Fail!");
        }
        else if (hand.Position.Y > elbow.Position.Y)
        {
            //使用 (0, 0) 做为屏幕的中心.  从用户的视角看, X轴左负右正.
            if (hand.Position.X <= elbow.Position.X - WAVE_THRESHOLD)
            {
                tracker.UpdatePosition(WavePosition.Left, timestamp);
            }
            else if (hand.Position.X >= elbow.Position.X + WAVE_THRESHOLD)
            {
                tracker.UpdatePosition(WavePosition.Right, timestamp);
            }
            else
            {
                tracker.UpdatePosition(WavePosition.Neutral, timestamp);
            }


            if (tracker.State != WaveGestureState.Success && tracker.IterationCount == REQUIRED_ITERATIONS)
            {
                tracker.UpdateState(WaveGestureState.Success, timestamp);
                System.Diagnostics.Debug.WriteLine("Success!");

                if (GestureDetected != null)
                {
                    GestureDetected(this, new EventArgs());
                }
            }
        }
        else
        {
            if (tracker.State == WaveGestureState.InProgress)
            {
                tracker.UpdateState(WaveGestureState.Failure, timestamp);
                System.Diagnostics.Debug.WriteLine("Fail!");
            }
            else
            {
                tracker.Reset();
            }
        }
    }
    else
    {
        tracker.Reset();
    }
}

下面的代码添加到WaveGestureTracker结构中:这些帮助方法维护结构中的字段,使得TrackWave方法易读。惟一须要注意的是UpdatePosition方法。TrackWave调用该方法判断手的位置已经移动。他的最主要目的是更新CurrentPosition和Timestamp属性,该方法也负责更新InterationCount字段合InPorgress状态。

public void UpdateState(WaveGestureState state, long timestamp)
{
    State = state;
    Timestamp = timestamp;
}

public void Reset()
{
    IterationCount = 0;
    State = WaveGestureState.None;
    Timestamp = 0;
    StartPosition = WavePosition.None;
    CurrentPosition = WavePosition.None;
}

public void UpdatePosition(WavePosition position, long timestamp)
{
    if (CurrentPosition != position)
    {
        if (position == WavePosition.Left || position == WavePosition.Right)
        {
            if (State != WaveGestureState.InProgress)
            {
                State = WaveGestureState.InProgress;
                IterationCount = 0;
                StartPosition = position;
            }

            IterationCount++;
        }

        CurrentPosition = position;
        Timestamp = timestamp;
    }
}
相关文章
相关标签/搜索