小熊不去实验室

上半部分: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封装为输入数据。

IFTResult和IFTModel:都是从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用于更新跟踪过程中每帧的彩色和深度图像数据。

头文件:

#ifndef __FACE_TRACKER_H__#define __FACE_TRACKER_H__#include <FaceTrackLib.h>#include <stdint.h>const uint32_t FRAME_WIDTH = 640;const uint32_t FRAME_HEIGHT = 480;class FaceTracker{public: FaceTracker(void); virtual ~FaceTracker(void); virtual bool Init(void); virtual bool Track(void); bool GetTrackStatus(void) const { return ( isTracked && SUCCEEDED(pFTResult->GetStatus()) ); }; IFTResult* GetResult(void) const { return pFTResult; }; IFTImage* GetColorImage(void) const { return pColorFrame; }; IFTImage* GetDepthImage(void) const { return pDepthFrame; }; IFTFaceTracker* GetTracker(void) const { return tracker; }; RECT GetFaceRect(void) const ; ////////////////////////////////////// // PURE VIRTUAL functions. virtual bool InitConfig(void) = 0; virtual bool SetColorFrame(void) = 0; virtual bool SetDepthFrame(void) = 0;private: IFTFaceTracker* tracker; IFTResult* pFTResult; IFTImage* pColorFrame; IFTImage* pDepthFrame; bool isTracked;public: FT_SENSOR_DATA* sensorData; FT_CAMERA_CONFIG colorConfig; // width, height, focal length FT_CAMERA_CONFIG depthConfig; // width, height, focal length};#endifCpp文件:#include "FaceTracker.h"FaceTracker::FaceTracker(void){ tracker = NULL; pFTResult = NULL; pColorFrame = NULL; pDepthFrame = NULL; sensorData = NULL; isTracked = false;}FaceTracker::~FaceTracker(void){ if (NULL != pFTResult) { pFTResult->Release(); } if (NULL != pColorFrame) { pColorFrame->Release(); } if (NULL != pDepthFrame) { pDepthFrame->Release(); } if (NULL != tracker) { tracker->Release(); } if (NULL != sensorData) { delete sensorData; }}bool FaceTracker::Init(void){ // prepare Image and Depth for 640×480 RGB images pColorFrame = FTCreateImage(); pDepthFrame = FTCreateImage(); if (!pColorFrame || !pDepthFrame) { return false; } pColorFrame->Allocate(FRAME_WIDTH, FRAME_HEIGHT, FTIMAGEFORMAT_UINT8_B8G8R8X8); pDepthFrame->Allocate(FRAME_WIDTH, FRAME_HEIGHT, FTIMAGEFORMAT_UINT16_D13P3); if ( this->InitConfig() ) { // Create an instance of face tracker tracker = FTCreateFaceTracker(); HRESULT hr = tracker->Initialize(&colorConfig, &depthConfig, 0, 0); if (FAILED(hr)) { return false; } // Create IFTResult to hold a face tracking result hr = tracker->CreateFTResult(&pFTResult); if(FAILED(hr)) { return false; } } sensorData = new FT_SENSOR_DATA(pColorFrame, pDepthFrame); return true;}bool FaceTracker::Track(void){ bool success = true; // Call your subclass method to fill the data buffer success &= this->SetColorFrame(); success &= this->SetDepthFrame(); if ( !success ) { isTracked = false; return false; } HRESULT hr; // Check if we are already tracking a face if ( !isTracked ) { // Initiate face tracking. This call is more expensive and searches the input image for a face. hr = tracker->StartTracking(sensorData, NULL, NULL, pFTResult); if( SUCCEEDED(hr) && SUCCEEDED(pFTResult->GetStatus()) ) { isTracked = true; } else { // Handle errors isTracked = false; } } else { // Continue tracking. It uses a previously known face position, so it is an inexpensive call. hr = tracker->ContinueTracking(sensorData, NULL, pFTResult); if( FAILED(hr) || FAILED (pFTResult->GetStatus()) ) { // Handle errors isTracked = false; } else { isTracked = true; } } return isTracked;}RECT FaceTracker::GetFaceRect(void) const{ RECT rect; if ( this->GetTrackStatus() ) { pFTResult->GetFaceRect(&rect); } return rect;}

下半部分:IFTModel真正的强者,不是流泪的人,而是含泪奔跑的人。

小熊不去实验室

相关文章:

你感兴趣的文章:

标签云: