Kinect程序设计

关键词:kinect   体感 kinect开发
日期:2011-10-18
作者:yangtao
----------------------------------------

一. 简介
Kinect是Xbox360上的体感外设,可以被应用到PC机上,提供动作捕捉、语音识别功能。类似于任天堂的Wii、PS Move,区别在于Kinect无需借助任何外设进行动作捕捉,目前只支持人类动作的捕捉。Kinect设备被应用于游戏行业中的体感游戏。

二. 开发环境
2011年6月,微软发布了Kinect for Windows SDK测试版本,此版本为非商业授权版本,商业授权将在下一个版本中提供。这套SDK的开发环境需求如下:
硬件
1. Kinect for Xbox 360 sensor
2. Xbox 360 Kinect AC Adapter/Power Supply
软件
1. 操作系统必须为Windows7以上版本,Windows8将完全兼容Kinect设备,目前Win7系统分为X64和X86两个版本。
2. 开发工具为Visual Studio 2010 Express以上版本,在开发时需要.NET4.0的支持,VS2010中集成了.NET4.0。
3. 开发语言支持C++、C#和VB.NET,我们这里主要使用C++进行描述。
这套SDK提供给我们的功能主要有骨骼追踪、未加工的深度图像流以及音频功能。在体感游戏开发中,我们主要使用到的是骨骼追踪功能。
搭建开发环境:
1. 安装Win7操作系统
2. 安装VS2010。下载地址为:
http://www.microsoft.com/visualstudio/en-us/products/2010-editions/express
3. 安装Kinect SDK。SDK版本根据操作系统是X86或X64来选择,下载地址为:
http://research.microsoft.com/en-us/um/redmond/projects/kinectsdk/download.aspx
注意:安装Kinect SDK需要确认VS2010开发工具已经正确安装完毕,因为Kinect SDK需要.NET4.0的支持才会正确安装,VS2010中集成了.NET4.0。另外需要注意的是,在安装SDK之前,请拔掉Kinect设备,安装成功后插入设备,系统会自动安装驱动。
完成上述步骤后,连接Kinect设备后会有绿灯闪烁,打开开始菜单,找到SDK目录,运行SDK提供的Sample,验证设备是否能够有效工作。
可能需要用到的资源包:
1. 在进行复杂动作识别时,可能需要配合XNA完成,下载地址:
http://www.microsoft.com/download/en/details.aspx?id=23714
2. 在进行图形图像识别时,可能需要配合OPENCV完成,下载地址:
http://sourceforge.net/projects/opencvlibrary/files/opencv-win/2.3.1/
3. Kinect设备底部拥有麦克风阵列,通过SDK可以获得音频流,如果对声音进行识别,就需要Microsoft Speech Platform软件开发包的配合,下载地址:
http://www.microsoft.com/download/en/details.aspx?id=14373
和语音识别相关的开发包还需下载:
Kinect for Windows Runtime Language Pack
http://go.microsoft.com/fwlink/?LinkId=220942
Microsoft Speech Platform – Server Runtime
http://www.microsoft.com/download/en/details.aspx?id=24974
 前面我们所阐述的全部为微软官方提供的Kinect SDK开发包的开发环境。另外我们在进行Kinect体感设备开发时,还有另一套方案就第三方不被微软承认的OpenNI,详细请参阅附录微软Kinect SDK与OpenNI的区别。

