Kinect人脸跟踪Kinect Face Tracking SDK
本文持续维护地址:http://guoming.me/kinect-face-tracking
箫鸣琴奏_CPP程序侠
相关资料免费积分下载链接(资源不是最新的,请复制下面代码替换相应的文件)
更新:
[2013-4-10]编译程序时遇到xcopy问题(error MSB3073),猪猪猪小航给出了他的解决办法(第4节)。
[2013-3-25]程序源代码由kexin_0311进行了改进,避免了人脸走出检测后窗体假死情况(第6节)。
目录:
1.前言
2.人脸跟踪概念
(1)软件硬件配置
(2)face tracking参数
(3)具体技术
(4)函数和结构
(5)人脸跟踪简易流程
3.更多人脸跟踪细节介绍
(1)线程问题
(2)人脸特征点
(3)人脸旋转
4.程序配置与说明
5.参考资源
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上也能运作。
--------------------------------------------------
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三维实时建模。
--------------------------------------------------
依赖于你PC的CPU能力,人脸跟踪引擎使用4~8ms对一帧图像进行人脸跟踪,仅仅只依赖于CPU(没有使用GPU)。
将会影响跟踪准确率的因素:
A.光照:光线应该充足,没有太多阴影或者太强的侧光。也就是光照要均匀,并且足够。
B.距离:距离Kinect的距离,距离体感越近则跟踪效果越好,当小于1.5m时,跟踪的效果是最好的,因为越近深度数据越精确。当然也不能太近了,如果小于0.5m,深度数据都无法获取,或者人脸遮住摄像头了,也无法跟踪。
C.遮挡:戴厚眼镜或者Lincoln那样的胡子,人脸跟踪会有问题,这点还是需要改善的方面。
使用了Active Apperance Model作为二维特征跟踪器,然后我们把计算模型扩展到我们的Kinect深度数据上,然后它可以跟踪三维的人脸,这样使得它比二维特征点跟踪器稳健。AAM算法对于真实世界的情况不是很稳健。一些算法:算法AAM、AAM人脸跟踪,算法3.
--------------------------------------------------
坐标系统:
使用了Kinect的坐标系统来输出三维跟踪结果(x,y,z)。Z轴是传感器到用户的距离,Y轴是上下指向。数据都是以米为计量单位(这样一般会读出2.xxx,1.xxx之类的浮点数值),角度都是旋转角度(不是弧度)。
上图是输出的三维面具在kinect的坐标下情况。
输入图像:
彩色图像和深度图像。其实还需要一个人头和脖子的三维坐标。
--------------------------------------------------
人脸追踪的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:权重矩阵。
--------------------------------------------------
不论是1个人的还是2个人的追踪,都差不多。一般追踪距离摄像头比较近的人,骨骼数据可能有时无法获取(那就使用上一帧的就是,经验之谈,因为很随机)。
-----------------------------------------------------------------------------------
这里介绍一些实际开发时遇到的问题。
一般至少2个线程,微软给的例子(FaceTrackingVisualization)一个是获取彩色、深度、骨骼的线程,另一个线程直接调用使用这三个数据,把数据传给人脸跟踪模块,都没进行线程同步。我有点疑问,不同步会不会有问题,但怎么运行测试都木有问题。每次跟踪后它都Sleep(16)一下,这个数值自己可以修改,我在win32应用里使用Sleep(30)运行结果不错,而在MFC中也使用Sleep(16)。
这里还需要特别说明的是,千万小心对于原始数据的操作,也就是彩色、深度数据的操作。如果你想显示数据,请拷贝数据后另行处理,不要直接在数据上进行处理,否则会出现很麻烦的问题。你以为把数据传给跟踪模块后,就可以直接在彩色数据(IFTImage)上进行更改了,但在你更改后,人脸跟踪模块由于设置的是16ms,马上又调用人脸跟踪,丢失了人脸模型!这样会出现一种结果:人脸跟踪十分不稳定,面具随机在人脸附近跳动,人脸一旦运动快点,跟踪会发生失效。所以之后我学乖了,要对数据处理或者显示,另外拷贝一份,其实拷贝图像数据是瞬间的事情。
微软在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显示人脸跟踪数组中的顶点结果,我们可以自己修改程序画出来,需要注意的就是把图像画大一些,否则字符串会重叠在一起。之所以我用线连起来,主要为了看上去更方便,如果一百多个数字显示在图片上,肯定没人想去研究。我把数字按顺序一次连接,发现某些点可以连接出如图所示的线段图。至于每个点到底什么意思,还是要对照表中定义。
显然这么多点,我们只取需要的点即可。这个仁者见仁智者见智吧。
下图是顶点定义的那张表:
获取人脸跟踪结果后,我们除了得到面部关键顶点还可以直接获取人脸朝向(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,是因为用它显示视频会十分简单,用到的代码也不多。
首先按照自己电脑上opencv的库文件地址、包含目录地址、库文件,将它们添加到相应的配置属性中
下方的OpenCV244\opencv\,,,,,配置在上方可添加路径区域效果是一样的,即:
包含目录配置:
或者
如果在编译运行的时候,遇见关于“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进行了改进,避免了人脸走出检测后窗体假死情况。
// 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
[email protected]
www.ilovecode.cn
备注:另有配套相关博客文章:
Kinect Face Tracking SDK[Kinect人脸跟踪]
******************************************************/
#include "stdafx.h"
#include
#include
#include
#include
//#include
#include "NuiApi.h"
using namespace cv;
using namespace std;
//----------------------------------------------------
#define _WINDOWS
#include
//显示网状人脸,初始化人脸模型
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"<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"<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>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"<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(_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(_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(_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;
}
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(_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(_malloca(sizeof(FT_VECTOR3D) * vertexCount));
hR = ftModel->Get3DShape(pSU,ftModel->GetSUCount(),pAUs,ftModel->GetAUCount(),scale,rotationXYZ,translationXYZ,pPts3D,vertexCount);
if (SUCCEEDED(hr)&&SUCCEEDED(hR))
{。。。。。。。。。