PR

手を動かして画面上の写真を左右にスライドさせるKinectサンプル

2012年8月20日(月)
薬師寺 国安

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

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

リスト4 (MainWindow.xaml.vb)

Option Strict On
Imports Microsoft.Kinect

左右の手の動きを認識するエンジンの含まれる、Microsoft.Samples.Kinect.SwipeGestureRecognizer名前空間をインポートします。

  Imports Microsoft.Samples.Kinect.SwipeGestureRecognizer

  Imports System.Windows.Media.Imaging

ストーリーボードを利用するためのクラスの含まれる、System.Windows.Media.Animation名前空間をインポートします。

Imports System.Windows.Media.Animation
Imports System.IO
Imports Coding4Fun.Kinect.Wpf
Class MainWindow

認識エンジンクラス用メンバ変数を宣言します。

  Dim myRecognizer As Recognizer

追跡されたスケルトンの一意のIDを格納するメンバ変数mySkeletonIDを宣言し-1で初期化しておきます。

  Dim mySkeletonID As Integer = -1

Kinectセンサーを表すKinectSensorクラス用メンバ変数を宣言します。

  Dim kinect As KinectSensor

BitmapImage型のリストである、新しいインスタンスのimageListメンバ変数を宣言します。

  Dim imageList As New List(Of BitmapImage)

XML要素を表すクラスであるXElementクラス用メンバ変数を宣言します。

  Dim xmldoc As XElement

スケルトンのデータを保持するのに必要なメモリを確保するために、新しいSkeletonのインスタンス、mySkeletons配列オブジェクトを作成します。

  Dim mySkeletons As Skeleton() = New Skeleton() {}

XML文書から画像名の要素を取り出すShared型変数indexを宣言しておきます。

  Shared index As Integer = 0

ページが読み込まれた時の処理

Kinectセンサーを動作させ、RGBカメラ、スケルトンを有効にするInitializeKinectプロシージャを実行します。

XElement.LoadメソッドでXML文書ファイル(Photo.xml)を読み込みます。

Descendants メソッドで、子孫要素である全ての 要素のコレクションに対して、各要素を変数result に格納していきます。
BitmapImage型のリストであるimageListにAddメソッドで、画像を配置しているフォルダ名のImagesを連結した、result.Element(要素名).Valueの値を、BitmapImageとして追加していきます。

カレント画像を表示するcurrentImageのSourceプロパティにimageListに格納されている0番目の画像を指定します。すると最初の画像が表示されるので、認識エンジンクラス用メンバ変数myRecognizerでCreateRecognizer関数を実行します。

  Private Sub MainWindow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded

    InitializeKinect()
    xmldoc = XElement.Load("Photo.xml")
 
    For Each result In From c In xmldoc.Descendants("画像名") Select c
      imageList.Add(New BitmapImage(New Uri("Images/" & result.Value, UriKind.Relative)))
    Next
    currentImage.Source = imageList(index) 
    TextBlock1.Text = (index + 1).ToString & "枚目=" & xmldoc.Descendants("画像名")(index).Value
    myRecognizer = CreateRecognizer()
  End Sub

左右の手の動きで画像をスライドさせる関数(Recognizerクラス型の関数)

新しい認識エンジンである、Recognizerクラスのインスタンスを作成します。
要素を選択するクエリを定義し、Countプロパティで要素の個数を取得します。取得した個数を変数imageCountに格納しておきます。
右手で、右から左に手をワイプさせた時に発生するSwipeRightDetectedイベントに、イベントハンドラを指定します。イベントハンドラ内では下記の処理を行います。スケルトンの追跡IDが、追跡されたスケルトンの一意のIDである場合の処理です。

メンバ変数indexの値が要素の個数から-3した値であった場合は、indexに要素の個数から-3した値を格納し、「最後」という文字を表示して処理を抜けます。

-3しているのは、一度に表示する画像が、カレント画像、ひとつ前の画像、次の画像の3つだからです。画像を配置しているImagesフォルダ内には2枚のダミーのPNG画像を配置しています。
最後の画像をcurrentImageに表示するには、要素の値である20から-3した値の、17番目の画像、インデックスは0から始まるため、0、1、2・・・・・17となり、最後の画像は18番目の画像が表示されることになります。
リスト1のXMLファイルにも最後の2つはダミーの画像を指定し、Imagesフォルダ内にもダミーの画像を配置しています。これを行っていないと、最後の画像を表示する場合nextImageに表示する画像が読み取れずエラーになりますので注意してください。

