Kinect结合OpenCV获取骨骼数据原理及相关学习资料
首先说一下,本系统所使用的开发环境版本是计算机系统Windows 10、Visual Studio 2013、Opencv3.1.0和Kinect SDK v2.0。
vs2013需要vc12,如果VS版本更高的话可以使用较高版本的opencv,最好是vs、opencv版本一致,不然很容易出现问题。
Kinect sdk可直接从官网下载,OpenCV的配置方法和Kinect引入VS开发环境网上也有很多的资料,需要提一下的是,Kinect SDK v2.0下载下来后,双击安装就行,然后在VS2013里面添加一下相关的库目录和链接库的附加依赖项:
在【包含目录】中加入【$(KINECTSDK20_DIR)\inc】
在【库目录】中加入【$(KINECTSDK20_DIR)\Lib\x86】
在【链接器】的【输入】里,【附加依赖项】中加入【kinect20.lib】
其他的在这里就不多赘述,直奔主题,使用kinect sdk2.0和opencv获取人体骨骼数据并显示。
本文参考:博主baolinq
https://blog.csdn.net/baolinq/article/details/52356947
一、Kinect API介绍
既然要进行Kinect的开发,首先我觉得最好的方法是了解它的API,这部分相关的书籍和资料很少,比较靠谱的是各路大佬在CSDN发布的一些遇到过的坑或者相关总结,那些学习笔记和心得对开发还是很有帮助的。
其次最好的学习资料是SDK里面的示例程序(Samples),会发现Kinect的API都是有规律的,每个类型的数据都有三个类与之对应:Source,Reader和Frame,在程序里面只需要实例化这三个类并调用相关类的成员函数就可以了。比如要读取骨架,就有IBodyFrameSource, IBodyFrameReader, IBodyFrame这三个类,而要读取深度数据,就有IDepthFrameSource,IDepthFrameReader,IDepthFrame这三个类,以此类推其他的如Body Index,Infrared,Color数据也是这样命名的。
下面讲讲这三个接口的关系:
1、Source 在初始化好Kinect后,需要请求Kinect打开一个目标源,后面从这个源获得数据。以骨骼数据源为例,下同,示例代码为:
m_pKinectSensor->get_BodyFrameSource(&pBodyFrameSource);
其中m_pKinectSensor是一个IKinectSensor类的实例化对象,是Kinect的源,所有数据都是从这个源获取的,pBodyFrameSource是一个IBodyFrameSource类的实例化对象,它可以打开读口(reader)传输数据到电脑。
2、Reader 由于Source是从Kinect获得的数据源,在电脑端需创建一个Reader和上面的Source绑定,然后可以通过这个Reader来获取数据。示例代码:
pBodyFrameSource->OpenReader(&m_pBodyFrameReader);
其中m_pBodyFrameReader是一个IBodyFrameReader类的实例化对象,获取最新的图像帧(AcquireLatestFrame)以及该类数据的其他属性。
3、Frame 真正存储数据的类,一般是先从Reader读取数据到Frame里面,Frame里面有各种各样的数据,可以按自己的需求获取。示例代码:
m_pBodyFrameReader->AcquireLatestFrame(&pBodyFrame);
其中pBodyFrame是一个IBodyFrame类的实例对象,调用其相关成员函数就可以把数据转换到数组或者其他格式,进而可以用Opencv显示出来。
这里的每个对象都需主动释放,否则无法获取其他变量。
4、如何从Frame中获得数据
请求Source和创建Reader对于每一个数据类型都是一模一样的,但是从Frame中提取信息则各有不同。下面讲讲深度信息、骨架信息、手势状态和人物二值图信息的提取方法。
4.1 深度信息:
在Kinect 2.0中,深度坐标空间的范围是(高*宽 = 424*512)(官网有说明)。从深度信息Frame中提取数据,主要就是把Frame中的数据转存到一个数组中(官网链接)。代码为:
pBodyIndexFrame->CopyFrameDataToArray(cDepthHeight*cDepthWidth, odyIndexArray);
这里cDepthHeight是424,cDepthWidth是512,bodyIndexArray就是一个424*512大小的16位unsigned int数组,用来存储深度数据。
4.2 骨架信息:
kinect 2.0可以同时追踪六个人的骨架,因此每次我们需要先调用函数,获得六个骨架信息(如果没有人,那么那个骨架类就是空指针)。代码为:
pBodyFrame->GetAndRefreshBodyData(_countof(ppBodies), ppBodies);
这里ppBodies是一个长度为6的IBody数组,IBody是用来存储追踪到的骨架信息的类。 在获得了这个类后,我们需要进一步从类中提取骨架位置,对于ppBodies中的每一个元素pBody,代码为:
pBody->GetJoints(_countof(joints), joints);
这里的joints是一个长度为25的数组,每一个元素就是骨架的位置信息。然而, 这个骨架位置信息是照相机坐标系(camera view)下的位置,x和y的范围都是-1到1。因此我们需要将它转化到深度坐标系中。这里要用到一个coordinateMapper类,具体代码为:
m_pCoordinateMapper->MapCameraPointToDepthSpace(joints[j].Position, &depthSpacePosition[j]);
coordinateMapper类的创建非常简单,具体可以参考代码。depthSpacePosition是一个长度也为25的数组,每一个元素是DepthSpacePoint,这个元素包含了在深度坐标系下的x和y坐标。
二、代码实现——深度图像数据获取
#include<Kinect.h> //Kinect的头文件
#include<iostream>
#include<opencv2\highgui.hpp> //opencv头文件
#include<iostream>using namespace std;
using namespace cv;
int main(void)
{IKinectSensor * mySensor = nullptr;GetDefaultKinectSensor(&mySensor); //获取感应器 mySensor->Open(); //打开感应器 IDepthFrameSource * mySource = nullptr; //取得深度数据 mySensor->get_DepthFrameSource(&mySource);int height = 0, width = 0;IFrameDescription * myDescription = nullptr; //取得深度数据的分辨率 mySource->get_FrameDescription(&myDescription);myDescription->get_Height(&height);myDescription->get_Width(&width);myDescription->Release();IDepthFrameReader * myReader = nullptr;mySource->OpenReader(&myReader); //打开深度数据的Reader IDepthFrame * myFrame = nullptr;Mat temp(height, width,CV_16UC1); //建立图像矩阵 Mat img(height, width, CV_8UC1);while (1){if(myReader->AcquireLatestFrame(&myFrame) == S_OK) //通过Reader尝试获取最新的一帧深度数据,放入深度帧中,并判断是否成功获取 {myFrame->CopyFrameDataToArray(height * width, (UINT16 *)temp.data); //先把数据存入16位的图像矩阵中 temp.convertTo(img, CV_8UC1, 255.0 / 4500); //再把16位转换为8位 imshow("TEST", img);myFrame->Release();}if(waitKey(30) == VK_ESCAPE)break;}myReader->Release(); //释放不用的变量并且关闭感应器 mySource->Release();mySensor->Close();mySensor->Release();return 0;
}
注意:为了简便起见,此段代码略掉了大部分错误和异常检测,只是为了做个示例获取Kinect的深度图像。
首先,声明了两个矩阵,一个为16位单通道,一个为8位单通道,之所以开16位的,是因为在Kinect的基本参数这篇里可以看到,深度数据是16位的。开8位的原因等下说。然后同样的,利用AcquireLatestFrame()来获取最新的一帧,不同的是下面一句,不再用AccessUnderlyingBuffer(),而是用CopyFrameDataToArray来把数据复制到openCV的图像矩阵Mat里,注意第三个参数的类型是UINT16*,所以需要强制转换一下。接下来要把16位的Mat转换成8位来输出显示,为什么不直接用16位?其实也可以用,但是直接用16位的话显示的图像很接接近于全黑,不方便观察,于是转换为8位。convertTo()这个函数的第一个参数是输出矩阵,第二个是转换的类型,第三个是缩放因子,其中4500是深度数据的最大距离。
三、代码实现——骨骼数据获取
#include <iostream>
#include <opencv2\imgproc.hpp> //opencv头文件
#include <opencv2\calib3d.hpp>
#include <opencv2\highgui.hpp>
#include <Kinect.h> //Kinect头文件using namespace std;
using namespace cv;void draw(Mat & img, Joint & r_1, Joint & r_2, ICoordinateMapper * myMapper);
int main(void)
{IKinectSensor * mySensor = nullptr;GetDefaultKinectSensor(&mySensor);mySensor->Open();IColorFrameSource * myColorSource = nullptr;mySensor->get_ColorFrameSource(&myColorSource);IColorFrameReader * myColorReader = nullptr;myColorSource->OpenReader(&myColorReader);int colorHeight = 0, colorWidth = 0;IFrameDescription * myDescription = nullptr;myColorSource->get_FrameDescription(&myDescription);myDescription->get_Height(&colorHeight);myDescription->get_Width(&colorWidth);IColorFrame * myColorFrame = nullptr;Mat original(colorHeight, colorWidth, CV_8UC4);//**********************以上为ColorFrame的读取前准备**************************IBodyFrameSource * myBodySource = nullptr;mySensor->get_BodyFrameSource(&myBodySource);IBodyFrameReader * myBodyReader = nullptr;myBodySource->OpenReader(&myBodyReader);int myBodyCount = 0;myBodySource->get_BodyCount(&myBodyCount);IBodyFrame * myBodyFrame = nullptr;ICoordinateMapper * myMapper = nullptr;mySensor->get_CoordinateMapper(&myMapper);//**********************以上为BodyFrame以及Mapper的准备***********************while (1){while (myColorReader->AcquireLatestFrame(&myColorFrame) != S_OK);myColorFrame->CopyConvertedFrameDataToArray(colorHeight * colorWidth * 4, original.data, ColorImageFormat_Bgra);Mat copy = original.clone(); //读取彩色图像并输出到矩阵while (myBodyReader->AcquireLatestFrame(&myBodyFrame) != S_OK); //读取身体图像IBody ** myBodyArr = new IBody *[myBodyCount]; //为存身体数据的数组做准备for (int i = 0; i < myBodyCount; i++)myBodyArr[i] = nullptr;if (myBodyFrame->GetAndRefreshBodyData(myBodyCount, myBodyArr) == S_OK) //把身体数据输入数组for (int i = 0; i < myBodyCount; i++){BOOLEAN result = false;if (myBodyArr[i]->get_IsTracked(&result) == S_OK && result) //先判断是否侦测到{Joint myJointArr[JointType_Count];if (myBodyArr[i]->GetJoints(JointType_Count, myJointArr) == S_OK) //如果侦测到就把关节数据输入到数组并画图{draw(copy, myJointArr[JointType_Head], myJointArr[JointType_Neck], myMapper);draw(copy, myJointArr[JointType_Neck], myJointArr[JointType_SpineShoulder], myMapper);draw(copy, myJointArr[JointType_SpineShoulder], myJointArr[JointType_ShoulderLeft], myMapper);draw(copy, myJointArr[JointType_SpineShoulder], myJointArr[JointType_SpineMid], myMapper);draw(copy, myJointArr[JointType_SpineShoulder], myJointArr[JointType_ShoulderRight], myMapper);draw(copy, myJointArr[JointType_ShoulderLeft], myJointArr[JointType_ElbowLeft], myMapper);draw(copy, myJointArr[JointType_SpineMid], myJointArr[JointType_SpineBase], myMapper);draw(copy, myJointArr[JointType_ShoulderRight], myJointArr[JointType_ElbowRight], myMapper);draw(copy, myJointArr[JointType_ElbowLeft], myJointArr[JointType_WristLeft], myMapper);draw(copy, myJointArr[JointType_SpineBase], myJointArr[JointType_HipLeft], myMapper);draw(copy, myJointArr[JointType_SpineBase], myJointArr[JointType_HipRight], myMapper);draw(copy, myJointArr[JointType_ElbowRight], myJointArr[JointType_WristRight], myMapper);draw(copy, myJointArr[JointType_WristLeft], myJointArr[JointType_ThumbLeft], myMapper);draw(copy, myJointArr[JointType_WristLeft], myJointArr[JointType_HandLeft], myMapper);draw(copy, myJointArr[JointType_HipLeft], myJointArr[JointType_KneeLeft], myMapper);draw(copy, myJointArr[JointType_HipRight], myJointArr[JointType_KneeRight], myMapper);draw(copy, myJointArr[JointType_WristRight], myJointArr[JointType_ThumbRight], myMapper);draw(copy, myJointArr[JointType_WristRight], myJointArr[JointType_HandRight], myMapper);draw(copy, myJointArr[JointType_HandLeft], myJointArr[JointType_HandTipLeft], myMapper);draw(copy, myJointArr[JointType_KneeLeft], myJointArr[JointType_FootLeft], myMapper);draw(copy, myJointArr[JointType_KneeRight], myJointArr[JointType_FootRight], myMapper);draw(copy, myJointArr[JointType_HandRight], myJointArr[JointType_HandTipRight], myMapper);}}}delete[]myBodyArr;myBodyFrame->Release();myColorFrame->Release();imshow("TEST", copy);if (waitKey(30) == VK_ESCAPE)break;}myMapper->Release();myDescription->Release();myColorReader->Release();myColorSource->Release();myBodyReader->Release();myBodySource->Release();mySensor->Close();mySensor->Release();return 0;
}void draw(Mat & img, Joint & r_1, Joint & r_2, ICoordinateMapper * myMapper)
{//用两个关节点来做线段的两端,并且进行状态过滤if (r_1.TrackingState == TrackingState_Tracked && r_2.TrackingState == TrackingState_Tracked){ColorSpacePoint t_point; //要把关节点用的摄像机坐标下的点转换成彩色空间的点Point p_1, p_2;myMapper->MapCameraPointToColorSpace(r_1.Position, &t_point);p_1.x = t_point.X;p_1.y = t_point.Y;myMapper->MapCameraPointToColorSpace(r_2.Position, &t_point);p_2.x = t_point.X;p_2.y = t_point.Y;line(img, p_1, p_2, Vec3b(0, 255, 0), 5);circle(img, p_1, 10, Vec3b(255, 0, 0), -1);circle(img, p_2, 10, Vec3b(255, 0, 0), -1);}
}
注意:此段代码也省略掉了大部分错误和异常检测,只是为了做个示例获取Kinect的骨骼图像。
可以看到,大部分代码同上面获取深度图是一样的。但由于骨骼部分要更麻烦一点,Kinect对于骨骼点的处理,最多只能同时处理6*25个骨骼点,6个人,每个人的25的骨骼点。在写程序的时候,需要对每个人的每个骨骼点进行循环访问就可以了,每个骨骼点有三种状态,跟踪到的,推测的,没有跟踪到的。把骨骼点用opencv里的画实心点(circle函数)表示,骨骼用线段(line函数)表示,可以看到一幅近似人体模型的骨架。在演示程序里头把人体骨架画在Kinect获取的彩色图像上,可以看到,骨骼点的位置和彩色图像的真实位置非常接近,也就是说骨骼点的三维坐标还是比较准确的,得到25个骨骼点的空间三维坐标。
四、Kinect学习的参考资料
Kinect for Windows C++ Reference(Kinect v2的API最全面和最权威的文档):
https://msdn.microsoft.com/en-us/library/hh855364.aspx?f=255&MSPPError=-2147217396
Github上的一系列讲义:http://kinect.github.io/tutorial/
还有很多人的博客和论坛也有讲解Kinect的入门开发:
(1)http://blog.csdn.net/dustpg/article/category/2408183
(2)http://blog.csdn.net/bbdxf/article/details/44857099
(3)https://blog.csdn.net/baolinq/category_6678354.html
Kinect结合OpenCV获取骨骼数据原理及相关学习资料相关推荐
- 太肝了,挑战扫雷世界纪录:扫雷自动化Python+OpenCV实战(附迪迦250G人工智能学习资料)
咱们废话不多说,先看成果~ 中级 - 0.74秒 3BV/S=60.81 相信许多人很早就知道有扫雷这么一款经典的游(显卡测试)戏(软件),更是有不少人曾听说过中国雷圣,也是中国扫雷第一.世界综合排名 ...
- java程序员提升必备大数据、架构师学习资料免费分享
大数据和架构师是很多java程序员工作2/3年后提升的首选方向,也是为数不多的学习方向. 而且大数据正在被越来越多的国家和企业定位发展战略,尤其是我国在贵州设立了大数据产业园区,更是证明了大数据产业在 ...
- 关于opencv获取摄像头数据时报错问题
未定义标识符 "CV_WINDOW_AUTOSIZE"C/C++ 解决办法:在代码开头的头文件中引入 #include <opencv2/highgui/highgui_c. ...
- 【数据原理及应用 学习总结】第二章 关系数据库标准语言SQL(1)
2.1SQL语言介绍 2.1.1SQL数据库的体系结构 SQL用户可以是应用程序,也可以是终端用户.SQL语言可以被嵌入在宿主语言的程序(如Python.C++.Java等)中使用,也可以作为独立的用 ...
- Kinect开发之获取彩色摄像头数据
刚接触到Kinect,简要地介绍一下其摄像头相关的结构功能: Kinect 有两类摄像头,近红外摄像头和普通的视频摄像头.视频摄像头提供了一般摄像 头类似的彩色影像.这种数据流是三中数据流中使用和设置 ...
- 使用手机摄像头做网络ip摄像头 并用opencv获取rtsp视频流
目录 前言 准备工作 DroidCam使用方法 IP摄像头使用方法 使用opencv调用摄像头 前言 最近要做一个和图像有关的项目,需要获取热成像摄像头的输入进行处理,最终调研找到了一款网络摄像机.苦 ...
- 如何通过python获取股票数据接口l2?
python提供了股票数据接口库Tushare,但用Tushare获取股票实时数据存在只能连续运行2次,就会被网站服务器禁止. 如使用以下代码: #导入模块 import tushare as ts ...
- 2.opencv获取和设置像素
opencv如何获取和设置图片像素 1.什么是像素? 2.OpenCV中的图像坐标系概述 3.项目结构 1.使用OpenCV获取和设置像素 2.OpenCV像素获取和设置结果 3.源代码下载 什么是像 ...
- Kinect Azure DK获取深度图、深度数据和人体骨骼关键点三维坐标
使用环境:Azure Kinect SDK v1.4.1 + Azure Kinect Body Tracking SDK 1.0.1 + VS2019 + Opencv 文章目录 一.获取深度图 二 ...
最新文章
- main方法_你知道为什么Java的main方法必须是public static void?
- 「人工智能视觉」一直被消费者忽视的工业领域应用
- PowerDesigner对列增加注释
- 承德计算机专业去哪考试,2021河北省考承德都在哪个学校考试?
- 四因素三水平正交试验表_案例 | 螺栓装配失效试验研究
- PyQt5树形结构控件QTreeWidget操作
- 当OpenOrg和OpenGov发生冲突时
- 5ecsgo启动失败2错误代码2_Xcode 10.2 编译失败,如何对敌?
- 6.Python学习笔记:[enumerate元素加序号;isdigit()像数字;len()计算长度]
- java memcached delete_Memcached删除/Delete数据
- 怎么去掉word标题前的黑点
- 关于新建android项目时 appcompat_v7报错问题的一点总结
- 云通俗的讲是一种基于网络的计算机,云计算通俗讲义(pdf+epub+mobi+txt+azw3)
- 教你如何用CAD画向日葵
- 电影推荐之《哈利波特与阿兹卡班的囚徒》 隐私策略(Privacy policy)
- TOPcoder准备
- OJ 2312 Problem B	Peach
- html5光线传感器,光线传感器是什么 光线传感器分类
- L W V th t发音
- 安卓手机便签怎么快速修改重复提醒设置?
热门文章
- 趣来宝机器人_App Store 上的“管家机器人”
- php rename函数_php rename函数怎么用
- 【Python笔记】倾向评分匹配(Propensity Score Matching)实战
- 【腾讯TMQ】激情测试-冒烟军团的远征
- 直接双击启动tomcat中的startup.bat闪退
- turtle库函数绘制包含同心圆的靶盘
- cdn服务器性能要求,搭建直播平台选择CDN服务器的标准
- 盐城北大青鸟机电产教融合基地丨强盛集团logo设计作业赏析
- 常见漏洞验证测试方法
- 在gitee码云上用 git 新建分支