K4W v2 C++ Part 4:讀取人體位置(Body Index)

| | 0 Comments| 11:10
Categories:

之前已經寫過《使用 OpenCV 顯示深度影像》和《讀取彩色影像與紅外線影像》,基本上,算是把把 Kinect 能拿到的影像資料的原始資料,都介紹過了。

而實際上,除了深度、彩色、紅外線影像這些算是原始資料外,Kinect for Windows SDK 能提供的影像資料,還有一種,那就是經過分析的結果:Body Index。

Body Index 在 K4W SDK v2 裡面,他的 frame source 的介面是 IBodyIndexFrameSourceMSDN),基本上是用來代表人體在深度畫面中,所佔位置的影像資料。在 K4W SDK 目前的系統下,最多能追蹤六個人,而 Body Index 就是用來告訴程式開發者,現在這六個人個別在畫面裡的哪個位置的。

而這資料能用來幹嘛呢?最直接的用法,就是在程式執行時,可以讓操作只知道自己在感應器的視角裡的樣子、確定偵測到的是對的。再來,對於開發者來說,也可以用來做操作者的簡單的位置偵測、或是去背的動作。

它的使用方法基本上和其他的影像類型的資料一樣,都是 Frame Source – Frame Reader – Frame 這樣的三層式的架構;而這三層的介面,分別是:

  • IBodyIndexFrameSource
  • IBodyIndexFrameReader
  • IBodyIndexFrame

而他所讀取出來的畫面,每個像素則是一個 BYTE(實際上是 unsigned char),代表的是該點的人的編號;由於 K4W SDK v2 只能追蹤六個人,所以這編號只有 0 – 5 是有用的,如果他的值不再這個範圍內的話,則代表這點是背景、不是正在被追蹤的人體。

如果要用 OpenCV 來顯示的話,程式大致上可以寫成下面的樣子:

// Standard Library
#include <iostream>

// OpenCV Header
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

// Kinect for Windows SDK Header
#include <Kinect.h>

using namespace std;

int main(int argc, char** argv)
{
    // 1a. Get default Sensor
    IKinectSensor* pSensor = nullptr;
    GetDefaultKinectSensor(&pSensor);

    // 1b. Open sensor
    pSensor->Open();

    // 2a. Get frame source
    IBodyIndexFrameSource* pFrameSource = nullptr;
    pSensor->get_BodyIndexFrameSource(&pFrameSource);

    // 2b. Get frame description
    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. get frame reader
    IBodyIndexFrameReader* pFrameReader = nullptr;
    pFrameSource->OpenReader(&pFrameReader);

    // 2c. release Frame source
    cout << "Release frame source" << endl;
    pFrameSource->Release();
    pFrameSource = nullptr;

    // Prepare OpenCV data
    cv::Mat    mImg(iHeight, iWidth, CV_8UC3);
    cv::namedWindow("Body Index Image");

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

    // Enter main loop
    while (true)
    {
        // 4a. Get last frame
        IBodyIndexFrame* pFrame = nullptr;
        if (pFrameReader->AcquireLatestFrame(&pFrame) == S_OK)
        {
            // 4c. Fill OpenCV image
            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];
                    if (uBodyIdx < 6)
                        mImg.at<cv::Vec3b>(y, x) = aColorTable[uBodyIdx];
                    else
                        mImg.at<cv::Vec3b>(y, x) = aColorTable[6];
                }
            }
            cv::imshow("Body Index Image", mImg);
        
            // 4e. release frame
            pFrame->Release();
        }

        // 4f. check keyboard input
        if (cv::waitKey(30) == VK_ESCAPE){
            break;
        }
    }

    // 3b. release frame reader
    pFrameReader->Release();
    pFrameReader = nullptr;

    // 1c. Close Sensor
    pSensor->Close();

    // 1d. Release Sensor
    pSensor->Release();
    pSensor = nullptr;

    return 0;
}

在前面取得 frame source、frame reader 的部分,基本上都和之前的一樣,所以這邊就不解釋了。不過這邊的程式碼基本上省略了錯誤處理的部分,如果是要看比較完整的程式碼的話,請參考 GitHub 上的檔案

而比較特別,是由於 body index 的資料算是索引值的影像,而 OpenCV 本身並不直接支援這類型的資料,所以會需要自行把每一個像素轉換成顏色。

為了做這件事,這邊在「color array」的地方,建立了一個大小為 7 的 cv::Vec3b 的陣列 aColorTable,裡面記錄了七種顏色,用來各自代表 0 – 5 的使用者,以及背景。

在「4c」的地方,則是一樣、透過 AccessUnderlyingBuffer() 來讀取原始的 body index 資料,然後再透過兩層迴圈、去掃過畫面裡的每一個像素、並從 aColorTable 裡面取出對應的色彩、填到 OpenCV 的影像(mImg)裡面。

而這個程式在執行後,如果有偵測到人的話,應該就會顯示類似下面的畫面;其中黃色的部分,就代表是他認為是人體的部分了~

如果把這個資料結合彩色影像,基本上就可以做出簡單的去背效果了~

不過由於 K4W SDK 所提供的深度影像資料和彩色影像的座標不一致,所以還會需要透過 ICoordinateMapper 這個介面(MSDN)提供的函式來做轉換。這部分就之後再做說明了~

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *