Kinect v2を使って、机の上のリンゴをつかんで移動してみる

2015年1月20日(火)
薬師寺 国安

新しいボディフレームの準備ができているときに発生するイベント処理

リスト4:MainWindow.xaml.vbの一部、リスト3の続き

01Private Sub myBodyFrameReader_FrameArrived(sender As Object, e As BodyFrameArrivedEventArgs)
02    Using myBodyFrame = e.FrameReference.AcquireFrame   (1)
03        If myBodyFrame Is Nothing = True Then
04            Return
05        End If
06        myBodyFrame.GetAndRefreshBodyData(myBodies)    (2)
07    End Using
08    CanvasBody.Children.Clear()   (3)
09    For Each body In myBodies
10        For Each joint In body.Joints   (4)
11            If joint.Value.TrackingState = TrackingState.Tracked Then
12                DrawEllipse(joint.Value, 10, Brushes.Transparent)    (5)
13                If joint.Value.JointType = JointType.HandRight Then
14                    If body.Joints(JointType.HandTipRight).TrackingState = TrackingState.Tracked Then
15                        myColorSpacePoint = myKinect.CoordinateMapper.MapCameraPointToColorSpace(body.Joints(JointType.HandTipRight).Position)    (6)
16                        myHandPositionX = CInt(myColorSpacePoint.X * 0.5)  (7)
17                        myHandPositionY = CInt(myColorSpacePoint.Y * 0.5)
18                        DrawHandState(body.Joints(JointType.HandRight), body.HandRightState, body.HandRightConfidence)    (8)
19                    End If
20                End If
21            End If
22        Next
23    Next
24End Sub
  1. e.FrameReference.AcquireFrameメソッドで、ボディフレームを取得し、myBodyFrameで参照します。myBodyFrameに何もなかった場合は、何も行いません。
  2. GetAndRefreshBodyDataメソッドで、更新されたボディデータを取得します。
  3. CanvasBody内を一度クリアしておきます。
  4. ボディデータの中を、反復処理を行いながら、繰り返し変数jointで関節の位置を反復処理しながら、関節位置を取得していきます。
  5. 関節が追跡されている場合は、関節の位置と、関節の位置を描く円の大きさ描く色(透明)を指定して、DrawEllipseを実行します。
  6. 右手が追跡されている場合は、CoordinateMapper.MapCameraPointToColorSpaceメソッドで、関節の位置を、カメラ空間から距離空間へのポイントにマップし、変数myColorSpacePointで参照します。
  7. 手のXとY軸の位置を取得して、メンバー変数、myHandPositionXとmyHandPositionYに格納します。
  8. 右手の位置、状態、そして追跡の精度を引数にして、DrawHandStateを実行します。

カラーフレーム到着時のイベント

リスト5:MainWindow.xaml.vbの一部、リスト4の続き

01Private Sub myColorFrameReader_FrameArrived(sender As Object, e As ColorFrameArrivedEventArgs)
02    Dim colorFramePorcessed As Boolean = False
03    Using myColorFrame As ColorFrame = e.FrameReference.AcquireFrame (1)
04        If myColorFrame Is Nothing = False Then   (2)
05            Dim myColorFrameDescription As FrameDescription = myColorFrame.FrameDescription   (3)
06            If myColorFrameDescription.Width = colorBitmap.PixelWidth AndAlso myColorFrameDescription.Height = colorBitmap.PixelHeight Then   (4)
07                If myColorFrame.RawColorImageFormat = ColorImageFormat.Bgra Then
08                    myColorFrame.CopyRawFrameDataToArray(ColorImagePixelData)    (5)
09                Else
10                    myColorFrame.CopyConvertedFrameDataToArray(ColorImagePixelData, ColorImageFormat.Bgra)    (6)
11                End If
12                colorFramePorcessed = True   (7)
13            End If
14        End If
15    End Using
16    If colorFramePorcessed = True Then
17        colorBitmap.WritePixels(New Int32Rect(0, 0, colorBitmap.PixelWidth, colorBitmap.PixelHeight), ColorImagePixelData, colorBitmap.PixelWidth * BytesPerPixel, 0)    (8)
18        roomImage.Source = colorBitmap    (9)
19    End If
20End Sub
  1. e.FrameReference.AcquireFrameメソッドでカラーフレームを取得し、myColorFrameで参照します。
  2. myColorFrameにデータがあった場合の処理です。
  3. KinectSensor から、イメージフレームのプロパティを取得し、変数myColorFrameDescriptionで参照します。
  4. 取得したプロパティのWidthとHeightが、それぞれcolorBitmapのPixelWidth、PixelHeightと同じであった場合の処理を行います。
  5. カラー フレーム データの形式がBgraであった場合は、CopyRawFrameDataToArrayメソッドで、Raw フレームのデータ指定された配列(ColorImagePixelData)にコピーします。
  6. そうでない場合は、CopyConvertedFrameDataToArrayメソッドで、フレームからBGRA形式のバイト列に変換し、バイト配列に格納します。
  7. Boolean型で宣言していた変数colorFrameProcessedをTrueで初期化します。
  8. colorFrameProcessed変数がTrueであった場合は、

colorBitmap(WriteableBitmapクラス)のWritePixelsメソッドで、ビットマップの指定した領域内のピクセルを更新します。CopyConvertedFrameDataToArrayメソッドとWritePixelsメソッドの書式については、第3回目を参照してください。9.roomImageのSourceプロパティにcolorBitmapオブジェクトを指定します。これでRGBカメラからの画像が表示されます。

関節の位置に透明の円を表示する処理

リスト6:MainWindow.xaml.vbの一部、リスト5の続き

01Private Sub DrawEllipse(myJoint As Joint, R As Integer, myBrush As Brush)
02    Dim myEllipse = New Ellipse
03    With myEllipse
04        .Width = R
05        .Height = R
06        .Fill = myBrush
07    End With
08 
09    point = myKinect.CoordinateMapper.MapCameraPointToDepthSpace(myJoint.Position)   (1)
10    If point.X < 0 OrElse point.Y < 0 Then
11        Return
12    End If
13    Canvas.SetLeft(myEllipse, point.X - (R / 2))    (2)
14    Canvas.SetTop(myEllipse, point.Y - (R / 2))
15    If myJoint.JointType = JointType.HandRight Then
16        commonJoint.JointType = JointType.HandRight
17        If flag = True Then
18            myImage.Margin = New Thickness(point.X, point.Y, 0, 0)    (3)
19        End If
20        '.Margin = New Thickness(point.X, point.Y, 0, 0)
21    End If
22    CanvasBody.Children.Add(myEllipse)
23End Sub

直径10ピクセルで透明の円を作成します。直径はDrawEllipseを呼ぶ際に指定しています(リスト4)。

  1. CoordinateMapper.MapCameraPointToDepthSpaceメソッドで、関節の位置を、カメラ空間から距離空間へのポイントにマップし、メンバー変数pointで参照します。
  2. SetLeftとSetTopメソッドで透明な円を、指定したXとY軸に描きます。
  3. 関節のタイプが右手であり、変数flagがTrue、つまり手の形がグーの場合は、myImageオブジェクトをPoint.XとPoint.Yの位置に移動します。これにより、リンゴをつかんで移動します。変数flagの初期化は、後述のDrawHandState(リスト7)の中で行っています。プレイヤーには透明な円で関節の位置が表示されていますが、目には見えません。

プレイヤーの手の形によって条件分岐を行う処理

リスト7:MainWindow.xaml.vbの一部、リスト6の続き

01Private Sub DrawHandState(joint As Joint, state As HandState, myTrackingConfidence As TrackingConfidence)
02    If myTrackingConfidence <> TrackingConfidence.High Then
03        Return
04    End If
05 
06    If state = HandState.Open Then   (1)
07        flag = False
08    ElseIf state = HandState.Closed Then   (2)
09        appleCanvas.Children.Clear()
10        Image1.Source = Nothing
11        myImage.Margin = New Thickness(point.X, point.Y, 0, 0)
12        appleCanvas.Children.Add(myImage)
13        flag = True   (3)
14    End If
15End Sub
  1. 手がパーの状態なら、flagにFalseを代入します。
  2. 手がグーの状態の場合は、リンゴが表示されていた領域をクリアし、新しく移動された位置にリンゴを表示します。
  3. flagにTrueを代入します。

ウインドウが閉じられる場合の処理

リスト8:MainWindow.xaml.vbの一部、リスト7の続き

1Private Sub MainWindow_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing
2    If myKinect Is Nothing = False Then
3        myKinect.Close()
4        myKinect = Nothing
5    End If
6End Sub
7End Class

Kinectが動作している場合は、Kinectを閉じ、全ての関連付けから解放します。

実際にプログラムを動作させると下記の動画のようになります。リンゴをつかんで動かしている感じがお分かりいただけると思います。

  • 画面上のオブジェクトをつかんで移動させるKinect v2プログラム

    『作りながら学ぶKinect v2プログラミング開発』 第6回のサンプルプログラムです。
薬師寺国安事務所

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

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