提取颜色数据并用OpenCV显示
http://blog.csdn.net/zouxy09/article/details/8146266
#include <windows.h>
#include <iostream>
#include <NuiApi.h>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char *argv[])
{
Mat image;
image.create(480, 640, CV_8UC3);
//1、初始化NUI
HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR);
if (FAILED(hr))
{
cout<<"NuiInitialize failed"<<endl;
return hr;
}
//2、定义事件句柄
//创建读取下一帧的信号事件句柄,控制KINECT是否可以开始读取下一帧数据
HANDLE nextColorFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
HANDLE colorStreamHandle = NULL; //保存图像数据流的句柄,用以提取数据
//3、打开KINECT设备的彩色图信息通道,并用colorStreamHandle保存该流的句柄,以便于以后读取
hr = NuiImageStreamOpen(NUI_IMAGE_TYPE_COLOR, NUI_IMAGE_RESOLUTION_640x480,
0, 2, nextColorFrameEvent, &colorStreamHandle);
if( FAILED( hr ) )//判断是否提取正确
{
cout<<"Could not open color image stream video"<<endl;
NuiShutdown();
return hr;
}
namedWindow("colorImage", CV_WINDOW_AUTOSIZE);
//4、开始读取彩色图数据
while(1)
{
const NUI_IMAGE_FRAME * pImageFrame = NULL;
//4.1、无限等待新的数据,等到后返回
if (WaitForSingleObject(nextColorFrameEvent, INFINITE)==0)
{
//4.2、从刚才打开数据流的流句柄中得到该帧数据,读取到的数据地址存于pImageFrame
hr = NuiImageStreamGetNextFrame(colorStreamHandle, 0, &pImageFrame);
if (FAILED(hr))
{
cout<<"Could not get color image"<<endl;
NuiShutdown();
return -1;
}
INuiFrameTexture * pTexture = pImageFrame->pFrameTexture;
NUI_LOCKED_RECT LockedRect;
//4.3、提取数据帧到LockedRect,它包括两个数据对象:pitch每行字节数,pBits第一个字节地址
//并锁定数据,这样当我们读数据的时候,kinect就不会去修改它
pTexture->LockRect(0, &LockedRect, NULL, 0);
//4.4、确认获得的数据是否有效
if( LockedRect.Pitch != 0 )
{
//4.5、将数据转换为OpenCV的Mat格式
for (int i=0; i<image.rows; i++)
{
uchar *ptr = image.ptr<uchar>(i); //第i行的指针
//每个字节代表一个颜色信息,直接使用uchar
uchar *pBuffer = (uchar*)(LockedRect.pBits) + i * LockedRect.Pitch;
for (int j=0; j<image.cols; j++)
{
ptr[3*j] = pBuffer[4*j]; //内部数据是4个字节,0-1-2是BGR,第4个现在未使用
ptr[3*j+1] = pBuffer[4*j+1];
ptr[3*j+2] = pBuffer[4*j+2];
}
}
imshow("colorImage", image); //显示图像
}
else
{
cout<<"Buffer length of received texture is bogus\r\n"<<endl;
}
//5、这帧已经处理完了,所以将其解锁
pTexture->UnlockRect(0);
//6、释放本帧数据,准备迎接下一帧
NuiImageStreamReleaseFrame(colorStreamHandle, pImageFrame );
}
if (cvWaitKey(20) == 27)
break;
}
//7、关闭NUI链接
NuiShutdown();
return 0;
}
首先,对Kinect,我们必须要包含下面两个头文件:
#include <windows.h>
#include <NuiApi.h>
1、初始化NUI
HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR);
任何想使用微软提供的API来操作KINECT,都必须在所有操作之前,调用NUI的初始化函数:
HRESULT NuiInitialize(DWORD dwFlags);
dwFlags参数是以标志位的含义存在的。可以使用下面几个值来指定你打算使用NUI中的哪些内容。
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX 提供带用户信息的深度图数据;
NUI_INITIALIZE_FLAG_USES_COLOR 提供色彩图像数据;
NUI_INITIALIZE_FLAG_USES_SKELETON 提供骨骼点数据;
NUI_INITIALIZE_FLAG_USES_DEPTH 提供深度图像数据.
NUI_INITIALIZE_FLAG_USES_AUDIO 提供声音数据;
NUI_INITIALIZE_DEFAULT_HARDWARE_THREAD 初始化默认的硬件线程;
以上的标志位,你可以使用一个,也可以用 | 操作符将它们组合在一起。
另外,Kinect提供了两种处理返回值的方式,就是判断上面的函数是否执行成功。
//这是一种处理返回值的方式
if( FAILED( hr ) )
{
cout<<"NuiInitialize failed"<<endl;
return hr;
}
//这是另一种处理返回值的方式
if(hr == S_OK)
{
cout<<"NuiInitialize successfully"<<endl;
}
2、定义事件句柄
HANDLE nextColorFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
CreateEvent()创建一个windows事件对象,创建成功则返回事件的句柄。事件有两个状态,有信号和没有信号!上面说到了。就是拿来等待新数据的。
CreateEvent函数需要4个参数: 设定为NULL的安全描述符; 一个设定为true的布尔值,因为应用程序将重置事件消息;
一个未指定的事件消息初始状态的布尔值; 一个空字符串,因为事件未命名。
HANDLE colorStreamHandle = NULL; //保存图像数据流的句柄,用以提取数据
3、打开KINECT设备的彩色数据流
hr=NuiImageStreamOpen(NUI_IMAGE_TYPE_COLOR, NUI_IMAGE_RESOLUTION_640x480,0,2,nextColorFrameEvent,&colorStreamHandle);
我们使用这个函数来打开kinect彩色或者深度图的访问通道,当然,其内部原理是通过”流”来实现的,因此,你也可以把这个函数理解为,创建一个访问彩色或者深度图的数据流。
参数:
eImageType [in]
这是一个 NUI_IMAGE_TYPE 枚举类型的值,用来详细指定你要创建的流类型。
比如你要打开彩色图,就使用 NUI_IMAGE_TYPE_COLOR。 要打开深度图,就使用 NUI_IMAGE_TYPE_DEPTH。
能打开的图像类型,必须是你在初始化的时候指定过的。eResolution [in]
这是一个 NUI_IMAGE_RESOLUTION 枚举类型的值,用来指定你要以什么分辨率来打开eImageType(参数1)中指定的图像类别。
假如你在参数eImageType中指定的是彩色图NUI_IMAGE_TYPE_COLOR,那么可以选择2种分辨率:
NUI_IMAGE_RESOLUTION_1280x1024,NUI_IMAGE_RESOLUTION_640x480
如果你在参数eImageType中指定的是深度图NUI_IMAGE_TYPE_DEPTH,那么可以选择3种分辨率
NUI_IMAGE_RESOLUTION_640x480,
NUI_IMAGE_RESOLUTION_320x240,
NUI_IMAGE_RESOLUTION_80x60dwImageFrameFlags_NotUsed
[in] 这是个无用参数,随便给个整数就行了。dwFrameLimit
指定NUI运行时环境将要为你所打开的图像类型建立几个缓冲。最大值是NUI_IMAGE_STREAM_FRAME_LIMIT_MAXIMUM(当前版本为 4)对于大多数啊程序来说,2就足够了。hNextFrameEvent
[in, optional] 一个用来手动重置信号是否可用的事件句柄(event),该信号用来控制KINECT是否可以开始读取下一帧数据。也就是说在这里指定一个句柄后,随着程序往后继续推进,当你在任何时候想要控制kinect读取下一帧数据时,都应该先使用WaitForSingleObject判断一下该句柄,判断是否有数据可拿。phStreamHandle
[out]出参,指定一个句柄的地址。函数成功执行后,将会创建对应的数据访问通道(流),并且让该句柄保存这个通道的地址。也就是说,如果现在创建成功了。那么以后你想读取数据,就要通过这个句柄了。返回值
只有S_OK表示成功打开,错误原因却有很多,比如打开一个没初始化过的数据流;打开一个已被使用的数据流;参数phStreamHandle为NULL等等。自己查阅API手册吧。
4、无限等待新的数据,等到后返回
WaitForSingleObject(nextColorFrameEvent, INFINITE)==0
和刚才说的一样,程序运行都这里,这个事件有信号,就是说有数据,那么程序往下执行,如果没有数据,就会等待。函数第二个参数表示你愿意等多久,具体的数据的话就表示你愿意等多少毫秒,还不来,我就不要了,继续往下走。如果是INFINITE的话,就表示无限等待新数据,直到海枯石烂,一定等到为止。等到有信号后就返回0 。
5、从数据流中拿数据
hr = NuiImageStreamGetNextFrame(colorStreamHandle, 0, &pImageFrame);
从刚才打开数据流的流句柄中得到该帧数据,读取到的数据地址存于pImageFrame。第二个参数表示你延时多少微秒拿数据,0表示,我立刻拿。
如果你没有遇到什么错误的话,那么刚才KINECT就捕获了一副画面,并将该画面的信息保存在一个NUI_IMAGE_FRAME结构中,pImageFrame指向该结构的地址。
pImageFrame包含了很多有用信息,包括:图像类型,分辨率,图像缓冲区,时间戳等等。
6、INuiFrameTexture接口
INuiFrameTexture * pTexture = pImageFrame->pFrameTexture;
一个容纳图像帧数据的对象,类似于Direct3D纹理,但是只有一层(不支持mip-maping)。
其公有方法包含以下:
AddRef—增加一个对象上接口的引用数目;该方法在每复制一个指向该对象上接口的指针时都要调用一次;
BufferLen—获得缓冲区的字节长度;
GetLevelDesc—获得缓冲区的描述;
LockRect—给缓冲区上锁;
Pitch—返回一行的字节数;
QueryInterface—获取指向对象所支持的接口的指针,该方法对其所返回的指针调用AddRef函数;
Release—减少一个对象上接口的引用计数;
UnlockRect—对缓冲区解锁;
7、提取数据帧到LockedRect并锁定数据
pTexture->LockRect(0, &LockedRect, NULL, 0);
提取数据帧到LockedRect,它包括两个数据对象:pitch每行字节数,pBits第一个字节地址。另外,其还锁定数据,这样当我们读数据的时候,kinect就不会去修改它
好了,现在真正保存图像的对象LockedRect我们已经有了,并且也将图像信息写入这个对象了。
8、将数据转换为OpenCV的Mat格式
然后我们就将其保存图像的对象LockedRect的格式,转化为OpenCV的Mat格式,便于我们处理和显示。