メンバ変数indexの値が要素の個数から-3した値以外であった場合は、indexの値を1ずつ加算します。
ひとつ前の画像を表示するpreviousImageのSourceプロパティにはimageListの画像データからindex-1した画像を指定します。カレントの画像を表示するcurrentImageにはindexに該当する画像を指定し、次の画像を表示するnextImageにはindex+1の画像を指定します。
TextBlockコントロールに何枚目か、どんな画像ファイル名かを表示します。
Resourcesで名前がLeftAnimationStoryboard(右手を右から左にワイプさせる時のストーリーボード)という名前のストーリーボードを取得し、実行します。

次に、左手で左から右に向かってワイプする時に発生する、SwipeLeftDetectedイベントの処理です。スケルトンの追跡IDが、追跡されたスケルトンの一意のIDである場合の処理になります。
変数indexの値が1かそれより小さい場合はindexに1を格納し、先頭と表示します。

ひとつ前の画像を表示するpreviousImageのSourceプロパティにはimageListのindex+1した画像を指定します。
カレントの画像を表示するcurrentImageにはindex-1に該当する画像を指定し、次の画像を表示するnextImageにはindexの画像を指定します。何枚目か、どんなファイル名かを表示し、変数indexの値を1ずつ減算します。

Resourcesで名前がRightAnimationStoryboard(左手を左から右にワイプさせる時のストーリーボード)という名前のストーリーボードを取得し、実行します。

認識エンジンであるmyRecognizer変数を戻り値とします。

  Private Function CreateRecognizer() As Recognizer
    myRecognizer = New Recognizer()
    Dim query = From c In xmldoc.Descendants("画像名") Select c
    Dim imageCount As Integer = query.Count
    AddHandler myRecognizer.SwipeRightDetected, Sub(rightSender As Object, rightArgs As KinectGestureEventArgs)
          If rightArgs.Skeleton.TrackingId = mySkeletonID Then
            TextBlock3.Text = String.Empty
              If index = query.Count - 3 Then
                index = query.Count - 3
                TextBlock3.Text = "最後"
                Exit Sub
              Else
                index = index + 1
                previousImage.Source = imageList(index - 1)
                currentImage.Source = imageList(index)
                nextImage.Source = imageList(index + 1)
                TextBlock1.Text = (index + 1).ToString & "枚目=" & xmldoc.Descendants("画像名")(index).Value
              End If
       Dim myStoryboard = TryCast(Resources("LeftAnimateStoryboard"), Storyboard)
          If myStoryboard IsNot Nothing Then
            myStoryboard.Begin()
          End If
        End If
      End Sub
 
  AddHandler myRecognizer.SwipeLeftDetected, Sub(leftSender As Object, leftArgs As KinectGestureEventArgs)
    If leftArgs.Skeleton.TrackingId = mySkeletonID Then
      TextBlock3.Text = String.Empty
        If index <= 1 Then
          index = 1
          TextBlock3.Text = "先頭"
        End If
            previousImage.Source = imageList(index + 1) 
            currentImage.Source = imageList(index - 1) 
            nextImage.Source = imageList(index)
            TextBlock1.Text = index.ToString & "枚目=" & xmldoc.Descendants("画像名")(index - 1).Value
              index = index - 1
  
            Dim myStoryboard = TryCast(Resources("RightAnimateStoryboard"), Storyboard)
            If myStoryboard IsNot Nothing Then
              myStoryboard.Begin()
            End If
        End If
      End Sub
    Return myRecognizer
  End Function

Kinectセンサーを動作させ、RGBカメラ、スケルトンを有効にする処理

イベントを解除し、Kinectセンサーが動作している場合は、動作を停止してリソースを解放するUninitializeKinectプロシージャを実行します。Kinectセンサーが接続されている場合はKinecセンサーを動作させます。
RGBカメラの動作を有効にし、スケルトンの動作を有効にします。
AddHandlerステートメントで、RGBカメラのフレームが更新されたことを通知するイベント、ColorFrameReadyにイベントハンドラを追加します。またスケルトンのフレームが更新されたことを通知するイベント、SkeletonFrameReadyにイベントハンドラを追加します。

  Private Sub InitializeKinect()
    UninitializeKInect()
    If KinectSensor.KinectSensors.Count <> 0 Then
      Try
        kinect = KinectSensor.KinectSensors(0)
        kinect.Start()
      Catch
        kinect = Nothing
        kinect.Stop()
        kinect.Dispose()
      End Try
    End If
    If kinect IsNot Nothing Then
      kinect.ColorStream.Enable()
      kinect.SkeletonStream.Enable()
      AddHandler kinect.ColorFrameReady, AddressOf kinect_ColorFrameReady
      AddHandler kinect.SkeletonFrameReady, AddressOf kinect_SkeletonFrameReady
    End If
  End Sub

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

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

  Private Sub kinect_ColorFrameReady(sender As Object, e As ColorImageFrameReadyEventArgs)
    Using myColorImage As ColorImageFrame = e.OpenColorImageFrame
      rgbImage.Source = myColorImage.ToBitmapSource
    End Using
  End Sub

