Top > Kinect > KinectForWindowsSDK > Tips > PollingModelOrEventModel
Last-modified: Thu, 12 Jul 2012 22:07:05 JST
Counter:4836 Today:2 Yesterday:4 Online:7
このエントリーをはてなブックマークに追加

ポーリング型実装かイベント型実装か

About

 Kinectを利用する実装方法については2種類あって、1つがNearealで多く紹介している"イベント型"、もう1つが"ポーリング型"です。それぞれ概要は次の様になっています。

  • イベント型
    • "Kinectからデータが送られてくる"というイベントが発生した際に実行する処理を事前登録しておく
  • ポーリング型
    • Kinectに対して任意の(あるいは継続的な)タイミングでデータの送信を要求する

 見ての通りそれぞれの実装方法は大きく異なります。具体的にどのように実装の住み分けがされるのかを考えてみます。

イベント型

 Kinectがデータの送信準備を完了した段階で無差別にデータを受け取るタイプの実装方法です。Nearealで多く紹介しているタイプの実装方法でもあります。

  • イベント型の実装が有効な場合
    • GUIアプリケーションなどで経過時間に伴った連続的な処理を実装する場合に有効
      • Ex.WPFやWindowsFormsを用いて、カメラ画像を連続して取得し、動画として再生する実装
  • ポーリング型で同じことをする
    • スレッドやタイマなどを利用して定期的にフレームを取得する様に実装する
      • スレッドやタイマなどを任意に用意する必要があるので実装面での手間がかかる
    • 非効率な処理を行う場合がある
      • スレッドやタイマの設定によってはKinectの最大周波数である30FPSを超えるタイミングでデータの送信を要求する可能性がある
      • 無意味に送信を要求する分非効率であるし、逆に30FPSに満たない設定を行ってしまう可能性もある。
      • ※とは言え調整すれば解決する可能性は十分にある

ポーリング型

 Kinectに対して任意のタイミングでデータの送信を要求するタイプの実装方法です。

  • ポーリング型の実装が有効な場合
    • あるタイミングでのデータのみ必要とする処理を実装する場合に有効
      • Ex.ボタンを押した時のみデータが取得できれば良いアプリケーションの実装
  • イベント型で同じことをする
    • ボタンが押された時点までに取得・更新された最新のフレームを利用する
      • 厳密にはボタンが押されたタイミングとは異なるフレームになる可能性がある
      • ※とは言えコンマ数秒なので誤差は少ない
      • ※連続して実行されるのであれば、ポーリング型でも最新であることが保証されるかは不明
    • 非効率な処理が発生する
      • ボタンが押されない間、不要なデータの送受信を最大30FPS行い続けるので非効率である

XNAにおける実装形態の検証

 XNAはKinectアプリの開発環境として多く用いられていますが、XNAはゲーム開発用フレームワークだけあって、あらかじめループ構造(定期更新する構造)が提供されています。XNAでは2012年現在で一般的な更新速度である(凡そ)60FPSを標準の更新速度としています。「Gameクラス」に実装される「Updateメソッド」および「Drawメソッド」が更新時に呼ばれるメソッドであり、つまり1秒間に60回程度、Updateメソッドが呼ばれる様な構造をとっています。

「Updateメソッド」は通常、ゲームロジックの更新などに利用されるメソッドです。どちらも殆ど60FPSで実行されますが描画を担当するDrawメソッドとは役割が異なります。

 Updateメソッド内で、ゲームロジックの更新にあわせて、Kinectからその時点で最新のデータを取得するのがもしかしたら実装方法としては自然かもしれません。またポーリング型実装はイベント型と異なり、イベントハンドラとして登録するメソッドを実装する"必要"が無いので、ソースコードの見た目もスッキリします。

 しかしながら現在MSKinectSDKで提供・設定される、Kinectからのフレーム取得速度は最大で30FPSです。つまり、Updateメソッド内でポーリングを行うと、単純計算2回に1回は失敗することが前提となるポーリングを行う必要が出てきます。

