自習室

こもります

Kinect for Windows SDK 2.0 Unity Pro Add-in でポイントクラウド風描画

はじめに

前回は、Unity上でシンプルなデプス画像を生成し、矩形にテクスチャとして貼り付けて表示しました。

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

今回は、Kinect for Windows SDK 2.0 付属の Kinect Studio で見られるような、取得したデプスデータを空間的に配置するビューワを作ってみます。できあがりは以下のような感じです。

ポイントクラウド風に表示すること自体はゲームでの利用や解析上は余り意味がなさそうですが、表現としてはカッコいいですよね。この手の表現を使ったもので好きなPV作品に、以下の様な物があります。これらの表現ではデプス画像に色を加えた上に、わざとノイズを加えて揺らしたり、ばらばらにして次のフレームに繋ぐ様な表現を加えてあって素敵です。

Radiohead - House of Cards - YouTube

ヴィーナスとジーザス - YouTube

手順

Kinect for Windows v2 および Unity Add-in の利用については、前記事で触れました。得られたデータを使ってポイントクラウド風に表示する手順は大まかに以下の3ステップになります。

  • Depth画像の各ピクセルを KinectSensor.CoordinateMapper.MapDepthFrameToCameraSpace() 関数を使って、3次元空間上のポイントに座標変換する
  • 3次元空間上のポイントをUnityのParticleSystemのParticleとして利用する
  • 素敵に描画する

座標変換

まず事前にKinectのCoordinateMapperが何をしているのかを整理した上で、今回行っている処理について説明します。

KinectSensor.CoordinateMapper についての理解

Kinectが出力・利用する座標系として以下の三つがあります。*1

  • CameraSpace : 3次元の(カメラによる射影前の)普通の座標系
  • ColorSpace : RGBカメラによって射影された、RGB画像の座標系
  • DepthSpace : デプスカメラによって射影された、DEPTH画像の座標系

これらの理解のためにこちらの記事を参考にさせていただきました。

Understanding Kinect Coordinate Mapping | Vangos Pterneas

たとえばボーントラッキングで bodyのjointsをとってくると、関節の座標joint.PositionCameraSpacePoint クラスで与えられます(便利!)。

RGBカメラ画像に対して人物のボーンやキャラクタのCGを重畳してゲームをするようなKinectの一般的な用途においては、CameraSpacePoint で得られた3次元的な関節座標を、RGBカメラの射影された座標系 ColorSpacePoint に射影する変換が必要になります。この場合は、

// cameraPointが、1関節の座標
// KinectSensor _sensor = KinectSensor.Default;
ColorSpacePoint colorPoint = _sensor.CoordinateMapper.MapCameraPointToColorSpace(cameraPoint);

のように、変換した後、colorPointRGB画像の上にCanvasなどで重ねて描画すれば、人とボーンのCGがきれいに重なります。

f:id:AMANE:20140812101428j:plain

