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