获取kinect的颜色图像和深度图像(网上资料的整合)

在使用OpenNI来驱动读取kinect数据时,我们需要了解context object这个名词。查看了下OpenNI UserGuide文档,简单翻译下这个名词的意思:

  ContextopenNI中一个主要的object,它掌握了OpenNI使用过程中应用程序的全部状态,以及这些状态的prodection chains,一个应用程序有多个context但是这些context之间不能共享信息

    例如一个中间件节点不能使用另一个context的驱动节点。Context在使用前必须被立即初始化,因此此时所有嵌入的模块被下载和分析。为了释放context的内存,应用程序需调用shutdown程序。      

  大概意思就是告诉我们在驱动kinect时,需要用到context这个类,且我们需要安装一定顺序去使用,这与一些常见的库驱动差不多,比如opengl,这些都需要什么初始化啊,设置属性啊等。因此我们只需要直接去看懂他人的一个工程实例就ok了。

  好了,本文参考Heresy的教程中的源码写的。

  在新建好工程文件后,需要包含XnCppWrapper头文件

  使用OpenNI读取颜色图和深度图的步骤如下(这个是程序的核心部分):

  1. 定义一个Context对象,并 调用该对象的Init()方法来进行初始化。

  2. 定义一个XnMapOutputMode格式对象,设置好分图像分辨率和帧率。

  3. 定义颜色图和深度图的节点对象,并用其Create()方法来创建,参数为Context对象.

  4. 设置颜色和深度图的输出模式,调用的方法是SetMapOutputMode();参数为步骤2中定义和设置好了的XnMapOutputMode对象。

  6. 如果深度图和颜色图在一张图上显示,则必须对深度图像进行校正,校正的方法是调用深度图的如下方法:.GetAlternativeViewPointCap().SetViewPoint();

  7. 调用context对象的StartGeneratingAll()来开启设备读取数据开关。

  8. 调用context对象的更新数据方法,比如WaitAndupdateAll()方法。

  9. 定义颜色图和色彩图的ImageMetaData对象,并利用对应的节点对象的方法GetMetaData()将获取到的数据保存到对应的ImageMetaData对象中。

  10. 如果需要将深度图转换成灰度图来显示,则需要自己将深度值转换成0~255的单通道或者多通道数据,然后直接用来显示。

  注意如果没有设置视觉校正,则深度图的显示与颜色图的显示会出现对应不上的情况,后面的实验可以看出这2者的区别,另外对于是否需要设置镜像就要看自己的具体应用场合了。

 

#include <stdlib.h>
#include <iostream>
#include <string>

#include <XnCppWrapper.h>

using namespace std;

void CheckOpenNIError( XnStatus eResult, string sStatus )
{
  if( eResult != XN_STATUS_OK )
    cerr << sStatus << " Error: " << xnGetStatusString( eResult ) << endl;
}

int main( int argc, 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 generate data
  eResult = mContext.StartGeneratingAll();

  // 5. read data
  eResult = mContext.WaitAndUpdateAll();
  if( eResult == XN_STATUS_OK )
  {
    // 5. get the depth map
    const XnDepthPixel*  pDepthMap = mDepthGenerator.GetDepthMap();
    // 6. Do something with depth map
  }
  // 7. stop
  mContext.StopGeneratingAll();
  mContext.Shutdown();

  return 0;
}


