姿势和手势通常会混淆,但是他们是两个不同的概念。当一个人摆一个姿势时,他会保持身体的位置和样子一段时间。但是手势包含有动作,例如用户通过手势在触摸屏上,放大图片等操作。

通常,游戏者很容易模仿指定姿势并且比较容易编写算法来识别指定的姿势。例如,如果开发一个用户在天上飞的游戏。 一种控制游戏的方式是,游戏者像鸟一样挥动手臂。挥动的频率越快游戏角色飞的越快,这是一个手势。还有一种方法是,展开双臂,双臂张得越快开,飞的越快。双臂离身体越近,飞的越慢。

身体以及各个关节点的位置定义了一个姿势。更具体的来说,是某些关节点相对于其他关节点的位置定义了一个姿势。姿势的类型和复杂度决定了识别算法的复杂度。通过关节点位置的交叉或者关节点之间的角度都可以进行姿势识别。

通过关节点交叉进行姿势识别就是对关节点进行命中测试。在前一篇文章中,我们可以确定某一个关节点的位置是否在UI界面上某一个可视化元素的有效范围内。我们可以对关节点做同样的测试。但是需要的工作量要少的多,因为所有的关节点都是在同一个坐标空间中,这使得计算相对容易。例如叉腰动作(hand-on-hip),可以从骨骼追踪的数据获取左右髋关节和左右手的位置。然后计算左手和左髋关节的位置。如果这个距离小于某一个阈值,就认为这两个点相交。这个阈值可以很小,对一个确定的相交点进行命中测试,就像我们对界面上可视化元素进行命中测试那样,可能会有比较不好的用户界面。即使通过一些平滑参数设置,从Kinect中获取的关节点数据要完全匹配也不太现实。另外,不可能期望用户做出一些连贯一致的动作,或者保持一个姿势一段时间。简而言之,用户运动的精度以及数据的精度使得这种简单计算不适用。因此,计算两个点的长度,并测试长度是否在一个阈值内是唯一的选择

当两个关节点比较接近时,会导致关节点位置精度进一步下降,这使得使用骨骼追踪引擎判断一个关节点的开始是否是另一个关节点的结束点变得困难。例如,如果将手放在脸的位置上,那么头的位置大致就在鼻子那个地方,手的关节点位置和头的关节点位置就不能匹配起来。这使得难以区分某些相似的姿势,比如,很难将手放在脸的前面,手放在头上,和手捂住耳朵这几个姿势区分开来。

节点交叉并不需要使用X,Y的所有信息。一些姿势只需要使用一个坐标轴信息。例如:立正姿势,在这个姿势中,手臂和肩膀近乎在一个垂直坐标轴内而不用考虑用户的身体的大小和形状。在这个姿势中,逻辑上只需要测试手和肩部节点的X坐标的差值,如果在一个阈值内就可以判断这些关节点在一个平面内。但是这并不能保证用户是立正姿势。应用程序还需要判断手在Y坐标轴上应该低于肩部。

并不是所有的姿势识别都适合使用节点交叉法,一些姿势使用其他方法识别精度会更高。例如,用户伸开双臂和肩膀在一条线上这个姿势,称之为T姿势。可以使用节点相交技术,判断手、肘、以及肩膀是否在Y轴上处于近乎相同的位置。另一种方法是计算某些关节点连线之间的角度。骨骼追踪引擎能够识别多达20个关节点数据。任何三个关节点就可以组成一个三角形。使用三角几何就可以计算出他们之间的角度。

实际上我们只需要根据两个关节点即可绘制一个三角形,第三个点有时候可以这两个关节点来决定的。知道每个节点的坐标就可以计算每个边长的值。然后使用余弦定理就可以计算出角度了。

为了演示使用关节点三角形方法来识别姿势,我们考虑在健美中常看到了展示肱二头肌姿势。用户肩部和肘在一条线上并且和地面平行,手腕与肘部与胳膊垂直。在这个姿势中,可以很容易看到有一个直角或者锐角三角形。我们可以使用上面所说的方法来计算三角形的每一个角度

