透过 OpneNI 读取 Kinect 深度影像数据

这一篇就来针对如何使用OpenNI 读取微软的 Kinect 的影像数据吧!

而先说明一下,Heresy 这篇文章是使用 Visual C++ 2010,针对目前微软的 Kinect、SensorKinect 的驱动程序,搭配 1.0.0.23 版的 OpenNI 版写的;如果使用其他版本、或是其他支持 OpenNI 的装置,那可能会要做一些对应的修改。同时,在开始阅读这篇文章前,建议也请先参考《在 WIndows 上安装 Kinect(含 MMD 使用 Kinect 简易教学)》来安装 Kinect 和 OpenNI,并确定可以正常运作。

首先,OpenNI 他默认的安装路径是在「C:\Program Files\OpenNI」,要开发 OpenNI 程序所有必要的档案,都会在这里;而在文件夹内,除了「Documentation」里有提供两份文件可以用来当作开发程序的依据外,在「Samples」目录下,也有提供不少范例可以用来参考。

OpenNI 的核心基本上是 C 语言,不过他有提供 C++ 的Wrapper 来当作 C++ 使用;基于个人习惯的关系,Heresy在这边会以 C++ 的形式,来使用 OpenNI。而必要的 header 档,都会在 OpenNI 的「Include」目录内,链接程序时所需的 openNI.lib 这个档案则是在「Lib」里。不过要注意的是,OpenNI 目前在 Windows 环境下只有 32 位的版本、没有 64 位版,所以目前只能编译 32 位的 OpenNI 程序。

而要设定一个使用 OpenNI 的 Visual C++ 项目也很简单,只要在项目「C/C++ \ AdditionalInclude Directories」里加入「$(OPEN_NI_INCLUDE)」、「Linker \ Additional Library Directories」里加上「$(OPEN_NI_LIB)」,并在「Linker \ Additional Dependencies」里加上「OpenNI.lib」,这样就可以了。

而接下来,Heresy 就先以读取 Kinect 的深度影像信息为目标,来写一个 C++ 的范例程序了~他的程序代码如下:

#include <stdlib.h>

#include <iostream>

#include <string>

 

#include <XnCppWrapper.h>

 

using namespacestd;

 

void CheckOpenNIError( XnStatus eResult, string sStatus )

{

  if( eResult != XN_STATUS_OK )

    cerr << sStatus << " Error: " << xnGetStatusString( eResult ) << endl;

}

 

int main( intargc, char**argv )

{

  XnStatus eResult = XN_STATUS_OK;

 

  // 2. initial context

  xn::Context mContext;

  eResult = mContext.Init();

  CheckOpenNIError( eResult, "initialize context" );

 

  // set map mode

  XnMapOutputMode mapMode;

  mapMode.nXRes = 640;

  mapMode.nYRes = 480;

  mapMode.nFPS = 30;

 

  // 3. create depth generator

  xn::DepthGenerator mDepthGenerator;

  eResult = mDepthGenerator.Create( mContext );

  CheckOpenNIError( eResult, "Create depth generator" );

  eResult = mDepthGenerator.SetMapOutputMode(mapMode );

 

  // 4. start generatedata

  eResult = mContext.StartGeneratingAll();

 

  // 5. read data

  eResult = mContext.WaitAndUpdateAll();

  if( eResult == XN_STATUS_OK )

  {

    // 5. get the depthmap

    const XnDepthPixelpDepthMap = mDepthGenerator.GetDepthMap();

    // 6. Do somethingwith depth map

  }

  // 7. stop

  mContext.StopGeneratingAll();

  mContext.Shutdown();

 

  return 0;

}

这个程序的功能,基本上就是去透过OpenNI 读取一张分辨率 640 x 480 的深度信息影像;但是在读取到数据后,并没有针对取得的数据做任何事,所以如果没有问题的话,这个程序是会直接结束,而没有任何产出的。

接下来,就来仔细看程序代码的部分。

1.    Header

首先,要以 C++ 的形式使用 OpenNI 的话,只需要加入「XnCppWrapper.h」这个头文件就好了,不用再 include 其他的档案。而 OpenNI 定义了名为「xn」的 namespace,所有的对象,大多都在这个 namespace 内,而不在 namespace 内的东西,也都有 XN 这个 prefix,所以应该还算满好区分的。

2.    初始化 context

要使用 OpenNI,要先建立一个型别为「xn::Context」的 conext 对象(这里就是「mContext」),用来管理整个 OpenNI 的环境状态以及资源;而在开始使用前,必须要呼叫它的成员函式「Init()」来进行起始化(上方程式码「initial context 」的部分)。在进行起始化的时候,所有 OpenNI 相关的模块会被读取、分析,直到呼叫「Shutdown()」这个函式,才会把所使用的资源释放出来。

3.    建立、设定所需要的 Production Node

在 context 起始化成功后,接下来是要建立所要使用的 production node 了。由于这个范例的目的只是要读取深度传感器的数据,所以这里要建立的就只有「depth generator」一种,他的型别是「xn::DepthGenerator」。  而建立一个 production node 的方法,则是先宣告出他的对象(这里就是「mDepthGenerator」),然后再去呼叫他的「Create()」函式,并把 context 传入,这样就可以了(上方程式码中「createdepth generator 」的部分)。

