2012年4月1日日曜日

Kinect for Windows SDKによる3次元ポイントクラウド

以前、OpenNIを用いたポイントクラウドの方法を紹介していました
 Kinect OpenNIによる3次元ポイントクラウド - 3次元描画 高速化

今回はマイクロソフトが公式で公開しているKinect for Windows SDKを使ってポイントクラウドを行ってみます。

描画にはいつもどおりOpenGLを使い、その過程でOpenCVを使っています。




まず初めにKinect SDKでRGB画像とDepth画像の取得方法について簡単に説明します。

細かい説明なんてどうでもいいんだよ!って人は 下のほうでサンプルプログラムがダウンロードできるので、下の方へスクロールしてってください。


では、プログラムの初期化処理は以下のようになる



初期化処理では
HRESULT NuiInitialize(
         DWORD dwFlags
)

で初期化します。

引数はRGB画像、Depth画像、骨格情報を得る場合
 NUI_INITIALIZE_FLAG_USES_COLOR | NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX | NUI_INITIALIZE_FLAG_USES_SKELETON
とします 次に画像取得のイベントハンドルを設定します。

Depth画像の大きさによって使用するNuiImageStreamOpen関数の引数が違うので
KINECT_DEPTH_WIDTHを#defineで事前に定義して、ここで条件分岐しています。
 
KinectSDKはBetaの時はDepth画像の解像度を320x240しか設定できなかったのですが
v1.0になり、640x480に設定することが可能となりました。

上記で設定したイベントで以下のようにしてRGB画像とDepth画像を取得します

扱いやすい形にするため、OpenCVのMatに格納します
また、その処理を関数化します


ここではGetImage(cv::Mat &image,HANDLE frameEvent,HANDLE streamHandle)
という関数を作りました。

この関数は以下のように用います。

関数の戻り値が-1の時は取得失敗ということです。

Kinect SDKではOpenNIを違い、Matへの格納が少しめんどうですね。

 次にポイントクラウドを行う上で、画像のピクセルが現実座標ではどこにあるのかを知る必要があります。

OpenNIではConvertProjectiveToRealWorldという関数がありました。

Kinect SDKでは以下の関数を用います

Vector4 NuiTransformDepthImageToSkeleton(
         LONG lDepthX,
         LONG lDepthY,
         USHORT usDepthValue
)


この関数はDepth画像のピクセルの位置(x,y)とその場所の値を与えることで現実座標系で帰ってきます。

では、Depth画像から現実座標配列を格納する関数を以下のように作ります。

NuiTransformDepthImageToSkeletonはDepth画像の解像度により引数を追加します。

これはbetaの時にDepth画像が640x480に対応していなかったからかなぁっと思います。


で、最後にポイントクラウドで描画します。

drawPointCloudではRGB画像とretrievePointCloudMapで取得した三次元位置を渡し、描画します。

また、この関数内でRGBとDepthのズレを補正しています。
OpepNIでいう
DepthGenerator.GetAlternativeViewPointCap().SetViewPoint は

KinectSDKでは
NuiImageGetColorPixelCoordinatesFromDepthPixel
または
NuiImageGetColorPixelCoordinatesFromDepthPixelAtResolution
を使います

これも、Depthの解像度により関数を変える必要があります。

まあ、NuiImageGetColorPixelCoordinatesFromDepthPixelのなかでNuiImageGetColorPixelCoordinatesFromDepthPixelAtResolutionが呼ばれてるんですけどね





これまで長く説明してきましたけど、まあ、こんな説明読むよりサンプルプログラムを見たほうが早いかな?

全体のプログラムはこんな感じです。


github
サンプルダウンロード

22 件のコメント:

  1. windows7 32bit VC++2010でKinect for Windows SDKによる3次元ポイントクラウド を実行すると

    1>------ ビルド開始: プロジェクト: fukuda, 構成: Debug Win32 ------
    1>main.obj : error LNK2019: 未解決の外部シンボル _glutSwapBuffers@0 が関数 "void __cdecl display(void)" (?display@@YAXXZ) で参照されました。
    1>main.obj : error LNK2019: 未解決の外部シンボル "void __cdecl cv::cvtColor(class cv::Mat const &,class cv::Mat &,int,int)" (?cvtColor@cv@@YAXABVMat@1@AAV21@HH@Z) が関数 "void __cdecl display(void)" (?display@@YAXXZ) で参照されました。
    ・・・・・
    1>C:\Users\mori\Documents\Visual Studio 2010\Projects\fukuda\Debug\fukuda.exe : fatal error LNK1120: 外部参照 51 が未解決です。
    ========== ビルド: 0 正常終了、1 失敗、0 更新不要、0 スキップ ==========
    というエラーが出ます。

    どうすれば解決できるか至急教えてください。

    返信削除
    返信
    1. >>後藤さん
      はじめまして。
      エラーに関してですが、拝見したところOpenGLとOpenCVのライブラリにリンクできていないようです。
      環境がわからないのでどこをどうすれば良いとは答えられないのですが、OpenGLやOpenCVの再インストールしたりしてインストールを確認したり、ライブラリのリンクや環境変数にPathが通っているかを確認してみてください。

      削除
  2. OpenCVは2.0と2.2のbinのパスを通してますが、OpenGLの場合どうすればよいのかわからないので、詳しく教えてください。

    返信削除
    返信
    1. 私は、正直OpenGLの設定はよく覚えていないのでこちらのサイトを参考にしてみてください。
      http://opengl.softwarecarpenter.biz/?eid=949759

      とは言ったものの、今までglutを使ってプログラムを作っていられましたら、関係無いかもしれません。

      削除
    2. 関係ないものを進められても困ります。
      必要なソフトと、VSに必要な設定を詳しく教えてください。

      削除
    3. 関係ない・・・と言うことはglutでの開発経験があるということですね。
      今までOpenCVやOpenGLを使っていてこのエラーが出たことはありませんか?

      こちらで再現することができないので、憶測で解決策を列挙してみます。

      ・OpenGLライブラリのリンク
      プログラムに下記を追加
      #pragma comment(lib, "OpenGL32.lib")

      ・freeglutをインストールしてみる
      下記のサイトを参考に
      http://www.wakayama-u.ac.jp/~tokoi/opengl/libglut.html#2.3

      ・OpenCVのエラー
      cvtColorの一行をコメントアウトしてみてください。
      OpenCVのエラーがなくなれば、cvtColorに関するヘッダやライブラリの設定にミスがあるか
      別のエラーが出た場合、OpenCV全体の設定にミスがあるかもしれません。

      削除
    4. いえ、前の文章で、「関係無いかもしれません」と書いていたもので。また、kassy708さまがApr 10, 2012 07:44 PMのメールを出す前に、OpenGLを設定し直していたら、新たに以下のエラーが出ました。

      1>------ ビルド開始: プロジェクト: fukuda, 構成: Debug Win32 ------
      1>main.obj : error LNK2001: 外部シンボル ""public: virtual void __thiscall cv::HOGDescriptor::setSVMDetector(class std::vector > const &)" (?setSVMDetector@HOGDescriptor@cv@@UAEXABV?$vector@MV?$allocator@M@std@@@std@@@Z)" は未解決です。
      1>main.obj : error LNK2001: 外部シンボル ""public: virtual bool __thiscall cv::HOGDescriptor::read(class cv::FileNode &)" (?read@HOGDescriptor@cv@@UAE_NAAVFileNode@2@@Z)" は未解決です。
      1>main.obj : error LNK2001: 外部シンボル ""public: virtual void __thiscall cv::HOGDescriptor::write(class cv::FileStorage &,class std::basic_string,class std::allocator > const &)const " (?write@HOGDescriptor@cv@@UBEXAAVFileStorage@2@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z)" は未解決です。
      1>main.obj : error LNK2001: 外部シンボル ""public: virtual bool __thiscall cv::HOGDescriptor::load(class std::basic_string,class std::allocator > const &,class std::basic_string,class std::allocator > const &)" (?load@HOGDescriptor@cv@@UAE_NABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@0@Z)" は未解決です。
      1>main.obj : error LNK2001: 外部シンボル ""public: virtual void __thiscall cv::HOGDescriptor::save(class std::basic_string,class std::allocator > const &,class std::basic_string,class std::allocator > const &)const " (?save@HOGDescriptor@cv@@UBEXABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@0@Z)" は未解決です。
      1>main.obj : error LNK2001: 外部シンボル ""public: virtual void __thiscall cv::HOGDescriptor::copyTo(struct cv::HOGDescriptor &)const " (?copyTo@HOGDescriptor@cv@@UBEXAAU12@@Z)" は未解決です。
      1>main.obj : error LNK2001: 外部シンボル ""public: virtual void __thiscall cv::HOGDescriptor::compute(class cv::Mat const &,class std::vector > &,class cv::Size_,class cv::Size_,class std::vector,class std::allocator > > const &)const " (?compute@HOGDescriptor@cv@@UBEXABVMat@2@AAV?$vector@MV?$allocator@M@std@@@std@@V?$Size_@H@2@2ABV?$vector@V?$Point_@H@cv@@V?$allocator@V?$Point_@H@cv@@@std@@@5@@Z)" は未解決です。
      1>main.obj : error LNK2001: 外部シンボル ""public: virtual void __thiscall cv::HOGDescriptor::detect(class cv::Mat const &,class std::vector,class std::allocator > > &,double,class cv::Size_,class cv::Size_,class std::vector,class std::allocator > > const &)const " (?detect@HOGDescriptor@cv@@UBEXABVMat@2@AAV?$vector@V?$Point_@H@cv@@V?$allocator@V?$Point_@H@cv@@@std@@@std@@NV?$Size_@H@2@2ABV45@@Z)" は未解決です。
      1>main.obj : error LNK2001: 外部シンボル ""public: virtual void __thiscall cv::HOGDescriptor::detectMultiScale(class cv::Mat const &,class std::vector,class std::allocator > > &,double,class cv::Size_,class cv::Size_,double,int)const " (?detectMultiScale@HOGDescriptor@cv@@UBEXABVMat@2@AAV?$vector@V?$Rect_@H@cv@@V?$allocator@V?$Rect_@H@cv@@@std@@@std@@NV?$Size_@H@2@2NH@Z)" は未解決です。
      1>main.obj : error LNK2001: 外部シンボル ""public: virtual void __thiscall cv::HOGDescriptor::computeGradient(class cv::Mat const &,class cv::Mat &,class cv::Mat &,class cv::Size_,class cv::Size_)const " (?computeGradient@HOGDescriptor@cv@@UBEXABVMat@2@AAV32@1V?$Size_@H@2@2@Z)" は未解決です。
      1>MSVCRTD.lib(crtexew.obj) : error LNK2019: 未解決の外部シンボル _WinMain@16 が関数 ___tmainCRTStartup で参照されました。
      1>C:\Users\morisa\Documents\Visual Studio 2010\Projects\fukuda\Debug\fukuda.exe : fatal error LNK1120: 外部参照 11 が未解決です。

      削除
    5. 「関係無いかもしれません」というのは、紹介するサイトがglutのインストール方法が記載されたページでしたので、OpenGL(glut)を使ったプログラムを今まで開発しておられた場合、インストール関係のサイトを紹介してもエラーとは関係なさそう、という意味です。

      上記エラーはOpenCVのリンカエラーですね。
      サンプルのプログラムには追加していない関数がエラーで出ているのがよくわかりませんが。
      プログラムに下記を追加してみてください
      #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_objdetect220d.lib")
      こちらも再現ができないので憶測です。

      2つお聞きしたいのですが、後藤さんはOpenCVやOpenGLでの開発経験はございますか?
      今までこれらのライブラリで作られたプログラムをコンパイルし実行したことはありますか?

      削除
    6. コメントありがとうございます。
      kassy708さんのおかげでプログラムを実行することができました。
      失礼ですが、マウスで思うように3D画像を回転したり、移動できないません。

      また、OpenCVは4年間使用していますが、OpenGLはあまり使用していません。

      削除
    7. プログラムが動いてよかったです。
      OpenGLの方は経験が浅いということですが、無事動いたようで安心しました。

      マウスの操作で思うように動かしたいということですが、操作に関しては人それぞれですので、思うような操作を行えるようプログラムを各自で改良していただくしかありません。
      サンプルのプログラムではpolarview()で視点の操作を行なっていますが、この関数を改良したり、まったく別の関数を独自で作るなどして頑張ってください。

      削除
  3. 後藤です。
    kassy708様、解答をコメントに載せていただき、まことにありがとうございます。
    あれから、私は、PCLとkinectSDKを用いて、物体認識の研究を行っています。
    それと、非常に申し訳ありませんが、またわからないところがでてきました。
    PCLとkinectSDKを用いて、http://d.hatena.ne.jp/kirii/20111120/1321779238のサンプル1のような3次元の点群画像(深度画像)と、深度画像の各画素の3次元座標値を検出する方法がわからないので、教えてていただけないでしょうか。

    返信削除
    返信
    1. 申し訳ありませんが、私はPCLを使ったことがないのでわかりません。
      調べた感じだと、PCLはKinect SDKをサポートしていないとありました。
      http://www.pcl-users.org/Getting-Data-through-Kinect-SDK-td3771858.html
      "No, MS Kinect SDK is not supported in PCL"

      削除
    2. コメントありがとうございます、kassy708様。
      申し訳ありません、PCLを使わず、Kinect SDKのみでhttp://d.hatena.ne.jp/kirii/20111120/1321779238のサンプル1のような3次元の点群画像(深度画像)と、深度画像の各画素の3次元座標値を検出する方法を教えていただけないでしょうか。

      削除
    3. 後藤様
      Kinect SDKのみでというのはOpenGLやOpenCVといった他のライブラリを使用しないということでしょうか。その場合はDirectXを使ったりするのでしょうが、私にはわかりません。

      OpenGL、OpenCVを使うということでしたら、お答えできます。
      「サンプル1のような3次元の点群画像(深度画像)」というのは色が付いてない白色のポイントクラウドの画像のことでよろしかったでしょうか。
      本記事サンプルプログラムのvoid drawPointCloud(Mat &rgbImage,Mat &pointCloud_XYZ)関数内にあるglColor3ubv(p);をglColor3ub(255,255,255);とすればよいと思います。

      「深度画像の各画素の3次元座標値」というのは本記事のretrievePointCloudMap()関数を参考にしてください。NuiTransformDepthImageToSkeleton()で取得できます。

      削除
    4. 申し訳ありません。言葉足らずでした。
      pclは使用せず、kinect sdk, OpenCV, OpenGLを使用して3次元の点群画像と、各画素の座標情報を取得することを考えています。

      削除
  4. たびたびすみません、kassy708様
    プログラムはエラーなく通りましたが、出力されたOpenGLが真っ白いままで、深度画像が表示されません。

    返信削除
    返信
    1. 申し訳ありません。出力画面は表示できるようになりました。

      削除
    2. 解決されたようで、安心しました。

      削除
  5. このサンプルプログラムをニアモードで実行したいのですが、
    距離カメラ初期化後に、
    NuiImageStreamSetImageFrameFlags(m_pDepthStreamHandle, NUI_IMAGE_STREAM_FLAG_ENABLE_NEAR_MODE);
    の一行を追加してみたのですが、ニアモードになりませんでした。
    どのようにすればうまく表示されるのでしょうか。。。
    ご教示お願いします。

    返信削除
  6. すみません。自己解決しました。

    返信削除
  7. わたしもこのサンプルプログラムをニアモードで実行したいのですが、同じことしていてニアモードになりませんでした。
    自己解決できないのでご教示お願いします。

    返信削除
  8. ちょいと質問です。
    NuiImageGetColorPixelCoordinatesFromDepthPixelAtResolutionの第六引数は0がセットされていますが、USHORTでdepth情報を指定しなくてもいいのでしょうか。こちらで同じようにやると、どうもRGBとDepthのズレが修正できてない気がします。素朴に考えると、距離の補正がいらなければ、そもそも毎ループごとにこの関数を呼ばなくても、初期化で一回だけ呼べばいいだけな気がします。で、試しにdepthを入れて見たのですが、それでもうまく動かないようです。ご教授、ご助言いただけるとうれしいです。

    返信削除