Kinect for Windows SDK开发入门七骨骼追踪基础 下.docx
- 文档编号:689578
- 上传时间:2023-04-29
- 格式:DOCX
- 页数:26
- 大小:218.08KB
Kinect for Windows SDK开发入门七骨骼追踪基础 下.docx
《Kinect for Windows SDK开发入门七骨骼追踪基础 下.docx》由会员分享,可在线阅读,更多相关《Kinect for Windows SDK开发入门七骨骼追踪基础 下.docx(26页珍藏版)》请在冰点文库上搜索。
KinectforWindowsSDK开发入门七骨骼追踪基础下
[译]KinectforWindowsSDK开发入门(七):
骨骼追踪基础下
上一篇文章用在UI界面上绘制骨骼数据的例子展示了骨骼追踪系统涉及的主要对象,然后详细讨论了骨骼追踪中所涉及的对象模型。
但是了解了基本概念是一回事,能够建立一个完整的可用的应用程序又是另外一回事,本文通过介绍一个简单的Kinect游戏来详细讨论如何应用这些对象来建立一个完整的Kinect应用,以加深对Kinect骨骼追踪所涉及的各个对象的了解。
1.Kinect连线游戏
相信大家在小时候都做过一个数学题目,就是在纸上将一些列数字(用一个圆点表示)从小到大用线连起来。
游戏逻辑很简单,只不过我们在这里要实现的是动动手将这些点连起来,而不是用笔或者鼠标。
这个小游戏显然没有第一人称射击游戏那样复杂,但如果能够做成那样更好。
我们要使用骨骼追踪引擎来收集游戏者的关节数据,执行操作并渲染UI界面。
这个小游戏展示了自然用户界面(NaturalUserInterface,NUI)的理念,这正是基于Kinect开发的常见交互界面,就是手部跟踪。
这个连线小游戏没有仅仅用到了WPF的绘图功能,没有好看的图片和动画效果,这些以后可以逐步添加。
在开始写代码之前,需要明确定义我们的游戏目标。
连线游戏是一个智力游戏,游戏者需要将数字从小到大连起来。
程序可以自定义游戏上面的数字和位置(合称一个关卡)。
每一个关卡包括一些列的数字(以点表示)及其位置。
我们要创建一个DotPuzzle类来管理这些点对象的集合。
可能一开始不需要这个类,仅仅需要一个集合就可以,但是为了以后方便添加其他功能,使用类更好一点。
这些点在程序中有两个地方需要用到,一个是最开始的时候在界面上绘制关卡,其次是判断用户是否碰到了这些点。
当用户碰到点时,程序开始绘制,直线以碰到的点为起始点,直线的终点位用户碰到的下一个点。
然后下一个点又作为另一条直线的起点,依次类推。
直到最后一个点和第一个点连起来,这样关卡算是通过了,游戏结束。
游戏规则定义好了之后,我们就可以开始编码了,随着这个小游戏的开发进度,可能会添加一些其他的新功能。
一开始,建一个WPF工程,然后引用Microsoft.Kinect.dll,和之前的项目一样,添加发现和初始化Kinect传感器的代码。
然后注册KinectSensor对象的SkeletonFrameReady事件。
1.1游戏的用户界面
游戏界面代码如下,有几个地方需要说明一下。
Polyline对象用来表示点与点之间的连线。
当用户在点和点之间移动手时,程序将点添加到Polyline对象中。
PuzzleBoardElementCanvas对象用来作为UI界面上所有点的容器。
Grid对象下面的Canvas的顺序是有意这样排列的,我们使用另外一个GameBoardElementCanvas对象来存储手势,以Image来表示,并且能够保证这一层总是在点图层之上。
将每一类对象放在各自层中的另外一个好处是重新开始一个新的游戏变得很容易,只需要将PuzzleBoardElement节点下的所有子节点清除,CrayonElement元素和其他的UI对象不会受到影响。
Viewbox和Grid对象对于UI界面很重要。
如上一篇文章中讨论的,骨骼节点数据是基于骨骼空间的。
这意味着我们要将骨骼向量转化到UI坐标系中来才能进行绘制。
我们将UI控件硬编码,不允许它随着UI窗体的变化而浮动。
Grid节点将UI空间大小定义为1920*1200。
通常这个是显示器的全屏尺寸,而且他和深度影像数据的长宽比是一致的。
这能够使得坐标转换更加清楚而且能够有更加流畅的手势移动体验。
Class="KinectDrawDotsGame.MainWindow" xmlns=" xmlns: x=" Title="MainWindow"Height="600"Width="800"Background="White"> Name="LayoutRoot"Width="1920"Height="1200"> Name="CrayonElement"Stroke="Black"StrokeThickness="3"/> Name="PuzzleBoardElement"/> Name="GameBoardElement"> Name="HandCursorElement"Source="Images/hand.png"Width="75" Height="75"RenderTransformOrigin="0.5,0.5"> Name="HandCursorScale"ScaleX="1"/> 硬编码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! =null) { //查找最近的游戏者 for(inti=0;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]; } } } } } returnskeleton; } 每一次事件执行时,我们查找第一个合适的游戏者。 程序不会锁定某一个游戏者。 如果有两个游戏者,那么靠Kinect最近的那个会是活动的游戏者。 这就是GetPrimarySkeleton的功能。 如果没有活动的游戏者,手势图标就隐藏。 否则,我们使用活动游戏者离Kinect最近的那只手作为控制。 查找控制游戏手的代码如下: privatestaticJointGetPrimaryHand(Skeletonskeleton) { JointprimaryHand=newJoint(); if(skeleton! =null) { primaryHand=skeleton.Joints[JointType.HandLeft]; JointrighHand=skeleton.Joints[JointType.HandRight]; if(righHand.TrackingState! =JointTrackingState.NotTracked) { if(primaryHand.TrackingState==JointTrackingState.NotTracked) { primaryHand=righHand; } else { if(primaryHand.Position.Z>righHand.Position.Z) { primaryHand=righHand; } } } } 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.Collapsed; } else { 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; } else { HandCursorScale.ScaleX=-1; } } } 编译运行程序,当移动手时,手势图标会跟着移动。 1.3绘制游戏界面逻辑 为了显示绘制游戏的逻辑,我们创建一个新的类DotPuzzle。 这个类的最主要功能是保存一些数字,数字在集合中的位置决定了在数据系列中的前后位置。 这个类允许序列化,我们能够从xml文件中读取关卡信息来建立新的关卡。 publicclassDotPuzzle { publicList 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! =null) { for(inti=0;i { 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) { //游戏结束 } else { Pointdot; if(this.puzzleDotIndex+1 { dot=this.puzzle.Dots[this.puzzleDotIndex+1]; } else { 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
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Kinect for Windows SDK开发入门七骨骼追踪基础 SDK 开发 入门 骨骼 追踪 基础
![提示](https://static.bingdoc.com/images/bang_tan.gif)