イベントを解除し、Kinectセンサーが動作している場合は、動作を停止してリソースを解放する処理

Kinectセンサーが動作している時は、イベントを解除し、Kinectの動作を停止してリソースを解放します。

  Private Sub UninitializeKinect()
    If kinect Is Nothing = False Then
      RemoveHandler kinect.ColorFrameReady, AddressOf kinect_ColorFrameReady
      RemoveHandler kinect.SkeletonFrameReady, AddressOf kinect_SkeletonFrameReady
      If kinect.IsRunning = True Then
        kinect.Stop()
        kinect.Dispose()
      End If
    End If
  End Sub

スケルトンのフレームが更新されたことを通知するイベント

e.OpenSkeletonFrameメソッドでスケルトンフレームを開き、スケルトンのフレームを取得します。スケルトンのフレームが取得できた時の処理を行います。
新しいSkeletonのインスタンスである、mySkeletons配列の長さが、スケルトンフレームの配列の長さと異なる場合は、スケルトンのデータ保持に必要なメモリを確保するため、mySkeletonFrame.SkeletonArrayLength-1で初期化された新しいSkeletonのインスタンス、mySkeletons配列オブジェクトを作成します。
skeletonArrayLengthプロパティは、スケルトン配列の長さを取得します。これで、スケルトンデータのバイト列を確保し、CopySkeletonDataToメソッドで、スケルトンフレームからスケルトンのデータを取り出します。

Integer型の変数newTrackingIdを宣言し-1で初期化しておきます。この変数にはスケルトンの追跡された一意のIDが格納されます。

変数mySkeletonでスケルトンフレームデータ(mySkeletons)の中を反復処理しながら、トラッキング(追跡)されているスケルトンのみを判別します。スケルトンの追跡された一意のIDを変数newTrackingIdに格納します。変数mySkeletonIDが、スケルトンの追跡された一意のIDでなかった場合は、mySkeletonIDにスケルトンの追跡された一意のIDを格納します。

認識エンジンのRecognizerクラスのRecognizeメソッドを実行して、手の動きの認識を開始します。書式は下記の通りです。

myRecognizer.Recognize(sender As Object,frame As Microsoft.Kinect.SkeletonFrame,skeletons() As Microsoft.Kinect.Skeleton)

  Private Sub kinect_SkeletonFrameReady(sender As Object, e As SkeletonFrameReadyEventArgs)
    Using mySkeletonFrame As SkeletonFrame = e.OpenSkeletonFrame()
      If mySkeletonFrame IsNot Nothing Then
        If mySkeletons.Length <> mySkeletonFrame.SkeletonArrayLength Then
          mySkeletons = New Skeleton(mySkeletonFrame.SkeletonArrayLength - 1) {}
        End If
        mySkeletonFrame.CopySkeletonDataTo(mySkeletons)
        Dim newTrackingId As Integer = -1 
        For Each mySkeleton In mySkeletons
          If mySkeleton.TrackingState = SkeletonTrackingState.Tracked Then
            newTrackingId = mySkeleton.TrackingId
          End If
          End If
        Next
        If mySkeletonID <> newTrackingIdThen
           mySkeletonID = newTrackingId
        End If
        myRecognizer.Recognize(sender, mySkeletonFrame, mySkeletons)
      End If
    End Using 
  End Sub
End Class

今回でKinectの基本編は終了です。いかがでしたか?Kinectで何ができるかの理解に、少しでもお役に立てたでしょうか?

ぜひ皆さんもKinectプログラミングに挑戦してみてください。未知の世界が広がり、きっと楽しい世界を体験できると思います。どうもありがとうございました。

PROJECT KySS 薬師寺国安

Think IT会員限定特典
  • 「スライドによるページナビゲーション」サンプルプログラム

薬師寺国安事務所

薬師寺国安事務所代表。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会員サービスの概要とメリットをチェック

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