有两种使用节点三角形的方法。最明显的如上面的例子那样,使用三个节点来构造一个三角形。另一个方法就是使用两个节点,第三个节点手动指定一个点。这种方法取决于姿势的限制和复杂度。在上面的例子中,我们使用三个及节点的方法,因为需要的角度可以由手腕-肘-肩部构成。不论其他部位如何变化,这三者所构成的三角形相形状相对不变。

使用两个节点来识别这一动作只需要肘部和手腕关节点信息。将肘部作为整个坐标系统的中心或者零点。以肘部为基准点,随便找一个水平的X轴上的点。然后就可以由这三点组成一个三角形。在两点方法中,用户在直立和有点倾斜姿势下所计算得到的结果是不一样的。


响应识别到的姿势

识别姿势的目的是触发一些操作。最简单的方法是当探测到某一姿势后立即响应一些类似鼠标点击之类的事件。

应用程序要使用姿势识别必须知道什么时候该忽略什么时候该响应特定的姿势。如前所述,最简单的方法是当识别到某一姿势时立即响应。如果这是应用程序的功能,需要选择一个用户不可能会在休息或者放松时会产生的姿势。选择一个姿势很容易,但是这个姿势不能是户自然而然或者大多数情况下都会产生的姿势。这意味着姿势必须是有意识的,就像是鼠标点击那样,用户需要进行某项操作才会去做某种特定的姿势。除了马上响应识别到的某个姿势外,另一种方法是触发一个计时器。只有用户保持这一姿势一段时间,应用程序才会触发相应的操作。

另一种方法是当用户摆出某一系列的姿势时才触发某一动作。这需要用户按照特定的序列摆出一些列的姿势,才会执行某一操作。使用系列姿势和一些不常用的姿势可以使得应用程序知道用户有意想进行某一项操作,而不是误操作。换句话说,这能够帮助用户减少误操作。


Simon Says 游戏中使用姿势识别

Simon指令时让用户按照顺序做一系列的姿势,而不是触摸那四个矩形。使用关节点角度进行姿势识别可以给予应用程序更多的姿势选择。

使用姿势替代可视化元素需要对代码做出较大改动,但幸好的是识别姿势的代码比命中测试和判断手是否在指定可视化元素有效范围内的代码要少。姿势识别主要是使用三角几何。改动代码的同时也改变了用提体验和游戏的玩法。所有界面上的矩形块都会移除,只保留TextBlocks和手形图标。我们还需要用一定的方式提示用户摆出某种姿势。最好的方式是显示要摆出姿势的图片。为了简便,我们这里使用一个TextBlock,显示姿势的名称,让用户来做指定的姿势。

使用摆出某种姿势来开始游戏 —— 用户摆出一个T型的姿势

游戏只需要在指定的时间内摆出某种要求的姿势,如果在规定的时间不能摆出姿势的话,游戏就结束了。如果识别了指定的姿势,游戏继续下一个姿势,计时器归零。

为了让游戏好玩,需要尽可能多的选择可识别的姿势。另外,还要能比较容易的将新的姿势添加进来。为了创建一个姿势库,需要创建一个新的PoseAngle类和名为Pose的结构。如下面的代码所示。Pose存储了一个姿势的名称和一个PoseAngle数组。PoseAngle的有两个JointType类型的成员变量用来计算角度,Angle为期望角度,Threshold 阈值。

就像命中测试那样,只要关节点夹角在一定的阈值范围内即可。

public class PoseAngle
{
public PoseAngle(JointType centerJoint, JointType angleJoint, double angle, double threshold)
{
CenterJoint = centerJoint;
AngleJoint = angleJoint;
Angle = angle;
Threshold = threshold;
} public JointType CenterJoint { get; private set;}
public JointType AngleJoint { get; private set;}
public double Angle { get; private set;}
public double Threshold { get; private set;}
} public struct Pose
{
public string Title;
public PoseAngle[] Angles;
}

