之前已經寫過《使用 OpenCV 顯示深度影像》和《讀取彩色影像與紅外線影像》,基本上,算是把把 Kinect 能拿到的影像資料的原始資料,都介紹過了。
而實際上,除了深度、彩色、紅外線影像這些算是原始資料外,Kinect for Windows SDK 能提供的影像資料,還有一種,那就是經過分析的結果:Body Index。
Body Index 在 K4W SDK v2 裡面,他的 frame source 的介面是 IBodyIndexFrameSource(MSDN),基本上是用來代表人體在深度畫面中,所佔位置的影像資料。在 K4W SDK 目前的系統下,最多能追蹤六個人,而 Body Index 就是用來告訴程式開發者,現在這六個人個別在畫面裡的哪個位置的。
而這資料能用來幹嘛呢?最直接的用法,就是在程式執行時,可以讓操作只知道自己在感應器的視角裡的樣子、確定偵測到的是對的。再來,對於開發者來說,也可以用來做操作者的簡單的位置偵測、或是去背的動作。
它的使用方法基本上和其他的影像類型的資料一樣,都是 Frame Source – Frame Reader – Frame 這樣的三層式的架構;而這三層的介面,分別是:
- IBodyIndexFrameSource
- IBodyIndexFrameReader
- IBodyIndexFrame
而他所讀取出來的畫面,每個像素則是一個 BYTE(實際上是 unsigned char),代表的是該點的人的編號;由於 K4W SDK v2 只能追蹤六個人,所以這編號只有 0 – 5 是有用的,如果他的值不再這個範圍內的話,則代表這點是背景、不是正在被追蹤的人體。
如果要用 OpenCV 來顯示的話,程式大致上可以寫成下面的樣子:
#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)提供的函式來做轉換。這部分就之後再做說明了~