openni学习摘记

来源:互联网 发布:js寻路算法 编辑:程序博客网 时间:2024/05/16 10:55

原文请参考openni user guide,翻译有错的地方请指出,以下仅是个人理解,请慎重采纳

 

一.总揽

抽象层

顶层:使用基于openni接口的应用层

中间层:代表openni,提供同时和传感器以及中间件模块交互的通信接口

底层:捕获视频和音频信息的硬件设备

 

二.基本概念

1.模块

         Openni框架是个抽象层,用以同时为物理设备和中间层组件提供接口,它的API能够使许多组件在openni框架中被注册,这些组件被称作模块,用于产生和处理传感器数据。选择需要的硬件设备组件或者中间件组件是非常容易和宽松的。

 

传感器模块:

l  3D传感器

l  RGB摄像头

l  红外摄像头

l  声音设备

 

中间件模块:

l  全身分析中间件:一个处理传感器数据和产生身体相关信息的软件模块,这些信息通常是描述关节、朝向和中心等的数据结构

l  手心分析中间件:处理传感器数据和产生手位置的软件模块

l  姿势识别中间件:检测预定义姿势然后通知程序的软件模块

l  场景分析中间件:分析场景图以产生如下数据的软件模块

·前景和背景分割

·地板的坐标

·场景中人物个体的标识

 

2.产品节点(production nodes

可懂的3D数据被定义为能够包括、理解以及解释场景。创建可懂的3D数据是个复杂的任务,典型地这是从使用一个产生某种原始输出数据的传感器硬件开始的。通常这个数据是深度图,每个像素的数据值代表它距离传感器的距离。专门的中间件被用于处理这个原始输出然后产生能被理解和被程序使用的更高层输出。

 

Openni把产品节点定义为一系列的组件,它们在自然交互需要的数据产生过程中具有生产地位(productive role)。每个生产节点封装了与生成特定数据类型相关的功能,这些产品节点是openni接口的基础。然而,产品节点的API只是定义了语言(指的应该是接口)。数据生成的逻辑必须被挂接到openni中的模块实现。

 

原则上,每个产品节点应该是独立的单元,用来产生指定类型的数据,并且能将它提供给任何对象,不论这个对象是其他的产品节点还是就是程序本身。然而,通常一些产品节点只是使用那些代表更低层数据的节点,分析这些数据然后产生更高层的数据。

 

产品节点的类型

每个openni中的产品节点都有类型,属于下面系列中的一种

l  传感器相关的产品节点

l  中间件相关的产品节点

 

当前openni中支持如下的产品节点类型:

传感器相关的

l  Device:一个代表物理硬件的节点

l  Depth Generator:生成深度图的产品节点,对于那些想要能被openni兼容的传感器,必须实现这个节点

l  Image Generator:产生颜色图像的节点,想要与openni兼容,也要实现这个节点

l  IR Generator:产生红外图像的节点,想要与openni兼容,要实现这个节点

l  Audio Generator:产生音频流的节点,想要与openni兼容,要实现这个节点

 

中间件相关的

 

l  Gestures Alert Generator :指定的姿势被检测到的时候,为程序产生回调信号(假如对这个回调添加了回调函数,这个回调函数就会被调用)

l  Scene Analyzer :分析场景,包括前景背景的分割,场景中人物的标识以及地面检测,场景分析的主要输出是一个打了标记的深度图,每个像素素都有一个标签指明它代表一个人还是背景

l  Hand Point Generator :支持手检测和跟踪,当一个手点被检测到时,这个节点发出回调信号,当一个手点正被跟踪时,改变它的位置

 

l  User Generator :生成3D场景中的一个人(全身或者部分)

基于录制的目的,还支持如下的节点类型

l  Recorder: 执行数据录制

l  Player:从录制的文件那里读取数据并且播放它

l  Codec:用于压缩和解压缩录制文件中的数据

 

 

产品节点和前面的模块对比,可以发现它们其实讲的内容都差不多。但是它们指的层面不一样,模块是从整个框架来描述的,而产品节点却是openni中各种功能的具体实现,在这些产品节点中封装了所有的功能

 

 

3.产品链表

如前面解释的,几个模块(中间件和传感器)可以同时注册给一个openni实现。这种拓扑给程序提供了选择哪种传感器设备以及用哪些中间件来生产和处理数据

 

产品链表指的是一个产品节点使用其他的产品节点作为输入,子产品节点又继续使用孙产品节点,如此组成的树形的拓扑关系,就是产品链表

 

例如:品牌A提供了一个user generator中间件的实现,品牌B提供了实现user generator独立中间件,它们两个都可以程序开发人员使用。现在openni允许程序去确定使用哪一个模块,或者说产品链。接着程序可以根据已经注册的模块来枚举所有可能的产品链,根据具体的品牌、组件或者版本等信息选择产品链中的一个。

 

通常,一个程序只对每个产品链的顶端节点感兴趣,这是在实际应用层输出所需数据的节点。Openni允许程序只使用顶端的单个节点,而不去管这个节点以下的其他节点

 

4Capabilities—功能

Capabilities机制支持多个中间件和设备方便的注册到openniOpenni采用这样的做法,就是不同的提供者对于他们的产品节点可以有不同的功能和配置,因而一些非强制的扩展由openni API定义。这些对API的可选扩展被称为Capabilities,引出了增强的功能,让程序自己决定是否使用某个扩展。一个产品节点可以被询问是否支持某一特定的功能,如果支持,那个特定的节点就可以调用这些函数。

 

Openni发布的时候就具备了一系列的功能,具有在将来添加进一步功能的选项。每个模块可以声明它支持的功能,当要求枚举产品链表时,程序可以指明Capabilities必须作为标准必须被支持,只有那些支持所需功能的模块能通过枚举返回。

 

当前支持如下的功能:

l  Alternative View: 能够让任意类型的图像生成器(depth,image,IR)将它的数据转换去显示,就好像这个传感器被放在另一个位置上一样(通常是另外一个传感器),就是以另外一种视图显示

 

l  Cropping: map generator能够将一帧指定区域的数据输出,而不是整帧。当cropping被允许的时候,生成的图像被缩减以匹配更低的分辨率。

 

l  Frame Sync: 让两个产生帧数据的传感器进行帧同步以让它们在同一时间到达

 

l  Mirror: 允许镜像generator产生的数据,当传感器放在用户前面的时候,做镜像处理很有用

l  Pose Detection:能让user generator识别出用户指定的姿势

l  Skeleton:能让user generator输出骨骼数据,这个数据包括骨骼关节点的位置,并且能够跟踪骨骼的位置以及进行用户标定

l  User Position:能让深度摄像头优化输出的场景的指定区域

l  Error State:允许一个节点报告它的错误状态,这说明在实际应用中,节点可能不能正常工作

l  Lock Aware:允许节点在上下文范围外被锁定

 

5.产生和读取数据

产生数据:

能产生数据的产品节点叫做generator,一旦它们被创建,并不立即开始产生数据,允许程序来设置所需的配置。这能够保证一旦这些对象开始抽取数据到程序,这些数据是根据所需配置产生的。数据生成器在没有指明被要求去产生数据的话,不会自己产生数据

xn::Generator::StartGenerating();//开始生成数据

xn::Generator::StopGenerating;//停止生成数据,但是节点仍然存在没有被销毁

 

读取数据:

数据生成器经常接收到新的数据,然而程序仍然可能在使用旧的数据(如上一帧的深度图像),因为这个原因,所有的生成器都在内部保存新的数据,直到被明确的要求将这些数据更新到最新的、程序可用的数据。也就是说,数据生成器会在其内部隐藏新数据,直到被明确要求将这些最新数据暴露给应用程序,这里要使用UpdateData函数。Openni允许程序等待,直到新的数据可以使用,使用xn::Generator::WaitAndUpdateData()先进行等待新数据,新数据到达后就进行更新

在某些场合,程序有多于一个的节点,并且希望所有的节点都被更新。分别有这些函数:

xn::Context::WaitAnyUpdateAll()xn::Context::WaitOneUpdateAll(): xn::Context::WaitNoneUpdateAll(): xn::Context::WaitAndUpdateAll():

函数作用即为其字面意思,这些函数在2秒超时后会自动退出。强烈建议使用上面的函数进行数据更新,可以避免许多的问题。

 

6.模拟节点(mock node

模拟节点的实现不包括任何生成数据用的逻辑,而是它允许外部的组件给其提供配置更改和数据。程序中不会用到模拟节点,它们一般用在从录制文件读取数据的播放节点中,用来模拟实际的节点

 

7.在程序和锁定节点间共享设备

在绝大多数情况下,openni节点产生的数据都来自硬件设备。一个硬件设备通常可以有多个配置,因而如果多个应用程序连续的使用同一个硬件设备,它们的配置就必须同步。然而,在编写程序的时候,是不可能知道其他的程序会同时执行什么操作,这样的话就不能进行配置同步。另外有的时候一个程序使用一个特定的配置是很重要的。

 

Openni有两种模式来允许多个程序共享一个硬件设备

l  完全共享(默认):在这个模式中,应用程序声明它自己能够处理这个节点的配置,openni接口允许给任何配置改变注册回调函数,所以当配置改变(被同一个应用程序,或被其他的程序)的时候,程序可以得到通知。

l  锁定配置:这个模式中一个,应用程序声明它想要锁定一个节点的当前配置,openni就不再允许在这个节点做“设置”操作。如果这个节点代表一个硬件设备(或任何其他可以在进程件共享的东西),它必须实现”lock aware”功能,用于允许跨进程锁定。

当一个节点被锁定的时候,锁定的程序获得一个加锁句柄,除了使用这个句柄去对这个节点解锁外,还可以使用这个句柄去改变节点的配置,这期间不需要释放这个锁,以防这期间被其他的程序拿走了这个节点的占有权

 

 

8.产品节点错误状态

每个节点都有一个错误状态,用以表明它当前是否能正常工作。默认的情况下每个节点的错误状态都是OK,如果节点实现了Error Status功能,就可以在错误发生的时候更改错误状态的值。

应用程序可以坚持每个节点的错误状态,为了获得错误状态更改的通知,应用程序可以为节点的错误状态的任何更改添加一个回调函数。

Openni把所有节点的错误状态合成为一个全局错误状态,这允许程序能更加方便的查询当前是否正常工作,即为XnContext里面的globalErrorState成员

 

三.入门

 

主要对象

 

<1>上下文对象(context object)

 

上下文对象是OpenNI的主对象,它包括(使用OpenNI的程序的)所有状态信息,以及这个程序使用的所有产品链。同一个程序可以创建多个上下文,但是它们不可以共享信息。上下文对象在使用前必须先被初始化,这个时候所有的内置模块都被加载。

 

上下文对象对应开发包里的类Context,值得注意的是它里面的一个成员

XnContext* m_pContext;

XnContext采用前向声明的方式,在XnTypes.h里有如下的声明:

typedef struct XnContext XnContext;

而它的实现则被定义在XnInternalTypes.h这个头文件里面:

struct XnContext

{

   XnLicenseList* pLicenses;

   XnModuleLoader* pModuleLoader;

   XnNodesMap* pNodesMap;

   XnBool bGlobalMirrorSet;

   XnBool bGlobalMirror;

   XnStatus globalErrorState;

   XnErrorStateChangedEvent* pGlobalErrorChangeEvent;

   XN_EVENT_HANDLE hNewDataEvent;

   XnUInt32 nLastLockID;

   XnFPSData readFPS;

};

 

 

 

Context类具体的实现过程为:

在类Context的定义里面,同时对所有的函数体都做了实现,即Context的声明和实现都放在一起,均在XnCppWrapper.h里面,这个文件里包括了XnTypes.h,但是没有包括XnInternalTypes.h。也就是说Context类里面的XnContext是个不完全类型,而由于Context类里面只使用了XnContext的指针对象,所以编译能够通过。同时在这个类里面以XnContext指针为参数调用一些函数对XnContext进行处理,包括进行初始化,这些函数都放在XnOpenNI.cpp中,而在这个文件里面包含了XnInternalTypes.h,即完成了类XnContext的定义。

 

总的来说,XnContext的使用采用了前向声明这种做法

 

<2>元数据对象(Metadata Objects

元数据对象封装了特定数据的一系列属性。每个生成器(generator)生产的数据都有它自己的元数据对象,也就是生成器的类里面有对应的元数据的成员变量。另外,元数据在节点产生对应数据的时候,在记录其配置信息方面起着重要的作用。有时候在从一个节点读取数据的过程中节点的配置发生了改变,这产生的不一致如果不处理好的话可能导致程序错误

 

例子

一个深度生成器被配置成QVGA模式(320 X 240),应用程序不断从它读取数据。某个时候程序将这个节点的输出分辨率改为VGA模式(640 X 480)。在新的一帧到来之前,程序可能会遇到不一致的情况,在调用xn::DepthGenerator::GetDepthMap()的时候返回一个QVGA图,但是调用xn::DepthGenerator::GetMapOutputMode()将返回当前的模式是VGA,这可能会让程序认为获取的深度图像是VGA模式的,这个时候就会发生访问越界的错误

 

解决的办法是:每个节点都有它自己的元数据对象,记录了读取数据时的相关属性,在上面的情况中,正确的方法是先获取节点的元数据对象,然后使用元数据对象来访问真正的数据以及获得其分辨率信息

 

实际编程都使用元数据来操作

下面是元数据的存储结构,元数据对象类里面封装了对应的结构体,并添加了相应的方法

 

typedef struct XnOutputMetaData

{

   /** Represents the time in which the data was received. **/

   XnUInt64 nTimestamp;

   /** The frame ID, or frame number, of this frame. **/

   XnUInt32 nFrameID; 

   /** The size of the data. **/

   XnUInt32 nDataSize;

   /** Specifies whether the generator updated this data on the last call to one of the XnWaitXXXUpdateXXX() functions. **/

   XnBool bIsNew;

 

} XnOutputMetaData;

 

/** Holds information about a frame of data that has a map format. **/

typedef struct XnMapMetaData

{

   /** A pointer to general information about this frame. **/

   XnOutputMetaData* pOutput;

   /** The resolution of this frame, including any cropping that was set. **/

   XnUInt32XYPair Res;

   /** The offset of the cropped region within this frame. Set to (0,0) if no cropping was done. **/

   XnUInt32XYPair Offset;

   /** The full resolution of this frame, disregarding cropping. **/

   XnUInt32XYPair FullRes; 

   /** The pixel format of this frame. **/

   XnPixelFormat PixelFormat; 

   /** The number of frames per second that was set when this frame was received. **/

   XnUInt32 nFPS;

} XnMapMetaData;

 

/** Holds information about a frame of depth. **/

typedef struct XnDepthMetaData

{

   /** A pointer to the map meta data of this frame. **/

   XnMapMetaData* pMap; 

   /** A pointer to the depth data of this frame. **/

   const XnDepthPixel* pData; 

   /** The value of the Z resolution of this frame - the maximum depth a pixel can have. **/

   XnDepthPixel nZRes;

} XnDepthMetaData;

 

typedef struct XnImageMetaData

{

   /** A pointer to the map meta data of this frame **/

   XnMapMetaData* pMap;

  

   /** A pointer to the image data of this frame **/

   const XnUInt8* pData;

} XnImageMetaData;

 

/** Holds information about an IR frame. **/

typedef struct XnIRMetaData

{

   /** A pointer to the map meta data of this frame **/

   XnMapMetaData* pMap;

  

   /** A pointer to the IR data of this frame **/

   const XnIRPixel* pData;

} XnIRMetaData;

 

typedef struct XnAudioMetaData

{

   /** A pointer to general information about this chunk of audio. **/

   XnOutputMetaData* pOutput;

 

   /** The wave output mode of this cunk of audio. **/

   XnWaveOutputMode Wave;

  

   /** A pointer to audio data of this cunk. **/

   const XnUInt8* pData;

} XnAudioMetaData;

 

typedef struct XnSceneMetaData

{

   /** A pointer to the map meta data of this frame. **/

   XnMapMetaData* pMap;

 

   /** A pointer to the scene data of this frame. **/

   const XnLabel* pData;

} XnSceneMetaData;

 

<3>配置更改

Openni里的接口的每个配置选项均遵从如下的函数

l  一个set函数用以修改配置

l  一个get函数获取当前配置

l  注册和反注册回调函数,回调函数在配置更改的时候都会被调用

 

<4>数据生成器

 

图像生成器(map generator

为产生各种图像的生成器提供了基本的接口,主要的功能有:输出模式配置,裁剪,可选视角和帧同步

 

深度生成器

产生深度图,主要功能有:获取深度图,获取设备最大深度,配置视场,user position capability

 

图片生成器(image generator

产生彩色图像,主要功能:获取彩色图,设置像素格式

 

红外生成器

产生红外图,主要功能:提供IR

 

场景分析器

获取原始传感器数据然后用标签来标识图像中的场景,主要功能:获取标签图,获取地面

 

声音生成器

产生声音数据,主要功能:获取声音缓存,波形输出模式设置(采样模式,通道数和每个样本的位数)

 

手势生成器:

进行躯体或者手势跟中,主要功能:添加/移除某个手势,获取活动手势的名称,注册/反注册手势回调函数,注册/反注册手势改变

 

手心生成器

进行手心跟踪,主要功能:开始/停止手跟踪,注册/反注册回调函数

 

用户生成器

产生与场景中人物相关的数据,主要功能:

获取用户数目,获取当前用户,获取用户的中心,获取用户的像素数据