前回、カメラ画像に位置を合わせた深度データを取ってくる事ができたので、今回はそれを 3 次元的にマッピングしてみようと思った。
これは、椅子と机の一部の奥行きデータを3次元的にプロットした結果である。
【射影変換の原理】
以下のような、カメラの光学中心を原点とする 3 次元 X-Y-Z 座標空間を、カメラ座標空間という。ここで便宜上、Z 軸はカメラ光軸に一致し、X 軸および Y 軸は、画像(投影面)の水平・垂直方向に対して平行な軸であるとする(※ 定義としてはかなり不十分なんだけど、厳密な説明はめんどいのでご容赦)。
カメラ座標空間における被写体の 3 次元点 X = (X, Y, Z)t を、投影面上の 2 次元座標 x = (x, y)t に射影する変換を射影変換と呼び、カメラの内部パラメータ行列
を用いて以下のように行う事ができる(ただし f はカメラ焦点距離、(cx, cy) は画像中心座標である)。
すなわち、
である。
仮に Z = f とおいて、上式を X, Y, Z について解くと、3次元点を貫くベクトルの各要素は X = x - cx, Y = y - cy, Z = f と求められる。
さらに、画素 (x, y) に対応する奥行き d が判れば、2 次元画像上の任意の点 (x, y) の3次元位置は以下のように表す事ができる。
この計算を、画像中の全画素に対して施せば、理論上、被写体の3次元化が可能である(※ 実は、ここで示した内部パラメータ行列はかなり簡略的な形である。精度はご容赦)。
だが、そのためには、Kinect のカメラの焦点距離 f 、および画像中心座標 (cx, cy) が既知でなくてはならない。
自力でそれらを推定するのはそれなりに骨が折れるので、Web 上を適当に探しまわったらこの辺のページになんか思わせぶりな数字が出てきたので、今回はそれをそのまま用いる事にした(本当は製品ごとに個体差がある)。
- f = 526.37013657
- cx = 313.68782938
- cy = 259.01834898
で、ひとまずこれを適当に実装したものが以下のソースコード。今回は 3 次元レンダリングという事で、OpenGL (と GLUT)を使用している。適当すぎて視点移動とかができない。
#include<cv.h> #include<highgui.h> #include<XnCppWrapper.h> #include<GL/glut.h> #include<limits.h> #pragma comment(lib,"cv200.lib") #pragma comment(lib,"cxcore200.lib") #pragma comment(lib,"highgui200.lib") #pragma comment(lib,"C:/Program files/OpenNI/Lib/openNI.lib") #pragma comment(lib, "glut32.lib") const int IMAGE_WIDTH = 640; const int IMAGE_HEIGHT = 480; const char* SAMPLE_XML_PATH = "C:/Program Files/OpenNI/Data/SamplesConfig.xml"; // グローバル変数。使い過ぎよくない。 xn::Context context; xn::EnumerationErrors errors; xn::DepthGenerator depth; xn::ImageGenerator image; xn::DepthMetaData depthMD; xn::ImageMetaData imageMD; cv::Ptr<IplImage> iplimage, ipldepth; // Kinectカメラの内部パラメータ // まぁ適当です const double f = 526.37013657; const double cx = 313.68782938; const double cy = 259.01834898; // ================== // 暇になったら再描画 // ================== void idle() { glutPostRedisplay(); } // ======================================= // 深度データを3次元的にマッピングして表示 // ======================================= void display() { context.WaitAnyUpdateAll(); // 深度・画像メタデータ取得 depth.GetMetaData(depthMD); image.GetMetaData(imageMD); // 深度データの座標ををカメラに合わせる depth.GetAlternativeViewPointCap().SetViewPoint(image); // ============================ // IplImage構造体にデータを格納 // ============================ iplimage->imageData = (char *)imageMD.WritableData(); ipldepth->imageData = (char *)depthMD.WritableData(); glClear(GL_COLOR_BUFFER_BIT); glPointSize(1); glBegin(GL_POINTS); // 点を打っていく for(int y = 0; y < IMAGE_HEIGHT; y++) { // ipldepthのピクセルデータへのポインタ short* pDepthImgData = (short*)( ipldepth->imageData + y * ipldepth->widthStep ); unsigned char* pCameraImgData = (unsigned char*)( iplimage->imageData + y * iplimage->widthStep ); for(int x = 0; x < IMAGE_WIDTH; x++) { // 画素(x, y)に対応する深度データ取得 int d = (int)pDepthImgData[ipldepth->nChannels * x]; if (d == 0) continue; d += (-SHRT_MAX / 2); // 3次元位置の推定 double X = x - cx; double Y = y - cy; double Z = f; double R = pCameraImgData [iplimage->nChannels*x+0]/255.0; double G = pCameraImgData [iplimage->nChannels*x+1]/255.0; double B = pCameraImgData [iplimage->nChannels*x+2]/255.0; // 3次元点を求める X *= d; Y *= d; Z *= d; // 焦点距離で割ってスケーリング X /= f; Y /= f; Z /= f; // ↓こう書くと、点に画像データの色がつく //glColor3d(R, G, B); // ↓こう書くと、白い点がプロットされる glColor3d(1.0, 1.0, 1.0); // 座標を指定して点をプロット glVertex3d(X, Y, Z); } } glEnd(); glFlush(); } void resize(int w, int h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0, (double)w / h, 1.0, INT_MAX); // 適当に視点を設定 gluLookAt( SHRT_MAX/4, SHRT_MAX/4, SHRT_MAX/8, -SHRT_MAX/4, -SHRT_MAX/4, -SHRT_MAX, 0, 1, 0 ); } void init() { glClearColor(0.0, 0.0, 0.0, 1.0); } int main(int argc, char** argv) { // OpenNI関連のいろいろ context.InitFromXmlFile(SAMPLE_XML_PATH); context.FindExistingNode(XN_NODE_TYPE_DEPTH,depth); context.FindExistingNode(XN_NODE_TYPE_IMAGE,image); // OpenCV関連のいろいろ iplimage = cvCreateImage( cvSize(IMAGE_WIDTH,IMAGE_HEIGHT),IPL_DEPTH_8U,3 ); ipldepth = cvCreateImage( cvSize(IMAGE_WIDTH,IMAGE_HEIGHT),IPL_DEPTH_16S,1 ); // OpenGL関連のいろいろ glutInitWindowSize(640, 480); glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA); glutCreateWindow("T"); glutIdleFunc(idle); glutDisplayFunc(display); glutReshapeFunc(resize); init(); glutMainLoop(); return 0; }
0 件のコメント:
コメントを投稿
ひとことどうぞφ(・ω・,,)