Windows上でマルチスレッドで動作するプログラムを記述するにはまず process.h を読み込みます。
#include <process.h> |
スレッドで走る関数 unsigned func (LPVOID) は次の形式で定義します。
unsigned __stdcall func (LPVOID pParam) { // このスレッドがやるべき仕事 _endthreadex(0); return 0; } |
マルチスレッドで動作するプログラムにおいては、 クリティカル・セクション(競合が発生する可能性がある場所)では 排他制御をする必要があります。これには Mutex を利用します。 すなわち、どのスレッドからも参照できる変数として Mutex を生成しておき、 この利用権を取れたスレッドだけがクリティカル・セクションに入って よいことにします。
Handle mutex; int main(int argc, char** argv) { mutex = CreateMutex(NULL, FALSE, NULL); // Mutexを生成する if (GetLastError() == ERROR_ALREADY_EXISTS) throw エラー; ...(略)... } unsigned __stdcall func (LPVOID pParam) { ...(略)... DWORD ret = WaitForSingleObject(mutex, INFINITE); // Mutexの利用権を取得する if (ret == WAIT_FAILED) throw エラー; ...(略)... //クリティカル・セクション ReleaseMutex(mutex); // Mutexの利用権を解放する ...(略)... _endthreadex(0); return 0; } |
メインのスレッドにおいて、N 個のスレッドを起動する場合は次のように記述します。 スレッドの起動を _beginthreadex()関数で、 全てのスレッドの終了待ちを WaitForMultipleObjects()関数で行います。
HANDLE hThread[N ] = { 0 }; hThread[0] = (HANDLE) _beginthreadex(NULL,0,関数名1,NULL,0,NULL); // 別スレッドで関数1を実行する if (hThread[0] == 0) throw エラー; ...(略)... // 2〜(N-2)のスレッドを生成する hThread[N -1] = (HANDLE) _beginthreadex(NULL,0,関数名N,NULL,0,NULL); // 別スレッドで関数Nを実行する if (hThread[N -1] == 0) throw エラー; ...(略)... // メインスレッドでやるべき仕事 DWORD ret = WaitForMultipleObjects(N ,hThread,TRUE,INFINITE); // 全てのスレッドの終了を待つ if (ret == WAIT_FAILED) throw エラー; return 0; } |
USE_THREAD 定数を define してから NtKinect.h を include する と NtKinect のマルチスレッド関係のメソッドが有効になります。
返り値の型 | メソッド名 | 説明 |
---|---|---|
void | acquire() | version1.6以降。 Mutex の権利を獲得する。獲得できるまで呼び出したスレッドの実行はブロックされる。 |
void | release() | version1.6以降。 Mutex の権利を解放する。 |
複数の関数呼び出しの間でatomic性を確保したい場合は、 自分でacquire()とrelease()を呼び出して排他制御した方がよいとは思いますが、 利用を簡便にするために、以下の関数を用意しました。 基本的に次の動作を行います。
acquire()を呼び出す。 Kinectの関数を呼び出す。 結果を引数で指定した変数にコピーする。 release()を呼び出す |
返り値の型 | メソッド名 | 説明 |
---|---|---|
void | _setRGB(cv::Mat& image ) |
version1.6以降。スレッドセーフ。 setRGB()を呼び出し、rgbImageをimage にコピーする。以下の操作と同じ。 acquire(); setRGB(); rgbImageをimage にコピーする。 release(); |
void | _setDepth(cv::Mat image, bool raw = true) |
version1.6以降。スレッドセーフ。 setDepth(raw)を呼出しdepthImageをimage にコピーする。以下の操作と同じ。 acquire(); setDepth(raw ); depthImageをimage にコピーする。 release(); |
void | _setInfrared(cv::Mat& image ) |
version1.6以降。スレッドセーフ。 setInfrared()を呼び出し、infraredImageをimage にコピーする。以下の操作と同じ。 acquire(); setInfrared(); infraredImageをimage にコピーする。 release(); |
void | _setBodyIndex(cv::Mat& image ) |
version1.6以降。スレッドセーフ。 setBodyIndex()を呼び出し、bodyIndexImageをimage にコピーする。以下の操作と同じ。 acquire(); setBodyIndex(); bodyIndexImageをimage にコピーする。 release(); |
void | _setSkeleton(vector<vector<Joint> >& skel , vector<int>& skelId , vector<UINT64>& skelTrackingId ) |
version1.6以降。スレッドセーフ。 setSkeleton()を呼び出し、結果を引数にコピーする。以下の操作と同じ。 acquire(); setSkeleton(); skeletonをskel にコピーする。 skeletonIdをskelId にコピーする。 skeletonTrakingIdをskelTrackingId にコピーする。 release(); |
pair<int,int> | _handState(int id =0, bool isLeft =true) |
version1.6以降。スレッドセーフ。 handState()を呼び出し、結果を返す。以下の操作と同じ。 acquire(); auto ret = handState(id , isLeft ); release(); return ret; |
void | _setFace(vector<vector<PointF> >& point , vector<cv::Rect>& rect , vector<cv::Vec3f>& direction , vector<vector<DetectionResult> >& property , vector<UINT64>& trackingId , bool isColorSpace =true) |
version1.6以降。スレッドセーフ。USE_FACEをdefineした場合。 setFace(isColorSpace )を呼び出し、結果を引数にコピーする。次の操作と同等。 acquire(); setFace(isColorSpace ); facePointを point にコピーする。; faceRectを rect にコピーする。; faceDirectionを direction にコピーする。; facePropertyを property にコピーする。; faceTrackingIdを trakingId にコピーする。; release(); |
void | _setHDFace(vector<vector<CameraSpacePoint> >& vertices , vector<UINT64>& trackingId , vector<pair<int,int> >& status ) |
version1.6以降。スレッドセーフ。USE_FACEをdefineした場合。 setHDFace()を呼び出し、結果を引数にコピーする。次の操作と同等。 acquire(); setHDFace(); hdfaceVerticesを vertices にコピーする。; hdfaceTrackingIdを trackingId にコピーする。; hdfaceStatusを status にコピーする。; release(); |
void | _setGesture( vector<pair<CComPtr<IGesture>,float> >& discrete , vector<pair<CComPtr<IGesture>,float> >& continuous , vector<UINT64>& dTrackingId , vector<INT64>& cTrackingId ) |
version1.6以降。スレッドセーフ。USE_GESTUREをdefineした場合。 setGesture()を呼び出し、結果を引数にコピーする。次の操作と同等。 acquire(); setGesture(); discreteGestureを discrete にコピーする。; continuousGestureを continuous にコピーする。; discreteTrackingIdを dTrackingId にコピーする。; continuousTrackingIdを cTrackingId にコピーする。; release(); |
string | _gesture2string(const CComPtr<IGesture>& gesture ) | version1.6以降。スレッドセーフ。USE_GESTUREをdefineした場合。 gesture2string(gesture)を呼び出す。 |
void | _setAudio(float& angle , float& confidence , UINT64& trackingId , bool flag = false) |
version1.6以降。スレッドセーフ。USE_AUDIOをdefineした場合。 setAudio(flag)を呼び出し、結果を引数にコピーする。次の操作と同等。 acquire(); setAudio(flag); beamAngleを angle にコピーする。; beamAngleConfidenceを confidence にコピーする。; beamTrackingIdを trackingId にコピーする。; release(); |
void | _setSpeech(pair<wstring,wstring>& p ) |
version1.6以降。スレッドセーフ。USE_SPEECHをdefineした場合。 setSpeech()を呼び出し、結果を引数にコピーする。次の操作と同等。 acquire(); setAudio(flag); speechTagのコピーとspeechItemのコピーのpairを p に代入する。; release(); |
HRESULT | _MapCameraPointToColorSpace( CameraSpacePoint sp , ColorSpacePoint *cp ) |
version1.6以降。スレッドセーフ。 coordinateMapper->MapCameraPointToColorSpace(sp,cp)を呼び出す。 |
HRESULT | _MapCameraPointToDepthSpace( CameraSpacePoint sp , DelpthSpacePoint *dp ) |
version1.6以降。スレッドセーフ。 coordinateMapper->MapCameraPointToDepthSpace(sp,dp)を呼び出す。 |
HRESULT | _MapDepthPointToColorSpace( DepthSpacePoint dp , UINT16 depth , ColorSpacePoint *cp ) |
version1.6以降。スレッドセーフ。 coordinateMapper->MapDepthPointToColorSpace(dp,depth,cp)を呼び出す。 |
HRESULT | _MapDepthPointToCameraSpace( DepthSpacePoint dp , UINT16 depth , CameraSpacePoint *sp ) |
version1.6以降。スレッドセーフ。 coordinateMapper->MapDepthPointToCameraSpace(dp,depth,sp)を呼び出す。 |
main.cpp |
#include <iostream> #include <sstream> #define USE_THREAD #include "NtKinect.h" using namespace std; NtKinect kinect; unsigned __stdcall doJob1(LPVOID pParam) { cv::Mat image; vector<vector<Joint> > skel; vector<int> skelId; vector<UINT64> skelTrackingId; while (1) { kinect._setRGB(image); kinect._setSkeleton(skel,skelId,skelTrackingId); for (auto person: skel) { for (auto joint: person) { if (joint.TrackingState == TrackingState_NotTracked) continue; ColorSpacePoint cp; kinect._MapCameraPointToColorSpace(joint.Position,&cp); cv::rectangle(image,cv::Rect((int)cp.X-5,(int)cp.Y-5,10,10),cv::Scalar(0,0,255),2); } } cv::imshow("1", image); auto key = cv::waitKey(1); if (key == 'q') break; } cv::destroyWindow("1"); _endthreadex(0); return 0; } void drawHand(cv::Mat& image, Joint& joint, pair<int,int>& state) { cv::Scalar colors[] = { cv::Scalar(255,0,0), // HandState_Unknown cv::Scalar(0,255,0), // HandState_NotTracked cv::Scalar(255,255,0), // HandState_Open cv::Scalar(255,0,255), // HandState_Closed cv::Scalar(0,255,255), // HandState_Lass }; ColorSpacePoint cp; kinect._MapCameraPointToColorSpace(joint.Position,&cp); cv::rectangle(image, cv::Rect((int)cp.X-8, (int)cp.Y-8, 16, 16), colors[state.first], 4); } unsigned __stdcall doJob2(LPVOID pParam) { cv::Mat image; vector<vector<Joint> > skel; vector<int> skelId; vector<UINT64> skelTrackingId; while (1) { kinect._setRGB(image); kinect._setSkeleton(skel,skelId,skelTrackingId); for (int i=0; i<skel.size(); i++) { Joint left = skel[i][JointType_HandLeft]; Joint right = skel[i][JointType_HandRight]; if (left.TrackingState != TrackingState_NotTracked) { auto state = kinect._handState(i,true); drawHand(image,left,state); } if (right.TrackingState != TrackingState_NotTracked) { auto state = kinect._handState(i,false); drawHand(image,right,state); } } cv::imshow("2", image); auto key = cv::waitKey(1); if (key == 'q') break; } cv::destroyWindow("2"); _endthreadex(0); return 0; } unsigned __stdcall doJob3(LPVOID pParam) { cv::Mat image; while (1) { kinect._setBodyIndex(image,false); cv::imshow("3", image); auto key = cv::waitKey(1); if (key == 'q') break; } cv::destroyWindow("3"); _endthreadex(0); return 0; } void doJob() { HANDLE hThread[3] = { 0 }; hThread[0] = (HANDLE) _beginthreadex(NULL,0,doJob1,NULL,0,NULL); if (hThread[0] == 0) throw runtime_error("cannot create thread 0"); hThread[1] = (HANDLE) _beginthreadex(NULL,0,doJob2,NULL,0,NULL); if (hThread[1] == 0) throw runtime_error("cannot create thread 1"); hThread[2] = (HANDLE) _beginthreadex(NULL,0,doJob3,NULL,0,NULL); if (hThread[2] == 0) throw runtime_error("cannot create thread 2"); DWORD ret = WaitForMultipleObjects(3,hThread,TRUE,INFINITE); if (ret == WAIT_FAILED) throw runtime_error("wait failed"); } int main(int argc, char** argv) { try { doJob(); } catch (exception &ex) { cout << ex.what() << endl; string s; cin >> s; } return 0; } |
コンソール画面以外のウィンドウを'q'キーで閉じると、コンソール画面も閉じてプログラムが終了します。
上記のzipファイルには必ずしも最新の NtKinect.h が含まれていない場合があるので、 こちらから最新版をダウンロードして 差し替えてお使い下さい。
音声認識に必要なファイル(WaveFile.h, KinectAudioStream.h, KinectAudioStream.cpp, Grammaer_jaJP.grxml)がプロジェクトに追加されていて、ライブラリ(sapi.lib)も参照するように追加されているはずです。
スレッドセーフではない関数を呼び出すところでは kinect.acquire(); と kinect.release(); で囲んで、競合状態が発生するのを避けています。
main.cpp |
#include <iostream> #include <sstream> #define USE_SPEECH #define USE_THREAD #include "NtKinect.h" using namespace std; NtKinect kinect; unsigned __stdcall doJob1(LPVOID pParam) { cv::Mat image; vector<vector<Joint> > skel; vector<int> skelId; vector<UINT64> skelTrackingId; while (1) { kinect._setRGB(image); kinect._setSkeleton(skel,skelId,skelTrackingId); for (auto person: skel) { for (auto joint: person) { if (joint.TrackingState == TrackingState_NotTracked) continue; ColorSpacePoint cp; kinect._MapCameraPointToColorSpace(joint.Position,&cp); cv::rectangle(image,cv::Rect((int)cp.X-5,(int)cp.Y-5,10,10),cv::Scalar(0,0,255),2); } } cv::imshow("1", image); auto key = cv::waitKey(1); if (key == 'q') break; } cv::destroyWindow("1"); _endthreadex(0); return 0; } unsigned __stdcall doJob2(LPVOID pParam) { cv::Mat image; while (1) { kinect._setBodyIndex(image,false); cv::imshow("2", image); auto key = cv::waitKey(1); if (key == 'q') break; } cv::destroyWindow("2"); _endthreadex(0); return 0; } unsigned __stdcall doJob3(LPVOID pParam) { ERROR_CHECK(CoInitializeEx(NULL, COINIT_MULTITHREADED)); kinect.acquire(); kinect.startSpeech(); kinect.release(); std::wcout.imbue(std::locale("")); pair<wstring,wstring> p; while (1) { bool ret = kinect._setSpeech(p); if (ret) { wcout << p.first << L" " << p.second << endl; } if (p.first == L"EXIT") break; //Sleep(1L); } kinect.acquire(); kinect.stopSpeech(); kinect.release(); _endthreadex(0); return 0; } void doJob() { HANDLE hThread[3] = { 0 }; hThread[0] = (HANDLE) _beginthreadex(NULL,0,doJob1,NULL,0,NULL); if (hThread[0] == 0) throw runtime_error("cannot create thread 0"); hThread[1] = (HANDLE) _beginthreadex(NULL,0,doJob2,NULL,0,NULL); if (hThread[1] == 0) throw runtime_error("cannot create thread 1"); hThread[2] = (HANDLE) _beginthreadex(NULL,0,doJob3,NULL,0,NULL); if (hThread[2] == 0) throw runtime_error("cannot create thread 2"); DWORD ret = WaitForMultipleObjects(3,hThread,TRUE,INFINITE); if (ret == WAIT_FAILED) throw runtime_error("wait failed"); } int main(int argc, char** argv) { try { doJob(); } catch (exception &ex) { cout << ex.what() << endl; string s; cin >> s; } return 0; } |
全てのスレッドが終了すると、コンソール画面も閉じてプログラムが終了します。
ただし、「音声認識と骨格認識」および「音声認識とbodyIndex画像取得」の同時動作は相性が悪く、 前者では骨格認識のスレッドが非常に遅くなり、また後者では音声認識の精度が非常に悪くなります。 どちらかのスレッドにSleep(INT32)関数を入れて動作のタイミングを調整すれば 少しはマシになるのかもしれませんが未確認です。
上記のzipファイルには必ずしも最新の NtKinect.h が含まれていない場合があるので、 こちらから最新版をダウンロードして 差し替えてお使い下さい。