自習室

こもります

Kinect for Windows SDK 2.0 Unity Pro Add-in で シンプルなデプス画像を表示する

はじめに

f:id:AMANE:20140808103334p:plain

Microsoftから配布されている Kinect for Windows SDK 2.0 Unity Pro Add-in には

  • カラー画像
  • 赤外画像
  • デプスにカラー画像をマッチングさせた立体画像
  • 人のボーン表示

を表示するサンプルシーンが含まれているのですが、デプスを単純にビットマップで表示するサンプルがありません。UnityでKinect v2 を使ってみる手慣らしとして作ってみました。結果が上の写真になります。

基本的なところ

Kinect for Windows v2

Kinect for Windows v2 良いですね。先代からの変化点などは、以下のサイト様が非常に詳しいです。いつもお世話になっております。

「Kinect for Windows SDK 2.0 Public Preview」が公開されています - Natural Software

上記事様のスライドが非常に良くまとまっています。

環境

SDK(およびドライバ)導入

Unityで使える様にするために、まずはWindowsからアクセス出来るようドライバ等をインストールし、機器を認識させます。

Kinect v2 をWindowsに認識させるところで手こずっている方が結構いらっしゃるようです。私はこちらのサイト様を参考に、USB3.0 カードを追加することで、Kinect v2 を認識させることに成功しました。

正しく認識できていたら、Kinect Studio などを動かして、デプス画像が表示されるか確認してみましょう。

Kinect for Windows SDK 2.0 Unity Pro Add-in を導入する

おなじみNatural Software様 の記事通りでOKです。まずは、サンプルのシーンが正しく表示されるか確認すると良いでしょう。

私の環境では、DLしたzipを Cube ICE を使って解凍したところ、エラーが出てファイルが正しく展開されませんでした。Windows標準の解凍機能を使ったら、すべてのファイルが展開されました。

シンプルなデプス画像

デプス情報の取得部

デプスの情報をセンサーから取得する部分については、 配布物に含まれて居る(上記サンプルシーンでも使われている) DepthSourceManager.cs が行ってくれますので、それをそのまま流用します。ヒエラルキに空のオブジェクトをひとつ追加し、それに scripts/DepthSourceManager.cs をアタッチします。(下図)

f:id:AMANE:20140808110048p:plain

デプス情報の表示部

ここではデプス情報をUnityのシーン内に描画するために、以下の事を行っています。

  • 表示にはQuadメッシュのゲームオブジェクトを利用し、テクスチャとしてデプス画像を貼り付けることにする。
  • 毎フレーム以下の事をする
    • ushort 形式でデプスデータを取得する
    • 取得したデプスデータを byte 形式に変換し、更にテクスチャを生成する
    • テクスチャを、ゲームオブジェクトのマテリアルに貼り付ける
ゲームオブジェクトの生成

できあがったゲームオブジェクトは下図のようになります。一個ずつ説明します。

f:id:AMANE:20140808112421p:plain

  • ヒエラルキにQuadのオブジェクトを作ります
  • 画像を表示したいだけなので、ライティングの影響を受けない Unlit/Texture のマテリアルを作成し、アタッチします
  • 正しい表示になる様に、オブジェクトを回転させます。ここではZ軸中心に180度回しています。
  • Scaleは (1, 1, 1) のままだと小さいのですが、スクリプトから変更します。縦横比は固定なので、インスペクタから直接編集してもOKです。
  • 以下に示すスクリプトをアタッチします
using UnityEngine;
using System.Collections;
using Windows.Kinect;

public class SimpleDepthView : MonoBehaviour {

  public GameObject depthSourceManager;
  private DepthSourceManager depthSourceManagerScript;
  
  Texture2D texture;
  byte[] depthBitmapBuffer;
  FrameDescription depthFrameDesc;

  public float scale = 1.0f;
  
  void Start () {
    // Get the description of the depth frames.
    depthFrameDesc = KinectSensor.GetDefault().DepthFrameSource.FrameDescription;

    // get reference to DepthSourceManager (which is included in the distributed 'Kinect for Windows v2 Unity Plugin zip')
    depthSourceManagerScript = depthSourceManager.GetComponent<DepthSourceManager> ();

    // allocate.
    depthBitmapBuffer = new byte[depthFrameDesc.LengthInPixels * 3];
    texture = new Texture2D(depthFrameDesc.Width, depthFrameDesc.Height, TextureFormat.RGB24, false);

    // arrange size of gameObject to be drawn
    gameObject.transform.localScale = new Vector3 (scale * depthFrameDesc.Width / depthFrameDesc.Height, scale, 1.0f);
  }
  
  void Update () {
    updateTexture ();
    gameObject.renderer.material.mainTexture = texture;
  }

  // referred the code below. thx kaorun55-san
  // https://github.com/kaorun55/Kinect-for-Windows-SDK-v2.0-Samples/blob/master/C%23(WinRT)/02_Depth/KinectV2-Depth-01/KinectV2/MainPage.xaml.cs

  void updateTexture() {
    // get new depth data from DepthSourceManager.
    ushort[] rawdata = depthSourceManagerScript.GetData ();

    // convert to byte data (
    for ( int i = 0; i < rawdata.Length; i++ ) {
      // 0-8000を0-256に変換する
      byte value = (byte)(rawdata[i] * 255 / 8000);

      int colorindex = i * 3;
      depthBitmapBuffer[colorindex + 0] = value;
      depthBitmapBuffer[colorindex + 1] = value;
      depthBitmapBuffer[colorindex + 2] = value;
    }

    // make texture from byte array
    texture.LoadRawTextureData (depthBitmapBuffer);
    texture.Apply ();
  }
}

画像の階調表現のはなし

黒から白へ256段階のグラデーションで全域を表現する

ポイントは、uShort 形式のデプス情報を byte 形式の RGBA 配列に圧縮しているところで、ここは、Natural Software様のこちらの記事を参考にさせていただきました。

http://www.naturalsoftware.jp/blog/8846

この変換だと、 0mm - 8000mm を8000階調で返してくるKinect for Windows v2 のデプスを、黒(0) - 白(255) にマップしています。 諧調が落ちてしまいますが、遠近感が分かりやすい表示になります。

f:id:AMANE:20140808132247p:plain

色の折り返しを許容して、グレースケースで8000階調を再現する

上記も良いのですが、せっかくKinect v2が8000階調でデータを渡してくれるので、それを再現します。256段階のグレーのグラデーションを繰り返していきます。

f:id:AMANE:20140810195913p:plain

これで、8000階調の本領が発揮されます。写っているのが私で申し訳ないのですが、先ほどはわからなかった服のしわなどが再現されているのがわかるかと思います。

ソースコードの変更点は以下のような感じです。

void updateTexture() {
  // get new depth data from DepthSourceManager.
  ushort[] rawdata = depthSourceManagerScript.GetData ();

  // convert to byte data (
  for ( int i = 0; i < rawdata.Length; i++ ) {
    // 0-8000を0-255に変換する
    // byte value = (byte)(rawdata[i] * 255 / 8000);
    // 0-8000 を色の折り返しを許容して256段階にマップする
    byte value = (byte)(rawdata[i] % 256); // ★変更点

    int colorindex = i * 3;
    depthBitmapBuffer[colorindex + 0] = value;
    depthBitmapBuffer[colorindex + 1] = value;
    depthBitmapBuffer[colorindex + 2] = value;
  }
  // make texture from byte array
  texture.LoadRawTextureData (depthBitmapBuffer);
  texture.Apply ();
}

8000階調をHSV表色系でHueを回して表現する

上記だと255->0となるときにパキッと色が折り返してしまうので、その違和感をなくすためにHSV表色系を利用してみます。色相hueを虹のようにぐるぐる回すことで連続的なデプス変化を表現できます。

f:id:AMANE:20140810200934p:plain

色のパキッとした折り返しがなく、かつ階調も細かくデプスが表現されているのでは、と思います。

ソースコードの変更点は以下のような感じです。

using UnityEditor; // HSVToRGB()のためにusingを追加

// 中略

void updateTexture() {
  // get new depth data from DepthSourceManager.
  ushort[] rawdata = depthSourceManagerScript.GetData ();
  
  // convert to byte data (
  for ( int i = 0; i < rawdata.Length; i++ ) {

    // HSVで色相をぐるぐる回す
    int value = (int)rawdata[i] % 360;

    Color color = EditorGUIUtility.HSVToRGB( (float)value/360.0f, 0.5f,  1.0f);
    int colorindex = i * 3;
    depthBitmapBuffer[colorindex + 0] = (byte)(color.r * 255.0f);
    depthBitmapBuffer[colorindex + 1] = (byte)(color.g * 255.0f);
    depthBitmapBuffer[colorindex + 2] = (byte)(color.b * 255.0f);
  }
  
  // make texture from byte array
  texture.LoadRawTextureData (depthBitmapBuffer);
  texture.Apply ();
}

最後に

やってみましたが、色の変化は、グレースケースの変化に比べると、すこし変化がわかりにくいかなぁ、という印象を持ちました。2番目にやったものの方が、服のしわや指の一本一本内のデプス変化がわかりやすい感じがします。

こういった画像でのデプス表示はあくまでデバッグ用だったり、目的の途中段階だったりすると思うので、用途に応じて使い分ければよいと思います。