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

悬浮按钮 (Hover Button)

悬浮按钮通过将鼠标点击换成悬浮然后等待(hover-and-wait)动作,解决了不小心点击的问题。当光标位于按钮之上时,意味着用户通过将光标悬浮在按钮上一段时间来表示想选中按钮。另一个重要特点是悬浮按钮在用户悬浮并等待时,多少提供了视觉反馈。

必须使用一个计时器来记录当前用户光标停留在按钮上的时间。一旦用户的手的光标和按钮的边界交叉就开始计时。如果某一个时间阈值内用户光标还没有移除,那么就触发点击事件。

创建一个名为HoverButton的类,他继承自之前创建的KinectButton类,在类中添加一个名为hoverTimer的DispatcherTime实例,代码如下。另外创建一个布尔型的timerEnable字段,将其设置为true。虽然目前不会用到这个字段,但是在后面部分将会用到,当我们想使用HoverButton的某些功能,但是不需要DispatcherTimer时就会非常有用。最后创建一个HoverInterval的依赖属性,使得运行我们将悬浮时间用代码或者xaml进行定义。默认设置为2秒,这是在大多是Xbox游戏中的时间。

public class HoverButton:KinectButton
{
    readonlyDispatcherTimerhoverTimer = newDispatcherTimer();
    protected booltimerEnabled = true; 

    public doubleHoverInterval
    {
        get{ return(double)GetValue(HoverIntervalProperty); }
        set
       {
            SetValue(HoverIntervalProperty, value);
        }
    } 

    public static readonlyDependencyPropertyHoverIntervalProperty =
        DependencyProperty.Register("HoverInterval", typeof(double), typeof(HoverButton), newUIPropertyMetadata(2000d));
……
}

 

要实现悬浮按钮的核心功能,我们必须覆写基类中的OnKinectCursorLeave和OnKinectCursorEnter方法,所有和KinectCursorManger进行交互的部分在KinectButton中已经实现了,因此我们在这里不用操心。在类的构造方法中,只需要实例化DispathcerTimer对象,HoverInterval依赖属性和注册hoverTimer_Tick方法到计时器的Tick事件上即可。计时器在一定的间隔时间会触发Tick事件,该事件简单的处理一个Click事件,在OnKinectCursorEnter方法中启动计数器,在OnKinectCursorLeave事件中停止计数器。另外,重要的是,在enter和leave方法中启动和停止鼠标光标动画效果。

public HoverButton()
{
    hoverTimer.Interval = TimeSpan.FromMilliseconds(HoverInterval);
    hoverTimer.Tick += newEventHandler(hoverTimer_Tick);
    hoverTimer.Stop();
} 

voidhoverTimer_Tick(objectsender, EventArgse)
{
    hoverTimer.Stop();
    RaiseEvent(newRoutedEventArgs(ClickEvent));
} 

protected override voidOnKinectCursorLeave(objectsender, KinectCursorEventArgse)
{
    if(timerEnabled)
    {
        e.Cursor.StopCursorAnimation();
        hoverTimer.Stop();
    }
} 

protected override voidOnKinectCursorEnter(objectsender, KinectCursorEventArgse)
{
    if(timerEnabled)
    {
        hoverTimer.Interval = TimeSpan.FromMilliseconds(HoverInterval);
        e.Cursor.AnimateCursor(HoverInterval);
        hoverTimer.Start();
    }
}

 

悬浮按钮唯一存在的问题是,光标手势悬停在按钮上时会抖动,这可能是Kinect中骨骼识别本身的问题。当在运动状态时,Kinect能够很好的对这些抖动进行平滑,因为即使在快速移动状态下,Kinect中的软件使用了一系列预测和平滑技术来对抖动进行处理。姿势,和上面的悬停一样,因为是静止的,所以可能存在抖动的问题。另外,用户一般不会保持手势静止,即使他们想哪样做。Kinect将这些小的运动返回给用户。当用户什么都没做时,抖动的手可能会破坏手势的动画效果。对悬浮按钮的一个改进就是磁性按钮(Magnet Button),随着体感游戏的升级,这种按钮逐渐取代了之前的悬浮按钮,后面我们将看到如何实现磁性按钮。




下压按钮

