Kinect For Windows V2開發日志七:照片合成與背景消除


上一篇里講到了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()這個函數。

首選,需要准備好三種數據源:ColorBodyIndexDepth,其中前兩個是完成功能本來就需要的,第三個是轉換坐標系時需要,無法直接把Color的坐標系映射到BodyIndex中,只能映射到Depth中。

然后是讀取背景圖,讀取之后也要轉換成Color圖的尺寸,這樣把Color中的點貼過去時坐標就不用再轉換,直接替換就行。接下來也要讀取三種Frame,為了易讀性,不如把准備工作在前面都做完,在這一步直接用Reader就行。

然后,利用MapColorFrameToDepthSpace(),將彩色幀映射到深度坐標系,它需要4個參數,第1個是深度幀的大小,第2個是深度數據,第3個是彩色幀的大小,第4個是一個DepthSpacePoint的數組,它用來儲存彩色空間中的每個點對應到深度空間的坐標。
要注意,這個函數只是完成坐標系的轉換,也就是說它對於彩色坐標系中的每個點,都給出了一個此點對應到深度坐標系中的坐標,並不涉及到具體的ColorFrame

最后,遍歷彩色圖像,對於每一點,都取出它對應的深度坐標系的坐標,然后把這個坐標放入BodyIndex的數據中,判斷此點是否屬於人體,如果屬於,就把這點從彩色圖中取出,跟背景圖中同一坐標的點替換。

要注意的是,DepthSpacePoint中的XY的值都是float的,用它們來計算在BodyIndex里的坐標時,需要強轉成int,不然畫面就會不干凈,一些不屬於人體的地方也被標記成了人體被替換掉。


效果圖

背景為一張1920 * 1080的壁紙







免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM