前回、カメラ画像に位置を合わせた深度データを取ってくる事ができたので、今回はそれを 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 件のコメント:
コメントを投稿
ひとことどうぞφ(・ω・,,)