上一篇里讲到了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的壁纸