前言:
之前写过一篇关于姿势(pose)识别的博文,之后又定义了几个姿势实现了体感俄罗斯方块。姿势识别是通过关节点与关节点之间的相对位置关系来进行判断。当判断成立,即执行指定的指令。那么,我们就可以指定姿势实现我们的天花乱坠的想法啦~~~ PPT播放助手应运而生。
基本思路:
有了Kinect,我们就可以使用手势(手部姿势)来控制幻灯片的播放,而不需要一边演讲一遍按键盘或者手持一个ppt控制器,我们只需要做的就是双手握住头部就实现放映,轻轻的向左或者向右挥一下手就可以控制幻灯片向前或者向后翻一页,双手举起就使屏幕变黑。酷吧。虽然可能在演讲的时候做这个动作可能有点奇怪,但是这也是一种控制幻灯片放映的好方法。
实现Kinect控制幻灯片播放很简单,主要思路是:使用Kinect捕捉人体动作,然后根据识别出来的动作向系统发出点击向前,向后,"F5","B"按键的事件,从而使得幻灯片能够切换,放映,变黑。 这里的核心功能在于手势的识别,我们在开发之前需要定义怎么样的手势算是向前或者向后切换幻灯片。
对应关系:
姿势 | 对应按键 | 触发动作 |
双手抱住头部 | F5 | 放映 |
向左挥动左手 | left | 上一页 |
向右挥动右手 | right | 下一页 |
双手举起 | B | 变黑 |
代码实现
由于之前几篇博文已经写了获取Kinect各种数据的代码,在此就讨论关键部分的代码。
由于是姿势控制,自然存在精确度的问题,所以我们需要定义几个阈值,作为判断依据。
/// <summary> /// 手臂水平伸展的阈值 /// </summary> private const double ArmStretchedThreshold = 0.45; /// <summary> /// 手臂垂直上举的阈值 /// </summary> private const double ArmRaisedThreshold = 0.20; /// <summary> /// 头离双手距离的阈值 /// </summary> private const double DistanceThreshold = 0.05;
在控制ppt播放命令中,我们设定,如果双手距离头部小于0.05,认为用户试图进行点击键盘上的F5键,如果右手关节点在x轴上的距离比头部关节点大于0.45的话,认为用户试图进行点击键盘上的right按钮。如果头部关节点位置在x轴方向是比左手关节点在x轴上的位置大于0.45的话,认为用户试图点击键盘上的left按钮。如果双手超过头部高度0.20的话,认为用户试图点击键盘上的B键,0.05、0.45和0.20这几个阈值是通过反复测试的出来的(没错,是Test,而不是Dubug);所以,测试很重要。
关键代码如下:
/// <summary> /// 处理手势的方法 /// </summary> /// <param name="head"></param> /// <param name="rightHand"></param> /// <param name="leftHand"></param> private void ProcessForwardBackGesture(Joint head, Joint rightHand, Joint leftHand) { //若右手位置的横坐标值超过设定的阈值,除法PPT下一页命令 if (rightHand.Position.X > head.Position.X + ArmStretchedThreshold) { if (!isForwardGestureActive) { //激活forward命令,确保每次操作执行一次命令 isForwardGestureActive = true; //模拟鼠标按下“右”方向键 System.Windows.Forms.SendKeys.SendWait("{Right}"); } } else { isForwardGestureActive = false; } //若左手位置的横坐标超过设定的阈值,触发PPT上一页命令 if (leftHand.Position.X < head.Position.X - ArmStretchedThreshold) { if (!isBackGestureActive) { //激活back命令,确保每次操作执行一次命令 isBackGestureActive = true; //模拟鼠标按下“左”方向键 System.Windows.Forms.SendKeys.SendWait("{Left}"); } } else { isBackGestureActive = false; } //双手同时上举,在控制PPT时让屏幕变黑 if ((leftHand.Position.Y > head.Position.Y - ArmRaisedThreshold) && (rightHand.Position.Y > head.Position.Y - ArmRaisedThreshold)) { if (!isBlackScreenActive) { isBlackScreenActive = true; System.Windows.Forms.SendKeys.SendWait("{B}"); } } else { isBlackScreenActive = false; } //判断双手靠近头部,触发PPT放映 if (Math.Abs(head.Position.Y - rightHand.Position.Y) < DistanceThreshold && (Math.Abs(head.Position.Y - leftHand.Position.Y) < DistanceThreshold && !isForwardGestureActive &&!isBackGestureActive)) { if (!isPresent) { isPresent = true; System.Windows.Forms.SendKeys.SendWait("{F5}"); } } else { isPresent = false; } }
上面的代码中,当判断到用户向右挥手动作是,执行System.Windows.Forms.SendKeys.SendWait("{Right}")语句从而发出点击键盘向右按键; 该方法执行时,要求PowerPoint程序处于当前活动的状态,这样里面的PPT才会向右键盘点击事件。需要注意的是方法中isBackGestureActive,isForwardGestureActive,isBlackScreenActive和isPresent这四个布尔型的标志位,可以防止当用户一直处于某一个动作时会一直发送System.Windows.Forms.SendKeys.SendWait("{xx}")。
上面的方法需要放在sensor_SkeletonFrameReady事件中,首先获取头部,左手右手关节点数据,然后调用该方法。
/// <summary> /// 骨骼事件处理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> 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 = skeletons.Where(s => s.TrackingState == SkeletonTrackingState.Tracked) .OrderBy(s => s.Position.Z * Math.Abs(s.Position.X)) .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.NotTracked || rightHand.TrackingState == JointTrackingState.NotTracked || leftHand.TrackingState == JointTrackingState.NotTracked) { //Don't have a good read on the joints so we cannot process gestures return; } //调用填充头和双手位置图案的的方法 SetEllipsePosition(ellipseHead, head, false); SetEllipsePosition(ellipseLeftHand, leftHand, isBackGestureActive); SetEllipsePosition(ellipseRightHand, rightHand, isForwardGestureActive); //调用处理手势的方法 ProcessForwardBackGesture(head, rightHand, leftHand); }
代码已开诚布公到GitHub:https://github.com/Marsyangkang/KinectPowerpointControl
Kinect开发系列博文: