第三篇  KinectV2骨骼获取原理和获取方法及源代码

首先声明一下,本系统所使用的开发环境版本是计算机系统Windows 10、Visual Studio 2013、Opencv3.0和Kinect SDK v2.0。这些都可以在百度上找到,download下来安装一下即可。

通过上一篇文章,我们已经基本学会如何获取Kinect 的各种数据源,对于骨骼数据的获取是稍微复杂的那一个,将在本篇文章详细讲解和深度剖析Kinect v2 获取骨骼数据的原理,最后将利用骨骼数据做一个好玩的东西。废话少说,我们开始吧。

一、骨骼跟踪的原理

1.骨骼图是由深度图获取得到的

(1)Kinect v1生成3D深度的图像的原理

采用的是PrimeSence公司Light Coding技术。Light Coding技术理论是利用连续光(近红外线)对测量空间进行编码,经感应器读取编码的光线,交由晶片运算进行解码后,产生成一张具有深度的图像。 Light Coding技术的关键是Laser Speckle雷射光散斑,当雷射光照射到粗糙物体、或是穿透毛玻璃后,会形成随机的反射斑点,称之为散斑。散斑具有高度随机性,也会随着距离而变换图案,空间中任何两处的散斑都会是不同的图案,等于是将整个空间加上了标记,所以任何物体进入该空间、以及移动时,都可确切纪录物体的位置。Light Coding发出雷射光对测量空间进行编码,就是指产生散斑。Kinect就是以红外线发出人眼看不见的class 1雷射光,透过镜头前的diffuser(光栅、扩散片)将雷射光均匀分布投射在测量空间中,再透过红外线摄影机记录下空间中的每个散斑,撷取原始资料后,再透过晶片计算成具有3D深度的图像。

(2)Kinect v2则采用了更为先进的TOF技术。红外发射器主动投射经调制的近红外光线,红外光线照到视野里的物体上就会发生反射,红外相机接收反射回来的红外线,采用TOF技术测量深度,计算光的时间差(通常是通过相位差来计算的),根据,可得物体的深度(即物体到深度相机的距离)。

2、Kinect骨骼跟踪的原理

了解Kinect如何获得影像后,接下来就是进行辨识的工作。透过Light Coding技术所获得的只是基本的影像资料,重点还是要辨识影像,转换为动作指令。

微软将侦测到的3D深度图像,转换到骨架追踪系统。该系统最多可同时侦测到6个人,包含同时辨识2个人的动作;每个人共可记录20组细节,包含躯干、四肢以及手指等都是追踪的范围,达成全身体感操作。为了看懂使用者的动作,微软也用上机器学习技术(machine learning),建立出庞大的图像资料库,形成智慧辨识能力,尽可能理解使用者的肢体动作所代表的涵义。

下面更加详细的来探讨一下骨骼跟踪的原理:

Kinect骨骼跟踪不受周围光照的影响,主要是因为红外信息,产生3D深度图像,上文已经介绍。

另外,Kinect采用分隔策略将人体从复杂的背景中区分出来,在这个阶段,为每个跟踪的人在深度图像中创建所谓的分割遮罩(分割遮罩为了排除人体以外背景图像,采取的图像分割的方法),如图1这是一个将背景图像(比如椅子和宠物等)剔除后的景深图像。在后面的处理流程中仅仅转送人体图像即可,以减轻体感计算量。

图1 剔除背景后的景深图像

Kinect需做的下一件事情就是寻找图像中较可能是人体的物体,接下来kinect会对景深图像(机器学习)进行评估,来判别人体的不同部位。

在识别人体的各部位之前,微软是通过开发的一个人工智能(被称为Exemplar(模型)系统),数以TB计的数据输入到集群系统训练模型,图2就是用来训练和测试Exemplar的数据之一。

图2  测试和训练数据

训练分类器的分方法,提出的是一种含有许多深度特征的分类器,来识别物体,该特征虽然简单却包含必要的信息,来确定身体的部位,其公式如(1)所示:

···········(1)

