NtKinect: Kinect V2 C++ Programming with OpenCV on Windows10

Kinect V2 の骨格認識をDLL化してUnityから利用する


2017.07.11: created by
2017.10.07: revised by
Japanese English
目次へ

前提として理解しておくべき知識


骨格認識プログラムのDLL化

NtKinectを利用して OpenCV や Kinect V2 の基本的機能を使う DLL ファイルを作成し、 他の自作のプログラムや Unity から利用する方法は 「NtKinect: Kinect V2 を使うプログラムをDLL化してUnityから利用する」 で説明しました。

本稿では、骨格認識を行う DLL ファイルを作成する方法について解説します。


プログラム作成の手順

では実際に既存の DLL ファイルを必要とする Kinect V2 の例として、 顔認識を行う DLL ライブラリを作成してみましょう。

  1. NtKinect: Kinect V2 を使うプログラムをDLL化してUnityから利用する」 の Visual Studio 2015 のプロジェクト NtKinectDll.zipを用いて作成します。
  2. 宣言をヘッダファイルに記述します。ヘッダファイルの名前は"プロジェクト名.h"で、この場合は "NtKinectDll.h" になります。
  3. マゼンタ色の部分が今回追加した関数のプロトタイプ宣言です。

    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);
    }
    
  4. 関数は"プロジェクト名.cpp"に記述します。この例だと NtKinectDll.cpp になります。
  5. 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;
    }
    
  6. 「アプリケーションの構成」を "Release"にしてコンパイルします。フォルダ x64/Release に NtKinectDll.lib および NtKinectDll.dll が生成されます。
  7. [注意](2017/10/07 追記) Visual Studio 2017 Update 2 でのビルド時に「dllimport ...」というエラーが起きる場合は こちらを参考にして NtKinectDll.cpp 内で NTKINECTDLL_EXPORTS をdefineする ことで対処して下さい。

  8. サンプルのプロジェクトはこちら NtKinectDll5.zip
  9. 上記のzipファイルには必ずしも最新の NtKinect.h が含まれていない場合があるので、 こちらから最新版をダウンロードして 差し替えてお使い下さい。


Unity内でDLLを利用する

NtKinectDll.dll を Unityで利用します。

  1. Unity で DLL を利用する方法の詳細については 公式のマニュアル を参照して下さい。
  2. Unity(C#) のデータは managed (ガベージコレクタによって管理されており場所を移動することがある) な状態であり、DLL(C++) のデータは unmanaged (場所が移動することはない) な状態です。 この間でデータを受け渡すには、状態を変換する必要があり、 それにはC# の System.Runtime.InteropServices.Marshal クラス のメソッドを利用します。
  3. 異なる言語の間でデータを受け渡す方法については、 MSDNにあるマニュアル "Interoperating with Unmanaged Code" のうち、特に Interop Marshaling の部分を参照して下さい。
  4. Unity の新しいプロジェクトを開始します。
  5. Unity のプロジェクトのAssets/Plugins/x86_64/の下に NtKinectDll.dll をコピーします。



  6. Empty Objectをシーンに配置して、名前を Skeleton に変更します。
  7. Hierarchy上で Skeleton を選択して、Inspectorで設定からResetとして Transform の Position x, y, z をそれぞれ 0 に初期化しておきます。

  8. ProjectのAssets/Scripts/の下に C# のスクリプトを生成します。
  9. 上部のメニューから「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);
    	}
          }
        }
      }
    }
    
  10. NtKinectBehaviour.cs を NtKinect のComponentとして付加します。
  11. カメラの位置を調節します。
  12. カメラの位置はデフォルトでは (x,y,z)=(0,1,-10)になっているはずです。 認識された骨格はz座標が正なのでカメラの位置は(x,y,z)=(0,0,-1)としてみましょう。

  13. 実行すると、認識した関節の位置に緑色の立方体が表示されます。
  14. [注意] 骨格認識の状態を表示するためにDLL内でOpenCVのウィンドウを生成しています。 後から生成されたこのウィンドウにフォーカスがあるときは (= Unityのウィンドウにフォーカスがない場合は) Unityの画面は変化しないので注意して下さい。 Unityのウィンドウの上部をクリックしてUnityのウィンドウにフォーカスが ある状態で動作を試して下さい。







  15. Unity のサンプルプロジェクトはこちら CheckNtKinectDll5.zip
  16. これを発展させた「Kinect V2 の骨格認識をDLL化してUnity の人型キャラクタを動かす」 方法についての解説はこちら


http://nw.tsuda.ac.jp/