欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Kinect学习(三):获取RGB颜色数据

程序员文章站 2022-05-13 15:08:45
...

前言

在前面的文章中介绍了如何搭建Kinect开发环境:Kinect学习(一):开发环境搭建。搭建好环境后,首先要做的当然就是试着读取Kinect中的数据了。

Kinect有三个镜头,中间的是RGB摄像头,左边的是红外线发射器,右边的是红外线CMOS摄像头构成的3D结构光摄像头,用来采集深度数据。彩色摄像头最大支持1280*960分辨率成像,红外摄像头最大支持640*480成像。

接下来就要通过微软提供的SDK来读取Kinect中的彩色摄像头的数据了。

代码

先上代码,里面有注释,后面再详细介绍。

#include <windows.h>
#include <NuiApi.h>
#include <iostream>
#include <opencv2/opencv.hpp>

/* 
几个常用的头文件:
1、NuiApi.h ---包含所有的NUI(自然用户界面) API头文件和定义基本的初始化和函数访问入口。这是我们C++工程的主要头文件,它已经包含了NuiImageCamera.h 和 NuiSkeleton.h。
2、NuiImageCamera.h ---定义了图像和摄像头服务的API,包括调整摄像头的角度和仰角,打开数据流和读取数据流等。
3、NuiSkeleton.h ---骨架有关的API,包括使能骨架跟踪,获取骨架数据,骨架数据转换和平滑渲染等。
4、NuiSensor.h ---音频API,包括ISoundSourceLocalizer接口,用于返回声源的方向(波束形成)和音频的位置。 
*/

using namespace std;
using namespace cv;

int main(int argc, char* argv[])
{
    cv::Mat img;
    img.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;
    }
    cv::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 < img.rows; i++)
                {
                    uchar *ptr = img.ptr<uchar>(i); //第i行的指针

                    //每个字节代表一个颜色信息,直接使用uchar
                    uchar *pBuffer = (uchar*)(LockedRect.pBits) + i * LockedRect.Pitch;
                    for (int j = 0;j < img.cols;j++)
                    {
                        //内部数据是4个字节,0-1-2是BGR,第4个现在未使用
                        ptr[3 * j] = pBuffer[4 * j];
                        ptr[3 * j + 1] = pBuffer[4 * j + 1];
                        ptr[3 * j + 2] = pBuffer[4 * j + 2];
                    }
                }

                cv::imshow("colorImage", img);  //显示图像
            }
            else
            {
                cout << "Buffer length of received texture is bogus\r\n" << endl;
            }

            //5、这帧已经处理完了,所以将其解锁
            pTexture->UnlockRect(0);
            //6、释放本帧数据,准备获取下一帧
            NuiImageStreamReleaseFrame(colorStreamHandle, pImageFrame);
        }
        if (cv::waitKey(20) == 27)
            break;
    }

    //7、关闭NUI连接
    NuiShutdown();


    return 0;
}

运行结果

Kinect学习(三):获取RGB颜色数据

说明

整个程序可以分为以下流程:

  1. 初始化NUI接口;
  2. 定义事件句柄;
  3. 打开Kinect设备的数据流(彩色RGB);
  4. 等待数据更新,若更新完成则进行下一步;
  5. 从数据流中拿出图像数据;
  6. 提取数据帧并锁定数据;
  7. 将数据转换为OpenCV的Mat格式。

1、初始化NUI接口

//1、初始化NUI
HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR);

要使用微软提供的SDK中的SDK来操作Kinect,必须先调用NUI初始化函数。
函数原型为:

HRESULT NuiInitialize(DWORD dwFlags);

dwFlags表示标志位,有以下几种情况:

  • NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX: 提供带用户信息的深度图数据;
  • NUI_INITIALIZE_FLAG_USES_COLOR:提供RGB彩色图像数据;
  • NUI_INITIALIZE_FLAG_USES_SKELETON:提供骨骼点数据;
  • NUI_INITIALIZE_FLAG_USES_DEPTH:提供深度图像数据;
  • NUI_INITIALIZE_FLAG_USES_AUDIO:提供声音数据;
  • NUI_INITIALIZE_DEFAULT_HARDWARE_THREAD:初始化默认的硬件线程;

以上的都各自对应一个标志位,使用时可以使用|将它们组合起来。

注意到,它还返回了一个HRESULT类型的参数,通过它可以判断初始化函数是否执行成功。

if (FAILED(hr))
{
    cout << "NuiInitialize failed" << endl;
    return hr;
}

或者判断是否等于S_OK

if(hr == S_OK)
{
     cout << "NuiInitialize successfully" << endl;
}

2、定义事件句柄

//2、定义事件句柄
//创建读取下一帧的信号事件句柄,控制KINECT是否可以开始读取下一帧数据
HANDLE nextColorFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

该函数会创建一个windows事件对象,创建成功则返回事件的句柄。这里的这个事件是用来判断是否有新数据的。
其中有四个参数:

  • 第一个是安全属性,设定为NULL的安全描述符;
  • 第二个表示设置信号复位方式为自动恢复为无信号状态(FALSE)还是手动恢复为无信号状态(TRUE),设为TRUE,因为后面应用程序会重置事件消息;
  • 第三个是事件消息初始状态的布尔值,为FALSE
  • 最后一个是信号名称,可以直接设置为NULL

    3、打开Kinect设备的彩色图像数据流

//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;
}

使用这个函数可以打开Kinect设备的彩色图或是深度图的访问通道。也可以理解为,创建一个访问彩色图或深度图的数据流。

函数原型:

_Check_return_ HRESULT NUIAPI NuiImageStreamOpen(
    _In_ NUI_IMAGE_TYPE eImageType,
    _In_ NUI_IMAGE_RESOLUTION eResolution,
    _In_ DWORD dwImageFrameFlags,
    _In_ DWORD dwFrameLimit,
    _In_opt_ HANDLE hNextFrameEvent,
    _Out_ HANDLE *phStreamHandle
    );

参数说明:

  1. eImageType:这是一个NUI_IMAGE_TYPE枚举类型的变量,用来指定要创建的数据流的类型。比如,NUI_IMAGE_TYPE_COLOR对应彩色图,NUI_IMAGE_TYPE_DEPTH对应深度图。注意,这里指定的图像的类型,必须是前面初始化是已经指定过的,否则无法打开。
  2. eResolution:这是一个NUI_IMAGE_RESOLUTION枚举类型的变量,用来指定打开图像的分别率,但是由于3D结构光摄像头与RGB摄像头的分辨率不同,所以根据前面eImageType参数指定的图像类型不同,这里也有所不同。如果eImageType指定为彩色图NUI_IMAGE_TYPE_COLOR,那么就有两种分辨率:NUI_IMAGE_RESOLUTION_1280x960NUI_IMAGE_RESOLUTION_640x480;如果eImageType指定为深度图NUI_IMAGE_TYPE_DEPTH,那么就有三种分辨率:NUI_IMAGE_RESOLUTION_640x480NUI_IMAGE_RESOLUTION_320x240NUI_IMAGE_RESOLUTION_80x60
  3. dwImageFrameFlags_NotUsed:看名字就知道了,没有用到这个参数,随便给个数就可以了。
  4. dwFrameLimit:指定NUI运行时环境将要为你所打开的图像类型建立几个缓冲。最大值是NUI_IMAGE_STREAM_FRAME_LIMIT_MAXIMUM,即4。大多数程序中,定为2就足够了。
  5. hNextFrameEvent: 一个用来手动重置信号是否可用的事件句柄(event),该信号用来控制Kinect是否可以开始读取下一帧数据。也就是说在这里指定一个句柄后,随着程序往后继续运行,当你在任何时候想要控制kinect读取下一帧数据时,都应该先使用WaitForSingleObject函数判断一下该句柄,判断是否有数据可拿。
  6. phStreamHandle:函数执行成功后,会创建对应的数据流,并让这个句柄保存其地址。后面可以通过这个句柄来从Kinect读取数据。
  7. 返回值:S_OK表示成功。

