這篇還是算延續前一篇的《透過 OpneNI 合併 Kinect 深度以及彩色影像資料》。在可以透過 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,這樣可以減少一些索引值的計算;而因為這樣修改,之前讀取彩色影像的程式也要由
修改為
而在函式內容的部分,第一段的部分主要是透過取得 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 直接一點一點畫出來的話,結果大概就會像上面的影片一樣了~
如果座在電腦螢幕前面 只有上半身 它也會知道嗎!XD
讚讚
你是指人體骨架追蹤嗎?如果是的話,要看狀況,如果半身的比例夠大,應該是可以的。
建議你直接試試看會比較快。
讚讚
你好
我是使用openGL 的glutMainLoop 來跑影像更新
但是因glutMainLoop 會跳不出來
所以我用freeglut 提供的glutMainLoopEvent 外面多加個無窮迴圈做測試
但是畫面並無更新 所以想請問glutMainLoopEvent 來取代glutMainLoop 的使用方式是?
謝謝你
讚讚
抱歉,Heresy 沒有那樣用過,所以也不知道怎麼用。
不過或許可以參考: http://sihan6677.blog.163.com/blog/static/56139796200901054735171/
讚讚
你好
我依照以上的code, 再用OpenGL畫了point cloud
不過並不是即時, 而是兩三秒才更新一次
請問如何解決? 需要更改任何設定嗎?
另外, 我想問如何將color data套用至point cloud上, 產生類似以上影片的效果?
謝謝
讚讚
這邊可能要先釐清一下,看看會不會是計算量太大的問題。
不知道有沒有跑過同樣的程式、但是沒有使用 OpenGL 來做繪製?
不知道可不可以試試看,以較低的解析度(320×240)來做測試?
讚讚
謝謝你的解答
我還有一個問題
用OpenNI+OpenGL的話, 如何只顯示某個人物/物件, 而不繪畫背景的point cloud?
讚讚
如果是 OpenNI 1.x 的話,可以透過 UserGenerator 偵測出來的人體,來做過濾。
如果是一般的物體的話,就要自己定義過濾的方法了。
讚讚
有點好奇的是 即使我側面 背面 等等都能抓到人體嗎? 那2.0就無此功能? 懇請指教
讚讚
如果你是要人體的輪廓的話,無關哪個方向,只要他認為可能是的,就會開始追蹤。
如果你是指骨架追蹤的部份的話,就算是側面,只要他能偵測到肢體(主要應該還是雙手)、到足以分析人的骨架、進行初始化,理論上就可以繼續追蹤。
而背面的話,他基本上應該分不出背面和側面,所以如果一開始拍到的是背面,他會以此當作正面來追蹤。
不過由於 NiTE 官方文件對此並無多加著墨,這邊也僅只是臆測。
讚讚
[…] 而如果把這些點在 3D 環境(OpenGL 或 Direct 3D)裡依序畫出來,基本上就會變成像是右邊的樣子,或者也可以參考之前版本的影片(YouTube)。 […]
讚讚