ロジックコードを記述する
リスト2 (MainWindow.xaml.vb)
2 | Imports Microsoft.Kinect |
3 | Imports System.Windows.Media.Imaging |
4 | Imports Microsoft.Speech.AudioFormat |
5 | Imports Microsoft.Speech.Recognition |
Assembly.GetExecutingAssembly.GetName.CodeBaseでアプリケーションパス(この場合bin\Debugディレクトリ内の実行ファイルパス)を取得する必要があるため、Assemblyクラスの含まれる、System.Reflection名前空間をインポートしておきます。最終的に取得したbin\Debugフォルダ内には、右手のひらに乗るdummy.pngという名前の画像が保存されます。
1 | Imports System.Reflection |
3 | Imports Coding4Fun.Kinect.Wpf |
一個のKinectセンサーを表すメンバ変数Kinectを宣言します。
1 | Dim Kinect As KinectSensor |
整数四角形の幅、高さ、および位置を表すInt32Rect構造体のメンバ変数myScreenImageRectを宣言します。領域の指定に使用されます。
1 | Dim myScreenImageRect As Int32Rect |
Short型の配列メンバ変数myDepthPixelDataとByte型の配列メンバ変数myColorPixelDataを宣言しておきます。
※深度情報は、1ピクセルあたり2バイトのshort型。画像情報はフルカラーなので1ピクセルあたり4バイトのbyte型です。
1 | Dim myDepthPixelData As Short() |
2 | Dim myColorPixelData As Byte() |
音声認識サービスを実行するためのアクセス権を提供するクラスである、SpeechRecognitionEngineクラス用メンバ変数engineを宣言します。
1 | Dim engine As SpeechRecognitionEngine |
Imageクラス型のmyImageメンバ変数を宣言します。
深度を指定する定数メンバ変数targetDepthを宣言し、2000(2m)で初期化しています。そのため、Kinectセンサーより2m離れてお試しください。
1 | Const targetDepth As Double=2000 |
WriteableBitmapクラス型のHuman1_bitmap、Human2_bitmap、Room_Bitmapプロパティを定義しておきます。
1 | Property Human1_bitmap As WriteableBitmap |
2 | Property Human2_bitmap As WriteableBitmap |
3 | Property Room_Bitmap As WriteableBitmap |
ウィンドウが読み込まれた時の処理
Choicesクラスは、要素を構成するための代替項目の一覧を表すクラスで、GrammarBuilder オブジェクトからのみ直接使用されます。認識させる言葉をAddメソッドで登録します。
GrammarBuilderクラスは、単純な入力から複雑な Grammar(構文情報を取得管理するクラス)を構築するためのメカニズムを提供するクラスで、登録された言葉の構文(文法)設定を行い、SpeechRecognitionEngineへと設定します。Appendメソッドで、登録した言葉を GrammarBuilder オブジェクトとして現在の GrammarBuilder に追加します。
文法のチェックされた言葉(builder)で初期化された、新しいGrammerクラスのインスタンス、myGrammerオブジェクトを作成します。Grammerクラスは、構文情報を取得および管理するためにランタイムをサポートするクラスです。
次に、SpeechRecognitionEngineクラスの新しいインスタンスengineオブジェクトを作成します。SpeechRecognitionEngineクラスのLoadGrammerメソッドで、Grammar によって指定された通りに、特定の構文を同期的に読み込みます。
Kinectセンサーを取得し、Kinectセンサーを動作させます。Kinectの音声インターフェースは、Kinect.AudioSourceで提供されます。Startメソッドで音声入力を開始します。
入力ストリームを取得し、SpeechRecognitionEngine クラスのSetInputToDefaultAudioDeviceメソッドで、SpeechRecognitionEngine の現在のインスタンスに、システム既定のオーディオ入力を割り当てます。
認識操作の後に、RecognizeAsync によって開始された認識を終了しないよう、RecognizeMode.Multipleを指定して、RecognizeAsyncメソッドで非同期音声認識を開始します。
言葉が認識された際には、AddHandlerステートメントで言葉を認識した際に発生するSpeechRecognizedイベントに、イベントハンドラを指定します。認識された音声(speechArgs.Result.Text)を変数wordsに格納します。Confidenceプロパティで音声認識の信頼度を設定します。-1が低、0が標準、1が高信頼度となります。-1を指定するとどんな言葉でも反応する恐れがあります。1を指定するとなかなか認識してくれません。今回は信頼度が0.5より大きい場合に設定しています。
「わかれる」と発声された場合はプレイヤーを画像として保存します。
アプリケーションへのパスをAssembly.GetExecutingAssembly.GetName.CodeBaseで取得し、変数fullAppNameに格納します。fullAppNameには「完全フルパス+bin\Debugフォルダ内のKINECT_SeparateManOnHand.exe」という実行ファイル名が格納されます。欲しいのは「完全フルパス+bin\Debug」というフォルダ名ですので、Path.GetDirectoryName(fullAppName)で、欲しいフォルダ名が取得できます。変数fullAppPathに格納しておきます。
BitmapFrameクラスのbmpFrame変数を宣言します。BitmapFrameクラスは、デコーダによって返されてエンコーダによって受け入れられるイメージ データを表すクラスです。
BitmapFrame.CreateメソッドでHuman2_bitmapプロパティからBitmapFrameを作成します。文字列dummy.pngというファイル名と、新規作成モードで初期化された、FileStreamのインスタンスstreamオブジェクトを作成します。
新しいPngBitmapEncoderクラスのインスタンスmyPngオブジェクトを作成します。PngBitmapEncodeクラスは、PNG形式のイメージのエンコードに使用されるエンコーダを定義するクラスです。
myPngオブジェクトのイメージ内の個別のフレームにAddメソッドで、streamオブジェクトで作成したファイルを追加していきます。Saveメソッドでストリームを保存します。これで、フルパス+bin\Debug内にdummy.pngという画像ファイルが保存されます。
AddHandlerステートメントで、RGBカメラ、距離カメラ、スケルトンのフレームが更新された時に発生する、AllFramesReadyイベントに、Kinect_AllFramesReadyイベントハンドラを指定します。
personImageのSourceプロパティに、フルパス+dummy.pngの画像を指定します。絶対Uriで指定します。
「くっつく」と発声された場合は、personImageのSourceプロパティにNothingを指定してオブジェクトの関連付けをなくします。「おわり」と発声された場合はKinectセンサーの動作を停止し、音声認識も停止し、Environment.Exit(0)でプログラムを終了します。
RGBカメラ、距離カメラ、スケルトンを有効にし、各画像データを初期化するinit_kinectプロシージャを実行します。
構成ツリーのオブジェクトがレンダリングされる直前に発生する、CompositionTarget.Renderingイベントにイベントハンドラを指定します。イベントハンドラ内では以下の処理を行います。
OpenNextFrame(100)メソッドで、KinectからRGBデータの次のフレームを開きます。次のフレームがなかった場合のタイムアウトを100ミリセコンドと指定しています。同様に、Kinectから深度データの次のフレームを開きます。次のフレームがなかった場合のタイムアウトを100ミリセコンドと指定しています。
プレイヤーを描画するRenderScreenプロシージャを実行します。引数として、Kinectのストリーミング用RGBデータのバッファと深度データのバッファを含んでいる、colorFrameとdepthFrameを渡しています。DataContextプロパティにMainWindow自身のインスタンスを指定します。この処理を行わないとプレイヤーが表示されませんので注意してください。
01 | Private Sub MainWindow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded |
02 | If KinectSensor.KinectSensors.Count = 0 Then |
03 | MessageBox.Show("Kinectが接続されておりません。") |
07 | Dim sentence As Choices = New Choices |
14 | Dim builder As GrammarBuilder = New GrammarBuilder |
15 | builder.Append(sentence) |
16 | Dim myGrammer As Grammar = New Grammar(builder) |
17 | engine = New SpeechRecognitionEngine() |
18 | engine.LoadGrammar(myGrammer) |
20 | AddHandler engine.SpeechRecognized, Sub(speechSender As Object, speechArgs As SpeechRecognizedEventArgs) |
22 | Dim confidence = speechArgs.Result.Confidence |
23 | If confidence > 0.5 Then |
24 | Select Case speechArgs.Result.Text |
26 | Dim fullAppName As String = Assembly.GetExecutingAssembly.GetName.CodeBase |
27 | Dim fullAppPath As String = Path.GetDirectoryName(fullAppName) |
28 | Dim bmpFrame As BitmapFrame = BitmapFrame.Create(Human2_bitmap) |
29 | Using stream As FileStream = New FileStream("dummy.png", FileMode.Create) |
30 | Dim myPng As PngBitmapEncoder = New PngBitmapEncoder |
31 | myPng.Frames.Add(bmpFrame) |
34 | AddHandler Kinect.AllFramesReady, AddressOf Kinect_AllFramesReady |
35 | personImage.Source = New BitmapImage(New Uri(Path.Combine(fullAppPath, "dummy.png"), UriKind.Absolute)) |
37 | personImage.Source = Nothing |
39 | If Kinect Is Nothing = False Then |
40 | If Kinect.IsRunning = True Then |
42 | Kinect.AudioSource.Stop() |
43 | engine.RecognizeAsyncStop() |
55 | Kinect = KinectSensor.KinectSensors(0) |
58 | Dim audio As KinectAudioSource = Kinect.AudioSource |
60 | Using s As Stream = audio.Start() |
61 | engine.SetInputToDefaultAudioDevice() |
62 | engine.RecognizeAsync(RecognizeMode.Multiple) |
69 | AddHandler CompositionTarget.Rendering, Sub(renderSender As Object, renderArgs As EventArgs) |
70 | Using colorFrame As ColorImageFrame = Kinect.ColorStream.OpenNextFrame(100) |
71 | Using depthFrame As DepthImageFrame = Kinect.DepthStream.OpenNextFrame(100) |
72 | RenderScreen(colorFrame, depthFrame) |
RGBカメラ、距離カメラ、スケルトンを有効にし、各画像データを初期化する処理
DepthStream.Enableメソッドで距離カメラの表示サイズを640×480、1秒あたり30フレームで、動作を開始します。同様にColorStream.Enableメソッドで、RGBカメラを、RGBフォーマット、表示サイズ640×480、1秒あたり30フレームで、動作を開始します。プレイヤーおよびスケルトンの認識も開始します。
骨格生成を行う場合は、深度センサー(距離カメラ)を有効にする必要があります。Dim depthStream As DepthImageStream = Kinect.DepthStreamで、距離カメラの処理を行うクラスのインスタンスを取得します。Short配列変数myDepthPixelDataに距離カメラのピクセルデータのバイト長分の配列を作成します。640×480の表示サイズの場合は、myDepthPixelData=New Short(307200-1){}分の配列を確保することになります。-1しているのは配列のインデックスが0から始まるためです。
バイト配列変数myColorPixelDataにRGBカメラのピクセルデータのバイト長分の配列を作成します。
WriteableBitmapクラス型のRoom_Bitmapを下記の書式で初期化します。
New WriteableBitmap(距離カメラのフレーム幅,距離カメラのフレーム高さ, ビットマップの水平値, ビットマップの垂直値,ビットマップのピクセルフォーマット,ビットマップのビットマップパレット)
「ビットマップのピクセルフォーマット」には、PixelFormats.Bgra32を指定します。Bgra32 は、bits per pixel(BPP)が 32 の sRGB 形式です。各カラー チャネル(青、緑、赤、およびアルファ)に割り当てられるbits per pixel(BPP)は 8 です。ここに、Brg32を指定すると背景が透明化されず真っ黒になってしまいますので注意してください。
ここは、Room_Bitmap = New WriteableBitmap(640, 480, 96, 96, PixelFormats.Bgra32, Nothing)
と指定しても同じです。
同様に、Human1_bitmap、Human2_bitmapについてもWriteableBitmapの書式で初期化しておきます。
IntRect32構造体のmyScreenImageRectを下記の書式で初期化し、領域を指定します。
New IntRect32(新しいInt32Rect のインスタンスの X座標,新しいInt32Rect のインスタンスの Y座標, 四角形の幅を指定した新しい Int32Rect のインスタンスの幅, 四角形の高さを指定した新しい Int32Rect のインスタンスの高さ)
「新しいInt32Rect のインスタンスの X座標」と「新しいInt32Rect のインスタンスの Y座標」には0を指定します。「四角形の幅を指定した新しい Int32Rect のインスタンスの幅」には、Human2_bitmapオブジェクトの幅を指定し、「Int32Rect のインスタンスの高さ」には、Human2_bitmapオブジェクトの高さを指定します。
結局は、
myScreenImageRect = New Int32Rect(0, 0, 640, 480)
と指定しても同じことです。
01 | Private Sub init_kinect() |
03 | If Kinect Is Nothing Then |
06 | Kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30) |
07 | Kinect.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30) |
08 | Kinect.SkeletonStream.Enable() |
10 | Dim depthStream = Kinect.DepthStream |
11 | myDepthPixelData = New Short(Kinect.DepthStream.FramePixelDataLength - 1) {} |
12 | myColorPixelData = New Byte(Kinect.ColorStream.FramePixelDataLength - 1) {} |
14 | Room_Bitmap = New WriteableBitmap(depthStream.FrameWidth, depthStream.FrameHeight, 96, 96, PixelFormats.Bgra32, Nothing) |
16 | Human1_bitmap = New WriteableBitmap(depthStream.FrameWidth, depthStream.FrameHeight, 96, 96, PixelFormats.Bgra32, Nothing) |
17 | Human2_bitmap = New WriteableBitmap(depthStream.FrameWidth, depthStream.FrameHeight, 96, 96, PixelFormats.Bgra32, Nothing) |
19 | myScreenImageRect = New Int32Rect(0, 0, CInt(Math.Ceiling(Human2_bitmap.Width)), CInt(Math.Ceiling(Human2_bitmap.Height))) |
21 | MessageBox.Show(ex.Message) |
RGBカメラ、距離カメラ、スケルトンのフレームが更新されたことを通知するイベント
Skeletonクラス型の配列変数allSkeletonsを宣言します。
フレームごとのスケルトンデータを表すクラスである、SkeletonFrame型のskeletonFrameData変数を宣言し、e.OpenSkeletonFrameメソッドで、新しいフレームのスケルトンの情報を取得します。
新しいSkeletonの配列オブジェクトallSkeletonsを、スケルトン配列の長さを取得するSkeletonArrayLengthプロパティで初期化した新しいインスタンスを作成します。
CopySkeletonDataToメソッドで、現在のSkeletonFrameDataにあるスケルトンデータを、指定した配列(allSkeletons)にコピーします。CopySkeletonDataToメソッドで取得されるデータはプレイヤー分取得されるため、それぞれのトラッキング状態を確認します。スケルトンデータを持つallSkeletons配列変数内を、skeletonResult変数で反復処理しながら、全ての関節の位置がトラッキングされた状態にある、シーケンスの最初の要素を取得し、Skeletonクラス型の反復変数skeletonResultに格納していきます。格納された要素をSkeleton型のmyFirstSkeletonで参照します。
分身を表示されるpersonImageコントロールと、トラッキングされた状態にある最初の右手のジョイント情報を引数に、ScalePositionプロシージャを実行します。また、スケルトンの位置を取得するGetCameraPointプロシージャも実行します。
01 | Private Sub Kinect_AllFramesReady(ByVal sender As Object, ByVal e As AllFramesReadyEventArgs) |
02 | Dim allSkeletons As Skeleton() |
03 | Using skeletonFrameData As SkeletonFrame = e.OpenSkeletonFrame() |
04 | If skeletonFrameData Is Nothing = True Then |
08 | allSkeletons = New Skeleton(skeletonFrameData.SkeletonArrayLength - 1) {} |
09 | skeletonFrameData.CopySkeletonDataTo(allSkeletons) |
11 | Dim myFirstSkeleton As Skeleton = (From skeletonResult In allSkeletons Where skeletonResult.TrackingState = SkeletonTrackingState.Tracked Select skeletonResult).FirstOrDefault() |
12 | If myFirstSkeleton Is Nothing Then |
16 | ScalePosition(personImage, myFirstSkeleton.Joints(JointType.HandRight)) |
17 | GetCameraPoint(myFirstSkeleton, e) |