Windows で DLL を作成し利用する方法の詳細に関しては、たとえば Microsoft のサイトの解説 などを参照して下さい。
NtKinectを利用して OpenCV や Kinect V2 を使う DLL ファイルを作成してみましょう。 Visual Studio Proefssional 2015 では C++ で DLL を作成するプロジェクトのテンプレートが用意されています。
OK -> 次へ を選択して「アプリケーションの設定」へ
変数、関数、クラスの記述例が既に挿入されていますので、それを参考に記述します。 宣言の先頭につける "大文字のプロジェクト名_API" (この場合だと "NTKINECTDLL_API" になります) はここで定義されています。
変数、関数、クラスの記述例が既に挿入されていますので、それを参考に記述します。 例からわかるように、宣言の先頭に "大文字のプロジェクト名_API" (この場合だと "NTKINECTDLL_API" になります) を記述する必要があります。 これは NtKinectDll.h の中で定義されているマクロで、DLLからのexport/importを容易にします。
では実際に Kinect V2 を利用したDLL ライブラリを自分で作成し、Unityで利用してみましょう。
なるべくわかりやすい話ということで、 右手の状態(パー、グー、チョキ、それ以外)を認識する例で説明します。
名前はここでは NtKinectDll とします。ソリューション名も自動的に NtKinectDll となります。 OK -> 次へ を選択して「アプリケーションの設定」へ
「構成プロパティ」 -> 「C/C++」 -> 全般 -> 追加のインクルードディレクトリ
$(KINECTSDK20_DIR)inc D:\opencv\include
「構成プロパティ」 -> 「リンカー」 -> 全般 -> 追加のライブラリディレクトリ
$(KINECTSDK20_DIR)Lib\x64 D:\opencv\lib
「構成プロパティ」 -> 「リンカー」 -> 全般 -> 入力
Kinect20.lib opencv_world310.lib
上記のリンクから NtKinect.h をダウンロードして下さい。 dllmain.cpp などプロジェクトのソースが置かれているフォルダ(この例だと NtKinectDll/NtKinectDll)に NtKinect.h を配置してから、 「ソリューションエクスプローラ」の「ヘッダーファイル」の上で右クリックで 「追加」 -> 「既存の項目」 -> NtKinect.h を選択します。
緑文字の部分がプロジェクトを作成した時から定義されている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); } |
関数宣言の先頭に "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; } |
[注意] ダイナミックリンクライブラリを作りたいので、ここではコンパイル、すなわちビルドするだけです。 このプロジェクトではプログラム本体を作成してはいないので、実行しようとすると (意味のない)エラーが発生します。
[注意2] .lib ファイルと .dll ファイルが生成されるのは プロジェクト直下のフォルダ (プロジェクト名)/x64/Release です。 ソースファイルが置かれているフォルダの下の (プロジェクト名)/NtKinectDll/x64/Release ではありません。
上記のzipファイルには必ずしも最新の NtKinect.h が含まれていない場合があるので、 こちらから最新版をダウンロードして 差し替えてお使い下さい。
生成したDLLファイルが正しく動作することを確認する簡単なプロジェクトを作成しましょう。
名前を CheckDLL にしてから、 OK -> 次へ を選択して「アプリケーションの設定」へ
プロジェクトの stdafx.cpp や CheckDLL.cpp が置かれているフォルダに NtKinectDll.h をコピーします。 そのあとで、「ソリューションエクスプローラー」の「ヘッダーファイル」の上で右クリックして 「追加」->「既存の項目の追加」 -> NtKinectDll.h を選択します。
プロジェクトの stdafx.cpp や CheckDLL.cpp が置かれているフォルダに NtKinectDll.dll と NtKinectDll.lib をコピーします。
「プロジェクトのプロパティ」から「リンカー」->「入力」->「追加の依存ファイル」 に NtKinectDll.lib を追加します。
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; } |
NtKinectDll.h, NtKinectDll.lib, NtKinectDll.dll を Unityで利用します。
上部のメニューから「Game Object」-> 「3D Object」 -> 「Cube」
上部のメニューから「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); } } } |
[注意] 骨格の認識状態を表示するためにDLL内でOpenCVのウィンドウを生成しています。 後から生成されたこのウィンドウにフォーカスがあるときは (= Unityのウィンドウにフォーカスがない場合は) Unityの画面は変化しないので注意して下さい。 Unityのウィンドウの上部をクリックしてUnityのウィンドウにフォーカスが ある状態で動作を試して下さい。