ここまで確認した内容を試してみます。
デバイス列挙コードは次のとおり(C#サンプル face_tracking.cs のアレンジ版です。)
デバイスを選んで、ストリーム一覧を表示する機能を入れてみます。
加えて、選択したストリームの設定一覧を表示する機能を入れてみます。
デバイスのストリーム一覧を列挙するコードが以下
ストリームの設定一覧を列挙するコードが以下
次は選択したストリームを再生して、生データを画像表示してみます。
複数ストリームを指定して、再生、録画できるようにしたいので、
WPFのデータテンプレート、データバインディングを使ってみたいと思います。
このあたりのWPF機能の経験が浅いので練習を行い、作業を記録します。
練習作業の完成予想図を下記に示します。
まず練習でこんなの作りたいなと思いました。
メインウィンドウが別ウィンドウを開き、全体にリストボックスが配置されている。
リストのアイテムクラスに依存して、アイテムごとのUIが異なる仕様。
既存の仕組みを使って、ストリームが Video なら4分割、Audio なら単一の画像を表示。
リストアイテムの配置はフレキシブルに左から横詰め、幅が足りなければ下の段へ続くという動作。
できましたので、どうやって作ったか示します。
別ウィンドウなので、ソリューションエクスプローラのソリューション選択時のコンテキストメニューから追加→ウィンドウを選びます。
ウィンドウクラス名は VideoWindow としました。
メインウィンドウから別ウィンドウを開くコードは次のとおり。(モードレスです。)
XAML には次のようにデータテンプレートとアイテムテンプレートセレクターを定義します。
加えて ListBox に横詰めとなるように ScrollViewer.HorizontalScrollBarVisibility="Disabled" を指定しています。
WrapPanel の指定も必要だそうです。簡単に検索してそうするとあったのでそうしただけです。(細かいところまで理解はしていません。)
コードビハインドは次のとおり
BindableBase については次のページを参考に用意しました。
最近 Store アプリで見ていたものですが、WPF にもほぼそのまま使えるそうですね。
WPFでもBindableBaseを使ってINotifyPropertyChangedを実装する
最後に、使用したデータテンプレートセレクターは次のとおりに実装しています。
一度経験したので、次からは調査なく設計、実装ができるようになりそう。
配列に値を入れるだけで思った通りにUIが変化してくれるのはうれしいですが、準備が少々面倒くさいですね。
練習作業が終わったので、選択した Stream から Video や Audio のフレームを取得するループを回そうと思います。
前回示した方法のうち PXC[M]Capture から任意の Stream を取り出す方法で行いました。(わからない人は読み直してね)
とりあえず Video のストリームを複数取得し、互いに独立して取得するループを実現、これを BitmapImage に変換して
先程練習で示した Image を配置する UI に表示するようにしてみました。
正しく動作しているようです。
右の画像は物体がセンサに近づくと輝度値が上がる深度画像です、素晴らしい精度ですね。
指先認識プログラムを実装する意欲がぐっと上がりました。
今回は WPF のデータテンプレート、バインディングを利用しているので気をつけるべき点を下記に示します。
下記コードが描画ループの文です。これを別スレッドで回します。
ハイライトしたところは、メインスレッドで BitmapImage を作成して m_imageBmps[i] へ格納するという処理です。
m_imageBmps[i] が UI の ImageSource とバインディングしているので、メインスレッドで格納する必要があります。
これが関係して落とし穴にはまってしまいました。(抜け出し方は未だ不明です。)
上記のループを行なうスレッドを実行する描画用ウィンドウがいますが、これをコードよりメインウィンドウから閉じようとしました。
単に何もしないループならば、Window.Close をすれば問題なく閉じます。(ループ終了フラグを立てた後)
しかし、Invoke してメインスレッドで処理を行うことをスレッド側で行うと、上記の終了処理でクラッシュします。
原因追求のためステップ実行すると問題は発生しません。
もしかして、ウィンドウのアクティブ化が問題なのかと思い、m_stopRequest が立った瞬間に MessageBox を表示するようにした所
発生しなくなります。
WPF データバインディングのための Invoke、ウィンドウをコードから閉じるという組み合わせで発生し
メッセージボックスを途中で表示したり、デバッグでステップ実行すると発生しません。
閉じたいウィンドウを閉じるボタンから閉じると問題は起きません。
ということで、これができないと先に進めなくなったらもう一度詳しく調べます。(今はあきらめて先に進めます。)
(むしろ今後は別ウィンドウを開くアプリは作らないようにしようと思います。)
と思いきや、サンプルでは次のとおり、ループを抜けたあとに同期を取っています。
この対応を入れることで、上記で理解に苦しんだ不具合があっさり解決。
良かったよかった。
このほか、引っかかった所をメモします。
PXCMScheduler.SyncPoint.SynchronizeEx は途中から割り込めない。
自分が自由に作ったのが悪かったのですが、ストリームを再生中に別のストリームの再生を割りこませようとしたら
これに失敗しました。
Synchronize では、常に PXCM_STATUS_EXEC_INPROGRESS が返り、途中参加させたストリームが失敗します。
しぶしぶドキュメントのサンプルのとおりの対処、つまり上記のコードで解決しました。
セッションを Dispose しないと、ストリームを終了できない。
Stream や Device を Dispose しただけでは、センサは赤外線を出したまま止まらず。
セッションを Dispose して、止まります。(そのうち Stream.StopCapture() とか出来たらうれしい。無かったよね?)
おや、サンプルは Util[M]Capture が Dispose したタイミングで赤外線を止めますね。
PXC[M]Capture の場合はどうすれば?
列挙時に捨てていたけど、残して試してみます。
結果、赤外線が止まりました。
なるほど、デバイスやストリームを取り出した Capture を Dispose すれば止められるのですね。
セッションまで Dispose する必要はありません。(確認できてよかった。)
CreateDevice, CreateStream を2回行なうと、2回目は同じ物を返す?
確認まで入れていませんが、2回目に作成した物が1回目に作成したものに影響を与えている模様。
例えば2回目に作成したものを Dispose した後、1回目に作成したものにアクセスするとクラッシュ。
まったく別物であるという感覚でしたので、予想外の仕様でした。
距離画像から距離値を取得するため、次のとおりに対処しました。
深度画像の画素値は 16bit なので byte 配列で取得したなら BitConverter で深度値[mm]に直します。
(単位は設定で切り替えられるとドキュメントにあり。0.1[mm]単位まで上げてテストしてみたいですね。)
スムージングを入れると、カットされた値は 32001 [mm] になる様子、ドキュメントに意味まで示して欲しかったですが
大きな数字なので最大計測値以上ということでカットします。
パレットとか使っていますが、何を指定しても変化なかったのでそのまま利用しています。(あとで調べます。)
ドキュメントを読んで作り始めたので、そんなに引っかかることも無いと思っていたがそんなことなかったです。
作ってみて色々と細かい所で気をつけないと全く動かないことがわかりました。
さて、次に行うのは Audio のプレビューです。
サンプルに波形を時系列で流すものがありました、参考にしてみます。
…見つけました、こちら↓です。
PXC[M]Audio を取得することはできると思います。
そこからの工程が上記のコードに示されています。
要は m_buffer がイメージバッファであり、これをサンプルごとに左へシフト
シフトして開いた右側の領域を Audio データをもとにピクセル一つ一つに書き込みます。
オーディオデータは m_sampleBuffer に書き込まれていますが、実はこれは生データではありません。
ResampleAudio 関数にて、現在のサンプリングレートに対応して 1サンプル 10 ピクセルとなるよう
リサンプリング(間引き)しています。
PCM 16bit や IEEE FLOAT 32bit のフォーマットはサンプリングレートでそれぞれの精度で値が取得できる。
というもの、フーリエ変換などして情報をプレビューする機構をこれから作ってみたいと思います。
まずは Audio 取得 + 画面表示 + ピクセル操作という練習を行ってみたいと思います。
…できました。下記がソースコードです。
格納先が穴だらけの中、null チェックを抜けたら描画処理へ進むというもの。
まだ画像データを綺麗に作る仕組みは入れていないですが、取得した情報を元に画像を生成することにしています。
これでビデオ入力とオーディオ入力を平行して取得できていることを確認できます。
ではどういった表示にしようか企画を立てます。
当初の考えのとおり、フレーム取得ごとにフーリエ変換を行い周波数特性をグラフで示し
なおかつ時系列でサンプリングした値の合計値を表示したいと思います。
まず何も学習する必要のない時系列表示を行います。
次に示すコードは PXCMAudio.AudioData を受け取って、波形を表示するための画像バッファを作成するクラスです。
GetByteArray で取得した RGB 画像を表示すると次のとおりです。
選択したストリームとフォーマットは次のとおり
ストリームからオーディオ情報も正しく受け取れていることが確認できました。
さぁ、いよいよ機能拡張、変換、認識アルゴリズムを走らせるなどを行なう工程に入ります。
思ったより確認することが多かったのでここまでで話を切ります。
次回の更新をお楽しみに!
2014/05/02 初記。
2014/05/11 更新。