錄製、重播 OpenNI 擷取到的資料

前面已經花了好幾篇文章,來講怎樣透過 OpenNI 來讀取 Kinect 的資料了,而接下來這一篇,則是大概來講一下,怎樣把讀到的資料記錄下來。

在簡介 OpenNI 的時候(文章),已經有提到過,OpenNI 裡提供錄製和撥放功能相關的 Production Node,分別是 Recorder、Player、Codec;而透過 OpenNI 所設計的架構,其實要錄製、或是使用錄製下來的資料,其實都是相當簡單的∼不管是在《OpenNI User Guide》或是《OpenNI Documentation》裡,都有提供範例可以參考。

檔案格式

而以 OpenNI 提供的方法來進行錄製的話,他會把指定 node 的資料,都錄製在一個特殊格式「ONI」的檔案裡;這個格式他裡面包含了複數個 node 的資料,所以只要一個檔案,就可以同時記錄深度和彩色影像了。

而實際上,OpenNI 官方範例裡的 NiViewer 本身就包括了這樣的錄製(程式執行後按鍵盤「s」)、以及撥放的功能(直接點 *.ONI 應該就會由 NiViewer 開啟了)。

錄製

首先,下面就是一個簡單的錄製深度以及彩色影像的程式碼:

// 1. initial context
xn::Context mContext;
mContext.Init();

// 2. map output mode
XnMapOutputMode mapMode;
mapMode.nXRes = 640;
mapMode.nYRes = 480;
mapMode.nFPS = 30;

// 3. create image generator
xn::ImageGenerator mImageGen;
mImageGen.Create( mContext );
mImageGen.SetMapOutputMode( mapMode );

// 4. create depth generator
xn::DepthGenerator mDepthGen;
mDepthGen.Create( mContext );
mDepthGen.SetMapOutputMode( mapMode );
mDepthGen.GetAlternativeViewPointCap().SetViewPoint( mImageGen );

// 5. create recorder
xn::Recorder mRecoder;
mRecoder.Create( mContext );
mRecoder.SetDestination( XN_RECORD_MEDIUM_FILE, sFilename.c_str() );
mRecoder.AddNodeToRecording( mImageGen, XN_CODEC_JPEG );
mRecoder.AddNodeToRecording( mDepthGen, XN_CODEC_16Z_EMB_TABLES );


// 6. start generate data
mContext.StartGeneratingAll();

// 7. loop
nsigned int i = 0;
while( true )
{
if( i > 1000 )
break;
cout << i << endl;
mContext.WaitAndUpdateAll();
}
mRecoder.Release();


// 8. stop
mContext.StopGeneratingAll();
mContext.Shutdown();

在上面的程式碼裡,應該可以很簡單地看的出來,大部分的程式其實都和《透過 OpneNI 合併 Kinect 深度以及彩色影像資料》一文內的程式碼差不多,主要有所不同的地方,僅有用黃色強調的部分而已;而實際上,要讓現有的 OpenNI 程式能夠把資料錄製下來,也就只要加上這些程式就夠了!

究竟要加那些東西呢?其實真的很簡單,只需要在本來的程式的 node 都設定完成後,在開始建立 xn::Recorder 這個錄製資料用的 node,並進行設定就可以了∼對應到上面的程式碼,就是「5. create recorder」的部分。

這裡,首先就是宣告出一個 xn::Recorder 的物件 mRecorder,並透過 Create() 這個函式來建立出 production node。接下來,則是透過 SetDestination() 這個函式,來指定 recorder 之後要將資料存在哪個檔案裡;而這個函式需要指定兩個參數,第一個是要錄製的媒體形式,目前 OpenNI 僅能使用 XN_RECORD_MEDIUM_FILE、也就是檔案的形式,而沒有其他選擇。第二個參數則就是檔案的名稱,也就是要錄製的檔案名稱(這邊 sFilename 的型別是 std::string)。

在這些設定好了以後,接下來就是要設定要錄製的 node 了,這邊所使用的是 AddNodeToRecording() 這個函式。這個函式在使用上也很單純,第一個參數就是要錄製的 node,在這邊就是 mImageGenmDepthGen 這兩個 node;而第二個參數,則是要用什麼樣的方法,來對這個 node 的資料進行壓縮。

在壓縮的 codec 來說,OpenNI 提供了幾種選擇:

  • XN_CODEC_NULL:採用 node 預設值
  • XN_CODEC_UNCOMPRESSED:不壓縮
  • XN_CODEC_JPEG:JPEG 壓縮(有損)
  • XN_CODEC_16ZXN_CODEC_16Z_EMB_TABLESXN_CODEC_8Z:ZIP 壓縮?

不過很遺憾的是,在官方資料裡面,似乎沒有針對這些 codec 做詳細的說明,所以 Heresy 不太確定最後三個壓縮方法的意義,不過以字面上來看,Heresy 個人覺得應該是針對 16bit 和 8bit 資料做 ZIP 壓縮;而以 NiViewer 裡面的程式來看,一般 image generator 應該是可以使用 XN_CODEC_JPEG 來做壓縮,而 depth generator 則是使用 XN_CODEC_16Z_EMB_TABLES。而如果不在乎空間的話,要使用 XN_CODEC_UNCOMPRESSED 應該也是可行的。

xn::Recorder 都設定好了之後,接下來就可以按照原來的方法,透過 context 的 StartGeneratingAll() 來開始讀取資料了∼而之後呢,只要呼叫了 context 的 WaitAndUpdateAll() 這系列的函式,xn::Recorder 就會把資料寫入到指定的 ONI 檔裡了∼(或者,也可以呼叫 xn::RecorderRecord() 函式)

而 Heresy 在這邊,則是用一個迴圈來做資料讀取、錄製的動作,這個迴圈會重複 1000 次,然後結束。在結束時,似乎是需要呼叫 xn::RecorderRelease() 函式、來釋放本身的資源、完成檔案寫出的動作。不過這邊可能要注意的是,目前 OpenNI 的 recorder 雖然可以持續的紀錄資料在 ONI 檔中,但是在檔案大小超過一定大小(約 2GB)後,會無法完成最後寫入的動作,導致最後的檔案無法被正常存取;所以如果要長時間錄製的話,可能就要自己注意檔案的大小、考慮想辦法切割檔案了。

 

播放

在播放的部分,如果只是單純要使用錄製下來的 ONI 的話,也相當地簡單,原有的程式只要加上一行就可以了!而如果要有額外的控制功能,也可以透過 OpenNI 的 xn::Player 來做到。

最簡單的其修改方法,就是在 OpenNI 的 context 初始化完成後、建立 Production Node 前,加上一行 xn::Context::OpenFileRecording(),來開起一個 ONI 檔。如果以《透過 OpneNI 合併 Kinect 深度以及彩色影像資料》一文中的程式來說,就是把「2. initial context」的部分,修改為:

xn::Context mContext;
eRes = mContext.Init();
mContext.OpenFileRecording( sFilename.c_str() );

接下來,就繼續沿用原來的程式碼就可以了!

而在透過 mContextOpenFileRecording() 去開啟 sFilename(型別為 std::string)這個檔案後,Conext 內部會由讀取實際的裝置(Kinect)、改成去讀取 ONI 檔內的資料,而之後所建立的 Production Node,也都會對應到 ONI 內所錄製的 node。而之後讀取的時候,也是會依序讀出 ONI 檔裡各 node 的資料,不會再去讀取實際的裝置。

不過也由於這邊的資料都是已經記錄好的,所以在設定上會有一些限制,像是 Alternative View 在這個情況下,就會無法使用;不過由於 OpenNI 的設計上並不會因為這類的函示無法呼叫,就強制中斷,所以程式還是可以可以繼續執行的∼但是如果去分析每一個步驟回傳的 XnStatus 的話,就可以看的出來那些函示無法在播放 ONI 時使用了。

另外,如果希望針對撥放在做進一步的控制的話,也可以使用 xn::Player 這個 node 來做操作,包括了跳到某個 frame、重播、撥放速度等等,都可以做控制。不過這部分在這邊就暫時不提,以後如果有機會再寫了。

 

這篇基本上就先到這了。而有了錄製和撥放的功能後,基本上測試資料就可以很簡單地記錄下來,要針對特殊的狀況來幫程式除錯,也會變得比較簡單了∼

30 thoughts on “錄製、重播 OpenNI 擷取到的資料”

  1. 您好!請問目前OPENNI的record還是不支持UserGenerator的錄製對嗎?

  2. Hi, Heresy: 首先非常感谢你的一系列文章。我也在用OpenNI开发一些应用,恰好需要同时对深度和颜色进行录制。目标是希望这两个通道,保持视角和时间同步。 nRetVal =depth.GetAlternativeViewPointCap().SetViewPoint( color ); nRetVal=depth.GetFrameSyncCap().FrameSyncWith(color);结果发现,无论用哪种context.WaitXupdateall()都无法实现录制的时间同步(颜色总是超前于深度)。当然,不用FrameSyncWith也是不同步的。这里的”不同步”,是用OpenNI的NIViewer中的overlay模式发现的(单击”9″)。 百思不得其解,请问你是否遇到这样的问题呢? 谢谢

  3. to charmp 抱歉,Heresy 這邊測試的時候,並沒有發生明顯的不同步的狀況。不知道你的不同不適差異多大?

  4. To: heresy 当我们的手臂以较快速度挥动时,这种不同步就非常明显。如果你能提供一个邮箱的话,我可以把source code和oni视频发给你 🙂 P.S. Recorder 似乎不支持Release函数啊。因此mRecoder.Release(); 好像难以编译通过。请问您用的是OpenNI1.0吗? 谢谢

  5. to charmp 您好,xn::Recorder 應該是有 Release() 這個函式沒有問題的。基本上每一個 Production Node 都有這樣的函式,不知道您的編譯錯誤訊息是什麼?

  6. To heresy:编译信息: estOp estOpenNI.cpp(131) : error C2039: “Release”: 不是“xn::Recorder”的成员在OpenNI1.0.0中,productionNode确实没有Release这个函数,也许你用的版本更高。另外,异步的问题是NiViewer的现实方式所导致的。Recorder本身并无问题。当然,depth.GetFrameSyncCap().FrameSyncWith(image);是必要的。

  7. to charmp xn::Recorder::Release() 的部分,可能要麻煩您換到最新的 unstable 版試試看了;Heresy 目前是用 1.0.0.25,應該是可以編譯的。而不同步的問題,Heresy 這邊並沒有認真研究過,所以可能幫不上忙了。

  8. 我编译了一下手部追踪滑鼠模拟的那个程序,也出现了error C2039: “Release”: 不是“xn::Context”的成员,怎么解决啊

  9. to 星星麻煩請回應在對應的文章。另外,也請將 OpenNI 以及 NITE 更新到最新版。Context 的 Release 是後來才加上,用來取代 Shutdown 的

  10. 谢谢指教! 只是暂时没找到我看的那篇文章 就在这回复了 嘻嘻

  11. 刚开始学看了博主的文章受益匪浅。这个档案是往哪个路径里写进去了?为什么默认目录下没有.oni的文件呢

  12. 刚刚有留言,但是不知道为什么再次刷新之后没有显示出来~按照文中的方法我尝试了用OpenFileRecording()读取已有的oni文件但是一直没有成功,能否给出正确的代码呢?我的邮箱:daisyhaohao@163.com
    麻烦博主了~:D

  13. to daisy

    上面的範例程式碼基本上只是省略了一些部分,其他的內容應該都還是可以正確執行的。
    請問你的錯誤是什麼?

  14. 我犯了一个很笨的错误,我把oni的路径弄错了!==!
    现在用的挺好的~嘿嘿~但是又出现了新的问题,我的代码总是循环的读我载入的oni文件,有没有什么方法比如获取到oni视频的总帧数作为停止读数据的判断呢?

  15. to daisy

    請參考官方文件內,關於 xn::Player 的部分。他有提供對應的函式可以做控制。

  16. to heresy

    我在重播 OpenNI 擷取到的資料時,用的是context.WaitNoneUpdateAll(),當第二次播放時該處會報錯:讀取位置…時發生訪問衝突,不知你可有遇到這種情況

  17. to henry
    抱歉,Heresy 沒碰過這樣的問題。
    不知道你用官方範例是否也會有同樣的問題?

  18. Heresy 你好,
    昨天问了你这个问题“请问通过record节点把视频录制下来后,能读取出关节信息吗?就是XnSkeletonJointTransformati
    on的内容。”
    例如NiUserTracker这个程序,它能读取录制的程序,对人的骨架辨识、追踪,但是否能读出人的关节位置信息呢?感觉既然标注出了骨架就应该有这个信息的。但没有找到资料。我试过了,没有成功,不知道是无法实现还是我自己的程序出了问题呢?是否有相关资料呢?非常感谢您的回应!

  19. to mymay
    你還是一樣要去建立 UserGenerator 來進行操作,使用方法就跟本來一樣。
    只要設定都正確,他就會從錄下來的 Depth map 來做分析,計算出骨架的位置了。

  20. heresy,你好,请问您试过单机多个kinect同步录制吗?我按照您单个录制的方法做的时候出现了点问题,是否需要开线程?谢谢!

  21. @mackintosh

    Heresy 沒有這樣測試過,不過理論上一個 recorder 只能對應一組資料,如果是多個 device 的話,可能要個別用一個 recorder 來錄?
    不知道有這樣測試過嗎?

  22. @heresy
    我尝试过用2个recorder对应1个context,
    然后分别AddNodeToRecording( Image1,…
    AddNodeToRecording( Image2,…
    用一个context 会在 context.WaitAndUpdateAll()时候出现异常,
    如果分别对应2个context,我开了两个线程,但是只有一个线程能正常工作,我想2路可以回放,应该也可以同时录制的,我不知道是哪出了问题。

  23. @mackintosh

    抱歉,這個就不確定了。
    Heresy 目前的開發環境也都已經切換到 OpenNI 2 了。

發佈回覆給「mackintosh」的留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。