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

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

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

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

リスト4 (MainWindow.xaml.vb)

Option Strict On
Imports Microsoft.Kinect

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

Imports System.Windows.Media.Animation

Imports Coding4Fun.Kinect.Wpf
Class MainWindow

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

Dim newSensor As KinectSensor

Dim _closing As Boolean = False

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

  Dim str As New Storyboard

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

  Dim 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センサーを開始します。例外が発生した場合はエラーを表示して処理を抜けます。

  Private Sub MainWindow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
    AddHandler KinectSensorChooser1.KinectSensorChanged, Sub(kinectSender As Object, kinectArgs As DependencyPropertyChangedEventArgs)
      Dim oldSensor As KinectSensor = DirectCast(kinectArgs.OldValue, KinectSensor)
      If oldSensor Is Nothing = False Then
        StopKinect()
      End If
      newSensor = DirectCast(kinectArgs.NewValue, KinectSensor)
      If newSensor Is Nothing = True Then
        Return
      End If
      Dim myParam As New TransformSmoothParameters
      With myParam
        .Smoothing = 0.75F 
        .Correction = 0.0F 
        .Prediction = 0.0F 
        .JitterRadius = 0.05F
        .MaxDeviationRadius = 0.4F
      End With
        newSensor.SkeletonStream.Enable(myParam)
        newSensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30)
        newSensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30)
  
        AddHandler newSensor.AllFramesReady, AddressOf sensor_AllFramesReady
 
          Try
            newSensor.Start()
          Catch ex As Exception
            MessageBox.Show(ex.Message)
            Exit Sub
          End Try
              End Sub
  End 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を渡します。

  Private Sub sensor_AllFramesReady(ByVal sender As Object, ByVal e As AllFramesReadyEventArgs)
    Using myColorImage As ColorImageFrame = e.OpenColorImageFrame
      SourceImage.Source =  myColorImage.ToBitmapSource
    End Using
 
    If _closing Then
      Return
    End If
  Try  
    Using skeletonFrameData As SkeletonFrame = e.OpenSkeletonFrame()
      Dim allSkeletons As Skeleton() = New Skeleton(skeletonFrameData.SkeletonArrayLength - 1) {}
      
      skeletonFrameData.CopySkeletonDataTo(allSkeletons)
      firstSkeleton = (From s In allSkeletons Where s.TrackingState = SkeletonTrackingState.Tracked Select s).FirstOrDefault()
    End Using
    If firstSkeleton Is Nothing Then
      Return
    End If
    GetCameraPoint(firstSkeleton, e)
    Catch  
      Exit Sub  
    End Try  
  End 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する必要があります。

  Private Sub GetCameraPoint(ByVal firstSkeleton As Skeleton, ByVal e As AllFramesReadyEventArgs)
    Using depth As DepthImageFrame = e.OpenDepthImageFrame()
      If depth Is Nothing OrElse kinectSensorChooser1.Kinect Is Nothing Then
        Return
      End If
      Dim leftDepthPoint As DepthImagePoint = depth.MapFromSkeletonPoint(firstSkeleton.Joints(JointType.HandLeft).Position)
      Dim rightDepthPoint As DepthImagePoint = depth.MapFromSkeletonPoint(firstSkeleton.Joints(JointType.HandRight).Position)
      Dim leftColorPoint As ColorImagePoint = depth.MapToColorImagePoint(leftDepthPoint.X, leftDepthPoint.Y, ColorImageFormat.RgbResolution640x480Fps30)
      Dim rightColorPoint As ColorImagePoint = depth.MapToColorImagePoint(rightDepthPoint.X, rightDepthPoint.Y, ColorImageFormat.RgbResolution640x480Fps30)
      
      Dim rightZ = depth.MapToSkeletonPoint(rightDepthPoint.X, rightDepthPoint.Y).Z
      Dim leftZ = depth.MapToSkeletonPoint(leftDepthPoint.X, leftDepthPoint.Y).Z
      
      Dim rightColorPointX = rightColorPoint.X
      Dim rightColorPointY = rightColorPoint.Y
 
      If rightZ > leftZ - 0.3 Then
        RipplEffectGo(rightColorPointX, rightColorPointY)
      Else
        str = Nothing
      End If
    End Using
  End 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のストーリーボードを実行します。これで、右手でタップした位置を中心に波紋が広がります。

  Private Sub RipplEffectGo(rightColorPositionX As Integer, rightColorPositionY As Integer)
    str = DirectCast(FindResource("RippleEffectStoryboard"), Storyboard)
    Dim myPosition As Point
    myPosition.X = righColortPositionX / 640
    myPosition.Y = rightColorPositionY / 480
    myRippleEffect.Center = myPosition
    str.RepeatBehavior = RepeatBehavior.Forever
    str.Stop()
    str.Begin()
  End Sub
End Class

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

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

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

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

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

  Private Sub StopKinect()
    If newSensor IsNot Nothing Then
      If newSensor.IsRunning = True Then
        RemoveHandler newSensor.AllFramesReady, AddressOf sensor_AllFramesReady
        newSensor.Stop()
        newSensor.Dispose()
      End If
    End If
  End Sub
End 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メルマガ会員のサービス内容を見る

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