上一篇里講到了Kinect可以從環境中區分出人體來。因此可以利用這個功能,來把攝像頭前的人合成進照片里,和利用Photoshop不同的是,這樣合成進去的人是動態且實時的。
簡單的思路
BodyIndex
用的是深度數據,只能用來判斷畫面中的點屬不屬於人體而不能用來直接顯示畫面,Color
圖里的數據只能用來顯示而沒有其他功能。所以如果深度數據能和彩色數據配合的話,就能利用深度數據來識別出彩色數據中的哪些點屬於人體。但是深度幀的分辨率是512 x 424
,而彩色幀的分辨率是1920 x 1080
,無法將他們對應起來。然而微軟提供了一個叫ICoordinateMapper
的類。
簡單來說:將彩色圖上的點轉換到深度圖的坐標系中->判斷某點是否是人體->是的話從彩色圖中取出此點,與背景替換。
代碼
//這份代碼累死哥了
#include <iostream>
#include <opencv2/imgproc.hpp>
#include <opencv2\highgui.hpp>
#include <Kinect.h>
using namespace std;
using namespace cv;
int main(void)
{
IKinectSensor * mySensor = nullptr;
GetDefaultKinectSensor(&mySensor);
mySensor->Open();
//************************准備好彩色圖像的Reader並獲取尺寸*******************************
int colorHeight = 0, colorWidth = 0;
IColorFrameSource * myColorSource = nullptr;
IColorFrameReader * myColorReader = nullptr;
IFrameDescription * myDescription = nullptr;
{
mySensor->get_ColorFrameSource(&myColorSource);
myColorSource->OpenReader(&myColorReader);
myColorSource->get_FrameDescription(&myDescription);
myDescription->get_Height(&colorHeight);
myDescription->get_Width(&colorWidth);
myDescription->Release();
myColorSource->Release();
}
//************************准備好深度圖像的Reader並獲取尺寸*******************************
int depthHeight = 0, depthWidth = 0;
IDepthFrameSource * myDepthSource = nullptr;
IDepthFrameReader * myDepthReader = nullptr;
{
mySensor->get_DepthFrameSource(&myDepthSource);
myDepthSource->OpenReader(&myDepthReader);
myDepthSource->get_FrameDescription(&myDescription);
myDescription->get_Height(&depthHeight);
myDescription->get_Width(&depthWidth);
myDescription->Release();
myDepthSource->Release();
}
//************************准備好人體索引圖像的Reader並獲取尺寸****************************
int bodyHeight = 0, bodyWidth = 0;
IBodyIndexFrameSource * myBodyIndexSource = nullptr;
IBodyIndexFrameReader * myBodyIndexReader = nullptr;
{
mySensor->get_BodyIndexFrameSource(&myBodyIndexSource);
myBodyIndexSource->OpenReader(&myBodyIndexReader);
myDepthSource->get_FrameDescription(&myDescription);
myDescription->get_Height(&bodyHeight);
myDescription->get_Width(&bodyWidth);
myDescription->Release();
myBodyIndexSource->Release();
}
//************************為各種圖像准備buffer,並且開啟Mapper*****************************
UINT colorDataSize = colorHeight * colorWidth;
UINT depthDataSize = depthHeight * depthWidth;
UINT bodyDataSize = bodyHeight * bodyWidth;
Mat temp = imread("test.jpg"),background; //獲取背景圖
resize(temp,background,Size(colorWidth,colorHeight)); //調整至彩色圖像的大小
ICoordinateMapper * myMaper = nullptr; //開啟mapper
mySensor->get_CoordinateMapper(&myMaper);
Mat colorData(colorHeight, colorWidth, CV_8UC4); //准備buffer
UINT16 * depthData = new UINT16[depthDataSize];
BYTE * bodyData = new BYTE[bodyDataSize];
DepthSpacePoint * output = new DepthSpacePoint[colorDataSize];
//************************把各種圖像讀進buffer里,然后進行處理*****************************
while (1)
{
IColorFrame * myColorFrame = nullptr;
while (myColorReader->AcquireLatestFrame(&myColorFrame) != S_OK); //讀取color圖
myColorFrame->CopyConvertedFrameDataToArray(colorDataSize * 4, colorData.data, ColorImageFormat_Bgra);
myColorFrame->Release();
IDepthFrame * myDepthframe = nullptr;
while (myDepthReader->AcquireLatestFrame(&myDepthframe) != S_OK); //讀取depth圖
myDepthframe->CopyFrameDataToArray(depthDataSize, depthData);
myDepthframe->Release();
IBodyIndexFrame * myBodyIndexFrame = nullptr; //讀取BodyIndex圖
while (myBodyIndexReader->AcquireLatestFrame(&myBodyIndexFrame) != S_OK);
myBodyIndexFrame->CopyFrameDataToArray(bodyDataSize, bodyData);
myBodyIndexFrame->Release();
Mat copy = background.clone(); //復制一份背景圖來做處理
if (myMaper->MapColorFrameToDepthSpace(depthDataSize, depthData, colorDataSize, output) == S_OK)
{
for (int i = 0; i < colorHeight; ++ i)
for (int j = 0; j < colorWidth;++ j)
{
DepthSpacePoint tPoint = output[i * colorWidth + j]; //取得彩色圖像上的一點,此點包含了它對應到深度圖上的坐標
if (tPoint.X >= 0 && tPoint.X < depthWidth && tPoint.Y >= 0 && tPoint.Y < depthHeight) //判斷是否合法
{
int index = (int)tPoint.Y * depthWidth + (int)tPoint.X; //取得彩色圖上那點對應在BodyIndex里的值(注意要強轉)
if (bodyData[index] <= 5) //如果判斷出彩色圖上某點是人體,就用它來替換背景圖上對應的點
{
Vec4b color = colorData.at<Vec4b>(i, j);
copy.at<Vec3b>(i, j) = Vec3b(color[0], color[1], color[2]);
}
}
}
imshow("TEST",copy);
}
if (waitKey(30) == VK_ESCAPE)
break;
}
delete[] depthData; //記得各種釋放
delete[] bodyData;
delete[] output;
myMaper->Release();
myColorReader->Release();
myDepthReader->Release();
myBodyIndexReader->Release();
mySensor->Close();
mySensor->Release();
return 0;
}
詳細說明:
SDK中提供了一個叫ICoordinateMapper
的類,功能就是坐標系之間的互相轉換,用來解決數據源的分辨率不同導致點對應不起來的問題。我們需要的是將彩色圖像中的點與深度圖像中的點一一對應起來,因此使用其中的MapColorFrameToDepthSpace()
這個函數。
首選,需要准備好三種數據源:Color
、BodyIndex
、Depth
,其中前兩個是完成功能本來就需要的,第三個是轉換坐標系時需要,無法直接把Color
的坐標系映射到BodyIndex
中,只能映射到Depth
中。
然后是讀取背景圖,讀取之后也要轉換成Color
圖的尺寸,這樣把Color
中的點貼過去時坐標就不用再轉換,直接替換就行。接下來也要讀取三種Frame
,為了易讀性,不如把准備工作在前面都做完,在這一步直接用Reader
就行。
然后,利用MapColorFrameToDepthSpace()
,將彩色幀映射到深度坐標系,它需要4個參數,第1個是深度幀的大小,第2個是深度數據,第3個是彩色幀的大小,第4個是一個DepthSpacePoint
的數組,它用來儲存彩色空間中的每個點對應到深度空間的坐標。
要注意,這個函數只是完成坐標系的轉換,也就是說它對於彩色坐標系中的每個點,都給出了一個此點對應到深度坐標系中的坐標,並不涉及到具體的ColorFrame
。
最后,遍歷彩色圖像,對於每一點,都取出它對應的深度坐標系的坐標,然后把這個坐標放入BodyIndex
的數據中,判斷此點是否屬於人體,如果屬於,就把這點從彩色圖中取出,跟背景圖中同一坐標的點替換。
要注意的是,DepthSpacePoint
中的X
和Y
的值都是float
的,用它們來計算在BodyIndex
里的坐標時,需要強轉成int
,不然畫面就會不干凈,一些不屬於人體的地方也被標記成了人體被替換掉。
效果圖
背景為一張1920 * 1080的壁紙