Kinect SDK——Face Tracking

转载地址:http://blog.csdn.net/yangtrees/article/details/8702778 (小熊不去实验室)

上半部分:Face Tracking 流程

在Kinect for Windows SDK中,搭载了一个Face Tracking SDK,可以供用户实时地跟踪人脸。详细信息可以见MSDN。在Kinect for Windows Developer Toolkit v1.5.0及以后版本中,还提供了一个Face Tracking Visualization的例子,其中的代码写得非常清晰,也推荐阅读一下。


要在C++中使用这个Face Tracking SDK很简单,只需声明 #include <FaceTrackLib.h>即可。然后将相应的目录加入到项目配置中,包括include目录和lib目录。如我的机器上的目录地址分别为C:\Program Files\Microsoft SDKs\Kinect\Developer Toolkit v1.5.0\inc和C:\Program Files\Microsoft SDKs\Kinect\Developer Toolkit v1.5.0\Lib\x86。之后还要将FaceTrackLib.lib作为依赖项加入。


文档中提到可以将这个头文件中用来检查是否定义了_WINDOWS的宏指令注释掉,这样在编译时就可以无需定义_WINDOWS。不过由于我们要通过kinect采集数据,而Kinect SDK本身也需要包含<windows.h>头文件,所以我们无需修改头文件,只要记得在编译选项里(Configuration Properties, C/C++, Preprocessor)将_CONSOLE更改为_WINDOWS即可。


在Face Tracking SDK中,主要包含四个类,分别是IFTFaceTracker, IFTImage, IFTResult, IFTModel,从名称基本就能看出来是做什么用的了。

IFTFaceTracker:是脸部跟踪的主要引擎。

IFTImage:主要是用于封装各种图像数据流以供使用,比如IFTFaceTracker跟踪时使用的彩色图像和深度图像就要通过IFTImage封装为输入数据。

IFTResultIFTModel:都是从IFTFaceTracker创建出的用于保存结果的类。前者保存了跟踪结果,后者保存了根据跟踪结果所构造的一个泛化的脸部模型。


在实际使用中,首先调用FTCreateImage创建两个IFTImage实例,分别存放彩色图像和深度图像数据。

IFTImage提供了两种初始化数据方法,分别是Allocate和Attach。前者为自己分配了存放数据的内存空间,可以通过调用Release来释放,在调用Reset时也会先释放内存空间;后者使用外部的存储空间,用户需要自己管理这部分内存。在准备好了图像数据后,实例化一个FT_SENSOR_DATA结构体,将这两个IFTImage作为其成员。这个FT_SENSOR_DATA对象就是要用于进行脸部跟踪的输入数据了。需要注意的是,Face Tracking SDK默认输入的图像数据是Kinect输出的数据,即深度图像坐标未与彩色图像对齐,且没有滤除后三位的player index。


然后需要调用FTCreateFaceTracker来创建一个IFTFaceTracker实例,之后的操作都是围绕这个实例进行。要开始脸部追踪过程,只需简单地调用StartTracking。这个函数本身开销比较大,因为它需要在整张图像中检测脸部位置,所以可以通过在参数中指定一些hints来减小开销,这包括利用pROI指定搜索区域,以及headPoints[2]指定脸部的pose方向。也可以将这些参数都留空,这样就会在整个图像上寻找脸部作为跟踪对象。这里我不确定脸部pose方向是否由Kinect skeleton data获取。


当StartTracking成功后,我们就可以使用ContinueTracking来继续跟踪。这个函数的开销要小于StartTracking,所以在获得跟踪对象后,应该一直调用这个函数进行跟踪而不应使用StartTracking,直到跟踪失败或停止跟踪后,要重新开始跟踪时,才应该使用StartTracking,并在获得新的跟踪对象后继续使用ContinueTracking来跟踪。只有在某些极端情况下,比如帧率非常低,或者脸部在帧间的移动速度非常快,使得ContinueTracking几乎不可能,这时才会考虑在每帧上都使用StartTracking。


有些时候,图像中存在多个要跟踪的脸部。这时可以先调用DetectFaces,然后为每个要跟踪的脸部都创建一个IFTFaceTracker实例。


在跟踪成功后,可以利用IFTResult和IFTModel来查看跟踪结果。IFTResult需要调用IFTFaceTracker::CreateFTResult来创建,用于储存StartTracking和ContinueTracking的结果。调用其GetStatus函数,返回S_OK表示脸部跟踪是否成功,这时可以进一步通过GetFaceRect获取所跟踪的脸部在图像坐标系下的位置,以及通过Get2DShapePoints获得脸部100个关键点的2D坐标。IFTModel可以通过 IFTFaceTracker::GetFaceModel创建,它是用于将跟踪到的脸部由2D图像转换为摄像机空间中的3D网格。具体的就不在这篇赘述了。


然后贴一下我自己的一个FaceTracker抽象类。程序中在实例化了其子类后,首先调用Init函数,成功后调用Track函数即可追踪,通过几个Get函数可以获得相应的状态和数据。三个纯虚函数中,InitConfig用于配置colorConfig和depthConfig,可以参见Kinect关于FT_CAMERA_CONFIG结构体的说明;SetColorFrame和SetDepthFrame用于更新跟踪过程中每帧的彩色和深度图像数据。

头文件:

  1. #ifndef __FACE_TRACKER_H__  
  2. #define __FACE_TRACKER_H__  
  3.   
  4.   
  5. #include <FaceTrackLib.h>  
  6. #include <stdint.h>  
  7.   
  8.   
  9. const uint32_t FRAME_WIDTH = 640;  
  10. const uint32_t FRAME_HEIGHT = 480;  
  11.   
  12.   
  13. class FaceTracker  
  14. {  
  15. public:  
  16.     FaceTracker(void);  
  17.     virtual ~FaceTracker(void);  
  18.   
  19.   
  20.     virtual bool Init(void);  
  21.     virtual bool Track(void);  
  22.   
  23.   
  24.     bool            GetTrackStatus(voidconst { return ( isTracked && SUCCEEDED(pFTResult->GetStatus()) ); };  
  25.     IFTResult*      GetResult(void)      const { return pFTResult; };  
  26.     IFTImage*       GetColorImage(void)  const { return pColorFrame; };  
  27.     IFTImage*       GetDepthImage(void)  const { return pDepthFrame; };  
  28.     IFTFaceTracker* GetTracker(void)     const { return tracker; };  
  29.     RECT            GetFaceRect(void)    const ;  
  30.   
  31.   
  32.     //  
  33.     // PURE VIRTUAL functions.  
  34.     virtual bool InitConfig(void)    = 0;  
  35.     virtual bool SetColorFrame(void) = 0;  
  36.     virtual bool SetDepthFrame(void) = 0;  
  37.   
  38.   
  39. private:  
  40.     IFTFaceTracker* tracker;  
  41.     IFTResult* pFTResult;  
  42.     IFTImage* pColorFrame;  
  43.     IFTImage* pDepthFrame;  
  44.     bool isTracked;  
  45. public:  
  46.     FT_SENSOR_DATA* sensorData;   
  47.     FT_CAMERA_CONFIG colorConfig; // width, height, focal length  
  48.     FT_CAMERA_CONFIG depthConfig; // width, height, focal length  
  49. };  
  50. #endif  
  1. Cpp文件:  
  1. #include "FaceTracker.h"  
  2.   
  3.   
  4. FaceTracker::FaceTracker(void)  
  5. {  
  6.     tracker = NULL;  
  7.     pFTResult = NULL;  
  8.     pColorFrame = NULL;  
  9.     pDepthFrame = NULL;  
  10.     sensorData = NULL;  
  11.   
  12.   
  13.     isTracked = false;  
  14. }  
  15.   
  16.   
  17. FaceTracker::~FaceTracker(void)  
  18. {  
  19.     if (NULL != pFTResult) {  
  20.         pFTResult->Release();  
  21.     }  
  22.     if (NULL != pColorFrame) {  
  23.         pColorFrame->Release();  
  24.     }  
  25.     if (NULL != pDepthFrame) {  
  26.         pDepthFrame->Release();  
  27.     }  
  28.     if (NULL != tracker) {  
  29.         tracker->Release();  
  30.     }  
  31.     if (NULL != sensorData) {  
  32.         delete sensorData;  
  33.     }  
  34. }  
  35.   
  36.   
  37. bool FaceTracker::Init(void)  
  38. {  
  39.     // prepare Image and Depth for 640x480 RGB images  
  40.     pColorFrame = FTCreateImage();  
  41.     pDepthFrame =  FTCreateImage();  
  42.     if (!pColorFrame || !pDepthFrame) {  
  43.         return false;  
  44.     }  
  45.     pColorFrame->Allocate(FRAME_WIDTH, FRAME_HEIGHT, FTIMAGEFORMAT_UINT8_B8G8R8X8);  
  46.     pDepthFrame->Allocate(FRAME_WIDTH, FRAME_HEIGHT, FTIMAGEFORMAT_UINT16_D13P3);  
  47.   
  48.   
  49.     if ( this->InitConfig() ) {  
  50.   
  51.   
  52.         // Create an instance of face tracker  
  53.         tracker = FTCreateFaceTracker();  
  54.   
  55.   
  56.         HRESULT hr = tracker->Initialize(&colorConfig, &depthConfig, 0, 0);  
  57.         if (FAILED(hr)) {  
  58.             return false;  
  59.         }  
  60.   
  61.   
  62.         // Create IFTResult to hold a face tracking result  
  63.         hr = tracker->CreateFTResult(&pFTResult);  
  64.         if(FAILED(hr)) {  
  65.             return false;  
  66.         }  
  67.     }  
  68.       
  69.     sensorData = new FT_SENSOR_DATA(pColorFrame, pDepthFrame);  
  70.   
  71.   
  72.     return true;  
  73. }  
  74.   
  75.   
  76. bool FaceTracker::Track(void)  
  77. {  
  78.     bool success = true;  
  79.   
  80.   
  81.     // Call your subclass method to fill the data buffer  
  82.     success &= this->SetColorFrame();  
  83.     success &= this->SetDepthFrame();  
  84.     if ( !success ) {  
  85.         isTracked = false;  
  86.         return false;  
  87.     }  
  88.   
  89.   
  90.     HRESULT hr;  
  91.     // Check if we are already tracking a face  
  92.     if ( !isTracked ) {  
  93.         // Initiate face tracking. This call is more expensive and searches the input image for a face.  
  94.         hr = tracker->StartTracking(sensorData, NULL, NULL, pFTResult);  
  95.         if( SUCCEEDED(hr) && SUCCEEDED(pFTResult->GetStatus()) ) {  
  96.             isTracked = true;  
  97.         }  
  98.         else {  
  99.             // Handle errors  
  100.             isTracked = false;  
  101.         }  
  102.     }  
  103.     else {  
  104.         // Continue tracking. It uses a previously known face position, so it is an inexpensive call.  
  105.         hr = tracker->ContinueTracking(sensorData, NULL, pFTResult);  
  106.         if( FAILED(hr) || FAILED (pFTResult->GetStatus()) ) {  
  107.             // Handle errors  
  108.             isTracked = false;  
  109.         }  
  110.         else {  
  111.             isTracked = true;  
  112.         }  
  113.     }  
  114.     return isTracked;  
  115. }  
  116.   
  117.   
  118. RECT FaceTracker::GetFaceRect(voidconst  
  119. {  
  120.     RECT rect;  
  121.   
  122.   
  123.     if ( this->GetTrackStatus() ) {  
  124.         pFTResult->GetFaceRect(&rect);  
  125.     }  
  126.     return rect;  
  127. }  


下半部分:IFTModel

在上一篇中,介绍了利用Face Tracking SDK来跟踪人脸的流程,并介绍了所涉及的四个类,其中IFTResult和IFTModel是用来查看跟踪结果的两个类。前者记录了脸部100个关键点的2D坐标,而后者则是用于将跟踪到的脸部由2D图像转换为摄像机空间中的3D网格。本篇主要就讨论一下IFTModel这个类。


IFTModel使用的是Candide3模型的一个子集,包括11个Shape Units和六个Animation Units。其中SU描述了脸部某些区域的形状,比如嘴、鼻子、眼睛的中点位置;而AU可以理解为描述了脸部肌肉的运动,换句话说,通过使用不同的AU参数,可以构造出不同的脸部表情。当所有的AU均为0时,就是所谓的neutral face完全无表情的脸部;如果同时所有的SU也均为0,则得到的就是标准的脸部模型。


MSDN中,详细描述了各个SU和AU所对应的脸部特征与脸部运动,这里不再赘述。


之前提到过IFTModel需要通过IFTFaceTracker::GetFaceModel创建,然后就可以在跟踪成功后读取模型的参数来查看模型。这里需要注意的一点,IFTModel本身并不保存真正的模型,而是通过SU和AU对标准的脸部模型进行变形,从而得到个人的脸部模型。所以在IFTModel中,提供的主要是Get类函数,而并未提供直接编辑模型的Set类函数。其中最主要的三个函数分别是Get3DShape, GetTriangles, GetProjectedShape。第一个函数返回一个3D顶点坐标数组,也就是变形后的个人脸部模型上的各个顶点的3D坐标。第二个函数返回模型上的三角形面片数组,其中每个三角形面片表示为其三个顶点的索引值。通过这两个函数,就可以将变形后的脸部模型输出为3D文件格式,然后使用一些3D模型查看器来观看模型。我这里给出一个输出为obj文件的简单例子。


  1. void SaveModel( const IFTModel* model,   
  2.                 const float* pSUs, uint32_t suCount,  
  3.                 const float* pAUs, uint32_t auCount,  
  4.                 float scale, const float rotationXYZ[3], const float translationXYZ[3] )  
  5. {  
  6.     uint32_t vertexCount = model->GetVertexCount();  
  7.     FT_VECTOR3D* pVertices = new FT_VECTOR3D[vertexCount];  
  8.     model->Get3DShape(pSUs, suCount, pAUs, auCount, scale, rotationXYZ, translationXYZ, pVertices, vertexCount);  
  9.   
  10.     uint32_t triangleCount = 0;  
  11.     FT_TRIANGLE* pTriangles = NULL;  
  12.     model->GetTriangles(&pTriangles, &triangleCount);  
  13.   
  14.     FILE* fobj = NULL;  
  15.     fopen_s(&fobj, "myself.obj""w");  
  16.     fprintf(fobj, "# %u vertices, # %u faces\n", vertexCount, triangleCount);  
  17.     for (uint32_t vi = 0; vi < vertexCount; ++vi) {  
  18.         fprintf(fobj, "v %f %f %f\n", pVertices[vi].x, pVertices[vi].y, pVertices[vi].z);  
  19.     }  
  20.     for (uint32_t ti = 0; ti < triangleCount; ++ti) {  
  21.         fprintf(fobj, "f %d %d %d\n", triangles[ti].i+1, triangles[ti].j+1, triangles[ti].k+1);  
  22.     }  
  23.     fclose(fobj);  
  24.   
  25.     delete[] pVertices;  
  26.   
  27.     return;  
  28. }  

GetProjectedShape函数的功能则是计算并输出3D顶点投影到图像平面上的位置。我们知道计算机视觉中有世界坐标系、摄像机坐标系、图像坐标系。简化起见可以令世界坐标系与摄像机坐标系重合,通过Get3DShape计算出的各顶点坐标即位于摄像机坐标系下。通过利用摄像机参数,可以计算空间点投影到成像平面后的坐标,也就是GetProjectedShape所计算出的各点坐标。显然该函数输出各点与Get3DShape输出各点是对应的。不过该函数并不会考虑投影点是否会被遮挡,也就是说,3D场景中在当前视点下无法看到的点,同样会被投影到2D平面上。在实际使用中,由于 Candide 模型的顶点基本都位于脸部正面,所以这个问题其实并不会带来太大的影响。在Face Tracking SDK提供的将IFTModel显示在图像中的例子里,也并没有针对这点做特殊处理。但在利用该函数的结果时,还是应该意识到这个问题的存在。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值