DxLib用KinectSDKまとめクラスヘッダファイル

12/02/09[thu]
【ダウンロード】→KinectSDKforDxLib_v1.0.zip


【注意】
 ・ここに掲載されているコードについて動作の保証は出来ません
 ・いかなる不具合の責任も取れません
 ・あくまで自己責任
 ・深度,RGBデータともに640x480固定


【使い方】
 [前準備]
 1・VisualStudio2010でDxLibとKinectSDKの開発環境を整えます
 2・"KinectSDKforDxLib_v1.0.h"をインクルード


 [初期化]
 サンプルコード

#include "./dxlib/DxLib.h"
#include "KinectSDKforDxLib_v1.0.h"

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
	// DXライブラリ初期化
	SetMainWindowText("Kinect Game Test");					// システムバーのタイトルの変更
	SetGraphMode( WINDOW_SIZE_X , WINDOW_SIZE_Y , 32 ) ;	// 画面モードの変更
	ChangeWindowMode( true ) ;								// ウインドウモードに変更
	SetAeroDisableFlag( false ) ;							// Vista以降の Windows Aero を無効にするかどうかをセットする、TRUE:無効にする FALSE:有効にする( DxLib_Init の前に呼ぶ必要があります )
	SetDrawScreen( DX_SCREEN_FRONT ) ;						// 描画先画面を表にする
	if( DxLib_Init() == -1 ){								// DXライブラリ初期化処理
		OutputDebugString("[Dxlib] 初期化に失敗しました\n");
		WaitKey();
		return -1;
	}
	
	// kinectオブジェクト生成
	KinectDX kinect;
	
	SetDrawScreen( DX_SCREEN_BACK ) ;
	DxLib::printfDx("[Dxlib] 適当にキー入力でループスタート");
	DxLib::ScreenFlip();
	DxLib::WaitKey();
	
	while (true){
		DxLib::ClearDrawScreen();
		DxLib::clsDx();
		
		DxLib::ScreenFlip();								// 1フレーム処理結果の描画
		if( DxLib::ProcessMessage() < 0 ) break;			// Windows 特有の面倒な処理をDXライブラリにやらせる        // -1 が返ってきたらループを抜ける
		if( DxLib::CheckHitKey( KEY_INPUT_ESCAPE ) ) break;	// もしESCキーが押されていたらループから抜ける
	}
	DxLib::DxLib_End();										// DXライブラリの後始末
	return 0;
}

 通常と同様にオブジェクトを生成する手順でOKです
 KinectDX kinect;
 
 因みにmicrosoftのサンプルコードではグローバルオブジェクトで生成してました
 一応,グローバルで生成しても,グローバルでポインタで生成してnewしても大丈夫と思います(←

 デバッグ情報としてちょこちょことprintfDxで画面に出してるので,邪魔臭かったら手動で消しちゃって下さい
 「初期化完了」とかが出てこなかったり,なんちゃら失敗とか出てきたら初期化出来ていないので,
 kinectとの接続を確認してソフトを再起動してください
 (一応UnInitしたらプラグアンドプレイも出来るのかな・・・?)
 
 実行結果
 



 [モーターの操作]

HRESULT SetTiltMotor(int angle){
	// 前回更新から1350ms以上経過していないと動作させない
	if( (DxLib::GetNowCount() - this->lastSetSensorAngleTime) < 1350){
		return E_FAIL;
	}
	::NuiCameraElevationSetAngle(angle);
	this->lastSetSensorAngleTime = DxLib::GetNowCount();	// Windowsを起動してからのms時間を取得
	return S_OK;
}

 kinectのチルトモーターを制御します
 引数:int型 -27~+27
 返値:HRESULT S_OK or E_FAIL
 
 なんか前回動作させてから1350ms以上待たないといけないっぽいので,
 1350ms以内に再度指令が来ても無視してE_FAILを返すようにしてます

 デバッグ情報としてちょこちょことprintfDxで画面に出してるので,邪魔臭かったら手動で消しちゃって下さい
 
 実行結果
 なし



 [RGBカメラからのデータを取得]

int GetVideoGrHandle(void){
	::WaitForSingleObject( m_ImageEvent, INFINITE );
	// カメラデータの取得
    NUI_IMAGE_FRAME imageFrame;
    HRESULT hr = m_pNuiSensor->NuiImageStreamGetNextFrame( m_VideoStreamHandle, 0, &imageFrame );
    if ( FAILED( hr ) )	return -1;
	
	// 画像データの取得
    INuiFrameTexture * pTexture = imageFrame.pFrameTexture;
    NUI_LOCKED_RECT LockedRect;
    pTexture->LockRect( 0, &LockedRect, NULL, 0 );
    if ( LockedRect.Pitch != 0 ){
		// Dxlibで使用する画像データの構造体設定
		BaseVideoImage.GraphData = static_cast(LockedRect.pBits);		// イメージデータをそのままコピー
    }
    else{
        OutputDebugString( "Buffer length of received texture is bogus\r\n" );
    }

	// カメラデータの解放
    pTexture->UnlockRect( 0 );
    m_pNuiSensor->NuiImageStreamReleaseFrame( m_VideoStreamHandle, &imageFrame );

	// 2回目以降はグラフィックハンドルへ映像を転送
	ReCreateGraphFromBaseImage( &BaseVideoImage, VideoGrHandle ) ;
	return VideoGrHandle;
}

 RGBカメラからのデータを取得してDxLib用のグラフィックハンドルを返します
 引数:なし
 返値:int DxLib用のグラフィックハンドル (失敗時は-1)
 
 サンプルコード

	while (true)
	{
		DxLib::ClearDrawScreen();
		DxLib::clsDx();
		
		// RGBカメラ表示
		DxLib::DrawGraph(0,0, kinect.GetVideoGrHandle(), false);
		
		DxLib::ScreenFlip();									// 1フレーム処理結果の描画
		if( DxLib::ProcessMessage() < 0 ) break;				// Windows 特有の面倒な処理をDXライブラリにやらせ
		if( DxLib::CheckHitKey( KEY_INPUT_ESCAPE ) ) break;		// もしESCキーが押されていたらループから抜ける
	}
 kinectから来るデータが約30fpsなので,きっちり回したい方は改造が必要かも
 fpsではなくて経過時間を測定してゲームをすすめるような作りがいいかもしれませんね
 
 いつものDrawGraphで描画できるので,DrawTurnGraphとかDrawModiGraphでも描画出来ると思います
 
 実行結果
 



 [深度カメラからのデータを取得]

int GetDepthGrHandle(void){
	::WaitForSingleObject( m_DepthEvent, INFINITE );
	// カメラデータの取得
    NUI_IMAGE_FRAME depthFrame;
	HRESULT hr = m_pNuiSensor->NuiImageStreamGetNextFrame( m_DepthStreamHandle, 0, &depthFrame );
	if ( FAILED( hr ) )	return -1;
	
	// 画像データの取得
    INuiFrameTexture * pTextureDepth = depthFrame.pFrameTexture;
    NUI_LOCKED_RECT LockedRectDepth;
    pTextureDepth->LockRect( 0, &LockedRectDepth, NULL, 0 );

	// 初期色
	if( GetDepthGrHandleOption.FillTransColor == true ){
		int r,g,b;
		GetTransColor(&r, &g, &b);
		FillSoftImage(softhandle, r,g,b,0);
	}else{
		FillSoftImage(softhandle, 0,0,0,0);
	}

	if ( LockedRectDepth.Pitch != 0 ){
        // draw the bits to the bitmap
        USHORT * pBufferRun = (USHORT *)LockedRectDepth.pBits;		// 深度データの取り込み?
		USHORT RealDepth;	// 深度情報
		USHORT Player;		// プレイヤーID情報
		
		LONG nx, ny;
		BYTE R,G,B;
		for(int y=0; BaseDepthImage.Height > y; y++){
			for(int x=0; BaseDepthImage.Width > x; x++){

				// 深度情報0の領域は描画しない
				if(GetDepthGrHandleOption.EnableZeroDepth == false){
					if(*pBufferRun == 0){
						++pBufferRun;
						continue;
					}
				}

				RealDepth = NuiDepthPixelToDepth(*pBufferRun);
				Player    = NuiDepthPixelToPlayerIndex(*pBufferRun);
				BYTE intensity = (BYTE)~(RealDepth >> 5);	// 13bit → 8bit (+反転)

				// プレイヤー以外の深度情報は無視
				if(GetDepthGrHandleOption.DrawBackGround == false){
					if(Player == 0){
						++pBufferRun;
						continue;
					}
				}

				// ズレ補正
				if(GetDepthGrHandleOption.EnableCorrection == true){
					hr = ::NuiImageGetColorPixelCoordinatesFromDepthPixelAtResolution(NUI_IMAGE_RESOLUTION_640x480,
																						NUI_IMAGE_RESOLUTION_640x480,
																						NULL,
																						x, y,								// coordinate in depth image space
																						0,								// The depth value in depth image space.
																						&nx, &ny);
					if( hr != S_OK ){
						++pBufferRun;
						continue;
					}
				}else{
					nx = x;
					ny = y;
				}

				// プレイヤーに色を付ける
				if(GetDepthGrHandleOption.ColorPlayer == true){
					R = intensity >> g_IntensityShiftByPlayerR[Player];
					G = intensity >> g_IntensityShiftByPlayerG[Player];
					B = intensity >> g_IntensityShiftByPlayerB[Player];
				}else{
					R = intensity;
					G = intensity;
					B = intensity;
				}

				DrawPixelSoftImage(softhandle, nx, ny,R, G, B, 0);
				++pBufferRun;
			}
		}
	}
    else{
        OutputDebugString( "Buffer length of received texture is bogus\r\n" );
    }

	// 前のグラフィックハンドルを削除する
	DeleteGraph(DepthGrHandle);

	// CPUで扱うイメージからグラフィックハンドルを作成する
	DepthGrHandle = CreateGraphFromSoftImage(softhandle);

	// カメラデータの解放
    pTextureDepth->UnlockRect( 0 );
    m_pNuiSensor->NuiImageStreamReleaseFrame( m_DepthStreamHandle, &depthFrame );

	return DepthGrHandle;
}

 深度カメラからのデータを取得してDxLib用のグラフィックハンドルを返します
 引数:なし
 返値:int DxLib用のグラフィックハンドル (失敗時は-1)
 
 オプションもセットです

 	struct DepthStatus{
		bool ColorPlayer;		// プレイヤー認識時に色をつける
		bool DrawBackGround;	// プレイヤー以外の深度データも描画する
		bool EnableCorrection;	// 深度データをRGBカメラの座標に補正する
		bool EnableZeroDepth;	// 深度0のデータも描画する(BYTE intensity の所で反転するので255)
		bool FillTransColor;	// 最初に透過色で塗りつぶす(falseで黒塗りから描画)
	}GetDepthGrHandleOption;
 いろんな深度データの取り方があると思うので汎用的に・・・と思ったら結局こうなってしまった
 もっと良いやり方無いかな?
 好きなように弄ってやってください
 
 サンプルコード

	while (true)
	{
		DxLib::ClearDrawScreen();
		DxLib::clsDx();
		
		// キー入力でオプション変更
		if( CheckHitKey( KEY_INPUT_1 ))			kinect.GetDepthGrHandleOption.ColorPlayer = 0;
		else if( CheckHitKey( KEY_INPUT_2 ))	kinect.GetDepthGrHandleOption.ColorPlayer = 1;
		else if( CheckHitKey( KEY_INPUT_3 ))	kinect.GetDepthGrHandleOption.DrawBackGround = 0;
		else if( CheckHitKey( KEY_INPUT_4 ))	kinect.GetDepthGrHandleOption.DrawBackGround = 1;
		else if( CheckHitKey( KEY_INPUT_5 ))	kinect.GetDepthGrHandleOption.EnableCorrection = 0;
		else if( CheckHitKey( KEY_INPUT_6 ))	kinect.GetDepthGrHandleOption.EnableCorrection = 1;
		else if( CheckHitKey( KEY_INPUT_7 ))	kinect.GetDepthGrHandleOption.EnableZeroDepth = 0;
		else if( CheckHitKey( KEY_INPUT_8 ))	kinect.GetDepthGrHandleOption.EnableZeroDepth = 1;
		else if( CheckHitKey( KEY_INPUT_9 ))	kinect.GetDepthGrHandleOption.FillTransColor = 0;
		else if( CheckHitKey( KEY_INPUT_0 ))	kinect.GetDepthGrHandleOption.FillTransColor = 1;
		
		printfDx(" ColorPlayer=%d\n DrawBackGround=%d\n EnableCorrection=%d\n EnableZeroDepth=%d\n FillTransColor=%d\n",
					kinect.GetDepthGrHandleOption.ColorPlayer,
					kinect.GetDepthGrHandleOption.DrawBackGround,
					kinect.GetDepthGrHandleOption.EnableCorrection,
					kinect.GetDepthGrHandleOption.EnableZeroDepth,
					kinect.GetDepthGrHandleOption.FillTransColor);
					
		// 深度カメラ表示
		DxLib::DrawGraph(0,0, kinect.GetDepthGrHandle(), true);
		
		DxLib::ScreenFlip();									// 1フレーム処理結果の描画
		if( DxLib::ProcessMessage() < 0 ) break;				// Windows 特有の面倒な処理をDXライブラリにやらせ
		if( DxLib::CheckHitKey( KEY_INPUT_ESCAPE ) ) break;		// もしESCキーが押されていたらループから抜ける
	}
 EnableCorrectionが重いです(←
 DrawBackGroundを切ったり,EnableZeroDepthを切ったりすると軽くなるかも
 描画時に透過を有効にしてRGBと重ねるのもアリかと
 
 例の如くピクセル単位の全ループ処理です
 microsoftのサンプルソースも同様にしてたので,深度データにプレイヤー情報をいれて取得する場合
 こうするしか無いみたいです
 深度情報だけだと直接描画できるかもかも
 深度データを直接扱いたい方は改造が必要  
 実行結果
 



 [トラッキングデータを取得する]

int GetSkeletonData(Vector4 *pSkel1, Vector4 *pSkel2){
	::WaitForSingleObject( m_SkeletonEvent,INFINITE );
    NUI_SKELETON_FRAME SkeletonFrame = {0};
    bool bFoundSkeleton = false;
	int user[2] = {-1,-1};
	int count = 0;

    if ( SUCCEEDED(m_pNuiSensor->NuiSkeletonGetNextFrame( 0, &SkeletonFrame )) ){
		// トラッキングされているかを調べる
        for ( int i = 0 ;  NUI_SKELETON_COUNT > i ; i++ ){
            if( SkeletonFrame.SkeletonData[i].eTrackingState == NUI_SKELETON_TRACKED){
                bFoundSkeleton = true;	// どれかがトラッキングされていればtrue
				user[count] = i;
				count++;
            }
        }
    }
    // no skeletons!
    if( !bFoundSkeleton ){
        return -1;
    }

	// トラッキングデータのスムージング
    HRESULT hr = m_pNuiSensor->NuiTransformSmooth(&SkeletonFrame,NULL);
    if ( FAILED(hr) ){
        return -2;
    }

	if( user[0] != -1 ){
		for(int i=0; NUI_SKELETON_POSITION_COUNT > i; i++){
			pSkel1[i] = SkeletonFrame.SkeletonData[ user[0] ].SkeletonPositions[i];
		}
	}
	if( user[1] != -1 ){
		for(int i=0; NUI_SKELETON_POSITION_COUNT > i; i++){
			pSkel2[i] = SkeletonFrame.SkeletonData[ user[1] ].SkeletonPositions[i];
		}
	}
	return count;
}

 スケルトンデータ(各ポイントの3次元座標)を取得します
 引数:Vector4型 user1[20],Vector4型 user2[20](先頭ポインタを渡す)
 返値:int トラッキング人数
       1 = user1がトラッキングされている状態
       2 = user2がトラッキングされている状態
       3 = user1とuser2が両方トラッキングされている状態
       -1 = トラッキング失敗(人数0)
       -2 = スムージング処理失敗
 
 サンプルコード

	Vector4 user1[NUI_SKELETON_POSITION_COUNT];		// NUI_SKELETON_POSITION_COUNTは20(定数)
	Vector4 user2[NUI_SKELETON_POSITION_COUNT];
	
	while (true)
	{
		DxLib::ClearDrawScreen();
		DxLib::clsDx();
		
		// トラッキング
		int count = kinect.GetSkeletonData(user1, user2);
		
		float x,y;
		if( count == 1 || count == 3){
			for(int j=0; NUI_SKELETON_POSITION_COUNT > j; j++){
				NuiTransformSkeletonToDepthImage(user1[j], &x, &y, NUI_IMAGE_RESOLUTION_640x480);	// x,yの引数の型に注意
				DrawCircle((int)(x), (int)(y), 5, 0x0000ff, true);
			}
		}
		if( count == 2 || count == 3){
			for(int j=0; NUI_SKELETON_POSITION_COUNT > j; j++){
				NuiTransformSkeletonToDepthImage(user2[j], &x, &y, NUI_IMAGE_RESOLUTION_640x480);
				DrawCircle((int)(x), (int)(y), 5, 0x00FF00, true);
			}
		}
		printfDx( "count = %d\n" , count) ;
		
		DxLib::ScreenFlip();									// 1フレーム処理結果の描画
		if( DxLib::ProcessMessage() < 0 ) break;				// Windows 特有の面倒な処理をDXライブラリにやらせ
		if( DxLib::CheckHitKey( KEY_INPUT_ESCAPE ) ) break;		// もしESCキーが押されていたらループから抜ける
	}
 恐らく一番使われるであろうトラッキング.(むしろこれ以外殆ど使う場面は無いんじゃないだろうか)
 トラッキングされてるかどうかは引数で判断してください
 
 kinectから直接受け取るスケルトンデータはxが-2.2~2.2,yが-1.6~1.6,zが0~4.0の
 kinectを原点としたメートル単位の相対座標なので,画面に描画する場合は変換してあげましょう
 サンプルコードはSDKの関数でスケルトンの座標から深度の座標に変換しています
 
 実行結果
 



 [骨格の座標をカラー座標に変換する]

HRESULT MapSkeletonPointToColor(Vector4 point, long *x, long *y){
	float nx, ny;
	NuiTransformSkeletonToDepthImage(point, &nx, &ny, NUI_IMAGE_RESOLUTION_640x480);
	hr = ::NuiImageGetColorPixelCoordinatesFromDepthPixelAtResolution(NUI_IMAGE_RESOLUTION_640x480,
																	NUI_IMAGE_RESOLUTION_640x480,
																	NULL,
																	(LONG)nx, (LONG)ny,			// coordinate in depth image space
																	0,							// The depth value in depth image space.
																	x, y);
	return hr;
}

 骨格の座標をカラー座標に変換します
 引数:Vector4型 user1の関節の座標箇所,int型 取得するx座標,int型 取得するy座標
 返値:HRESULT
 
 サンプルコード

	Vector4 user1[NUI_SKELETON_POSITION_COUNT];
	Vector4 user2[NUI_SKELETON_POSITION_COUNT];
	
	while (true)
	{
		DxLib::ClearDrawScreen();
		DxLib::clsDx();
		
		// RGBカメラ表示
		DxLib::DrawGraph(0,0, kinect.GetVideoGrHandle(), false);

		// 深度カメラ表示
		DxLib::DrawGraph(0,0, kinect.GetDepthGrHandle(), true);
		
		// トラッキング
		int count = kinect.GetSkeletonData(user1, user2);
		LONG x,y;
		if( count == 1 || count == 3){
			for(int j=0; NUI_SKELETON_POSITION_COUNT > j; j++){
				kinect.MapSkeletonPointToColor(user1[j],&x,&y);			// xとyはLONG型!!!
				DrawCircle((int)(x), (int)(y), 5, 0x0000ff, true);
			}
		}
		if( count == 2 || count == 3){
			for(int j=0; NUI_SKELETON_POSITION_COUNT > j; j++){
				kinect.MapSkeletonPointToColor(user2[j],&x,&y);
				DrawCircle((int)(x), (int)(y), 5, 0x00FF00, true);
			}
		}
		printfDx( "count = %d\n" , count) ;
		
		DxLib::ScreenFlip();									// 1フレーム処理結果の描画
		if( DxLib::ProcessMessage() < 0 ) break;				// Windows 特有の面倒な処理をDXライブラリにやらせ
		if( DxLib::CheckHitKey( KEY_INPUT_ESCAPE ) ) break;		// もしESCキーが押されていたらループから抜ける
	}
 マネージでしか提供されていないMapSkeletonPointToColorを再現?
 とは言っても,スケルトン座標→深度座標→RGB座標って手順で変換してるだけ
 深度→RGBは深度画像の所で使ってたのと同じ.まぁ数が少ないからそんなに重くないかと
 
 サンプルコードはRGB,深度も同時に描画してます
 
 実行結果
 




トップページに戻る