[Kinect SDK] 建立支援Kinect的WPF應用程式(一) - 取得攝影機拍攝的畫面

上次跟大家分享了如何在Windows 7裡面使用Kinect,也順便介紹了幾個Kinect SDK中內建的小範例。不過,身為一個稱職的工程師,想要自己也動手來寫一個,是很合理,也很合邏輯的。
所以,這次就來跟大家分享如果自己撰寫支援Kinect的應用程式喔!!這次,我們要使用WPF搭配C#,來建立支援Kinect的應用程式!!一切當然都要從基礎開始,我們就從取得拍攝到的畫面開始吧!!

 

上次跟大家分享了如何在Windows 7裡面使用Kinect,也順便介紹了幾個Kinect SDK中內建的小範例。不過,身為一個稱職的工程師,想要自己也動手來寫一個,是很合理,也很合邏輯的。

所以,這次就來跟大家分享如果自己撰寫支援Kinect的應用程式喔!!這次,我們要使用WPF搭配C#,來建立支援Kinect的應用程式!!一切當然都要從基礎開始,我們就從取得拍攝到的畫面開始吧!!
 

Step 1. 建立專案

建立應用程式的第一步,當然是開啟Visual Studio 2010囉!!點選New Project...

image

在專案樣版中的Visual C# -> Windows -> WPF Application,並且依照自己的喜好設定專案名稱和路徑。

image

 

Step 2. 檢查並修改專案執行平台

由於Kinect SDK目前只支援32位元的執行期環境,所以如果使用的作業系統是64位元的話,請記得先確認一下專案的執行平台是不是設定為32位元。

方法為:在Visual Studio 2010中Solution Explorer裡的專案名稱上按下滑鼠右鍵 -> 點選Properties

image

接著將出現的專案設定頁面切換到Build頁籤,並且確認Platform target的內容為x86(如果為x64或是Any CPU的話,到時候將無法正常執行喔!!)修改後請記得存檔

image

 

Step 3. 加入Kinect SDK元件庫的參考

都說好要寫Kinect的應用程式了,所以很自然的就得在專案中引用Kinect SDK中幫我們準備好的元件庫囉!!

方法為:在Solution Explorer中專案下的References上按下滑鼠右鍵,點選Add Reference...

image

接著請加為名為Microsoft.Research.Kinect的參考。

image

再來,在MianWindow.xaml.cs中加入Microsoft.Research.Kinect.Nui這個Namespace的引用。這樣,我們就可以開始真正的透過Kinect SDK提供的API取得拍攝到的畫面囉~~

 

Step 4. 在MainWindow中顯示Kinect補捉到的畫面

我們可以透過Microsoft.Research.Kinect.Nui這個Namespace中的Runtime類別來跟Kinect互動,包含取得補捉到的畫面、景深感謝及骨架分析資訊等等。

Runtime類別中提供了三個很方便的事件可以讓我們呼叫,以用來取得原始的視訊畫面(VideoFrameReady)、景深感應資訊(DepthFrameReady)以及骨架分析資訊(SkeletonFrameReady),我們只需要針對這三個事件設定好相對應的Event Handler,就可以在Kinect在處理完不同資訊的時候,取得經過Kinect分析完的資訊。
這邊就簡單的來示範一下怎麼來使用這些資訊。

從SDK所附的文件中可以看到,上述的三個事件除了SkelotonFrameReady傳入的參數為SkeletonFrameReadyEventArgs之外,另外兩個事件都會傳出一個類別為ImageFrameReadyEventArgs的參數,其中包含了用來封裝畫面的ImageFrame類別。但是,在WPF中要拿來放在Image控制項中顯示的圖片格式得用BitmapSource類別比較方便,所以我們只需要簡單的在取得ImageFrame之後,將它轉為BitmapSource,再指定給某個Image控制項來顯示即可。

而這次我們要取得的是攝影機拍攝到的畫面而已,所以很簡單的,請在MainWindow.xaml中先放好一個Image控制項。