开始姿势和姿势库定义好了之后,下面来开始改写游戏的逻辑代码。当游戏GameOver时,会调用ProcessGameOver方法。在前篇文章中,这个方法用来判断用户的双手是否在指定的对象上,现在替换为识别用户的姿势是否是指定的姿势。如下代码展示了如何处理游戏开始和姿势识别,IsPose方法判断是否和指定的姿势匹配,这个方法在多个地方都可能会用到。IsPost方法遍历一个姿势中的所有PoseAngle,如果任何一个关节点角度和定义的不一致,方法就返回false,表示不是指定的姿势。方法中的if语句用来判断角度是否在360度范围内,如果不在,则转换到该范围内。

private void ProcessGameOver(Skeleton skeleton)
{
if(IsPose(skeleton, this.startPose))
{
ChangePhase(GamePhase.SimonInstructing);
}
} private bool IsPose(Skeleton skeleton, Pose pose)
{
bool isPose = true;
double angle;
double poseAngle;
double poseThreshold;
double loAngle;
double hiAngle; for(int i = ; i < pose.Angles.Length && isPose; i++)
{
poseAngle = pose.Angles[i].Angle;
poseThreshold = pose.Angles[i].Threshold;
angle = GetJointAngle(skeleton.Joints[pose.Angles[i].CenterJoint], skeleton.Joints[pose.Angles[i].AngleJoint]); hiAngle = poseAngle + poseThreshold;
loAngle = poseAngle - poseThreshold; if(hiAngle >= || loAngle < )
{
loAngle = (loAngle < ) ? + loAngle : loAngle;
hiAngle = hiAngle % ; isPose = !(loAngle > angle && angle > hiAngle);
}
else
{
isPose = (loAngle <= angle && hiAngle >= angle);
}
}
return isPose;
}

IsPost方法调用GetJointAngle方法来计算两个关节点之间的角度。GetJointAngle调用GetJointPoint方法来获取每一个节点在主UI布局空间中的坐标。这一步其实没有太大必要,原始的位置信息也可以用来计算角度。但是,将关节点的坐标转换到主UI界面上来能够帮助我们进行调试。获得了节点的位置后,使用余弦定理计算节点间的角度。Math.Acos返回的值是度,将其转换到角度值。If语句处理角度值在180-360的情况。余弦定理返回的角度在0-180度内,if语句将在第三和第四象限的值调整到第一第二象限中来。

private double GetJointAngle(Joint centerJoint, Joint angleJoint)
{
Point primaryPoint = GetJointPoint(this.KinectDevice, centerJoint, this.LayoutRoot.RenderSize, new Point());
Point anglePoint = GetJointPoint(this.KinectDevice, angleJoint, this.LayoutRoot.RenderSize, new Point());
Point x = new Point(primaryPoint.X + anglePoint.X, primaryPoint.Y); double a;
double b;
double c; a = Math.Sqrt(Math.Pow(primaryPoint.X - anglePoint.X, ) + Math.Pow(primaryPoint.Y - anglePoint.Y, ));
b = anglePoint.X;
c = Math.Sqrt(Math.Pow(anglePoint.X - x.X, ) + Math.Pow(anglePoint.Y - x.Y, )); double angleRad = Math.Acos((a * a + b * b - c * c) / ( * a * b));
double angleDeg = angleRad * / Math.PI; if(primaryPoint.Y < anglePoint.Y)
{
angleDeg = - angleDeg;
} return angleDeg;
}

程序还必须识别姿势并启动程序。当程序识别到启动的姿势是,将游戏的状态切换到SimonInstructing。这部分代码和GenerateInstructions及DisplayInstructions是分开的。将GenerateInstructions产生的指令改为随机的从姿势库中选取某一个姿势。然后使用选择的姿势填充指令集合。DisplayInstructions方法可以使用自己的方法比如图片来给用户以提示。一旦游戏显示完指令,游戏转入PlayerPerforming阶段。这个阶段给了游戏者一定的时间来摆出特定的姿势,当程序识别到需要的姿势时,转到下一个姿势,并重启计时器。如果超过给定时间仍然没有给出指定的姿势,游戏结束。WPF中System.Windows.Threading命名空间下的DispatcherTimer类可以简单的完成计时器的功能。


提升

可以考虑创建一个PoseEngine类,他有一个PoseDetected事件。当引擎识别到骨骼数据摆出了一个姿势时,触发该事件。默认地,PoseEngine类监听SkeletonFrameReady事件,他能够一帧一帧的使用某种方法测试骨骼数据帧,这使得能够支持“拉”数据模型。PosEngine类有一个Pose集合,他定义了一些能够识别的姿势合集。可以就像.Net中的List那样使用Add和Remove方法进行添加或者删除,开发者可以为应用程序定义一个姿势库。

为了能够动态的添加和删除姿势,姿势定义那部分代码不能像我们之前的Simon Says游戏中的那样硬编码。最简单的方法是使用序列化。序列化姿势数据有两个好处,一是姿势很容易从应用程序中添加和移除。应用程序可以在运行时动态对添加到配置文件中的姿势进行读取。更进一步的,我们可以将这些姿势配置持久化,使得我们可以创建一个专门的工具来捕捉或者定义姿势。

开发一个能够捕捉用户姿势,并将数据序列化成应用程序直接使用的数据源不是太难。这个程序可以使用前面我们所讲到的知识开发出来。可以在SkeletonView自定义控件的基础上,添加关节点之间角度计算逻辑。然后显示在SkeletonVeiw的输出信息中,将角度信息显示在关节点位置。姿势捕捉工具使用函数来对这用户的姿势进行截图,这截图实际上是一系列关节点之间的角度信息,截图可以序列化,使得能够很容易的添加到其他应用程序中去。

将SkeletonView根据上面的想法进行改进后,可以显示关节点夹角信息。下图展示了可能的输出。使得能够很容易的看出各个关节点之间的夹角。可以根据这个夹角来手动的定义一些姿势。甚至可以开发出一些工具根据这些夹角来生成姿势配置文件。将夹角显示在UI上也能提供很多有用的调试信息。


