Kinectを使って、自分の手のひらに小さな分身を出現させてみる
ロジックコードを記述する
リスト2 (MainWindow.xaml.vb)
Option Strict On Imports Microsoft.Kinect Imports System.Windows.Media.Imaging Imports Microsoft.Speech.AudioFormat Imports Microsoft.Speech.Recognition Imports System.IO
Assembly.GetExecutingAssembly.GetName.CodeBaseでアプリケーションパス(この場合bin\Debugディレクトリ内の実行ファイルパス)を取得する必要があるため、Assemblyクラスの含まれる、System.Reflection名前空間をインポートしておきます。最終的に取得したbin\Debugフォルダ内には、右手のひらに乗るdummy.pngという名前の画像が保存されます。
Imports System.Reflection Imports Coding4Fun.Kinect.Wpf Class MainWindow
一個のKinectセンサーを表すメンバ変数Kinectを宣言します。
Dim Kinect As KinectSensor
整数四角形の幅、高さ、および位置を表すInt32Rect構造体のメンバ変数myScreenImageRectを宣言します。領域の指定に使用されます。
Dim myScreenImageRect As Int32Rect
Short型の配列メンバ変数myDepthPixelDataとByte型の配列メンバ変数myColorPixelDataを宣言しておきます。
※深度情報は、1ピクセルあたり2バイトのshort型。画像情報はフルカラーなので1ピクセルあたり4バイトのbyte型です。
Dim myDepthPixelData As Short() Dim myColorPixelData As Byte()
音声認識サービスを実行するためのアクセス権を提供するクラスである、SpeechRecognitionEngineクラス用メンバ変数engineを宣言します。
Dim engine As SpeechRecognitionEngine
Imageクラス型のmyImageメンバ変数を宣言します。
Dim myImage As Image
深度を指定する定数メンバ変数targetDepthを宣言し、2000(2m)で初期化しています。そのため、Kinectセンサーより2m離れてお試しください。
Const targetDepth As Double=2000
WriteableBitmapクラス型のHuman1_bitmap、Human2_bitmap、Room_Bitmapプロパティを定義しておきます。
Property Human1_bitmap As WriteableBitmap Property Human2_bitmap As WriteableBitmap 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自身のインスタンスを指定します。この処理を行わないとプレイヤーが表示されませんので注意してください。
Private Sub MainWindow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded If KinectSensor.KinectSensors.Count = 0 Then MessageBox.Show("Kinectが接続されておりません。") Exit Sub End If Dim sentence As Choices = New Choices With sentence .Add("わかれる") .Add("くっつく") .Add("おわり") End With Dim builder As GrammarBuilder = New GrammarBuilder builder.Append(sentence) Dim myGrammer As Grammar = New Grammar(builder) engine = New SpeechRecognitionEngine() engine.LoadGrammar(myGrammer) AddHandler engine.SpeechRecognized, Sub(speechSender As Object, speechArgs As SpeechRecognizedEventArgs) Try Dim confidence = speechArgs.Result.Confidence If confidence > 0.5 Then Select Case speechArgs.Result.Text Case "わかれる" Dim fullAppName As String = Assembly.GetExecutingAssembly.GetName.CodeBase Dim fullAppPath As String = Path.GetDirectoryName(fullAppName) Dim bmpFrame As BitmapFrame = BitmapFrame.Create(Human2_bitmap) Using stream As FileStream = New FileStream("dummy.png", FileMode.Create) Dim myPng As PngBitmapEncoder = New PngBitmapEncoder myPng.Frames.Add(bmpFrame) myPng.Save(stream) End Using AddHandler Kinect.AllFramesReady, AddressOf Kinect_AllFramesReady personImage.Source = New BitmapImage(New Uri(Path.Combine(fullAppPath, "dummy.png"), UriKind.Absolute)) Case "くっつく" personImage.Source = Nothing Case "おわり" If Kinect Is Nothing = False Then If Kinect.IsRunning = True Then Kinect.Stop() Kinect.AudioSource.Stop() engine.RecognizeAsyncStop() Kinect.Dispose() End If Environment.Exit(0) End If End Select End If Catch Exit Sub End Try End Sub Kinect = KinectSensor.KinectSensors(0) Kinect.Start() Dim audio As KinectAudioSource = Kinect.AudioSource Using s As Stream = audio.Start() engine.SetInputToDefaultAudioDevice() engine.RecognizeAsync(RecognizeMode.Multiple) End Using targetDepth = 2000 init_kinect() AddHandler CompositionTarget.Rendering, Sub(renderSender As Object, renderArgs As EventArgs) Using colorFrame As ColorImageFrame = Kinect.ColorStream.OpenNextFrame(100) Using depthFrame As DepthImageFrame = Kinect.DepthStream.OpenNextFrame(100) RenderScreen(colorFrame, depthFrame) End Using End Using End Sub DataContext = Me End Sub
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)
と指定しても同じことです。
Private Sub init_kinect() Try If Kinect Is Nothing Then Exit Sub End If Kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30) Kinect.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30) Kinect.SkeletonStream.Enable() Dim depthStream = Kinect.DepthStream myDepthPixelData = New Short(Kinect.DepthStream.FramePixelDataLength - 1) {} myColorPixelData = New Byte(Kinect.ColorStream.FramePixelDataLength - 1) {} Room_Bitmap = New WriteableBitmap(depthStream.FrameWidth, depthStream.FrameHeight, 96, 96, PixelFormats.Bgra32, Nothing) Human1_bitmap = New WriteableBitmap(depthStream.FrameWidth, depthStream.FrameHeight, 96, 96, PixelFormats.Bgra32, Nothing) Human2_bitmap = New WriteableBitmap(depthStream.FrameWidth, depthStream.FrameHeight, 96, 96, PixelFormats.Bgra32, Nothing) myScreenImageRect = New Int32Rect(0, 0, CInt(Math.Ceiling(Human2_bitmap.Width)), CInt(Math.Ceiling(Human2_bitmap.Height))) Catch ex As Exception MessageBox.Show(ex.Message) Exit Sub End Try End Sub
RGBカメラ、距離カメラ、スケルトンのフレームが更新されたことを通知するイベント
Skeletonクラス型の配列変数allSkeletonsを宣言します。
フレームごとのスケルトンデータを表すクラスである、SkeletonFrame型のskeletonFrameData変数を宣言し、e.OpenSkeletonFrameメソッドで、新しいフレームのスケルトンの情報を取得します。
新しいSkeletonの配列オブジェクトallSkeletonsを、スケルトン配列の長さを取得するSkeletonArrayLengthプロパティで初期化した新しいインスタンスを作成します。
CopySkeletonDataToメソッドで、現在のSkeletonFrameDataにあるスケルトンデータを、指定した配列(allSkeletons)にコピーします。CopySkeletonDataToメソッドで取得されるデータはプレイヤー分取得されるため、それぞれのトラッキング状態を確認します。スケルトンデータを持つallSkeletons配列変数内を、skeletonResult変数で反復処理しながら、全ての関節の位置がトラッキングされた状態にある、シーケンスの最初の要素を取得し、Skeletonクラス型の反復変数skeletonResultに格納していきます。格納された要素をSkeleton型のmyFirstSkeletonで参照します。
分身を表示されるpersonImageコントロールと、トラッキングされた状態にある最初の右手のジョイント情報を引数に、ScalePositionプロシージャを実行します。また、スケルトンの位置を取得するGetCameraPointプロシージャも実行します。
Private Sub Kinect_AllFramesReady(ByVal sender As Object, ByVal e As AllFramesReadyEventArgs) Dim allSkeletons As Skeleton() Using skeletonFrameData As SkeletonFrame = e.OpenSkeletonFrame() If skeletonFrameData Is Nothing = True Then Return End If allSkeletons = New Skeleton(skeletonFrameData.SkeletonArrayLength - 1) {} skeletonFrameData.CopySkeletonDataTo(allSkeletons) Dim myFirstSkeleton As Skeleton = (From skeletonResult In allSkeletons Where skeletonResult.TrackingState = SkeletonTrackingState.Tracked Select skeletonResult).FirstOrDefault() If myFirstSkeleton Is Nothing Then Return End If ScalePosition(personImage, myFirstSkeleton.Joints(JointType.HandRight)) GetCameraPoint(myFirstSkeleton, e) End Using End Sub
自分の分身を手のひらに乗せるKinectサンプル
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- Kinectを使って、画面上の赤い輪をくぐるサンプル
- 人体の連続した動作を音声でキャプチャするKinectのサンプルプログラム
- Kinectで結成したマイ・ダンスチームを、サンプルを見ながら実際の背景に合成してみよう
- Kinectの音声認識を使って、プレイヤーを分離、結合させるデモを試してみる
- プレイヤーの身体パーツを判別するKinectサンプル
- これであなたもダンスグループの一員!?Kinectで自分を分身させるプログラムを作る
- Kinectで距離カメラの値を取得して、指定した距離で人物が背景に溶け込むサンプル
- 人物を切り抜いて画面に表示するKinectサンプル
- Kinectを使って、顔の動きを認識して画面に表示する
- 人物特定に使える!?実際の映像で顔を認識するKinectプログラム