Kinect for Windows SDK开发入门七骨骼追踪基础 下Word文档格式.docx
《Kinect for Windows SDK开发入门七骨骼追踪基础 下Word文档格式.docx》由会员分享,可在线阅读,更多相关《Kinect for Windows SDK开发入门七骨骼追踪基础 下Word文档格式.docx(26页珍藏版)》请在冰点文库上搜索。
Polyline对象用来表示点与点之间的连线。
当用户在点和点之间移动手时,程序将点添加到Polyline对象中。
PuzzleBoardElementCanvas对象用来作为UI界面上所有点的容器。
Grid对象下面的Canvas的顺序是有意这样排列的,我们使用另外一个GameBoardElementCanvas对象来存储手势,以Image来表示,并且能够保证这一层总是在点图层之上。
将每一类对象放在各自层中的另外一个好处是重新开始一个新的游戏变得很容易,只需要将PuzzleBoardElement节点下的所有子节点清除,CrayonElement元素和其他的UI对象不会受到影响。
Viewbox和Grid对象对于UI界面很重要。
如上一篇文章中讨论的,骨骼节点数据是基于骨骼空间的。
这意味着我们要将骨骼向量转化到UI坐标系中来才能进行绘制。
我们将UI控件硬编码,不允许它随着UI窗体的变化而浮动。
Grid节点将UI空间大小定义为1920*1200。
通常这个是显示器的全屏尺寸,而且他和深度影像数据的长宽比是一致的。
这能够使得坐标转换更加清楚而且能够有更加流畅的手势移动体验。
<
Windowx:
Class="
KinectDrawDotsGame.MainWindow"
xmlns="
xmlns:
x="
Title="
MainWindow"
Height="
600"
Width="
800"
Background="
White"
>
<
Viewbox>
Gridx:
Name="
LayoutRoot"
1920"
1200"
Polylinex:
CrayonElement"
Stroke="
Black"
StrokeThickness="
3"
/>
Canvasx:
PuzzleBoardElement"
GameBoardElement"
Imagex:
HandCursorElement"
Source="
Images/hand.png"
75"
RenderTransformOrigin="
0.5,0.5"
Image.RenderTransform>
TransformGroup>
ScaleTransformx:
HandCursorScale"
ScaleX="
1"
/TransformGroup>
/Image.RenderTransform>
/Image>
/Canvas>
/Grid>
/Viewbox>
/Window>
硬编码UI界面也能够简化开发过程,能够使得从骨骼坐标向UI坐标的转化更加简单和快速,只需要几行代码就能完成操作。
况且,如果不应编码,相应主UI窗体大小的改变将会增加额外的工作量。
通过将Grid嵌入Viewbox节点来让WPF来帮我们做缩放操作。
最后一个UI元素是Image对象,他表示手的位置。
在这个小游戏中,我们使用这么一个简单的图标代表手。
你可以选择其他的图片或者直接用一个Ellipse对象来代替。
本游戏中图片使用的是右手。
在游戏中,用户可以选择使用左手或者右手,如果用户使用左手,我们将该图片使用ScaleTransform变换,使得变得看起来像右手。
1.2手部追踪
游戏者使用手进行交互,因此准确判断是那只手以及手的位置对于基于Kinect开发的应用程序显得至关重要。
手的位置及动作是手势识别的基础。
追踪手的运动是从Kinect获取数据的最重要用途。
在这个应用中,我们将忽视其他关节点信息。
小时候,我们做这中连线时一般会用铅笔或者颜料笔,然后用手控制铅笔或则颜料笔进行连线。
我们的这个小游戏颠覆了这种方式,我们的交互非常自然,就是手。
这样有比较好的沉浸感,使得游戏更加有趣。
当然,开发基于Kinect的应用程序这种交互显得自然显得至关重要。
幸运的是,我们只需要一点代码就能实现这一点。
在应用程序中可能有多个游戏者,我们设定,不论那只手离Kinect最近,我们使用距离Kinect最近的那个游戏者的那只手作为控制程序绘图的手。
当然,在游戏中,任何时候用户可以选择使用左手还是右手,这会使得用户操作起来比较舒服,SkeletonFrameReady代码如下:
privatevoidKinectDevice_SkeletonFrameReady(objectsender,SkeletonFrameReadyEventArgse)
{
using(SkeletonFrameframe=e.OpenSkeletonFrame())
{
if(frame!
=null)
frame.CopySkeletonDataTo(this.frameSkeletons);
Skeletonskeleton=GetPrimarySkeleton(this.frameSkeletons);
Skeleton[]dataSet2=newSkeleton[this.frameSkeletons.Length];
frame.CopySkeletonDataTo(dataSet2);
if(skeleton==null)
HandCursorElement.Visibility=Visibility.Collapsed;
}
else
JointprimaryHand=GetPrimaryHand(skeleton);
TrackHand(primaryHand);
TrackPuzzle(primaryHand.Position);
}
privatestaticSkeletonGetPrimarySkeleton(Skeleton[]skeletons)
Skeletonskeleton=null;
if(skeletons!
//查找最近的游戏者
for(inti=0;
i<
skeletons.Length;
i++)
if(skeletons[i].TrackingState==SkeletonTrackingState.Tracked)
skeleton=skeletons[i];
if(skeleton.Position.Z>
skeletons[i].Position.Z)
returnskeleton;
每一次事件执行时,我们查找第一个合适的游戏者。
程序不会锁定某一个游戏者。
如果有两个游戏者,那么靠Kinect最近的那个会是活动的游戏者。
这就是GetPrimarySkeleton的功能。
如果没有活动的游戏者,手势图标就隐藏。
否则,我们使用活动游戏者离Kinect最近的那只手作为控制。
查找控制游戏手的代码如下:
privatestaticJointGetPrimaryHand(Skeletonskeleton)
JointprimaryHand=newJoint();
if(skeleton!
primaryHand=skeleton.Joints[JointType.HandLeft];
JointrighHand=skeleton.Joints[JointType.HandRight];
if(righHand.TrackingState!
=JointTrackingState.NotTracked)
if(primaryHand.TrackingState==JointTrackingState.NotTracked)
primaryHand=righHand;
if(primaryHand.Position.Z>
righHand.Position.Z)
returnprimaryHand;
优先选择的是距离Kinect最近的那只手。
但是,代码不单单是比较左右手的Z值来判断选择Z值小的那只手,如前篇文章讨论的,Z值为0表示该点的深度信息不能确定。
所以,我们在进行比较之前需要进行验证,检查每一个节点的TrackingState状态。
左手是默认的活动手,除非游戏者是左撇子。
右手必须显示的追踪,或者被计算认为离Kinect更近。
在操作关节点数据时,一定要检查TrackingState的状态,否则会得到一些异常的位置信息,这样会导致UI绘制错误或者是程序异常。
知道了哪只手是活动手后,下一步就是在界面上更新手势图标的位置了。
如果手没有被追踪,隐藏图标。
在一些比较专业的应用中,隐藏手势图标可以做成一个动画效果,比如淡入或者放大然后消失。
在这个小游戏中只是简单的将其状态设置为不可见。
在追踪手部操作时,确保手势图标可见,并且设定在UI上的X,Y位置,然后根据是左手还是右手确定UI界面上要显示的手势图标,然后更新。
计算并确定手在UI界面上的位置可能需要进一步检验,这部分代码和上一篇文章中绘制骨骼信息类似。
后面将会介绍空间坐标转换,现在只需要了解的是,获取的手势值是在骨骼控件坐标系中,我们需要将手在骨骼控件坐标系统中的位置转换到对于的UI坐标系统中去。
privatevoidTrackHand(Jointhand)
if(hand.TrackingState==JointTrackingState.NotTracked)
HandCursorElement.Visibility=Visibility.Visible;
DepthImagePointpoint=this.kinectDevice.MapSkeletonPointToDepth(hand.Position,this.kinectDevice.DepthStream.Format);
point.X=(int)((point.X*LayoutRoot.ActualWidth/kinectDevice.DepthStream.FrameWidth)-(HandCursorElement.ActualWidth/2.0));
point.Y=(int)((point.Y*LayoutRoot.ActualHeight/kinectDevice.DepthStream.FrameHeight)-(HandCursorElement.ActualHeight/2.0));
Canvas.SetLeft(HandCursorElement,point.X);
Canvas.SetTop(HandCursorElement,point.Y);
if(hand.JointType==JointType.HandRight)
HandCursorScale.ScaleX=1;
HandCursorScale.ScaleX=-1;
编译运行程序,当移动手时,手势图标会跟着移动。
1.3绘制游戏界面逻辑
为了显示绘制游戏的逻辑,我们创建一个新的类DotPuzzle。
这个类的最主要功能是保存一些数字,数字在集合中的位置决定了在数据系列中的前后位置。
这个类允许序列化,我们能够从xml文件中读取关卡信息来建立新的关卡。
publicclassDotPuzzle
publicList<
Point>
Dots{get;
set;
publicDotPuzzle()
this.Dots=newList<
();
定义好结构之后,就可以开始将这些点绘制在UI上了。
首先创建一个DotPuzzle类的实例,然后定义一些点,puzzleDotIndex用来追踪用户解题的进度,我们将puzzleDotIndex设置为-1表示用户还没有开始整个游戏,代码如下:
publicMainWindow()
InitializeComponent();
puzzle=newDotPuzzle();
this.puzzle.Dots.Add(newPoint(200,300));
this.puzzle.Dots.Add(newPoint(1600,300));
this.puzzle.Dots.Add(newPoint(1650,400));
this.puzzle.Dots.Add(newPoint(1600,500));
this.puzzle.Dots.Add(newPoint(1000,500));
this.puzzle.Dots.Add(newPoint(1000,600));
this.puzzle.Dots.Add(newPoint(1200,700));
this.puzzle.Dots.Add(newPoint(1150,800));
this.puzzle.Dots.Add(newPoint(750,800));
this.puzzle.Dots.Add(newPoint(700,700));
this.puzzle.Dots.Add(newPoint(900,600));
this.puzzle.Dots.Add(newPoint(900,500));
this.puzzle.Dots.Add(newPoint(200,500));
this.puzzle.Dots.Add(newPoint(150,400));
this.puzzleDotIndex=-1;
this.Loaded+=(s,e)=>
KinectSensor.KinectSensors.StatusChanged+=KinectSensors_StatusChanged;
this.KinectDevice=KinectSensor.KinectSensors.FirstOrDefault(x=>
x.Status==KinectStatus.Connected);
DrawPuzzle(this.puzzle);
};
最后一步是在UI界面上绘制点信息。
我们创建了一个名为DrawPuzzle的方法,在主窗体加载完成的时候触发改事件。
DrawPuzzle遍历集合中的每一个点,然后创建UI元素表示这个点,然后将这个点添加到PuzzleBoardElement节点下面。
另一种方法是使用XAML创建UI界面,将DotPuzzle对象作为ItemControl的ItemSource属性,ItemsControl对象的ItemTemplate对象能够定义每一个点的外观和位置。
这种方式更加优雅,他允许定义界面的风格及主体。
在这个例子中,我们把精力集中在Kinect代码方面而不是WPF方面,尽量减少代码量来实现功能。
如果有兴趣的话,可以尝试改为ItemControl这种形式。
DrawPuzzle代码如下:
privatevoidDrawPuzzle(DotPuzzlepuzzle)
PuzzleBoardElement.Children.Clear();
if(puzzle!
puzzle.Dots.Count;
GriddotContainer=newGrid();
dotContainer.Width=50;
dotContainer.Height=50;
dotContainer.Children.Add(newEllipse{Fill=Brushes.Gray});
TextBlockdotLabel=newTextBlock();
dotLabel.Text=(i+1).ToString();
dotLabel.Foreground=Brushes.White;
dotLabel.FontSize=24;
dotLabel.HorizontalAlignment=HorizontalAlignment.Center;
dotLabel.VerticalAlignment=VerticalAlignment.Center;
dotContainer.Children.Add(dotLabel);
//在UI界面上绘制点
Canvas.SetTop(dotContainer,puzzle.Dots[i].Y-(dotContainer.Height/2));
Canvas.SetLeft(dotContainer,puzzle.Dots[i].X-(dotContainer.Width/2));
PuzzleBoardElement.Children.Add(dotContainer);
1.4游戏逻辑实现
到目前为止,我们的游戏已经有了用户界面和基本的数据。
移动手,能够看到手势图标会跟着移动。
我们要将线画出来。
当游戏者的手移动到点上时,开始绘制直线的起点,然后知道手朋到下一个点时,将这点作为直线的终点,并开始另一条直线,并以该点作为起点。
TrackPuzzle代码如下:
privatevoidTrackPuzzle(SkeletonPointposition)
if(this.puzzleDotIndex==this.puzzle.Dots.Count)
//游戏结束
Pointdot;
if(this.puzzleDotIndex+1<
this.puzzle.Dots.Count)
dot=this.puzzle.Dots[this.puzzleDotIndex+1];
dot=this.puzzle.Dots[0];
DepthImagePointpoint=this.kinectDevice.MapSkeletonPointToDepth(position,kinectDevice.DepthStream.Format);
point.X=(int)(point.X*LayoutRoot.ActualWidth/kinectDevice.DepthStream.FrameWidth);
point.Y=(int)(point.Y*LayoutRoot.ActualHeight/kinectDevice.DepthStream.FrameHeight);
PointhandPoint=newPoint(point.X,point.Y);
PointdotDiff=newPoint(dot.X-handPoint.X,dot.Y-handPoint.Y);
doublelength=Math.Sqrt(dotDiff.X*dotDiff.X+dotDiff.Y*dotDiff.Y);
intlastPoint=this.CrayonElement.Points.Count-1;
//手势离点足够近
if(length<
25)
if(lastPoint>
0)
//移去最后一个点
this.CrayonElement.Points.RemoveAt(lastPoint);
//设置直线的终点
this.CrayonEleme