[Kinect SDK] 建立支援Kinect的WPF應用程式(三) - 初探身體感應功能

 

經過了前面兩章的暖身,這次我們就要來對Kinect最威能的部份-「身體感應」來做個基本的了解啦!!不然只拿Kinect來拍照的話對Kinect來說可是一種天大的污辱啊~~

 

Kinect SDK中提供的身體感應點

Kinect SDK預設就內建了20個身體部位的感應點,比某些第三方元件提供的13~16個點就硬生生的多了好幾個。而在C#版的Kinect SDK中也很友善的幫我們預先把這二十個點都以一個Enum封裝了起來,我們只需要透過Enum去選取我們要的部位就可以取得該點的座標值。

下圖即為目前Kinectsdk中提供的二十個感應點名稱(不過在C#版提供的Enum中是沒底線的喔)。

image

 

Kinect 的座標系

另外,Kinect的座標系也跟我們平常習慣用在電腦上的座標系有所不同。我們在電腦螢幕上習慣以左上角為起點,往右的話X軸的值增加,往下的話Y軸的值增加;不過,Kinect是以面對Kinect Sensor的方向為準,往左手邊的話則X軸的值增加,往上方的話Y軸的值增加,而往前的話則Z軸的值增加。

image

 

牛刀小試

有了上述的兩個基本觀念之後,再看看實際的範例,應該就會更有Fu啦~懶人有懶人的作法,讓我們直接使用S/L大法,以KinectSkeletonApplication專案樣版來建立一個應用程式,接著來研究裡面的程式吧!!我就把裡面幾個比較重要的地方列出來和大家分享。

為了讓抓取到的點更明顯,我將原來專案中的三個紅點以自己定義的控制項取代了,修改後的Xaml檔如下:

MainWindow.xaml

<Window x:Class="Wpf_KinectSample03.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="600" Width="800">
    <Grid>
        <Image Name="videoImage"></Image>
        <Canvas Background="Transparent">
            <Grid Name="grdLeftHand">
                <Ellipse Fill="Green" Height="40" Width="40" Name="leftHand" Stroke="White" />
                <TextBlock Text="左手" FontSize="24" FontWeight="Bold" TextAlignment="Center" HorizontalAlignment="Center"
                        VerticalAlignment="Center" />
            </Grid>
            <Grid Name="grdRightHand">
                <Ellipse Fill="Red" Height="40" Width="40" Name="rightHand" Stroke="White" />
                <TextBlock Text="右手" FontSize="24" FontWeight="Bold" TextAlignment="Center" HorizontalAlignment="Center"
                        VerticalAlignment="Center" />
            </Grid >
            <Grid Name="grdHead">
                <Ellipse Fill="Yellow" Height="50" Width="50" Name="head" Stroke="White" />
                <TextBlock Text="頭" FontSize="24" FontWeight="Bold" TextAlignment="Center" HorizontalAlignment="Center"
                        VerticalAlignment="Center" />
            </Grid>
        </Canvas>
    </Grid>
</Window>

再來看看Code-Behind的部份(我在幾個比較重要的地方加上了註解):

MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Research.Kinect.Nui;

namespace Wpf_KinectSample03
{
    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.
        Runtime runtime = new Runtime();

        public MainWindow()
        {
            InitializeComponent();

            //Runtime initialization is handled when the window is opened. When the window
            //is closed, the runtime MUST be unitialized.
            this.Loaded += new RoutedEventHandler( MainWindow_Loaded );
            this.Unloaded += new RoutedEventHandler( MainWindow_Unloaded );

            //Handle the content obtained from the video camera, once received.
            runtime.VideoFrameReady += new EventHandler<Microsoft.Research.Kinect.Nui.ImageFrameReadyEventArgs>( runtime_VideoFrameReady );

            runtime.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>( runtime_SkeletonFrameReady );
        }

        void runtime_SkeletonFrameReady( object sender , SkeletonFrameReadyEventArgs e )
        {
            SkeletonFrame skeletonSet = e.SkeletonFrame;

            SkeletonData data = ( from s in skeletonSet.Skeletons
                                  where s.TrackingState == SkeletonTrackingState.Tracked
                                  select s ).FirstOrDefault();
            //這段是我自己加上來的,可以避免當在拍攝的範圍中偵測不到人的時候會發生Exception的情況
            if( data == null )
            {
                return;
            }

            //下面這三行就是直接透過Kinect SDK提供的Enum去抓取頭、左手和右手的位置,再透過SetFrameworkElementPosition方法將抓到的位置套到畫面上預先放好的控制項中
            SetFrameworkElementPosition( grdHead , data.Joints[ JointID.Head ] );
            SetFrameworkElementPosition( grdLeftHand , data.Joints[ JointID.HandLeft ] );
            SetFrameworkElementPosition( grdRightHand , data.Joints[ JointID.HandRight ] );
        }

        //這個方法會傳入代表某個點的控制項,還有該點相對於Kinect擷取到的座標
        private void SetFrameworkElementPosition( FrameworkElement frameworkElement , Joint joint )
        {

            Microsoft.Research.Kinect.Nui.Vector vector = new Microsoft.Research.Kinect.Nui.Vector();
            //下面的兩行為透過呼叫ScaleVector方法,將Kinect的座標系換算回螢幕座標系
            vector.X = ScaleVector( 640 , joint.Position.X );
            //別忘了,Y座標軸往上是負的,所以這邊要變負數才行
            vector.Y = ScaleVector( 480 , -joint.Position.Y  );
            vector.Z = joint.Position.Z;

            //宣告一個新的Joint物件來存放重新計算過的座標
            Joint updatedJoint = new Joint();
            updatedJoint.ID = joint.ID;
            updatedJoint.TrackingState = JointTrackingState.Tracked;
            updatedJoint.Position = vector;

            //重新設定控制項的座標位置,後面我自己加上了加回控制項大小的程式碼,以減少誤差
            Canvas.SetLeft( frameworkElement , updatedJoint.Position.X + frameworkElement.ActualWidth  );
            Canvas.SetTop( frameworkElement , updatedJoint.Position.Y + frameworkElement.ActualHeight );
        }

        //這個方法會將Kinect的座標系換算回螢幕的座標系
        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;
        }

        void MainWindow_Unloaded( object sender , RoutedEventArgs e )
        {
            runtime.Uninitialize();
        }

        void MainWindow_Loaded( object sender , RoutedEventArgs e )
        {
            try
            {
                runtime.Initialize( Microsoft.Research.Kinect.Nui.RuntimeOptions.UseColor | RuntimeOptions.UseSkeletalTracking );

                runtime.VideoStream.Open( ImageStreamType.Video , 2 , ImageResolution.Resolution640x480 , ImageType.Color );
            }
            catch( Exception )
            {
                MessageBox.Show( "Kinect裝置初始化失敗!!" );
            }
        }
        void runtime_VideoFrameReady( object sender , Microsoft.Research.Kinect.Nui.ImageFrameReadyEventArgs e )
        {
            PlanarImage image = e.ImageFrame.Image;

            BitmapSource source = BitmapSource.Create( image.Width , image.Height , 96 , 96 ,
                PixelFormats.Bgr32 , null , image.Bits , image.Width * image.BytesPerPixel );
            videoImage.Source = source;
        }
    }
}

好啦~~來看看執行畫面吧!!

image

 

最後一樣奉上專案原始碼,請自行取用:


 


如果這篇文章對您有幫助,請幫我點一下「我要推薦」、按個讚、或是幫我推到其他平台;您的鼓勵將會是我繼續努力的一大動力!!

若是有任何指教或是需要討論之處,也不用客氣,請在下面留言給我,我將會儘速回覆~

Share | . . . . . . . . . .


Comments

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by duck on 2011/8/15 下午 08:36 回覆
版主你好 我有用本來的KinectSkeletonApplication
和你所寫得跑過 但都跑不起來 另外我開Sample Skeletal Viewer

發現我的kinect 只要一抓到人物骨架資訊 fps就會暴跌到10以下

導致我很多測試範例 ; Sample Shape Game 都無法正常運作

想詢問版主有沒有遇過這方面問題或者聽說過這樣的情況
# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/8/16 上午 10:21 回覆

to duck :
如果是FPS過低的話,我個人猜測可能是顯示卡或是CPU不夠力造成的。建議您可以先更新一下顯示卡的驅動程式再試看看,也順便檢查一下您開發的環境上的硬體是不是有符合Kinect SDK建議的最低規格需求喔!!

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Amy on 2011/8/17 上午 10:51 回覆
版主您好, 想請問一下
我跑這個程式的時候
頭和左右手的位置都有點誤差
全部都有下移的現象