namespace SimonSayAction
{
public enum GamePhase
{
GameOver = ,
SimonInstructing = ,
PlayerPerforming =
} public partial class MainWindow : Window
{
#region Member Variables
private KinectSensor kinectDevice;
private Skeleton[] frameSkeletons;
private GamePhase currentPhase;
private int[] instructionSequence;
private int instructionPosition;
private int currentLevel;
private Random rnd = new Random();
private Pose[] poseLibrary;
private Pose startPose;
private DispatcherTimer poseTimer;
#endregion Member Variables #region Constructor
public MainWindow()
{
InitializeComponent(); this.currentLevel = ; this.poseTimer = new DispatcherTimer();
this.poseTimer.Interval = TimeSpan.FromSeconds();
// 返回表示指定秒数的 System.TimeSpan,其中对秒数的指定精确到最接近的毫秒
this.poseTimer.Tick += (s, e) => { ChangePhase(GamePhase.GameOver); };
this.poseTimer.Stop(); PopulatePoseLibrary(); // 初始化游戏姿势库
ChangePhase(GamePhase.GameOver); KinectSensor.KinectSensors.StatusChanged += KinectSensors_StatusChanged;
this.KinectDevice = KinectSensor.KinectSensors.FirstOrDefault(x => x.Status == KinectStatus.Connected);
}
#endregion Constructor #region Methods
private void KinectSensors_StatusChanged(object sender, StatusChangedEventArgs e)
{
switch (e.Status)
{
case KinectStatus.Initializing:
case KinectStatus.Connected:
case KinectStatus.NotPowered:
case KinectStatus.NotReady:
case KinectStatus.DeviceNotGenuine:
this.KinectDevice = e.Sensor;
break;
case KinectStatus.Disconnected:
//TODO: Give the user feedback to plug-in a Kinect device.
this.KinectDevice = null;
break;
default:
//TODO: Show an error state
break;
}
} private void KinectDevice_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
using (SkeletonFrame frame = e.OpenSkeletonFrame())
{
if (frame != null)
{
frame.CopySkeletonDataTo(this.frameSkeletons);
Skeleton skeleton = GetPrimarySkeleton(this.frameSkeletons); // 获取最近骨架 if (skeleton == null)
{
ChangePhase(GamePhase.GameOver);
}
else
{
if (this.currentPhase == GamePhase.SimonInstructing)
{
LeftHandElement.Visibility = System.Windows.Visibility.Collapsed;
RightHandElement.Visibility = System.Windows.Visibility.Collapsed;
}
else
{
TrackHand(skeleton.Joints[JointType.HandLeft], LeftHandElement, LayoutRoot);
TrackHand(skeleton.Joints[JointType.HandRight], RightHandElement, LayoutRoot); switch (this.currentPhase)
{
case GamePhase.GameOver:
ProcessGameOver(skeleton);
break; case GamePhase.PlayerPerforming:
ProcessPlayerPerforming(skeleton);
break;
}
}
}
}
}
} private void TrackHand(Joint hand, FrameworkElement cursorElement, FrameworkElement container)
{
if (hand.TrackingState == JointTrackingState.NotTracked)
{
cursorElement.Visibility = Visibility.Collapsed;
}
else
{
cursorElement.Visibility = Visibility.Visible;
Point jointPoint = GetJointPoint(this.KinectDevice, hand, container.RenderSize, new Point(cursorElement.ActualWidth / 2.0, cursorElement.ActualHeight / 2.0));
Canvas.SetLeft(cursorElement, jointPoint.X);
Canvas.SetTop(cursorElement, jointPoint.Y);
}
} private void ProcessGameOver(Skeleton skeleton)
{
if (IsPose(skeleton, this.startPose)) // 是否是开始姿势
{
ChangePhase(GamePhase.SimonInstructing);
}
} private static Point GetJointPoint(KinectSensor kinectDevice, Joint joint, Size containerSize, Point offset)
{
// 虽然骨骼点在同一坐标空间,但是通过转换成UI相关,可以方便调试
DepthImagePoint point = kinectDevice.MapSkeletonPointToDepth(joint.Position, kinectDevice.DepthStream.Format);
point.X = (int)((point.X * containerSize.Width / kinectDevice.DepthStream.FrameWidth) - offset.X);
point.Y = (int)((point.Y * containerSize.Height / kinectDevice.DepthStream.FrameHeight) - offset.Y); return new Point(point.X, point.Y);
} private double GetJointAngle(Joint centerJoint, Joint angleJoint)
{
// 用余弦定理来求角度
Point primaryPoint = GetJointPoint(this.KinectDevice, centerJoint, this.LayoutRoot.RenderSize, new Point());
Point anglePoint = GetJointPoint(this.KinectDevice, angleJoint, this.LayoutRoot.RenderSize, new Point());
Point x = new Point(primaryPoint.X + anglePoint.X, primaryPoint.Y); double a;
double b;
double c; a = Math.Sqrt(Math.Pow(primaryPoint.X - anglePoint.X, ) + Math.Pow(primaryPoint.Y - anglePoint.Y, ));
b = anglePoint.X;
c = Math.Sqrt(Math.Pow(anglePoint.X - x.X, ) + Math.Pow(anglePoint.Y - x.Y, )); double angleRad = Math.Acos((a * a + b * b - c * c) / ( * a * b));
double angleDeg = angleRad * / Math.PI; if (primaryPoint.Y < anglePoint.Y)
{
angleDeg = - angleDeg;
} return angleDeg;
} private void PopulatePoseLibrary() // 游戏姿势库
{
this.poseLibrary = new Pose[]; //游戏开始 Pose - 伸开双臂 Arms Extended
// 肩,轴,髋
this.startPose = new Pose();
this.startPose.Title = "Start Pose";
this.startPose.Angles = new PoseAngle[];
this.startPose.Angles[] = new PoseAngle(JointType.ShoulderLeft, JointType.ElbowLeft, , ); // 角度,阈值
this.startPose.Angles[] = new PoseAngle(JointType.ElbowLeft, JointType.WristLeft, , );
this.startPose.Angles[] = new PoseAngle(JointType.ShoulderRight, JointType.ElbowRight, , );
this.startPose.Angles[] = new PoseAngle(JointType.ElbowRight, JointType.WristRight, , ); //Pose 1 -举起手来 Both Hands Up
// Wrist 部位比较固定,可以作为基准
this.poseLibrary[] = new Pose();
this.poseLibrary[].Title = "举起手来(Arms Up)";
this.poseLibrary[].Angles = new PoseAngle[];
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ShoulderLeft, JointType.ElbowLeft, , );
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ElbowLeft, JointType.WristLeft, , );
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ShoulderRight, JointType.ElbowRight, , );
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ElbowRight, JointType.WristRight, , ); //Pose 2 - 把手放下来 Both Hands Down
this.poseLibrary[] = new Pose();
this.poseLibrary[].Title = "把手放下来(Arms Down)";
this.poseLibrary[].Angles = new PoseAngle[];
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ShoulderLeft, JointType.ElbowLeft, , );
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ElbowLeft, JointType.WristLeft, , );
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ShoulderRight, JointType.ElbowRight, , );
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ElbowRight, JointType.WristRight, , ); //Pose 3 - 举起左手 Left Up and Right Down
this.poseLibrary[] = new Pose();
this.poseLibrary[].Title = "(举起左手)Left Up and Right Down";
this.poseLibrary[].Angles = new PoseAngle[];
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ShoulderLeft, JointType.ElbowLeft, , );
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ElbowLeft, JointType.WristLeft, , );
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ShoulderRight, JointType.ElbowRight, , );
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ElbowRight, JointType.WristRight, , ); //Pose 4 - 举起右手 Right Up and Left Down
this.poseLibrary[] = new Pose();
this.poseLibrary[].Title = "(举起右手)Right Up and Left Down";
this.poseLibrary[].Angles = new PoseAngle[];
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ShoulderLeft, JointType.ElbowLeft, , );
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ElbowLeft, JointType.WristLeft, , );
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ShoulderRight, JointType.ElbowRight, , );
this.poseLibrary[].Angles[] = new PoseAngle(JointType.ElbowRight, JointType.WristRight, , );
} private bool IsPose(Skeleton skeleton, Pose pose)
{
// 判断一个骨架中是否包含指定姿势 —— 注意传入的参数PoseAngle(JointType centerJoint, JointType angleJoint, double angle, double threshold)
bool isPose = true;
double angle;
double poseAngle;
double poseThreshold;
double loAngle;
double hiAngle; for (int i = ; i < pose.Angles.Length && isPose; i++)
{
poseAngle = pose.Angles[i].Angle;
poseThreshold = pose.Angles[i].Threshold;
angle = GetJointAngle(skeleton.Joints[pose.Angles[i].CenterJoint], skeleton.Joints[pose.Angles[i].AngleJoint]); hiAngle = poseAngle + poseThreshold;
loAngle = poseAngle - poseThreshold; if (hiAngle >= || loAngle < )
{
// 如果角度出现大于180度,需要调整
loAngle = (loAngle < ) ? + loAngle : loAngle;
hiAngle = hiAngle % ; isPose = !(loAngle > angle && angle > hiAngle);
}
else
{
isPose = (loAngle <= angle && hiAngle >= angle);
}
} return isPose;
} private void ProcessPlayerPerforming(Skeleton skeleton)
{
int instructionSeq = this.instructionSequence[this.instructionPosition]; if (IsPose(skeleton, this.poseLibrary[instructionSeq])) // 姿势是否符合要求
{
this.poseTimer.Stop();
this.instructionPosition++; if (this.instructionPosition >= this.instructionSequence.Length)
{
ChangePhase(GamePhase.SimonInstructing);
}
else
{
//TODO: Notify the user of correct pose
this.poseTimer.Start();
}
}
} private void ChangePhase(GamePhase newPhase)
{
if (newPhase != this.currentPhase)
{
this.currentPhase = newPhase;
this.poseTimer.Stop(); // 计时器重置 switch (this.currentPhase)
{
case GamePhase.GameOver:
this.currentLevel = ;
GameStateElement.Text = "GAME OVER!";
GameInstructionsElement.Text = "Place hands over the targets to start a new game.";
break; case GamePhase.SimonInstructing:
this.currentLevel++;
GameStateElement.Text = string.Format("Level {0}", this.currentLevel);
GameInstructionsElement.Text = "Watch for Simon's instructions";
GenerateInstructions(); // 产生一级的游戏
DisplayInstructions(); // 显示
break; case GamePhase.PlayerPerforming:
this.poseTimer.Start();
this.instructionPosition = ;
break;
}
}
} private void GenerateInstructions()
{
this.instructionSequence = new int[this.currentLevel]; for (int i = ; i < this.currentLevel; i++)
{
this.instructionSequence[i] = rnd.Next(, this.poseLibrary.Length - );
}
} private void DisplayInstructions()
{
GameInstructionsElement.Text = string.Empty;
StringBuilder text = new StringBuilder();
int instructionsSeq; for (int i = ; i < this.instructionSequence.Length; i++)
{
instructionsSeq = this.instructionSequence[i];
text.AppendFormat("{0}, ", this.poseLibrary[instructionsSeq].Title);
} GameInstructionsElement.Text = text.ToString();
ChangePhase(GamePhase.PlayerPerforming);
} private static Skeleton GetPrimarySkeleton(Skeleton[] skeletons)
{
Skeleton skeleton = null; if (skeletons != null)
{
//Find the closest skeleton
for (int i = ; i < skeletons.Length; i++)
{
if (skeletons[i].TrackingState == SkeletonTrackingState.Tracked)
{
if (skeleton == null)
{
skeleton = skeletons[i];
}
else
{
if (skeleton.Position.Z > skeletons[i].Position.Z)
{
skeleton = skeletons[i];
}
}
}
}
} return skeleton;
}
#endregion Methods #region Properties
public KinectSensor KinectDevice
{
get { return this.kinectDevice; }
set
{
if (this.kinectDevice != value)
{
//Uninitialize
if (this.kinectDevice != null)
{
this.kinectDevice.Stop();
this.kinectDevice.SkeletonFrameReady -= KinectDevice_SkeletonFrameReady;
this.kinectDevice.SkeletonStream.Disable();
SkeletonViewerElement.KinectDevice = null;
this.frameSkeletons = null;
} this.kinectDevice = value; //Initialize
if (this.kinectDevice != null)
{
if (this.kinectDevice.Status == KinectStatus.Connected)
{
this.kinectDevice.SkeletonStream.Enable();
this.frameSkeletons = new Skeleton[this.kinectDevice.SkeletonStream.FrameSkeletonArrayLength];
this.kinectDevice.Start(); SkeletonViewerElement.KinectDevice = this.KinectDevice;
this.KinectDevice.SkeletonFrameReady += KinectDevice_SkeletonFrameReady;
}
}
}
}
}
#endregion Properties
}
}

