65.9K
CodeProject 正在变化。 阅读更多。
Home

实现 Kinect 手势

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2014年1月27日

CPOL

5分钟阅读

viewsIcon

46386

downloadIcon

69

如何实现 Kinect 手势

原文发布: http://pterneas.com/2014/01/27/implementing-kinect-gestures/  

手势识别是开发基于 Kinect 的应用程序(或其他任何自然用户界面)的基本要素。手势用于导航、交互或数据输入。最常见的示例包括挥手、扫动、缩放、合掌等等。不幸的是,目前的 Kinect for Windows SDK 并没有内置手势检测机制。所以,你可能认为使用 Kinect 识别手势很麻烦?现在不是了。今天,我将向你展示如何使用一些非常简单的技术来实现自己的手势。你不需要成为数学大师或人工智能的“尤达”,就可以构建一个简单的手势检测机制。

必备组件

什么是手势?

在实现任何东西之前,最好先对其进行定义。Kinect 每秒为您提供用户关节的位置(X、Y 和 Z)30 次(或帧)。如果某些特定点在给定时间内移动到特定的相对位置,那么你就得到了一个手势。因此,从 Kinect 的角度来看,手势是给定帧数下某些关节的相对位置。以挥手动作为例。人们挥手是通过举起左手或右手并将其从一侧移到另一侧。在整个过程中,手通常保持在肘部上方,并周期性地从左到右移动。这是运动的图形表示。

Kinect wave gesture

现在您已经看过并理解了什么是手势,让我们尝试指定其底层算法。

手势片段

在挥手动作中,手保持在肘部上方并且周期性地从左到右移动。每个位置(左/右)是手势的离散部分。正式来说,这些部分称为片段

因此,第一个片段将包含“手在肘部上方”和“手在肘部右侧”的条件

  • Hand.Position.Y > Elbow.Position.Y AND
  • Hand.Position.X > Elbow.Position.X

同样,第二个片段将包含“手在肘部上方”和“手在肘部左侧”的条件

  • Hand.Position.Y > Elbow.Position.Y AND
  • Hand.Position.X < Elbow.Position.X

就是这样。如果您注意到以上片段连续重复了三到四次或更多次,那么用户就在挥手!在 .NET 中,源代码将非常简单;只需两个类代表每个片段。当然,每个片段类都应该实现一个 Update 方法。Update 方法确定给定骨骼身体是否满足指定条件。如果满足片段的所有条件,则返回 Succeeded;如果没有任何条件满足,则返回 Failed

// WaveGestureSegments.cs
using Microsoft.Kinect;

namespace KinectSimpleGesture
{
    public interface IGestureSegment
    {
        GesturePartResult Update(Skeleton skeleton);
    }

    public class WaveSegment1 : IGestureSegment
    {
        public GesturePartResult Update(Skeleton skeleton)
        {
            // Hand above elbow
            if (skeleton.Joints[JointType.HandRight].Position.Y > 
                skeleton.Joints[JointType.ElbowRight].Position.Y)
            {
                // Hand right of elbow
                if (skeleton.Joints[JointType.HandRight].Position.X > 
                    skeleton.Joints[JointType.ElbowRight].Position.X)
                {
                    return GesturePartResult.Succeeded;
                }
            }

            // Hand dropped
            return GesturePartResult.Failed;
        }
    }

    public class WaveSegment2 : IGestureSegment
    {
        public GesturePartResult Update(Skeleton skeleton)
        {
            // Hand above elbow
            if (skeleton.Joints[JointType.HandRight].Position.Y > 
                skeleton.Joints[JointType.ElbowRight].Position.Y)
            {
                // Hand left of elbow
                if (skeleton.Joints[JointType.HandRight].Position.X < 
                    skeleton.Joints[JointType.ElbowRight].Position.X)
                {
                    return GesturePartResult.Succeeded;
                }
            }

            // Hand dropped
            return GesturePartResult.Failed;
        }
    }
}

GesturePartResult 是一个 enum(我们甚至可以使用布尔值)

// GesturePartResult.cs
using System;

namespace KinectSimpleGesture
{
    public enum GesturePartResult
    {
        Failed,
        Succeeded
    }
}

注意:对于更高级的示例,我们可以使用另一个 GesturePartResult(比如“Undetermined”),它表示我们不确定当前手势的结果。

更新手势

我们现在需要一种方法来更新和检查手势,每当传感器向我们提供新的骨骼/身体数据时。这种检查将在一个单独的类中进行,并且每秒会被调用 30 次,或者至少与我们的 Kinect 传感器允许的频率一样高。在更新手势时,我们会检查每个片段,并指定移动是否完成,或者是否需要继续请求数据。

窗口大小

