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

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

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

リスト2 (MainPage.xaml.vb)

1Option Strict On

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

1Imports Coding4Fun.Kinect.KinectService.Common
2Imports Coding4Fun.Kinect.KinectService.PhoneClient
3 
4Imports System.IO

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

1Imports System.IO.IsolatedStorage

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

Imports System.Xml.Linq

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

1Imports System.Windows.Threading
2 
3Partial Public Class MainPage
4  Inherits PhoneApplicationPage
5  
6  ' コンストラクター
7  Public Sub New()
8    InitializeComponent()
9  End Sub

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

1Dim flag As Boolean = False

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

1Dim _skeletonClient As SkeletonClient
2Dim _colorClient As ColorClient

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

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

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

1Dim 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(データ一覧)の使用を可能にします。そうでない場合は、不可のままです。

01Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs)
02  myTimer = New DispatcherTimer
03  myTimer.Interval = New TimeSpan(1000)
04 
05  AddHandler myTimer.Tick, Sub(timerSender As Object, timerArgs As EventArgs)
06                              inTextBlock.Text = DateTime.Now.ToString("yyyy年MM月dd日HH時mm分ss秒")
07                           End Sub
08  myTimer.Start()
09  Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
10  If storage.DirectoryExists("kinect") = False Then
11     storage.CreateDirectory("kinect")
12  End If
13  Dim xmlFilePath As String = Path.Combine("kinect", "FileList.xml")
14  If storage.FileExists(xmlFilePath) = True Then
15     dataButton.IsEnabled = True
16  Else
17     dataButton.IsEnabled = False
18  End If
19  MyBase.OnNavigatedTo(e)
20End Sub

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

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

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

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

01Private Sub okButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles okButton.Click
02    Dim myIPAddress As String = addressTextBox.Text
03 
04    If myIPAddress = String.Empty Then
05      MessageBox.Show("Addressを入力してください。")
06      Exit Sub
07    Else
08      _skeletonClient = New SkeletonClient()
09      _colorClient = New ColorClient()
10 
11    If Not _colorClient.IsConnected Then
12      _colorClient.Connect(myIPAddress, 4530)
13    Else
14      _colorClient.Disconnect()
15    End If
16    If Not _skeletonClient.IsConnected Then
17      _skeletonClient.Connect(myIPAddress, 4532)
18    Else
19      _skeletonClient.Disconnect()
20    End If
21    AddHandler _colorClient.ColorFrameReady, AddressOf client_ColorFrameReady
22    AddHandler _skeletonClient.SkeletonFrameReady, AddressOf client_SkeletonFrameReady
23  End If
24 
25End Sub

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

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

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

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

01Private Sub client_ColorFrameReady(ByVal sender As Object, ByVal e As ColorFrameReadyEventArgs)
02  If e.ColorFrame.BitmapImage Is Nothing = False Then
03    Image1.Source = e.ColorFrame.BitmapImage
04  End If
05  TextBlock1.Text = flag.ToString
06  If flag = True Then
07    myTimer.Stop()
08    ListBox1.Items.Clear()
09    ListBox1.Items.Add(DateTime.Now.ToString("yyyy年MM月dd日HH時mm分ss秒"))
10    ListBox1.SelectedIndex = 0
11  Else
12    DataSave()
13    myTimer.Start()
14  End If
15End Sub

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

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

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

01Private Sub client_SkeletonFrameReady(ByVal sender As Object, ByVal e As SkeletonFrameReadyEventArgs)
02  Dim mySkeleton As Skeleton = (From s In e.SkeletonFrame.Skeletons Where s.TrackingState = SkeletonTrackingState.Tracked Select s).FirstOrDefault()
03 
04  If mySkeleton Is Nothing Then
05    flag = False
06    myTimer.Start()
07    Return
08  Else
09    flag = True
10    myTimer.Stop()
11  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の内容をクリアします。

01Private Sub DataSave()
02  If ListBox1.Items.Count > 0 Then
03    nowTime = ListBox1.SelectedItem.ToString
04    inTime = inTextBlock.Text
05 
06    Dim xmlStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
07    Dim xmlFilePath As String = Path.Combine("kinect", "FileList.xml")
08 
09    If xmlStorage.FileExists(xmlFilePath) = False Then
10      Dim xmldoc As XDocument = <?xml version="1.0" encoding="utf-8"?>
11                                <人物記録>
12                                  <情報>
13                                    <入時間><%= inTime %></入時間>
14                                    <出時間><%= nowTime %></出時間>
15                                  </情報>
16                                </人物記録>
17      Using xmlStream As IsolatedStorageFileStream = xmlStorage.CreateFile(xmlFilePath)
18      End Using
19 
20      If xmlStorage.FileExists(xmlFilePath) = True Then
21        Using xmlwriter As StreamWriter = New StreamWriter(xmlStorage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Write))
22          xmlwriter.Flush()
23          xmlwriter.Write(xmldoc.ToString)
24          'xmlwriter.Close()
25        End Using
26        dataButton.IsEnabled = True
27 
28      End If
29    Else
30      Dim xmlStream As IsolatedStorageFileStream = xmlStorage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Read)
31      Using xmlreader As StreamReader = New StreamReader(xmlStream)
32 
33        Dim readXmldoc As String = xmlreader.ReadToEnd
34        Dim doc As XElement = XElement.Parse(readXmldoc)
35        Dim addXml As XElement = <情報>
36                                   <入時間><%= inTime %></入時間>
37                                   <出時間><%= nowTime %></出時間>
38                                 </情報>
39        doc.Add(addXml)
40        xmlStream.Close()
41 
42        If xmlStorage.FileExists(xmlFilePath) = True Then
43          Using xmlwriter As StreamWriter = New StreamWriter(xmlStorage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Write))
44            xmlwriter.Flush()
45            xmlwriter.Write(doc.ToString)
46            dataButton.IsEnabled = True
47          End Using
48        End If
49      End Using
50    End If
51    ListBox1.Items.Clear()
52  End If
53End Sub

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

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

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

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

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

リスト3 (DataIchiranPage.xaml.vb)

01Option Strict On
02Imports System.Xml.Linq
03Imports System.IO
04Imports System.IO.IsolatedStorage
05Imports Microsoft.Phone
06Imports System.Windows.Media.Imaging
07 
08Partial Public Class DataIchiranPage
09  Inherits PhoneApplicationPage
10 
11  Public Sub New()
12    InitializeComponent()
13  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コントロール内に「入時間」と「出時間」の一覧が表示されます。

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

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

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

1  Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button1.Click
2    Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
3    Dim filePath As String = Path.Combine("kinect", "FileList.xml")
4    If storage.FileExists(filePath) = True Then
5      storage.DeleteFile(filePath)
6    End If
7    NavigationService.Navigate(New Uri("/MainPage.xaml", UriKind.Relative))
8  End Sub
9End 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メルマガ会員のサービス内容を見る

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