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

Kinect V2开发(4)抠图

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

实际上,除了深度图像、彩色图像、红外线图像这三种算是原始图像数据之外,Kinect还提供一种经过分析的图像数据——BodyIndex,直译过来是人物索引的意思。
简单来说,就是在图像中对人物相关的数据进行了标记,把人体和背景区分开来了,我们可以用这个数据还做简单的位置检测,或者抠图。
1.人体轮廓表示
BodyIndex数据的获取方法也和其他数据一样,IBodyIndexFrameSourceIBodyIndexFrameReaderIBodyIndexFrame这样三层,但是它读取到的画面中每一个像素是一位(BYTE),代表的是这个点的人的编号。因为Kinect V2最多只能追踪6个人,所以实际上这个编号只有0-5号是有用的,超过这个范围的话,代表这个点不是人体。
效果图如下:
Kinect V2开发(4)抠图
代码如下:

using namespace std;
using   namespace   cv;
int main(void)
{
    // 1a.获取感应器
    IKinectSensor* pSensor = nullptr;
    GetDefaultKinectSensor(&pSensor);

    // 1b. 打开感应器
    pSensor->Open();

    // 2a. 取得BodyIndex数据
    IBodyIndexFrameSource* pFrameSource = nullptr;
    pSensor->get_BodyIndexFrameSource(&pFrameSource);

    // 2b. 取得BodyIndex数据的描述信息(宽、高)
    int        iWidth = 0;
    int        iHeight = 0;
    IFrameDescription* pFrameDescription = nullptr;
    pFrameSource->get_FrameDescription(&pFrameDescription);
    pFrameDescription->get_Width(&iWidth);
    pFrameDescription->get_Height(&iHeight);
    pFrameDescription->Release();
    pFrameDescription = nullptr;

    // 3a. 打开BodyIndex数据阅读器
    IBodyIndexFrameReader* pFrameReader = nullptr;
    pFrameSource->OpenReader(&pFrameReader);

    IBodyIndexFrame* pFrame = nullptr;

    // 建立图像矩阵,mBodyIndexImg用来存储图像数据
    Mat mBodyIndexImg(iHeight, iWidth, CV_8UC3);
    namedWindow("BodyIndexImage");

    // color array
    Vec3b   color[7] = { Vec3b(0,0,255),Vec3b(0,255,255),Vec3b(255,255,255),Vec3b(0,255,0),Vec3b(255,0,0),Vec3b(255,0,255),Vec3b(0,0,0) };

    // 主循环
    while (1)
    {
        // 4a. 取得最新数据
        if (pFrameReader->AcquireLatestFrame(&pFrame) == S_OK)
        {
            // 4c. 把数据存入图像矩阵中
            UINT uSize = 0;
            BYTE* pBuffer = nullptr;
            pFrame->AccessUnderlyingBuffer(&uSize, &pBuffer);
            for (int y = 0; y < iHeight; ++y)
            {
                for (int x = 0; x < iWidth; ++x)
                {
                    int uBodyIdx = pBuffer[x + y * iWidth];//0-5代表人体,其他值代表背景
                    if (uBodyIdx < 6)
                        mBodyIndexImg.at<Vec3b>(y, x) = color[uBodyIdx];
                    else
                        mBodyIndexImg.at<Vec3b>(y, x) = color[6];
                }
            }
            imshow("Body Index Image", mBodyIndexImg);

            // 4e. 释放变量pFrame
            pFrame->Release();
        }

        if (waitKey(30) == VK_ESCAPE) {
            break;
        }
    }

    // 3b. 释放变量pFrameReader
    pFrameReader->Release();
    pFrameReader = nullptr;

    // 2c.释放变量pFramesSource
    pFrameSource->Release();
    pFrameSource = nullptr;

    // 1c.关闭感应器
    pSensor->Close();

    // 1d.释放感应器
    pSensor->Release();
    pSensor = nullptr;

    return 0;
}

color array建立的是颜色表,储存的是7种颜色,包括对应0-5号人体编号的颜色以及背景颜色。
另外,在4c那一步,和以前的步骤不太一样,是先通过 AccessUnderlyingBuffer()读取原始的BodyIndex数据,然后再通过两层循环扫过画面里面每一个像素点,把代表人体的点筛选出来,然后从color array里面取出对应的颜色,填到OpenCV显示的图像里面。

2.抠图
读取BodyIndex数据可以将环境与人体区分开来,利用这个特点,我们可以将前景中的人合成到静态图片当中,和PS中的抠图类似,但是抠出来的人像是动态的。
BodyIndex的数据用的是深度数据,只能用来判断是否是人体,而Color中的图像只能显示不能做其他处理,用的是彩色凸显数据,所以在这个地方需要让彩色图像数据和深度数据进行配合。
彩色图像帧的分辨率是1920*1080,深度帧的分辨率是512*424,我们需要利用ICoordinateMapper这个类,将彩色图像中的点转换到深度图的坐标系中。
Kinect V2开发(4)抠图
事实上,Kinect V2里面有三种不同的坐标系统,包括:
1. 彩色空间坐标系统(Color Space) ——ColorSpacePoint(x,y)
2. 深度空间坐标系统(Depth Space) ——DepthSpacePoint(x,y)
3. 摄像头空间坐标系统(Camera Space)——CameraSpacePoint(x,y,z)
其中,彩色图像就是彩色空间坐标系统,红外线图像、深度图像以及BodyIndex图像都使用深度空间坐标系统,这两个坐标系统都是2维的,就是以左上角为原点,往右是+X,往下是+Y,单位是像素。摄像头坐标系则是以感应器为原点的3维空间坐标系统,单位是米(m),做人体骨架追踪需要用到这个坐标系统。
针对这三个不同的坐标系统,微软提供了多个不同的函数,可以用来做单点、多点或是整个画面的转换。
Kinect V2开发(4)抠图
在这里我们用的是将彩色空间坐标系转换到深度空间坐标系的函数
Kinect V2开发(4)抠图
第一个参数depthDataPointCount代表的是深度图像数据的个数,基本上就是512*424,第二个参数depthFrameData代表的是深度图像数据,格式是 UINT16*,是使用 CopyFrameDataToArray() 复制出来的阵列。
depthPointCount则是彩色图像的像素点数,是 1920 * 1080,depthSpacePoints就是用来存贮转换结果的阵列,类型是DepthSpacePoint
这样转换完成之后, depthSpacePoints就会是 1920 * 1080 个DepthSpacePoint,而每个DepthSpacePoint 记录的就是该点对应到深度空间坐标系的位置。

转换过程实际上就是:
1. 获取彩色图像、深度图像以及人物轮廓
2. 把彩色图像转换到深度图像坐标系中
3. 判断某一点是不是人体
4. 如果是,就取出来替换掉背景

效果图:
Kinect V2开发(4)抠图

#include <iostream>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <Kinect.h>

using namespace std;
using namespace cv;

int main(void)
{
    // 1a.获取感应器
    IKinectSensor* pSensor = nullptr;
    GetDefaultKinectSensor(&pSensor);
    // 1b. 打开感应器
    pSensor->Open();
/****************2.打开深度图像阅读器************************/
    // 取得深度数据
    IDepthFrameSource* pDepthSource = nullptr;
    pSensor->get_DepthFrameSource(&pDepthSource);
    // 取得深度数据的描述信息(宽、高)
    int        iDepthWidth = 0;
    int        iDepthHeight = 0;
    IFrameDescription* pDepthDescription = nullptr;
    pDepthSource->get_FrameDescription(&pDepthDescription);
    pDepthDescription->get_Width(&iDepthWidth);
    pDepthDescription->get_Height(&iDepthHeight);
    // 打开深度数据阅读器
    IDepthFrameReader* pDepthReader = nullptr;
    pDepthSource->OpenReader(&pDepthReader);    
    pDepthDescription->Release();
    pDepthDescription = nullptr;
    // 释放变量pDepthSource
    pDepthSource->Release();
    pDepthSource = nullptr;

/******************3.打开彩色图像阅读器**********************/
    // 取得彩色数据
    IColorFrameSource* pColorSource = nullptr;
    pSensor->get_ColorFrameSource(&pColorSource);
    // 取得彩色数据的描述信息(宽、高)
    int        iColorWidth = 0;
    int        iColorHeight = 0;
    IFrameDescription* pColorDescription = nullptr;
    pColorSource->get_FrameDescription(&pColorDescription);
    pColorDescription->get_Width(&iColorWidth);
    pColorDescription->get_Height(&iColorHeight);
    // 打开彩色数据阅读器
    IColorFrameReader* pColorReader = nullptr;
    pColorSource->OpenReader(&pColorReader);
    pColorDescription->Release();
    pColorDescription = nullptr;
    // 释放变量pColorSource
    pColorSource->Release();
    pColorSource = nullptr;

/*******************4.打开人体索引的阅读器*******************/
    // 取得BodyIndex数据
    IBodyIndexFrameSource* pBodyIndexSource = nullptr;
    pSensor->get_BodyIndexFrameSource(&pBodyIndexSource);
    // 取得深度数据的描述信息(宽、高)
    int        iBodyIndexWidth = 0;
    int        iBodyIndexHeight = 0;
    IFrameDescription* pBodyIndexDescription = nullptr;
    pBodyIndexSource->get_FrameDescription(&pBodyIndexDescription);
    pBodyIndexDescription->get_Width(&iBodyIndexWidth);
    pBodyIndexDescription->get_Height(&iBodyIndexHeight);
    // 打开深度数据阅读器
    IBodyIndexFrameReader* pBodyIndexReader = nullptr;
    pBodyIndexSource->OpenReader(&pBodyIndexReader);
    pBodyIndexDescription->Release();
    pBodyIndexDescription = nullptr;
    // 释放变量pBodyIndexSource
    pBodyIndexSource->Release();
    pBodyIndexSource = nullptr;

/*************5.为各种图像建立buffer,准备坐标转换*************/
    UINT    iColorDataSize = iColorHeight * iColorWidth;
    UINT    iDepthDataSize = iDepthHeight * iDepthWidth;
    UINT    iBodyIndexDataSize = iBodyIndexHeight * iBodyIndexWidth;
    //获取背景图并调整至彩色图像的大小
    Mat temp = imread("test.jpg"), background;               
    resize(temp, background, Size(iColorWidth, iColorHeight));   
    //开启mapper                                                          
    ICoordinateMapper   * myMaper = nullptr;                
    pSensor->get_CoordinateMapper(&myMaper);
    //准备buffer
    Mat ColorData(iColorHeight, iColorWidth, CV_8UC4);        
    UINT16  * DepthData = new UINT16[iDepthDataSize];
    BYTE    * BodyData = new BYTE[iBodyIndexDataSize];
    DepthSpacePoint * output = new DepthSpacePoint[iColorDataSize];

/***********6.把各种图像读进buffer里,然后进行坐标转换以及替换**************/

    while (1)
    {
        //读取color图
        IColorFrame * pColorFrame = nullptr;
        while (pColorReader->AcquireLatestFrame(&pColorFrame) != S_OK);   
        pColorFrame->CopyConvertedFrameDataToArray(iColorDataSize * 4, ColorData.data, ColorImageFormat_Bgra);
        pColorFrame->Release();

        //读取depth图
        IDepthFrame * pDepthframe = nullptr;
        while (pDepthReader->AcquireLatestFrame(&pDepthframe) != S_OK);   
        pDepthframe->CopyFrameDataToArray(iDepthDataSize, DepthData);
        pDepthframe->Release();

        //读取BodyIndex图
        IBodyIndexFrame * pBodyIndexFrame = nullptr;                       
        while (pBodyIndexReader->AcquireLatestFrame(&pBodyIndexFrame) != S_OK);
        pBodyIndexFrame->CopyFrameDataToArray(iBodyIndexDataSize, BodyData);
        pBodyIndexFrame->Release();

        //复制一份背景图来做处理
        Mat copy = background.clone();                  
        if (myMaper->MapColorFrameToDepthSpace(iDepthDataSize, DepthData, iColorDataSize, output) == S_OK)
        {
            for (int i = 0; i < iColorHeight; ++i)
                for (int j = 0; j < iColorWidth; ++j)
                {
                    //取得彩色图像上包含对应到深度图上的坐标的一点
                    DepthSpacePoint tPoint = output[i * iColorWidth + j];    
                    //判断这个点是否合法
                    if (tPoint.X >= 0 && tPoint.X < iDepthWidth && tPoint.Y >= 0 && tPoint.Y < iDepthHeight)  
                    {
                        //取得彩色图上那点对应在BodyIndex里的值
                        int index = (int)tPoint.Y * iDepthWidth + (int)tPoint.X; 
                        //判断出彩色图上某点是人体,就用替换背景图上对应点
                        if (BodyData[index] <= 5)                  
                        {
                            Vec4b   color = ColorData.at<Vec4b>(i, j);
                            copy.at<Vec3b>(i, j) = Vec3b(color[0], color[1], color[2]);
                        }
                    }
                }
            imshow("Background Remove", copy);
        }
        if (waitKey(30) == VK_ESCAPE) {
            break;
        }
    }
    delete[] DepthData;         
    delete[] BodyData;
    delete[] output;

    // 1c.关闭感应器
    pSensor->Close();
    // 1d.释放感应器
    pSensor->Release();
    pSensor = nullptr;

    return 0;
}

参考文档
官方文档 Kinect for Windows C++ Reference
Hersey博客 K4W v2 C++ Part 5:簡單的去背程式

相关标签: kinect