对应CSDN上本文的地址(有百来条评论,如有问题可以先去看看)
更新:
[2013-10-10]添加了三维人脸特征点获取的代码
[2013-4-10]编译程序时遇到xcopy问题(error MSB3073),猪猪猪小航给出了他的解决办法(第4节)。
[2013-3-25]程序源代码由kexin_0311进行了改进,避免了人脸走出检测后窗体假死情况(第6节)。
前言
自上篇Kinect SDK 1.5 Face Tracking文章出现后(2012-5-27),许多人阅读到了(CSDN,博客园,百度文库),相关代码也被上百次下载(百度文库、csdn)。已经过去很久了,但是国内依旧没有发现很好的关于这方面的博客(大家都藏着掖着吗)。当时我对于C++中的多线程还半知半解,如今多次使用有些熟悉。
之所以Kinect中的Face Tracking没有得到很多的关注,我想主要原因还是这个方法必须使用Kinect硬件(真不便宜啊,普通摄像头几十块ok了),只适合开发Kinect室内应用。其次这个Face Tracking也是很耗CPU的,一般的双核电脑一旦运行经常会占用90%多的CPU。题外话,微软有许多强大的算法,比如人脸跟踪(基于普通彩色摄像头),但是他们只给了Windows Phone下的API,却不给出PC下的。我也去天津参加过Kinect的会议,遇到一些微软的产品,确实做得很强大很稳健,人脸检测用改进的特征进行Adaboost,之后用AAM进行面部特征点跟踪,十分稳健。去年参加了不少面试,国内的很多公司都在做人脸识别,但真正有实力的只有大公司弄的好。但如果说你想靠这个Kinect的人脸识别混饭吃,有点困难,除非你研究AAM人脸跟踪算法再去研究人脸识别、表情识别、三维建模等算法才会有饭吃。
鉴于之前没有对Kinect的Face Tracking进行详细描述和探讨,直接暴力的给出相关代码,有必要给出更详细的文章和更好的代码。
————————————————————————————
人脸跟踪概念
首先现在大部分开发都针对Kinect for windows(目前1900RMB,还是有点贵的)了,但这个face tracking SDK在xbox版的Kinect依旧可以运行,自然精度会不好。Kinect for windows的深度数据是640*480的,而xbox的是320*240。而彩色的RGB摄像头也清晰不少,这点官方没有具体介绍,但实际效果好很多。如果要做研究,自然使用贵很多的Kinect for windows了(这样也导致了个人一般不会去玩这个传感器),不过对于学校、公司这点钱不多。下面默认使用Kinect for windows,但代码稍加修改在xbox的kinect上也能运作。
————————————————–
(1)软件硬件配置
Kinect for Windows有如下要求:
采用下列操作系统的一种:
Windows 7
嵌入式Windows 标准7
嵌入式WindowsPOSReady 7
Windows 8
硬件要求
32位(x86)或64位(x64) 处理器
双核2.66-GHz 或更快的处理器(建议使用i7)
专用USB 2.0总线
2 GB内存
从Kinect SDK 1.5开始的Kinect for windows开发工具包中含有人脸追踪模块,当前最新的是SDK1.6。在近几个星期里,将会有新的SDK会更新。新的SDK将会带来简单的动作识别(手握拳和张开)和Kinect Fusion三维实时建模。
————————————————–
(2)face tracking参数
依赖于你PC的CPU能力,人脸跟踪引擎使用4~8ms对一帧图像进行人脸跟踪,仅仅只依赖于CPU(没有使用GPU)。
将会影响跟踪准确率的因素:
A.光照:光线应该充足,没有太多阴影或者太强的侧光。也就是光照要均匀,并且足够。
B.距离:距离Kinect的距离,距离体感越近则跟踪效果越好,当小于1.5m时,跟踪的效果是最好的,因为越近深度数据越精确。当然也不能太近了,如果小于0.5m,深度数据都无法获取,或者人脸遮住摄像头了,也无法跟踪。
C.遮挡:戴厚眼镜或者Lincoln那样的胡子,人脸跟踪会有问题,这点还是需要改善的方面。
使用了Active Apperance Model作为二维特征跟踪器,然后我们把计算模型扩展到我们的Kinect深度数据上,然后它可以跟踪三维的人脸,这样使得它比二维特征点跟踪器稳健。AAM算法对于真实世界的情况不是很稳健。一些算法:算法AAM、AAM人脸跟踪,算法3.
————————————————–
(3)具体技术
坐标系统:
使用了Kinect的坐标系统来输出三维跟踪结果(x,y,z)。Z轴是传感器到用户的距离,Y轴是上下指向。数据都是以米为计量单位(这样一般会读出2.xxx,1.xxx之类的浮点数值),角度都是旋转角度(不是弧度)。
上图是输出的三维面具在kinect的坐标下情况。
输入图像:
彩色图像和深度图像。其实还需要一个人头和脖子的三维坐标。
————————————————–
(4)函数和结构
人脸追踪的SDK是免注册的COM对象。主要有4个COM接口
IFTFaceTracker:人脸追踪主要接口。
IFTResult:人脸追踪运算的结果。
IFTImage:图像缓冲区,类似OpenCV的Mat。
IFTModel:三维人脸模型
还有一些结构:
FT_SENSOR_DATA:包含用于人脸追踪所有所需的输入数据。
FT_CAMERA_CONFIG:包含彩色或者深度传感器的信息。
FT_VECTOR2D:二维向量。也就是(x,y),(x,y)…
FT_VECTOR3D:三维向量。
FT_TRIANGLE:三维模型人脸角度。
FT_WEIGHTED_RECT:权重矩阵。
————————————————–
(5)人脸跟踪简易流程
不论是1个人的还是2个人的追踪,都差不多。一般追踪距离摄像头比较近的人,骨骼数据可能有时无法获取(那就使用上一帧的就是,经验之谈,因为很随机)。
———————————————————————————–
更多人脸跟踪细节介绍
这里介绍一些实际开发时遇到的问题。
(1)线程问题
一般至少2个线程,微软给的例子(FaceTrackingVisualization)一个是获取彩色、深度、骨骼的线程,另一个线程直接调用使用这三个数据,把数据传给人脸跟踪模块,都没进行线程同步。我有点疑问,不同步会不会有问题,但怎么运行测试都木有问题。每次跟踪后它都Sleep(16)一下,这个数值自己可以修改,我在win32应用里使用Sleep(30)运行结果不错,而在MFC中也使用Sleep(16)。
这里还需要特别说明的是,千万小心对于原始数据的操作,也就是彩色、深度数据的操作。如果你想显示数据,请拷贝数据后另行处理,不要直接在数据上进行处理,否则会出现很麻烦的问题。你以为把数据传给跟踪模块后,就可以直接在彩色数据(IFTImage)上进行更改了,但在你更改后,人脸跟踪模块由于设置的是16ms,马上又调用人脸跟踪,丢失了人脸模型!这样会出现一种结果:人脸跟踪十分不稳定,面具随机在人脸附近跳动,人脸一旦运动快点,跟踪会发生失效。所以之后我学乖了,要对数据处理或者显示,另外拷贝一份,其实拷贝图像数据是瞬间的事情。
(2)人脸特征点
微软在face tracking介绍给了一张的人脸图像,这里我就不给图像了,因为那张人脸特征点图像不论是位置还是下标都是不符的。这里我给出我得到的图像
:
上图有121个特征点(有些点重复,有些不存在)微软的面部特征点符合Candide-3标准,我们可以在文献(Candide-3_-_an_updated_parameterised_face)中阅读到一张表Appendix A: The CANDIDE -3Vertices and the Corresponding MPEG -4 Facial Feature Points。它列出了113个顶点(下标从0开始),这些顶点的定义和程序中获取的数组一一对应,还多出来的部分可以无视掉。
这张图像是使用OpenCV显示人脸跟踪数组中的顶点结果,我们可以自己修改程序画出来,需要注意的就是把图像画大一些,否则字符串会重叠在一起。之所以我用线连起来,主要为了看上去更方便,如果一百多个数字显示在图片上,肯定没人想去研究。我把数字按顺序一次连接,发现某些点可以连接出如图所示的线段图。至于每个点到底什么意思,还是要对照表中定义。
显然这么多点,我们只取需要的点即可。这个仁者见仁智者见智吧。
下图是顶点定义的那张表:
(3)人脸旋转
获取人脸跟踪结果后,我们除了得到面部关键顶点还可以直接获取人脸朝向(Get3DPose函数)。下图是人脸朝向的定义:
Angle |
Value |
Pitch angle 0=neutral |
-90 = looking down towards the floor +90 = looking up towards the ceiling Face Tracking tracks when the user’s head pitch is less than 20 degrees, but works best when less than 10 degrees. |
Roll angle 0 = neutral |
-90 = horizontal parallel with right shoulder of subject +90 = horizontal parallel with left shoulder of the subject Face Tracking tracks when the user’s head roll is less than 90 degrees, but works best when less than 45 degrees. |
Yaw angle 0 = neutral |
-90 = turned towards the right shoulder of the subject +90 = turned towards the left shoulder of the subject Face Tracking tracks when the user’s head yaw is less than 45 degrees, but works best when less than 30 degrees |
自然如果你做简单的人头旋转头部动作识别,肯定得用到这块。如果你做人脸标表情、动作或者三维建模,也少不了使用这个旋转角度对人脸进行一些仿射变换。很显然,Roll方向上的人脸旋转可以消除掉(我在我的人脸识别中就这样)。如果你使用OpenCV进行放射旋转,要熟悉矩阵旋转公式,一旦人脸旋转,那么所有的顶点坐标都要跟着旋转,如果扣除人脸区域,那么相应的人脸顶点坐标也要减去x轴和y轴的数值。还有需要考虑一些临界情况,比如人脸从左侧移除,人脸从上方、下方走出,会不会导致程序崩溃?此时的人脸数据不可以使用。
之后还有动画单元等概念,也是可以通过函数直接获取结果,这部分我未进行研究,如果做表情或者人脸动画,则需要深入研究下。
———————————————————————————-
程序配置与说明
对于Win32程序。如果只想看效果,不想研究代码,安装好Kinect驱动后,可以直接点击Debug下的exe应用程序进行运行即可。如果想研究代码,需要配置OpenCV环境(文章链接),随便你使用OpenCV2.x某个版本,建议使用目前最新的OpenCV2.4.4。之所以使用OpenCV,是因为用它显示视频会十分简单,用到的代码也不多。
[2013-4-10]配置方法和解决xcopy方法由猪猪猪小航提供。
4.1配制方法
首先按照自己电脑上opencv的库文件地址、包含目录地址、库文件,将它们添加到相应的配置属性中
下方的OpenCV244opencv,,,,,配置在上方可添加路径区域效果是一样的,即:
包含目录配置:
或者
4.2编译程序遇到xcopy问题
如果在编译运行的时候,遇见关于“xcopy”的问题
在配置属性中生成事件->后期生成事件中删除命令行的内容就可以了
———————————————————————————–
参考网站和资源:
博客:
[shi19871987]Kinect for windows SDK1.5人脸识别与跟踪
http://blog.csdn.net/shi19871987/article/details/7748094
[yangecnu, 杨洋]Kinect for Windows SDK开发入门(十六)面部追踪上【C#】
http://www.cnblogs.com/yangecnu/archive/2012/10/12/KinectSDK_FaceTracking.html
[箫鸣] Kinect SDK 1.5 Face Tracking —> 使用opencv显示后的超级简化版本
http://blog.csdn.net/guoming0000/article/details/7607473
http://wenku.baidu.com/view/cf533d6858fafab069dc0256.html
一些官方资源:
Kinect官网
http://www.microsoft.com/zh-cn/kinectforwindows/
How To Use Kinect Face Tracking SDK
http://www.codeproject.com/Articles/394975/How-To-Use-Kinect-Face-Tracking-SDK
Candide标准
http://www.bk.isy.liu.se/candide/main.html
Face Tracking
http://msdn.microsoft.com/en-us/library/jj130970.aspx
【需要翻墙】
http://nsmoly.wordpress.com/2012/05/21/face-tracking-sdk-in-kinect-for-windows-1-5/
最后如果有任何问题,或者想一起讨论最好去我的CSDN留言,因为留言会自动发送到我的邮箱里面。也可以私信我的新浪微博。还有文章应该会不断更新,在我的网站(ilovecode.cn)上对应标题的文章会进行持续更新。欢迎大家分享相关知识,上篇kinect人脸跟踪文章写出后快一年了,中国这么大也未有人与我讨论。
———————————————————————————–
程序源代码
[2013-3-25]程序由kexin_0311进行了改进,避免了人脸走出检测后窗体假死情况。
[2013-3-27]修正了对face tracking窗口按下ESC不响应的问题(逻辑问题),去除了cout显示的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 |
// win32_KinectFaceTracking.cpp : 定义控制台应用程序的入口点。 /**************************************************** 程序用途:KinectFace Tracking简单例子 开发环境:VisualStudio 2010 win32程序 OpenCV2.4.4 显示界面库 Kinect SDK v1.6 驱动版本 Windows 7 操作系统 开发人员:箫鸣 开发时间:2013-3-11~ 2013-3-12 联系方式:weibo.com/guoming0000 guoming0000@sina.com www.ilovecode.cn 备注:另有配套相关博客文章: Kinect Face Tracking SDK[Kinect人脸跟踪] ******************************************************/ #include "stdafx.h" #include <windows.h> #include <opencv2opencv.hpp> #include <mmsystem.h> #include <assert.h> //#include <strsafe.h> #include "NuiApi.h" using namespace cv; using namespace std; //---------------------------------------------------- #define _WINDOWS #include <FaceTrackLib.h> //显示网状人脸,初始化人脸模型 HRESULT VisualizeFaceModel(IFTImage* pColorImg, IFTModel* pModel, FT_CAMERA_CONFIG const* pCameraConfig, FLOAT const* pSUCoef, FLOAT zoomFactor, POINT viewOffset, IFTResult* pAAMRlt, UINT32 color);//pColorImg为图像缓冲区,pModel三维人脸模型 //---图像大小等参数-------------------------------------------- #define COLOR_WIDTH 640 #define COLOR_HIGHT 480 #define DEPTH_WIDTH 320 #define DEPTH_HIGHT 240 #define SKELETON_WIDTH 640 #define SKELETON_HIGHT 480 #define CHANNEL 3 BYTE DepthBuf[DEPTH_WIDTH*DEPTH_HIGHT*CHANNEL]; //---人脸跟踪用到的变量------------------------------------------ IFTImage* pColorFrame,*pColorDisplay; //彩色图像数据,pColorDisplay是用于处理的深度数据 IFTImage* pDepthFrame; //深度图像数据 FT_VECTOR3D m_hint3D[2]; //头和肩膀中心的坐标 //----各种内核事件和句柄----------------------------------------------------------------- HANDLE m_hNextColorFrameEvent; HANDLE m_hNextDepthFrameEvent; HANDLE m_hNextSkeletonEvent; HANDLE m_pColorStreamHandle;//保存图像数据流的句柄,用以提取数据 HANDLE m_pDepthStreamHandle; HANDLE m_hEvNuiProcessStop;//用于结束的事件对象 //----------------------------------------------------------------------------------- //获取彩色图像数据,并进行显示 int DrawColor(HANDLE h) { const NUI_IMAGE_FRAME * pImageFrame = NULL; HRESULT hr = NuiImageStreamGetNextFrame( h, 0, &pImageFrame ); if( FAILED( hr ) ) { cout<<"Get Color Image Frame Failed"<<endl; return -1; } INuiFrameTexture * pTexture = pImageFrame->pFrameTexture; NUI_LOCKED_RECT LockedRect; pTexture->LockRect( 0, &LockedRect, NULL, 0 );//提取数据帧到LockedRect中,包括两个数据对象:pitch表示每行字节数,pBits第一个字节的地址 if( LockedRect.Pitch != 0 )//如果每行字节数不为0 { BYTE * pBuffer = (BYTE*) LockedRect.pBits;//pBuffer指向数据帧的第一个字节的地址 //该函数的作用是在LockedRect第一个字节开始的地址复制min(pColorFrame->GetBufferSize(), UINT(pTexture->BufferLen()))个字节到pColorFrame->GetBuffer()所指的缓冲区 memcpy(pColorFrame->GetBuffer(), PBYTE(LockedRect.pBits), //PBYTE表示无符号单字节数值 min(pColorFrame->GetBufferSize(), UINT(pTexture->BufferLen())));//GetBuffer()它的作用是返回一个可写的缓冲指针 //OpenCV显示彩色视频 Mat temp(COLOR_HIGHT,COLOR_WIDTH,CV_8UC4,pBuffer); imshow("ColorVideo",temp); int c = waitKey(1);//按下ESC结束 //如果在视频界面按下ESC,q,Q都会导致整个程序退出 if( c == 27 || c == 'q' || c == 'Q' ) { SetEvent(m_hEvNuiProcessStop); } } NuiImageStreamReleaseFrame( h, pImageFrame ); return 0; } //获取深度图像数据,并进行显示 int DrawDepth(HANDLE h) { const NUI_IMAGE_FRAME * pImageFrame = NULL; HRESULT hr = NuiImageStreamGetNextFrame( h, 0, &pImageFrame ); if( FAILED( hr ) ) { cout<<"Get Depth Image Frame Failed"<<endl; return -1; } INuiFrameTexture * pTexture = pImageFrame->pFrameTexture; NUI_LOCKED_RECT LockedRect; pTexture->LockRect( 0, &LockedRect, NULL, 0 ); if( LockedRect.Pitch != 0 ) { USHORT * pBuff = (USHORT*) LockedRect.pBits;//注意这里需要转换,因为每个数据是2个字节,存储的同上面的颜色信息不一样,这里是2个字节一个信息,不能再用BYTE,转化为USHORT // pDepthBuffer = pBuff; memcpy(pDepthFrame->GetBuffer(), PBYTE(LockedRect.pBits), min(pDepthFrame->GetBufferSize(), UINT(pTexture->BufferLen()))); for(int i=0;i<DEPTH_WIDTH*DEPTH_HIGHT;i++) { BYTE index = pBuff[i]&0x07;//提取ID信息 USHORT realDepth = (pBuff[i]&0xFFF8)>>3;//提取距离信息 BYTE scale = 255 - (BYTE)(256*realDepth/0x0fff);//因为提取的信息时距离信息 DepthBuf[CHANNEL*i] = DepthBuf[CHANNEL*i+1] = DepthBuf[CHANNEL*i+2] = 0; switch( index ) { case 0: DepthBuf[CHANNEL*i]=scale/2; DepthBuf[CHANNEL*i+1]=scale/2; DepthBuf[CHANNEL*i+2]=scale/2; break; case 1: DepthBuf[CHANNEL*i]=scale; break; case 2: DepthBuf[CHANNEL*i+1]=scale; break; case 3: DepthBuf[CHANNEL*i+2]=scale; break; case 4: DepthBuf[CHANNEL*i]=scale; DepthBuf[CHANNEL*i+1]=scale; break; case 5: DepthBuf[CHANNEL*i]=scale; DepthBuf[CHANNEL*i+2]=scale; break; case 6: DepthBuf[CHANNEL*i+1]=scale; DepthBuf[CHANNEL*i+2]=scale; break; case 7: DepthBuf[CHANNEL*i]=255-scale/2; DepthBuf[CHANNEL*i+1]=255-scale/2; DepthBuf[CHANNEL*i+2]=255-scale/2; break; } } Mat temp(DEPTH_HIGHT,DEPTH_WIDTH,CV_8UC3,DepthBuf); imshow("DepthVideo",temp); int c = waitKey(1);//按下ESC结束 if( c == 27 || c == 'q' || c == 'Q' ) { SetEvent(m_hEvNuiProcessStop); } } NuiImageStreamReleaseFrame( h, pImageFrame ); return 0; } //获取骨骼数据,并进行显示 int DrawSkeleton() { NUI_SKELETON_FRAME SkeletonFrame;//骨骼帧的定义 cv::Point pt[20]; Mat skeletonMat=Mat(SKELETON_HIGHT,SKELETON_WIDTH,CV_8UC3,Scalar(0,0,0)); //直接从kinect中提取骨骼帧 HRESULT hr = NuiSkeletonGetNextFrame( 0, &SkeletonFrame ); if( FAILED( hr ) ) { cout<<"Get Skeleton Image Frame Failed"<<endl; return -1; } bool bFoundSkeleton = false; for( int i = 0 ; i < NUI_SKELETON_COUNT ; i++ ) { if( SkeletonFrame.SkeletonData[i].eTrackingState == NUI_SKELETON_TRACKED ) { bFoundSkeleton = true; } } // 跟踪到了骨架 if( bFoundSkeleton ) { NuiTransformSmooth(&SkeletonFrame,NULL); for( int i = 0 ; i < NUI_SKELETON_COUNT ; i++ ) { if( SkeletonFrame.SkeletonData[i].eTrackingState == NUI_SKELETON_TRACKED ) { for (int j = 0; j < NUI_SKELETON_POSITION_COUNT; j++) { float fx,fy; NuiTransformSkeletonToDepthImage( SkeletonFrame.SkeletonData[i].SkeletonPositions[j], &fx, &fy ); pt[j].x = (int) ( fx * SKELETON_WIDTH )/320; pt[j].y = (int) ( fy * SKELETON_HIGHT )/240; circle(skeletonMat,pt[j],5,CV_RGB(255,0,0)); } // cout<<"one people"<<endl; // cv::line(skeletonMat,pt[NUI_SKELETON_POSITION_SHOULDER_CENTER],pt[NUI_SKELETON_POSITION_SPINE],CV_RGB(0,255,0)); // cv::line(skeletonMat,pt[NUI_SKELETON_POSITION_SPINE],pt[NUI_SKELETON_POSITION_HIP_CENTER],CV_RGB(0,255,0)); cv::line(skeletonMat,pt[NUI_SKELETON_POSITION_HEAD],pt[NUI_SKELETON_POSITION_SHOULDER_CENTER],CV_RGB(0,255,0)); cv::line(skeletonMat,pt[NUI_SKELETON_POSITION_HAND_RIGHT],pt[NUI_SKELETON_POSITION_WRIST_RIGHT],CV_RGB(0,255,0)); cv::line(skeletonMat,pt[NUI_SKELETON_POSITION_WRIST_RIGHT],pt[NUI_SKELETON_POSITION_ELBOW_RIGHT],CV_RGB(0,255,0)); cv::line(skeletonMat,pt[NUI_SKELETON_POSITION_ELBOW_RIGHT],pt[NUI_SKELETON_POSITION_SHOULDER_RIGHT],CV_RGB(0,255,0)); cv::line(skeletonMat,pt[NUI_SKELETON_POSITION_SHOULDER_RIGHT],pt[NUI_SKELETON_POSITION_SHOULDER_CENTER],CV_RGB(0,255,0)); cv::line(skeletonMat,pt[NUI_SKELETON_POSITION_SHOULDER_CENTER],pt[NUI_SKELETON_POSITION_SHOULDER_LEFT],CV_RGB(0,255,0)); cv::line(skeletonMat,pt[NUI_SKELETON_POSITION_SHOULDER_LEFT],pt[NUI_SKELETON_POSITION_ELBOW_LEFT],CV_RGB(0,255,0)); cv::line(skeletonMat,pt[NUI_SKELETON_POSITION_ELBOW_LEFT],pt[NUI_SKELETON_POSITION_WRIST_LEFT],CV_RGB(0,255,0)); cv::line(skeletonMat,pt[NUI_SKELETON_POSITION_WRIST_LEFT],pt[NUI_SKELETON_POSITION_HAND_LEFT],CV_RGB(0,255,0)); m_hint3D[0].x=SkeletonFrame.SkeletonData[i].SkeletonPositions[NUI_SKELETON_POSITION_SHOULDER_CENTER].x; m_hint3D[0].y=SkeletonFrame.SkeletonData[i].SkeletonPositions[NUI_SKELETON_POSITION_SHOULDER_CENTER].y; m_hint3D[0].z=SkeletonFrame.SkeletonData[i].SkeletonPositions[NUI_SKELETON_POSITION_SHOULDER_CENTER].z; m_hint3D[1].x=SkeletonFrame.SkeletonData[i].SkeletonPositions[NUI_SKELETON_POSITION_HEAD].x; m_hint3D[1].y=SkeletonFrame.SkeletonData[i].SkeletonPositions[NUI_SKELETON_POSITION_HEAD].y; m_hint3D[1].z=SkeletonFrame.SkeletonData[i].SkeletonPositions[NUI_SKELETON_POSITION_HEAD].z; // cout<<"("<<m_hint3D[0].x<<","<<m_hint3D[0].y<<","<<m_hint3D[0].z<<")"<<endl; // cout<<"("<<m_hint3D[1].x<<","<<m_hint3D[1].y<<","<<m_hint3D[1].z<<")"<<endl<<endl; } } } imshow("SkeletonVideo",skeletonMat); waitKey(1); int c = waitKey(1);//按下ESC结束 if( c == 27 || c == 'q' || c == 'Q' ) { SetEvent(m_hEvNuiProcessStop); } return 0; } DWORD WINAPI KinectDataThread(LPVOID pParam)//线程函数 { HANDLE hEvents[4] = {m_hEvNuiProcessStop,m_hNextColorFrameEvent, m_hNextDepthFrameEvent,m_hNextSkeletonEvent};//内核事件 while(1) { int nEventIdx; nEventIdx=WaitForMultipleObjects(sizeof(hEvents)/sizeof(hEvents[0]), hEvents,FALSE,100); if (WAIT_OBJECT_0 == WaitForSingleObject(m_hEvNuiProcessStop, 0)) { break; } // Process signal events if (WAIT_OBJECT_0 == WaitForSingleObject(m_hNextColorFrameEvent, 0)) { DrawColor(m_pColorStreamHandle);//获取彩色图像并进行显示 } if (WAIT_OBJECT_0 == WaitForSingleObject(m_hNextDepthFrameEvent, 0)) { DrawDepth(m_pDepthStreamHandle); } if (WAIT_OBJECT_0 == WaitForSingleObject(m_hNextSkeletonEvent, 0)) { DrawSkeleton(); } //这种方式关闭程序时可能出问题 // switch(nEventIdx) // { // case 0: // break; // case 1: // DrawColor(m_pVideoStreamHandle); // case 2: // DrawDepth(m_pDepthStreamHandle); // case 3: // DrawSkeleton(); // } } CloseHandle(m_hEvNuiProcessStop); m_hEvNuiProcessStop = NULL; CloseHandle( m_hNextSkeletonEvent ); CloseHandle( m_hNextDepthFrameEvent ); CloseHandle( m_hNextColorFrameEvent ); return 0; } int main() { m_hint3D[0].x=0;//肩膀的中心坐标,三维向量 m_hint3D[0].y=0; m_hint3D[0].z=0; m_hint3D[1].x=0;//头的中心坐标,三维向量 m_hint3D[1].y=0; m_hint3D[1].z=0; //使用微软提供的API来操作Kinect之前,要进行NUI初始化 HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX|NUI_INITIALIZE_FLAG_USES_COLOR|NUI_INITIALIZE_FLAG_USES_SKELETON); if( hr != S_OK )//Kinect提供了两种处理返回值的方式,就是判断上面的函数是否执行成功。 { cout<<"NuiInitialize failed"<<endl; return hr; } //1、Color ----打开KINECT设备的彩色图信息通道 m_hNextColorFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );//创建一个windows事件对象,创建成功则返回事件的句柄 m_pColorStreamHandle = NULL;//保存图像数据流的句柄,用以提取数据 //打开KINECT设备的彩色图信息通道,并用m_pColorStreamHandle保存该流的句柄,以便于以后读取 hr = NuiImageStreamOpen(NUI_IMAGE_TYPE_COLOR,NUI_IMAGE_RESOLUTION_640x480, 0, 2, m_hNextColorFrameEvent, &m_pColorStreamHandle); if( FAILED( hr ) ) { cout<<"Could not open image stream video"<<endl; return hr; } //2、Depth -----打开Kinect设备的深度图信息通道 m_hNextDepthFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); m_pDepthStreamHandle = NULL;//保存深度数据流的句柄,用以提取数据 //打开KINECT设备的深度图信息通道,并用m_pDepthStreamHandle保存该流的句柄,以便于以后读取 hr = NuiImageStreamOpen( NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX, NUI_IMAGE_RESOLUTION_320x240, 0, 2, m_hNextDepthFrameEvent, &m_pDepthStreamHandle); if( FAILED( hr ) ) { cout<<"Could not open depth stream video"<<endl; return hr; } //3、Skeleton -----定义骨骼信号事件句柄,打开骨骼跟踪事件 m_hNextSkeletonEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); hr = NuiSkeletonTrackingEnable( m_hNextSkeletonEvent, NUI_SKELETON_TRACKING_FLAG_ENABLE_IN_NEAR_RANGE|NUI_SKELETON_TRACKING_FLAG_ENABLE_SEATED_SUPPORT); if( FAILED( hr ) ) { cout<<"Could not open skeleton stream video"<<endl; return hr; } //4、用于结束的事件对象 m_hEvNuiProcessStop = CreateEvent(NULL,TRUE,FALSE,NULL); //5、开启一个线程---用于读取彩色、深度、骨骼数据,该线程用于调用线程函数KinectDataThread,线程函数对深度读取彩色和深度图像并进行骨骼跟踪,同时进行显示 HANDLE m_hProcesss = CreateThread(NULL, 0, KinectDataThread, 0, 0, 0); //////////////////////////////////////////////////////////////////////// //m_hint3D[0] = FT_VECTOR3D(0, 0, 0);//头中心坐标,初始化 //m_hint3D[1] = FT_VECTOR3D(0, 0, 0);//肩膀的中心坐标 pColorFrame = FTCreateImage();//彩色图像数据,数据类型为IFTImage* pDepthFrame = FTCreateImage();//深度图像数据 pColorDisplay = FTCreateImage(); //----------1、创建一个人脸跟踪实例----------------------- IFTFaceTracker* pFT = FTCreateFaceTracker();//返回数据类型为IFTFaceTracker* if(!pFT) { return -1;// Handle errors } //初始化人脸跟踪所需要的数据数据-----FT_CAMERA_CONFIG包含彩色或深度传感器的信息(长,宽,焦距) FT_CAMERA_CONFIG myCameraConfig = {COLOR_WIDTH, COLOR_HIGHT, NUI_CAMERA_COLOR_NOMINAL_FOCAL_LENGTH_IN_PIXELS}; // width, height, focal length FT_CAMERA_CONFIG depthConfig = {DEPTH_WIDTH, DEPTH_HIGHT, NUI_CAMERA_DEPTH_NOMINAL_FOCAL_LENGTH_IN_PIXELS}; //depthConfig.FocalLength = NUI_CAMERA_DEPTH_NOMINAL_FOCAL_LENGTH_IN_PIXELS; //depthConfig.Width = DEPTH_WIDTH; //depthConfig.Height = DEPTH_HIGHT;//这里一定要填,而且要填对才行!! //IFTFaceTracker的初始化, --- 人脸跟踪主要接口IFTFaceTracker的初始化 hr = pFT->Initialize(&myCameraConfig, &depthConfig, NULL, NULL); if( FAILED(hr) ) { return -2;// Handle errors } // 2、----------创建一个实例接受3D跟踪结果---------------- IFTResult* pFTResult = NULL; hr = pFT->CreateFTResult(&pFTResult); if(FAILED(hr)) { return -11; } // prepare Image and SensorData for 640x480 RGB images if(!pColorFrame) { return -12;// Handle errors } // Attach assumes that the camera code provided by the application // is filling the buffer cameraFrameBuffer //申请内存空间 pColorDisplay->Allocate(COLOR_WIDTH, COLOR_HIGHT, FTIMAGEFORMAT_UINT8_B8G8R8X8); hr = pColorFrame->Allocate(COLOR_WIDTH, COLOR_HIGHT, FTIMAGEFORMAT_UINT8_B8G8R8X8); if (FAILED(hr)) { return hr; } hr = pDepthFrame->Allocate(DEPTH_WIDTH, DEPTH_HIGHT, FTIMAGEFORMAT_UINT16_D13P3); if (FAILED(hr)) { return hr; } //填充FT_SENSOR_DATA结构,包含用于人脸追踪所需要的所有输入数据 FT_SENSOR_DATA sensorData; POINT point; sensorData.ZoomFactor = 1.0f; point.x = 0; point.y = 0; sensorData.ViewOffset = point;//POINT(0,0) bool isTracked = false;//跟踪判断条件 //int iFaceTrackTimeCount=0; // 跟踪人脸 while ( 1 ) { sensorData.pVideoFrame = pColorFrame;//彩色图像数据 sensorData.pDepthFrame = pDepthFrame;//深度图像数据 //初始化追踪,比较耗时 if(!isTracked)//为false { //会耗费较多cpu计算资源,开始跟踪 hr = pFT->StartTracking(&sensorData, NULL, m_hint3D, pFTResult);//输入为彩色图像,深度图像,人头和肩膀的三维坐标 if(SUCCEEDED(hr) && SUCCEEDED(pFTResult->GetStatus())) { isTracked = true; } else { isTracked = false; } } else { //继续追踪,很迅速,它一般使用一个已大概知晓的人脸模型,所以它的调用不会消耗多少cpu计算,pFTResult存放跟踪的结果 hr = pFT->ContinueTracking(&sensorData, m_hint3D, pFTResult); if(FAILED(hr) || FAILED (pFTResult->GetStatus())) { // 跟丢 isTracked = false; } } int bStop; if(isTracked) { IFTModel* ftModel;//三维人脸模型 HRESULT hr = pFT->GetFaceModel(&ftModel);//得到三维人脸模型 FLOAT* pSU = NULL; UINT numSU; BOOL suConverged; pFT->GetShapeUnits(NULL, &pSU, &numSU, &suConverged); POINT viewOffset = {0, 0}; pColorFrame->CopyTo(pColorDisplay,NULL,0,0);//将彩色图像pColorFrame复制到pColorDisplay中,然后对pColorDisplay进行直接处理 hr = VisualizeFaceModel(pColorDisplay, ftModel, &myCameraConfig, pSU, 1.0, viewOffset, pFTResult, 0x00FFFF00);//该函数为画网格 if(FAILED(hr)) printf("显示失败!!n"); Mat tempMat(COLOR_HIGHT,COLOR_WIDTH,CV_8UC4,pColorDisplay->GetBuffer()); imshow("faceTracking",tempMat); bStop = waitKey(1);//按下ESC结束 } else// -----------当isTracked = false时,则值显示获取到的彩色图像信息 { pColorFrame->CopyTo(pColorDisplay,NULL,0,0); Mat tempMat(COLOR_HIGHT,COLOR_WIDTH,CV_8UC4,pColorDisplay->GetBuffer()); imshow("faceTracking",tempMat); bStop = waitKey(1); } if(m_hEvNuiProcessStop!=NULL) { if( bStop == 27 || bStop == 'q' || bStop == 'Q' ) { SetEvent(m_hEvNuiProcessStop); if(m_hProcesss!=NULL) { WaitForSingleObject(m_hProcesss,INFINITE); CloseHandle(m_hProcesss); m_hProcesss = NULL; } break; } } else { break; } //这里也要判断是否m_hEvNuiProcessStop已经被激活了! Sleep(16); // iFaceTrackTimeCount++; // if(iFaceTrackTimeCount>16*1000) // break; } if(m_hProcesss!=NULL) { WaitForSingleObject(m_hProcesss,INFINITE); CloseHandle(m_hProcesss); m_hProcesss = NULL; } // Clean up. pFTResult->Release(); pColorFrame->Release(); pFT->Release(); NuiShutdown(); return 0; } //显示网状人脸,初始化人脸模型 HRESULT VisualizeFaceModel(IFTImage* pColorImg, IFTModel* pModel, FT_CAMERA_CONFIG const* pCameraConfig, FLOAT const* pSUCoef, FLOAT zoomFactor, POINT viewOffset, IFTResult* pAAMRlt, UINT32 color)//zoomFactor = 1.0f viewOffset = POINT(0,0) pAAMRlt为跟踪结果 { if (!pColorImg || !pModel || !pCameraConfig || !pSUCoef || !pAAMRlt) { return E_POINTER; } HRESULT hr = S_OK; UINT vertexCount = pModel->GetVertexCount();//面部特征点的个数 FT_VECTOR2D* pPts2D = reinterpret_cast<FT_VECTOR2D*>(_malloca(sizeof(FT_VECTOR2D) * vertexCount));//二维向量 reinterpret_cast强制类型转换符 _malloca在堆栈上分配内存 //复制_malloca(sizeof(FT_VECTOR2D) * vertexCount)个字节到pPts2D,用于存放面部特征点,该步相当于初始化 if (pPts2D) { FLOAT *pAUs; UINT auCount;//UINT类型在WINDOWS API中有定义,它对应于32位无符号整数 hr = pAAMRlt->GetAUCoefficients(&pAUs, &auCount); if (SUCCEEDED(hr)) { //rotationXYZ人脸旋转角度! FLOAT scale, rotationXYZ[3], translationXYZ[3]; hr = pAAMRlt->Get3DPose(&scale, rotationXYZ, translationXYZ); if (SUCCEEDED(hr)) { hr = pModel->GetProjectedShape(pCameraConfig, zoomFactor, viewOffset, pSUCoef, pModel->GetSUCount(), pAUs, auCount, scale, rotationXYZ, translationXYZ, pPts2D, vertexCount); //这里获取了vertexCount个面部特征点,存放在pPts2D指针数组中 if (SUCCEEDED(hr)) { POINT* p3DMdl = reinterpret_cast<POINT*>(_malloca(sizeof(POINT) * vertexCount)); if (p3DMdl) { for (UINT i = 0; i < vertexCount; ++i) { p3DMdl[i].x = LONG(pPts2D[i].x + 0.5f); p3DMdl[i].y = LONG(pPts2D[i].y + 0.5f); } FT_TRIANGLE* pTriangles; UINT triangleCount; hr = pModel->GetTriangles(&pTriangles, &triangleCount); if (SUCCEEDED(hr)) { struct EdgeHashTable { UINT32* pEdges; UINT edgesAlloc; void Insert(int a, int b) { UINT32 v = (min(a, b) << 16) | max(a, b); UINT32 index = (v + (v << 8)) * 49157, i; for (i = 0; i < edgesAlloc - 1 && pEdges[(index + i) & (edgesAlloc - 1)] && v != pEdges[(index + i) & (edgesAlloc - 1)]; ++i) { } pEdges[(index + i) & (edgesAlloc - 1)] = v; } } eht; eht.edgesAlloc = 1 << UINT(log(2.f * (1 + vertexCount + triangleCount)) / log(2.f)); eht.pEdges = reinterpret_cast<UINT32*>(_malloca(sizeof(UINT32) * eht.edgesAlloc)); if (eht.pEdges) { ZeroMemory(eht.pEdges, sizeof(UINT32) * eht.edgesAlloc); for (UINT i = 0; i < triangleCount; ++i) { eht.Insert(pTriangles[i].i, pTriangles[i].j); eht.Insert(pTriangles[i].j, pTriangles[i].k); eht.Insert(pTriangles[i].k, pTriangles[i].i); } for (UINT i = 0; i < eht.edgesAlloc; ++i) { if(eht.pEdges[i] != 0) { pColorImg->DrawLine(p3DMdl[eht.pEdges[i] >> 16], p3DMdl[eht.pEdges[i] & 0xFFFF], color, 1); } } _freea(eht.pEdges); } // 画出人脸矩形框 RECT rectFace; hr = pAAMRlt->GetFaceRect(&rectFace);//得到人脸矩形 if (SUCCEEDED(hr)) { POINT leftTop = {rectFace.left, rectFace.top};//左上角 POINT rightTop = {rectFace.right - 1, rectFace.top};//右上角 POINT leftBottom = {rectFace.left, rectFace.bottom - 1};//左下角 POINT rightBottom = {rectFace.right - 1, rectFace.bottom - 1};//右下角 UINT32 nColor = 0xff00ff; SUCCEEDED(hr = pColorImg->DrawLine(leftTop, rightTop, nColor, 1)) && SUCCEEDED(hr = pColorImg->DrawLine(rightTop, rightBottom, nColor, 1)) && SUCCEEDED(hr = pColorImg->DrawLine(rightBottom, leftBottom, nColor, 1)) && SUCCEEDED(hr = pColorImg->DrawLine(leftBottom, leftTop, nColor, 1)); } } _freea(p3DMdl); } else { hr = E_OUTOFMEMORY; } } } } _freea(pPts2D); } else { hr = E_OUTOFMEMORY; } return hr; } |
获取人脸特征点三维坐标(2013-10-10)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
IFTModel* ftModel; HRESULT hr = context->m_pFaceTracker->GetFaceModel(&ftModel),hR; if(FAILED(hr)) return false; FLOAT* pSU = NULL; UINT numSU; BOOL suConverged; FLOAT headScale,tempDistance=1; context->m_pFaceTracker->GetShapeUnits(&headScale, &pSU, &numSU, &suConverged); POINT viewOffset = {0, 0}; IFTResult* pAAMRlt = context->m_pFTResult; UINT vertexCount = ftModel->GetVertexCount();//顶点 FT_VECTOR2D* pPts2D = reinterpret_cast<FT_VECTOR2D*>(_malloca(sizeof(FT_VECTOR2D) * vertexCount)); if ( pPts2D ) { FLOAT* pAUs; UINT auCount; hr = pAAMRlt->GetAUCoefficients(&pAUs, &auCount); if (SUCCEEDED(hr)) { FLOAT scale, rotationXYZ[3], translationXYZ[3]; hr = pAAMRlt->Get3DPose(&scale, rotationXYZ, translationXYZ); //rotationXYZ是最重要的数据! if (SUCCEEDED(hr)) { hr = ftModel->GetProjectedShape(&m_videoConfig, 1.0, viewOffset, pSU, ftModel->GetSUCount(), pAUs, auCount, scale, rotationXYZ, translationXYZ, pPts2D, vertexCount); FT_VECTOR3D* pPts3D = reinterpret_cast<FT_VECTOR3D*>(_malloca(sizeof(FT_VECTOR3D) * vertexCount)); hR = ftModel->Get3DShape(pSU,ftModel->GetSUCount(),pAUs,ftModel->GetAUCount(),scale,rotationXYZ,translationXYZ,pPts3D,vertexCount); if (SUCCEEDED(hr)&&SUCCEEDED(hR)) { //pPts3D就是三维坐标了,后面随你玩了~~ |