其中x是像素值,d1(x)是像素值在图像I中的深度值,参数θ=(u,v),u和v是一对偏移向量(怎么理解?),1/d1(x)是偏移正规化,用来处理人体尺寸的缩放,这是一个非常简单的特征,也就是简化目标像素u和v值这两个像素深度偏移的不同。很显然,这些特征测量与像素周围的区域的3D外形相关,这足以说明手臂和腿之间的区别。如图3所示,其中十字架代表被像素被分的类别,而圆圈表示公式(1)计算出的偏移像素。若偏移像素是背景,d1(x)深度值将会是正无穷大。

a、两个具有较大响应的特征


 
b、两个具有较小响应值的特征 
图3 深度图像特征

接下来训练一个决策树分类器。决策树森林即众多决策树的集合,每棵树用一组预先标签的身体部位的深度图像来训练,决策树被修改更新,知道决策树为特定的身体部位上的测试集的图像给出了正确的分类。用100w幅图像训练3颗数,利用GPU加速,在1000个核的集群去分析。根据微软实验,大概耗时一天。这些训练过的分类器指定每一个像素在每一个身体部分的可能性。下一个阶段的算法简单的为每一个身体部位挑选最大几率的区域。因此,如果“手臂”分类器是最大的几率,这个区域则被分配到“手臂”类别。最后一个阶段是计算分类器建议的关节位置(节点)相对位置作为特别的身体部位,如图4所示。


图4 Kinect 人体节点识别过程

另外,只要有大字形的物体,Kinect都会努力去追踪,如图5所示。当然,这个物体也必须是接近人体的大小比例,尺寸小的玩具是无法识别的。


图5 骨骼跟踪结果

在Kinect前放一个没有体温的塑料人体模特,或者一件挂着衬衣的衣架,Kinect会认为那是一个静止的人。红外传感器所能捕捉的只是一个人体轮廓。

模型匹配:生成骨架的系统

处理流程的最后一步是使用之前阶段输出的结果,根据追踪到的20个关节点来生成一幅骨架系统。Kinect会评估Exemplar输出的每一个可能的像素来确定关节点。通过这种方式Kinect能够基于充分的信息最准确地评估人体实际所处位置。另外模型匹配阶段还做了一些附加输出滤镜来平滑输出以及处理闭塞关节等特殊事件。

参考资料:

[1] Shotton J, Sharp T, Kipman A, et al. Real-time human pose recognition in parts from single depth images[J]. Communications of the ACM, 2013, 56(1): 116-124.

[2] http://blog.csdn.net/u014365862/article/details/46849309

二、骨骼数据获取方法

还是老规矩,先上代码后面分析

#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 v2最强大的功能--获取人体骨骼数据,骨骼数据的获取原理和获取方法、代码一一详细介绍。下面附上更加完整和规范的源码下载地址,0积分下载。

http://download.csdn.net/detail/baolinq/9616276

下一篇见。

以一幅美图来结束本篇文章。豪车真养眼。

第三篇 KinectV2骨骼获取原理和获取方法及源代码相关推荐

  1. qqkey获取原理_获取QQKEY源码[C++版]

    前几天有个人找我用E源码转成C++的   现在共享给大家. 原理很简单  我也是照着别人给我的E源码改的  就是利用IWebBrowser2没什么难度 GetQQkeyDlg  用QQKEY的结构体  ...

  2. 负载均衡原理与实践详解 第三篇 服务器负载均衡的基本概念-网络基础

    负载均衡原理与实践详解 第三篇 服务器负载均衡的基本概念-网络基础 系列文章: 负载均衡详解第一篇:负载均衡的需求 负载均衡详解第二篇:服务器负载均衡的基本概念-网络基础 负载均衡详解第三篇:服务器负 ...

  3. 化工原理计算机辅助设计,化工原理课程设计心得三篇

    篇一:化工原理课程设计心得 小结: 本次化工原理课程设计历时两周,是学习化工原理以来第一次独立的工业设计.化工原理课程设计是培养学生化工设计能力的重要教学环节,通过课程设计使我们初步掌握化工设计的基础 ...

  4. python网页爬虫循环获取_Python 爬虫第三篇(循环爬取多个网页)

    本篇是 python 爬虫的第三篇,在前面两篇 Python 爬虫第一篇(urllib+regex) 和 Python 爬虫第二篇(urllib+BeautifulSoup) 中介绍了如何获取给定网址 ...

  5. Noah Mt4跟单系统制作第三篇 Mt4TradeApi获取报价篇

    Noah Mt4跟单系统制作第三篇 Mt4TradeApi获取报价篇 using System; using Mt4TradeApi;namespace Demo {class Program{sta ...

  6. 批训练、注意力模型及其声纹分割应用,谷歌三篇论文揭示其声纹识别技术原理

    声纹识别技术在谷歌的诸多产品中有着重要应用.除了安卓手机目前所独有的声纹解锁功能外,谷歌的家庭语音助手 Google Home 也能通过声纹识别支持多用户模式,从而针对不同用户提供个性化服务.当你向 ...

  7. MariaDB/MySQL备份和恢复(三):xtrabackup用法和原理详述

    MariaDB/MySQL备份恢复系列: 备份和恢复(一):mysqldump工具用法详述 备份和恢复(二):导入.导出表数据 备份和恢复(三):xtrabackup用法和原理详述 xtrabacku ...

  8. JAVA层HIDL服务的获取原理-Android10.0 HwBinder通信原理(九)

    摘要:本节主要来讲解Android10.0 JAVA层HIDL服务的获取原理 阅读本文大约需要花费19分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的 ...

  9. Native层HIDL服务的获取原理-Android10.0 HwBinder通信原理(七)

    摘要:本节主要来讲解Android10.0 Native层HIDL服务的获取原理 阅读本文大约需要花费23分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Androi ...

最新文章

  1. CVPR 2020录用率十年最低,商汤官宣62篇入选
  2. MatLab基础操作
  3. python中__init__函数以及参数self
  4. 计算机网络学习笔记:第三章
  5. html 刷新页面 未传参数值,vue router路由参数刷新消失问题的解决方法
  6. java中引用数据类型和基本数据类型的一些区别(貌似不完整,但会有些启示)
  7. pro缺点和不足 一加7t_看点满满,一用难忘:一加7T上手体验全方位测评
  8. 安装zsh-autosuggestions zsh-syntax-highlighting
  9. CCNA考试题库中英文翻译版及答案16
  10. 如何卸载赛门铁克symantec,ivanti
  11. tkinter打包为exe后找不到图片 tkinter_TclError:couldn‘t open “a.png“ no such file or directory
  12. Cloudera Manager安装之利用parcels方式安装单节点集群(包含最新稳定版本或指定版本的安装)(添加服务)(CentOS6.5)(四)...
  13. 注入修改代码,白嫖按键精灵ios手机版
  14. postgresql windows下修改帐号密码 (图文)
  15. 计算机网络与应用初稿,计算机网络原理与应用(第2版)
  16. php文本框限制只输入数字,js限制文本框只能输入数字方法小结_javascript技巧
  17. 将人民币中的阿拉伯数字转换为大写
  18. Win11小组件加载不出来怎么办?
  19. ffmpeg_分割一个mp4文件到多个小的mp4文件
  20. 服务器划分虚拟主机教程,服务器划分虚拟主机

热门文章

  1. Oracle EBS OAF开发入门(3)-OAF如何部署,译编和发布
  2. win7输入网络密码来自动连接
  3. 暨南大学2019年计算机考研报录比,暨南大学2018年考研报录比
  4. 酒精测试仪方案了解-PCBA电子设计
  5. 如何使用Matlab将其画出的图片,直接生成一个Powerpoint?
  6. SQL several 数据库基础知识
  7. SAP存储税率税码的相关表
  8. vscode-cats插件是如何开发的?小包带你来实现一下
  9. 阿里云服务器快速安装Mysql,贴心手把手教你安装,本人踩过很多坑!(我的服务器系统CentOS 7.8 64位)
  10. SCVMM(系统中心虚拟机管理)安装