KINECT来了——解析SDK(MS SDK 2)

NUI图像数据流概述

NUI的流数据是通过连续静态图像序列传递的。在上下文初始化阶段,应用程序将识别需要读取的流数据,并对其进行附加的流相关设置,包括数据解析度、图像类型、用于存储输入帧的缓冲区数量等内容。在应用程序检索并释放相关帧之前,如果运行时数据占满了缓冲区,那么系统将自动丢弃最旧的帧并重用缓冲区,也就是说,帧数据是可被丢弃的。同时系统最多允许请求四个缓冲区,而在大多数应用情形下通常只需要其中的两个。应用程序可通过API获取如下类型的图像信息:彩色图像数据、深度图像数据、用户分割数据等。下面将分别对上述三种类型的数据进行一些说明。

彩色图像数据:系统提供两种格式的彩色图像数据,包括32位X8R8G8B8格式的sRGB位图数据和16位的UYVY格式的YUV位图数据。由于两者实际来自同一图像数据,因此两种格式的最终图像实际上没有任何差别。不过后者要求图像保持640x480的分辨率和15FPS的帧率,同时内存需求更小。应注意,由于系统采用USB连接,传感器层会首先将1280x1024分辨率的Bayer彩色滤波马赛克图像压缩并转换为RGB格式传输,运行时系统再对该数据进行解压缩操作。上述特性可以保证数据帧率可达30FPS,但解码操作会损失一定的图像精度。

深度图像数据:深度图像帧中的每一个像素点都表示从摄像头所在平面到视野内最近物体的笛卡尔坐标系距离,单位为毫米。系统目前支持630x480、320x240、80x60三种规格的深度图像帧。应用程序可根据深度图像数据跟踪人物动作、识别并忽略背景物体。深度图像数据含有两种格式,其一是唯一表示深度值:那么像素的低12位表示一个深度值,高4位未使用;其二是既表示深度值又含有人物序号,则低三位保存人物序号,其余数据位表示深度值。应注意如果获取的深度值为0,则说明物体距离摄像头过近或过远以致超出了设备规格。

用户分割数据:SDK beta目前支持读取两个用户的分割映射数据,其数据帧的相关像素分别记录了用户的序号。尽管用户分割数据是独立生成的数据流,在实际应用中仍可以将深度数据和用户分割数据整合成一个帧,其中像素值的高13位保存了深度值,低三位保存用户序号,其中序号为0则表示无用户,1和2分别表示两个不同的用户。在实际应用中,应用程序往往利用用户分割数据在深度图像和原始彩色图像中获取ROI感兴趣信息。

基本编程模型

基于NUI API的编程模型本质上就是获取传感器图像数据的过程。应用程序往往通过相关代码首先将图像的最后一帧读入至缓冲区中,如果该帧已预备好,那么其将进入缓冲区,如果帧数据尚未就绪,代码仍可选择是否继续挂起等待或暂时释放并稍后重试。NUI摄像头API绝对不会多次传递相同的图像数据。框架包含的基本编程模型如下:

1、POLLing模型,该模型较为基础易用。首先应开启图像数据流,然后请求并设置等待下一帧的时间,范围允许从0到无穷大,单位为毫秒;如果帧数据尚未就绪,则系统将等待刚才指定的时间然后返回。如果帧数据成功返回,则应用程序可请求下一帧数据并在同一线程执行其它操作。通常一个C++应用程序应调用NuiImageStreamOpen函数首先启动一个彩色或深度数据流,并忽略可选事件。托管代码则需调用ImageStream.Open方法。请求彩色或深度图像帧的C++函数为NuiImageStreamGetNextFrame,C#为ImageStream.GetNextFrame方法。

2、事件模型,事件模型允许将获取骨骼帧的功能精确、灵活地集成入应用程序引擎。在该模型中,C++程序首先调用NuiImageStreamOpen函数并传入一个事件句柄。每当一个新的图像帧数据可用时,事件信号将被触发。任何相关的等待线程将被唤醒并通过调用NuiImageGetNextFrame函数获取骨骼信息,与此同时事件将被系统重置。托管代码应绑定Runtime.DepthFrameReady和Runtime.ImageFrameReady事件到相关的处理函数,当新数据可用时,处理函数可调用ImageStream.GetNextFrame获取该数据。

NUI骨骼跟踪应用

NUI还包括一个Skeleton骨骼跟踪模块,该部分提供最多两名用户的详细位置和朝向信息。骨骼跟踪的输出是一个点集,称作Skeleton Positions,该点集表示了一个完整的人体骨骼信息,如下图所示。

[singlepic id=44 w=320 h=240 mode=watermark float=center]

骨骼位置信息表示了用户当前的位置和姿态,如果要使用骨骼跟踪功能,应用程序应在NUI初始化阶段设置相关内容。NUI骨骼数据获取的方式与NUI Image部分基本一致,其基本编程模型也包括Polling和Event两种。前者的C++函数为NuiSkeletonGetNextFrame,托管函数为SkeletonEngine.GetNextFrame;后者的C++句柄绑定操作由NuiSkeletonTrackingEnable完成,并在处理线程内调用NuiSkeletonGetNextFrame;C#则使用Runtime.SkeletonFrameReady绑定事件,然后调用SkeletonEngine.GetNextFrame获取相关信息。

骨骼跟踪模块通过深度数据计算地板裁切面,其基本方法将在后文进行介绍。如果应用程序在NUI初始化时开启了骨骼跟踪功能,则其每处理完一套深度数据则就放出相应的骨骼数据信号,而无论该深度数据中是否真的包含骨骼信息。应用程序使用地板裁切面数据获取骨骼框架,返回的骨架信息将携带一个时间戳以和相关的深度信息进行匹配。

NUI骨骼跟踪分主动和被动两种模式,提供最多两副完整的骨骼跟踪数据。主动模式下需要调用相关帧读取函数获得用户骨骼数据,而被动模式下还支持额外最多四人的骨骼跟踪,但是在该模式下仅包含了用户的位置信息。对于所有获取的骨骼数据,其至少包含以下信息:

1、相关骨骼的跟踪状态,被动模式时仅包括位置数据,主动模式包括完整的骨骼数据。

2、唯一的骨骼跟踪ID,用于分配给视野中的每个用户。

3、用户质心位置,该值仅在被动模式下可用。

4、对于主动模式下的骨骼跟踪数据,还包括用户完整的骨骼数据。

5、对于被动模式下的骨骼跟踪数据,仅包括用户位置信息,不包括详细的骨骼数据。

NUI坐标变换原理

深度图像空间:这是一个仅包含物体到传感器法平面的深度数据的投影空间,其z值是唯一有效的,而x、y值仅仅是进行了插值计算的结果,其数据实际并无任何物理意义;

骨骼空间:用户骨骼位置使用x、y、z三维坐标表示,与深度图像空间坐标系不同的是,该空间的单位为米,且是一个传感器朝向为z轴正向的右手系。应注意,骨骼空间坐标系与Kinect位置息息相关,如果传感器被放置在一个非水平面上,那么计算得到的坐标系可能并非是标准形式,最终的用户数据也有可能是倾斜的。

地板裁剪面的确定:骨骼数据均需要包含一个地板裁剪面向量,该向量保存了与地板平面方程有关的系数coefficients,骨骼跟踪模块通过地板裁剪平面除去背景,并将用户图像分割出来。对于平面的一般式方程Ax+By+Cz+D=0,该方程经过规范化即D值实际是指传感器到地板平面的高度值。如果地板不可见,那么地板裁剪平面向量实际为0。地板裁剪面向量值可在NUI_SKELETON_FRAME结构的vFloorClipPlane成员中获取。托管代码则保存在SkeletonFrame.FloorClipPlane域中。

骨骼镜像:默认的用户图像实际上是一个镜像数据,也就是说该数据表示了用户当前面朝屏幕内部。然而有时确实需要图像面向用户本人一侧,那么需要确定一个镜像变换矩阵以达到此类要求。