4、等待数据更新,若更新完成则进行下一步;

//4.1、无线等待新的数据,等到就返回
if (WaitForSingleObject(nextColorFrameEvent, INFINITE) == 0)
{
...
}

前面也提到了这个函数,如果事件(对应nextColorFrameEvent)有信号,即有数据,那么返回值为0,程序也会往下执行;如果没有数据,就会等待。函数的第二个参数表示等待时间,单位为ms,这里设为INFINITE,表示一直等待。

5、从数据流中拿出图像数据;

//4.2、从刚才打开数据流的流句柄中得到该帧的数据,读取到的数据地址存于pImageFrame中
hr = NuiImageStreamGetNextFrame(colorStreamHandle, 0, &pImageFrame);

colorStreamHandle为前面保存了Kinect设备的彩色信息通道的句柄,这个函数会从colorStreamHandle中取出RGB图像数据,并保存在pImageFrame中。第二个参数,表示延时多久获取数据,直接取为0,就是不等待直接取数据。
成功调用完这个函数之后,从Kinect捕获到的一帧图像,会保存在一个NUI_IMAGE_FRAME结构体中,pImageFrame为指向那个结构体的指针,其中包含了很多信息,如:图像类型,分辨率,图像缓冲区,时间戳等等。

6、提取数据帧并锁定数据;

INuiFrameTexture * pTexture = pImageFrame->pFrameTexture;
NUI_LOCKED_RECT LockedRect;

//4.3、提取数据帧到LockedRect(它包括两个数据对象:pitch每行字节数,pBits第一个字节地址)
//并锁定数据,这样当我们读取数据的时候,kinect就不会去修改它
pTexture->LockRect(0, &LockedRect, NULL, 0);

INuiFrameTexture是一个保存图像帧数据的对象,主要要用到他的下面两个共有方法:

  • LockRect:给缓冲区上锁;
  • UnlockRect:给缓冲区解锁;

因为图像帧是保存在缓冲区的,如果不上锁的话,缓冲区中还有的图像可能会导致Kinect修改要取出的图像。

提取数据帧到LockedRect后,它包含两个数据对象:pitch,每行字节数;pBits,第一个字节地址。

7、将数据转换为OpenCV的Mat格式。

//4.4、确认获得的数据是否有效
if (LockedRect.Pitch != 0)
{
    //4.5、将数据转换为OpenCV的Mat格式
    for (int i = 0; i < img.rows; i++)
    {
        uchar *ptr = img.ptr<uchar>(i); //第i行的指针

        //每个字节代表一个颜色信息,直接使用uchar
        uchar *pBuffer = (uchar*)(LockedRect.pBits) + i * LockedRect.Pitch;
        for (int j = 0;j < img.cols;j++)
        {
            //内部数据是4个字节,0-1-2是BGR,第4个现在未使用
            ptr[3 * j] = pBuffer[4 * j];
            ptr[3 * j + 1] = pBuffer[4 * j + 1];
            ptr[3 * j + 2] = pBuffer[4 * j + 2];
        }
    }

这一部分没什么说的了,就是把LockedRect中的数据取出来,保存为OpenCV支持的Mat格式。

参考资料

  1. https://blog.csdn.net/zouxy09/article/details/8146266
  2. https://blog.csdn.net/timebomb/article/details/7169372

后记

这个笔记总体来说不难,主要是套路,微软官网的文档早就撤了,毕竟用的还是Kinect v1.0的,靠着博客和看看源码大概还能用用。
前段时间直到最近感觉都挺多事情的,很多笔记和写好的代码都没时间去整理,还要加把劲了。这段时间又有世界杯,熬夜看球什么的真的挺“伤”的。

相关标签: kinect