zoukankan      html  css  js  c++  java
  • Kinect For Windows V2开发日志八:侦测、追踪人体骨架

    # 简介 Kinect一个很强大的功能就是它可以侦测到人体的骨骼信息并追踪,在Kinect V2的SDK 2.0中,它最多可以同时获取到6个人、每个人25个关节点的信息,并且通过深度摄像头,可以同时获取到这些关节点的坐标。此时的坐标使用的是`Camera Space`,也就是`摄像机空间坐标系`,代表了物体距离深度摄像头的距离。

    与前面获取数据源稍微不同的是,在把数据读取到IBodyFrame之后,还需要再从IBodyFrame里把数据读到一个6*m的二维数组里,这个数组存储了6个人的关节点信息,在这个数组中确定某一维(人)之后,再从这一维中读取出详细的关节信息。

    所以简而言之,获取关节数据的步骤为:Sensor -> Source -> Reader -> Frame ->BodyArr->JointArr->Joint

    关节点以及此坐标系的结构如下(其中坐标原点就是深度摄像头的大概位置):


    代码

    #include <iostream>
    #include <opencv2highgui.hpp>
    #include <string>
    #include <Kinect.h>
    
    using	namespace	std;
    using	namespace	cv;
    
    const	string	get_name(int n);	//此函数判断出关节点的名字
    int	main(void)
    {
    	IKinectSensor	* mySensor = nullptr;
    	GetDefaultKinectSensor(&mySensor);
    	mySensor->Open();
    
    	int	myBodyCount = 0;
    	IBodyFrameSource	* myBodySource = nullptr;
    	IBodyFrameReader	* myBodyReader = nullptr;
    	mySensor->get_BodyFrameSource(&myBodySource);
    	myBodySource->OpenReader(&myBodyReader);
    	myBodySource->get_BodyCount(&myBodyCount);
    
    	IDepthFrameSource	* myDepthSource = nullptr;
    	IDepthFrameReader	* myDepthReader = nullptr;
    	mySensor->get_DepthFrameSource(&myDepthSource);
    	myDepthSource->OpenReader(&myDepthReader);
    
    	int	height = 0, width = 0;
    	IFrameDescription	* myDescription = nullptr;;
    	myDepthSource->get_FrameDescription(&myDescription);
    	myDescription->get_Height(&height);
    	myDescription->get_Width(&width);	//以上为准备好深度数据和骨骼数据的Reader
    
    	IBodyFrame	* myBodyFrame = nullptr;
    	IDepthFrame	* myDepthFrame = nullptr;
    	Mat	img16(height, width, CV_16UC1);	//为显示深度图像做准备
    	Mat	img8(height, width, CV_8UC1);			
    
    	while (1)
    	{
    		while (myDepthReader->AcquireLatestFrame(&myDepthFrame) != S_OK);
    		myDepthFrame->CopyFrameDataToArray(width * height,(UINT16 *)img16.data);
    		img16.convertTo(img8,CV_8UC1,255.0 / 4500);
    		imshow("Depth Img", img8);	//深度图像的转化及显示
    
    		while (myBodyReader->AcquireLatestFrame(&myBodyFrame) != S_OK);
    
    		int	myBodyCount = 0;		
    		IBody	** bodyArr = nullptr;
    		myBodySource->get_BodyCount(&myBodyCount);
    		bodyArr = new IBody *[myBodyCount];
    		for (int i = 0; i < myBodyCount; i++)	//bodyArr的初始化
    			bodyArr[i] = nullptr;		
    
    		myBodyFrame->GetAndRefreshBodyData(myBodyCount,bodyArr);
    		for (int i = 0; i < myBodyCount; i++)	//遍历6个人(可能用不完)
    		{
    			BOOLEAN		result = false;
    			if (bodyArr[i]->get_IsTracked(&result) == S_OK && result)	//判断此人是否被侦测到
    			{
    				cout << "Body " << i << " tracked!" << endl;
    
    				int	count = 0;
    				Joint	jointArr[JointType_Count];
    				bodyArr[i]->GetJoints(JointType_Count,jointArr);	//获取此人的关节数据
    				for (int j = 0; j < JointType_Count; j++)
    				{
    					if (jointArr[j].TrackingState != TrackingState_Tracked)	//将确定侦测到的关节显示出来
    						continue;
    					string	rt = get_name(jointArr[j].JointType);	//获取关节的名字
    					if (rt != "NULL")	//输出关节信息
    					{
    						count++;
    						cout << "	" << rt << " tracked" << endl;
    						if (rt == "Right thumb")
    							cout << "		Right thumb at " << jointArr[j].Position.X << "," << jointArr[j].Position.Y << "," << jointArr[j].Position.Z << endl;
    					}
    				}
    				cout << count << " joints tracked" << endl << endl;
    			}
    		}
    		myDepthFrame->Release();
    		myBodyFrame->Release();
    		delete[] bodyArr;
    
    		if (waitKey(30) == VK_ESCAPE)
    			break;
    		Sleep(1000);	//为避免数据刷太快,每秒钟更新一次
    	}
    	myBodyReader->Release();
    	myDepthReader->Release();
    	myBodySource->Release();
    	myDepthSource->Release();
    	mySensor->Close();
    	mySensor->Release();
    
    	return	0;
    }
    
    const	string	get_name(int n)
    {
    	switch (n)
    	{
    	case	2:return	"Neck"; break;
    	case	3:return	"Head"; break;
    	case	4:return	"Left shoulder"; break;
    	case	8:return	"Right shoulder"; break;
    	case	7:return	"Left hand"; break;
    	case	11:return	"Right hand"; break;
    	case	22:return	"Left thumb"; break;
    	case	24:return	"Right thumb"; break;
    	default	:return	"NULL";
    	}
    }
    

    详细解释

    这是一份判断上半身关节点是否被侦测到的代码,如果侦测到右拇指的话,还会显示右拇指到摄像头的距离。只判断上半身的原因是一共有25个关节点,也就是说全部都判断的话要写25个case,实在太麻烦了。

    在这里,为了方便观察,我不仅使用了骨骼数据,同时也将深度数据显示出来,所以一共用到了两种数据源。而且为了不让代码刷太快,所以控制了时间间隔,每秒1帧。就像之前一样,当同时使用到多种数据源时,为了使得代码清晰,比较好的办法是实现先把各种数据源的Reader准备好,到时候直接拿来用就行。

    在把两种数据源都读入到Frame之后,首先要做的是利用BodyFrameGetAndRefreshBodyData()这个函数把所有信息输出到数组里。数组的类型是IBody,这个类同样包含了很多方法,链接在此。下一步所需要用到的,就是用它里面的get_IsTracked()这个函数来判断当前这一维是否有效。注意不仅需要判断get到的bool值,同时也需要通过函数的返回值判断是否get成功。如果有效,那么接下来就是利用它里面的GetJoints(),来把此人的详细关节信息输出到一个Joint数组里面。注意Joint是一个结构,而不是一个类。

    接下来,就是对关节的详细处理了,首先,每个关节的状态有三种:TrackingState_NotTrackedTrackingState_InferredTrackingState_Tracked,其中第一个和第三个代表的是没有侦测到侦测到,第二个代表的是推测,代表它利用一些不太完全的信息推测出此关节点的位置。为了精确起见,我在代码里只接受Tracked这种状态。然后,就是通过JointType这个变量,来取得关节点的类型,但是JointType是一个枚举量,也就是说只能获得一个int值,所以要再写一个函数来取得关节名称,这里我只写了上半身的关节点的代码。Joint里还有一个Position的重要变量没用到,它包含的是此关节点在摄像机坐标系下的XYZ值,不用太可惜了,所以就用它来判断下右拇指的距离好了。

    让人遗憾的是,SDK 2.0对拇指的判断非常非常不精确,且不说距离,状态值就非常难以理解了。当我的双手放到摄像头完全看不到的地方时它还是信誓旦旦的返回了一个Tracked,连个Inferred都不是,只好寄希望于微软在下一版的SDK中能解决这个问题。


    效果图

    可以看到我的双手放在桌面下,但是依然显示侦测到了拇指。 ╮(╯▽╰)╭





  • 相关阅读:
    【原创】【1】rich editor系列教程。前期准备,兼容
    html5 video,audio控制播放多次,请求/监测全屏状态
    javascript中的位运算,
    温习classList api
    Fiddler常用功能总结
    小程序测试点总结
    Pycharm使用git版本控制
    python 运行当前目录下的所有文件
    MySQL查询count(*)、count(1)、count(field)的区别收集
    python 操作redis,存取为字节格式,避免转码加
  • 原文地址:https://www.cnblogs.com/xz816111/p/5187424.html
Copyright © 2011-2022 走看看