2011/04/25

Kinectプチハック(エピソード4)

【ミッション: Kinectで立体復元に挑戦しよう】

前回奥行き情報を(一応)推定する事ができたので、そろそろ 3D にしてみようと思う。

本日のターゲットは、このどうしようもない写真。


これを、Kinect で適当に 3 次元復元して俯瞰したら、こうなった。

机上の本や PC はもちろん、部屋の隅っこの柱やパイプなどの凹凸がくっきり浮き出ている事が確認できる。

※ ちなみにぼくの隣は博士様の席なのだ。いないけど。



理論自体はほとんどそのままなのだが、前のソースは数値計算の精度的にアレだったり座標系の置き方的にアレだったりしたので、ほんのわずかながら改良を加えた。

ちなみに、依然として視点操作ができない。この辺はダメなままである。あと、毎度の事ながら、OpenCV 2.0 以外のバージョンではうまくビルドできない可能性があるので注意。もしダメそうだったらリンクするライブラリとかインクルードするヘッダファイルとかをいじってみてね。

#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();
}

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();

    // 画像を反転
    cvFlip(iplimage, iplimage, 1);
    cvFlip(ipldepth, ipldepth, 1);

    glClear(GL_COLOR_BUFFER_BIT);
    glPointSize(1);
    glBegin(GL_POINTS);

    // 点の色は白
    glColor3d(1.0, 1.0, 1.0);

    // 点を打っていく
    for(int y = 0; y < IMAGE_HEIGHT; y++) {
        // ピクセルデータへのポインタ
        short* pDepthImgData = (short*)(
            ipldepth->imageData + y * ipldepth->widthStep
        );
        
        /* 点群にRGB画像のテクスチャを貼りたいとき用
          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;

            // 3次元位置の推定
            double X = (x - cx)/f;
            double Y = (y - cy)/f;
            double Z =          1;

            // 3次元点を求める
            X *= (double)d;
            Y *= (double)d;
            Z *= (double)d;

            /* ↓これを書くと、点に画像データの色がつく
              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;
              glColor3d(R, G, B);
            */

            // 座標を指定して点をプロット
            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(
        0, -5000, 1950, 
        0,     0, 2000, 
        0,     0,    1
    );
}

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;
}

ふぅ…。

もはやこのシリーズは自分の中で備忘録的な位置づけになってしまったので、ソースコードが解りにくいのは仕様。あしからず。

8 件のコメント:

  1. 2011/04/25 Kinectプチハック(エピソード4)
    ですが、追加のインクルードファイル、ライブラリファイル、依存ファイルの一覧を教えてください。

    返信削除
    返信
    1. コメントありがとうございます。

      だいぶ前に書いた記事ですのであまり自信はありませんが、使用したライブラリは以下の通りであったと記憶しています。

      GLUT 3.7
      OpenCV 2.0
      OpenNI 1.0.0.23
      Nite 1.3.0.17

      このうち、特に OpenNI の導入には苦戦しました。こちらの解説が参考になると思います。

      また、OpenCV の最新版(2.3)ではライブラリの構成が変わっているため、2.0 以外をお使いになる場合はソースコードを修正する必要があるかと思われます。


      各々の依存しているヘッダファイルや静的リンクライブラリ(拡張子が .lib のファイル)は、ソースコード中で明示的に書いてある通りです。

      【ヘッダ】
      ・GL/glut.h       // GLUT
      ・cv.h            // OpenCV
      ・highgui.h       // OpenCV
      ・XnCppWrapper.h  // OpenNI


      【静的リンクライブラリ】
      ・glut32.lib      // GLUT
      ・cv200.lib       // OpenCV
      ・cxcore200.lib   // OpenCV
      ・highgui200.lib  // OpenCV
      ・openNI.lib      // OpenNI

      削除
  2. GNです、コメントを書いてくれてありがとうございます。

    実はまた質問があります。
    CMakeとMPI_CXXをご存知ですか?
    御存じなら1つ教えてほしいのですが、MPI_CXXをwindowsにインストールする方法を知っていたら教えてくれないでしょうか?

    返信削除
    返信
    1. コメントありがとうございます。

      申し訳ありませんが、私の知識不足ゆえお役に立つことができません。

      なお、Open MPI 1.5.4 の Windows 向けインストーラパッケージには、mpicxx.exe という名前の(恐らく Windows 向け)バイナリが含まれていると思うのですが、詳しい事は分かりません。

      削除
    2. コメントありがとうございます。
      たーせるさんのコメント通りにプログラムを作成します。

      削除
  3. windows7 32bitとPCL All-One.exeを用いてkinect動画に法線ベクトルを表示するソースコードを作成したいのです。

    また、以下の2つの機能も必要としています。
    ①法線ベクトルの数を増やす
    ②入力画像を領域分け(背景と物体を分ける)する

    上記の条件を満たすソースコードの作成を教えてください。
    お願いします。

    返信削除
    返信
    1. コメントありがとうございます。

      申し訳ございませんが、本文と無関係なご質問にはお答えしかねます。

      ご了承ください。

      削除
  4. OpenNIを勉強している初心者なのですが質問があります。
    3次元における深度を取得したいのですが、
    画素(x, y)に対応する深度データ取得
    int d = (int)pDepthImgData[ipldepth->nChannels * x];
    で取得できる値は3次元点のKinectからの直線距離のような気がするのですがこの値は深度になりますか?
    間違っていたらすいません。
    またKinectの焦点距離の取得の方法も知っていたら教えてもらえないでしょうか?

    返信削除

ひとことどうぞφ(・ω・,,)