如何使用 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 教學目錄