OpenNI 里提供录制和拨放功能相关的 ProductionNode,分别是 Recorder、Player、Codec;而透过 OpenNI 所设计的架构,其实要录制、或是使用录制下来的数据,其实都是相当简单的∼不管是在《OpenNI User Guide》或是《OpenNI Documentation》里,都有提供范例可以参考。
文件格式
而以 OpenNI 提供的方法来进行录制的话,他会把指定 node 的数据,都录制在一个特殊格式「ONI」的档案里;这个格式他里面包含了复数个 node 的资料,所以只要一个档案,就可以同时记录深度和彩色影像了。
而实际上,OpenNI 官方范例里的 NiViewer 本身就包括了这样的录制(程序执行后按键盘「s」)、以及拨放的功能(直接点 *.ONI 应该就会由 NiViewer 开启了)。
录制
首先,下面就是一个简单的录制深度以及彩色影像的程序代码:
<span style="font-family:Microsoft YaHei;font-size:14px;">#include <iostream>
#include <string>
#include <XnCppWrapper.h>
using namespace std;
using namespace xn;
int main()
{
// 1. initial context
Context mContext;
mContext.Init();
// 2. map output mode
XnMapOutputMode mapMode;
mapMode.nXRes = 640;
mapMode.nYRes = 480;
mapMode.nFPS = 30;
// 3. create image generator
ImageGenerator mImageGenerator;
mImageGenerator.Create( mContext );
mImageGenerator.SetMapOutputMode( mapMode );
// 4. create depth generator
DepthGenerator mDepthGenerator;
mDepthGenerator.Create( mContext );
mDepthGenerator.SetMapOutputMode( mapMode );
mDepthGenerator.GetAlternativeViewPointCap().SetViewPoint( mImageGenerator );
// 5. create recorder
Recorder mRecoder;
mRecoder.Create( mContext );
mRecoder.SetDestination( XN_RECORD_MEDIUM_FILE, "E:\\OpenNI_Test.oni" );
mRecoder.AddNodeToRecording( mImageGenerator, XN_CODEC_UNCOMPRESSED );
mRecoder.AddNodeToRecording( mDepthGenerator, XN_CODEC_UNCOMPRESSED );
// 6. start generate data
mContext.StartGeneratingAll();
// 7. loop
unsigned int i = 0;
while( true )
{
if( ++i > 500 )
break;
cout << i << endl;
mContext.WaitAndUpdateAll();
}
mRecoder.Release();
// 8. stop
mContext.StopGeneratingAll();
mContext.Shutdown();
}</span>
在上面的程序代码里,应该可以很简单地看的出来,大部分的程序其实都和《透过 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,在这边就是mImageGen 和 mDepthGen 这两个 node;而第二个参数,则是要用什么样的方法,来对这个 node 的数据进行压缩。
在压缩的 codec 来说,OpenNI 提供了几种选择:
· XN_CODEC_NULL:采用 node 默认值
· XN_CODEC_UNCOMPRESSED:不压缩
· XN_CODEC_JPEG:JPEG 压缩(有损)
· XN_CODEC_16Z、 XN_CODEC_16Z_EMB_TABLES、XN_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::Recorder 的 Record() 函式)
而Heresy 在这边,则是用一个循环来做数据读取、录制的动作,这个循环会重复 1000 次,然后结束。在结束时,似乎是需要呼叫xn::Recorder 的 Release() 函式、来释放本身的资源、完成档案写出的动作。不过这边可能要注意的是,目前 OpenNI 的recorder 虽然可以持续的纪录数据在 ONI 文件中,但是在档案大小超过一定大小(约 2GB)后,会无法完成最后写入的动作,导致最后的档案无法被正常存取;所以如果要长时间录制的话,可能就要自己注意档案的大小、考虑想办法切割档案了。
播放
在播放的部分,如果只是单纯要使用录制下来的 ONI 的话,也相当地简单,原有的程序只要加上一行就可以了!而如果要有额外的控制功能,也可以透过 OpenNI 的 xn::Player 来做到。
最简单的其修改方法,就是在 OpenNI 的 context 初始化完成后、建立 Production Node 前,加上一行 xn::Context::OpenFileRecording(),来开起一个 ONI 檔。如果以《透过OpneNI 合并Kinect 深度以及彩色影像数据》一文中的程序来说,就是把「2. initialcontext」的部分,修改为:
xn::Context mContext;
eRes = mContext.Init();
mContext.OpenFileRecording( sFilename.c_str());
接下来,就继续沿用原来的程序代码就可以了!
而在透过 mContext 的 OpenFileRecording() 去开启 sFilename(型别为 std::string) 这个档案后,Conext 内部会由读取实际的装置(Kinect)、改成去读取 ONI 文件内的数据,而之后所建立的 Production Node,也都会对应到 ONI 内所录制的 node。而之后读取的时候,也是会依序读出 ONI 文件里各 node 的数据,不会再去读取实际的装置。
不过也由于这边的数据都是已经记录好的,所以在设定上会有一些限制,像是 Alternative View 在这个情况下,就会无法使用;不过由于 OpenNI 的设计上并不会因为这类的函示无法呼叫,就强制中断,所以程序还是可以可以继续执行的∼但是如果去分析每一个步骤回传的 XnStatus 的话,就可以看的出来那些函示无法在播放 ONI 时使用了。
另外,如果希望针对拨放在做进一步的控制的话,也可以使用xn::Player 这个 node 来做操作,包括了跳到某个 frame、回放、拨放速度等等,都可以做控制。不过这部分在这边就暂时不提,以后如果有机会再写了。
这篇基本上就先到这了。而有了录制和拨放的功能后,基本上测试资料就可以很简单地记录下来,要针对特殊的状况来帮程序除错,也会变得比较简单了∼