Last-modified: Sun, 01 Jul 2012 15:07:20 HADT
Counter:5597 Today:1 Yesterday:0 Online:5
このエントリーをはてなブックマークに追加

3.深度情報を取得・可視化してXNA上に表示する

About

result.png

 このコンテンツはKinectプログラミングについて順を追って解説しています。未読の場合は先のコンテンツ「2.Kinectで骨格の座標を取得してXNAに表示する」を先に参照してください。ここではKinectを使ってユーザの深度情報を取得して、それを可視化します。

補足資料

Kinectへのアクセスの確保と深度情報取得機能の有効化

 Kinectのプロジェクトを作成し、インストールしたKinectSDKへの参照を追加しておきます。「Kinectプログラミングのためのプロジェクト作成」に掲載された内容です。読了していない場合は必ず参照してください。

 ここでは深度情報を取得するサンプルを取り扱いますが、深度情報とは奥行き方向を示す値であって画像ではありません。しかしながら画面上のある位置における深度がどれだけであるかを直感的に把握するためには、その深度情報を一律の規則で変換して画像として表示することが有効です。そのため深度情報を可視化、つまりある色情報へと変換し表示するためのテクスチャを用意しておきます。

        //深度情報を可視化するためのテクスチャ
        Texture2D tex2d_DepthCamera;

 Kinectへのアクセスの確保に必要な変数の用意やその取得方法はこれまでと同様なのでここでは説明を割愛します。まずは深度情報の取得(ストリーム)を有効にする設定を追加します。「KinectSensor.DepthStream.Enableメソッド」によって深度情報の取得を有効化します。メソッドで指定する「DepthImageFormat」とは、取得する深度情報のデータ形式を示し、丁度カメラ画像の表示で取り扱った「ColorImageFormat」と同様の考え方です。

 Kinectから受け取ったデータを処理する機構は、カメラ画像、骨格情報の場合と同様に、イベントハンドラによって実装されます。深度情報の場合は「DepthFrameReadyイベント」が通知されるので、そこにイベントハンドラを追加します。例によってイベントハンドラの中身については後述します。ここまで設定を終えた後、「Kinect.Startメソッド」によってKinectの動作を開始します。

            //深度情報を取得するための設定
            kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
            kinect.DepthFrameReady +=
                new EventHandler<DepthImageFrameReadyEventArgs>(kinect_DepthFrameReady);

            //ユーザ領域特定のため骨格座標の取得を有効化しておく
            kinect.SkeletonStream.Enable();

            //Kinectの動作を開始する
            kinect.Start();

 ここで骨格情報取得のための「KinectSensor.SkeletonStream」も有効化している点に注意してください。この設定によって後述するユーザの画素領域特定機能が利用できるようになります。

Kinectから受け取った深度情報の処理

 カメラ画像・骨格情報の処理と同様に、Kinectから受信した深度情報はまずイベントハンドラ内で扱われます。

