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

Kinect V2 を使うプログラムをDLL化してUnityから利用する


2016.08.25: created by
Japanese English
目次へ

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


プログラムのDLL化

Windows で DLL を作成し利用する方法の詳細に関しては、たとえば Microsoft のサイトの解説 などを参照して下さい。

NtKinectを利用して OpenCV や Kinect V2 を使う DLL ファイルを作成してみましょう。 Visual Studio Proefssional 2017 では C++ で DLL を作成するプロジェクトのテンプレートが用意されています。

  1. ファイル -> 新規作成 -> プロジェクト-> Visual C++ ->Windows -> Win32 -> Win32コンソールアプリケーション を選択します。
  2. OK -> 次へ を選択して「アプリケーションの設定」へ

  3. 「アプリケーションの設定」で「アプリケーションの種類」を「DLL」に, 追加のオプションの「シンボルのエクスポート」をチェックします。 「Security Development Lifecycle (SDL)のチェック」という項目がもしもあれば、チェックをはずします。
  4. Visual Studio のウィンドウの上部メニューの "x86" を "x64" に変更して、64bitアプリケーションを作るように設定します。
  5. 関数の宣言をヘッダファイルに記述します。
  6. 変数、関数、クラスの記述例が既に挿入されていますので、それを参考に記述します。 宣言の先頭につける "大文字のプロジェクト名_API" (この場合だと "NTKINECTDLL_API" になります) はここで定義されています。

  7. 関数は"プロジェクト名.cpp"に記述します。
  8. 変数、関数、クラスの記述例が既に挿入されていますので、それを参考に記述します。 例からわかるように、宣言の先頭に "大文字のプロジェクト名_API" (この場合だと "NTKINECTDLL_API" になります) を記述する必要があります。 これは NtKinectDll.h の中で定義されているマクロで、DLLからのexport/importを容易にします。

  9. 「アプリケーションの構成」を "Release"にしてコンパイルします(ビルドするだけです。実行はしません)。 プロジェクト直下のフォルダ NtKinectDll/x64/Release に.lib ファイルと .dll ファイルが生成されます。

プログラム作成の手順

では実際に Kinect V2 を利用したDLL ライブラリを自分で作成し、Unityで利用してみましょう。