Kinect:30FPS ÷ XNA:60FPS = 1/2

 確かにXNAの更新に合わせて、その時点で最新のフレームを取得することはできますが、もしフレームの取得に失敗(厳密には取得した結果がnullである)した場合、その(XNAの更新タイミングでの)処理をスキップするか、直前までに得られた最新のフレームによって処理を実行するかの、いずれかになります。

 先述の通り、更新速度の差から非効率なポーリングを行う上、しかも更新時に最新のフレームが確実に取得できる保証はないので、XNAでの実装においてポーリングが最良の選択とは言い難いです。

 とは言え、イベント型の実装においても「nullチェック」は必要になるわけです。条件文が一つ入るという実装形態はポーリング型においてもイベント型においても同様で、しかもポーリングの処理負荷がそれほど大きいものでない様なので、実装形態として自然で、かつイベントハンドラのためのメソッドを用意しなくても良い(=ソースコードの見た目がスッキリする)ポーリング型を選択してはならない、ということは無いと思われます。

 ポーリングに成功した時のロジックを別のメソッドに分離して管理する様であれば、必要なメソッド数はイベント型と同じになります。つまりイベントハンドラに登録するメソッドを用意するのと同じ状況になる、ということです。

 Kinectが更新に成功した時(=ポーリングに成功した時)に実行される、という処理を実装したい場合はポーリング型の方が良いです。イベント型で同様の実装を行う場合、先の実行から今の実行までに、更新されたかどうかを判別するための処理と変数を用意する必要が生じます。

更新タイミングの検証

 Kinectの更新タイミングとXNAの更新タイミングは残念ながら常に1:2とは限りません(確かにほぼ1:2になりますが)。それを検証から確認しました。検証方法にミスがある可能性はありますが、少なくとも1:2で同期されることの証明もできないでしょう。

        protected override void Update(GameTime gameTime)
        {
            // ゲームの終了条件をチェックします。
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: ここにゲームのアップデート ロジックを追加します。
            ColorImageFrame temp = kinect.ColorStream.OpenNextFrame(0);
            if (temp != null)
            {
                tex2d_Camera = temp.GetTexture2D(GraphicsDevice);
                temp.Dispose();
                PollingCount++;
            }

            UpdateCount++;

            Console.WriteLine(PollingCount+","+UpdateCount);

            base.Update(gameTime);
        }

 ColorImageFrameにnullが返らず、正常にカメラ画像データを取得できた場合にポーリングが成功したとみなし、PollingCountを数え上げます。更新数UpdateCountは、Updateメソッドが実行される度に常に数え上げられる仕様です。今回はログを取るためにコンソールに書きだしてデバッグで確認することにしました。厳密にはヨロシクないのでしょうが、そこまで拘るべき検証内容では無いのでまぁ良しにします。

結果・Result

0.png

 誤差を減少させるため、少なくとも1000回以上の更新を幾度か繰り返しましたが、およそ図の様な結果が得られます。「,」左側がポーリングに成功した回数、右側がXNAの更新回数です。出だしが謎ですが何度やっても出だし5フレーム程度は1:1で結果が得られています。しかしそれを差し引けばXNAの更新100フレーム目辺りまでにはKinectの更新とXNAの更新が1:2になることが分かります。

 それでも繰り返し実行しているうちに徐々に更新がズレていき、1000フレーム周辺では、1:3(図中の赤)であるとか、それを補う様に1:1(図中の青)であるとか、そういうフレームが現れるようになります。更にその現象が生じる組み合わせ(赤と青の出現頻度)が1:1で現れている様だ、ということも分かります。

 最後のフレームを見ても面白いことが分かります。頭の5フレームを差し引くと更新比率はKinect:XNA=500:955となります。つまりXNAの2回の更新に対して1回以上の割合でKinectが更新されている様に見えます。これはXNAが厳密には60FPSで回っているわけではない(TV=59.94)ことによって生じる現象だと思われますが、ここでは言及も検証もしません。

結論・Conclusions

  • Kinectのデータを連続的に処理する場合はイベント型実装が良い
  • ある任意のタイミングでKinectのデータが必要な場合はポーリング型実装良い
  • XNAではシンプルな実装をするのであればポーリング型の方が見た目スッキリ
    • メソッドに分離するのであれば好みの問題
      • ただし軽負荷とは言え非効率なポーリングをしているのは間違いない

References