vector.X = ScaleVector( 1366 , joint.Position.X );
這裡的vector.X和Y已經調成螢幕解析度 1366*768

但是還是有點誤差
請問有什麼建議嗎?

謝謝您 :)
# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/8/17 下午 12:37 回覆

to Amy :
這個問題是因為原來專案中的ScaleVector裡面的演算法造成的。其實要把Joint對應到螢幕上的演算法有蠻多種的,但是我比較懶,就直接用現成的了~

提供您另一篇文章連結,裡面的演算法您可以參考看看:

Kinect SDK: Skeleton Tracking

希望對您有幫助!!

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/8/17 下午 04:40 回覆

to Ouch :
再補充另一個日文的文章連結:

Kinect SDK XNA 骨格情報の2D座標変換

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Amy on 2011/8/22 下午 10:20 回覆
可以再問您一下 就是KINECT SDK 改版過後 舊有的程式碼都不能編譯了 可以跟您請教一下嗎?
# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/8/23 上午 09:56 回覆

to Amy :
請問您所謂的改版指的是從哪個版本改到哪個版本呢?我上了Kinect for Windows SDK的官網,看到的版本一樣是29 July 2011的1.0.0.12版,跟我文章範例中使用的是相同的版本喔!!麻煩您撥空檢查一下您所說的版本,好讓我有機會幫您找出可能的問題~

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Amy on 2011/8/23 下午 08:52 回覆
就是 29 JULY 2011 之前那一版 謝謝
# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/8/24 上午 10:36 回覆

to Amy :
我沒用過1.0.0.12之前的版本,所以也沒遇過改版的問題~
剛剛我試著去找改版的資訊,但是也沒找到相關的訊息。
建議您比對一下編譯時期的錯誤訊息,以及新版的Kinect for Windows SDK類別庫和舊版本的內容,看看是不是有哪些Namespace或是Method在新版和舊版本中有差異的地方,再將您的程式碼修改成適用於新版的寫法。

雖然這樣比較辛苦,不過這個應該是比較腳踏實地的辦法喔!!
 

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Amy on 2011/8/24 上午 11:08 回覆
謝謝你喔QQ 我還在找辦法 可能真的是有一些CODE我沒有看清吧
# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/8/24 下午 01:56 回覆

to Amy :
加油加油~~祝您的專案早日可以Build!!~

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Amy on 2011/8/24 下午 03:01 回覆
您好,我現在已經成功地可以抓到骨架的資訊,也可以執行成你做的樣子~ 想問一下如果想把這些點連起來有方法可以實現嗎?

謝謝您 :)
# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/8/24 下午 03:50 回覆

to Amy :
您可以參考WPF 中圖案和基本繪圖概觀,透過在SkeletonFrameReady的EventHandler中動態的設定Line或是PolyLine的起點和終點位置來繪製出您要的線段。
不過如果要做得精細的話,應該會有點小累人吧~加油!!

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Peter on 2011/8/24 下午 03:57 回覆
您好 有幾個問題想問一下
1.我單跑此程式 頭手位置還算滿精準的(要離kinect有點距離)
可是我將此程式與前幾篇文章結合後 再跑 會出現 kinect 裝置初始化失敗的視窗 (註解是說偵測不到人 可是我試過各種距離 每跑必出現) 按掉後 視訊畫面仍然會出現 可是頭手的位置 就往左下平移了 這是什麼問題呢?
2.要如何將手勢當作滑鼠的功能呢? (因為要結合的程式有滑鼠觸發的事件 想用抓到的手當滑鼠使用)
# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/8/25 上午 10:15 回覆

to Peter :
關於初始化失敗的問題;在這幾篇文章會拋出"初始化失敗"錯誤的地方應該都是去針對Runtime下Initialize或是要求Runtime開始取得串流的部份,而不是在取得串流內容後才拋出的,所以可能得麻煩您詳細的檢查一下Exception的內容或是您程式的邏輯。

而滑鼠控制的部份就可能就得麻煩您多費點心在Google找看看,應該能找到不少範例喔!!

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by williamken on 2011/8/31 下午 01:36 回覆
請問一下
Kinect SDK 是左手座標系還是右手?
# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/8/31 下午 02:05 回覆

to williamken :
Kinect SDK骨架使用的座標系是是右手座標系喔!!

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by XD on 2011/9/6 上午 09:54 回覆
請問我該如何將取出來的骨架位置轉換成text輸出呢?
我是有看了一些資料但好像跟您寫得不太一樣,所以我不太曉得該如何去修改或增加這個功能呢
# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/9/6 上午 10:40 回覆