なるべくわかりやすい話ということで、 右手の状態(パー、グー、チョキ、それ以外)を認識する例で説明します。

  1. ファイル -> 新規作成 -> プロジェクト-> Visual C++ ->Windows -> Win32 -> Win32コンソールアプリケーション を選択します。
  2. 名前はここでは NtKinectDll とします。ソリューション名も自動的に NtKinectDll となります。 OK -> 次へ を選択して「アプリケーションの設定」へ




  3. 「アプリケーションの設定」で「アプリケーションの種類」を「DLL」に, 追加のオプションの「シンボルのエクスポート」をチェックします。 「Security Development Lifecycle (SDL)のチェック」項目がもしあれば、チェックをはずします。



  4. Visual Studio のソリューションエクスプローラーは次のように表示されているはずです。



  5. Visual Studio のウィンドウの上部メニューの "x86" を "x64" に変更して、64bitアプリケーションを作るように設定します。実行速度を速くするために"Debug"モードを"Release"モードに変更します。



  6. プロジェクトのプロパティからインクルードファイルやライブラリに関する設定を行う。
    1. ソリューションエクスプローラでプロジェクト名の上で右ドラッグして「プロパティ」を選択します。



    2. 構成:「すべての構成」, プラットフォーム 「アクティブ(x64)」の状態で設定を行います。こうすることによってDebugおよびReleaseのどちらの設定ができます。もちろん、別々に設定しても構いません。
    3. インクルードファイルの場所を追加します。
    4. 「構成プロパティ」 -> 「C/C++」 -> 全般 -> 追加のインクルードディレクトリ

        $(KINECTSDK20_DIR)inc
        C:\opencv\include






    5. ライブラリの場所を追加します。
    6. 「構成プロパティ」 -> 「リンカー」 -> 全般 -> 追加のライブラリディレクトリ

        $(KINECTSDK20_DIR)Lib\x64
        C:\opencv\lib






    7. リンクするライブラリを追加します。
    8. 「構成プロパティ」 -> 「リンカー」 -> 全般 -> 入力

        Kinect20.lib
        opencv_world330.lib






  7. (重要)プロジェクトのヘッダーファイルに NtKinect.h ( this site, github ) を追加します。
  8. 上記のリンクから NtKinect.h をダウンロードして下さい。 dllmain.cpp などプロジェクトのソースが置かれているフォルダ(この例だと NtKinectDll/NtKinectDll)に NtKinect.h を配置してから、 「ソリューションエクスプローラ」の「ヘッダーファイル」の上で右クリックで 「追加」 -> 「既存の項目」 -> NtKinect.h を選択します。









  9. 宣言をヘッダファイルに記述します。ヘッダファイルの名前は"プロジェクト名.h"で、この場合は "NtKinectDll.h" になります。
  10. 緑文字の部分がプロジェクトを作成した時から定義されているDLLのimport/exportに関する部分です。 NtKinectDll.hはこのプロジェクト内ではexport用の宣言となり、他のプロジェクトに読み込まれたときは import用の宣言となります。

    青文字の部分が自分で定義した関数に関する部分です。 C++で関数名が mangling (= 関数名がC++コンパイラによって返り値の型と引数の型を含めた名前に変更されること) されるのを避けるために extern "C" {} の中で関数のプロトタイプを宣言します。 これによりこの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 rightHandState(void* kinect);
    }
    
    
  11. 関数は"プロジェクト名.cpp"に記述します。この例だと NtKinectDll.cpp になります。
  12. 関数宣言の先頭に "NTKINECTDLL_API" を記述する必要があります。 これは NtKinectDll.h の中で定義されているマクロで、DLLからのexport/import を容易にするものです。

    DLLの中ではオブジェクトはヒープに確保する必要があります。 そのため void* getKinect()関数では NtKinectを new してそのポインタを(void *)にキャストして返しています。

    DLLの関数を実行するときは、ヒープ上のNtKinectオブジェクトのポインタを渡してもらい、 (void *)型のポインタから (NtKinect *)型のポインタに変更してNtKinect の機能を利用します。 下の例では関数中では kinect 変数は (NtKinect *)型のポインタになるので、 たとえば rgbImage というメンバ変数へのアクセスは (*kinect).rgbImage と記述します。 int rightHandState(void *) 関数では、まず カメラで取得した画像を1/16に縮小してその上に関節を赤で描画しcv::imshow()で表示しています。 ウィンドウの内容を正しく表示させるためにはcv::waitKey(1)を呼び出す必要があります。 その後で、認識できた誰か一人の右手の状態を返します。 返り値の意味は以下の通り Kinect.h で定義されている値です。

    enum _HandState {
        HandState_Unknown= 0,
        HandState_NotTracked= 1,
        HandState_Open= 2,
        HandState_Closed= 3,
        HandState_Lasso= 4
    };
    
    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 rightHandState(void* ptr) {
      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("rgb",img);
      cv::waitKey(1);
      for (int i=0; i<(*kinect).skeleton.size(); i++) {
        Joint right = (*kinect).skeleton[i][JointType_HandRight];
        if (right.TrackingState == TrackingState_NotTracked) continue;
        auto state = (*kinect).handState(i,false);
        if (state.first == HandState_Open
    	|| state.first == HandState_Closed
    	|| state.first == HandState_Lasso ) {
          return state.first;
        }
      }
      return HandState_Unknown;
    }
    
    
  13. 「アプリケーションの構成」を "Release"にしてコンパイルします。 メニューからBuild -> プロジェクトの Rebuild を選びます。 フォルダ x64/Release に NtKinectDll.lib および NtKinectDll.dll が生成されます。
  14. [注意] ダイナミックリンクライブラリを作りたいので、ここではコンパイル、すなわちビルドするだけです。 このプロジェクトではプログラム本体を作成してはいないので、実行しようとすると (意味のない)エラーが発生します。

    [注意2] .lib ファイルと .dll ファイルが生成されるのは プロジェクト直下のフォルダ (プロジェクト名)/x64/Release です。 ソースファイルが置かれているフォルダの下の (プロジェクト名)/NtKinectDll/x64/Release ではありません。



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


生成したDLLファイルの動作確認

