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 が含まれていない場合があるので、 こちらから最新版をダウンロードして 差し替えてお使い下さい。