• 透過 OpenNI 建立 Kinect 3D Point Cloud


    在可以透過 OpenNI 讀取到 Kinect 的深度、色彩資訊之後,其實就可以試著用這些資訊,來重建 3D 的環境做顯示了~不過實際上,在前面的範例中所讀到的深度資訊,都算是原始資料,而且座標軸也都是感應器二維影像的座標系統,如果要重建 3D 場景的話,這些資訊都還是需要換算的;所幸,OpenNI 在 Depth Generator 已經有提供 ConvertProjectiveToRealWorld() 和ConvertRealWorldToProjective() 這兩個函式,可以幫助程式開發者快速地進行座標轉換了!

    而如果把直接把這些 3D 的點位附加顏色、用 OpenGL 畫出來呢,就大概會是下面影片的樣子吧~

    當然,point cloud 不見得是最好的顯示方式,有需要的話也可以重建出多邊形再畫,不過多邊形的重建已經算是另一個主題了,所以 Heresy 也不打算在這邊討論;另外,Heresy 在這篇也不會提及 OpenGL 顯示的部分,只會提供簡單的範例,示範如何建立出這些 point cloud 而已。

     

    而為了儲存這些點的位置以及顏色資訊,這邊先定義了一個簡單的結構、SColorPoint3D

    struct SColorPoint3D
    {
      float  X;
      float  Y;
      float  Z;
      float  R;
      float  G;
      float  B;
    
      SColorPoint3D( XnPoint3D pos, XnRGB24Pixel color )
      {
        X = pos.X;
        Y = pos.Y;
        Z = pos.Z;
        R = (float)color.nRed / 255;
        G = (float)color.nGreen / 255;
        B = (float)color.nBlue / 255;
      }
    };

    這個結構只是單純的六個福點數,分別記錄這個點的位置、以及顏色;而建構子的部分,則是傳入 OpenNI 定義的結構的變數:代表位置的 XnPoint3D  以及代表 RGB 顏色的 XnRGB24Pixel

    而為了方便起見,Heresy 把座標轉換的部分寫成一個函式 GeneratePointCloud(),其內容如下:

    void GeneratePointCloud( xn::DepthGenerator& rDepthGen,
                             const XnDepthPixel* pDepth,
                             const XnRGB24Pixel* pImage,
                             vector<SColorPoint3D>& vPointCloud )
    {
      // 1. number of point is the number of 2D image pixel
      xn::DepthMetaData mDepthMD;
      rDepthGen.GetMetaData( mDepthMD );
      unsigned int uPointNum = mDepthMD.FullXRes() * mDepthMD.FullYRes();
    
      // 2. build the data structure for convert
      XnPoint3D* pDepthPointSet = new XnPoint3D[ uPointNum ];
      unsigned int i, j, idxShift, idx;
      for( j = 0; j < mDepthMD.FullYRes(); ++j )
      {
        idxShift = j * mDepthMD.FullXRes();
        for( i = 0; i < mDepthMD.FullXRes(); ++i )
        {
          idx = idxShift + i;
          pDepthPointSet[idx].X = i;
          pDepthPointSet[idx].Y = j;
          pDepthPointSet[idx].Z = pDepth[idx];
        }
      }
    
      // 3. un-project points to real world
      XnPoint3D* p3DPointSet = new XnPoint3D[ uPointNum ];
      rDepthGen.ConvertProjectiveToRealWorld( uPointNum, pDepthPointSet, p3DPointSet );
      delete[] pDepthPointSet;
    
      // 4. build point cloud
      for( i = 0; i < uPointNum; ++ i )
      {
        // skip the depth 0 points
        if( p3DPointSet[i].Z == 0 )
          continue;
    
        vPointCloud.push_back( SColorPoint3D( p3DPointSet[i], pImage[i] ) );
      }
       delete[] p3DPointSet;
    }

    這個函示要把 xn::DepthGenerator 以及讀到的深度影像和彩色影像傳進來,用來當作資料來源;同時也傳入一個vector<SColorPoint3D>,作為儲存轉換完成後的 3D 點位資料。

    其中,深度影像的格式還是一樣用 XnDepthPixel 的 const 指標,不過在彩色影像的部分,Heresy 則是改用把 RGB 封包好的 XnRGB24Pixel,這樣可以減少一些索引值的計算;而因為這樣修改,之前讀取彩色影像的程式也要由

    const XnUInt8* pImageMap = mImageGenerator.GetImageMap();

    修改為

    const XnRGB24Pixel* pImageMap = mImageGenerator.GetRGB24ImageMap();

    而在函式內容的部分,第一段的部分主要是透過取得 depth generator 的 meta-data:xn::DepthMetaData 來做簡單的大小、索引計算;如果不想這樣用的話,其實也是可以直接用 640 x 480 這樣固定的值來做計算,不過就是要和之前在 SetMapOutputMode() 所設定的解析度一致就是了。

    第二部分「build the data structure for convert」,則是將深度影像的 640 x 480 個點,都轉換為 XnPoint3D 形式的一為陣列,已準備進行之後的座標轉換。

    第三部分「un-project points to real world」則就是實際進行轉換的部分了。這邊要把座標由影像的座標系統轉換到 3D 座標系統,主要是用 Depth Generator 的 ConvertProjectiveToRealWorld() 這個函式;而它的使用方法也很簡單,只要告訴他要轉換的點的數量(uPointNum)、把要轉換的點用陣列的形式傳(const XnPoint3D*)進去,並給他一塊已經 allocate 好的 XnPoint3D 陣列(p3DPointSet),就可以自動進行轉換了~

    第四部份 Heresy 則是再用一個迴圈去掃過全部的點,並把深度為 0 的點給去掉(因為這些點是代表是 Kinect 沒有辦法判定深度的部分)、並和顏色的資訊一起轉換為 SColorPoint3D 的形式,丟到 vPointCloud 裡儲存下來了。

    (這個動作其實也可以在第二步的時候先做掉,但是在那邊做顏色的部分會比較麻煩就是了。)

    而回到主程式的部分,本來讀取資料的程式是:

    // 8. read data
    eResult = mContext.WaitNoneUpdateAll();
    if( eResult == XN_STATUS_OK )
    {
      // 9a. get the depth map
      const XnDepthPixel*  pDepthMap = mDepthGenerator.GetDepthMap();
      // 9b. get the image map
      const XnUInt8*    pImageMap = mImageGenerator.GetImageMap();
    }

    前面也提過了,Heresy 這邊不打算提及用 OpenGL 顯示的部分,所以這邊為了不停地更新資料,所以改用一個無窮迴圈的形式來不停地更新資料、並進行座標轉換;而轉換後的結果,也很簡單地只輸出它的點的數目了。

    // 8. read data
    vector<SColorPoint3D> vPointCloud;
    while( true )
    {
      eResult = mContext.WaitNoneUpdateAll();
      // 9a. get the depth map
      const XnDepthPixel*  pDepthMap = mDepthGenerator.GetDepthMap();
    
      // 9b. get the image map
      const XnRGB24Pixel*  pImageMap = mImageGenerator.GetRGB24ImageMap();
    
      // 10 generate point cloud
      vPointCloud.clear();
      GeneratePointCloud( mDepthGenerator, pDepthMap, pImageMap, vPointCloud );
      cout << "Point number: " << vPointCloud.size() << endl;
    }

    如果是要用 OpenGL 畫出來的話,基本上就是不要使用無窮迴圈,而是在每次要畫之前,再去讀取 Kinect 的資料、並透過 GeneratePointCloud() 做轉換了~而如果不打算重建多邊形、而是像 Heresy 直接一點一點畫出來的話,結果大概就會像上面的影片一樣了~

    trackback: http://kheresy.wordpress.com/2011/01/25/build_3d_point_cloud_via_openni/

  • 相关阅读:
    URAL 1998 The old Padawan 二分
    URAL 1997 Those are not the droids you're looking for 二分图最大匹配
    URAL 1995 Illegal spices 贪心构造
    URAL 1993 This cheeseburger you don't need 模拟题
    URAL 1992 CVS
    URAL 1991 The battle near the swamp 水题
    Codeforces Beta Round #92 (Div. 1 Only) A. Prime Permutation 暴力
    Codeforces Beta Round #7 D. Palindrome Degree hash
    Codeforces Beta Round #7 C. Line Exgcd
    Codeforces Beta Round #7 B. Memory Manager 模拟题
  • 原文地址:https://www.cnblogs.com/JohnShao/p/2053496.html
Copyright © 2020-2023  润新知