在之前的一系列文章裡,其實 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, "MockDepth" );
// create user generator using mock depth generator
xn::Query xQuery;
xQuery.AddNeededNode( "MockDepth" );
xn::UserGenerator xUser;
xUser.Create( xContext, &xQuery );
在上面的程式碼裡,首先還是先建立 OpenNI 的 context xContext、並對他進行初始化。接下來,用一般的方法,建立一個 depth generator xDepthGen,用來讀取感應器的資料。
再來,則是建立一個 xn::MockDepthGenerator xMockDepth,用來當作虛擬的 depth generator;這邊是使用 CreateBasedOn() 這個函示來做建立的動作,如此可以把 xDepthGen 的基本資訊直接拿來給 xMockDepth 用。而第二個參數「"MockDepth"」則是一個字串,用來指定建立出來的 node 的名稱。
而在建立 user generator 的時候,則就需要透過 xn::Query 來做限制,來要求 OpenNI 去建立一個使用 xMockDepth 的資料的 user generator 了~而這邊限制條件的方法,基本上就是先建立一個 xn::Query 的物件 xQuery,然後透過他的 AddNeededNode() 這個函式來做條件的指定;而指定的條件,這邊就是給他 xMockDepth 的名稱,也就是 "MockDepth" 了~
在設定好 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 技術,把計算丟到顯示卡去了~這點就是要自己斟酌的了。
请问,视角矫正是等同于标定之后的效果吗?还有kinect本身存在了内参数信息吗?
to liuyuan
抱歉,看不懂你是在問什麼?
你的「标定之后的效果」和「内参数信息」是指什麼?
您好!
我有个比较低级的问题,您之前编写的这么多的程序中用到了很多函数,您是怎么知道它们的输出的,比如定义:SceneMetaData smd;在哪里可以看到smd(x,y)是用来做标识的?
我的意思就是说想要去做应用,我该参考哪些材料,感觉对openni中有很多的函式的输出还不清楚更不知道去用!希望heresy能够指点迷津。
to 自溟
安裝完 OpenNI SDK 後,他會有提供一份 API Reference,主要就是參考這份文件。
谢谢您的回答!
我仔细找了一下,您指的是“C:\。。OpenNIDocumentation”中的OpenNI.chm吗?
还是指的别的文件夹里面的?
另外是不是C:Program FilesOpenNISamples中提供的例程?我主要感觉到的是Samples中例子还是有点少,看到heresy写了这么多好例程,想必该有些相对全面的材料。。。
期待您的回答!
谢谢!
是的,主要就是這份文件。
實際上,OpenNI 官方範例裡面已經幾乎涵蓋到所有的基本使用狀況了;雖然在文件的部分,有的地方可能會有一些不容易理解的地方,但是基本上只要再去做一些測試,應該也是夠用了。
而 Heresy 這邊的文件或城市,也都是根據他的文件、以及範例來做延伸、修改的,應該沒有不足的問題。
嗯,感谢您的回答!
heresy,您好,新手,想问一个可能太傻的问题,就是用openni得到的深度数据16位的,这16位存的都是深度吗,像kinect sdk得到的深度数据低三位是游戏者索引。期待您的回答,谢谢!
OpenNI 取得的深度值就單純是深度,沒有其他資訊。
使用者的資訊要另外透過 User Generator 來取得。
http://viml.nchc.org.tw/blog/paper_info.php?CLASS_ID=1&SUB_ID=1&PAPER_ID=278
在這邊有辦法經由WritableDepthMap()來限定只有某個範圍的深度資料嗎?
因為我想修改DepthMetaData,讓我只需要處理某個範圍的深度資料
to kobe
技術上應該可以自己把不要的深度值濾掉。
heresy您好!
我现在遇到一个问题,我用一个普通摄像头采集RGB图像,用kinect采集深度信息,然后用深度信息和RGB图像进行物体分割匹配,但是发现大多数时候两者采集的时间相差很大,即采集一幅RGB图像,但Kinect采集的可能还是上一时刻的深度信息,这样就导致两者不匹配了,请问该如何处理呢?谢谢啦!
to Thanks
你的 RGB 影像不是用 Kinect 本身的嗎?
如果你是用額外的攝影機的話,由於兩者是完全不同的設備,所以一定會有時間差,這點基本上是無法克服的。
不過假設時間差是固定的話,應該可以透過事先的同步(例如用特殊的場景狀態),來計算出兩者個時間差,之後再把時間差考慮進來處理。
您好~想請問一下mock這個功能有一定要接著原本的hardware device嗎?
我如果有其他會產生depth的軟體想直接用setdata餵給openni能用mock辦到嗎?
感謝您的幫忙~!
to viaair
理論上可以自己指定資料進去,不用依賴現有的 Depth Generator。
不過 Heresy 沒試過。
您好~ 我有下載您的一段code來嘗試:
http://goo.gl/G5fcS
當code執行過”No depth generator found. Using a default one…”這一段之後再執行create usergenerator
會一直fail
請問這兩段是互相衝突的嗎?
感謝您的回覆!
to viaair
在這個範例哩,是使用 MockDepthGenerator 的 CreateBasedOn() 這個函式,來根據現有的 Depth Generator 來建立 mock depth generator。
而如果因為沒有 depth generator 而進入到「No depth generator」這個斷落的話,他就已經是在建立 mock depth generator 了。
所以接下來的動作,就相當於是在建立 mock depth generator 的 mock node…實際上在這邊是會出問題的。
所以後來的 user generator 也沒辦法根據 g_MockDepth 來做建立。
您好~
很抱歉我還是有一點不清楚
您說:
“所以接下來的動作,就相當於是在建立 mock depth generator 的 mock node…實際上在這邊是會出問題的。”
請問接下來的動作是指
// by Heresy, create mock node
g_MockDepth.CreateBasedOn( g_DepthGenerator, “mock-depth” );
// by Heresy, create an user generator fot mock depth node
xn::Query xQuery;
xQuery.AddNeededNode( “mock-depth” );
nRetVal = g_UserGenerator.Create( g_Context, &xQuery );
CHECK_RC(nRetVal, “Find user generator”);
這一段嗎?
為什麼user generator會因此無法create呢?
感謝您的回覆!!
to viaair
實際上是在這邊就出錯了
g_MockDepth.CreateBasedOn( g_DepthGenerator, “mock-depth” );
如果有加上錯誤檢查,就可以發現這邊就會有錯誤訊息,實際上 g_MockDepth 已經建立失敗了,所以後續的 user generator 自然也找不到對應的 depth generator 來建立。
to viaair
另外補充一下,如果是不基於現有的 depth generator 來建立 mockDepthGenerator 的話(不使用 CreateBasedOn() 而是使用 Create()),實際上會因為缺乏很多內部資訊,而無法給 NITE 的 user generator 使用。
要把這些資料都補進去…應該很麻煩(最重要的是官方似乎沒有相關說明)
而另一方面,NITE 的使用授權基本上是只允許使用 PrimeSense 的硬體(Kinect 和 Xtion 都是)使用其相關功能,如果你是搭配其他廠商的 Depth Generator 來使用的話,應該算是違反它的使用協議的。
OK 我了解了 非常感謝您的回答!!
你好Heresy,按照你的步骤试了一下,
在mContext.WaitOneUpdateAll(depthGenerator);这里出现:
Access violation reading location这种错误,但不是每次执行程序都会有错误。
不知道为什么,但是我把// create user generator using mock depth generator这里下面的代码注释掉,也就是先不使用mock generator,就没有问题,循环更新里也可以修改数据。请教Heresy,谢谢!
残云52011
抱歉,Heresy 沒碰到這個問題。
Heresy 你好,
请问通过record节点把视频录制下来后,能读取出关节信息吗?就是XnSkeletonJointTransformation的内容。非常困惑,没有查找到资料,多谢啦
OpenNI 的 recorder 不能記錄關節資料,但是可以再撥放的時候,從錄下來的資料重新進行辨識、追蹤。
你好,我按照你的思想,实现了NiUserTracker 这个Sample的更改,但是更改后的数据为什么还会跟踪到录像文件里的人呢?谢谢。
我修改的数据是生效的,但是UserGenerator使用的还是原先oni文件中的数据;按照你说的“修改深度資料、讓 User Generator 來分析”,我的理解应该是分析修改后的深度资料;但是我发现程序分析的依然是原先oni文件读出的深度资料,谢谢。
多谢你的文章,对我帮助不少,我看到你的http://goo.gl/G5fcS
这篇博文,希望对相似的问题的人多谢帮助。
to xbd
請問問題有解決了嗎?
已经解决了,谢谢heresy!,Thanks