生成したDLLファイルが正しく動作することを確認する簡単なプロジェクトを作成しましょう。

  1. ファイル -> 新規作成 -> プロジェクト-> Visual C++ ->Windows -> Win32 -> Win32コンソールアプリケーション を選択します。
  2. 名前を CheckDLL にしてから、 OK -> 次へ を選択して「アプリケーションの設定」へ




  3. 「アプリケーションの設定」で「アプリケーションの種類」を「コンソールアプリケーション」に, 追加のオプションでは「プリコンパイル済みヘッダー」をチェックします。 「Security Development Lifecycle (SDL)のチェック」が存在すればチェックしておきます(デフォルトの設定だと思います)。



  4. Visual Studio のウィンドウの上部メニューの "x86" を "x64" に変更して、64bitアプリケーションを作るように設定します。



  5. プロジェクトのファイルに NtKinectDll.h を加えます。
  6. プロジェクトの stdafx.cpp や CheckDLL.cpp が置かれているフォルダに NtKinectDll.h をコピーします。 そのあとで、「ソリューションエクスプローラー」の「ヘッダーファイル」の上で右クリックして 「追加」->「既存の項目の追加」 -> NtKinectDll.h を選択します。





  7. プロジェクトのフォルダにDLLのファルを置きます。
  8. プロジェクトの stdafx.cpp や CheckDLL.cpp が置かれているフォルダに NtKinectDll.dll と NtKinectDll.lib をコピーします。

  9. プロジェクトのプロパティで、NtKinectDll.lib ライブラリをリンクするように設定します。
  10. 「プロジェクトのプロパティ」から「リンカー」->「入力」->「追加の依存ファイル」 に NtKinectDll.lib を追加します。









  11. CheckDLL.cppの内容を以下のように変更します。
  12. CheckDLL.cpp
    #include "stdafx.h"
    #include <iostream>
    #include <sstream>
    
    #include "NtKinectDll.h"
    
    using namespace std;
    
    int main() {
      void* kinect = getKinect();
      while (1) {
        int state = rightHandState(kinect);
        switch (state) {
        case 2: cout << "Open" << endl; break;
        case 3: cout << "Closed" << endl; break;
        case 4: cout << "Lasso" << endl; break;
        default: cout << "unknown" << endl;  break;
        }
      }
      return 0;
    }
    
    
    
  13. プログラムを実行するとコンソール画面に、右手の状態が Open(パー), Closed(グー), Lasso(チョキ) または unknown(判定できない) と表示されていきます。 右手の形が判定できるのは、骨格が認識できた場合に限ります。 DLLファイルの rightHandState() 関数の中では、 RGBカメラで取得した画像の上に関節位置を赤で表した画像をウィンドウ表示するので 現在骨格が認識されているかどうかがわかります。



  14. サンプルのプロジェクトはこちら CheckDLL.zip

Unity内でDLLを利用する

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

  1. Unity で DLL を利用する方法の詳細については 公式のマニュアル を参照して下さい。
  2. Unity(C#) のデータは managed (ガベージコレクタによって管理されており場所を移動することがある) な状態であり、DLL(C++) のデータは unmanaged (場所が移動することはない) な状態です。 この間でデータを受け渡すには、状態を変換する必要があり、 それにはC# の System.Runtime.InteropServices.Marshal クラス のメソッドを利用します。
  3. Unity の新しいプロジェクトを開始します。



  4. ProjectのAssets/Plugins/x86_64/の下に NtKinectDll.dll をimportします。



  5. Cubeをシーンに配置します。
  6. 上部のメニューから「Game Object」-> 「3D Object」 -> 「Cube」



  7. ProjectのAssets/Scripts/の下に C# のスクリプトを生成する。
  8. 上部のメニューから「Assets」-> 「Create」 -> 「C# Script」 -> ファイル名は CubeBehaviour




    C++ のポインタは C# では System.IntPtr として扱います。

    CubeBehaviour.cs
    using UnityEngine;
    using System.Collections;
    using System.Runtime.InteropServices;
    
    public class CubeBehaviour : MonoBehaviour {
        [DllImport ("NtKinectDll")] private static extern System.IntPtr getKinect();
        [DllImport ("NtKinectDll")] private static extern int rightHandState(System.IntPtr kinect);
        private System.IntPtr kinect;
        
        void Start () {
    	kinect = getKinect();
        }
        
        void Update () {
    	int state = rightHandState(kinect);
    	if (state == 2) {
    	    gameObject.GetComponent<Renderer>().material.color = new Color(1.0f, 0.0f, 0.0f, 1.0f);
    	} else if (state == 3) {
    	    gameObject.GetComponent<Renderer>().material.color = new Color(0.0f, 1.0f, 0.0f, 1.0f);
    	} else if (state == 4) {
    	    gameObject.GetComponent<Renderer>().material.color = new Color(0.0f, 0.0f, 1.0f, 1.0f);
    	} else {
    	    gameObject.GetComponent<Renderer>().material.color = new Color(1.0f, 1.0f, 1.0f, 1.0f);
    	}
        }
    }
    
    
  9. CubeBehaviour.cs を CubeのComponentとして付加する。



  10. 実行すると、右手の状態によって立方体の色が、パー->赤、グー->緑、チョキ->青、それ以外->白 と変化します。
  11. [注意] 骨格の認識状態を表示するためにDLL内でOpenCVのウィンドウを生成しています。 後から生成されたこのウィンドウにフォーカスがあるときは (= Unityのウィンドウにフォーカスがない場合は) Unityの画面は変化しないので注意して下さい。 Unityのウィンドウの上部をクリックしてUnityのウィンドウにフォーカスが ある状態で動作を試して下さい。




  12. Unity のサンプルプロジェクトはこちら CheckNtKinectDll.zip


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