PR

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のWebサイトにログインすることでさまざまな限定特典を入手できるようになります。

Think IT会員サービスの概要とメリットをチェック

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