我们请求数据的帧数称为窗口大小,您通过对代码进行实验来找到它。对于持续约一秒的简单手势,窗口大小为 30 或 50 就足够了。对于挥手动作,我选择了 50。

Gesture 类

在确定了窗口大小参数后,我们现在可以构建 WaveGesture 类。请注意此过程

  • 在构造函数中,我们创建手势部分,并在 _segments 数组中指定它们的顺序。您可以根据需要使用任意数量的每个片段!
  • Update 方法中,我们跟踪帧索引并检查每个片段的成功或失败。
  • 如果成功,我们将引发 GestureRecognized 事件并重置手势。
  • 如果失败或已达到窗口大小,我们将重置手势并重新开始。

这是我们挥手动作的最终类

// WaveGesture.cs
using Microsoft.Kinect;
using System;

namespace KinectSimpleGesture
{
    public class WaveGesture
    {
        readonly int WINDOW_SIZE = 50;

        IGestureSegment[] _segments;

        int _currentSegment = 0;
        int _frameCount = 0;

        public event EventHandler GestureRecognized;

        public WaveGesture()
        {
            WaveSegment1 waveSegment1 = new WaveSegment1();
            WaveSegment2 waveSegment2 = new WaveSegment2();

            _segments = new IGestureSegment[]
            {
                waveSegment1,
                waveSegment2,
                waveSegment1,
                waveSegment2,
                waveSegment1,
                waveSegment2
            };
        }

        public void Update(Skeleton skeleton)
        {
            GesturePartResult result = _segments[_currentSegment].Update(skeleton);

            if (result == GesturePartResult.Succeeded)
            {
                if (_currentSegment + 1 < _segments.Length)
                {
                    _currentSegment++;
                    _frameCount = 0;
                }
                else
                {
                    if (GestureRecognized != null)
                    {
                        GestureRecognized(this, new EventArgs());
                        Reset();
                    }
                }
            }
            else if (result == GesturePartResult.Failed || _frameCount == WINDOW_SIZE)
            {
                Reset();
            }
            else
            {
                _frameCount++;
            }
        }

        public void Reset()
        {
            _currentSegment = 0;
            _frameCount = 0;
        }
    }
}

Using the Code

使用我们创建的代码非常简单。在程序中创建一个 WaveGesture 类的实例,并订阅 GestureRecognized 事件。请记住,每当您有新的 Skeleton 帧时,都要调用 Update 方法。这是一个完整的控制台应用程序示例

using Microsoft.Kinect;
using System;

namespace KinectSimpleGesture
{
    class Program
    {
        static WaveGesture _gesture = new WaveGesture();

        static void Main(string[] args)
        {
            var sensor = KinectSensor.KinectSensors.Where(
                         s => s.Status == KinectStatus.Connected).FirstOrDefault();

            if (sensor != null)
            {
                sensor.SkeletonStream.Enable();
                sensor.SkeletonFrameReady += Sensor_SkeletonFrameReady;

                _gesture.GestureRecognized += Gesture_GestureRecognized;

                sensor.Start();
            }

            Console.ReadKey();
        }

        static void Sensor_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            using (var frame = e.OpenSkeletonFrame())
            {
                if (frame != null)
                {
                    Skeleton[] skeletons = new Skeleton[frame.SkeletonArrayLength];

                    frame.CopySkeletonDataTo(skeletons);

                    if (skeletons.Length > 0)
                    {
                        var user = skeletons.Where(
                                   u => u.TrackingState == 
                                        SkeletonTrackingState.Tracked).FirstOrDefault();

                        if (user != null)
                        {
                            _gesture.Update(user);
                        }
                    }
                }
            }
        }

        static void Gesture_GestureRecognized(object sender, EventArgs e)
        {
            Console.WriteLine("You just waved!");
        }
    }
}

就是这样!现在站在您的 Kinect 传感器前,用您的右手挥挥手!

需要注意的一点

显然,您不能期望用户做对所有事情。有人可能会挥手,但没有完成整个动作。另一些人可能只是动作太快或太慢。在开发针对 Kinect 平台的业务应用程序时,您必须意识到所有这些问题,并在代码中添加条件。在常见情况下,您需要指定用户是否“几乎”正在执行手势。也就是说,在确定最终手势结果之前,您需要绕过一定数量的帧。这就是我之前提到“Undetermined”状态的原因。

Vitruvius

因此,如果您想立即获得更多生产就绪的手势,请考虑下载 Vitruvius。Vitruvius 是我构建的一个免费且开源的库,它为您的 Kinect 应用程序提供了许多实用程序。它目前支持 9 种手势,并且还有更多手势即将推出。该代码更通用,您可以轻松地在其之上构建自己的扩展。试一试,享受它,甚至可以贡献您自己的力量!

© . All rights reserved.