三. Kinect SDK程序设计
下面我们来介绍一下在体感游戏中使用比较多的骨骼追踪功能如何实现。我们使用C++语言进行描述。
1. 在VS2010中创建项目之后,在Project下的Properties中选择VC++ Directories选项,设置包含目录如下:
包含目录中加入 $(MSRKINECTSDK)\inc
库目录中加入   $(MSRKINECTSDK)\lib
这里的MSRKINECTSDK是环境变量,在我们正确安装微软Kinect for Windows SDK后,会在计算机的环境变量中看到。
2. 添加lib库文件。除了指定目录外,我们还需要在Project下的Properties中选择Link选项,在Input中加入MSRKinectNUI.lib
3. 为了使用NUI中的API,我们需要包含MSR_NuiApi.h。注意,在包含这个文件之前,需要确定你已经包含了windows.h,否则MSR_NuiApi.h中很多根据windows平台定义的数据类型及宏都不生效。即:
#include
#include “MSR_NuiApi.h”
4. 初始化NUI。任何想使用微软提供的API来操作Kinect设备,都必须在所有操作之前,调用NUI的初始化函数:
HRESULT NuiInitialize(DWORD dwFlags);
dwFlags参数是以标志位的含义存在的。我们可以使用下面几个值来制定我们打算使用Kinect设备中的哪些内容。
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_DPTH
仅仅使用深度图数据
以上4个标志位,我们可以使用一个,也可以用|操作符将他们组合在一起使用。
一个应用程序对应一个Kinect设备,必须要调用初始化函数一次,并且只能调用一次。如果在这之后又调用一次初始化,势必会引起逻辑错误,即使是两个不同的程序。比如你运行一个SDK的例子,在没关闭它的前提下,再运行一个,那么后运行的就无法初始化成功,但不会影响之前的程序继续运行。另外,开发Kinect程序时,需要记得的是,微软SDK中提供的运行环境在处理Kinect设备传输数据时,是遵循下面3步骤的运行管线的。
 第一阶段只处理彩色和深度数据
 第二阶段处理用户索引并根据用户索引将颜色信息追加到深度图中。
 第三阶段处理骨骼追踪数据
NuiInitialize就是应用程序用通过传递给dwFlags参数具体值,来初始化这个管线中必须的阶段。因此,我们总是现在标志位中指定图像类型,才可以在接下来的环节中去调用NuiImageStreamOpen之类的函数。
好的,现在我们初始化一下NUI,本此描述只对骨骼数据感兴趣,那么标志如何设置?你懂的!
HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_SKELETON);
If(FAILED(hr))
{
  cout<<”failed”;
  return hr;
}
Else
{
  cout<<”successfully”;
}
5. 释放NUI。初始化以后,在我们继续其他深入获取NUI设备的数据之前,先了解一下如何关闭我们的程序与Kinect设备之间的联系。
VOID NuiShutdown();
这个函数,没什么可说的,当我们的程序退出时,都应该调用一下。甚至于,你的程序暂时不使用Kinect设备了,就应该放开对设备的控制权。
6. 打开骨骼追踪。
NuiSkeletonTrackingEnable(hNextFrameEvent, dwFlags);
这个函数是打开骨骼追踪功能,让Kinect设备能够为我们提供人体各关节数据。值得一提的是第一个hNextFrameEvent参数,当一个新帧的骨骼数据到来时,这个hNextFrameEvent事件被触发,我们可以用CreateEvent事先创建一个事件传递给这个参数,这个参数也可以被设定为NULL。
我们在实现这个骨骼追踪功能时,使用了一个线程在不断监听hNextFrameEvent这个事件,当被触发时就调用处理接口来处理从Kinect设备获得的骨骼关节数据。
7. 获得骨骼帧。
NuiSkeletonGetNextFrame(dwMillisecondsToWait,pSkeletonFrame);
dwMillisecondsToWait参数是等待返回下一帧的时间,如果为0,代表立即返回。
pSkeletonFrame参数是NUI_SKELETON_FRAME类型的结构体,它包含一个骨骼帧所包含的所有数据。因此,我们使用是可以这样使用:
NUI_SKELETON_FRAME SkeletonFrame;
         HRESULT hr  =  NuiSkeletonGetNextFrame( 0, &SkeletonFrame );
在获得到一帧数据时,我们需要检查这一帧数据中是否拥有人体骨骼数据,也就是说骨骼数据是否被追踪到。如果追踪到了,我们接下来需要对追踪到的骨骼帧数据进行降噪,使帧之间的骨骼数据尽量平滑。需要用到的函数是:
NuiTransformSmooth(pSkeletonFrame, pSmoothingParams);
pSkeletonFrame参数为上面获得的骨骼数据帧。
pSmoothingParams参数为降噪的一些参数,置为NULL就为默认参数。
8. 获得骨骼帧中各个ID的数据。Kinect设备在一帧当中支持2个人的全身追踪,也就是说,每一个人的全身20个关节数据列为一组骨骼数据,2个人的全身骨骼数据是存在一个骨骼帧中的,他们通过ID来区分具体是哪个人的骨骼关节数据。我们观察一下如下代码段:

for( int i = 0 ; i < NUI_SKELETON_COUNT ; i++ )
{
   if( SkeletonFrame.SkeletonData[i].eTrackingState == NUI_SKELETON_TRACKED )
  {
   WorkingHere(&SkeletonFrame.SkeletonData[i]);
  }
}
NUI_SKELETON_COUNT宏是代表当前可支持的最大人体追踪数量,我们可以看出,每当某个人的ID被NUI_SKELETON_TRACKED到时,就去调用处理函数来处理我们获得的数据。那么SkeletonFrame.SkeletonData[i]数据便是第i个人的骨骼关节数据。我们在使用是可以这样:
NUI_SKELETON_DATA Data;
int HandRightX = Data.SkeletonPositions[NUI_SKELETON_POSITION_HAND_RIGHT].x;
这样我们就实现了对右手骨骼关节的追踪。
9. OK,理论到此为止,下面我们来实践一下吧,贴出代码段如下:
初始化
HRESULT Nui_Init()
{
m_hNextSkeletonEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
HRESULT  hr;
hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX | NUI_INITIALIZE_FLAG_USES_SKELETON);
if( FAILED( hr ) )
{
   return hr;
}
hr = NuiSkeletonTrackingEnable( m_hNextSkeletonEvent, 0 );
if( FAILED( hr ) )
{
return hr;
}
m_hEvNuiProcessStop=CreateEvent(NULL,FALSE,FALSE,NULL);
m_hThNuiProcess=CreateThread(NULL,0,Nui_ProcessThread,NULL,0,NULL);
return hr;
}
 去初始化
 void Nui_UnInit( )
{
      if(m_hEvNuiProcessStop != NULL)
      {
            SetEvent(m_hEvNuiProcessStop);
          if(m_hThNuiProcess != NULL)
          {
              WaitForSingleObject(m_hThNuiProcess,INFINITE);
              CloseHandle(m_hThNuiProcess);
          }
          CloseHandle(m_hEvNuiProcessStop);
      }
      NuiShutdown( );
if( m_hNextSkeletonEvent && (m_hNextSkeletonEvent != INVALID_HANDLE_VALUE))
      {
          CloseHandle(m_hNextSkeletonEvent);
          m_hNextSkeletonEvent = NULL;
      }
}
 获得骨骼帧
 void Nui_GotSkeletonAlert( )
{
  NUI_SKELETON_FRAME SkeletonFrame;
      HRESULT hr = NuiSkeletonGetNextFrame( 0, &SkeletonFrame );
bool bFoundSkeleton = true;
      for( int i = 0 ; i < NUI_SKELETON_COUNT ; i++ )
      {
          if( SkeletonFrame.SkeletonData[i].eTrackingState == NUI_SKELETON_TRACKED )
          {
              bFoundSkeleton = false;
          }
      }
      if( bFoundSkeleton )
      {
          return;
      }
      NuiTransformSmooth(&SkeletonFrame,NULL);
     m_bScreenBlanked = false;
      m_LastSkeletonFoundTime = -1;
      bool bBlank = true;
      for( int i = 0 ; i < NUI_SKELETON_COUNT ; i++ )
      {
          if( SkeletonFrame.SkeletonData[i].eTrackingState == NUI_SKELETON_TRACKED )
   {
    WorkingHere(&SkeletonFrame.SkeletonData[i]);
   }
          bBlank = false;
      }
}
 线程回调
static DWORD WINAPI Nui_ProcessThread(LPVOID pParam)
{
      HANDLE                hEvents[2];
      int                    nEventIdx,t,dt;
      hEvents[1] = m_hNextSkeletonEvent;
      while(1)
      {
          nEventIdx=WaitForMultipleObjects(sizeof(hEvents)/sizeof(hEvents[0]),
hEvents,FALSE,100);
          if(nEventIdx==0)
              break;
              t = timeGetTime( );
          if(m_LastFPStime == -1)
          {
              m_LastFPStime = t;
              m_LastFramesTotal = m_FramesTotal;
          }
          dt = t - m_LastFPStime;
          if( dt > 1000 )
          {
              m_LastFPStime = t;
              int FrameDelta = m_FramesTotal - m_LastFramesTotal;
              m_LastFramesTotal = m_FramesTotal;
          }
          if( m_LastSkeletonFoundTime == -1 )
             m_LastSkeletonFoundTime = t;
          dt  =  t - m_LastSkeletonFoundTime;
          if( dt > 250 )
          {
              if( !m_bScreenBlanked )
              {
                  m_bScreenBlanked = true;
              }
          }
     switch(nEventIdx)
          {
              case 1:
    {
     Nui_GotSkeletonAlert( );
    }
          }
  }
     return (0);
}

