2011/04/13

Kinectプチハック

【ミッション: Kinectのカメラ映像をIplImageに変換して使おう】

前回のあらすじOpenNI を入れた

Kinect のカメラ、及び深度データを OpenCV で使うための最小限のソースコードをこの辺で見つけたので、さっそく試してみた。

自分の環境に合わせてじゃっかんソースコードを書き換えたが、概ねそのままである(下記のコードは、Visual Studio と OpenCV 2.0 のコンビネーションじゃないとうまく動かない気がする。特にライブラリのバージョンに注意)。
#include<cv.h>
#include<highgui.h>

// ※インクルードパスに
//  「C:\Program files\OpenNI\Include」
//  を追加しておこう!
#include<XnCppWrapper.h>

#pragma comment(lib,"C:/Program files/OpenNI/Lib/openNI.lib")

#define SAMPLE_XML_PATH "C:/Program Files/OpenNI/Data/SamplesConfig.xml"

int main() {
    xn::Context context;
    xn::EnumerationErrors errors;

    context.InitFromXmlFile(SAMPLE_XML_PATH);

    xn::DepthGenerator depth; // 深度コンテキスト
    context.FindExistingNode(XN_NODE_TYPE_DEPTH, depth);

    xn::ImageGenerator image; // イメージコンテキスト
    context.FindExistingNode(XN_NODE_TYPE_IMAGE, image);

    xn::DepthMetaData depthMD;
    xn::ImageMetaData imageMD;

    cv::Mat depthshow;
    cv::Mat show;
    int key = 0;
    bool isWarp=false;
    while (key!='q')
    {
        // waitとエラー処理
        context.WaitAnyUpdateAll();

        image.GetMetaData(imageMD);
        depth.GetMetaData(depthMD);

        // 画像と深度はOpenCVのMatに格納
        cv::Mat depth16(480,640,CV_16SC1,(unsigned short*)depthMD.WritableData());
        cv::Mat imni(480,640,CV_8UC3,(uchar*)imageMD.WritableData());

        // RGB色空間をBGRに変換
        cv::cvtColor(imni,show,CV_RGB2BGR);  
        
        // 11ビット画像を8ビット画像に変換
        depth16.convertTo(depthshow,CV_8U,-255/4096.0,255);

        cv::imshow("depth",depthshow);
        cv::imshow("image",show);
        key = cv::waitKey(33);
    }
    context.Shutdown();
    return 0;
}
これで、Kinect から労せずしてカメラ映像を取得する事ができるようになった。

……と見せかけて、上記のサンプルコードのままでは(僕にとって)多少問題がある

この段階で、画像データは OpenCV の汎用行列クラスである cv::Mat のオブジェクトとして扱えるようになるのだが、このページの情報によると、 cv::Mat のメンバである step の値が、4 バイト単位に調整されなくなるという不都合が残っているらしい。

以前書いたような Windows API との連携を考えた場合、画像の管理には素性がはっきりしている IplImage 構造体を用いた方が、面倒も少なくて済むだろう。

僕がてきとうに調べた限り、Kinect から取ってきた画像データを直に IplImage に変換するような先人のソースコードを見つける事はできなかった。

それでも、恐らく需要はあるかも知れないと思い、ちょっと作ってみることに。



もしかしたら少々面倒かなぁと思ったのだが、意外とあっけなくできたので掲載しておく。
#include<cv.h>
#include<highgui.h>
#include<XnCppWrapper.h>

#pragma comment(lib,"C:/Program files/OpenNI/Lib/openNI.lib")

const char* SAMPLE_XML_PATH = "C:/Program Files/OpenNI/Data/SamplesConfig.xml";

int main() {
    xn::Context context;
    xn::EnumerationErrors errors;

    context.InitFromXmlFile(SAMPLE_XML_PATH);
  
    xn::ImageGenerator image; // イメージコンテキスト
    context.FindExistingNode(XN_NODE_TYPE_IMAGE, image);

    xn::ImageMetaData imageMD;

    int key = 0;

    // 大人の事情で、IplImageで管理することに。
    cv::Ptr<IplImage> iplimage;
    iplimage = cvCreateImage(cvSize(640, 480),IPL_DEPTH_8U,3);
 
    while (key!='q') {
        // waitとエラー処理
        context.WaitAnyUpdateAll();

        // 画像メタデータ取得
        image.GetMetaData(imageMD);
        
        // IplImageにデータを格納
        iplimage->imageData = (char *)imageMD.WritableData();
        cvCvtColor(iplimage, iplimage, CV_RGB2BGR);

        // iplImageはcvShowImageで表示
        cvShowImage("hoge", iplimage);

        key = cv::waitKey(33);
    }

    context.Shutdown();
    return 0;
}
なお、cv::Mat オブジェクト宣言時、コンストラクタに IplImage 構造体を渡す事によって、事実上 IplImage から cv::Mat への変換は容易に可能である。 Kinect から取ってきた映像データは一度 IplImage に格納しておくのがよいだろう。

cv::imshow ではなく、cvShowImage 関数で IplImage を表示した結果は次の通りである。
ちゃんと画像を取ってくる事ができたので、あとは OpenCV で煮るなり焼くなりできる。

0 件のコメント:

コメントを投稿

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