手を動かして画面上の写真を左右にスライドさせる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 薬師寺国安

  • 「スライドによるページナビゲーション」サンプルプログラム

薬師寺国安事務所

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

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