实现 Kinect 手势





5.00/5 (3投票s)
如何实现 Kinect 手势
原文发布: http://pterneas.com/2014/01/27/implementing-kinect-gestures/
手势识别是开发基于 Kinect 的应用程序(或其他任何自然用户界面)的基本要素。手势用于导航、交互或数据输入。最常见的示例包括挥手、扫动、缩放、合掌等等。不幸的是,目前的 Kinect for Windows SDK 并没有内置手势检测机制。所以,你可能认为使用 Kinect 识别手势很麻烦?现在不是了。今天,我将向你展示如何使用一些非常简单的技术来实现自己的手势。你不需要成为数学大师或人工智能的“尤达”,就可以构建一个简单的手势检测机制。
必备组件
- Kinect for Windows 或 Kinect for XBOX 传感器
- Microsoft Kinect SDK (或 OpenNI SDK,稍作修改)
什么是手势?
在实现任何东西之前,最好先对其进行定义。Kinect 每秒为您提供用户关节的位置(X、Y 和 Z)30 次(或帧)。如果某些特定点在给定时间内移动到特定的相对位置,那么你就得到了一个手势。因此,从 Kinect 的角度来看,手势是给定帧数下某些关节的相对位置。以挥手动作为例。人们挥手是通过举起左手或右手并将其从一侧移到另一侧。在整个过程中,手通常保持在肘部上方,并周期性地从左到右移动。这是运动的图形表示。
现在您已经看过并理解了什么是手势,让我们尝试指定其底层算法。
手势片段
在挥手动作中,手保持在肘部上方并且周期性地从左到右移动。每个位置(左/右)是手势的离散部分。正式来说,这些部分称为片段。
因此,第一个片段将包含“手在肘部上方”和“手在肘部右侧”的条件
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 种手势,并且还有更多手势即将推出。该代码更通用,您可以轻松地在其之上构建自己的扩展。试一试,享受它,甚至可以贡献您自己的力量!