在之前的一系列文章裡,其實 Heresy 自己是覺得應該已經把大部分、在寫 OpenNI 應用程式的基礎部分都講完了(聲音除外),其他的東西,在 Heresy 來看都是比較偏應用、或是偏進階的東西;雖然其實現在有些部分會想改版重寫,像是現在會想把早期的範例改成用 Map metadta 當範例這類的,不過暫時應該還不會真的下去做這件事。
而這篇的內容,基本上也算是一個特殊的進階使用,那就是:
要怎麼修改深度資料、讓 User Generator 來分析?
對於這個問題,Heresy 最初的想法,是自己去寫一個新的 Depth Generator,在裡面去讀取原始的 Depth Generator 後、做完修改再來讓 User Generator 來用。這個方法理論上應該也是可以的,但是實際上真的要自己寫一個 OpenNI 的 production node 其實還是滿麻煩的…
而後來,想到的方法,就是在撥放 ONI 檔時、拿來操作的虛擬 Production node、也就是「Mock Node」來用了!
OpenNI 的 Mock Node 基本上是一種沒有真正對應到資料來源的虛擬原始資料的 Production node,包括了下面四種:
- xn::MockDepthGenerator
- xn::MockImageGenerator
- xn::MockIRGenerator
- xn::MockAudioGenerator
基本上,就是對應到 OpenNI 裡面,對應到硬體、去讀取原始資料的四種 generator 了~而這四種 Mock Node 基本上都是繼承自本來的 Generator,所以可以和本來的 generator 做一樣的操作。不過,由於他們本身不會去讀取硬體的資料,所以必須要靠程式透過 SetData() 來把資料餵進去,然後再給 OpenNI 的其他模組使用。
而在 OpenNI 官方的「NiRecordSynthetic」這個範例裡,就有用到 xn::MockDepthGenerator 了~有興趣的話,可以參考看看官方範例的寫法;而這邊,Heresy 基本上也是以 xn::MockDepthGenerator 為例子來做說明了。
初始化
在建立的部分,基本概念就是:
// OpenNI Context xn::Context xContext; xContext.Init(); // Depth generator xn::DepthGenerator xDepthGen; xDepthGen.Create( xContext ); // mock depth generator xn::MockDepthGenerator xMockDepth; xMockDepth.CreateBasedOn( xDepthGen, "mock-depth" ); // create user generator using mock depth generator xn::Query xQuery; xQuery.AddNeededNode( "mock-depth" ); xn::UserGenerator xUser; xUser.Create( xContext, &xQuery );
在上面的程式碼裡,首先還是先建立 OpenNI 的 context xContext、並對他進行初始化。接下來,用一般的方法,建立一個 depth generator xDepthGen,用來讀取感應器的資料。
再來,則是建立一個 xn::MockDepthGenerator xMockDepth,用來當作虛擬的 depth generator;這邊是使用 CreateBasedOn() 這個函示來做建立的動作,如此可以把 xDepthGen 的基本資訊直接拿來給 xMockDepth 用。而第二個參數「"mock-depth"」則是一個字串,用來指定建立出來的 node 的名稱。
(注意!Node 名稱似乎有可能會導致錯誤!建議不要改掉字串、或是用不包含「depth」這類可能是內部定義的單字來命名。)
而在建立 user generator 的時候,則就需要透過 xn::Query 來做限制,來要求 OpenNI 去建立一個使用 xMockDepth 的資料的 user generator 了~而這邊限制條件的方法,基本上就是先建立一個 xn::Query 的物件 xQuery,然後透過他的 AddNeededNode() 這個函式來做條件的指定;而指定的條件,這邊就是給他 xMockDepth 的名稱,也就是 "mock-depth" 了~
在設定好 xQuery 後,只要在 user generator 建立的時候,把他的位址當作 Create() 的第二個參數傳進去,這樣 OpenNI 就會建立一個使用 xMockDepth 的 User generator 出來了~
資料更新
初始化完成後,接下來的設定基本上都和本來的用法一樣了~不一樣的地方,在於每次在主迴圈內、進行資料更新的時候,會需要做特殊的處理。
首先,本來在主迴圈裡面、每次都需要呼叫 Context 的 WaitAndUpdateAll() 這個函式,來要求 OpenNI Context 針對所有的 Node 進行更新、直到都更新好了再繼續執行;但是現在由於 mock node 本身不會自己產生資料,所以如果使用 WaitAndUpdateAll(),會因為等不到 xMockDepth 的更新,而讓程式卡住(不會完全不動,只是更新很慢)。所以,這邊要修改一下,變成呼叫 WaitOneUpdateAll(),然後把 xDepthGen 傳進去,讓他等到 depth generator 的資料準備好了,就繼續執行。
而等到 xDepthGen 取得新的深度資料之後呢,就要手動把新的 depth map 讀取出來、經過修改(看自己要怎麼改)、透過 xn::MockDepthGenerator 的 SetData() 這個函式,把修改後的結果餵給 xMockDepth 了~如此一來,之後 user generator 就會使用這個被修改過的資料,拿來做分析了~
實際上寫的話,大概就會像下面這樣:
// update data xContext.WaitOneUpdateAll( xDepthGen ); // get original depth map xn::DepthMetaData xDepthData; xDepthGen.GetMetaData( xDepthData ); // make data writable and modify xDepthData.MakeDataWritable(); // modify data.... // set data xMockDepth.SetData( xDepthData );
這邊基本上就是在 xDepthGen 的資料更新後,透過 GetMetaData() 把讀取到的資料(xDepthData)拿出來,然後透過 xn::DepthMetaData 的 MakeDataWritable() 這個函式,在內部建立一份額外的記憶體空間,讓資料可以被修改(本來是唯獨的)。
而之後,就可以針對 xDepthData 進行修改了~看是要套用什麼影像處理的演算法,就是看各自的需求了。修改資料時,還需要先取出可寫入的資料,最簡單的方法應該就是使用 WritableDepthMap() 這個函式,下面就是一個把所想像素都填 0 的範例:
xn::DepthMap& rDepthMap = xDepthData.WritableDepthMap(); for( unsigned int y = 0; y < rDepthMap.YRes(); ++ y ) for( unsigned int x = 0; x < rDepthMap.XRes(); ++ x ) rDepthMap( x, y ) = 0;
而如果需要的話,也可以直接透過 WritableData() 這個函式,來取得 XnDepthPixel 的指標,對整個陣列進行修改。
當修改完成後,只要透過 SetData() 這個函式,就可以把修改過的 xDepthData 送給 xMockDepth;而當資料送給 xMockDepth 後,對應的 user generator、也就是之前建立的 xUser 就會得到有新資料的通知,並且向 xMockDepth 來要這筆修改過的深度資料、並進行分析了~
這篇大概就先寫道這了。基本上,只是著重於概念上的說明,實際上要應用,主要還是看是要做什麼處理了;基本上,Heresy 想到比較可能會需要的,應該會是以影像處理或電腦視覺的 denoise、也就是減少深度的雜訊為主吧~另外,有需要的話,應該也是有可能輔以彩色的資料,來把深度感應器上的洞、閃爍的效果降低,這樣或許可以加強後續分析的穩定性。
不過,由於 OpenNI 互動程式基本上都是互動式的,如果在這部分影像處理花了太多的 CPU 計算能力,有可能會產生效能上的問題…這時候,勢必會需要靠 OpenMP 這類的平行化方法來針對多核心處理器做最佳化,甚至是採用 nVIDIA CUDA 或 OpenCL 這類的 GPGPU 技術,把計算丟到顯示卡去了~這點就是要自己斟酌的了。
[…] 修改 USER GENERATOR 用來分析的深度影像 […]
讚讚
Herecy,我用的设备是Xtion2,OpenNI是Xtion官网下的1.5.7版本。修改后的深度数据老是无法创建用户, 提示错误Couldn’t get max shift value,请问是什么原因
讚讚
代码如下
#define CHECK_RC(rc, what) \
if (rc != XN_STATUS_OK) \
{ \
printf(“%s failed: %s\n", what, xnGetStatusString(rc)); \
return rc; \
}
using namespace xn;
using namespace cv;
using namespace std;
// callback function of user generator: new user
void XN_CALLBACK_TYPE NewUser(xn::UserGenerator& generator,
XnUserID user,
void* pCookie)
{
cout << "New user identified: " << user << endl;
generator.GetSkeletonCap().RequestCalibration(user, true);
}
// callback function of skeleton: calibration end
void XN_CALLBACK_TYPE CalibrationEnd(xn::SkeletonCapability& skeleton,
XnUserID user,
XnCalibrationStatus eStatus,
void* pCookie)
{
cout << "Calibration complete for user " << user << ", ";
if (eStatus == XN_CALIBRATION_STATUS_OK)
{
cout << "Success" << endl;
skeleton.StartTracking(user);
}
else
{
cout << "Failure" << endl;
skeleton.RequestCalibration(user, true);
}
}
int main(int argc, char* argv[])
{
XnStatus nRetVal = XN_STATUS_OK;
Context xContext;
nRetVal = xContext.Init();
CHECK_RC(nRetVal, "Init");
XnMapOutputMode mapMode;
mapMode.nXRes = 640;
mapMode.nYRes = 480;
mapMode.nFPS = 30;
DepthGenerator xDepthGen;
nRetVal = xDepthGen.Create(xContext);
CHECK_RC(nRetVal, "xDepthGen");
xDepthGen.SetMapOutputMode(mapMode);
MockDepthGenerator xMockDepth;
nRetVal = xMockDepth.CreateBasedOn(xDepthGen, "mock-depth");
CHECK_RC(nRetVal, "xMockDepth");
xn::Query xQuery;
nRetVal = xQuery.AddNeededNode("mock-depth");
CHECK_RC(nRetVal, "mock-depth");
xn::UserGenerator xUser;
nRetVal = xUser.Create(xContext, &xQuery);
CHECK_RC(nRetVal, "xUser");
XnCallbackHandle hUserCB;
xUser.RegisterUserCallbacks(NewUser, NULL, NULL, hUserCB);
xn::SkeletonCapability mSC = xUser.GetSkeletonCap();
mSC.SetSkeletonProfile(XN_SKEL_PROFILE_ALL);
XnCallbackHandle hCalibCB;
mSC.RegisterToCalibrationComplete(CalibrationEnd, &xUser, hCalibCB);
xContext.StartGeneratingAll();
for (int i = 1; i < 100; i++)
{
// update data
xContext.WaitOneUpdateAll(xDepthGen);
// get original depth map
DepthMetaData xDepthData;
xDepthGen.GetMetaData(xDepthData);
//depth
DepthMetaData depthMD;
xDepthData.AllocateData(640, 480); //openni里的depthMD必须为指定size大小
//depthMD.AllocateData(depth_img.cols, depth_img.rows);
nRetVal = xDepthData.MakeDataWritable();
CHECK_RC(nRetVal, "Make depth data writable");
//DepthMap& depthMap = depthMD.WritableDepthMap();
DepthMap& rDepthMap = xDepthData.WritableDepthMap();
// modify data….
for (XnUInt32 y = 0; y < rDepthMap.YRes(); y++)
{
for (XnUInt32 x = 0; x 0)
{
// 8. get users
XnUserID* aUserID = new XnUserID[nUsers];
xUser.GetUsers(aUserID, nUsers);
// 9. check each user
for (int i = 0; i < nUsers; ++i)
{
// 10. if is tracking skeleton
if (mSC.IsTracking(aUserID[i]))
{
// 11. get skeleton joint data
XnSkeletonJointTransformation mJointTran;
mSC.GetSkeletonJoint(aUserID[i], XN_SKEL_HEAD, mJointTran);
// 12. output information
cout << "The head of user " << aUserID[i] << " is at (";
cout << mJointTran.position.position.X << ", ";
cout << mJointTran.position.position.Y << ", ";
cout << mJointTran.position.position.Z << ")" << endl;
}
}
delete[] aUserID;
}
}
xContext.StopGeneratingAll();
xContext.Release();
printf("\n");
return 0;
}
讚讚
Heresy 沒有試過 OpenNI1 搭配 Xtion 2 的狀況。
但是你有先試過標準的 NiTE 範例嗎?
讚讚
[…] 而另一方面,這也可以用來模擬類似 OpenN1 時提供的「Mock Node」、也就是可以用來修改取得道的深度資料、讓之後的 middleware(PrimeSense NiTE)可以使用。(參考《修改 User Generator 用來分析的深度影像》) […]
讚讚
請問 Heresy 大大:這樣的方法還是需要接上一個 Kinect ,是否有其他方法可以不用到Kinect,而是接上自製的 stereo camera 然後使用 Nite 的 Skeleton SDK function 呢?
讚讚
技術上可行(不過很麻煩),不過 NiTE 的授權不允許這件事。
NiTE 的授權是:只可以用在他自家的晶片產品上。
讚讚
Heresy 您好
我把 http://goo.gl/G5fcS 這個網址的main.cpp整個複製到程式去執行,執行錯誤
Find user generator failed: Can’t create any node of the requested type!
我把nRetVal = g_UserGenerator.Create( g_Context ,&xQuery);其中的&xQuery拿掉之後,
執行到g_MockDepth.SetData(depthMD);也是發生錯誤,我把這行註解掉之後,程式便正常執行 了,我想請問的是,g_MockDepth.SetData(depthMD);一定要執行嘛!?照理來說好像要,但是我執行之後就掛掉了,請問heresy知道為什麼嘛!?
讚讚
請參考內文說明,你所刪掉的 xQuery 或 SetData 這兩個部分,都是必要的。
讚讚
我知道,但是我加上這兩個部分,就掛了…
沒加 反而正常…正常是指 你有修改的部分都有顯示出來!!
讚讚
確認了一下。
你是使用 1.5.4.0 unstable 嗎?如果是的話,請換回 stable 版。
這次的 unstable 版的 MockDepthGenerator 似乎在 CreateBasedOn() 上有問題。
讚讚
我原本是用1.5.4.0 unstable,
但我改成openni-win64-1.5.2.23-dev stable之後
還是有一樣的問題….
讚讚
Heresy 這邊測試 1.5.2.23 是正常的。
讚讚
請問Heresy,
我現在改回stable版
Depth也正常了,但是換成UserGenerator 無法使用,
所有的Callback都沒有反應…
請問您有遇過這問題嘛!?
讚讚
不好意思,我找到問題了!
我忘記SetData給g_MockDepth了!!
讚讚
Heresy大 請問這樣可以在修改xDepthData的時候 把全部的pixel 換成另一張depth(也就是換一張圖,source是kinect抓的),然後丟給user generator畫出另一張深度圖的skeleton嗎 感謝~!
讚讚
理論上是可以的。
讚讚
感謝~! 我會做看看
讚讚
Heresy大大你好,
我試著把文章的內容加到
“使用 Qt 顯示 OpenNI 的人體骨架"文中的Initial()和UpdateData()的部份
可是我怎麼是就是只能改掉Qt的顯示部分,判別骨架的部份還是無法更改
我在想是不是因為我使用的影像是錄下來的.oni檔來分析
所以程式無法讀取新的xMockDepth去更改新的判別內容
而是讀取.oni檔舊有的m_Depth node
如果是這樣的話,那請問要如何解決這個問題呢?
還是就只能重錄一途阿??
期待你的回答
謝謝你的分享
讚讚
我後來又試過直接接上kinect測試
我也是把rDepthMap( x, y ) = 0直接用;
Qt的畫面都是只有彩色圖
但人走進去還是有paint畫出來的人骨架耶
是要怎麼強迫他讀取新的xMockDepth啊
謝謝你分享了
讚讚
播放 ONI 的時候,本身就是使用 mockDepthGenerator 了,再加上一層並不會有問題。
實際上 Heresy 自己在測試的時候,就是採用 ONI 檔來做測試的。
請確定你有把該修改的部分修改完。
主要是四個部分:
1. 建立 mockDepthGenerator
2. 修改 User Generator 的建立方法
3. 修改 OpenNI context 更新資料的方法
4. 讀取深度修改後丟給 mockDepthGenerator
讚讚
我把原本的Initial()改成如下
bool Initial()
{
// Initial OpenNI Context
m_eResult = m_Context.Init();
ifstream inFile(“E:\\Q\\depth_data\\path.txt");
string file_record;
getline(inFile,file_record);
play_buttom.SetPlaybackSpeed(speed_for_replay);
play_buttom.SetRepeat(0);
if( CheckError( “Context Initial failed" ) )
return false;
// create image node
m_eResult = m_Image.Create( m_Context );
if( CheckError( “Create Image Generator Error" ) )
return false;
// create depth node
m_eResult = m_Depth.Create( m_Context );
if( CheckError( “Create Depth Generator Error" ) )
return false;
//更改
xMockDepth.CreateBasedOn(m_Depth,"MockDepth");
xQuery.AddNeededNode(“MockDepth");
xUser.Create(m_Context, &xQuery);
// set nodes 更改
m_eResult = m_Depth.GetAlternativeViewPointCap().SetViewPoint( m_Image );
CheckError( “Can’t set the alternative view point on depth generator" );
XnCallbackHandle hUserCB;
xUser.RegisterUserCallbacks( CB_NewUser, NULL, NULL, hUserCB );
xUser.GetSkeletonCap().SetSkeletonProfile( XN_SKEL_PROFILE_ALL );
//m_User.GetSkeletonCap().SetSkeletonProfile( XN_SKEL_PROFILE_HEAD_HANDS );
XnCallbackHandle hCalibCB;
xUser.GetSkeletonCap().RegisterToCalibrationComplete( CB_CalibrationComplete, NULL, hCalibCB );
return true;
}
讚讚
UpdateData()修改如下,
bool UpdateData()
{
// update
m_eResult = m_Context.WaitOneUpdateAll(m_Depth);
if( CheckError( “Update Data" ) )
return false;
// get new data
m_Depth.GetMetaData( m_DepthMD );
m_DepthMD.MakeDataWritable();
xn::DepthMap& rDepthMap = m_DepthMD.WritableDepthMap();
for( unsigned int y = 0; y < rDepthMap.YRes(); ++ y )
for( unsigned int x = 0; x < rDepthMap.XRes(); ++ x )
rDepthMap( x, y ) = 0;
//transformDepthMD(m_DepthMD);
xMockDepth.SetData(m_DepthMD);
m_Image.GetMetaData( m_ImageMD );
pImageMap=m_Image.GetRGB24ImageMap();
return true;
}
讚讚
而新加入的宣告則放在 class COpenNI 的
private:
xn::MockDepthGenerator xMockDepth;
xn::Query xQuery;
xn::UserGenerator xUser;
只是不知道為何就是不能去更改骨架的結果耶
試了好幾次都無法
麻煩Heresy解惑了
謝謝
讚讚
你好,Heresy 這邊也重現你的問題了。
花了好一段時間,最後是發現…
應該是 mock depth generator 的名稱命名不太好,導致在根據名稱做 query 的時候找錯 dpeth generator 造成的…
似乎某些名稱(例如這邊的「MockDepth」)、在某些狀況下,似乎會出問題去用到其他的 depth generator…
Heresy 這邊只要換一個名字(「mock-depth」)就可以了。
麻煩你再試試看。
讚讚
WOW~~~
真的可以用了耶~~
謝謝Heresy~~
不過跑起來就真的會有點lag~~
讚讚
建議:
1. 如果是使用 ASUS Xtion 的話,可以考慮用 QVGA 解析度
2. 想辦法平行化!GPGPU 難度很高,但是應該至少可以透過 OpenMP 來針對多核心做平行化加速~
讚讚
Hi Heresy,
Great post , but i am having run time errors while
xDepthData.MakeDataWritable ();
/ / Modify the data ….
so when i try to modify it or even access it , it says , “read access violation error", i dont know whats the problem any idea ? :)
thanks
讚讚
可以麻煩提供稍微完整一點的程式碼嗎?
你是怎樣做修改的?
讚讚
nRetVal = g_Context.FindExistingNode(XN_NODE_TYPE_DEPTH,
g_DepthGenerator);
CHECK_RC(nRetVal, “Find depth generator");
nRetVal =
g_mockdepth.CreateBasedOn(g_DepthGenerator,"MockDepth");
CHECK_RC(nRetVal, “Create mock depth node");
xn::Query xQuery;
xQuery.AddNeededNode(“MockDepth");
// Adding the Skeletal and Pose detection capabilities
xQuery.AddSupportedCapability(XN_CAPABILITY_SKELETON);
xQuery.AddSupportedCapability(XN_CAPABILITY_POSE_DETECTION);
nRetVal = g_Context.FindExistingNode(XN_NODE_TYPE_USER,
g_UserGenerator);
if (nRetVal != XN_STATUS_OK) {
nRetVal = g_UserGenerator.Create(g_Context,&xQuery);
CHECK_RC(nRetVal, “Find user generator");
}
This is what is done in the while loop
{
g_Context.WaitOneUpdateAll(g_DepthGenerator);
g_DepthGenerator.GetMetaData(depthMD);
nRetVal = depthMD.MakeDataWritable();
transformDepthMD(depthMD); // we could use this to modify the
depth map but for the time being i have commented it out
nRetVal = g_mockdepth.SetData(depthMD);
…..
…..
…..
…..
…..
…..
}
so when it accesses the depthMD in transform function it gives violation error
讚讚
首先,你應該是使用 XML 來做初始化?
不過這邊這種 mock depth generator 的方法,應該沒辦法透過 XML 來做建立;但是你的寫法似乎有去考慮到有去找以建立的 user generator,沒找到再透過 query 條件來建立?
這樣的話…如果在 XML 裡面有建立 user generator 的話,應該會導致 mock node 的機制失效…這點可能是要注意的地方。
再來,可以麻煩你檢查一下 MakeDataWritable() 回傳的結果嗎?
另外,不知道你的 transformDepthMD() 這個含式是怎麼做處理的?
是按照官方範例的做法嗎?
讚讚
creation of user generator does not give any error.
I am using the xml files to do the initialization.
depthMD.MakeDataWritable() returns XN_STATUS_OK
transformDepthMD () contains the same function as the official sample , i am not doing anything to modify the depth map, it gives error while reading theDepthMap (x, y)
in the fist iteration.
I do not know where anything is getting wrong.
讚讚
可以麻煩確認一下,原始的 NiRecordSynthetic 範例是否可以正確執行嗎?
另外,Heresy 有試著修改 OpenNI 的「NiUserTracker」這個範例,讓他使用 mock depth generator 來做處理。
修改後的 main.cpp 在: http://goo.gl/G5fcS
他會在深度影像上,每隔五個像素、加上一條橫線。
可以比對原來的版本,或是搜尋「by Heresy」找到程式有修改的地方。
這樣的程式在 Heresy 這邊是可以正確執行的。
麻煩試試看。
讚讚
Thank you so much for the help :) . I figured out my error
I forgot to update the depthMD after context data generation.
g_DepthGenerator.GetMetaData(depthMD) // previously i was only doing this
g_Context.WaitOneUpdateAll (g_DepthGenerator);
g_DepthGenerator.GetMetaData(depthMD) // somehow this line got deleted
now its working fine.
Thanks a lot Heresy :)
讚讚
:)
讚讚
嚇!!!原來還有這招……
我之前還以為只能把他轉OpenCV格式才能改深度資訊勒> <
讚讚
這主要是修改過還要給 user generator 用啦~
如果沒要給它用的話,方法就很多了。
讚讚