ぼくのUnityの使い方は、ゲームよりもセンサーなどを使ったコンテンツが多いので、センサーの座標系とUnityの座標系の変換方法が前からの課題でした。Unityユーザー助け合い所でやっと実現できる回答をもらったので、やってみました。
できたことは
「UnityにKinectのColor画像を表示させて、その上にBodyの関節位置を表示させる」
ことです。
プロジェクト一式はこちらに置いておきます
最終的には次の手順となりました。
- KinectのBodyの座標を取得する
- KinectのColorの座標系に変換する
- Unityのスクリーン座標系に変換する
- Unityのワールド座標系に変換する
それぞれの座標系をおさらいすると、Kinectは
- 3次元のCamera座標系
- 2次元のColor座標系
- 2次元のDepth座標系
の3つがあり、相互に変換できます。
Unityは
- 3次元のワールド座標系
- 2次元のスクリーン座標系
- 2次元のビューポート座標系
の3つで、やはり相互に変換できます。
ここで2次元のColor/Depth座標系とスクリーン座標系の変換ができれば、相互に座標を変換することができそうです。
しかし、KinectのColor/Depthの解像度とスクリーン座標のカメラの座標を合わせる必要があるため、悩んでいたのですが「変換のためのカメラを別に用意する」というアイデアをもらいました。
というわけで、変換用のカメラを用意しました。これで
- KinectのBody(Camera座標)を取得する
- Kinect SDKのCoordinateMapper.MapCameraPointToColorSpace()でCamera座標をColor座標に変換する
- Kinectのカラー座標(1920x1080)をUnityのスクリーン座標(Screen.width,Screen.height)に比率変換する
- Untiyのスクリーン座標をCamera.ScreenToWorldPoint()でワールド座標に変換する
ことができました。
なお、ScreenToWorldPointするためのカメラはProjectionをOrthographicにする必要があります。
コード的にはこんな感じです
今回はKinect v2でやりましたが、Webカメラでの顔検出などにも使えるかと思います。
Depthの座標を変換する場合にはMapCameraPointToColorSpaceをMapCameraPointToDepthSpaceにしてください。
同じようなことをLeap Motionもやっていますが、座標変換自体は、Leap Motionの原点に相当するHandControllerを用意し、Local->World座標の変換を行っているようです。カメラ画像との位置合わせのやり方まで終えてませんが、もっとよい方法がありそうです。