to XD :
骨架的資訊會被封裝在SkeletonData.Joints裡面,您可能得自己實作一個迴圈,取出整個JointsCollection中的每個身體部位的Joint資料,再將每個Joint中您想要輸出的屬性轉成字串輸出喔!!

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by XD on 2011/9/6 下午 08:57 回覆
那如果我有要取針對部位拿取的資料呢?
因為其實我也才第一次接觸C#...所以問的問題可能會笨了點,不好意思喔>"<
# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by channle on 2011/9/6 下午 10:51 回覆
請問一下我已經有抓到各關節的值,想請問官方SDK是否有提供判斷一些手勢(例如左手舉起、放下等),因為上網GOOGLE發現好像只有非官方的有提供該功能,不曉得是嗎? 那如果我想抓動作前的值跟動作後的值是否要透過陣列儲存還有辦法?
# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/9/7 上午 09:45 回覆

to XD :
如果要取得特定部位的資料,請參考本文範例中取得頭和雙手資料的部份,透過JointID這個列舉型別去取得該部位的資料。
另外,操作列舉型別時您可能會用到 [C#]將string轉為enum Convert String To Enum 這篇文章裡面的技巧,提供給您參考,希望對您有幫助。

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/9/7 上午 09:54 回覆

to channle :
關於動作判斷的部份,我記得之前的Kinect Toolkit裡面好像有,但是該專案因為微軟未來可能也會推出同名的Toolkit,所以就下架了。

不過我有在CodePlex上找到另外一個專案,可以用來將動作錄成檔案,或許您可以參考看看:Kinect SDK Dynamic Time Warping (DTW) Gesture Recognition

最後,關於動作前後的問題,因為骨架資料是以串流的方式輸出給我們使用,我們只會一直拿到最新的資料,所以這個部份可能就得自己想辦法實作囉!!

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Amy on 2011/9/8 上午 11:26 回覆
想請問一下範例檔的z軸資料有調校嗎??

我把它印出來是-1~1的值如果要做景深動作的判斷好像有點困難 :(

請問有什麼方法可以解決嗎??

謝謝 :)
# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/9/8 下午 02:28 回覆

to Amy :
您好,如同您所看到的,範例中只針對X和Y軸座標轉換成2D的平面顯示,並沒有對Z軸座標做處理喔!!

因為Kinect通常是運用在「動態」的動作判斷上,其實您只要透過Z軸值的改變就可以判斷玩家的位置是前進或後退了;但是,如果是要做到更精細的3D Rendering的話,我就沒研究了~

如果您有什麼特殊的需求,要針對Z軸座標進行額外的處理的話,不妨提出來,好讓我想看看能怎麼幫助您~
 

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Air on 2011/9/28 上午 10:47 回覆
請問一下如何能區分兩個玩家的skeleton信息呢?我的程式目前只能在同一個時間認識一個玩家的操作,請問有什麼方法可以解決嗎?謝謝”
# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/9/28 下午 01:17 回覆

to Air :
SkeletonFrame.Skeletons屬性裡會包含所有被補捉到的玩家的骨架資訊(SkeletonData),如果您要處理多個玩家,只需要將SkeletonFrame.Skeletons裡的每個SkeletonData取出使用就行了!!

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Momo on 2011/10/17 下午 10:08 回覆
不好意思,我不會取出個別的骨架資料,請問你能夠幫忙寫一段簡單的程式跟我講說該怎分辨玩家一跟玩家二嗎?
例如:可以讓手套出現在玩家1身上和玩家2身上
# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by Ouch on 2011/10/18 上午 11:26 回覆

to Momo :
我寫了一篇關於取得多個使用者方式的文章:[Kinect SDK]獨樂樂不如眾樂樂 - 取得並顯示多個玩家的骨架資訊,希望對您有幫助!!

# re: [Kinect SDK]建立支援Kinect的WPF應用程式(三) - 初探身體感應功能
Posted by uthorse on 2011/10/18 下午 01:44 回覆
請問一下我們若是要設置一個button 儲存要怎麼使用?!
請問有相關程式碼可以做為參考嘛!?

回應

Title *
Name *
Email (將不會被顯示)
Url
Comment *

登入後使用進階評論

Please add 2 and 7 and type the answer here: