Windows Phoneで自宅の様子をリモート監視するKinectプログラムを作ってみる

2012年10月15日(月)
薬師寺 国安

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

リスト2 (MainPage.xaml.vb)

Option Strict On

KinectServiceに接続するための機能を提供する、2つの名前空間をインポートしておきます。

Imports Coding4Fun.Kinect.KinectService.Common
Imports Coding4Fun.Kinect.KinectService.PhoneClient

Imports System.IO

仮想ファイルシステムを作成および使用するための型が含まれている、System.IO.IsolatedStorage名前空間をインポートします。分離ストレージによって、安全なクライアント側のストレージが提供されます。

Imports System.IO.IsolatedStorage

XML to LINQを使用するためSystem.Xml.Linq名前空間をインポートしておきます。

Imports System.Xml.Linq

タイマーの機能を提供するクラスであるSystem.Windows.Threading名前空間をインポートしておきます。

Imports System.Windows.Threading

Partial Public Class MainPage
  Inherits PhoneApplicationPage
 
  ' コンストラクター
  Public Sub New()
    InitializeComponent()
  End Sub

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

  Dim flag As Boolean = False

リスナーに接続するためのクライアントであるSkeletonClientクラス型のメンバ変数_skeletonClientを宣言します。同様にColorClientクラス型のメンバ変数_colorClientを宣言します。

  Dim _skeletonClient As SkeletonClient
  Dim _colorClient As ColorClient

Kinectセンサーの視界から外れた時刻を記録するメンバ変数nowTimeと、視界に入ってきた時刻を記録するメンバ変数inTimeを宣言します。

  Dim nowTime As String = String.Empty
  Dim inTime As String

タイマーを表すクラスであるDispatcherTimerクラス型のメンバ変数myTimerを宣言します。

  Dim myTimer As DispatcherTimer

ページがアクティブになった時の処理

新しいDispatcherTimerのインスタンスを作成します。Intervalプロパティに1000ミリセコンド(1秒)を指定し、タイマーをスタートします。AddHandlerステートメントで、指定したタイマーの間隔が経過し、タイマーが有効である場合に発生するTickイベントにイベントハンドラを追加します。イベントハンドラ内では、非表示となっているinTextBlockのTextプロパティに現在の年月日時間分秒の値を入れています。

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。分離ストレージ内にkinectというフォルダが存在しない場合は、CreateDirectoryメソッドでkinectというフォルダを作成します。

Path.Combineメソッドで、2つの文字列を1つのパスに結合します。ここでは、kinectフォルダとFileList.xmlを結合しています。kinect\FileList.xmlとなります。この値をxmlFilePath変数に格納します。FileExistsメソッドでxmlFilePathに格納したファイルが存在する場合は、dataButton(データ一覧)の使用を可能にします。そうでない場合は、不可のままです。

  Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs)
    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()
    Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
    If storage.DirectoryExists("kinect") = False Then
       storage.CreateDirectory("kinect")
    End If
    Dim xmlFilePath As String = Path.Combine("kinect", "FileList.xml")
    If storage.FileExists(xmlFilePath) = True Then
       dataButton.IsEnabled = True
    Else
       dataButton.IsEnabled = False
    End If
    MyBase.OnNavigatedTo(e)
  End Sub

[OK]ボタンがクリックされた時の処理

Address入力ボックスに入力されたIPアドレスを、変数myIPAddressに格納しておきます。IPアドレスが入力されていない場合は、警告メッセージを表示し、処理を抜けます。IPアドレスが入力されている場合は、新しいSkeletonClientのインスタンスとColorClientのインスタンスを作成します。ColorClientが接続されていなかった場合は、ConnectメソッドでIPアドレスとポートを指定して接続します。

SkeletonClientが接続されていなかった場合は、同じくConnectメソッドでIPアドレスとポートを指定して接続します。

AddHandlerステートメントでRGBカメラのフレームが更新された場合のイベントハンドラを追加します。同じく、スケルトンのフレームが更新された場合のイベントハンドラを追加します。

  Private Sub okButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles okButton.Click
      Dim myIPAddress As String = addressTextBox.Text
 
      If myIPAddress = String.Empty Then
        MessageBox.Show("Addressを入力してください。")
        Exit Sub
      Else
        _skeletonClient = New SkeletonClient()
        _colorClient = New ColorClient()
 
      If Not _colorClient.IsConnected Then
        _colorClient.Connect(myIPAddress, 4530)
      Else
        _colorClient.Disconnect()
      End If
      If Not _skeletonClient.IsConnected Then
        _skeletonClient.Connect(myIPAddress, 4532)
      Else
        _skeletonClient.Disconnect()
      End If
      AddHandler _colorClient.ColorFrameReady, AddressOf client_ColorFrameReady
      AddHandler _skeletonClient.SkeletonFrameReady, AddressOf client_SkeletonFrameReady
    End If
 
  End Sub

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

Image1のSourceプロパティにRGBカメラのフレームをビットマップイメージに変換して表示します。

非表示となっているTextBlock1にメンバ変数flagの値を格納します。flagの値がTrueの場合、つまりKinectセンサーの視界内にプレイヤーが存在した場合は、タイマーを停止し、ListBoxコントロールをクリアして、現在の年月日時間分秒をListBoxに追加し、選択状態にします。

それ以外、つまりKinectセンサーの視界内にプレイヤーが存在していなかった場合は、DataSaveプロシージャーを実行し、タイマーをスタートします。

  Private Sub client_ColorFrameReady(ByVal sender As Object, ByVal e As ColorFrameReadyEventArgs)
    If e.ColorFrame.BitmapImage Is Nothing = False Then
      Image1.Source = e.ColorFrame.BitmapImage
    End If
    TextBlock1.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
    Else
      DataSave()
      myTimer.Start()
    End If
  End Sub

スケルトンフレームが更新された時に発生するイベント

Skeletonクラス用のオブジェクト変数であるmySkeletonを使って、スケルトンデータを持つe.SkeletonFrame.Skeletons内で、全ての関節の位置がトラッキングされた状態にある、シーケンスの最初の要素を取得していきます。

プレイヤーの骨格が追跡されていない場合は、メンバ変数flagをFalseで初期化し、タイマーをスタートさせます。それ以外の場合、つまり、プレイヤーの骨格が追跡されていた場合は、flagをTrueで初期化しタイマーを停止します。

  Private Sub client_SkeletonFrameReady(ByVal sender As Object, ByVal e As SkeletonFrameReadyEventArgs)
    Dim mySkeleton As Skeleton = (From s In e.SkeletonFrame.Skeletons Where s.TrackingState = SkeletonTrackingState.Tracked Select s).FirstOrDefault()
 
    If mySkeleton Is Nothing Then
      flag = False
      myTimer.Start()
      Return
    Else
      flag = True
      myTimer.Stop()
    End If    End Sub

プレイヤーがKinectセンサーの視界に入った時刻と、視界から外れた時刻をXMLファイルとして保存する処理

ListBoxコントロールに現在の年月日時間分秒が追加されている場合の処理です。メンバ変数nowTimeにListBoxコントロールの項目を格納します。メンバ変数inTimeに非表示となっているinTextBlockに格納されている時刻を格納します。

変数xmlStorageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。Path.CombineでkinectというフォルダとFileList.xmlに格納されている画像名とを連結し、変数xmlFilePathに格納しておきます。

kinectというフォルダ内にFileList.xmlが存在していない場合は、Visual Basic の埋め込み式を用いて、XML宣言とルート要素、その子要素として、その子要素としてとを作成し、埋め込み式の構文である を用いてとの内容テキストに、変数inTimeとnowTimeを指定します。これは ASP.NET で使用される構文と同じです。

分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数xmlStreamを用意し、IsolatedStorageFile.CreateFileメソッドで、分離ストレージ内にxmlFilePath変数の持っているフォルダ名付きXMLファイルを作成します。

kinectフォルダ内にFileList.xmlファイルが存在すれば、新しいStreamWriter を生成し、IsolatedStorageFile.OpenFileメソッドで、指定したファイルアクセスを使用して指定したモードでファイルを開き、初期化します。Writeメソッドで埋め込み式のXMLをストリームに書き込みます。[データ一覧]ボタンの使用を可能にします。

次は、既にkinectフォルダ内にFileList.xmlが存在する場合の処理です。

IsolatedStorageFileクラスのOpenFileメソッドでkinectフォルダ内のFileList.xmlファイルを、指定したファイルアクセスを使用して指定したモードで開きます。開いたファイルをStreamReaderで読み込みます。ReadToEndメソッドでファイルの最後まで読み取り変数readXmldoc変数に格納しておきます。読み込んだXMLテキストをParseメソッドでXElementとして読み込みます。

追加するとその子要素とを作成します。と要素の内容テキストに、埋め込み式を用いてinTimeとnowTime変数の値を指定します。新しく生成したXML要素を、読み込んだXMLにAddメソッドで追加します。IsolatedStorageFileStreamを閉じます。

FileList.xmlファイルが存在する場合は、新しいStreamWriter を生成し、IsolatedStorageFile.OpenFileメソッドで、指定したファイルアクセスを使用して指定したモードでファイルを開き、初期化します。Writeメソッドで新しいXML要素の追加されたXMLを、ストリームに書き込みます。[データ一覧]ボタンの使用を可能にし、ListBoxの内容をクリアします。

  Private Sub DataSave()
    If ListBox1.Items.Count > 0 Then
      nowTime = ListBox1.SelectedItem.ToString 
      inTime = inTextBlock.Text
 
      Dim xmlStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
      Dim xmlFilePath As String = Path.Combine("kinect", "FileList.xml")
 
      If xmlStorage.FileExists(xmlFilePath) = False Then
        Dim xmldoc As XDocument = <?xml version="1.0" encoding="utf-8"?>
                                  <人物記録>
                                    <情報>
                                      <入時間><%= inTime %></入時間>
                                      <出時間><%= nowTime %></出時間>
                                    </情報>
                                  </人物記録>
        Using xmlStream As IsolatedStorageFileStream = xmlStorage.CreateFile(xmlFilePath)
        End Using
 
        If xmlStorage.FileExists(xmlFilePath) = True Then
          Using xmlwriter As StreamWriter = New StreamWriter(xmlStorage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Write))
            xmlwriter.Flush()
            xmlwriter.Write(xmldoc.ToString)
            'xmlwriter.Close()
          End Using
          dataButton.IsEnabled = True
 
        End If
      Else
        Dim xmlStream As IsolatedStorageFileStream = xmlStorage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Read)
        Using xmlreader As StreamReader = New StreamReader(xmlStream)
 
          Dim readXmldoc As String = xmlreader.ReadToEnd
          Dim doc As XElement = XElement.Parse(readXmldoc)
          Dim addXml As XElement = <情報>
                                     <入時間><%= inTime %></入時間>
                                     <出時間><%= nowTime %></出時間>
                                   </情報>
          doc.Add(addXml)
          xmlStream.Close()
 
          If xmlStorage.FileExists(xmlFilePath) = True Then
            Using xmlwriter As StreamWriter = New StreamWriter(xmlStorage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Write))
              xmlwriter.Flush()
              xmlwriter.Write(doc.ToString)
              dataButton.IsEnabled = True
            End Using
          End If
        End Using
      End If
      ListBox1.Items.Clear()
    End If
  End Sub

[データ一覧]ボタンがクリックされた時の処理

KinectServiceを切断します。DataIchiranPageに遷移します。

  Private Sub dataButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles dataButton.Click
    RemoveHandler _colorClient.ColorFrameReady, AddressOf client_ColorFrameReady
    RemoveHandler _skeletonClient.SkeletonFrameReady, AddressOf client_SkeletonFrameReady
    _colorClient.Disconnect()
    _skeletonClient.Disconnect()
    NavigationService.Navigate(New Uri("/DataIchiranPage.xaml", UriKind.Relative))
  End Sub
End Class

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

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

リスト3 (DataIchiranPage.xaml.vb)

Option Strict On
Imports System.Xml.Linq
Imports System.IO
Imports System.IO.IsolatedStorage
Imports Microsoft.Phone
Imports System.Windows.Media.Imaging

Partial Public Class DataIchiranPage
  Inherits PhoneApplicationPage

  Public Sub New()
    InitializeComponent()
  End Sub

ページがアクティブになった時の処理

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。Path.Combineメソッドで、2つの文字列を1つのパスに結合します。ここでは、kinectフォルダとFileList.xmlを結合しています。kinect\FileList.xmlとなります。この値をfilePath変数に格納します。

IsolatedStorageFileクラスのOpenFileメソッドでkinectフォルダ内のFileList.xmlファイルを、指定したファイルアクセスを使用して指定したモードで開きます。開いたファイルをStreamReaderで読み込みます。ReadToEndメソッドでファイルの最後まで読み取り変数readXmldoc変数に格納しておきます。読み込んだXMLテキストをParseメソッドでXElementとして読み込みます。

文字列型の新しいリストである、myListInfoオブジェクトを作成します。読み込んだXMLファイルの要素コレクション内を変数resultで反復処理しながら、以下の処理を実行します。

文字列型のリストオブジェクトにAddメソッドで、と要素の内容テキストを、文字列と改行を付加して追加していきます。ListBoxのItemsSourceプロパティに時間の追加されたmyListInfoオブジェクトを指定します。ListBoxコントロール内に「入時間」と「出時間」の一覧が表示されます。

  Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs)
    Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
    Dim filePath As String = Path.Combine("kinect", "FileList.xml")
    Using myStream As IsolatedStorageFileStream = storage.OpenFile(filePath, FileMode.Open, FileAccess.Read)
      Using reader As StreamReader = New StreamReader(myStream)
        Dim readXmldoc As String = reader.ReadToEnd
        'myStream.Close()
        Dim doc As XElement = XElement.Parse(readXmldoc)
        Dim myListInfo As New List(Of String)
 
        For Each result In From c In doc.Descendants("情報") Select c
          myListInfo.Add("入時間=" & result.Element("入時間").Value & vbCrLf & "出時間=" & result.Element("出時間").Value & vbCrLf & "---------------------------")
        Next
        ListBox1.ItemsSource = myListInfo
      End Using
    End Using
    MyBase.OnNavigatedTo(e)
  End Sub

[全データの削除]ボタンをクリックした時の処理

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。Path.Combineメソッドで、2つの文字列を1つのパスに結合します。ここでは、kinectフォルダとFileList.xmlを結合しています。kinect\FileList.xmlとなります。この値をfilePath変数に格納します。kinect\FileList.xmlが存在していた場合は、DeleteFileメソッドで削除します。その後MainPageに遷移します。

  Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button1.Click
    Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
    Dim filePath As String = Path.Combine("kinect", "FileList.xml")
    If storage.FileExists(filePath) = True Then
      storage.DeleteFile(filePath)
    End If
    NavigationService.Navigate(New Uri("/MainPage.xaml", UriKind.Relative))
  End Sub
End Class

今回で全14回にわたってお届けしたKinect応用編は終了です。基本編と応用編で全28個のサンプルを紹介してきましたが、いかがだったでしょうか。自分なりのKinectアプリケーション作成のヒントにはなりましたか。もし、少しでもお役に立てたならうれしい限りです。どうも長い間ありがとうございました。

PROJECT KySS 薬師寺国安

  • 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メルマガ会員のサービス内容を見る

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