Last-modified: Sun, 13 Oct 2013 14:35:00 HADT
Counter:5424 Today:1 Yesterday:0 Online:7
このエントリーをはてなブックマークに追加

1.XNAでKinectのカメラ画像を表示する

About

result.png

 ここではKinectプログラミングへの導入として、Kinectで取得したカメラ画像をXNA上に表示する最小限のサンプルについて解説します。順を追って解説するために、以降の同コンテンツの内容はここで解説する内容を継承している点に注意してください。またこのページは次のコンテンツの内容を読了して、KinectプログラミングのためのVisualStudioプロジェクトを作成できるとしています。

  1. Kinect for Windows SDKのインストール
  2. Kinectプログラミングのためのプロジェクト作成

補足資料

Kinectへのアクセスの確保とカメラ画像取得機能の有効化

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

 まずKinectへのアクセスを確保するために「KinectSensorクラス」の変数を、受け取ったカメラ画像のデータをテクスチャとして表示するために「Texture2Dクラス」の変数をそれぞれ用意します。

        //Kinectへのアクセスを保存する
        KinectSensor kinect;

        //Kinectのカメラ画像を映すテクスチャ
        Texture2D tex2d_KinectCamera;

 続いてGameクラスのコンストラクタ内でKinectへのアクセスを確保して、さらにそのKinectからのカメラ画像のストリーム(取得)を有効化します。Kinectへのアクセスは「KinectSensor.KinectSensors[index]」で確保することができます。またアクセスを確保したKinectからの画像の取得を有効化するためには「kinect.ColorStream.Enable(ColorImageFormat)メソッド」を利用します。このとき「ColorImageFormat」の値によってKinectから受け取るカメラ画像のフォーマット(形式)を決定します。ここでは「ColorImageFormat.RgbResolution640x480Fps30」としていますが、これは「KinectからRGB形式で横640縦480の画像データを1秒間に30回受信する」という設定になっています。その他のフォーマットや用語の詳細については「カメラデータ・深度データのフォーマットの種類」を参照してください。

            //0番目に接続されたKinectへのアクセスを確保する
            kinect = KinectSensor.KinectSensors[0];
            //カメラ画像を取得するための設定
            kinect.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);

データ受信時の動作設定とKinectの開始

 アクセスを確保したKinectからデータが送られてくるとき「ColorFrameReadyイベント」が通知されます。Kinectのデータを利用した処理は、そのイベントが通知されたときに実行される(イベントハンドラ)ようにします。イベントハンドラの登録は次のソースコードのようにして行います。イベントハンドラの実装例については次の章で解説します。ここまでの処理の設定が終わったら、Kinectの動作を開始します。アクセスを確保したKinectの動作を開始するには「kinect.Start()メソッド」を呼び出します。

            //Kinectからカメラ画像が送られてきた時に実行されるイベントハンドラ(メソッド)を登録する
            kinect.ColorFrameReady +=
               new EventHandler<ColorImageFrameReadyEventArgs>(kinect_ColorFrameReady);
            //Kinectの動作を開始する
            kinect.Start();

テクスチャの初期化

 ここでテクスチャを先に初期化しておきます。「LoadContentメソッド」内で、次の様に適当な大きさのテクスチャとして初期化しておきます。これを行わなくても動作はしますが、後述する内容で問題が生じる可能性があります。

        protected override void LoadContent()
        {
            // 新規の SpriteBatch を作成します。これはテクスチャーの描画に使用できます。
            spriteBatch = new SpriteBatch(GraphicsDevice);

            //初期化しておく
            tex2d_KinectCamera = new Texture2D(GraphicsDevice, 1, 1);
        }

