Windows Phoneで自宅の様子をリモート監視するKinectプログラムを作ってみる
ロジックコードを記述する
リスト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センサーの前を通過する人物を認識するサンプルプログラム
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- Kinectで得た人体情報を転送して、Windows Phoneの画面上に関節の位置を表示してみる
- 写真をハート型に切り抜いて撮影するサンプル
- カメラ撮影時に装飾アイテムを追加するサンプル
- センサーの範囲内にいる人間を見つけて撮影・保存するKinectサンプル
- カメラに配置した装飾アイテムを移動・変形させるサンプル
- 写真を切りぬいて新しい写真を撮影するサンプル
- タッチパネルでドラッグ&ドロップを使う汎用的なサンプル
- Kinectを使ったバーチャル試着室で着せ替えシミュレーション
- 声で選んだアイテムをプレイヤーの身体に装着・連動させるKinectサンプル
- カメラで写した写真を分離ストレージに保存し、写真の一覧を表示する