KinectをARToolKitで動かしてみた(実験その3)。マーカを床に置いて、Depthカメラ情報と重ねてみた。ずれ具合がよくわかる。 #openkinect #kinect #artoolkit VT: http://twitvideo.jp/044Pt
17:13 from ついっぷる/twipple
ARToolKitのarVideoXXXX関数は戻り値が、正常:0、異常:-1、なのか。勘違いしていた。
by robo8080 on Twitter
【注意】 以下のプログラムは、私が試行錯誤で調べたものを参考のためにまとめたものです。
バグ、間違い等があるかもしれませんので、流用する場合はご注意ください。
また、もっと簡単な方法があるかもしれません。
このサンプルプログラムのコンパイル方法は、【その1.VC++プロジェクト作成編】を参照してください。
【追記'10.12.05】
このサンプルプログラムのままでは、Depthカメラと画像カメラ間のずれがかなり大きくなります。
ここを参考に改造を加えると、かなり改善される可能性があります。
ただし、Kinectの個体差のために効果はそれぞれ異なります。
Depthカメラと画像カメラ間のキャリブレーション方法が確立されれば解決するかもしれません。
【追記'10.12.07】
knVideoGetDepthRaw()関数を追加。
このサンプルプログラムでは異常処理をかなり手抜きしてありますので、流用する場合は必要に応じて実装してください。
いずれ機会をみて改訂します。
//---"knVideo.h"---ここから-------------------------------------------------------
//2010.12.03 Ver.1.00
//2010.12.07 Ver.1.10 追加 knVideoGetDepthRaw()関数
#include "Kinect-win32.h"
#include "Kinect-Utility.h"
int knVideoDispOption(void);
int knVideoOpen(char *config);
int knVideoClose(void);
int knVideoCapStart(void);
int knVideoCapStop(void);
int knVideoCapNext(void);
int knVideoInqSize(int *x, int *y);
float * knVideoGetDepth(void);
unsigned short * knVideoGetDepthRaw(void); //追加 2010.12.07
unsigned char * knVideoGetDepthColor(void);
unsigned char * knVideoGetDepthImage(void);
unsigned char * knVideoGetImage(void);
int knVideoSetMotorPosition(float MotorPosition);
int knVideoSetLedMode(int LedMode);
//---"knVideo.h"---ここまで-------------------------------------------------------
//---"knVideo.cpp"---ここから-------------------------------------------------------
//2010.12.03 Ver.1.00
//2010.12.06 Ver.1.01 関数戻り値訂正
//2010.12.07 Ver.1.10 追加 knVideoGetDepthRaw()関数
#include "knVideo.h"
//このクラスは、"Kinect-Demo.cpp"の"KinectTestクラス"を参考にしてます。
class Listener: public Kinect::KinectListener
{
public:
unsigned short mGammaMap[2048];
unsigned char mDepthColor[640*480*3]; //RGB
unsigned char mDepthImage[640*480*3]; //RGB
float mDepthBuffer[640*480];
Kinect::Kinect *mKinect;
float mMotorPosition;
int mLedMode;
public:
void GetDepth()
{
mKinect->ParseDepthBuffer();
int i =0 ;
for (int y = 0;y<480;y++)
{
float *destdepth = mDepthBuffer + ((y)*(640));
for (int x = 0;x<640;x++)
{
unsigned short Depth = mKinect->mDepthBuffer[i];
if (Kinect::Kinect_IsDepthValid(Depth))
{
*destdepth++ = Kinect::Kinect_DepthValueToZ(Depth);
}
else
{
*destdepth++ = -100000;
}
i++;
}
}
}
void ParseDepth()
{
mKinect->ParseDepthBuffer();
int i =0 ;
for (int y = 0;y<480;y++)
{
unsigned char *destrow = mDepthColor + ((y)*(640))*3;
float *destdepth = mDepthBuffer + ((y)*(640));
for (int x = 0;x<640;x++)
{
unsigned short Depth = mKinect->mDepthBuffer[i];
if (Kinect::Kinect_IsDepthValid(Depth))
{
float D = Kinect::Kinect_DepthValueToZ(Depth);
*destdepth++ = D;
}
else
{
*destdepth++ = -100000;
}
int pval = mGammaMap[Depth];
int lb = pval & 0xff;
switch (pval>>8)
{
case 0:
destrow[2] = 255; destrow[1] = 255-lb; destrow[0] = 255-lb;
break;
case 1:
destrow[2] = 255; destrow[1] = lb; destrow[0] = 0;
break;
case 2:
destrow[2] = 255-lb; destrow[1] = 255; destrow[0] = 0;
break;
case 3:
destrow[2] = 0; destrow[1] = 255; destrow[0] = lb;
break;
case 4:
destrow[2] = 0; destrow[1] = 255-lb; destrow[0] = 255;
break;
case 5:
destrow[2] = 0; destrow[1] = 0; destrow[0] = 255-lb;
break;
default:
destrow[2] = 0; destrow[1] = 0; destrow[0] = 0;
break;
}
destrow+=3;
i++;
}
}
}
void ParseDepthImage()
{
int i =0 ;
mKinect->ParseDepthBuffer();
for (int y = 0;y<480;y++)
{
unsigned char *destrow = mDepthImage + ((y)*(640))*3;
float *destdepth = mDepthBuffer + ((y)*(640));
for (int x = 0;x<640;x++)
{
unsigned short Depth = mKinect->mDepthBuffer[i];
if (Kinect::Kinect_IsDepthValid(Depth))
{
float D = Kinect::Kinect_DepthValueToZ(Depth);
*destdepth++ = D;
float xx = (float)x;
float yy = (float)y;
float zz = (float)D;
Kinect::KinectDepthToWorld(xx,yy,zz);
//if((x == 320)&&(y == 240)) printf("xx = %f yy = %f zz = %f \n",xx,yy,zz);
float cx = xx;
float cy = yy;
Kinect::KinectWorldToRGBSpace(cx,cy,zz);
//if((x == 320)&&(y == 240)) printf("cx = %f cy = %f\n",cx,cy);
destrow[2] = mKinect->mColorBuffer[(((int)cx)+((int)cy)*(640))*3+2]; //B
destrow[1] = mKinect->mColorBuffer[(((int)cx)+((int)cy)*(640))*3+1]; //G
destrow[0] = mKinect->mColorBuffer[(((int)cx)+((int)cy)*(640))*3+0]; //R;
}
else
{
destrow[0] = 0; destrow[1] = 0; destrow[2] = 0;
*destdepth++ = -100000;
}
destrow+=3;
i++;
}
}
}
Listener(Kinect::Kinect *inK)
{
mKinect = inK;
for (int i=0; i<2048; i++)
{
mGammaMap[i] = (unsigned short)(float)(powf(i/2048.0f,3)*6*6*256);
};
mMotorPosition = 1.0;
mLedMode = 0;
}
~Listener()
{
mKinect->RemoveListener(this);
}
void Run()
{
mKinect->AddListener(this);
}
virtual void KinectDisconnected(Kinect::Kinect *K)
{
printf("Kinect disconnected!\n");
}
virtual void DepthReceived(Kinect::Kinect *K) {}
virtual void ColorReceived(Kinect::Kinect *K) {}
virtual void AudioReceived(Kinect::Kinect *K) {} // not functional yet:
};
/////////////////////////////////////////////////////////////////////////////////////
Listener *L;
Kinect::KinectFinder KF;
static unsigned char gColorBuffer[640 * 480 * 4]; // BGRA
static unsigned char gDepthColorBuffer[640 * 480 * 4]; // BGRA
static unsigned char gDepthImageBuffer[640 * 480 * 4]; // BGRA
static float gDepthBuffer[640 * 480];
unsigned short gDepthRawBuffer[640 * 480]; //追加 2010.12.07 knVideoGetDepthRaw()関数で使用
int knVideoOpen(char *config)
{
if (KF.GetKinectCount() < 1)
{
printf("Unable to find Kinect devices... Is one connected?\n");
// return 0; //訂正 2010.12.06
return -1; //訂正 2010.12.06
}
Kinect::Kinect *K = KF.GetKinect();
if (K == 0)
{
printf("error getting Kinect...\n");
// return 0; //訂正 2010.12.06
return -1; //訂正 2010.12.06
};
// create a new Listener instance
L = new Listener(K);
K->SetLedMode(Kinect::Led_Yellow);
K->SetMotorPosition(1);
// return 1; //訂正 2010.12.06
return 0; //訂正 2010.12.06
};
int knVideoCapStart(void)
{
L->Run();
L->mKinect->SetLedMode(Kinect::Led_Green);
// return 1; //訂正 2010.12.06
return 0; //訂正 2010.12.06
};
int knVideoDispOption(void) //ダミー
{
// return 1; //訂正 2010.12.06
return 0; //訂正 2010.12.06
};
int knVideoClose(void)
{
L->mKinect->SetLedMode(Kinect::Led_Off);
// remove and delete the listener instance
L->mKinect->RemoveListener(L);
delete L;
// return 1; //訂正 2010.12.06
return 0; //訂正 2010.12.06
};
int knVideoCapStop(void) //ダミー
{
// return 1; //訂正 2010.12.06
return 0; //訂正 2010.12.06
};
int knVideoCapNext(void) //ダミー
{
// return 1; //訂正 2010.12.06
return 0; //訂正 2010.12.06
};
//KienectのDepth情報を取得する
float * knVideoGetDepth(void)
{
L->GetDepth();
for( int i = 0; i < (640*480); i++)
{
gDepthBuffer[i] = L->mDepthBuffer[i];
}
return gDepthBuffer;
}
//KienectのDepth情報を色に変換して取得する
unsigned char * knVideoGetDepthColor(void)
{
L->ParseDepth();
int i= 0;
int j =0;
for( j = 0; j < (640*480*3); j+=3)
{
gDepthColorBuffer[i+0] = L->mDepthColor[j+0]; //B
gDepthColorBuffer[i+1] = L->mDepthColor[j+1]; //G
gDepthColorBuffer[i+2] = L->mDepthColor[j+2]; //R
gDepthColorBuffer[i+3] = 0; //A
i += 4;
}
return gDepthColorBuffer;
}
//KienectのDepthカメラに対応するカメラ画像を取得する(多少ずれる)
//この関数を使うときは、先に"knVideoGetImage()"関数を実行しておく必要がある。
unsigned char * knVideoGetDepthImage(void)
{
L->ParseDepthImage();
int i= 0;
int j =0;
for( j = 0; j < (640*480*3); j+=3)
{
gDepthImageBuffer[i+0] = L->mDepthImage[j+2]; //B
gDepthImageBuffer[i+1] = L->mDepthImage[j+1]; //G
gDepthImageBuffer[i+2] = L->mDepthImage[j+0]; //R
gDepthImageBuffer[i+3] = 0; //A
i += 4;
}
return gDepthImageBuffer;
}
//Kienectのカメラ画像を取得する
unsigned char * knVideoGetImage(void)
{
L->mKinect->ParseColorBuffer();
int i= 0;
int j =0;
for( j = 0; j < (640*480*3); j+=3)
{
gColorBuffer[i+0] = L->mKinect->mColorBuffer[j+2]; //B
gColorBuffer[i+1] = L->mKinect->mColorBuffer[j+1]; //G
gColorBuffer[i+2] = L->mKinect->mColorBuffer[j+0]; //R
gColorBuffer[i+3] = 0; //A
i += 4;
}
return gColorBuffer;
};
int knVideoInqSize(int *x, int *y)
{
*x = 640;
*y = 480;
// return 1; //訂正 2010.12.06
return 0; //訂正 2010.12.06
};
int knVideoSetMotorPosition(float MotorPosition)
{
if(MotorPosition < 0.0) MotorPosition = 0.0f;
if(MotorPosition > 1.0) MotorPosition = 1.0f;
// SetMotorPosition accepts 0 to 1 range
L->mMotorPosition = MotorPosition;
L->mKinect->SetMotorPosition(MotorPosition);
// return 1; //訂正 2010.12.06
return 0; //訂正 2010.12.06
}
int knVideoSetLedMode(int LedMode)
{
if(LedMode < 0) LedMode = 0;
if(LedMode > 7) LedMode = 7;
L->mLedMode = LedMode;
// Led mode ranges from 0 to 7, see the "Kinect-win32.h" for possible values
L->mKinect->SetLedMode(LedMode);
// return 1; //訂正 2010.12.06
return 0; //訂正 2010.12.06
}
//追加 2010.12.07 knVideoGetDepthRaw()関数
//KienectのRawDepthデータを取得する
unsigned short *knVideoGetDepthRaw(void)
{
L->mKinect->ParseDepthBuffer();
for( int i = 0; i < (640*480); i++)
{
gDepthRawBuffer[i] = L->mKinect->mDepthBuffer[i];
}
return gDepthRawBuffer;
};
//---"knVideo.cpp"---ここまで-------------------------------------------------------
【注意】以下の手順は、私が試行錯誤で調べたものを、参考のためにまとめたものです。
間違い勘違い等があるかもしれませんので、その場合はご了承ください。
また、もっと簡単な方法があるかもしれません。
1.開発環境
OS:Windows XP
使用言語:C++(Visual C++ 2010 Express)
ベースソフト:ARToolKit "ARToolKit-2.72.1-bin-win32.zip"
:OpenKinect "Kinect-v15-withsource.zip"
Visual C++ 2010 Express、ARToolKit、OpenKinectの使用方法は知っているものとして説明します。
わからない場合は、それぞれ詳しく解説しているサイトがありますのでそちらを参照ください。
2.ファイルを準備
ARToolKitをここからダウンロードします。
OpenKinectをここからダウンロードします。
今回使用したのは、以下の2つです。
ARToolKit-2.72.1-bin-win32.zip
Kinect-v15-withsource.zip
3.適当なディレクトリにファイルを解凍
ダウンロードしたファイルを適当なディレクトリに解凍します。
この例では、C:\testに解凍するものとします。
解凍すると次のようになります。
C:\test\ARToolKit-2.72.1-bin-win32
C:\test\Kinect-v15-withsource
4.VC++でビルドして動作確認
(1)Visual C++ 2010 Expressで以下のソリューションを開きビルドします。
C:\test\ARToolKit-2.72.1-bin-win32\ARToolKit.sln
C:\test\ARToolKit-2.72.1-bin-win32\Kinect.sln
C:\test\Kinect-v15-withsource\Kinect.sln
ソリューションを開くと変換しますかと聞いてくるので、変換を実行します。
以降、説明を簡単にするため、ARToolKitの"simpleTest2"プロジェクトを改造して使用するものとします。
(2)変換が終わったら、ソリューション構成をReleaseにして、ソリューションのビルドをします。
(大量の"warning"が出ますが、とりあえず無視しても大丈夫のようです。)
(3)USBカメラを接続してARToolKitが正常に動くのを確認します。
C:\test\ARToolKit\bin\simpleTest2.exe
"Esc"キーを押すと終了します。
【注意】ARToolKitをDebugでビルドした"simpleTest2d.exe"はうまく動かないので
以降、すべてReleaseでビルドするものとします。こんなダイアログが出ます。
原因はいずれ調べようと思っていますが、Releaseで動くので当面放置してます。
(4)Kinectを接続して、OpenKinectが正常に動くのを確認します。
C:\test\Kinect-v15-withsource\Kinect-Demo\Kinect-Demo.exe
"Esc"キーを押すと終了します。
【注意】Kinectを最初に接続したとき、ドライバをインストールするか、と聞いてくるので、
ドライバのあるディレクトリ"C:\test\Kinect-v15-withsource\Drivers"を指定します。
ドライバのインストールは3回あります。
OpenKinectの詳しい使い方の説明は省略します。
5.ARToolKitプロジェクトにKinectプロジェクトを追加
C:\test\ARToolKit-2.72.1-bin-win32\ARToolKit.slnを開きます。
ソリューションエクスプローラで、
ソリューション'ARToolKit'を右クリック->追加->既存のプロジェクト を選択します。
以下のファイルを追加します。
C:\test\Kinect-v15-withsource\Kinect.vcxproj
6.参照設定
ソリューションエクスプローラで、SimpleTest2プロジェクト右クリック->プロパティ を選択します。
共通プロパティ->Frameworkと参照 を選択します。
”新しい参照の追加”で、"Kinect"を追加します。
7.インクルードファイルディレクトリを追加
6.に引き続き、構成プロパティ->C++->全般 を選択します。
”追加のインクルードディレクトリ”に、"C:\test\Kinect-v15-withsource\"を追加します。
8.simpleTest2プロジェクトにファイルを追加
"simpleTest2.c"を一旦プロジェクトから除外します。
"simpleTest2.c"の拡張子をcppに変更し、"simpleTest2.cpp"を再度プロジェクトに追加します。
以下の2つのファイルをプロジェクトに追加します。
"knVideo.cpp"
"knVideo.h"
ファイルの詳細は【その2.サンプルプログラム編】を参照してください。
【追記'10.12.05】
"knVideo.cpp"と"knVideo.h"は、"Kinect"プロジェクトの方に追加したほうが
使いやすいかもしれません。その辺はお好みで。
9.使用するインクルードファイルを変更
"simpleTest2.cpp"の14行目付近にある
#include
をコメントアウトし、
#include "knVideo.h"
を追加します。
10."simpleTest2.cpp"の関数名を変更
ここで一度ビルドします。
ソリューション構成が"Release"になっていることを確認します。
ソリューションエクスプローラ->SimpleTest2プロジェクト を右クリック スタートアッププロジェクトに設定します。
ソリューションエクスプローラ->SimpleTest2プロジェクト->ビルド
そうすると、"arVideoXXX"という関数がエラーになるので、すべて"knVideoXXX"に変更します。
11.ビルド&実行
ソリューション構成が"Release"になっていることを確認します。
ソリューションエクスプローラ->SimpleTest2プロジェクト を右クリック スタートアッププロジェクトに設定します。
ソリューションエクスプローラ->SimpleTest2プロジェクト->ビルド でビルドが正常に終了したら、下記ファイルを実行します。
C:\test\ARToolKit\bin\simpleTest2.exe
うまくいけばこのようになるはずです。(使用したパターンは"pattHiro.pdf"です。)
"simpleTest2.cpp"の86行目を以下のように変更すると、DepthカメラのDepthを色に変換した
画像が表示されます。(ただし、この画像ではマーカの検出は出来ません。)
変更前:
if( (dataPtr = (ARUint8 *)knVideoGetImage()) == NULL ) {
変更後:
if( (dataPtr = (ARUint8 *)knVideoGetDepthColor()) == NULL ) {
---以上---