四. Kinect SDK原型制作
1. 用上述方法,将获取骨骼数据的功能制作成DLL,需要注意的是,在DLL中应该提供类似信号量的保证线程安全的方法。
2. 在游戏程序当中调用这个DLL,接口方式取决于具体的游戏程序。比如,我们在Unity3D中的所有与图像显示相关的逻辑,需要全部放在Updata函数中,那么我们只需要在DLL中定义一个主动获得骨骼数据的接口,具体识别动作的算法写在Updata函数中;如果我们使用的是其他环境制作的游戏,那么我们可以在DLL中定义一个回调函数,把识别动作的算法写在DLL中,当我们感兴趣的动作发生时,DLL会自动调用我们在程序中实现的回调。
3. 识别某动作的方法。这里我们拿开车动作为例,首先总结开车动作的规律,我们把方向盘的左右转动定为游戏中一个小车的左右转动。双手在某一时刻的空间中的坐标我们是知道的,使用数学方法计算空间中两个点形成的角度。这样我们有了这个角度,在通过角度区间来定制什么时候是左转以及右转。

五. 参考资料
1. Kinect SDK的视频开发教程:
http://channel9.msdn.com/Series/KinectSDKQuickstarts?sort=recent#tab_sortBy_recent
2. Kinect SDK的开发指南:
http://research.microsoft.com/en-us/um/redmond/projects/kinectsdk/guides.aspx
3. Kinect SDK的官方论坛:
http://social.msdn.microsoft.com/Forums/en-US/kinectsdk/threads

六. 附录
1. 微软Kinect SDK与OpenNI的区别
Microsoft`s Kinect SDK(Beta)
优点
 支持音频
 支持马达
 全身追踪
 不需要象OpenNI一样的标定姿势(投降姿势)
 包括头、手、脚和锁骨
 处理关节闭塞更好些
 支持多传感器(多台Kinect)
 简化安装(开发平台搭建更容易)
 当新的视频或深度图有效时,Kinect SDK会有可用事件
 官方承认(Kinect设备为微软产品,微软只承认这套SDK)
缺点
 非商用(商业需要付费授权)
 只能追踪全身(不包含特定的追踪模式:例如只追踪手)
 全身追踪方面
 关节只有坐标数据,没有旋转数据
 只能追踪全身,不包含特定的追踪模式:例如只追踪手或上半身
 和OpenNI相比,看起来更消耗CPU(没有采用适当的基准)
 不包含手势识别系统
 不支持PrimeSense和华硕的WAVI Xtion硬件平台
 开发环境和产品只支持Win7(32位和64位)
 不支持Unity3D游戏引擎
 不支持数据记录或回放到硬盘
 不支持红外线视频数据流
 一个用户被侦测到或用户丢失等,没有事件发生
PrimeSense OpenNI
优点
 可以商用(不需要付费)
 包含手部追踪框架
 包含手势识别框架
 可以自动对齐深度图数据到彩色图数据
 全身追踪
 包含坐标数据和旋转数据
 支持特殊跟踪模式,例如只追踪手和头或上半身
 和微软SDK相比消耗的CPU更少
 支持PrimeSense和华硕的WAVI Xtion硬件平台
 支持多传感器,但是需要安装和枚举
 支持Windows(包括Vista、XP、Win7),Linux系统和苹果操作系统
 自带的代码全面支持Unity3D游戏引擎
 支持数据记录到硬盘或从硬盘回放数据
 支持红外线数据流
 用户被侦测到或用户丢失等,有此类事件发生(提供回调函数供开发者使用)
缺点
 不支持音频
 不支持马达
 全身追踪方面
 缺乏头、手、脚和锁骨方面的关节数据
 需要一个标定姿势(投降姿势)才能开始追踪骨骼
 关闭闭塞没有被估算
 支持多感应器,但是需要安装和枚举
 需要单独安装NITE
 微软不承认OpenNI
结论
微软Kinect SDK在骨骼识别和音频方面有优势。OpenNI似乎更适合做一些带颜色的点云的工作,在非Win7平台来开发商业项目。在手势识别方面,如果你想开发基于上半身或手识别的项目,可以使用OpenNI;如果是全身识别毋庸置疑微软的Kinect SDK是最好的,但是作为代价,你必须自己编写手势识别代码。


欢迎转载,转载请注明出处.

你可能感兴趣的:(Kinect程序设计)