不过要注意的是,有的时候在建立出 node 后,还需要对这个 node 作一些设定。像在这边,就还必须要透过「SetMapOutputMode()」这个函式,来设定 mDepthGenerator 这个 depth generator 的输出模式;而以 Kinect 来说,是要设定成为 640 x 480、30FPS。

4.    开始产生数据

在必要的 production node(这边只有一个)都建立好了以后,接下来就是开始产生数据(generatedata)了!由于 OpenNI 的概念是所以属于generator 的 production node(名称里有 generator 的都是)在使用时,都会不停地产生数据,所以得透过 context 来统一控制数据读取的开关。

而控制的方法很简单,就是透过 context 的成员函式「StartGeneratingAll()」来开始、并透过「StopGeneratingAll()」停止。在一个 context 执行「StartGeneratingAll()」开始读取后,属于他的 generator node 都会开始产生数据,直到呼叫「StopGeneratingAll()」才会停止。

5.    读取数据

在开始产生数据后,就可以读取各个不同的 production node 的资料了~不过不同类型的 generator 必须要透过不同的函式来读取数据,像这边的 depth generator 就是要用「GetDepthMap()」这个函式,来取得目前的 depth map。而 Depth Generator 取得的数据,会是一个「XnDepthPixel」的 const 指针,指向他实际数据的空间。

不过这边另外要注意的就是,generator 虽然是会不停地读取新的数据,但透过「GetDepthMap()」这类的函式,是有可能会拿到旧的数据的。而为了确保能取得最新的数据,在读取 Generator 的数据前,都必须要先呼叫 context 的 wait / update 这一系列的函式,来进行 node 数据的更新。

这系列的函示有四个:WaitAnyUpdateAll()、WaitOneUpdateAll()、WaitNoneUpdateAll() 和这边所使用的 WiatAndUpdateAll()。这四者都会更新 context 下所有的 node 的数据,差别只在于更新的条件;Heresy 这边所使用的 WiatAndUpdateAll() 会等到所有的 node 都取得新数据后,再统一更新所有的 node 的数据;而WaitAnyUpdateAll() 是等到随便一个 node 有新数据时就会更新、WaitOneUpdateAll() 则是等到指定的 node 有新数据时再更新、WaitNoneUpdateAll() 则是不管有没有新数据就强制更新。基本上,这四个不同的函式就是自己看时机、需求使用了。

6.    处理读取到的数据

前面已经有提过了,Depth Generator 取得的资料,会是一个「XnDepthPixel」的 const 指标、而实际上它就是一个大小是 640 x 480 的一维数组(因为现在的输出模式是 640 x480),基本上可以把它看作一张 640 x480 的灰阶图片,其中每一个点都代表他的在这个位置的深度、型别是「XnDepthPixel」;而他的深度值在 Windows 32 位的平台上,型别应该等同于「unsignedshort」。基本上,这里的深度值越大、代表距离越远(0 则是代表该点深度无法判别),如果透过 OpenNI 的函式,也可以换算出绝对距离,不过在这篇文章暂时不会提到就是了。

在这个范例程序里,Heresy什么事都没有做。如果要额外处理这个深度图的数据的话,只要在「//6. Do something with depth map」那里,读取「pDepthMap」这个指针的数据来做处理就可以了。像如果把直接它的深度信息由 XnDepthPixel 转换为一般的 256 灰阶图输出的话,就会是类似右边的结果;而当然,这样的图意义不大,但是其实这些深度信息还可以拿来做很多应用,这点就看程序开发者怎么发挥了~

(Heresy 本来有想连储存图档一起写,不过由于牵扯到储存图档的话,程序代码会变得比较复杂,所以在这边也就先跳过了)

7.    结束

当读取完数据,不再继续读取数据后,就要把 OpenNI 停下来;而这边为了停止继续产生数据所呼叫的函示,就是之前已经提到过的「StopGeneratingAll()」。而如果完全不打算继续使用 OpenNI 的环境的话,则也要记得呼叫「Shutdown()」这个函式,把 OpenNI 所使用的资源释放出来。

8.    错误侦测

如果仔细看前面的程序代码应该可以发现,Heresy 在大部分的地方都用一个型别是「XnStatus」的变量「eResult」来接 OpenNI 函式的回传值,而实际上,这就是用来判断 OpenNI 的函式是否正确执行的依据;如果一个 OpenNI 函式的回传值式「XN_STATUS_OK」的话,就代表他执行结果是正确的,但是如果不是的话,就代表可能出问题了~而要知道出了什么问题,则可以透过「xnGetStatusString()」这个函式,来取得文字的错误讯息;像上面 Heresy 自己定义的「CheckOpenNIError()」,就是在做这件事的。

这篇 Kinect +OpenNI 的第一个范例,就大概先写到这了。基本上,这篇算是透过抓取深度的范例,来大概解释一下怎么使用OpenNI 里的 map generator 了~而这边的程序也相当单纯,之后还会再慢慢写一些更进阶的应用的。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值