Kinectで手の動きに合わせて波紋を発生させるサンプル

2012年7月25日(水)
薬師寺 国安

次に、ソリューションエクスプローラー内のMainWindow.xamlを展開して表示される、MainWindow.xaml.vbをダブルクリックしてリスト4のコードを記述します。

ロジックコードを記述する

リスト4 (MainWindow.xaml.vb)

1Option Strict On
2Imports Microsoft.Kinect

Soryboardを使用するのに必要なクラスの含まれる、System.Windows.Media.Animation名前空間をインポートします。

1Imports System.Windows.Media.Animation
2 
3Imports Coding4Fun.Kinect.Wpf
4Class MainWindow

Kinect センサーを表すメンバ変数newSensorを宣言します。

1Dim newSensor As KinectSensor
2 
3Dim _closing As Boolean = False

新しいStoryboardクラス用のインスタンス、strメンバ変数を宣言します。

1Dim str As New Storyboard

Skeletonクラス型のメンバ変数firstSkeletonを宣言します。

1Dim firstSkeleton As Skeleton

ウィンドウが読み込まれた時の処理

KinectSensorChooser1.KinectSensorChangedイベントで、Kinectセンサーが接続されているかどうかを確認します。古いセンサーが動作している時は停止させ、新しいセンサーを取得します。
パラメータのスムージング変換を行う、新しいTransformSmoothParameters型のインスタンスmyParamオブジェクトを作成します。各プロパティの値を設定します。
Smoothingでは、スムージングの量を設定します。値は0から1.0の範囲で、規定値は0.5です。値が大きいほど平滑化されますが、処理時間は増加します。
Correctionでは、平滑化の緩急を付けます。値は0から1.0の範囲で、規定値は0.5です。1.0に近いほど処理時間は早くなります。
Predictionでは、スムーズに動作させるため予測されたフレームの数を設定します。
JitterRadiusでは、ジッタ低減の半径(メートル)を設定します。デフォルトは」0.05(5cm)です。
MaxDeviationRadiusでは、フィルタされた値と生データとの誤差の許容最大値を設定します。単位はメートルで、規定値は0.04(4cm)です。
上記のプロパティを設定したmyParamオブジェクトを、SkeletonStreamのEnableメソッドに指定し、スケルトンの機能を有効にします。
ColorStream.EnableメソッドでKinectセンサーのRGBカメラの機能を有効にします。ColorImageFormat.RgbResolution640x480Fps30列挙体で「RGBフォーマットで、解像度は640×480、フレームレートは毎秒30フレーム」と設定します。
DepthStream.Enableメソッドで距離カメラの機能を有効にします。「解像度は 640 × 480、フレーム レートは 毎秒30フレーム」と設定します。
RGBカメラ、距離カメラ、骨格のフレーム更新イベントであるAllFramesReadyにsensor_AllFramesReadyイベントハンドラを指定します。
Kinectセンサーを開始します。例外が発生した場合はエラーを表示して処理を抜けます。

01Private Sub MainWindow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
02  AddHandler KinectSensorChooser1.KinectSensorChanged, Sub(kinectSender As Object, kinectArgs As DependencyPropertyChangedEventArgs)
03    Dim oldSensor As KinectSensor = DirectCast(kinectArgs.OldValue, KinectSensor)
04    If oldSensor Is Nothing = False Then
05      StopKinect()
06    End If
07    newSensor = DirectCast(kinectArgs.NewValue, KinectSensor)
08    If newSensor Is Nothing = True Then
09      Return
10    End If
11    Dim myParam As New TransformSmoothParameters
12    With myParam
13      .Smoothing = 0.75F
14      .Correction = 0.0F
15      .Prediction = 0.0F
16      .JitterRadius = 0.05F
17      .MaxDeviationRadius = 0.4F
18    End With
19      newSensor.SkeletonStream.Enable(myParam)
20      newSensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30)
21      newSensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30)
22 
23      AddHandler newSensor.AllFramesReady, AddressOf sensor_AllFramesReady
24 
25        Try
26          newSensor.Start()
27        Catch ex As Exception
28          MessageBox.Show(ex.Message)
29          Exit Sub
30        End Try
31            End Sub
32End Sub