就像悬浮按钮在Xbox中那样普遍一样,一些Kinect开发者也想创建一些类似PC上的那种交互方式的按钮,这种按钮称之为下压按钮(push button)。下压按钮试图将传统的GUI界面上的按钮移植到Kinect上去。为了代替鼠标点击,下压按钮使用一种将手向前推的手势来表示按下这一动作。

    这种手势,手掌张开向前,在形式上有点像动态鼠标。下压按钮的核心算法就是探测手势在Z轴上有一个向负方向的运动。另外,相符方向必须有一个距离阈值,使得超过这一阈值就认为用户想要执行下压指令。代码如下所示:下压按钮有一个称之为Threshold的依赖属性,单位为毫米,这个值可以由开发者来根据动作的灵敏度来进行设置。当用户的手移动到下压按钮的上方时,我们记录一下当前位置手的Z值,以此为基准,然后比较手的深度值和阈值,如果超过阈值,就触发点击事件。

public class PushButton:KinectButton
{
    protected double handDepth;
    public double PushThreshold
    {
        get { return (double)GetValue(PushThresholdProperty); }
        set { SetValue(PushThresholdProperty, value); }
    }

    public static readonly DependencyProperty PushThresholdProperty =
        DependencyProperty.Register("PushThreshold", typeof(double), typeof(PushButton), new UIPropertyMetadata(100d));

    protected override void OnKinectCursorMove(object sender, KinectCursorEventArgs e)
    {
        if (e.Z < handDepth – PushThreshold)
        {
            RaiseEvent(new RoutedEventArgs(ClickEvent));
        }
    }

    protected override void OnKinectCursorEnter(object sender, KinectCursorEventArgs e)
    {
        handDepth = e.Z;
    }
}

 



磁性按钮 (Magnet Button)

磁性按钮是对悬浮按钮的一种改进。他对用户悬浮在按钮上的这一体验进行了一些改进。他试图追踪用户手的位置,然后自动将光标对齐到磁性按钮的中间。当用户的手离开磁性按钮的区域是,手势追踪又恢复正常。在其他方面磁性按钮和悬浮按钮的行为一样。考虑到磁性按钮和悬浮按钮在功能方面差异很小,而我们将他单独作为一个完全不同的控件来对待可能有点奇怪。但是,在用户体验设计领域(UX),这一点差异就是一个完全不同的概念。从编码角度看,这一点功能性的差异也使得代码更加复杂。

首先,创建一个继承自HoverButton的名为MagnetButton的类。磁性按钮需要一些额外的事件和属性来管理手进入到磁性按钮区域和手自动对齐到磁性按钮中间区域的时间。我们需要在KinectInput类中添加新的lock和unlock事件

public static readonly RoutedEvent KinectCursorLockEvent = EventManager.RegisterRoutedEvent("KinectCursorLock", RoutingStrategy.Bubble,
typeof(KinectCursorEventHandler), typeof(KinectInput));

public static void AddKinectCursorLockHandler(DependencyObject o, KinectCursorEventHandler handler)
{
    ((UIElement)o).AddHandler(KinectCursorLockEvent, handler);
}

public static readonly RoutedEvent KinectCursorUnlockEvent = EventManager.RegisterRoutedEvent("KinectCursorUnlock", RoutingStrategy.Bubble,
typeof(KinectCursorEventHandler), typeof(KinectInput));

public static void RemoveKinectCursorUnlockHandler(DependencyObject o, KinectCursorEventHandler handler)
{
    ((UIElement)o).RemoveHandler(KinectCursorUnlockEvent, handler);
}

public class MagnetButton : HoverButton
{
    protected bool isLockOn = true;
    public static readonly RoutedEvent KinectCursorLockEvent = KinectInput.KinectCursorUnlockEvent.AddOwner(typeof(MagnetButton));
    public static readonly RoutedEvent KinectCursorUnlockEvent = KinectInput.KinectCursorLockEvent.AddOwner(typeof(MagnetButton));
    private Storyboard move;
    public event KinectCursorEventHandler KinectCursorLock
    {
        add { base.AddHandler(KinectCursorLockEvent, value); }
        remove { base.RemoveHandler(KinectCursorLockEvent, value); }
    }

    public event KinectCursorEventHandler KinectCursorUnLock
    {
        add { base.AddHandler(KinectCursorUnlockEvent, value); }
        remove { base.RemoveHandler(KinectCursorUnlockEvent, value); }
    }

    public double LockInterval
    {
        get { return (double)GetValue(LockIntervalProperty); }
        set { SetValue(LockIntervalProperty, value); }
    }

    public static readonly DependencyProperty LockIntervalProperty =
        DependencyProperty.Register("LockInterval", typeof(double), typeof(MagnetButton), new UIPropertyMetadata(200d));

    public double UnlockInterval
    {
        get { return (double)GetValue(UnlockIntervalProperty); }
        set { SetValue(UnlockIntervalProperty, value); }
    }

    public static readonly DependencyProperty UnlockIntervalProperty =
        DependencyProperty.Register("UnlockInterval", typeof(double), typeof(MagnetButton), new UIPropertyMetadata(80d));

……}

 

磁性按钮的代码中,核心地方在于光标从当前位置移动到磁性按钮的中心位置。看起来很简单,实际上实现起来有点麻烦。需要重写基类中的OnKinectCursorEnter和OnKinectCursorLeave方法。确定磁性按钮的锁定位置第一步需要找到磁性按钮本身所处的位置。代码如下,我们使用WPF中最常见名为FindAncestor帮助方法来遍历可视化对象树来进行查找,需要找到承载该磁性按钮的Windows对象,匹配磁性按钮的当前实例到Windows上,然后将其赋给名为Point的变量。但是point对象只保存了当前磁性按钮的左上角的位置。所以,我们需要给在这个点上加一个磁性按钮一半长宽的偏移值,才能获取到磁性按钮的中心位置x,y。

private T FindAncestor<T>(DependencyObjectdependencyObject) whereT:class
{
    DependencyObjecttarget=dependencyObject;
    do
   {
        target=VisualTreeHelper.GetParent(target);
    }
    while(target!=null&&!(target isT));
    returntarget asT;
} 

protected override void OnKinectCursorEnter(objectsender, KinectCursorEventArgse)
{
        //获取按钮位置
     varrootVisual=FindAncestor<Window>(this);
        varpoint=this.TransformToAncestor(rootVisual).Transform(newPoint(0,0)); 

        varx=point.X+this.ActualWidth/2;
        vary=point.Y+this.ActualHeight/2; 

        varcursor=e.Cursor;
        cursor.UpdateCursor(newPoint(e.X,e.Y),true); 

        //找到目的位置
        PointlockPoint=newPoint(x-cursor.CursorVisual.ActualWidth/2,y-cursor.CursorVisual.ActualHeight/2);
        //当前位置
        PointcursorPoint=newPoint(e.X-cursor.CursorVisual.ActualWidth/2,e.Y-cursor.CursorVisual.ActualHeight/2);
        //将光标从当前位置传送到目的位置
        AnimateCursorToLockPosition(e,x,y,cursor,reflockPoint,refcursorPoint);
        base.OnKinectCursorEnter(sender,e);
} 

protected override void OnKinectCursorLeave(objectsender, KinectCursorEventArgse)
{
     base.OnKinectCursorLeave(sender, e);
    e.Cursor.UpdateCursor(newPoint(e.X,e.Y),false); 

    varrootVisual=FindAncestor<Window>(this);
    varpoint=this.TransformToAncestor(rootVisual).Transform(newPoint(0,0)); 

    varx=point.X+this.ActualWidth/2;
    vary=point.Y+this.ActualHeight/2; 

    varcursor=e.Cursor; 

    //找到目的位置
    PointlockPoint=newPoint(x-cursor.CursorVisual.ActualWidth/2,y-cursor.CursorVisual.ActualHeight/2);
    //当前位置
    PointcursorPoint=newPoint(e.X-cursor.CursorVisual.ActualWidth/2,e.Y-cursor.CursorVisual.ActualHeight/2); 

    AnimateCursorAwayFromLockPosition(e,cursor,reflockPoint,refcursorPoint);
}

 

接下来,我们用手所在的X,Y位置替换手势图标的位置。然而,我们也传入了第二个参数,告诉手势图标自动停止追踪手的位置一段时间。当用户看到光标不听手的使唤自动对齐到磁性按钮的中心,这可能有点不太友好。

    虽然我们现在有了磁性按钮的中心位置,但是我们仍不能很好的将手势光标定位到中心。我们必须额外的给手势光标本身给一个一半长宽的偏移值,以使得手在光标的中心位置而不是在左上角。在完成这些操作之后,我们将最终的值赋给lockPoint变量。我们也执行了同样的操作来查找光标目前的左上角位置以及偏移量,并将其赋值给cursorPoint变量。有了这两个值,我们就可以从当前的位置使用动画移动到目标位置了。动画方法代码如下:

private void AnimateCursorAwayFromLockPosition(KinectCursorEventArgse,CursorAdornercursor,refPointlockPoint,refPointcursorPoint)
{
     DoubleAnimationmoveLeft = newDoubleAnimation(lockPoint.X, cursorPoint.X, newDuration(TimeSpan.FromMilliseconds(UnlockInterval)));
    Storyboard.SetTarget(moveLeft, cursor.CursorVisual);
    Storyboard.SetTargetProperty(moveLeft, newPropertyPath(Canvas.LeftProperty));
    DoubleAnimationmoveTop = newDoubleAnimation(lockPoint.Y, cursorPoint.Y, newDuration(TimeSpan.FromMilliseconds(UnlockInterval)));
    Storyboard.SetTarget(moveTop, cursor.CursorVisual);
    Storyboard.SetTargetProperty(moveTop, newPropertyPath(Canvas.TopProperty));
    move = newStoryboard();
    move.Children.Add(moveTop);
    move.Children.Add(moveLeft);
    move.Completed += delegate{
        move.Stop(cursor);
        cursor.UpdateCursor(newPoint(e.X, e.Y), false);
        this.RaiseEvent(newKinectCursorEventArgs(KinectCursorUnlockEvent, newPoint(e.X, e.Y), e.Z) { Cursor = e.Cursor });
    };
    move.Begin(cursor, true);
} 

private voidAnimateCursorToLockPosition(KinectCursorEventArgse,doublex,doubley,CursorAdornercursor,refPointlockPoint,refPointcursorPoint)
{
     DoubleAnimationmoveLeft=newDoubleAnimation(cursorPoint.X,lockPoint.X,newDuration(TimeSpan.FromMilliseconds(LockInterval)));
    Storyboard.SetTarget(moveLeft,cursor.CursorVisual);
    Storyboard.SetTargetProperty(moveLeft,newPropertyPath(Canvas.LeftProperty)); 

    DoubleAnimationmoveTop=newDoubleAnimation(cursorPoint.Y,lockPoint.Y,newDuration(TimeSpan.FromMilliseconds(LockInterval)));
    Storyboard.SetTarget(moveTop,cursor.CursorVisual);
    Storyboard.SetTargetProperty(moveTop,newPropertyPath(Canvas.TopProperty));
    move=newStoryboard();
    move.Children.Add(moveTop);
    move.Children.Add(moveLeft);
    move.Completed+=delegate
   {
        this.RaiseEvent(newKinectCursorEventArgs(KinectCursorLockEvent,newPoint(x,y),e.Z){Cursor=e.Cursor});
    };
    if(move!=null)
        move.Stop(e.Cursor);
    move.Begin(cursor,false);
}


时间: 2024-10-13 13:03:46

Kinect 开发 —— 常见手势识别(上)的相关文章

Kinect 开发 &mdash;&mdash; 常见手势识别(下)

划动(Swipe) 划动手势和挥手(wave)手势类似.识别划动手势需要不断的跟踪用户手部运动,并保持当前手的位置之前的手的位置.因为手势有一个速度阈值,我们需要追踪手运动的时间以及在三维空间中的坐标.下面的代码展示了存储手势位置点的X,Y,Z坐标以及时间值.如果熟悉图形学中的矢量计算,可以将这个认为是一个四维向量.将下面的结构添加到类库中. public struct GesturePoint { public double X { get; set; } public double Y {

Kinect 开发 &mdash;&mdash; 手势识别(上)

像点击(clicks)是GUI平台的核心,轻点(taps)是触摸平台的核心那样,手势(gestures)是Kinect应用程序的核心 关于手势的定义的中心在于手势能够用来交流,手势的意义在于讲述而不是执行 在人机交互领域,手势通常被作为传达一些简单的指令而不是交流某些事实.描述问题或者陈述想法 使用手势操作电脑通常是命令式的,这通常不是人们使用手势的目的.例如,挥手(wave)这一动作,在现实世界中通常是打招呼的一种方式,但是这种打招呼的方式在人机交互中却不太常用.通常第一次写程序通常会显示"h

Kinect 开发 &mdash;&mdash; 手势识别(下)

基本手势追踪 手部追踪在技术上和手势识别不同,但是它和手势识别中用到的一些基本方法是一样的.在开发一个具体的手势控件之前,我们先建立一个可重用的追踪手部运动的类库以方便我们后续开发.这个手部追踪类库包含一个以动态光标显示的可视化反馈机制.手部追踪和手势控件之间的交互高度松耦合. 首先在Visual Studio中创建一个WPF控件类库项目.然后添加四个类: KinectCursorEventArgs.cs,KinectInput.cs,CusrorAdorner.cs和KinectCursorM

C后端设计开发 - 第6章-武技-常见组件上三路

正文 第6章-武技-常见组件上三路 后记 如果有错误, 欢迎指正. 有好的补充, 和疑问欢迎交流, 一块提高. 在此谢谢大家了.

Kinect 开发 &mdash;&mdash; 骨骼追踪

骨骼追踪技术通过处理景深数据来建立人体各个关节的坐标,骨骼追踪能够确定人体的各个部分,如那部分是手,头部,以及身体.骨骼追踪产生X,Y,Z数据来确定这些骨骼点.骨骼追踪系统采用的景深图像处理技术使用更复杂的算法如矩阵变换,机器学习及其他方式来确定骨骼点的坐标. 获取骨骼数据 彩色影像数据,景深数据分别来自ColorImageSteam和DepthImageStream,同样地,骨骼数据来自SkeletonStream.访问骨骼数据和访问彩色影像数据.景深数据一样,也有事件模式和 "拉"

【Kinect开发笔记之(一)】初识Kinect

一.Kinect简介 Kinect是微软在2010年6月14日对XBOX360体感周边外设正式发布的名字.它是一种3D体感摄影机(开发代号"Project Natal"),同时它导入了即时动态捕捉.影像辨识.麦克风输入.语音辨识.社群互动等功能. 二.Kinect分类 Kinect for Xbox 360:该版本设计之初就是为了Xbox 360定制的,并未考虑其他的平台.从微软授权角度而言,它无法用于商业开发. Kinect for Windows : 固件上做了升级,支持"

【Kinect开发笔记之(二)】Kinect for windows发展历程

新版本SDK和旧版本的SDK完全兼容,如果您之前安装过旧版本的,可以直接安装新版本的SDK,但是如果您之前的开发版本是Beta版的,则需要卸载之后再安装新版本.在Kinect for Windows SDK 1.0版本中,SDK和示例文件是打包一起安装的.而在之后的版本,为了可以分别升级,微软把这两者分开独立为Kinect for Windows SDK和Kinect for Windows Developer Toolkit这两部分,所以需要分别下载安装, Kinect for Windows

Kinect 开发 &mdash;&mdash; 引言

自然人机交互设计技术 (全息三维投影,手势肢体识别,眼动跟踪 ...) 符合人类心理的交互方式 自然用户界面 -- Natural User Interface 有机用户界面 -- Organic User Interface   第六感设备 -- 手势识别,摄像头,投影,云计算 通过对熟知的技术的组合,产生一种NB 的应用               http://www.pranavmistry.com/ 追影技术 -- 用普通摄像头结合运动跟踪算法,实现体感 微软的 CamBot 技术,G

Kinect开发学习笔记之(一)Kinect介绍和应用

Kinect开发学习笔记之(一)Kinect介绍和应用 [email protected] http://blog.csdn.net/zouxy09 一.Kinect简单介绍 Kinectfor Xbox 360,简称 Kinect,是由微软开发,应用于Xbox 360 主机的周边设备.它让玩家不须要手持或踩踏控制器,而是使用语音指令或手势来操作 Xbox360 的系统界面.它也能捕捉玩家全身上下的动作,用身体来进行游戏,带给玩家"免控制器的游戏与娱乐体验".其在2010年11月4日于