实现Kinect控制幻灯片播放很简单,主要思路是:使用Kinect捕捉人体动作,然后根据识别出来的动作向系统发出点击向前,向后按键的事件,从而使得幻灯片能够切换。 这里的核心功能在于手势的识别,我们在开发之前需要定义怎么样的手势算是向前或者向后切换幻灯片。手势和姿势识别在我的Kinect开发入门第九,第十,十一篇文章有详细介绍。本文仅讨论主要思路及关键代码部分。
通过姿势实现PPT控制
姿势(pose)识别是通过关节点与关节点之间的相对位置关系来进行判断,相对来说比较容易,只需要通过某一帧骨骼关节点数据即可进行判断。而手势(gesture)识别则是通过对连续的一段时间内的动作来进行判断,比较复杂。但是两者对于我们需要实现特定的目的来说并没有优劣之分,就像常用的算法那样,并不是越复杂越好,有些方法就非常简单高效。
在控制ppt播放命令中,我们设定,如果右手关节点在x轴上的距离比头部关节点大于0.45的话,认为用户试图进行点击键盘上的right按钮。如果头部关节点位置在x轴方向是比左手关节点在x轴上的位置大于0.45的话,认为用户试图点击键盘上的left按钮。0.45这个值是通过反复试验的出来的,这种通过试验的方法在Kinect开发中比较常见。关键代码如下:
private void ProcessForwardBackGesture(Joint head, Joint rightHand, Joint leftHand) { if (rightHand.Position.X > head.Position.X + 0.45) { if (!isBackGestureActive && !isForwardGestureActive) { isForwardGestureActive = true; System.Windows.Forms.SendKeys.SendWait("{Right}"); } } else { isForwardGestureActive = false; } if (leftHand.Position.X < head.Position.X - 0.45) { if (!isBackGestureActive && !isForwardGestureActive) { isBackGestureActive = true; System.Windows.Forms.SendKeys.SendWait("{Left}"); } } else { isBackGestureActive = false; } }
上面的代码中,当判断到用户向右挥手动作是,执行System.Windows.Forms.SendKeys.SendWait("{Right}")语句从而发出点击键盘向右按键; 该方法执行时,要求PowerPoint程序处于当前活动的状态,这样里面的PPT才会向右键盘点击事件。需要注意的是方法中isBackGestureActive和isForwardGestureActive这两个布尔型的标志位,可以防止当用户一直处于某一个动作时会一直发送System.Windows.Forms.SendKeys.SendWait("{xx}")。
上面的方法可以放在sensor_SkeletonFrameReady事件中,首先获取头部,左手右手关节点数据,然后调用该方法。
void sensor_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) { using (var skeletonFrame = e.OpenSkeletonFrame()) { if (skeletonFrame == null) return; if (skeletons == null || skeletons.Length != skeletonFrame.SkeletonArrayLength) { skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength]; } skeletonFrame.CopySkeletonDataTo(skeletons); Skeleton closestSkeleton = (from s in skeletons where s.TrackingState == SkeletonTrackingState.Tracked && s.Joints[JointType.Head].TrackingState == JointTrackingState.Tracked select s).OrderBy(s => s.Joints[JointType.Head].Position.Z) .FirstOrDefault(); if (closestSkeleton == null) return; var head = closestSkeleton.Joints[JointType.Head]; var rightHand = closestSkeleton.Joints[JointType.HandRight]; var leftHand = closestSkeleton.Joints[JointType.HandLeft]; if (head.TrackingState != JointTrackingState.Tracked || rightHand.TrackingState != JointTrackingState.Tracked || leftHand.TrackingState != JointTrackingState.Tracked) { //Don't have a good read on the joints so we cannot process gestures return; } ProcessForwardBackGesture(head, rightHand, leftHand); } }
通过姿势识别来进行幻灯片控制简单高效,但是也存在着两个主要问题:
首先是,如果幻灯片中嵌套有视频,flash或者其他多媒体要素的话,可能不能很好的控制这些要素的播放和暂停,一种处理办法是使用动画,使得用户在点击键盘显示多媒体的时候就开始播放。还有一种方法是使用VSTO编写针对PowerPoint的插件,来监听鼠标来控制多媒体播放。
其次是误操作问题,这个问题是使用基于姿势识别存在的最大问题。有时候可能出于肢体语言表达需要,可能需要张开双臂,或者弯下腰来捡东西,这样会使得头部关节点位置和手部关节点位置的相对关系可能会满足之前我们设定的距离,从而产生误操作。
使用姿势识别的第一个问题是一个普遍存在的问题,即使使用ppt控制器也存在该问题,ppt控制器似乎也是通过发送键盘点击事件来进行幻灯片控制的。第二个问题可以使用手势识别的方式来在一定程度上避免。
Skeleton closestSkeleton = (from s in skeletons where s.TrackingState == SkeletonTrackingState.Tracked && s.Joints[JointType.Head].TrackingState == JointTrackingState.Tracked select s).OrderBy(s => s.Joints[JointType.Head].Position.Z) .FirstOrDefault();
手势识别控制
手势识别通过判断在一定时间内一系列连续的动作之间的前后相关关系来进行动作的识别,在第十篇 文章中对swip这一动作如何识别有详细介绍。在Kinect for Windows Developer ToolKit v 1.5中,增加了一个名为Slideshow Gesture-WPF的例子。
要使用该dll提供的手势识别,必须先创建一个Recognizer对象。然后初始化改Recognizer。在初始化的时候注册左右挥动识别后进行的操作,该操作可以通过方法提供,由于方法体比较小,这里使用lambda表达式。
private readonly Recognizer activeRecognizer; this.activeRecognizer = this.CreateRecognizer(); private Recognizer CreateRecognizer() { // Instantiate a recognizer. var recognizer = new Recognizer(); // swipe right to press right key . recognizer.SwipeRightDetected += (s, e) => { System.Windows.Forms.SendKeys.SendWait("{Right}"); }; // swipe left to press left key .. recognizer.SwipeLeftDetected += (s, e) => { System.Windows.Forms.SendKeys.SendWait("{Left}"); }; return recognizer; }
然后在sensor_SkeletonFrameReady事件中调用即可
private Skeleton[] skeletons = new Skeleton[0]; private void sensor_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) { // Get the frame. using (var frame = e.OpenSkeletonFrame()) { // Ensure we have a frame. if (frame != null) { // Resize the skeletons array if a new size (normally only on first call). if (this.skeletons.Length != frame.SkeletonArrayLength) { this.skeletons = new Skeleton[frame.SkeletonArrayLength]; } // Get the skeletons. frame.CopySkeletonDataTo(this.skeletons); // Pass skeletons to recognizer. this.activeRecognizer.Recognize(sender, frame, this.skeletons); } } }
把上面的代码放到程序中,运行即可看到使用左手向左挥手,使用右手向右挥手即可控制幻灯片向左向右切换。如前面的文章所述,使用手势识别动作有一个时间阈值和一定的规则,如果该动作在某一时间内没有完成则识别失败。所以在本例中,如果挥手动作过慢,可能导致识别不出。而不像之前使用姿势识别例子中的那样,只需保持某个动作即可完成识别操作,这在一定程度上减少了第一种情况下出现误识别的概率。