MainWindow.xaml

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" x:Class="Wpf_KinectSample.MainWindow" mc:Ignorable="d" d:DesignWidth="800"
        d:DesignHeight="600" Title="MainWindow" Height="Auto" Width="Auto">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="0.913*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Image x:Name="imgVideoFrame" Grid.Row="0" Grid.Column="0" Margin="10" />
        <StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center"
                Margin="0,10">
            <Button x:Name="btnStart" Content="開始擷取" Margin="10,0" Padding="10,1" VerticalAlignment="Center"
                    FontSize="16" Click="btnStart_Click" Visibility="Collapsed" >
            	<i:Interaction.Triggers>
            		<i:EventTrigger EventName="Click">
            			<ei:ChangePropertyAction PropertyName="Visibility">
            				<ei:ChangePropertyAction.Value>
            					<Visibility>Collapsed</Visibility>
            				</ei:ChangePropertyAction.Value>
            			</ei:ChangePropertyAction>
            			<ei:ChangePropertyAction TargetObject="{Binding ElementName=btnStop}" PropertyName="Visibility"/>
            		</i:EventTrigger>
            	</i:Interaction.Triggers>
            </Button>
            <Button x:Name="btnStop" Content="停止擷取" Margin="10,0" Padding="10,1" VerticalAlignment="Center"
                    FontSize="16" Click="btnStop_Click" >
            	<i:Interaction.Triggers>
            		<i:EventTrigger EventName="Click">
            			<ei:ChangePropertyAction PropertyName="Visibility">
            				<ei:ChangePropertyAction.Value>
            					<Visibility>Collapsed</Visibility>
            				</ei:ChangePropertyAction.Value>
            			</ei:ChangePropertyAction>
            			<ei:ChangePropertyAction TargetObject="{Binding ElementName=btnStart}" PropertyName="Visibility"/>
            		</i:EventTrigger>
            	</i:Interaction.Triggers>
            </Button>
            <Button x:Name="btnSavePicture" Content="儲存圖片" Margin="10,0" Padding="10,1" VerticalAlignment="Center"
                    FontSize="16" Click="btnSavePicture_Click" />
        </StackPanel>
    </Grid>
</Window>

再來呢,只要簡單的透過Runtime這個類別幫我們準備好的事件來取得畫面就行啦~而根據SDK中的說明文件裡面的描述,在建立好Runtime物件之後,我們得呼叫Runtime的Initialize()方法,並且指定以RuntimeOptions這個列舉型別定義的參數,以讓Runtime物件可以知道它該擷取哪幾種來源的畫面。

程式的部份原始碼如下:

MainWindow.xaml.cs

using System;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Research.Kinect.Nui;

namespace Wpf_KinectSample
{
    public partial class MainWindow : Window
    {
        private Runtime _runtime;

        public MainWindow()
        {
            InitializeComponent();

            InitializeRuntime();
        }

        private void InitializeRuntime()
        {
            UninitializeRuntime();

            _runtime = new Runtime();

            //因為我們只要取得基本的鏡頭拍到的畫面而已,所以只需要將RuntimeOptions設定為UseColor即可
            _runtime.Initialize( RuntimeOptions.UseColor );

            //透過VideoStream.Open方法開始取得視訊串流
            _runtime.VideoStream.Open( ImageStreamType.Video , 2 , ImageResolution.Resolution640x480 , ImageType.Color );

            //替VideoFrameReady加入EventHandler,把畫面畫在Image中。
            _runtime.VideoFrameReady += new EventHandler<ImageFrameReadyEventArgs>( RuntimeVideoFrameReady );
        }

        private void UninitializeRuntime()
        {
            if( _runtime == null )
            {
                return;
            }

            _runtime.VideoFrameReady -= RuntimeVideoFrameReady;

            _runtime.Uninitialize();

            _runtime = null;
        }

        private void RuntimeVideoFrameReady( object sender , ImageFrameReadyEventArgs e )
        {
            PlanarImage image = e.ImageFrame.Image;

            byte[] pixels = image.Bits;

            //將取得的圖片轉為BitmapSource,並用來當作Image控制項的Source
            imgVideoFrame.Source = BitmapSource.Create( image.Width , image.Height , 96 , 96 , PixelFormats.Bgr32 , null , pixels , image.Width * PixelFormats.Bgr32.BitsPerPixel / 8 );
        }

        private void btnStart_Click( object sender , RoutedEventArgs e )
        {
            InitializeRuntime();
        }

        private void btnStop_Click( object sender , RoutedEventArgs e )
        {
            UninitializeRuntime();
        }

        private void btnSavePicture_Click( object sender , RoutedEventArgs e )
        {
            JpegBitmapEncoder encoder = new JpegBitmapEncoder();

            encoder.Frames.Add( BitmapFrame.Create( BitmapFrame.Create( imgVideoFrame.Source as BitmapSource ) ) );

            Microsoft.Win32.SaveFileDialog openFileDialog = new Microsoft.Win32.SaveFileDialog();
            openFileDialog.FileName = "MyImage"; 
            openFileDialog.DefaultExt = ".jpg"; 
            openFileDialog.Filter = "Jpeg Image (.jpg)|*.jpg"; 

            Nullable<bool> result = openFileDialog.ShowDialog();

            string fileName = string.Empty;

            if( result == true )
            {
                fileName = openFileDialog.FileName;
            }
            else
            {
                return;
            }

            using( var stream = new FileStream( fileName , FileMode.Create ) )
            {
                encoder.Save( stream );
            }
        }
    }
}


喔耶!!大功告成!!來看看執行的畫面~

image

 

老樣子,奉上專案原始碼,請自行服用~