Kinectから受け取ったデータとその扱い方

 イベントハンドラ(のメソッド)は次のソースコードの様に実装されます。カメラ画像の時は「ColorImageFrameReadyEventArgsクラス」骨格情報の場合は「SkeletonFrameReadyEventArgsクラス」でしたが深度情報では「DepthImageFrameReadyEventArgsクラス」の変数 e から、データを取得することが出来ます。

 基本的な扱いはカメラ画像・骨格情報の時と同様です。まず「e.OpenDepthImageFrameメソッド」によってKinectから送られてきた深度情報「DepthImageFrame」を受け取ります。例によって、Kinectが何かしらの影響で深度情報に関するデータを送信してこない場合があるので null の値を取った時は処理を中断します。深度情報は「short型の配列」によって与えられることに注意してください。よって深度情報を保存するための変数(ここではdepthDataArray)を用意しておきます。

 次にKinectから受け取った深度情報「DepthImageFrame」から実際のデータを保存します。用意したshort型の配列に「DepthImageFrame.CopyPixelDataToメソッド」を利用して深度情報を保存します。データの保存に十分な配列の長さは「DepthImageFrame.PixelDataLengthプロパティ」によって取得することができます。

        void kinect_DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e)
        {

            //DepthImageFrameを取得する
            DepthImageFrame temp = e.OpenDepthImageFrame();

            //深度情報が正常に取得できていない時は処理を中断する
            if (temp == null)
                return;

            //今回はIRカメラからの深度情報を解像度640*480で取得している
            short[] depthDataArray = new short[temp.PixelDataLength];

            //IRカメラ映像の深度情報のbyte配列
            temp.CopyPixelDataTo(depthDataArray);

 カメラ画像を保存する場合も、骨格座標を保存する場合も、深度情報を保存する場合も、殆ど同じような処理手順を踏んでいることに注目してください。受け取ったデータの処理を上手く行うことが重要であり、受け取るまでの仮定はどれも大差ありません。

 Kinectによって与えられる深度情報はshort型の配列であり、その値が示すものは深度情報そのものではないため、このままでは利用することができません。よって、まずは深度情報から深度値を取り出す必要があります。サンプルプログラムでは「ConvertDepthArrayToColorArrayメソッド」にその処理手順を記載しています。このメソッドでは取り出した深度値を可視化するための変換までを取り扱いますが、順を追って「深度値の取得」と「深度値を色情報へ変換する」と2つに分けて解説します。

short型の配列からプレイヤ識別値と深度値を取得する

 DepthFrameImageから受け取ったshort型の配列から取得できる値は2つあります。1つは「深度値」。2つ目は、IRカメラが深度を捕えたその画素にユーザが映っているか、という「プレイヤ識別値」です。これら2つの情報がshort型で与えられるわけですが、「bit操作」を利用してそれぞれを抽出する必要があり多少複雑な内容となっています。この取得方法に関しては深度情報(short[])のデータ構造と各値の取得方法詳細で詳細に解説しています。難しい場合はとりあえず以下のソースコードの用に書けば、「プレイヤ識別値playerIndex」と「深度値depth」とそれぞれが取得できるものだと覚えてしまっても良いと思います。

        private Color[] ConvertDepthArrayToColorArray(short[] depthDataArray)
        {

            //可視化のためにRGBAデータを保存します
            Color[] depthColorArray = new Color[depthDataArray.Length];

            //取得した深度情報の分だけ実行する
            for (int i = 0; i < depthDataArray.Length; i++)
            {

                //Player識別値の取得
                int playerIndex = depthDataArray[i] & DepthImageFrame.PlayerIndexBitmask;
                //深度値の取得
                int depth = depthDataArray[i] >> DepthImageFrame.PlayerIndexBitmaskWidth;

深度値から可視化画像を生成する

 このサンプルプログラムでは「ConvertDepthArrayToColorArrayメソッド」の内部で、データの取得とそれを色情報へ変換する処理を行っています。先の処理によって取得することができた深度値「depth」は最大で4095の値を取得します。そこでこの深度値を0~255の間に収める(正規化する)ことで、疑似的に色情報として扱うことにします。例えば4095の値を取るときは黒、0の値を取るときは白、中間値はその間を補間するような灰色を取るようにします。奥に行くほど黒くなって欲しいので深度値が4095を示す時、0になるような計算式を次のソースコード(108)の様に設定します。変換した色は、後でテクスチャに適用するのでColorの値の配列として保存しておきましょう。

      //可視化のためにRGBAデータを保存します
            Color[] depthColorArray = new Color[depthDataArray.Length];

            //取得した深度情報の分だけ実行する
            for (int i = 0; i < depthDataArray.Length; i++)
            {

                //Player識別値の取得
                int playerIndex = depthDataArray[i] & DepthImageFrame.PlayerIndexBitmask;
                //深度値の取得
                int depth = depthDataArray[i] >> DepthImageFrame.PlayerIndexBitmaskWidth;

                //深度値を正規化して0~255の値に収めます
                byte grayColor = (byte)(255 - (255 * depth / 4095));

                //ユーザの値が確認できなかった場合
                if (playerIndex == 0)
                {
                    //近すぎる場合は赤にする
                    if (depth == kinect.DepthStream.TooNearDepth)
                        depthColorArray[i] = new Color(255, 0, 0, 255);

                    //遠かった場合は緑にする
                    else if (depth == kinect.DepthStream.TooFarDepth)
                        depthColorArray[i] = new Color(0, 255, 0, 255);

                    //値が正常に取れない場合は青にする
                    else if (depth == kinect.DepthStream.UnknownDepth)
                        depthColorArray[i] = new Color(0, 0, 255, 255);

                    //正常に取れた場合は深度値に応じる
                    else
                        depthColorArray[i] =
                            new Color(grayColor, grayColor, grayColor, 255);
                }
                //ユーザの値が確認できた場合
                else
                {
                    depthColorArray[i] = new Color(255, 255, 0, 255);
                }
            }

            return depthColorArray;

 ここで少し細工をしています。Kinectから取得できる深度値は常に正常に取得できているものとは限りません。例えばKinectとの距離が近すぎて取得できなかった場合(TooNearDepth)、Kinectとの距離が遠すぎて取得できなかった場合(TooFarDepth)、反射などしてまったく推定できない場合(UnknownDepth)があります。そう行った場合は、その領域がハッキリと分かる様、色を付けることにします。またプレイヤ識別値が0以外、つまり対象となる画素に人物が映っている場合は、その領域を黄色にすることにします。

変換した色情報をテクスチャへ適用

 メソッドの説明が再び「kinect_DepthFrameReadyメソッド」に戻ります。以上の様な処理を経て、深度値を取得し、その深度値に応じた色情報を設定することができたので、得られた色情報をテクスチャに設定します。まずはテクスチャを深度値の解像度と同じサイズに初期化して、そこに「setData<Color>メソッド」によって先のColor型配列を設定します。

            //IRカメラ映像の深度情報のbyte配列
            temp.CopyPixelDataTo(depthDataArray);

            //深度情報を処理してテクスチャに画像として表示する
            tex2d_DepthCamera = new Texture2D(GraphicsDevice, temp.Width, temp.Height);
            //深度情報を変換して色情報にした後、テクスチャに設定する
            tex2d_DepthCamera.SetData<Color>(ConvertDepthArrayToColorArray(depthDataArray));

            //DepthImageFrameを破棄する
            temp.Dispose();

 後はこのテクスチャを今まで通りSpriteBatchにて描画すれば完了です。ImageFrameの破棄やKinect終了時の処理手続き等についてもここでは解説を割愛するので、忘れてしまった場合は前の資料を参照してください。