人脸旋转 《转》

Posted wainiwann

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了人脸旋转 《转》相关的知识,希望对你有一定的参考价值。

近期接触了一下四元数,目的是要获取人脸朝向,看到这篇文章才发现了一些线索,所以记录一下。

-----------------------------------------------------------------------

更新一下进展:

自从发现了下面的 pitch roll yaw 对照,基本上也算是了解了个大概了。

然后就又发现了下面的文章:

原贴地址:https://www.amobbs.com/thread-5504669-1-1.html

四元数转换成欧拉角,pitch角范围问题?

用下面的语句将四元数[w x y z]转换成欧拉角roll,pitch,yaw

Roll  = atan2(2 * (w * z + x * y) , 1 - 2 * (z * z + x * x));      
Pitch = asin(2 * (w * x - y * z));
Yaw  = atan2(2 * (w * y + z * x) , 1 - 2 * (x * x + y * y));

 

Roll和Pitch的范围是-180到180度,Pitch用asin计算范围只有-90到90度。
最大的问题时当Pitch(超过)90度,也就是说88度89度90度,然后递减又是89度88度87度,这个90度的分解处,roll和yaw角会有一个180度的转变(假定原来roll和yaw都是0,在pitch角90度的分解处会突然变成180度)
请问这个该如何解决?
感谢~~~

----------------------------------------------------------------------

从上面的文章大致找到了获取角度的处理,然而获取角度之后大概应该也就知道朝向的问题了。有很多的突破!

(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轴的数值。还有需要考虑一些临界情况,比如人脸从左侧移除,人脸从上方、下方走出,会不会导致程序崩溃?此时的人脸数据不可以使用。

之后还有动画单元等概念,也是可以通过函数直接获取结果,这部分我未进行研究,如果做表情或者人脸动画,则需要深入研究下。

==================================================================================================

原贴地址:http://brightguo.com/kinect-face-tracking/

 =================================================================================================

// 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在堆栈上分配内存
    //以上是关于人脸旋转 《转》的主要内容,如果未能解决你的问题,请参考以下文章

处理屏幕旋转上的片段重复(带有示例代码)

使用 ActionBar 旋转 Android 的双片段

[转]7行Python代码的人脸识别

旋转人脸检测

Butterknife 片段旋转给出 NullPointer

在 JavaScript 中旋转人脸 (Node.js 10)