声で選んだアイテムをプレイヤーの身体に装着・連動させるKinectサンプル

2012年7月26日(木)
薬師寺 国安

前ページからの続きです。

RGBカメラ、距離カメラ、スケルトンのフレームが更新されたことを通知するイベント

personImageという名前を持つImageコントロールのSourceプロパティに、e.OpenColorImageFrameメソッドで新しいフレームのRGBカメラの情報を取得します。
次に、Coding4Fun.Kinect.Wpfの拡張メソッドであるToBitmapSourceで、colorFrameをBitmapSourceに変換して指定します。
これで、personImage内にカラー画像(実写)が表示されます。

フレームごとのスケルトンデータを表すクラスである、SkeletonFrame型のskeletonFrameData変数を宣言し、e.OpenSkeletonFrameメソッドで、新しいフレームのスケルトンの情報を取得します。
スケルトン配列の長さを取得するSkeletonArrayLengthプロパティで初期化された、新しいSkeletonクラス型の配列変数allSkeletonsを宣言します。
CopySkeletonDataToメソッドで、現在のSkeletonFrameDataにあるスケルトンデータを、指定した配列(allSkeletons)にコピーします。
CopySkeletonDataToメソッドで取得されるデータはプレイヤー分取得されるため、それぞれのトラッキング状態を確認します。
Skeletonクラス用オブジェクト変数firstSkeletonで、スケルトンデータを持つallSkeletons配列変数内で、全ての関節の位置がトラッキングされた状態にある、先頭の要素を取得し、Skeletonクラス型のメンバ変数firstSkeletonに格納していきます。
このメソッドで取得されるColorImageFrameおよびSkeletonFrameは、Usingで括るか、明示的にDisposeする必要があります。

faceImageとトラッキングされた状態にある、先頭の頭のジョイント情報と、rightHandImageとトラッキングされた状態にある、先頭の右手のジョイント情報を引数にScalePositionプロシージャを実行します。
また、スケルトンの位置を取得するGetCameraPointプロシージャも実行します。

01  Private Sub kinect_AllFramesReady(sender As Object, e As AllFramesReadyEventArgs)
02    Try
03      kinect = TryCast(sender, KinectSensor)
04      If kinect Is Nothing Then
05        Return
06      End If
07      Using colorFrame = e.OpenColorImageFrame()
08        personImage.Source = colorFrame.ToBitmapSource
09      End Using
10    Catch ex As Exception
11      MessageBox.Show(ex.Message)
12   Exit Sub 
13    End Try
14  Try
15    If _closing = True Then
16      Return
17    End If
18    Using skeletonFrameData As SkeletonFrame = e.OpenSkeletonFrame()
19      Dim allSkeletons As Skeleton() = New Skeleton(skeletonFrameData.SkeletonArrayLength - 1) {}
20      skeletonFrameData.CopySkeletonDataTo(allSkeletons)
21      firstSkeleton = (From s In allSkeletons Where s.TrackingState = SkeletonTrackingState.Tracked Select s).FirstOrDefault()
22    End Using
23  
24    If firstSkeleton Is Nothing Then
25      Return
26    End If
27    ScalePosition(faceImage, firstSkeleton.Joints(JointType.Head))
28    ScalePosition(rightHandImage, firstSkeleton.Joints(JointType.HandRight))
29    GetCameraPoint(firstSkeleton, e) 
30  Catch 
31    Exit Sub 
32 End Try 
33End Sub

スケルトンの位置を取得する処理

Kinectセンサーの距離カメラから、距離カメラのフレームデータを表すDepthImageFrameクラス型のdepth変数を宣言し、OpenDepthImageFrameメソッドで、距離カメラのフレームデータを取得します。
距離データのピクセル座標、および距離、プレイヤーIDを表す、DepthImagePoint構造体のheadDepthPoint変数を宣言し、MapFromSkeletonPointメソッドで、スケルトンの座標を、距離カメラの座標に変換します。
この場合、頭の位置を距離カメラの座標に変換します。同じく右手の位置も距離カメラの座標に変換します。
MapFromSkeletonPointメソッドでの書式は下記です。

DepthImagePoint. MapFromSkeletonPoint(変換するスケルトンの座標)

次に、頭の位置を取得します。RGBカメラのX-Y座標データを表す、ColorImagePoint構造体の変数headColorPointを宣言し、MapToColorImagePointメソッドで、距離カメラの座標をRGBカメラの座標に変換します。
同様に、右手の位置をrightColorPointで取得します。書式は下記です。

DepthImageFrame.MapToColorImagePoint(距離カメラのX座標,距離カメラのY座標,RGBカメラのフォーマット)

この場合、頭の距離カメラのX座標と、Y座標、RGBフォーマットで解像度が640×480、フレームレートは毎秒30フレームに変換しています。同様に右手に対しても同じ処理を行います。
距離カメラのデータをRGBカメラ(実写)のデータにマップします。

faceImageと頭のRGBカメラの座標、rightHandImageと右手のRGBカメラの座標を引数に、手の動きに合わせて画像の位置が変化するCameraPositionプロシージャを実行します。

01Private Sub GetCameraPoint(ByVal first As Skeleton, ByVal e As AllFramesReadyEventArgs)
02  Using depth As DepthImageFrame = e.OpenDepthImageFrame()
03    If depth Is Nothing = True Then
04      Return
05    End If
06    Dim headDepthPoint As DepthImagePoint = depth.MapFromSkeletonPoint(first.Joints(JointType.Head).Position)
07    Dim rightDepthPoint As DepthImagePoint = depth.MapFromSkeletonPoint(first.Joints(JointType.HandRight).Position)
08 
09    Dim headColorPoint As ColorImagePoint = depth.MapToColorImagePoint(headDepthPoint.X, headDepthPoint.Y, ColorImageFormat.RgbResolution640x480Fps30)
10    Dim rightColorPoint As ColorImagePoint = depth.MapToColorImagePoint(rightDepthPoint.X, rightDepthPoint.Y, ColorImageFormat.RgbResolution640x480Fps30)
11 
12    CameraPosition(faceImage, headColorPoint)
13    CameraPosition(rightHandImage, rightColorPoint)
14  End Using
15End Sub

頭や右手の動きに合わせて画像の位置が変化する処理

Canvas.SetLeftとSetTopプロパティの書式は下記です。

Canvas.SetLeft(プロパティ値の書き込み対象の要素,指定した要素のCanvas.Left属性を設定)
Canvas.SetTop(プロパティ値の書き込み対象の要素,指定した要素のCanvas.Top属性を設定)

1Private Sub CameraPosition(ByVal element As FrameworkElement, ByVal point As ColorImagePoint)
2  Canvas.SetLeft(element, point.X - (CLng(element.Width) / 2))
3  Canvas.SetTop(element, point.Y - (CLng(element.Height) / 2))
4End Sub

画像が頭や右手の動きに反応する処理

Coding4Fun Kinect ToolkitのScaleToメソッドで、指定した幅と高さにJointオブジェクトの位置をスケーリングします。書式は下記です。

Joint.ScaleTo(width As Integer, height As Integer)

Canvas.SetLeftとSetTopプロパティの書式は下記です(1920×1080は筆者のパソコンの解像度)。

Canvas.SetLeft(プロパティ値の書き込み対象の要素,指定した要素のCanvas.Left属性を設定)
Canvas.SetTop(プロパティ値の書き込み対象の要素,指定した要素のCanvas.Top属性を設定)

1Private Sub ScalePosition(ByVal element As FrameworkElement, ByVal joint As Joint)
2  Dim scaledJoint As Joint = joint.ScaleTo(1920, 1080)
3  Canvas.SetLeft(element, scaledJoint.Position.X)
4  Canvas.SetTop(element, scaledJoint.Position.Y)
5End Sub

「お面」の画像が表示されているListBox内の項目が選択された時の処理

ListBox1より選択された項目を、FancyDressInfoクラスにキャストして、_widthと_heightプロパティの値を取得して変数に格納しておきます。
BitmapImageであるmySource変数には、FancyDressInfoクラスのnameプロパティの値を格納します。

faceImageコントロールのWidthとHeightプロパティに、取得した幅と高さの半分の値を指定します(半分にしているのは単に元の画像が大きいという理由です)。

Sourceプロパティには変数mySourceオブジェクトを指定します。

1Private Sub ListBox1_SelectionChanged(sender As Object, e As System.Windows.Controls.SelectionChangedEventArgs) Handles ListBox1.SelectionChanged
2  Dim myWidth = CInt(DirectCast(ListBox1.SelectedItem, FancyDressInfo)._width)
3  Dim myHeight = CInt(DirectCast(ListBox1.SelectedItem, FancyDressInfo)._height)
4  Dim mySource = New BitmapImage(New Uri(DirectCast(ListBox1.SelectedItem, FancyDressInfo).name, UriKind.Relative))
5  faceImage.Width = myWidth / 2
6  faceImage.Height = myHeight / 2
7  faceImage.Source = mySource
8End Sub

「金棒」や「扇子」の画像が表示されているListBox内の項目が選択された時の処理

ListBox1の処理と同じです。「金棒」や「扇子」は元の画像が大きくありませんので、そのままのサイズを指定しています。

1Private Sub ListBox2_SelectionChanged(sender As Object, e As System.Windows.Controls.SelectionChangedEventArgs) Handles ListBox2.SelectionChanged
2  Dim myWidth = CInt(DirectCast(ListBox2.SelectedItem, FancyDressInfo)._width)
3  Dim myHeight = CInt(DirectCast(ListBox2.SelectedItem, FancyDressInfo)._height)
4  Dim mySource = New BitmapImage(New Uri(DirectCast(ListBox2.SelectedItem, FancyDressInfo).name, UriKind.Relative))
5  rightHandImage.Width = myWidth
6  rightHandImage.Height = myHeight
7  rightHandImage.Source = mySource
8End Sub

ウィンドウが閉じられる時の処理

Kinectセンサーが動作している時は、イベントハンドラの登録を解除し、Kinectセンサーの動作を停止します。
音声認識も停止します。

最後にDisposeメソッドでリソースを解放します。

01  Private Sub MainWindow_Closing(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles Me.Closing
02    If kinect Is Nothing = False Then
03      If kinect.IsRunning = True Then
04        RemoveHandler kinect.AllFramesReady, AddressOf kinect_AllFramesReady
05        kinect.Stop()
06        engine.RecognizeAsyncStop()
07        kinect.Dispose()
08      End If
09    End If
10  End Sub
11End Class

以上で今回のサンプルは終了です。手軽にキャラクターになりきることができたりと、Kinectの本領を発揮するサンプルなので、ぜひ試してみてください。

  • 声でアイテムを選んで身体に装着・連動させるKinectサンプル

薬師寺国安事務所

薬師寺国安事務所代表。Visual Basic プログラミングと、マイクロソフト系の技術をテーマとした、書籍や記事の執筆を行う。
1950年生まれ。事務系のサラリーマンだった40歳から趣味でプログラミングを始め、1996年より独学でActiveXに取り組む。1997年に薬師寺聖とコラボレーション・ユニット PROJECT KySS を結成。2003年よりフリーになり、PROJECT KySS の活動に本格的に参加、.NETやRIAに関する書籍や記事を多数執筆する傍ら、受託案件のプログラミングも手掛ける。Windows Phoneアプリ開発を経て、現在はWindows ストア アプリを多数公開中

Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。Microsoft MVP for Development Platforms - Windows Phone Development(Oct 2012-Sep 2013)。Microsoft MVP for Development Platforms - Client Development(Oct 2013-Sep 2014)。Microsoft MVP for Development Platforms-Windows Platform Development (Oct 2014-Sep 2015)。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています