如何使用 Kinect 辨識判斷手勢動作(For Windows SDK V1)
最近KT 陸陸續續收到一些問題反應,
KT在這邊"痾浪死"( announce ,翻譯:宣布) 一下,
讓後來的人,也可以得到解答。
反應: 降低文章等級
先謝謝大家的反應,因為KT之前一直鎖定不到讀者(好久貼的文都沒人反應),
怕大家都是"猛猛"級的,之前文章廢話就太多了,
直接貼CODE就好了,不用太多贅述說明~
結果兩三個來信,信中帶到是"萌萌"級的,希望KT可以再多一點說明。
OK~了解!!!
"英文"與"程式"的說明,未來盡可能再白話一點,不整份貼CODE。
之前V1版的文章,KT會再找時間,補拍影片來操作說明一下。
V1之前版本的就...呵呵呵~
好~今天要來談一下「如何使用 Kinect 辨識手勢動作」,
之前有幾位"達人"分享相關文章如 Kinect Toolbox,
但有人反應那判斷演算法太複雜,對於"萌萌"有點吃力,
但其實那是滿嚴謹且精準的判斷方式。
我們聞香看看:
// Swipe to right if (ScanPositions((p1, p2) => Math.Abs(p2.Y - p1.Y) < SwipeMaximalHeight, // Height (p1, p2) => p2.X - p1.X > -0.01f, // Progression to right (p1, p2) => Math.Abs(p2.X - p1.X) > SwipeMinimalLength, // Length SwipeMininalDuration, SwipeMaximalDuration)) // Duration { RaiseGestureDetected("SwipeToRight"); return; } // Swipe to left if (ScanPositions((p1, p2) => Math.Abs(p2.Y - p1.Y) < SwipeMaximalHeight, // Height (p1, p2) => p2.X - p1.X < 0.01f, // Progression to right (p1, p2) => Math.Abs(p2.X - p1.X) > SwipeMinimalLength, // Length SwipeMininalDuration, SwipeMaximalDuration))// Duration { RaiseGestureDetected("SwipeToLeft"); return; }
哇~還ABSㄟ這可不是什麼新款的煞車系統,而是傳回絕對值的數學函式,
萌萌看到這裡可能就開始倒胃,
想繼續往下看的,幾乎掛蛋,更何況這只是一小角的關鍵演算法。
別急著關閉這一個繁體中文僅搜尋到,可能還有一線生機的網頁,
秀上面這張圖,並非是獻寶說什麼KT搜尋排名第一位,
而是讓KT感到有點寒心,繁體中文玩家研究最新V1版的,
到今天搜尋為止,竟然沒半個人@@,
只有一個完全不正經、半調子、愛啦哩啦喳的KT文章在分享心得,
所以谷哥指引來KT這必然有一定的道理,我們應該也有一定的緣分在。
就姑且繼續看KT拉賽下去吧!!!
之前 Kinect PowerPoint Control 這位達人,
寫出一套易懂易用的辨識判斷手勢動作演算法,
但可惜目前這位作者無繼續更新。
SDK程式版本停留在beta 2版,V1版不在適用,需做移植更新的動作,
KT這邊就改一下將他升級到V1版,順便優化一下這位作者的程式,
延續這位達人的精神,但好像程式差異度高達80%,
等於快全部改寫,這是因為beta 2移植到V1的寫法與宣告幾乎完全不同 。
首先我們先討論一下怎麼使用Kinect 判斷手勢,
Kinect 的強項,最大特色即是可以做到骨架追蹤(Skeleton tracking)這件事,
可以判斷身體共20個關節點。
我們即是要用骨架追蹤這個原理,來做出追蹤手勢動作等相關應用程式,
從底下這張圖片得知,
骨架追蹤手掌節點名稱為HAND_RIGHT (右手)和HAND_LEFT (左手),
而以頭(HEAD)為中心點,來判斷手勢移動上下左右的位置。
所以我們將開啟骨架追蹤功能
(之前KT有討論過 如何使用 Kinect 骨架追蹤(For Windows SDK V1),
所以骨架追蹤基礎概念,這篇略過,詳細請參考那篇文章)
優化骨架追蹤方式,載入平滑處理參數,這是滿常見的作法:
原作者無加入此法,頗讓KT驚訝,若無此舉,還滿容易受雜訊干擾。
//平滑處理,防止高頻率微小抖動和突發大跳動造成的關節雜訊 var parameters = new TransformSmoothParameters { Smoothing = 0.3f, Correction = 0.0f, Prediction = 0.0f, JitterRadius = 1.0f, MaxDeviationRadius = 0.5f }; sensor.SkeletonStream.Enable(parameters);//載入平滑處理參數 sensor.SkeletonStream.Enable();//開啟,骨架追蹤
改採用Coding4Fun的2D座標向量轉換式:
//處理螢幕大小2D座標值 private float ScaleVector(int length, float position) { float value = (((((float)length) / 1f) / 2f) * position) + (length / 2); if (value > length) { return (float)length; } if (value < 0f) { return 0f; } return value; }
KT這邊判斷手勢動作共四種:右手揮動、左手揮動、右手舉高和左手舉高,
(延伸了原作者只做兩種手勢判斷式)
圖解座標說明解釋部分,
KT這邊摒棄傳統會讓"萌萌"看了毛骨悚然的令什麼為X1和X2,
X2減掉X1又會變出啥挖糕,Y2>Y1成立後...等說明解釋,
改採用"萌萌專用"傻瓜標示法來說明。
- 右手揮動
以右手揮動判斷來看,抓取到右手與頭X座標關節點,
判斷右手X關節點是否有大於頭X座標關節點+0.5點的位置,
若有,則判斷為右手揮動。
//==="右手"揮動判斷式=== if (rightHand.Position.X > head.Position.X + 0.5) { CheckGesture(); if (CheckGesture_ready) { isRightHelloGestureActive = true; MessageBox.Show("右手揮動 000 "); } } else { isRightHelloGestureActive = false; }
- 左手揮動
相對的左手揮動,抓取到左手與頭X座標關節點,
判斷左手X關節點是否有大於頭X座標關節點+0.5點的位置,
若有,則判斷為左手揮動。
//==="左手"揮動判斷式=== if (leftHand.Position.X < head.Position.X - 0.5) { CheckGesture(); if (CheckGesture_ready) { isLeftHelloGestureActive = true; MessageBox.Show("000 左手揮動"); } } else { isLeftHelloGestureActive = false; }
- 右手舉高
右手舉高,則是抓取右手Y座標關節點,
判斷右手Y座標關節點是否大於(高於)頭Y座標節點位置,
若有,則判斷為右手舉高 。
//==="右手"舉高判斷式=== if (rightHand.Position.Y > head.Position.Y) { CheckGesture(); if (CheckGesture_ready) { isRightHandOverHead = true; MessageBox.Show("右手舉高 000 000 000 "); } } else { isRightHandOverHead = false; }
- 左手舉高
左手舉高,則是抓取左手Y座標關節點,
判斷左手Y座標關節點是否大於(高於)頭Y座標節點位置,
若有,則判斷為左手舉高 。
//==="左手"舉高判斷式=== if (leftHand.Position.Y > head.Position.Y) { CheckGesture(); if (CheckGesture_ready) { isLeftHandOverHead = true; MessageBox.Show("000 000 000 左手舉高"); } } else { isLeftHandOverHead = false; }
有了以上概念,就可以完成Kinect 辨識判斷手勢動作等應用程式,
這邊KT示範當偵測到手勢動作時彈出訊息視窗(MessageBox),
當然如果你希望可以偵測到手勢動作時有其他動作,
如原作者,初始概念是用Kinect 判斷手勢揮動,
可以來切換投影片上一頁或下一頁。
此做法是當偵測到手勢動作時, 模擬送出鍵盤的動作如:
System.Windows.Forms.SendKeys.SendWait("{Left}");//模擬鍵盤左鍵按下動作 System.Windows.Forms.SendKeys.SendWait("{Right}");//模擬鍵盤右鍵按下動作
此舉當你打開PowerPoint(投影片)時,
即可做出利用Kinect判斷手勢來切換投影片。
影片教學:
本範例完整程式碼如下:
XAML CODE:
C# CODE:
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using Microsoft.Kinect; using System.Linq; namespace Kinect_GestureRecognition_Demo { public partial class MainWindow : Window { //Instantiate the Kinect runtime. Required to initialize the device. //IMPORTANT NOTE: You can pass the device ID here, in case more than one Kinect device is connected. KinectSensor sensor = KinectSensor.KinectSensors[0]; //宣告 KinectSensor //變數初始化定義 byte[] pixelData; Skeleton[] skeletons; bool isRightHelloGestureActive = false; bool isLeftHelloGestureActive = false; bool isRightHandOverHead = false; bool isLeftHandOverHead = false; bool CheckGesture_ready = false; public MainWindow() { InitializeComponent(); //載入與卸載 this.Loaded += new RoutedEventHandler(MainWindow_Loaded); this.Unloaded += new RoutedEventHandler(MainWindow_Unloaded); sensor.ColorStream.Enable();//開啟,彩色影像 //平滑處理,防止高頻率微小抖動和突發大跳動造成的關節雜訊 var parameters = new TransformSmoothParameters { Smoothing = 0.3f, Correction = 0.0f, Prediction = 0.0f, JitterRadius = 1.0f, MaxDeviationRadius = 0.5f }; sensor.SkeletonStream.Enable(parameters);//載入平滑處理參數 sensor.SkeletonStream.Enable();//開啟,骨架追蹤 } //MainWindow 載入 void MainWindow_Loaded(object sender, RoutedEventArgs e) { sensor.SkeletonFrameReady += runtime_SkeletonFrameReady; sensor.ColorFrameReady += runtime_VideoFrameReady; sensor.Start(); } //MainWindow 卸載 void MainWindow_Unloaded(object sender, RoutedEventArgs e) { sensor.Stop(); } //彩色影像,處理函數 void runtime_VideoFrameReady(object sender, ColorImageFrameReadyEventArgs e) { bool receivedData = false; using (ColorImageFrame CFrame = e.OpenColorImageFrame()) { if (CFrame == null) { // The image processing took too long. More than 2 frames behind. } else { pixelData = new byte[CFrame.PixelDataLength]; CFrame.CopyPixelDataTo(pixelData); receivedData = true; } } if (receivedData) { BitmapSource source = BitmapSource.Create(640, 480, 96, 96, PixelFormats.Bgr32, null, pixelData, 640 * 4); videoImage.Source = source; } } //骨架追蹤,處理函數 void runtime_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) { bool receivedData = false; using (SkeletonFrame SFrame = e.OpenSkeletonFrame()) { if (SFrame == null) { // The image processing took too long. More than 2 frames behind. } else { skeletons = new Skeleton[SFrame.SkeletonArrayLength]; SFrame.CopySkeletonDataTo(skeletons); receivedData = true; } } if (receivedData) { Skeleton currentSkeleton = (from s in skeletons where s.TrackingState == SkeletonTrackingState.Tracked select s).FirstOrDefault(); if (currentSkeleton != null) { //取得骨架關節點 3D(X、Y、Z)座標值。 var head = currentSkeleton.Joints[JointType.Head]; var rightHand = currentSkeleton.Joints[JointType.HandRight]; var leftHand = currentSkeleton.Joints[JointType.HandLeft]; SetEllipsePosition(ellipseHead, head, false); SetEllipsePosition(ellipseLeftHand, leftHand, isLeftHelloGestureActive); SetEllipsePosition(ellipseRightHand, rightHand, isRightHelloGestureActive); ProcessGesture(head, rightHand, leftHand);//呼叫處理手勢函數 } } } //設定圖案位置 private void SetEllipsePosition(Ellipse ellipse, Joint joint, bool isHighlighted) { //將3D 座標轉換成螢幕上大小,如640*320 的 2D 座標值 Microsoft.Kinect.SkeletonPoint vector = new Microsoft.Kinect.SkeletonPoint(); vector.X = ScaleVector(640, joint.Position.X); vector.Y = ScaleVector(480, -joint.Position.Y); vector.Z = joint.Position.Z; // Z值原封不動 Joint updatedJoint = new Joint(); updatedJoint = joint; updatedJoint.TrackingState = JointTrackingState.Tracked; updatedJoint.Position = vector; //得到 2D座標值(X、Y)後,將值設定為圖案顯示的位置 Canvas.SetLeft(ellipse, updatedJoint.Position.X); Canvas.SetTop(ellipse, updatedJoint.Position.Y); } //處理螢幕大小2D座標值 private float ScaleVector(int length, float position) { float value = (((((float)length) / 1f) / 2f) * position) + (length / 2); if (value > length) { return (float)length; } if (value < 0f) { return 0f; } return value; } //判斷處理手勢函數 private void ProcessGesture(Joint head, Joint rightHand, Joint leftHand) { //==="右手"揮動判斷式=== if (rightHand.Position.X > head.Position.X + 0.5) { CheckGesture(); if (CheckGesture_ready) { isRightHelloGestureActive = true; MessageBox.Show("右手揮動 000 "); //System.Windows.Forms.SendKeys.SendWait("{Right}"); } } else { isRightHelloGestureActive = false; } //==="左手"揮動判斷式=== if (leftHand.Position.X < head.Position.X - 0.5) { CheckGesture(); if (CheckGesture_ready) { isLeftHelloGestureActive = true; MessageBox.Show("000 左手揮動"); //System.Windows.Forms.SendKeys.SendWait("{Left}"); } } else { isLeftHelloGestureActive = false; } //==="右手"舉高判斷式=== if (rightHand.Position.Y > head.Position.Y) { CheckGesture(); if (CheckGesture_ready) { isRightHandOverHead = true; MessageBox.Show("右手舉高 000 000 000 "); } } else { isRightHandOverHead = false; } //==="左手"舉高判斷式=== if (leftHand.Position.Y > head.Position.Y) { CheckGesture(); if (CheckGesture_ready) { isLeftHandOverHead = true; MessageBox.Show("000 000 000 左手舉高"); } } else { isLeftHandOverHead = false; } } //判斷手勢是否就緒,防止同一個動作誤判 private void CheckGesture() { if (!isLeftHelloGestureActive && !isRightHelloGestureActive && !isRightHandOverHead && !isLeftHandOverHead) { CheckGesture_ready = true; } else { CheckGesture_ready = false; } } } }
範例程式碼下載:
更多相關參考文章:
1.Kinect Toolbox
2.Kinect PowerPoint Control
3.HKT線上教學教室 - Kinect 教學目錄