NtKinectを利用して OpenCV や Kinect V2 の基本的機能を使う DLL ファイルを作成し、 他の自作のプログラムや Unity から利用する方法は 「NtKinect: Kinect V2 を使うプログラムをDLL化してUnityから利用する」 で説明しました。
本稿では、骨格認識を行う DLL ファイルを作成する方法について解説します。
では実際に既存の DLL ファイルを必要とする Kinect V2 の例として、 顔認識を行う DLL ライブラリを作成してみましょう。
マゼンタ色の部分が今回追加した関数のプロトタイプ宣言です。
NtKinectDll.h |
#ifdef NTKINECTDLL_EXPORTS #define NTKINECTDLL_API __declspec(dllexport) #else #define NTKINECTDLL_API __declspec(dllimport) #endif extern "C" { NTKINECTDLL_API void* getKinect(void); NTKINECTDLL_API int setSkeleton(void* ptr, float* skelton, int* state, int *id); } |
int setSkeleton(void *, float *data,int* state, int *id) 関数の定義を追加します。 この関数ではまず、 カメラで取得した画像を1/16に縮小してその上に関節を赤で描画してからcv::imshow()で表示しています。 ウィンドウの内容を正しく表示させるためにはcv::waitKey(1)を呼び出す必要があります。 1つの関節の位置は3個のfloat値で表現されていて、 一人につき 25 個の関節の位置が得られます。 最大で6人分の骨格が認識されるので、 data 領域はこの関数を呼び出す側で 「floatのバイト数 * 3 * 25 * 6 」バイト以上確保されている必要があります。 返り値は、認識できた骨格の個数です。
NtKinectDll.cpp |
#include "stdafx.h" #include "NtKinectDll.h" #include "NtKinect.h" using namespace std; NTKINECTDLL_API void* getKinect(void) { NtKinect* kinect = new NtKinect; return static_cast<void*>(kinect); } NTKINECTDLL_API int setSkeleton(void* ptr, float *skeleton, int* state, int* id) { NtKinect *kinect = static_cast<NtKinect*>(ptr); (*kinect).setRGB(); (*kinect).setSkeleton(); int scale = 4; cv::Mat img((*kinect).rgbImage); cv::resize(img,img,cv::Size(img.cols/scale,img.rows/scale),0,0); for (auto& person: (*kinect).skeleton) { for (auto& joint: person) { if (joint.TrackingState == TrackingState_NotTracked) continue; ColorSpacePoint cp; (*kinect).coordinateMapper->MapCameraPointToColorSpace(joint.Position,&cp); cv::rectangle(img, cv::Rect((int)cp.X/scale-2, (int)cp.Y/scale-2,4,4), cv::Scalar(0,0,255),2); } } cv::imshow("face",img); cv::waitKey(1); int idx=0, jt=0, st=0; for (auto& person: (*kinect).skeleton) { for (auto& joint: person) { skeleton[jt++] = joint.Position.X; skeleton[jt++] = joint.Position.Y; skeleton[jt++] = joint.Position.Z; state[st++] = joint.TrackingState; } id[idx] = (*kinect).skeletonId[idx]; idx++; } return idx; } |
[注意](2017/10/07 追記) Visual Studio 2017 Update 2 でのビルド時に「dllimport ...」というエラーが起きる場合は こちらを参考にして NtKinectDll.cpp 内で NTKINECTDLL_EXPORTS をdefineする ことで対処して下さい。
上記のzipファイルには必ずしも最新の NtKinect.h が含まれていない場合があるので、 こちらから最新版をダウンロードして 差し替えてお使い下さい。
NtKinectDll.dll を Unityで利用します。
Hierarchy上で Skeleton を選択して、Inspectorで設定からResetとして Transform の Position x, y, z をそれぞれ 0 に初期化しておきます。
上部のメニューから「Assets」-> 「Create」 -> 「C# Script」 -> ファイル名は NtKinectBehaviour
C++ のポインタは C# では System.IntPtr として扱います。
NtKinectBehaviour.cs |
using UnityEngine; using System.Collections; using System.Runtime.InteropServices; public class NtKinectBehaviour : MonoBehaviour { [DllImport ("NtKinectDll")] private static extern System.IntPtr getKinect(); [DllImport ("NtKinectDll")] private static extern int setSkeleton(System.IntPtr kinect, System.IntPtr data, System.IntPtr state, System.IntPtr id); private System.IntPtr kinect; int bodyCount = 6; int jointCount = 25; GameObject[] obj; int counter; void Start () { kinect = getKinect(); obj = new GameObject[bodyCount * jointCount]; for (int i=0; i<obj.Length; i++) { obj[i] = GameObject.CreatePrimitive(PrimitiveType.Cube); obj[i].transform.position = new Vector3(0,0,-10); obj[i].transform.localScale = new Vector3(0.1f,0.1f,0.1f); } } void Update () { float[] data = new float[bodyCount * jointCount * 3]; int[] state = new int[bodyCount * jointCount]; int[] id = new int[bodyCount]; GCHandle gch = GCHandle.Alloc(data,GCHandleType.Pinned); GCHandle gch2 = GCHandle.Alloc(state,GCHandleType.Pinned); GCHandle gch3 = GCHandle.Alloc(id,GCHandleType.Pinned); int n = setSkeleton(kinect,gch.AddrOfPinnedObject(),gch2.AddrOfPinnedObject(),gch3.AddrOfPinnedObject()); gch.Free(); gch2.Free(); gch3.Free(); int idx=0; for (int i=0; i<bodyCount; i++) { for (int j=0; j<jointCount; j++) { if (i < n) { float x = data[idx++], y=data[idx++], z=data[idx++]; obj[i*jointCount + j].transform.position = new Vector3(x,y,z); obj[i*jointCount + j].GetComponent<Renderer>().material.color = new Color(0.0f,1.0f,0.0f,1.0f); } else { obj[i*jointCount + j].transform.position = new Vector3(0,0,-10); obj[i*jointCount + j].GetComponent<Renderer>().material.color = new Color(1.0f,1.0f,1.0f,1.0f); } } } } } |
カメラの位置はデフォルトでは (x,y,z)=(0,1,-10)になっているはずです。 認識された骨格はz座標が正なのでカメラの位置は(x,y,z)=(0,0,-1)としてみましょう。
[注意] 骨格認識の状態を表示するためにDLL内でOpenCVのウィンドウを生成しています。 後から生成されたこのウィンドウにフォーカスがあるときは (= Unityのウィンドウにフォーカスがない場合は) Unityの画面は変化しないので注意して下さい。 Unityのウィンドウの上部をクリックしてUnityのウィンドウにフォーカスが ある状態で動作を試して下さい。