センサーの範囲内にいる人間を見つけて撮影・保存するKinectサンプル

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

ウィンドウ(WPF)の追加(DataShowWindow.xaml)

保存した画像の一覧を表示させるために、DataShowWindow.xamlを作成します。VS2010メニューの「プロジェクト(P)/新しい項目の追加(W)」と選択して、「インストールされたテンプレート」から「WPF」を選択します。

右側に表示される項目から「ウィンドウ(WPF)」を選択し、「名前(N)」に「DataShowWindow.xaml」と指定します(図5)。

図5:ウィンドウ(WPF)を追加している(クリックで拡大)

コントロールの配置

DataShowWindow.xamlのデザイン画面上にListBoxコントロールを1個配置します(図6)。書き出されるXAMLコードをリスト2のように編集します。

図6:ListBoxコントロールを1個だけ配置した(クリックで拡大)

リスト2 書き出され編集されたXAMLコード(DataShowWindow.xaml)

  • (1)プロパティ要素内にKeyがListBoxTemplateという名前の 要素を配置します。その中に要素を配置し、Marginプロパティに10を指定して、余白を設けます。要素内にTextBlockを1個配置し、TextプロパティにOutRoomTimeプロパティをバインドしておきます。またImageを1個配置しSourceプロパティにImageNameプロパティをバインドしておきます。これらの名前はVBコード内で定義したプロパティ名です。
  • (2)ListBoxコントロールのItemTemplateで(1)で定義したListBoxTemplateを参照します。
<Window x:Class="DataShowWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="DataShowWindow" Height="777" Width="489">
  <Window.Resources> ■(1)
    <DataTemplate x:Key="ListBoxTemplate">
      <StackPanel Margin="10">
        <TextBlock Text="{Binding OutRoomTime}" FontSize="20" /> ■(1)
        <Image Width="358" Height="269" Source="{Binding ImageName}"/> ■(1)
      </StackPanel>
    </DataTemplate>
  </Window.Resources> ■(1)
  <Grid Height="751" Width="482">
    <ListBox Height="716" HorizontalAlignment="Left" Margin="15,12,0,0" Name="ListBox1" VerticalAlignment="Top" Width="441" ItemTemplate="{StaticResource ListBoxTemplate}"/> ■(2)
    <Button Content="データ全削除" Height="50" HorizontalAlignment="Left" Margin="12,14,0,0" Name="Button1" VerticalAlignment="Top" Width="443" FontSize="22" FontWeight="Bold" Visibility="Collapsed" />
  </Grid>
</Window>

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

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

リスト3 (MainWindow.xaml.vb)

Option Strict On
Imports Microsoft.Kinect
Imports Coding4Fun.Kinect.Wpf

タイマーを使用するためSystem.Windows.Threading名前空間をインポートします。

Imports System.Windows.Threading
Imports System.IO

Class MainWindow

一個のKinectセンサーを表すメンバ変数kinectを宣言します。

  Dim kinect As KinectSensor

Kinectセンサーの視界内に人がいるかどうかを判別するブール型のメンバ変数flagを宣言し、Falseで初期化しておきます。

  Dim flag As Boolean = False

Kinectセンサーの視界から離れた時間と、視界に入った時間を保持するメンバ変数を宣言します。

  Dim outTime As String = String.Empty
  Dim inTime As String

指定した時間の間隔で、指定した優先順位で処理されるタイマークラスのメンバ変数myTimerを宣言します。

  Dim myTimer As DispatcherTimer
  Dim imageFileName As String

ウィンドウが読み込まれた時の処理

システムの特別なフォルダである、「マイピクチャ」フォルダへのディレクトリパスを取得して、変数dirに格納しておきます。「マイピクチャ」フォルダ内にKINECTというサブフォルダが存在していなかった場合は、CreateDirectoryメソッドで「マイピクチャ」内にKINECTというフォルダを作成します。

KINECTフォルダへのディレクトリパスを変数fileDirに格納します。KINECTフォルダ内にImageFileList.xmlというファイルが存在する場合、つまり、画像が保存されている場合は、[データ表示]ボタンの使用を可能にします。それ以外は、使用を不可とします。

新しいタイマーのインスタンスを作成し、Intervalプロパティに1000ミリセコンド(1秒)を指定します。

AddHandlerステートメントで、指定したタイマーの間隔が経過し、タイマーが有効である場合に発生するTickイベントに、イベントハンドラを指定します。イベントハンドラ内では、非表示にしているinTextBlock内に現在の年月日時分秒を表示しておきます(ただし非表示)。タイマーをスタートします。

Kinectセンサーを取得しRGBカメラを有効にします。またスケルトンも有効にし、RGBカメラ、スケルトンのフレームが更新された時に発生する、AllFramesReadyイベントにkinect_AllFramesReadyイベントハンドラを指定します。Kinectセンサーを動作させます。

  Private Sub MainWindow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
    Dim dir As String= Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)
 
    If Directory.Exists(Path.Combine(dir, "KINECT")) = False Then
      Directory.CreateDirectory(Path.Combine(dir, "KINECT"))
    End If
    Dim fileDir = Path.Combine(dir, "KINECT")
 
    If File.Exists(Path.Combine(fileDir, "ImageFileList.xml")) = True Then
      Button1.IsEnabled = True
    Else
      Button1.IsEnabled = False
    End If
    
    myTimer = New DispatcherTimer
    myTimer.Interval = New TimeSpan(1000)
 
    AddHandler myTimer.Tick, Sub(timerSender As Object, timerArgs As EventArgs)
                               inTextBlock.Text = DateTime.Now.ToString("yyyy年MM月dd日HH時mm分ss秒")
                             End Sub
    myTimer.Start()

 
    If KinectSensor.KinectSensors.Count = 0 Then
      MessageBox.Show("KINECTが接続されておりません。")
      Exit Sub
    Else
      kinect = KinectSensor.KinectSensors(0)
      kinect.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30)
      kinect.SkeletonStream.Enable()
 
      AddHandler kinect.AllFramesReady, AddressOf kinect_AllFramesReady
      kinect.Start()
    End If
  End Sub

RGBカメラ、距離カメラ、スケルトンのフレームが更新された時に発生するイベント

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

非表示になっているTextBlock2内にFalseと表示します(ただし非表示)。メンバ変数flagがTrueの場合、つまりKinectセンサーの視界内に人がいる場合は、タイマーを停止し、ListBox内をクリアして、現在の年月日時分秒を追加します。ListBox1.SelectedIndex = 0で、その項目を選択された状態にしておきます。

DataSaveプロシージャを実行します。メンバ変数flagがFalseの場合、つまりKinectセンサーの視界から人がいなくなった場合は、タイマーを再開します。スケルトンを追跡するmySkeletonShowプロシージャを実行します。「マイピクチャ」のKINECTフォルダには1秒ごとの画像データが保存されますので、ストレージを圧迫しないようこまめに削除してお試しください。

  Private Sub kinect_AllFramesReady(sender As Object, e As AllFramesReadyEventArgs)
    Using myImage As ColorImageFrame = e.OpenColorImageFrame
      Image1.Source = myImage.ToBitmapSource
    End Using
    TextBlock2.Text = flag.ToString
    If flag = True Then
      myTimer.Stop()
      ListBox1.Items.Clear()
      ListBox1.Items.Add(DateTime.Now.ToString("yyyy年MM月dd日HH時mm分ss秒"))
      ListBox1.SelectedIndex = 0
      DataSave()
    Else
      TextBlock1.Text = String.Empty
      myTimer.Start()
    End If
    mySkeletonShow(e)
  End Sub

スケルトンを追跡し、Kinectセンサーの視界に人がいるかどうかを判別する処理

e.OpenSkeletonFrameメソッドでスケルトンフレームを開き、スケルトンのフレームを取得します。スケルトンのフレームが取得できた時の処理を行います。

スケルトンのデータを保持するのに必要なメモリを確保するために、mySkeletonFrame.SkeletonArrayLength-1で初期化された新しいSkeletonのインスタンス、skeletonData配列オブジェクトを作成します。skeletonArrayLengthプロパティは、スケルトン配列の長さを取得します。これで、スケルトンデータのバイト列を確保し、CopySkeletonDataToメソッドで、スケルトンフレームからスケルトンのデータを取り出します。
取得されるデータは人の分取得されますので、それぞれのトラッキング状態を確認します。CopySkeletonDataToメソッドは、指定した配列、この場合skeletonData配列変数に、現在のスケルトンフレーム、この場合mySkeletonFrameオブジェクトをコピーするメソッドです。

変数mySkeletonでスケルトンフレームデータの中を反復処理しながら、スケルトンがトラッキング(追跡)されている場合は、メンバ変数flagをTrueで初期化しタイマーを停止します。

スケルトンのフレームが取得できなかった場合は、メンバ変数flagをFalseで初期化し、タイマーを開始します。

  Private Sub mySkeletonShow(e As AllFramesReadyEventArgs)
    Dim mySkeletonFrame As SkeletonFrame = e.OpenSkeletonFrame
    If mySkeletonFrame Is Nothing = False Then
      Dim skeletonData As Skeleton() = New Skeleton(mySkeletonFrame.SkeletonArrayLength - 1) {}
      mySkeletonFrame.CopySkeletonDataTo(skeletonData)
 
      For Each mySkeleton In skeletonData
        If mySkeleton.TrackingState = SkeletonTrackingState.Tracked Then
          flag = True
          myTimer.Stop()
        End If
      Next
    Else
      flag = False
      myTimer.Start()
    End If
  End Sub
  • センサーの範囲内にいる人間を見つけて撮影・保存するKinectサンプル

薬師寺国安事務所

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

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