Kinect 开发 —— 姿势识别的更多相关文章

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

    Kinect开发学习笔记之(一)Kinect介绍和应用 zouxy09@qq.com http://blog.csdn.net/zouxy09 一.Kinect简单介绍 Kinectfor Xbox ...

  2. Kinect开发笔记之二Kinect for Windows 2.0新功能

    这是本博客翻译文档的第一篇文章.笔者已经苦逼的竭尽全力的在翻译了.但无奈英语水平也是非常有限.不正确或者不妥当不准确的地方必定会有,还恳请大家留言或者邮件我以批评指正.我会虚心接受. 谢谢大家.   ...

  3. Kinect 开发 —— Kinect Interaction 交互控件

    Kinect Interactions 提供了一些新的带有姿势识别的控件如 push-to-press 按钮, grip-to-pan 列表控件, 而且支持多用户,同时二个人进行的交互,这些新添加的控 ...

  4. Kinect 开发 —— 控制PPT播放

    实现Kinect控制幻灯片播放很简单,主要思路是:使用Kinect捕捉人体动作,然后根据识别出来的动作向系统发出点击向前,向后按键的事件,从而使得幻灯片能够切换. 这里的核心功能在于手势的识别,我们在 ...

  5. Kinect 开发 —— 常见手势识别(下)

    划动(Swipe) 划动手势和挥手(wave)手势类似.识别划动手势需要不断的跟踪用户手部运动,并保持当前手的位置之前的手的位置.因为手势有一个速度阈值,我们需要追踪手运动的时间以及在三维空间中的坐标 ...

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

    像点击(clicks)是GUI平台的核心,轻点(taps)是触摸平台的核心那样,手势(gestures)是Kinect应用程序的核心 关于手势的定义的中心在于手势能够用来交流,手势的意义在于讲述而不是 ...

  7. Kinect 开发 —— 杂一

    Kinect 提供了非托管(C++)和托管(.NET)两种开发方式的SDK,如果您用C++开发的话,需要安装Speech Runtime(V11),Kinect for Windows Runtime ...

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

    悬浮按钮 (Hover Button) 悬浮按钮通过将鼠标点击换成悬浮然后等待(hover-and-wait)动作,解决了不小心点击的问题.当光标位于按钮之上时,意味着用户通过将光标悬浮在按钮上一段时 ...

  9. Kinect 开发 —— 硬件设备解剖

    Kinect for Xbox: 360 不支持“近景模式” 三只眼睛 —— 红外投影机,RGB摄像头,红外深度投影头  —— 色彩影像中的每个像素分别与深度影像中的一个像素对应 四只耳朵 —— L形 ...

