OpenNI 2 的座標系統轉換


在之前的《OpenNI 2 VideoStream 與 Device 的設定與使用》一文裡,已經有解釋怎麼樣用 OpenNI 2 來讀取感應器的彩色以及深度影像了。接下來,就是 OpenNI 的座標系統的問題了~

首先,在 OpenNI 1.x 的座標系統,基本上分成「投影座標系統」(Projective Coordinate System)「真實世界座標系統」(Real World Coordinate System)兩種,這在之前的《OpenNI 的座標系統》已經有介紹過了,如果還沒看過的,請稍微閱讀一下這篇文章。

到了 OpenNI 2,在這部分的概念,在名詞的部分,則是稍微做了一些修改。座標系統變成是以「深度座標系統」(Depth)「世界座標系統」(World)這兩者為主;不過,雖然名字換了,但是實際上「深度座標系統」就是以前的「投影座標系統」,而「世界座標系統」就是以前的「真實世界座標系統」。

而在座標系統的轉換 API 的部分,OpenNI 2 則是提供了名為 openni::CoordinateConverter官網)的類別,專門用來做座標系統的轉換。它總共提供了五個 static 函式、三種轉換的方向,包括了:

  • 世界座標系統到深度座標系統
    Status convertWorldToDepth( const VideoStream& depthStream,
                                float worldX, float worldY, float worldZ,
                                int* pDepthX, int* pDepthY, DepthPixel* pDepthZ)
  • 深度座標系統到世界座標系統
    Status convertDepthToWorld( const VideoStream& depthStream,
                                int depthX, int depthY, DepthPixel depthZ,
                                float* pWorldX, float* pWorldY, float* pWorldZ)
  • 深度座標系統到彩色影像
    Status convertDepthToColor( const VideoStream& depthStream,
                                const VideoStream& colorStream,
                                int depthX, int depthY, DepthPixel depthZ,
                                int* pColorX, int* pColorY)

前兩者基本上就是把單一個點,在「深度座標系統」與「世界座標系統」間進行轉換。而根據世界座標系統的值型別的不同(float 的版本),這兩個函式都各還有另一個版本,不過用法完全相同。而這兩種函式,基本上就等同於 OpenNI 1.x Depth Generator 提供 ConvertProjectiveToRealWorld()ConvertRealWorldToProjective() 這兩個函式,比較不一樣的是,OpenNI 1.x 所提供的介面,是可以大量進行轉換的,而 OpenNI 2 的介面,一次只能轉換一個點。

而實際在使用的時候,大致上就會像這樣:

VideoFrameRef vfDepthFrame;
const DepthPixel* pDepthArray = NULL;
if( g_vsDepthStream.readFrame( &vfDepthFrame ) == STATUS_OK )
{
  pDepthArray = (const DepthPixel*)vfDepthFrame.getData();
  for( int y = 0; y < vfDepthFrame.getHeight() - 1; ++ y )
  {
    for( int x = 0; x < vfDepthFrame.getWidth() - 1; ++ x )
    {
      int idx = x + y * vfDepthFrame.getWidth();
      const DepthPixel&  rDepth = pDepthArray[idx];
      float fX, fY, fZ;
CoordinateConverter::convertDepthToWorld( g_vsDepthStream, x, y, rDepth, &fX, &fY, &fZ );
} } }

在上面的例子裡,g_vsDepthStream 就是一個深度感應器的 VideoStream,而當取得他的資料之後,接下來就是透過兩層迴圈,依序地去讀取出深度影像裡面的每一個點的值;而這時候,這個點在深度影像中的位置 ( x, y ),再加上該點的深度值 rDepth,所構成的 ( x, y, rDepth ) 就是一個位在深度座標系統內的一個點。

而要把這個點轉換成為世界座標系統裡的位置的話,就是要呼叫 CoordinateConverter 提供的 convertDepthToWorld() 這個函式,也就是上方黃底的部分。而呼叫這個函式時,需要的參數包含了三個部分:

  • 儲存轉換資訊的深度感應器的 VideoStreamvsDepthStream
  • 輸入的資料、也就深度座標系統的點位:xyrDepth 
  • 輸出的結果,也就世界座標系統的點位:fXfYfZ

上面的程式執行完之後,( fX, fY, fZ ) 就是深度影像中 ( x, y ) 這一點,在世界座標系統內的位置了~基本上,在轉換前後,兩個座標系統內的 Z 的值基本上是不會變的,也就是 rDepth 的值會等於 fZ

而如果把這些點在 3D 環境(OpenGL 或 Direct 3D)裡依序畫出來,基本上就會變成像是右邊的樣子,或者也可以參考之前版本的影片(YouTube)。

完整的 OpenGL 範例程式,Heresy 之後s之後會再補上來。

convertWorldToDepth() 則就是提供一個反向的轉換,可以把空間中的點,計算出他在深度影像上會是在哪個一位置、值是多少,基本上也是同樣的使用方法。


另外和 OpenNI 1.x 比起來比較特別的是,OpenNI 2 還有提供一個 convertDepthToColor() 的函式,可以把深度影像上的點,轉換到彩色影像上。根據官方文件(連結)的說法,他的轉換結果,和透過 Device 提供的 setImageRegistrationMode() 函式開啟 IMAGE_REGISTRATION_DEPTH_TO_COLOR 這項設定會是相同的。

不過 Heresy 這邊測試的結果,這個轉換的結果,都會回傳 STATUS_ERROR,無法正確進行轉換就是了…


注意事項:

  • OpenNI 2 的 VideoStream 似乎是預設開啟鏡像(mirror)的設定的~如果有需要,可以透過 VideoDtream 提供的 setMirroringEnable() 這個函式,來控制鏡像的開啟或關閉。

  • 根據深度的格式(單位)的不同,convertDepthToWorld() 轉換出來的 fXfYfZ 的值的範圍也會不同。

  • 另外,官方文件也有提到,從深度座標系統轉換到世界座標系統所需要的計算輛是相對高的,所以建議可以的話,應該盡量在深度影像座標系統上進行處理,而且盡量只轉換有需要的點。


OpenNI / Kinect 相關文章目錄

對「OpenNI 2 的座標系統轉換」的想法

  1. 您好。我现在想实现kinect v2的彩色图和深度图的叠加。使用了您BLOG的代码以后,彩色图能显示出来,但是深度图全黑。猜测可能转成了0-255以后,全部是255。这个要怎么处理呢?

發表留言

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料