透過 OpneNI 讀取 Kinect 深度影像資料
2011/01/20 159 則迴響
前一篇文章已經大概針對 OpenNI 的架構做了一些說明,而這一篇就來針對如何使用 OpenNI 讀取微軟的 Kinect 的影像資料吧!
而先說明一下,Heresy 這篇文章是使用 Visual C++ 2010,針對目前微軟的 Kinect、SensorKinect 的驅動程式,搭配 1.0.0.23 版的 OpenNI 版寫的;如果使用其他版本、或是其他支援 OpenNI 的裝置,那可能會要做一些對應的修改。同時,在開始閱讀這篇文章前,建議也請先參考《在 WIndows 上安裝 Kinect(含 MMD 使用 Kinect 簡易教學)》來安裝 Kinect 和 OpenNI,並確定可以正常運作。
首先,OpenNI 他預設的安裝路徑是在「C:\Program Files\OpenNI」,要開發 OpenNI 程式所有必要的檔案,都會在這裡;而在資料夾內,除了「Documentation」裡有提供兩份文件可以用來當作開發程式的依據外,在「Samples」目錄下,也有提供不少範例可以用來參考。
OpenNI 的核心基本上是 C 語言,不過他有提供 C++ 的 Wrapper 來當作 C++ 使用;基於個人習慣的關係,Heresy 在這邊會以 C++ 的形式,來使用 OpenNI。而必要的 header 檔,都會在 OpenNI 的「Include」目錄內,連結程式時所需的 openNI.lib 這個檔案則是在「Lib」裡。不過要注意的是,OpenNI 目前在 Windows 環境下只有 32 位元的版本、沒有 64 位元版,所以目前只能編譯 32 位元的 OpenNI 程式。
而要設定一個使用 OpenNI 的 Visual C++ 專案也很簡單,只要在專案「C/C++ \ Additional Include Directories」裡加入「$(OPEN_NI_INCLUDE)」、「Linker \ Additional Library Directories」裡加上「$(OPEN_NI_LIB)」,並在「Linker \ Additional Dependencies」裡加上「OpenNI.lib」,這樣就可以了。
而接下來,Heresy 就先以讀取 Kinect 的深度影像資訊為目標,來寫一個 C++ 的範例程式了~他的程式碼如下:
#include <stdlib.h>
#include <iostream>
#include <string>
#include <XnCppWrapper.h>
using namespace std;
void CheckOpenNIError( XnStatus eResult, string sStatus )
{
if( eResult != XN_STATUS_OK )
cerr << sStatus << " Error: " << xnGetStatusString( eResult ) << endl;
}
int main( int argc, char** argv )
{
XnStatus eResult = XN_STATUS_OK;
// 2. initial context
xn::Context mContext;
eResult = mContext.Init();
CheckOpenNIError( eResult, "initialize context" );
// set map mode
XnMapOutputMode mapMode;
mapMode.nXRes = 640;
mapMode.nYRes = 480;
mapMode.nFPS = 30;
// 3. create depth generator
xn::DepthGenerator mDepthGenerator;
eResult = mDepthGenerator.Create( mContext );
CheckOpenNIError( eResult, "Create depth generator" );
eResult = mDepthGenerator.SetMapOutputMode( mapMode );
// 4. start generate data
eResult = mContext.StartGeneratingAll();
// 5. read data
eResult = mContext.WaitAndUpdateAll();
if( eResult == XN_STATUS_OK )
{
// 5. get the depth map
const XnDepthPixel* pDepthMap = mDepthGenerator.GetDepthMap();
// 6. Do something with depth map
}
// 7. stop
mContext.StopGeneratingAll();
mContext.Shutdown();
return 0;
}
這個程式的功能,基本上就是去透過 OpenNI 讀取一張解析度 640 x 480 的深度資訊影像;但是在讀取到資料後,並沒有針對取得的資料做任何事,所以如果沒有問題的話,這個程式是會直接結束,而沒有任何產出的。
接下來,就來仔細看程式碼的部分。
-
Header
首先,要以 C++ 的形式使用 OpenNI 的話,只需要加入「XnCppWrapper.h」這個標頭檔就好了,不用再 include 其他的檔案。而 OpenNI 定義了名為「xn」的 namespace,所有的物件,大多都在這個 namespace 內,而不在 namespace 內的東西,也都有 XN 這個 prefix,所以應該還算滿好區分的。
-
初始化 context
要使用 OpenNI,要先建立一個型別為「xn::Context」的 conext 物件(這裡就是「mContext」),用來管理整個 OpenNI 的環境狀態以及資源;而在開始使用前,必須要呼叫它的成員函式「Init()」來進行起始化(上方程式碼「initial context 」的部分)。在進行起始化的時候,所有 OpenNI 相關的模組會被讀取、分析,直到呼叫「Shutdown()」這個函式,才會把所使用的資源釋放出來。
-
建立、設定所需要的 Production Node
在 context 起始化成功後,接下來是要建立所要使用的 production node 了。由於這個範例的目的只是要讀取深度感應器的資料,所以這裡要建立的就只有「depth generator」一種,他的型別是「xn::DepthGenerator」。 而建立一個 production node 的方法,則是先宣告出他的物件(這裡就是「mDepthGenerator」),然後再去呼叫他的「Create()」函式,並把 context 傳入,這樣就可以了(上方程式碼中「create depth generator 」的部分)。
不過要注意的是,有的時候在建立出 node 後,還需要對這個 node 作一些設定。像在這邊,就還必須要透過「SetMapOutputMode()」這個函式,來設定 mDepthGenerator 這個 depth generator 的輸出模式;而以 Kinect 來說,是要設定成為 640 x 480、30FPS。
-
開始產生資料
在必要的 production node(這邊只有一個)都建立好了以後,接下來就是開始產生資料(generate data)了!由於 OpenNI 的概念是所以屬於 generator 的 production node(名稱裡有 generator 的都是)在使用時,都會不停地產生資料,所以得透過 context 來統一控制資料讀取的開關。
而控制的方法很簡單,就是透過 context 的成員函式「StartGeneratingAll()」來開始、並透過「StopGeneratingAll()」停止。在一個 context 執行「StartGeneratingAll()」開始讀取後,屬於他的 generator node 都會開始產生資料,直到呼叫「StopGeneratingAll()」才會停止。 -
讀取資料
在開始產生資料後,就可以讀取各個不同的 production node 的資料了~不過不同類型的 generator 必須要透過不同的函式來讀取資料,像這邊的 depth generator 就是要用「GetDepthMap()」這個函式,來取得目前的 depth map。而 Depth Generator 取得的資料,會是一個「XnDepthPixel」的 const 指標,指向他實際資料的空間。
不過這邊另外要注意的就是,generator 雖然是會不停地讀取新的資料,但透過「GetDepthMap()」這類的函式,是有可能會拿到舊的資料的。而為了確保能取得最新的資料,在讀取 Generator 的資料前,都必須要先呼叫 context 的 wait / update 這一系列的函式,來進行 node 資料的更新。
這系列的函示有四個:WaitAnyUpdateAll()、WaitOneUpdateAll()、WaitNoneUpdateAll() 和這邊所使用的 WiatAndUpdateAll()。這四者都會更新 context 下所有的 node 的資料,差別只在於更新的條件;Heresy 這邊所使用的 WiatAndUpdateAll() 會等到所有的 node 都取得新資料後,再統一更新所有的 node 的資料;而 WaitAnyUpdateAll() 是等到隨便一個 node 有新資料時就會更新、WaitOneUpdateAll() 則是等到指定的 node 有新資料時再更新、WaitNoneUpdateAll() 則是不管有沒有新資料就強制更新。基本上,這四個不同的函式就是自己看時機、需求使用了。
-
處理讀取到的資料
前面已經有提過了,Depth Generator 取得的資料,會是一個「XnDepthPixel」的 const 指標、而實際上它就是一個大小是 640 x 480 的一維陣列(因為現在的輸出模式是 640 x480),基本上可以把它看作一張 640 x480 的灰階圖片,其中每一個點都代表他的在這個位置的深度、型別是「XnDepthPixel」;而他的深度值在 Windows 32 位元的平台上,型別應該等同於「unsigned short」。基本上,這裡的深度值越大、代表距離越遠(0 則是代表該點深度無法判別),如果透過 OpenNI 的函式,也可以換算出絕對距離,不過在這篇文章暫時不會提到就是了。
而在這個範例程式裡,Heresy 什麼事都沒有做。如果要額外處理這個深度圖的資料的話,只要在「// 6. Do something with depth map」那裡,讀取「pDepthMap」這個指標的資料來做處理就可以了。像如果把直接它的深度資訊由 XnDepthPixel 轉換為一般的 256 灰階圖輸出的話,就會是類似右邊的結果;而當然,這樣的圖意義不大,但是其實這些深度資訊還可以拿來做很多應用,這點就看程式開發者怎麼發揮了~ (Heresy 本來有想連儲存圖檔一起寫,不過由於牽扯到儲存圖檔的話,程式碼會變得比較複雜,所以在這邊也就先跳過了)
-
結束
當讀取完資料,不再繼續讀取資料後,就要把 OpenNI 停下來;而這邊為了停止繼續產生資料所呼叫的函示,就是之前已經提到過的「StopGeneratingAll()」。而如果完全不打算繼續使用 OpenNI 的環境的話,則也要記得呼叫「Shutdown()」這個函式,把 OpenNI 所使用的資源釋放出來。
-
錯誤偵測
如果仔細看前面的程式碼應該可以發現,Heresy 在大部分的地方都用一個型別是「XnStatus」的變數「eResult」來接 OpenNI 函式的回傳值,而實際上,這就是用來判斷 OpenNI 的函式是否正確執行的依據;如果一個 OpenNI 函式的回傳值式「XN_STATUS_OK」的話,就代表他執行結果是正確的,但是如果不是的話,就代表可能出問題了~而要知道出了什麼問題,則可以透過「xnGetStatusString()」這個函式,來取得文字的錯誤訊息;像上面 Heresy 自己定義的「CheckOpenNIError()」,就是在做這件事的。
這篇 Kinect + OpenNI 的第一個範例,就大概先寫到這了。基本上,這篇算是透過抓取深度的範例,來大概解釋一下怎麼使用 OpenNI 裡的 map generator 了~而這邊的程式也相當單純,之後還會再慢慢寫一些更進階的應用的。
Pingback: 使用 Qt GraphicsView 顯示 OpenNI 影像資料 « Heresy's Space
大大您好 請運用Dev c++這套有辦法寫嗎? 還是一定要用visual c++
抱歉,Heresy 沒有在用 Dev-C++(也不建議用、原因參考),所以不曉得。
建議你可以使用為軟體提供的免費版 Visual C++ Express。
你好 我用的64位元的版本 那在設定OpenNI.lib的相依性那邊是不是要改? 因為我只有一個lib64而沒有lib的資料夾 感謝
是的,64 位元的話,要使用的 lib 檔也請改為 openNI64.lib。
Heresy大大~我想讀取kinect或者Xtion Pro的人體三維座標值,使用Open NI不曉得大大有什麼好方法嗎?
意思就是Open NI第一步辨識到人體還未骨架化之前,偵查人體輪廓出現(藍色的人體外框),我想讀取人體輪廓裡的值,直接存入TEXT檔。
你如果是要抓到人的區域的話,請使用 UserGenerator 的 GetUserPixels()。
謝謝Heresy~我嘗試重劃骨架(我感覺它骨架會亂動)^^",因為他關節點亂動骨架線條換亂飄,OPEN GL本來是這樣寫連畫線的
XnPoint3D pt[2];
pt[0] = joint1.position;
pt[1] = joint2.position; //線條隨關節點移動
g_DepthGenerator.ConvertRealWorldToProjective(2, pt, pt);
glColor3f(1,1,1);
glVertex3i(pt[0].X, pt[0].Y, 0);
glVertex3i(pt[1].X, pt[1].Y, 0);// (Z值都為0 2D)
glLineWidth(3);
我想自己嘗試看看重劃,但是我不曉得如何帶值,假如我想從right hand 連一條線到right elobw再到 rught shoulder該如何帶呢?
NITE 的 Skeleton 本來就會有一定的雜訊,所以會有抖動的問題。
這點可以透過 SetSmoothing() 來做一定程度的平滑化(參考)。
謝謝~我加了SetSmoothing後的確有差異,但是我想重新建構它的骨架,我不曉得該重哪裡著手?
我的問題可以說得不好>"<….假如我想些改righthand關節點位置,我該如何修正?
抱歉,不瞭解你的問題。
基本上,OpenNI 只是提供你可以讀出骨架個關節的資料而已,接下來要做什麼處理,都是隨便你怎麼做的。
另,可以的話,麻煩請將問題回應在對應的文章,謝謝。
我照Heresy大大提供的方法下去執行,想將值存入text檔,但是『SceneMetaData &smd』這兩個是代表什麼 東西ㄚ>"<
XnStatus GetUserPixels (XnUserID user, SceneMetaData &smd)
——————————————————————————————-
case 'a':
g_Pose=1;
myfile1.open("t_pose1.txt",g_UserGenerator.GetUserPixels
(usernumber,SceneMetaData&smd));
break;
關於 GetUserPixels() 的使用,建議請參考官方文件,或是 OpenNI 的 NiUserTracker 這個官方範例。
大大~不好意思我今天try了好久~GetUserPixels()這個函式怎麼try都試不出來 orz…不曉得大大有沒有範例^^?
請問你的問題是?
另外,在 NITE 的官方範例 StickFigure 裡,就有用到 GetUserPixels() 了,建議先參考看看。
謝謝 Heresy大哥指導…..
我用OPENCV 寫出輸出 depth影像
您好,想請問一下,我試著透過深度資訊來擷取人體,
但在頭髮的地方都會有嚴重的破損,請問有沒有辦法修補呢?
不知道你是用什麼方法擷取出來的?
不過基本,Heresy 會認為在原理上,Kinect 這種光學式的深度感應器對於黑色的效果應該不會很好,所以有可能很難從來源上來處理。
或許會需要透過影像處理的方法、或是再加上彩色影像作動作偵測來做輔助。
还想向您请教一个关于图像显示的问题。在例NisimpleViewer.cpp中图像是通过OpenGL的texture map来显示的。图像的大小为640*480;但是texture map的大小却需要设定为1024*512. 我不理解这是为什么?非常感谢!!
沒弄錯的話,應該是因為早期的 OpenGL Texture 有 Power of 2 的限制(長寬都要是 2 的冪次方),所以他為了符合 OpenGL 的最低需求才這樣寫。
知道了,非常感谢你的指导!
您好!想向您请教一个问题,在通过DepthGenerator.GetMetaData(DepthMetaData)可以读取depth value的【DepthMetaData(x,y)】。这里的x,y坐标的参考原点在哪里?是视野区域的左下角吗?
Heresy 自己沒用過這個。
不過基本上在電腦圖學裡,沒有特別說明的話,原點應該都是左上角。
另外,這種東西其實只要稍微測試一下應該都可以確認的。
建議自己可以多試試看。
Dear Heresy:
能不能把輸出深度圖的程式,另外用Email寄給小弟呢, Email: chen.minya@gmail.com , 小弟想研究一下,非常的謝謝Heresy教學
抱歉,Heresy 這邊影像檔輸出的程式是使用工作地方內部的函式庫,並不方便外流。
建議你研究一下 libpng、libtiff 這類的基本影像格式的函式庫,或是 OpenCV 這類的函式庫來做儲存的工作。
Dear Heresy:
恩恩,小弟了解,真不好意思
小弟按照上面的程式輸入在VS C++ 2010 都會出現這樣錯誤 , OpenNI 版本1.0.0.23
1>depth.obj : error LNK2019: unresolved external symbol __imp__xnGetStatusString referenced in function “void __cdecl CheckOpenNIError(unsigned int,class std::basic_string<char,struct std::char_traits,class std::allocator >)" (?CheckOpenNIError@@YAXIV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z)
是否能跟小弟提示一下是哪裡出錯了
這是 link 不到 xnGetStatusString 這個函式造成的。請確認你有正確地設定 VC++ 的專案,有正確地指定連結 openNI.lib 這個檔案。
另外,目前 OpenNI 的版本已經和你所用的差很多了,也建議你先更新一下到最新版。
很感謝Heresy,問題已經解決了
小弟還有最後的問題…..
start debug後….輸出執行檔,但會出現"系統找不到指定的檔案"
盼望Heresy能夠給小弟最後的幫忙….再次謝謝Heresy
請先確認你的編譯過程全都是正確的,並確認輸出的執行檔是在你預期的地方。
這個錯誤個人認為應該是你的 VC 專案有改到一些不該改動的東西,所以導致編譯好、最後輸出的執行檔和 debug 環境中設定要執行的檔案在不同地方所造成的。
Pingback: 點閱次數超過五十萬了~ « Heresy's Space