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

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

 

經過了前面兩章的暖身,這次我們就要來對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

 

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