RGBカメラ、距離カメラ、骨格のフレーム更新イベント

SourceImageという名前を持つImageコントロールのSourceプロパティに、e.OpenColorImageFrameメソッドで、新しいフレームのRGBカメラの情報を取得し、Coding4Fun.Kinect.Wpfの拡張メソッドであるToBitmapSourceで、myColorImageをBitmapSourceに変換して指定します。
これで、Imageコントロール内にカラー画像(実写)が表示されます。このメソッドで取得するColorImageFrameは、Usingで括るか、明示的にDisposeする必要があります。
フレームごとのスケルトンデータを表すクラスである、SkeletonFrame型のskeletonFrameData変数を宣言し、e.OpenSkeletonFrameメソッドで、新しいフレームのスケルトンの情報を取得します。
スケルトン配列の長さを取得するSkeletonArrayLengthプロパティで初期化された、新しいSkeletonクラス型の配列変数allSkeletonsを宣言します。
CopySkeletonDataToメソッドで、現在のSkeletonFrameDataにあるスケルトンデータを、指定した配列(allSkeletons)にコピーします。CopySkeletonDataToメソッドで取得されるデータはプレイヤー分取得されるため、それぞれのトラッキング状態を確認します。
Skeletonクラス用メンバ変数firstSkeletonで、スケルトンデータを持つallSkeletons配列変数内で、全ての関節の位置がトラッキングされた状態にある、先頭の要素を取得していきます。
このメソッドで取得されるSkeletonFrameは、Usingで括るか、明示的にDisposeする必要があります。
スケルトンの位置を取得するGetCameraPointプロシージャも実行します。その際、引数として全ての関節の位置がトラッキングされた状態にある、先頭の要素と、RGBカメラ、距離カメラ、スケルトンのデータを更新したイベントで渡されるAllFramesReadyEventArgsを渡します。

01Private Sub sensor_AllFramesReady(ByVal sender As Object, ByVal e As AllFramesReadyEventArgs)
02  Using myColorImage As ColorImageFrame = e.OpenColorImageFrame
03    SourceImage.Source =  myColorImage.ToBitmapSource
04  End Using
05 
06  If _closing Then
07    Return
08  End If
09Try 
10  Using skeletonFrameData As SkeletonFrame = e.OpenSkeletonFrame()
11    Dim allSkeletons As Skeleton() = New Skeleton(skeletonFrameData.SkeletonArrayLength - 1) {}
12     
13    skeletonFrameData.CopySkeletonDataTo(allSkeletons)
14    firstSkeleton = (From s In allSkeletons Where s.TrackingState = SkeletonTrackingState.Tracked Select s).FirstOrDefault()
15  End Using
16  If firstSkeleton Is Nothing Then
17    Return
18  End If
19  GetCameraPoint(firstSkeleton, e)
20  Catch 
21    Exit Sub 
22  End Try 
23End Sub

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

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

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

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

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

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

MapToSkeletonPointメソッドで、距離カメラの座標に対応する、スケルトンの座標を取得します。書式は下記の通りです。

DepthImageFrame.MapToSkeletonPoint(距離カメラのX座標,距離カメラのY座標)

距離カメラの座標に対応する、スケルトンの座標を取得して、プレイヤーの右手と距離カメラとの距離を取得し、変数rightZに格納します。同じく左手の距離を取得してleftZに格納します。
右手のKinectセンサーからの距離が、センサーに30cm近い左手の距離よりも遠くに位置する場合、つまり、右手が左手よりも距離カメラに近かった場合に、RippleEffectGoプロシージャを実行します。
その際、引数としてRGBカメラの座標に変換された、右手のXとY座標の位置を渡します。このメソッドで取得するDepthImageFrameは、Usingで括るか、明示的にDisposeする必要があります。

01Private Sub GetCameraPoint(ByVal firstSkeleton As Skeleton, ByVal e As AllFramesReadyEventArgs)
02  Using depth As DepthImageFrame = e.OpenDepthImageFrame()
03    If depth Is Nothing OrElse kinectSensorChooser1.Kinect Is Nothing Then
04      Return
05    End If
06    Dim leftDepthPoint As DepthImagePoint = depth.MapFromSkeletonPoint(firstSkeleton.Joints(JointType.HandLeft).Position)
07    Dim rightDepthPoint As DepthImagePoint = depth.MapFromSkeletonPoint(firstSkeleton.Joints(JointType.HandRight).Position)
08    Dim leftColorPoint As ColorImagePoint = depth.MapToColorImagePoint(leftDepthPoint.X, leftDepthPoint.Y, ColorImageFormat.RgbResolution640x480Fps30)
09    Dim rightColorPoint As ColorImagePoint = depth.MapToColorImagePoint(rightDepthPoint.X, rightDepthPoint.Y, ColorImageFormat.RgbResolution640x480Fps30)
10     
11    Dim rightZ = depth.MapToSkeletonPoint(rightDepthPoint.X, rightDepthPoint.Y).Z
12    Dim leftZ = depth.MapToSkeletonPoint(leftDepthPoint.X, leftDepthPoint.Y).Z
13     
14    Dim rightColorPointX = rightColorPoint.X
15    Dim rightColorPointY = rightColorPoint.Y
16 
17    If rightZ > leftZ - 0.3 Then
18      RipplEffectGo(rightColorPointX, rightColorPointY)
19    Else
20      str = Nothing
21    End If
22  End Using
23End Sub

RippleEffect処理を実行する処理

FindResourceメソッドでRippleEffectStoryboardという名前のリソースを見つけ、Storyboardにキャストします。Point構造体型の変数myPositionを宣言し、myPositionのXとY座標に、RGBカメラの座標に変換された、右手のXとY座標の位置を、NameがSourceImageのImageコントロールの、WidthとHeightで除算した値を指定します。
ImageコントロールのWidthとHeightの値で除算しているのは、RippleEffectの効果が、640×480の範囲内で発生させるためです。Point構造体は、2次元空間でのxとy座標のペアを表します。
RippleEffectのCenterプロパティにmyPositionオブジェクトを指定します。RepeatBehaviorプロパティにForeverを指定して、永遠にRippleEffectを繰り返すよう指定し、BeginメソッドでRippleEffectのストーリーボードを実行します。これで、右手でタップした位置を中心に波紋が広がります。

01  Private Sub RipplEffectGo(rightColorPositionX As Integer, rightColorPositionY As Integer)
02    str = DirectCast(FindResource("RippleEffectStoryboard"), Storyboard)
03    Dim myPosition As Point
04    myPosition.X = righColortPositionX / 640
05    myPosition.Y = rightColorPositionY / 480
06    myRippleEffect.Center = myPosition
07    str.RepeatBehavior = RepeatBehavior.Forever
08    str.Stop()
09    str.Begin()
10  End Sub
11End Class

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

Kinectセンサーの動作を停止するStopKinectプロシージャを実行します。

1Private Sub MainWindow_Closing(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles Me.Closing
2  StopKinect()
3End Sub

Kinectセンサーの動作を停止する処理

Kinectセンサーが動作している場合は、AllFramesReadyイベントを解除し、Kinectセンサーを停止します。最後にリソースを解放します。

01  Private Sub StopKinect()
02    If newSensor IsNot Nothing Then
03      If newSensor.IsRunning = True Then
04        RemoveHandler newSensor.AllFramesReady, AddressOf sensor_AllFramesReady
05        newSensor.Stop()
06        newSensor.Dispose()
07      End If
08    End If
09  End Sub
10End Class

今回のサンプルは以上で終了です。

薬師寺国安事務所

薬師寺国安事務所代表。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メルマガ会員のサービス内容を見る

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