这个程序的功能,基本上就是去透过 OpenNI 读取一张分辨率 640 x 480 的深度信息影像;但是在读取到数据后,并没有针对取得的数据做任何事,所以如果没有问题的话,这个程序是会直接结束,而没有任何产出的。
接下来,就来仔细看程序代码的部分。
1. Header
首先,要以 C++ 的形式使用 OpenNI 的话,只需要加入 "XnCppWrapper.h "这个头文件就好了,而 OpenNI 定义了名为xn 的 namespace,所有的对象,大多都在这个 namespace 内,而不在 namespace 内的东西,也都有 XN 这个前缀( prefix)。
2. 初始化 context
要使用 OpenNI,要先建立一个型别为 xn::Context 的 conext 对象(这里就是「mContext」),用来管理整个 OpenNI 的环境状态以及资源;而在开始使用前,必须要呼叫它的成员函式  Init() 来进行初始化。在进行初始化的时候,所有 OpenNI 相关的模块会被读取、分析,直到调用 Shutdown() 这个函式,才会把所使用的资源释放出来。
3. 建立、设定所需要的 Production Node
在 context 初始化成功后,接下来是要建立所要使用的 production node 了。由于这个范例的目的只是要读取深度感应器的数据,所以这里要建立的就只有「depth generator」一种,他的型别是「xn::DepthGenerator」。  而建立一个 production node 的方法,则是先声明出他的对象(这里就是「mDepthGenerator」),然后再去调用他的「Create()」函式,并把 context 传入,这样就可以了(上方程式代码中「create depth generator 」的部分)。 
不过要注意的是,有的时候在建立出 node 后,还需要对这个 node 作一些设定。像在这边,就还必须要透过「SetMapOutputMode()」这个函式,来设定 mDepthGenerator 这个 depth generator 的输出模式;而以 Kinect 来说,是要设定成为 640 x 480、30FPS。
4. 开始产生数据
在必要的 production node(这边只有一个)都建立好了以后,接下来就是开始产生数据(generate data)了!由于 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 的数据,差别只在于更新的条件;

作我在 这边所使用的 WiatAndUpdateAll() 会等到所有的 node 都取得新数据后,再统一更新所有的 node 的数据;

而 WaitAnyUpdateAll() 是等到随便一个 node 有新数据时就会更新、WaitOneUpdateAll() 则是等到指定的 node 有新数据时再更新、WaitNoneUpdateAll() 则是不管有没有新数据就强制更新。基本上,这四个不同的函式就是自己看时机、需求使用了。
6. 处理读取到的数据
前面已经有提过了,Depth Generator 取得的资料,会是一个「XnDepthPixel」的 const 指标、而实际上它就是一个大小是 640 x 480 的一维数组(因为现在的输出模式是 640 x480),基本上可以把它看作一张 640 x480 的灰阶图片,其中每一个点都代表他的在这个位置的深度、型别是「XnDepthPixel」;而他的深度值在 Windows 32 位的平台上,型别应该等同于「unsigned short」。基本上,这里的深度值越大、代表距离越远(0 则是代表该点深度无法判别),如果透过 OpenNI 的函式,也可以换算出绝对距离,不过在这篇文章暂时不会提到就是了。
[align=left]而在这个范例程序里,我什么事都没有做。如果要额外处理这个深度图的数据的话,只要在「// 6. Do something with depth map」那里,读取「pDepthMap」这个指针的数据来做处理就可以了。像如果把直接它的深度信息由 XnDepthPixel 转换为一般的 256 灰阶图输出的话,就会是类似下图的结果;而当然,这样的图意义不大,但是其实这些深度信息还可以拿来做很多应用,这点就看程序开发者怎么发挥了~ 
(我本来有想连储存图档一起写,不过由于牵扯到储存图档的话,程序代码会变得比较复杂,所以在这边也就先跳过了)
获取kinect的颜色图像和深度图像(网上资料的整合)_第1张图片
7. 结束
当读取完数据,不再继续读取数据后,就要把 OpenNI 停下来;而这边为了停止继续产生数据所呼叫的函示,就是之前已经提到过的「StopGeneratingAll()」。而如果完全不打算继续使用 OpenNI 的环境的话,则也要记得呼叫「Shutdown()」这个函式,把 OpenNI 所使用的资源释放出来。
8. 错误侦测
如果仔细看前面的程序代码应该可以发现,我在大部分的地方都用一个型别是「XnStatus」的变量「eResult」来接 OpenNI 函式的回传值,而实际上,这就是用来判断 OpenNI 的函式是否正确执行的依据;

如果一个 OpenNI 函式的回传值式「XN_STATUS_OK」的话,就代表他执行结果是正确的,但是如果不是的话,就代表可能出问题了~而要知道出了什么问题,则可以透过「xnGetStatusString()」这个函式,来取得文字的错误讯息;像上面 我自己定义的「CheckOpenNIError()」,就是在做这件事的。
这篇 Kinect + OpenNI 的第一个范例

你可能感兴趣的:(获取kinect的颜色图像和深度图像(网上资料的整合))