随机推荐

  1. Goldengate进程的合并与拆分规范

    Goldengate抽取进程的合并与拆分原则 1.    文档综述 1.1.  文档说明 本文档描述了对GoldenGate的抽取进程进行拆分和合并的基本原则和详细步骤.  1.2.  读者范围 本文 ...

  2. 关于ios11和Chrome浏览器非HTTPS 对高德定位不支持问题

    最近,在开发二维码签到系统时,需要对用户的签到的经纬度进行获取,并且判断签到距离是否在可支持范围内. 使用了高德地图.因为本人最近刚刚入手了一款新的Iphone7里面是最新的ios11系统.发现我的手 ...

  3. Git 内部原理 - (3) Git 引用 (4)包文件

    Git 引用 我们可以借助类似于 git log 1a410e 这样的命令来浏览完整的提交历史,但为了能遍历那段历史从而找到所有相关对象,你仍须记住 1a410e 是最后一个提交. 我们需要一个文件来 ...

  4. Windows下使用VS的ADO访问MySQL

    数据库的访问的一种方式就是:CS结构.即使用TCP/UDP协议进行远程访问,而数据库对于服务端的软件是本地访问!这种管理方式比较常见. 这里主要叙述Windows访问本地数据库的方法. 需要了解几个概 ...

  5. gcd步数

    题目描述 一个有趣的函数F(a,b),表示对于数对(a,b)调用辗转相除法的步数为多少 例如 (24,40)....0 (16,24).....1 (8,16).....2 (0,8)....3,即f ...

  6. free---显示内存

    free命令可以显示当前系统未使用的和已使用的内存数目,还可以显示被内核使用的内存缓冲区. 语法 free(选项) 选项 -b:以Byte为单位显示内存使用情况: -k:以KB为单位显示内存使用情况: ...

  7. git error: object file .git/objects/b9/e269f50db2a3415cc8ad5ba40b82b9b6a13d45 is empty

    错误现象: 解决方法: 1.  find .git/objects/ -type f -empty | xargs rm 2. git fetch -p 3. git fsck --full

  8. 伸缩--也可用于tabs

    var $ranklist_li = $("div.ranklist_model ul li"); $ranklist_li.hover(function () { $(this) ...

  9. 安卓ContentObserver模式获取短信用正则自己主动填充验证码

    近期做注冊的时候看到非常多app在手机接受到短信的时候直接填写验证码到界面省略用户自己主动输入,感觉这样确实蛮人性化的呵呵.于是自己也做了一个 步骤: 首先我使用了ContentObserver监听短 ...

  10. Using a Plugin

    创建hello.world <?xml version="1.0"?> <sdf version="1.4"> <world na ...