Unity上で、Kinect v2カメラを使って取得したカラー映像に、スケルトン(骨格情報)を重ねて表示する方法を紹介します。使いまわしやすいUnity用コードがネット上に見つからなかったので作成したので公開しておきます。
Kinect v2 with MS-SDKアセットには何かと便利な機能が揃っているので、このアセットをベースに開発を行うことにします。ただし、スケルトンをオーバーレイ表示する機能が存在しないため、その部分だけ自作して利用することにしました。
まずは、Kinect v2 with MS-SDKアセット(20ドル)を購入しインポートします。
スケルトンを描画する用のスクリプトを用意しましたので、新規にc#スクリプトを作成しコピペします。スクリプト名をKinectSkeletonOverlay_Psychic.csとしておきましょう。
using UnityEngine; using System.Collections; using System; using System.Collections.Generic; using Windows.Kinect; public class KinectSkeletonOverlay_Psychic : MonoBehaviour { public GUITexture displayGui; //スケルトン表示用 private Texture2D displayTexture; private KinectInterop.BodyData bodyData; private KinectInterop.SensorData sensorData = null; private KinectSensor _sensor; // Use this for initialization void Start () { _sensor = KinectSensor.GetDefault(); displayTexture = new Texture2D(1920,1080); } // Update is called once per frame void Update () { //Kinectマネージャーのインスタンスを取得 KinectManager manager = KinectManager.Instance; //カラーマップ取得(カメラ撮影のカラー画像) if (displayGui && (displayGui.texture == null)) { displayTexture = manager.GetUsersClrTex(); } //センサーデータをDrawSkeleton2関数内で利用できるように取得しておく sensorData = manager.GetSensorData(); //人数分ループ for (int i = 0; i < manager.GetUsersCount(); i++) { bodyData = manager.GetUserBodyData(manager.GetUserIdByIndex(i)); DrawSkeleton2(displayTexture, ref bodyData); displayTexture.Apply(); } displayGui.texture = displayTexture; } //指定のテクスチャにスケルトンを追加描画 private void DrawSkeleton2(Texture2D aTexture, ref KinectInterop.BodyData bodyData) { //関節でループ int jointsCount = sensorData.jointCount; for (int i = 0; i < jointsCount; i++) { int parent = (int)sensorData.sensorInterface.GetParentJoint((KinectInterop.JointType)i); if (bodyData.joint[i].trackingState != KinectInterop.TrackingState.NotTracked && bodyData.joint[parent].trackingState != KinectInterop.TrackingState.NotTracked) { //3次元座標をカラーマップ座標に変換 //CamraSpacePointはメートル座標 //ColorSpacePointはカラーマップ(フルHD)座標 CameraSpacePoint posParent_cameraSpace = new CameraSpacePoint(); posParent_cameraSpace.X = bodyData.joint[parent].kinectPos.x; posParent_cameraSpace.Y = bodyData.joint[parent].kinectPos.y; posParent_cameraSpace.Z = bodyData.joint[parent].kinectPos.z; ColorSpacePoint posParent_colorPoint = _sensor.CoordinateMapper.MapCameraPointToColorSpace(posParent_cameraSpace); Vector2 posParent = new Vector2(posParent_colorPoint.X, posParent_colorPoint.Y); CameraSpacePoint posJoint_cameraSpace = new CameraSpacePoint(); posJoint_cameraSpace.X = bodyData.joint[i].kinectPos.x; posJoint_cameraSpace.Y = bodyData.joint[i].kinectPos.y; posJoint_cameraSpace.Z = bodyData.joint[i].kinectPos.z; ColorSpacePoint posJoint_colorPoint = _sensor.CoordinateMapper.MapCameraPointToColorSpace(posJoint_cameraSpace); Vector2 posJoint = new Vector2(posJoint_colorPoint.X, posJoint_colorPoint.Y); if (posParent != Vector2.zero && posJoint != Vector2.zero) { //関節情報が飛んでる場合には骨を描画せず if (float.IsInfinity(posParent.x)) { break; } if (float.IsInfinity(posParent.y)) { break; } if (float.IsInfinity(posJoint.x)) { break; } if (float.IsInfinity(posJoint.y)) { break; } //KinectInteropに用意されている機能を使い骨を描画 KinectInterop.DrawLine(aTexture, (int)posParent.x, (int)posParent.y, (int)posJoint.x, (int)posJoint.y, Color.yellow); } } } } }
余談ですが、Kinect v2 with MS-SDKアセットにはDrawSkeletonという関数が用意されています。この関数は良く出来ていて、Depth情報を元に人以外の背景を切り抜いてくれたりするんですが、Depth情報を元にしているため画角が512×424サイズのテクスチャとしてしか取得できなかったり、背景切り抜きをオフに出来なかったりと、少し使い勝手が悪かったりします。
メモ
bodyData.joint[i].kinectPos.xで取得できる座標はメートル単位のデータなので、これをカラーマップ座標系(1920×1080)に変換する必要がある。
変換に必要なMapCameraPointToColorSpace関数はKinect v2 with MS-SDKアセット内でラッパー関数が用意されていないので
using Windows.Kinect;でKinectのSDKを呼び出し
private KinectSensor _sensor;
_sensor = KinectSensor.GetDefault();
_sensor.CoordinateMapper.MapCameraPointToColorSpace()として利用する必要あり
このあたりの処理をきっちりやらないとボーンが実際の映像とずれたりします。
続いて、下記を参考にシーンの準備をします。
実行するとこんな感じでカラーマップ(カラー映像)にスケルトンのボーンがオーバーレイ描画されます。
(散らかった部屋と、タイマー撮影の方法がわからず左手でPrintScreenを押してるのはご愛嬌)
・KinectSkeletonOverlay_Psychic.cs
・テスト用のシーン
・各種必要GameObject及び設定
をまとめたPackageを作成しておきましたので公開しておきます。
「Kinect v2withMS-SDKアセット」は含みませんので「Kinect v2withMS-SDKアセット」をインポートした後に下記パッケージをインポートして実行してください。
KinectV2SkeletonOverlay.unitypackage
ついでに、Kinect v2withMS-SDKを利用して、スケルトンの3次元情報をそのままUnityの3D空間で利用する際のハマりどころの解説をしておきます。
Kinect v2withMS-SDKアセットは[続]Kinect v2でユニティちゃんを動かすで紹介したように、Kinectで取得した骨格情報をMechanimで利用することが簡単にできます。この機能は良く出来過ぎていて、下記の機能が実装されています。ホネホネ人間はCubemanといいます。
これは、非常に便利で有意義な機能なのですが、Kinectが取得した3次元情報をそのまま利用せずに、人体を認識した瞬間の場所をoffset情報として相対的な座標計算に用いています。1人分の情報しか利用しない場合はいいのですが、ふたり以上の人物の情報を同時に扱う場合はには、二人の各関節の場所が絶対座標ではなく、offsetを元に相対的な座標であるため問題が発生します。具体的には二人が手を合わせてたとしても、それぞれ別のoffsetが聞いていて、Cubemanの手が合いません。
CubemanController.csの163行目のoffset処理している箇所をコメントアウトすることでこの問題を回避することが可能です。
//CubemanController.cs //163行目 //transform.position = initialPosOffset + (verticalMovement ? posPointMan : new Vector3(posPointMan.x, 0, posPointMan.z)); transform.position = (verticalMovement ? posPointMan : new Vector3(posPointMan.x, 0, posPointMan.z));
仲良く二人分のCubemanを表示することができるようになりました。
ただし、この処理をはずすことで、Kinectカメラの高さがCubemanの垂直方向の位置に影響をおよぼすようになるため、足が地面にめり込んだり宙に浮いたりするようになりますので、調整が必要になります。