Kinectから受け取ったカメラデータの処理

 先述の通り、Kinectから受け取ったデータはまずイベントハンドラ内で扱われます。ここでは受け取ったデータの扱いとXNA上に表示するための処理と2つに分けて解説します。

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

 イベントハンドラ(のメソッド)は次のソースコードの様に実装されます。引数にある「ColorImageFrameReadyEventArgsクラス」の変数 e からKinectから送られたデータを取得することが出来ます。

 まずKinectから送られたデータを取得します。「e.OpenColorImageFrame()メソッド」によって「ColorImageFrameクラス」のインスタンスを取得することができます。取得したインスタンスに全てのKinectが取得したカメラ画像に関するデータの一切が含まれています。ここで注意したいのが、ColorImageFrameは必ず取得できるわけでは無い、ということです。何らかの原因で、Kinectがデータを取得できないことがあります。そのため、データを取得できなかった場合を想定する必要があるのです。データを取得できなかった場合、「e.OpenColorImageFrame()メソッド」の戻り値は null となります。ここでは null であった場合(Kinectがカメラ画像を送って来なかった)に return によって処理を抜ける様に実装しています。

        void kinect_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {

            //ColorImageFrameを取得する
            ColorImageFrame colorImageFrame = e.OpenColorImageFrame();

            //カメラ画像が正常に取得できていない時は処理を中断する
            if (colorImageFrame == null)
                return;

            //カメラ画像のbyte配列を保存するための変数を用意する
            byte[] kinectColorArray = new byte[colorImageFrame.PixelDataLength];

            //カメラ画像のbyteデータを全て保存する
            colorImageFrame.CopyPixelDataTo(kinectColorArray);

 続いて受け取ったデータからカメラ画像のデータを引き出して保存します。カメラ画像のデータはbyte型の配列となっています。そこで、同じ長さのbyte配列を用意して、そこにデータを保存するようにします。カメラ画像データのbyte配列の長さは「ColorImageFrame.PixelDataLengthプロパティ」によって取得することができます。用意したbyte配列にカメラ画像のデータを保存するには「ColorImageFrame.CopyPixelDataTo()メソッド」を利用します。引数に用意した配列を指定すると、その配列の中にKinectから送られてきたカメラ画像のデータが全て複製されます。これでKinectからカメラ画像のデータを受け取る所まで完了しました。

受け取ったデータをテクスチャにする

 受け取ったカメラ画像のデータをXNA上に表示しようと思います。しかしながら小さな問題があって、XNAでは表示するまでに手間がかかります。Kinectから受け取る画像データは「BGR(null)」であるのに対し、XNA4.0以降では「RGBA」しか対応しなくなったのです。そこで赤の成分(R)と青の成分(B)を入れ替える必要があります。入れ替えるためのソースコードは次のようになっています。ここでは2次元のテクスチャとして表示するのでついでに透過成分(α)の値も255に設定して透けない様にしておきます。

            //XNA上に表示するために、もう一つ同じ長さのbyte配列を用意する
            byte[] xnaColorArray = new byte[colorImageFrame.PixelDataLength];

            //RGBAの順序を入れ替える
            for (int i = 0; i + 3 < kinectColorArray.Length; i += 4)
            {
                xnaColorArray[i] = kinectColorArray[i + 2];//R
                xnaColorArray[i + 1] = kinectColorArray[i + 1];//G
                xnaColorArray[i + 2] = kinectColorArray[i];//B
                xnaColorArray[i + 3] = 255;//A
            }

            //テクスチャを初期化する
            tex2d_KinectCamera = new Texture2D
                            (GraphicsDevice,//デバイスの設定
                            colorImageFrame.Width,//表示するテクスチャの幅
                            colorImageFrame.Height);//表示するテクスチャの高さ

            //テクスチャに画像データをセットする
            tex2d_KinectCamera.SetData<byte>(xnaColorArray);

            //Kinectから受け取ったデータは使わなくなったら破棄する
            colorImageFrame.Dispose();

 RとBの反転が完了したら、先に用意したテクスチャにカメラのデータを設定します。テクスチャにbyte配列の画像データを設定するには「Texture2D.SetData<T>()メソッド」を利用します。引数にRGBA順にデータが並んだbyte配列を指定します。

 最後にKinectから受け取ったデータを破棄します。破棄しなくても動作はするのですが、利用しなくなったデータは破棄するようにしましょう。「ColorImageFrame.Dispose()メソッド」によって、受けっとたデータを破棄することが出来ます。

 先述の通り、Kinectからデータが送られなかった場合にこれらの処理は中断されています。先のように「LoadContentメソッド」内でテクスチャを初期化していなかった場合、テクスチャは null になることに注意してください。nullのテクスチャを描画しようとするとエラーが生じます。

Textureの描画とKinectの終了

 先の章までにKinectから受け取ったデータを設定したテクスチャを用意したので「Drawメソッド」内で描画します。ここでは一般的な2Dテクスチャの描画と同じように、SpriteBatchを使って描画します。

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();

            //左上の原点から生成したテクスチャを描画する
            spriteBatch.Draw(this.tex2d_KinectCamera, Vector2.Zero, Color.White);

            spriteBatch.End();

            base.Draw(gameTime);
        }

 Kinectは停止命令が実行されるまで、指定された間隔でデータを送り続けてきます。そこで、XNAゲームを終了する際にはKinectを停止するようにします。XNAのUnloadContentはゲームが終了するときに1度だけ呼ばれるメソッドです。そこで終了処理を実行することにします。「Kinect.Disposeメソッド」によってKinectへのアクセスを破棄します(解放した全てのストリームが閉じられ、確保したメモリ領域も解放されます)。これで全ての実装が完了しました。

        protected override void UnloadContent()
        {

            //Kinectへの接続が確立されていたら
            if (this.kinect != null)
            {
                //Kinectを停止して破棄します。
                kinect.Dispose();
            }
        }