(picture above is referred to from the origimal post at http://pterneas.com/2014/05/06/understanding-kinect-coordinate-mapping)

そのほかの CoordinateMapper クラスの関数の一覧はこちら。

CoordinateMapper Methods

デプス画像をUnityで使える3次元座標に変換する

さて上記を踏まえてここでは、Unityの世界にポイントクラウドを配置するために、(レンズを通ってカメラで射影された)DepthSpaceのデプス情報をCameraSpaceに変換します。

変換は以下の様に行います

// ushort[] rawdata が、DepthSourceManagerから受け取るデプス画像の生値
// KinectSensor _sensor = KinectSensor.Default;
CameraSpacePoint[] cameraSpacePoints = new CameraSpacePoint[depthWidth * depthHeight]; // 3次元空間上の点の配列
_sensor.CoordinateMapper.MapCameraPointToColorSpace(rawdata, cameraSpacePoints);

これで、cameraSpacePoints に、変換された座標が配列として格納されます。

3次元空間上のポイントをUnityのParticleSystemのParticleとして利用する

多数の点群をUnity上で一気に描く極力Unityらしい手法として、ParticleSystemに外部から点群を指定することが出来れば良いのじゃないか?と思い調べてみたところ、以下のページが非常に参考になりました。このページでは、数式で点群を生成してそれをビジュアライズするために UnityのParticleSystemを利用しています。

Graphs, a Unity C# Tutorial

ここで行っていることをざっくりまとめると以下の様な感じです。

  • Rendererだけを持つほぼすっからかんのParticleSystem オブジェクトをつくる
  • いかの処理を行うスクリプトを作り、上記オブジェクトにアタッチする
    • ★なにかしらのアルゴリズムで点の座標群を生成する
    • その点群を ParticleSystem.Particle[resolution] 配列の座標として格納する
    • Particleごとの大きさと色も指定する
    • Updateごとに particleSystem.SetParticles(points, points.Length); する

コードは最後にまとめて記載します。

素敵に描画する

ParticleSystemに唯一残したRendererで、描画の指定を行います。各ParticleをBillboardで表示する際に、一つ一つの矩形にマテリアルを貼り付けます。マトリックス的な表現?にする為に、 Standard Assets/ParticleSystem に含まれている Sparkle2.mat を利用してみました。 Particle/Additive のシェーダに暗めの緑色を指定しておくと、重なったところが白くサチって、発光している様な雰囲気になります。

見た目の調整のために、インスペクタからパーティクルの色と大きさをコントロールできるようにしておきました。(下記コード参照)

完成

以上で完成です。利用したコードを改めてまとめて記載しておきます

// DepthParticlize.cs

using UnityEngine;
using System.Collections;
using Windows.Kinect;

public class DepthParticlize : MonoBehaviour {

  // FROM KINECT v2
  public GameObject depthSourceManager;
  DepthSourceManager depthSourceManagerScript;
  FrameDescription depthFrameDesc;
  CameraSpacePoint[] cameraSpacePoints;
  CoordinateMapper mapper;

  private int depthWidth;
  private int depthHeight;

  // PARTICLE SYSTEM
  private ParticleSystem.Particle[] particles;

  // DRAW CONTROL
  public Color color = Color.white;
  public float size = 0.2f;
  public float scale = 10f;

  void Start () {

    // Get the description of the depth frames.
    depthFrameDesc = KinectSensor.GetDefault().DepthFrameSource.FrameDescription;
    depthWidth = depthFrameDesc.Width;
    depthHeight = depthFrameDesc.Height;

    // buffer for points mapped to camera space coordinate.
    cameraSpacePoints = new CameraSpacePoint[depthWidth * depthHeight];
    mapper = KinectSensor.GetDefault ().CoordinateMapper;

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

    // particles to be drawn
    particles = new ParticleSystem.Particle[depthWidth * depthHeight];
  }

  void Update () {
    // get new depth data from DepthSourceManager.
    ushort[] rawdata = depthSourceManagerScript.GetData ();
    // map to camera space coordinate
    mapper.MapDepthFrameToCameraSpace (rawdata, cameraSpacePoints);

    for (int i = 0; i < cameraSpacePoints.Length; i++) {

      particles[i].position = new Vector3(cameraSpacePoints[i].X * scale, cameraSpacePoints[i].Y * scale, cameraSpacePoints[i].Z * scale);
      particles[i].color = color;
      particles[i].size = size;
      if( rawdata[i] == 0 ) particles[i].size = 0;
    }

    // update particle system
    particleSystem.SetParticles (particles, particles.Length);
  }
}

また、デプス情報を適用してポイントクラウドを描くParticleSystemオブジェクトのインスペクタは下のような感じになります。前回同様、Depth画像のデータを取得する DepthSourceManager を、DepthParticlilze 内で読み込めるように指定する点にお気をつけください。

f:id:AMANE:20140812235928p:plain

最後に

問題点(1) モアレ

f:id:AMANE:20140813002349p:plain

CoordinateMapper.MapDepthFrameToCameraSpace() した結果、ウィンドウサイズやParticleの大きさ次第でモアレが発生します。これは、座標変換の結果、元々グリッド状に均一に整列してたピクセルが、所々不均一に並べ直されるために発生します。特に本来平面な部分に顕著なようです。

問題点(2) フレームレート

今回はデプスデータが 512x424 点もあるため、私のPC環境では 10fps ほどになってしまいました。Updateごとにデプス画像を取得しているので遅延が蓄積するようなことはありませんが、スピードを重視する場合は、先にデプス画像を間引きしてからParticleSystemに食わせるようにした方が良いかもしれません。

また、今回やった処理より高速になる手法をご存じの方がいらっしゃいましたら、是非教えていただきたいです! ><

アート的な方面に活用したい

今回はただポイントを描画して終わりましたが、もう少しアートっぽいことにもチャレンジしてみたいです。

*1:SDK1.8まではCamera Spaceの代わりにSkelton Spaceと呼ばれる座標系があり、座標変換用に別の関数群が用意されていたようですが、一新されたようです http://msdn.microsoft.com/en-us/library/hh973078.aspx