如何使用 Kinect 辨識判斷手勢動作(For Windows SDK V1)

Photobucket

最近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ㄟ這可不是什麼新款的煞車系統,而是傳回絕對值的數學函式,

萌萌看到這裡可能就開始倒胃,

想繼續往下看的,幾乎掛蛋,更何況這只是一小角的關鍵演算法。

別急著關閉這一個繁體中文僅搜尋到,可能還有一線生機的網頁,

Photobucket

秀上面這張圖,並非是獻寶說什麼KT搜尋排名第一位,

而是讓KT感到有點寒心,繁體中文玩家研究最新V1版的,

到今天搜尋為止,竟然沒半個人@@,

只有一個完全不正經、半調子、愛啦哩啦喳的KT文章在分享心得,

所以谷哥指引來KT這必然有一定的道理,我們應該也有一定的緣分在。

就姑且繼續看KT拉賽下去吧!!!



之前 Kinect PowerPoint Control 這位達人,

寫出一套易懂易用的辨識判斷手勢動作演算法,

但可惜目前這位作者無繼續更新。

SDK程式版本停留在beta 2版,V1版不在適用,需做移植更新的動作,

KT這邊就改一下將他升級到V1版,順便優化一下這位作者的程式,

延續這位達人的精神,但好像程式差異度高達80%,

等於快全部改寫,這是因為beta 2移植到V1的寫法與宣告幾乎完全不同 。




首先我們先討論一下怎麼使用Kinect 判斷手勢,

Kinect 的強項,最大特色即是可以做到骨架追蹤(Skeleton tracking)這件事,

可以判斷身體共20個關節點。

我們即是要用骨架追蹤這個原理,來做出追蹤手勢動作等相關應用程式,

從底下這張圖片得知,

Photobucket

骨架追蹤手掌節點名稱為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成立後...等說明解釋,

改採用"萌萌專用"傻瓜標示法來說明。

  • 右手揮動 
Photobucket

以右手揮動判斷來看,抓取到右手與頭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;
            }

  • 左手揮動  
Photobucket
相對的左手揮動,抓取到左手與頭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;
            }

  • 右手舉高  
Photobucket

右手舉高,則是抓取右手Y座標關節點,
判斷右手Y座標關節點是否大於(高於)頭Y座標節點位置,
若有,則判斷為右手舉高 。
//==="右手"舉高判斷式===
            if (rightHand.Position.Y > head.Position.Y)
            {
                CheckGesture();
                if (CheckGesture_ready)
                {
                    isRightHandOverHead = true;
                    MessageBox.Show("右手舉高 000 000 000 ");
                }
            }            
            else
            {
                isRightHandOverHead = false;
            }



  • 左手舉高  
Photobucket

左手舉高,則是抓取左手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 教學目錄

這個網誌中的熱門文章

最新入門零基礎 Java 教學【從零開始學 Java 程式設計】Java教學課程目錄 (IntelliJ IDEA 開發教學)

2023 最新入門零基礎 Kotlin教學【從零開始學 Kotlin 程式設計】Kotlin 教學課程目錄 (Android Kotlin, IntelliJ IDEA, Android Studio, Android APP 開發教學)

Android Studio 歷代版本下載點

nano 文字編輯器

2022 最新入門零基礎 Flutter教學 【Flutter 程式設計入門實戰 30 天】Flutter 教學課程目錄 (IntelliJ IDEA 開發教學)