OpenNi学习笔记

OpenNI概念三层视图

顶层:展示了基于 OpenNI 实现体感的应用程序。

中间层:展示了 OpenNI,提供传感器和中间件组件之间交互的接口,中间件分析 传感器数据。

底层:展示了捕捉视觉和声音场景元素的硬件设备。

Modules 模块

OpenNI框架是个提供了物理设备和中间件组件的一个抽象层。API能够让众多组件 在OpenNI框架中注册。这些组件被称为模块,被用来生成和处理感官数据。

(1) 传感器模块:

3D sensor 三维传感器;

RGB camera RGB摄像头;

IR camera 红外摄像头;

Audio device 音频设备(麦克风);

(2) 中间件组件:

全肢体分析中间件:十一个处理感官数据,生成肢体相关信息(常见的数据结构如关节,方向,重心等)

手心分析中间件:是一个处理感官数据和生成手心的位置信息的软件组件。

手势探测中间件:是一个分辨预定义的手势和提醒应用程序的软件组件。

场景分析中间件:是一个分析场景图像的软件中间件,产生如下信息:场景的前景和背景的分离;平面图的坐标;场景中独特轮廓的识别。


Production Nodes 生产节点

OpenNI定义了生产节点 它具有拥有能在生成体感要求的数据过程中充当生产性角 色的一套单元。 每个生产节点都能够使用其他更低级的生产节点 (读数据, 控制配置等) , 也能够被其他高级节点或本应用程序使用。

生产节点类型 :传感器相关生产节点,中间件相关生产节点。

(1) 传感器相关生产节点Sensor-related Production Nodes 

设备Device :这种节点是物理的设备(例如:深度传感器,或者RGB摄像头)。这个节点的主要角色是使设备可配置。

a.深度生成器Depth Generator::这种节点能够生成深度映射。

b.图像生成器Image Generator :这种节点能够生成彩色图像映射。

c.红外生成器IR Generator :这种节点能够生成红外图像映射。

e.音频生成器Audio Generator :这种节点产生音频流。

(2) 中间件相关生产节点Middleware Related Production Nodes

a.手势告警生成器Gestures Alert Generator :当特定手势被识别能够回调应用程序。

b.场景分析器Scene Analyzer :分析一个场景,包括前景从背景分开,识别场景中的体型,发现 平面图。场c景分析器的主要输出是标记的深度映射,每一个像素都包含一个标 签,指明是体型还是背景的一部分。

c.手心生成器Hand Point Generator :支持手的发现和跟踪。这个节点当发现一个手心(手掌),或者 当手心被跟踪时,位置发生了变化,就产生一个回调事件。

d.用户生成器User Generator :生成一个在三维场景中的全部或部分肢体图画。

对于记录目的,以下产品节点被支持:

记录器Recorder :实现数据记录。

播放器Player: :从记录里读取数据并且播放它。

编码器Codec :用来压缩和解压缩记录中的数据

Production Chains 生产链

节点顺序是相互依赖的,以产生所需的肢体数据,被称为生产链

Capabilities 能力

一个生产节点可以被问是否支持某个能力。如果支持,这些功能就可以被特定节点调用。 OpenNI发布包括一套能力,也可以在将来继续增加新的能力。每一个模块能申明它 所支持的能力。此外,当需要生产链的列表,应用程序可以指定能力作为支持的条件。 只有满足需要能力的模块才能被列出来。

目前支持的能力有:

(1) 替换视图Alternative View : 让任何类型的映射生成器(深度、 图像、 红外) 能够转换它的数据, 显得仿佛传感器被放到了另一个位置(被另一个生产节点显示,通常是另外一 个传感器)

(2) 裁剪Cropping :让一个映射生成器(景深、图像、红外)能够输出帧的可选区域而区别 裁剪 于整个帧。 当具备裁剪能力时, 生成的映射的尺寸被减少为适合更低的分辨率。

例如, 一个映射生成器工作在VGA分辨率 (640x480) 应用程序要裁剪在300x200, 下一个像素行从300像素后开始。裁剪在性能提升方面非常有用。

(3) 帧同步Frame Sync :让两个传感器产生帧数据(例如:深度、图像)能够同步他们的帧, 以致他们同时到达。

(4) 镜像Mirror :让生成器能够生成的数据的镜像。如果传感器放在用户面前,传感器捕 捉到的影像被镜像,镜像这时很有用,这样右手就可以以镜像的体型中的左手 出现了。

(5) 姿势检测Pose Detection :让用户生成器认出用户摆出的特定姿势。

(6) 骨骼Skeleton :让用户生成器能够输出用户骨骼数据。这个数据包括骨骼关节的位置, 跟踪骨骼的位置的能力,用户校准的能力。

(7) 用户位置User Position :让深度生成器能够为场景的特定区域而优化输出特定深度映射。

(8) 错误状态Error State:使一个结点在出错的时候能报告它的状态

(9) 锁发现Lock Aware :让节点能够被上下文边界锁定。详细信息,参考在应用和锁节点间共锁发现


 

生成和读取数据

1.生成数据

产生数据的生产节点被称为生成器。数据生成器只等到确定指令要求才实际生成数 据。函数xn::Generator::StartGenerating()用来开始生成。应用程序为了保留配置,可以 通过函数xn::Generator::StopGenerating来停止数据生成,而不必释放节点对象。

2.读取数据

数据生成器还有个输出锁存功能,其在内部储存新数据,直到收到刷新指令UpdateData ,再把最新数据传输上去。当然,OpenNI让应用程序能够等到新数据可用再刷新,指令是函数xn::Generator::WaitAndUpdateData()

在某些情况下,应用程序操作不止一个节点,想等所有节点一起刷新。OpenNI为此

提供了几个函数,根据在UpdateData之前什么应该发生来区分:

xn::Context::WaitAnyUpdateAll(): 一旦任意一个节点有 新数据,那么刷新所有节点数据。

xn::Context::WaitOneUpdateAll(): 一旦这个节点有新 数据, 那么刷新所有节点数据。 (在当几个节点都在生成数据但只有一个节点决定了 应用程序进度时,这个函数特别有用。)

xn::Context::WaitNoneUpdateAll(): 不等待。所有节点都立即刷新。

xn::Context::WaitAndUpdateAll(): 等到所有节点都有新数据是,再刷新他们。 以上四个函数都是在超时两秒后退出。建议使用其中一个加上函数 UpdateAll(),除非你需要刷新特定节点。

在应用和锁节点间共享设备

说白点,不同应用程序对同一设备有不同的配置,不能这个我还没用完,你就来给我改设备的配置,这样就乱了。

OpenNI有两个模式让多个应用能够共享硬件设备:

完全共享(缺省) :在这种模式中,应用程序申明可以处理这个节点的任何配置。 OpenNI 接口让注册者能够回调任何配置改变的函数,所以,当配置发生改变,应 用就被通知(被同一个应用,或者另一个使用相同硬件设备的应用) 

锁配置:这种模式下,应用程序声明它要锁定节点的当前配置。OpenNI 因此不允 许这个节点的”Set”函数被调用。如果节点是硬件设备(或任何其他可以在进程间共享的模块) ,他应该实现” Lock Aware”能力,这样才能跨进程边界加锁。

注意:当一个节点被锁,加锁的应用收到一个锁句柄。不同于使用这个句柄解锁,句柄 用来在不解锁的情况下来改变节点配置(为了不让节点配置被其他应用“偷走”)

Licensing 授权

模块使用授权机制来确保它们只被授权过的应用程序使用。特定的厂商的模块能被 安装在特定的设备上,如果使用该模块的应用程序提供授权才能访问该模块。当OpenNI 在列表中查找合适的生产链,模块能够检查授权列表。如果要求的授权没有注册过,模 块能够隐藏自己,也就是说不返回任何结果,因此也不被算到可能的生产链中。

The Context Object 上下文对象

上下文是指拥有适用OpenNI的应用程序的全部状态的 对象,包括应用程序使用的所有生产链。同一个应用可以生成多个上下文对象,但是上 下文对象之间不能共享信息。上下文在使用前必须首先初始化。这个时候,所有的外挂的模块被载入和解 析。应用程序通过调用shutdown函数来释放上下文对象的内存。

Metadata Objects 元数据对象

OpenNI元数据对象封装一组特定数据相关的属性和该数据绑在一起。例如,深度映射常 见属性是映射的坐标(例如,在 和Y轴的像素点数)。每个产生数据的生成器都有自己 特定的元数据对象。


配置改变

接口的每个配置选项有以下选项组成:

Set函数用来编辑配置 Get函数用来返回当前配置值

注册和注销函数,是能够在选项改变时被回调的函数。


数据生成器

解释

主要函数:

Map Generator 映射生成器

对产生任何映射的数据生成器的基本接口

Output Mode property: 控制按照哪个配置生成映射。

Cropping capability

Alternative Viewpoint capability

Frame Sync capability

Depth Generator 深度生成器

能够产生深度视图的对象。


Get depth map: 返回深度映射。

Get Device Max Depth: 深度生成器的最大可用距离。

Field of View property: 配置传感器的水平和垂直角度值。

User Position capability

Image Generator 图像生成器


产生彩色图像映射的生成器。

Get Image Map: 返回彩色图像映射

Pixel format property

IR Generator 红外生成器


生成红外映射的生成器。

Get IR Map: 返回当前红外映射

Scene Analyzer 场景分析器






映射生成器,能获取原始体感数据并产生有清晰场景标注的映射。

Get Label Map: 返回一个每个像素带有意义标注(比如,体型1,体型2,背景等 等)的映射

Get Floor: 返回平面图坐标系

Audio Generator 语音生成器




产生语音数据的对象。

Get Audio Buffer

Wave Output Modes property: 配置语音输出,包括采样率、通道数和采样精度。

Gesture Generator 手势生成器






能够进行特定肢体或手势的跟踪的对象。

Add/Remove Gesture: 打开或关闭手势。一旦打开,生成器就开始寻找手势。

Get Active Gestures: 返回当前活动的手势名称

Register/Unregister Gesture callbacks

Register/Unregister Gesture change

Hand Point Generator 手心生成器






能跟踪手心的对象。

Start/Stop Tracking: 开始/停止跟踪特定的手(根据手的位置)

Register/Unregister Hand Callbacks: 以下动作将产生手回调:

当出现一个新的手 ;已有的手出现在一个新位置 ;已有的手消失了

User Generator 用户生成器






产生和场景人物相关数据的对象。

Get Number of Users: 返回当前识别的场景中的使用者数量。

Get Users: 返回当前使用者 Get User CoM:返回使用者人群的中心位置 Get User Pixels: 返回显示使用者的像素。输出时一个完整场景像素映射,像素通过 使用者 ID 的标注来显示使用者肢体。 Register/Unregister user callbacks: 以下动作将产生使用者回调: 当新的使用者被识别 ;当已存在的使用者消失

代码实例

1.Basic Functions: Initialize, Create a Node and Read Data 初始化创建一个节点和读取数据

(初始化一个上下文对象,创建并且从单一的深度节点中读取数据)

XnStatus nRetVal = XN_STATUS_OK;

xn::Context context; // Initialize context object

nRetVal = context.Init(); // TODO: check error code

xn::DepthGenerator depth; // Create a DepthGenerator node

nRetVal = depth.Create(context); // TODO: check error code

nRetVal = context.StartGeneratingAll(); // Make it start generating data // TODO: check error code

// Main loop

while (bShouldRun)

{

nRetVal = context.WaitOneUpdateAll(depth); // Wait for new data to be available

if (nRetVal != XN_STATUS_OK)

{

printf("Failed updating data: %s\n",

xnGetStatusString(nRetVal));

continue;

}

const XnDepthPixel* pDepthMap = depth.GetDepthMap(); // Take current depth map

// TODO: process depth map

}

context.Shutdown(); // Clean-up


2.Enumerating Possible Production Chains 枚举可能的生产链

xn::Query query; // Build a query object

nRetVal = query.SetVendor("MyVendor"); // TODO: check error code

query.AddSupportedCapability(XN_CAPABILITY_SKELETON);

xn::NodeInfoList possibleChains;

nRetVal = context.EnumerateProductionTrees(XN_NODE_TYPE_USER, &query, possibleChains, NULL); // Enumerate // TODO: check error code

// No errors so far. This means list has at least one item. Take the first one

xn::NodeInfo selected = *possibleChains.Begin();

nRetVal = context.CreateProductionTree(selected); // Create it // TODO: check error code

xn::UserGenerator userGen;

nRetVal = selected.GetInstance(userGen); // Take the node // TODO: check error code

// TODO: check error code // Now we can start to use it

有时候,应用程序枚举指定节点,会收到 0 个结果。一个显而易见的原因可能是没有模块的实例安装了这个节点类型,尽管会有其它原因,包括模块可能已安装但没有许可,或者请求的硬件设备当前断开连接。

以下代码试图创建一个手生成器节点,并且当枚举失败的时候,检查所有的错误:

xn::EnumerationErrors errors;

xn::HandsGenerator handsGen;

nRetVal = context.CreateAnyProductionTree(XN_NODE_TYPE_HANDS, NULL,

handsGen, &errors);

if (nRetVal == XN_STATUS_NO_NODE_PRESENT)

{

// Iterate over enumeration errors, and print each one

for (xn::EnumerationErrors::Iterator it = errors.Begin(); it != errors.End(); ++it)

{

XnChar strDesc[512];

xnProductionNodeDescriptionToString(&it.Description(), strDesc,

512);

printf("%s failed to enumerate: %s\n",

xnGetStatusString(it.Error()));

}

return (nRetVal);

}

else if (nRetVal != XN_STATUS_OK)

{

printf("Create failed: %s\n", xnGetStatusString(nRetVal));

return (nRetVal);

}


3.Working with Depth, Color and Audio Maps 使用深度、颜色和声音图

以下代码示例创建一个深度生成器,检查是否它能够以 30 帧每秒的速度生成 VGA ,

置它为这种模式,然后从它读取数据,打印出中间像素值:

XnStatus nRetVal = XN_STATUS_OK;

Context context;

nRetVal = context.Init();

// TODO: check error code

// Create a depth generator

DepthGenerator depth;

nRetVal = depth.Create(context);

// TODO: check error code

// Set it to VGA maps at 30 FPS

XnMapOutputMode mapMode;

mapMode.nXRes = XN_VGA_X_RES;

mapMode.nYRes = XN_VGA_Y_RES;

mapMode.nFPS = 30;

nRetVal = depth.SetMapOutputMode(mapMode);

// TODO: check error code

// Start generating

nRetVal = context.StartGeneratingAll();

// TODO: check error code

// Calculate index of middle pixel

XnUInt32 nMiddleIndex =

XN_VGA_X_RES * XN_VGA_Y_RES/2 + // start of middle line

XN_VGA_X_RES/2;

// middle of this line

while (TRUE)

{

// Update to next frame

nRetVal = context.WaitOneUpdateAll(depth);

// TODO: check error code

const XnDepthPixel* pDepthMap = depth.GetDepthMap();

printf("Middle pixel is %u millimeters away\n",

pDepthMap[nMiddleIndex]);

}

// Clean up

context.Shutdown();


4.Working with Audio Generators 使用音频生成器

声音生成器积累数据直到调用 UpdateData()函数,将整个累积的音频缓冲区返回给应用程序。每次调用的音频缓冲区的大小可能不同,应用程序应该每次都调用xn::AudioGenerator::GetDataSize()函数获得当前缓冲区的大小。

以下示例了创建一个音频生成器,配置成 CD 音质,并且从它不断的读取数据:

Context context;

nRetVal = context.Init();

// TODO: check error code

AudioGenerator audio;

nRetVal = audio.Create(context);

// TODO: check error code

XnWaveOutputMode waveMode;

waveMode.nSampleRate = 44100;

waveMode.nChannels = 2;

waveMode.nBitsPerSample = 16;

nRetVal = audio.SetWaveOutputMode(waveMode);

// TODO: check error code

while (TRUE)

{

// Update to next data

nRetVal = context.WaitOneUpdateAll(audio);

// TODO: check error code

// Get the audio buffer

const XnUChar* pAudioBuf = audio.GetAudioBuffer();

XnUInt32 nBufSize = audio.GetDataSize();

// Queue the buffer for playing

}

// Clean up

context.Shutdown();


5.Recording and Playing Data 录制和播放数据

Recording 录制:

// Create a depth generator DepthGenerator depth;

nRetVal = depth.Create(context);

// TODO: check error code

// Start generating

nRetVal = context.StartGeneratingAll();

// TODO: check error code

// Create Recorder

Recorder recorder;

nRetVal = recorder.Create(context);

// TODO: check error code

// Init it

nRetVal = recorder.SetDestination(XN_RECORD_MEDIUM_FILE,

"c:\\temp\\tempRec.oni");

// Add depth node to recording

nRetVal = recorder.AddNodeToRecording(depth, XN_CODEC_16Z_EMB_TABLES);

// TODO: check error code

while (TRUE)

{

// Update to next frame (this will also record that frame)

nRetVal = context.WaitOneUpdateAll(depth);

// TODO: check error code // Do application logic

}

Playing 播放:

播放记录文件,使用 xn::Context::OpenFileRecording()函数。OpenNI 会打开该文件,文件中的每一个节点创建一个模拟节点,然后用记录的配置填充节点。应用程序可以通过调用xn::Context::FindExistingNode()函数来取到应用程序需要的节点, 并正常的使用它们。

注意:被播放器创建的节点是被锁定的,并且不能被改变,配置必须仍然依据被录制的配置。

Context context;

nRetVal = context.Init();

// Open recording

nRetVal = context.OpenFileRecording("c:\\temp\\tempRec.oni");

// TODO: check error code

// Take the depth node (we assume recording contains a depth node)

DepthGenerator depth;

nRetVal = context.FindExistingNode(XN_NODE_TYPE_DEPTH, depth);

// Add regular application logic


6.Node Configuration 节点配置

应用程序通常想在开始流数据前完全的配置节点。由于这个原因,OpenNI 定义了一个流, 在这个流程中可以进行配置, 一旦所有配置被设定, 节点的 xn::Generator::StartGenerating() 函数被调用,开始数据流。

以下代码创建一个深度生成器,配置为 VGA 解析度,30 帧每秒,并启动它:

// Create a DepthGenerator node

xn::DepthGenerator depth;

nRetVal = depth.Create(context);

// TODO: check error code

XnMapOutputMode outputMode;

outputMode.nXRes = 640;

outputMode.nYRes = 480;

outputMode.nFPS = 30;

nRetVal = depth.SetMapOutputMode(outputMode);

// TODO: check error code

// We're done configuring it. Make it start generating data

nRetVal = context.StartGeneratingAll();

// TODO: check error code

// Main loop

while (bShouldRun)

{

// Wait for new data to be available

nRetVal = context.WaitOneUpdateAll(depth);

if (nRetVal != XN_STATUS_OK)

{

printf("Failed updating data: %s\n",

xnGetStatusString(nRetVal));

continue;

}

// Take current depth map

const XnDepthPixel* pDepthMap = depth.GetDepthMap();

// TODO: process depth map

}

英语生词:

alert [ə'lə:t] adj.警觉的, 灵敏的 vt.使意识到, 警惕 n.警戒, 警报

skeleton /ˈskelɪtn; `skɛlətn/骨骼

vendor ['vendə] n.自动售货机, 小贩, 卖方, 供货商

context ['kɔntekst]上下文 the context object上下文对象

Generator ['dʒenəˌreitə] n. 发电机, 发生器

configuration [kənˌfigjə'reiʃən] n [计算机] 配置

implement /ˈɪmplɪmənt; `ɪmpləmənt/ v执行

implementation [ˌimpləmen'teiʃən] n.履行, 落实, 装置

codec 编码译码器

compatibility [kəmˌpætə'biləti] n.兼容 backwards compatibility 向后兼容

Scene [si:n] n.情景, , 

simulate ['simjəˌleit] vt.假装, 模拟, 模仿

detection /dɪˈtekʃn; dɪ`tɛkʃən/ n检测

explicit [ik'splisit] adj显然的

mock /mɒk; mɑk/ adj not real; substitute 非真实的; 模拟的

portability [ˌpəurtə'biləti] n.可移植性, 可携带, 轻便

utility [ju:'tiləti] n.效用, 实用, 公用事业, []应用程序 adj.多效用的,多功能的

replica /ˈreplɪkə; `rɛplɪkə/ n复制品

initialize [i'niʃəˌlaiz] vt. 初始化

Metadata n. []元数据

sensory ['sensəri] adj. 知觉的, 感觉的, 知觉器官的

enumerate [i'nju:məˌreit] vt. , 列举, 枚举

query ['kwiri] 询问

accumulate [ə'kju:mjəˌleit] v.积累, 增加, 聚集

element /ˈelɪmənt; `ɛləmənt/ 元件

prior ['praiə] adj.优先的, 在前的, 更重要的 adv.居先,在前

attribute /əˈtrɪbjuːt; ə`trɪbjᴜt/ n属性

pixel ['piksəl] n.像素

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在信号处理领域,DOA(Direction of Arrival)估计是一项关键技术,主要用于确定多个信号源到达接收阵列的方向。本文将详细探讨三种ESPRIT(Estimation of Signal Parameters via Rotational Invariance Techniques)算法在DOA估计中的实现,以及它们在MATLAB环境中的具体应用。 ESPRIT算法是由Paul Kailath等人于1986年提出的,其核心思想是利用阵列数据的旋转不变性来估计信号源的角度。这种算法相比传统的 MUSIC(Multiple Signal Classification)算法具有较低的计算复杂度,且无需进行特征值分解,因此在实际应用中颇具优势。 1. 普通ESPRIT算法 普通ESPRIT算法分为两个主要步骤:构造等效旋转不变系统和估计角度。通过空间平移(如延时)构建两个子阵列,使得它们之间的关系具有旋转不变性。然后,通过对子阵列数据进行最小二乘拟合,可以得到信号源的角频率估计,进一步转换为DOA估计。 2. 常规ESPRIT算法实现 在描述中提到的`common_esprit_method1.m`和`common_esprit_method2.m`是两种不同的普通ESPRIT算法实现。它们可能在实现细节上略有差异,比如选择子阵列的方式、参数估计的策略等。MATLAB代码通常会包含预处理步骤(如数据归一化)、子阵列构造、旋转不变性矩阵的建立、最小二乘估计等部分。通过运行这两个文件,可以比较它们在估计精度和计算效率上的异同。 3. TLS_ESPRIT算法 TLS(Total Least Squares)ESPRIT是对普通ESPRIT的优化,它考虑了数据噪声的影响,提高了估计的稳健性。在TLS_ESPRIT算法中,不假设数据噪声是高斯白噪声,而是采用总最小二乘准则来拟合数据。这使得算法在噪声环境下表现更优。`TLS_esprit.m`文件应该包含了TLS_ESPRIT算法的完整实现,包括TLS估计的步骤和旋转不变性矩阵的改进处理。 在实际应用中,选择合适的ESPRIT变体取决于系统条件,例如噪声水平、信号质量以及计算资源。通过MATLAB实现,研究者和工程师可以方便地比较不同算法的效果,并根据需要进行调整和优化。同时,这些代码也为教学和学习DOA估计提供了一个直观的平台,有助于深入理解ESPRIT算法的工作原理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值