【转】Kinect使用
文章转自http://blog.csdn.net/yangtrees/article/details/16106271
Kinect中深度值最大为4096mm (0x0fff)
微软建议在开发中使用1220mm~3810mm范围内的值。
USHORT realDepth = (depthID & 0xfff8) >> 3; //提取距离信息,高13位
USHORT player = depthID & 0x07 ; //提取ID信息,低3位
在对OpenCV进行赋值时需要将其转化到[0,255]
BYTE b = 255 - static_cast<BYTE>(256 * realDepth / 0x0fff);
Kinect放置的位置会影响生成的图像。例如,Kinect可能被放置在非水平的表面上或者有可能在垂直方向上进行了旋转调整来优化视野范围。在这种情况下,y轴就往往不是相对地面垂直的,或者不与重力方向平行。最终得到的图像中,尽管人笔直地站立,在图像中也会显示出事倾斜的。
Kinect最多可以跟踪两个骨骼,可以最多检测六个人。站立模式可以跟踪20个关节点,坐姿模式的话,可以跟踪10个关节点。
NUI骨骼跟踪分主动和被动两种模式,提供最多两副完整的骨骼跟踪数据。主动模式下需要调用相关帧读取函数获得用户骨骼数据,而被动模式下还支持额外最多四人的骨骼跟踪,但是在该模式下仅包含了用户的位置信息,不包括详细的骨骼数据。也就是说,假如Kinect面前站着六个人,Kinect能告诉你这六个人具体站在什么位置,但只能提供其中两个人的关节点的数据(这两个人属于主动模式),也就是他们的手啊,头啊等等的位置都能告诉你,而其他的人,Kinect只能提供位置信息,也就是你站在哪,Kinect告诉你,但是你的手啊,头啊等具体在什么位置,它就没法告诉你了(这四个人属于被动模式)。
对于所有获取的骨骼数据,其至少包含以下信息:
1)、相关骨骼的跟踪状态,被动模式时仅包括位置数据(用户所在位置),主动模式包括完整的骨骼数据(用户20个关节点的空间位置信息)。
2)、唯一的骨骼跟踪ID,用于分配给视野中的每个用户(和之前说的深度数据中的ID是一个东西,用以区分现在这个骨骼数据是哪个用户的)。
3)、用户质心位置,该值仅在被动模式下可用(就是标示用户所在位置的,当无法检测出Skeleton数据时,就应该显示用户质心位置,因为质心是根据深度图的UserID计算出来的,并不受骨骼信息影响)。
1. 基本设置
1.1 在vs2010项目中,需要设置C++目录
包含目录中加入 $(KINECTSDK10_DIR)\inc;
库目录中加入 $(KINECTSDK10_DIR)\lib\x86
(注意安装Kinect SDK以后,路径不一样了,环境变量名变成KINECTSDK10_DIR)
MSRKINECTSDK是环境变量,正确安装MS KINECT FRO WINDOWS SDK 后,会在计算机中的环境变量中看到。
1.2 添加特定库
除了指定目录外,你还需要在链接器中设置附加依赖项,填入KinectNUI.lib
1.3 头文件
为了使用NUI中的API,首先我们要包含 NuiApi.h,切记,在这之前,要保证你已经包含了windows.h
#include <Windows.h>
#include "NuiApi.h"
切记,在这之前,要保证你已经包含了windows.h否则 Nuiapi中很多根据windows平台定义的数据类型及宏都不生效。
1.4 其他设置
如果要使用Kinect SDK 1.7+中的Inteaction部分控件,除了以上提到的基本设置需要额外加入Kinect toolkit文件夹下的相应的.h、.lib以及dll文件。
PS:
其实最简单的方法就是把Kinect Toolkits中的类似sample Install到目录,一看就知道怎么做的了
2. Nui初始化
接下来,任何想使用微软提供的API来操作KINECT,都必须在所有操作之前,调用NUI的初始化函数
HRESULT NuiInitialize(DWORD dwFlags);
DWORD dwFlags参数:
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX 使用NUI中的带用户信息的深度图数据
NUI_INITIALIZE_FLAG_USES_COLOR 使用NUI中的彩色图数据
NUI_INITIALIZE_FLAG_USES_SKELETON 使用NUI中的骨骼追踪数据
NUI_INITIALIZE_FLAG_USES_DEPTH 仅仅使用深度图数据(如果你自己有良好的场景分析或物体识别算法,那么你应该用这个)
以上4个标志位,你可以使用一个,也可以用 | 操作符将它们组合在一起。例如:
HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR);//只使用彩色图
HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX | NUI_INITIALIZE_FLAG_USES_SKELETON | NUI_INITIALIZE_FLAG_USES_COLOR);//使用带用户信息的深度图/使用用户骨骼框架/使用彩色图
NuiInitialize就是应用程序用通过传递给dwFlags参数具体值,来初始化Kinect SDK处理管线中必须的阶段。因此,我们总是先在标志位中指定图像类型,才可以在接下来的环节中去调用NuiImageStreamOpen之类的函数。如果你初始化的时候没指定NUI_INITIALIZE_FLAG_USES_COLOR,那你以后就别指望NuiImageStreamOpen能打开彩色数据了,它肯定会调用失败,因为没初始化嘛。
初始化以后,在我们继续其他深入获取NUI设备的数据之前,先了解一下如何关闭你的程序与NUI之间的联系。
VOID NuiShutdown();
关于这个函数,没什么可说的,你的程序退出时,都应该调用一下。甚至于,你的程序暂时不使用KINECT了,就放开对设备的控制权,好让其他程序可以访问KINECT。
友情提示使用OpenGL的程序员们,如果你们是在使用glut库,那么不要在glMainLoop()后面调用NuiShutdown(),因为它不会执行,你应该在窗口关闭以及任意你执行了退出代码的时刻调用它。
注意:
一个应用程序对一个KINECT设备,必须要调用此函数一次,并且也只能调用一次。如果在这之后又调用一次初始化,势必会引起逻辑错误(即使是2个不同程序)。比如你运行一个SDK的例子,在没关闭它的前提下,再运行一个,那么后运行的就无法初始化成功,但不会影响之前的程序继续运行。
如果你的程序想使用多台KINECT,那么请使用INuiInstance接口来初始化你的设备。
3. CreateNextFrame Event(是否读取下一帧的控制信号)
一个用来手动重置信号是否可用的事件句柄(event),该信号用来控制KINECT是否可以开始读取下一帧数据。
HANDLE m_hNextVideoFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
HANDLE m_hNextDepthFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
HANDLE m_hNextSkeletonEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
HANDLE m_hEvNuiProcessStop = CreateEvent(NULL,TRUE,FALSE,NULL);//用于结束的事件对象;
也就是说在这里指定一个句柄后,随着程序往后继续推进,当你在任何时候想要控制kinect读取下一帧数据时,
都应该先使用WaitForSingleObject(handleEvent, 0)判断一下该句柄,是否有数据可拿。
CreateEvent()创建一个windows事件对象,创建成功则返回事件的句柄。事件有两个状态,有信号和没有信号!上面说到了。就是拿来等待新数据的。
CreateEvent函数需要4个参数:
设定为NULL的安全描述符;
一个设定为true的布尔值,因为应用程序将重置事件消息;
一个未指定的事件消息初始状态的布尔值;
一个空字符串,因为事件未命名
4. Open Stream(For color & depth stream)
打开对NUI设备的访问通道,只针对彩色数据流和深度数据流。
使用这个函数来打开kinect彩色或者深度图的访问通道,当然,其内部原理是通过"流"来实现的,因此,你也可以把这个函数理解为,创建一个访问彩色或者深度图的数据流。似乎从很久远的时候开始,微软就在windows中开始使用流来访问所有硬件设备了。
HRESULT NuiImageStreamOpen (NUI_IMAGE_TYPE eImageType,
NUI_IMAGE_RESOLUTION eResolution,
DWORD dwImageFrameFlags_NotUsed,
DWORD dwFrameLimit,
HANDLE hNextFrameEvent,
HANDLE *phStreamHandle);
参数:
NUI_IMAGE_TYPE eImageType [in]
这是一个NUI_IMAGE_TYPE 枚举类型的值(对应NuiInitialize中的标志位),用来详细指定你要创建的流类型。比如你要打开彩色图,就使用NUI_IMAGE_TYPE_COLOR。
要打开深度图,就使用NUI_IMAGE_TYPE_DEPTH。能打开的图像类型,必须是你在初始化的时候指定过的。
NUI_IMAGE_RESOLUTION eResolution[in]
这是一个NUI_IMAGE_RESOLUTION 枚举类型的值,用来指定你要以什么分辨率来打开eImageType(参数1)中指定的图像类别。
彩色图NUI_IMAGE_TYPE_COLOR,支持2种分辨率:NUI_IMAGE_RESOLUTION_1280x1024,NUI_IMAGE_RESOLUTION_640x480
深度图NUI_IMAGE_TYPE_DEPTH,支持3种分辨率:NUI_IMAGE_RESOLUTION_640x480, NUI_IMAGE_RESOLUTION_320x240, NUI_IMAGE_RESOLUTION_80x60
DWORD dwImageFrameFlags_NotUsed [in]
一点用没有,你随便给个整数就行了。以后的版本里不知道它会不会有意义。
DWORD dwFrameLimit[in]
指定NUI运行时环境将要为你所打开的图像类型建立几个缓冲。最大值是NUI_IMAGE_STREAM_FRAME_LIMIT_MAXIMUM(当前版本为 4).对于大多数啊程序来说,2就足够了。
HANDLE hNextFrameEvent [in, optional]
就是之前建立的一个用来手动重置信号是否可用的事件句柄(event),该信号用来控制KINECT是否可以开始读取下一帧数据。
也就是说在这里指定一个句柄后,随着程序往后继续推进,当你在任何时候想要控制kinect读取下一帧数据时,都应该先使用WaitForSingleObject判断一下该句柄。
HANDLE phStreamHandle[out] (就是通过这个读取数据)
指定一个句柄的地址。函数成功执行后,将会创建对应的数据访问通道(流),并且让该句柄保存这个通道的地址。也就是说,如果现在创建成功了。
那么以后你想读取数据,就要通过这个句柄了。
5. Skeleton Flag Enable(For skeleton stream)
设置Skeleton Stream跟踪标志位
HRESULTNuiSkeletonTrackingEnable( m_hNextSkeletonEvent, Flag );
Flag:
近景: NUI_SKELETON_TRACKING_FLAG_ENABLE_IN_NEAR_RANGE
坐姿: NUI_SKELETON_TRACKING_FLAG_ENABLE_SEATED_SUPPORT; 只有头肩手臂10个节点;
站姿:flag&~(NUI_SKELETON_TRACKING_FLAG_ENABLE_SEATED_SUPPORT); 有标准的20个节点;
默认值0:站姿和非近景数据位分布如上图。
6.等待新的数据,等到后触发
WaitForSingleObject(nextColorFrameEvent, INFINITE)==0
WAIT_OBJECT_0 == WaitForSingleObject(m_hEvNuiProcessStop, 0)
WAIT_OBJECT_0 == WaitForSingleObject(m_hNextVideoFrameEvent, 0)
WAIT_OBJECT_0 == WaitForSingleObject(m_hNextDepthFrameEvent, 0)
WAIT_OBJECT_0 == WaitForSingleObject(m_hNextSkeletonEvent, 0)
程序运行堵塞在这里,这个事件有信号,就是说有数据,那么程序往下执行,如果没有数据,就会等待。函数第二个参数表示你愿意等多久,具体的数据的话就表示你愿意等多少毫秒,还不来,我就不要了,继续往下走。如果是INFINITE的话,就表示无限等待新数据,直到海枯石烂,一定等到为止。等到有信号后就返回0 。
7. 从流数据获得Frame数据
7.1 For Color & Depth Sream
7.1.1 NuiImageStreamGetNextFrame
HRESULT NuiImageStreamGetNextFrame (HANDLE hStream,
DWORD dwMillisecondsToWait,
CONST NUI_IMAGE_FRAME **ppcImageFrame);
eg:
const NUI_IMAGE_FRAME * pImageFrame = NULL;
HRESULT hr = NuiImageStreamGetNextFrame( h, 0, &pImageFrame );
参数:
HANDLE hStream [in]
前面NuiImageStreamOpen打开数据流时的输出参数,就是流句柄,彩色数据对应彩色流句柄,深度数据对应深度流句柄。
DWORD dwMillisecondsToWait [in]
延迟时间,以微秒为单位的整数。当运行环境在读取之前,会先等待这个时间。
CONST NUI_IMAGE_FRAME **ppcImageFrame [out] 出参,
指定一个 NUI_IMAGE_FRAME 结构的指针,当读取成功后,该函数会将读取到的数据地址返回,保存在此参数中。pImageFrame包含了很多有用信息,包括:图像类型,分辨率,图像缓冲区,时间戳等等。
返回值
同样是S_OK表示成功
7.1.2 INuiFrameTexture接口
INuiFrameTexture * pTexture = pImageFrame->pFrameTexture;
一个容纳图像帧数据的对象,类似于Direct3D纹理,但是只有一层(不支持mip-maping)。
其公有方法包含以下:
AddRef---增加一个对象上接口的引用数目;该方法在每复制一个指向该对象上接口的指针时都要调用一次;
BufferLen---获得缓冲区的字节长度;
GetLevelDesc---获得缓冲区的描述;
LockRect---给缓冲区上锁;
Pitch---返回一行的字节数;
QueryInterface---获取指向对象所支持的接口的指针,该方法对其所返回的指针调用AddRef函数;
Release---减少一个对象上接口的引用计数;
UnlockRect---对缓冲区解锁;
7.1.3 提取数据帧到LockedRect并锁定数据
pTexture->LockRect(0, &LockedRect, NULL, 0);
提取数据帧到LockedRect,它包括两个数据对象:pitch每行字节数,pBits第一个字节地址。另外,其还锁定数据,这样当我们读数据的时候,kinect就不会去修改它。
好了,现在真正保存图像的对象LockedRect我们已经有了,并且也将图像信息写入这个对象了。
7.1.4 将数据转换为OpenCV的Mat格式
然后将其保存图像的对象LockedRect的格式,转化为OpenCV的Mat格式。
BYTE* pBuffer = (BYTE*) LockedRect.pBits;
Mat colorImg(COLOR_HIGHT,COLOR_WIDTH,CV_8UC4,pBuffer);
Mat depthImg(DEPTH_HIGHT,DEPTH_WIDTH,CV_16U,pBuffer);
注意:
对于INuiFrameTexture * pTexture = pImageFrame->pFrameTexture;获得的数据彩色数据和深度数据的规格是不一样的!!!
彩色数据:单位数据是32位,对应BGRA
深度数据:单位数据是16位。
深度数据的取值还有另一种方式:
INuiFrameTexture* pDepthImagePixelFrame; BOOL nearMode = TRUE; m_pNuiSensor->NuiImageFrameGetDepthImagePixelFrameTexture(m_pDepthStreamHandle, &pImageFrame, &nearMode, &pDepthImagePixelFrame); INuiFrameTexture * pTexture = pDepthImagePixelFrame; NUI_LOCKED_RECT LockedRect; pTexture->LockRect(0, &LockedRect, NULL, 0 );