在前面一篇文章中,我们使用Emgu来识别人的脸部,当时的Kinect SDK版本是1.0,五月份发布1.5版本的SDK之后,我们就能够直接使用Kinect实现人脸识别,而不需要借助第三方类库。

    SDK1.5中新增了人脸识别类库:Microsoft.Kinect.Toolkit.FaceTracking使得在Kinect中进行人脸识别变得简单,该类库的源代码也在Developer Toolkit中。在Developer Toolkit中也自带人脸识别的例子,您也可以打开运行或者查看源代码。

Developer Toolkit face tracking examplew

     本文使用一个简单的例子来演示如何使用Kinect中的面部追踪功能。本例子实现的简单功能是使用WPF来绘制一个简单的人脸图,然后使用面部追踪来让这个图动起来,这里面只使用了Kinect中获取面部的一些简单的信息,用来演示如何使用Kinect来进行面部追踪,所以您如果想要实现人脸的三维展现,或者绘制脸部三角形面的话,您可以参考Developer Toolkit中的自带例子。

 

一、开发前准备

    要使用面部追踪功能,Kinect SDK版本应该至少是1.5,最新版本为1.6,您可以参考之前的那篇文章下载安装,Kinect SDK 和 Kinect Developer Toolkit要一起安装,我的机器上装的是最新的SDK1.6版本。

    安装Kinect Developer Toolkit会安装Kinect Studio、一些C#/VB.Net/C++的应用程序示例、源码以及两个用于面部追踪的类库FaceTrackData.dll ,FaceTrackLib.dll包括32位和64位版本,安装好了之后,这些dll应该都在该目录下面 :

Kinect SDK folderw

    如果用C++开发的话,可以直接在项目中使用着两个dll,否则,如果使用.NET开发的话,还需要将这些dll包装成托管代码。

    幸运的是, Developer Toolkit中提供了两个使用.NET来实现面部追踪的代码,我们可以直接使用其替我们包装好了dll,该类库的源码也可以看到。 打开Developer Toolkit Browser 找到Component示例,然后安装,之后浏览所在目录,应该能够找到下面两个dll,不安装也可以,您也可以直接到SDK目录中查找这两个dll,在我的及其上目录为C:\Program Files\Microsoft SDKs\Kinect\Developer Toolkit v1.6.0\Samples\bin:

  • Microsoft.Kinect.Toolkit
  • Microsoft.Kinect.Toolkit.FaceTracking

    这两个dll的源码可以在示例中找到,引用了这两个dll之后,还需要将FaceTrackData.dll和FaceTrackLib.dll拷贝到项目中,并保证他和exe在同一个目录下面。

Kinect SDK facetrackingw

二、创建WPF项目

    新建一个名为KinectFaceTracking的WPF项目,然后引用Microsoft.Kinect ,Microsoft.Kinect.Toolkit.FaceTracking 和Microsoft.Kinect.Toolkit这三个dll。

2.1 初始化KinectSensor和FaceTracker

    要使用面部追踪,在开启KinectSensor之后需要启用ColorImageStream,  DepthImageStream 和SkeletonStream这三个Stream,部分代码如下:

//启用必要的三个Stream
kinectSensor.ColorStream.Enable();
kinectSensor.DepthStream.Enable(DepthImageFormat.Resolution80x60Fps30);

kinectSensor.SkeletonStream.TrackingMode = SkeletonTrackingMode.Seated;
kinectSensor.SkeletonStream.Enable(new TransformSmoothParameters() { Correction = 0.5f, JitterRadius = 0.05f, MaxDeviationRadius = 0.05f, Prediction = 0.5f, Smoothing = 0.5f });

// 监听流事件.
kinectSensor.AllFramesReady +=kinectSensor_AllFramesReady;

// 初始化数据
colorPixelData = new byte[kinectSensor.ColorStream.FramePixelDataLength];
depthPixelData = new short[kinectSensor.DepthStream.FramePixelDataLength];
skeletonData = new Skeleton[6];

//打开Kinect
kinectSensor.Start();

//初始化人脸识别
faceTracker = new FaceTracker(kinectSensor);    

    前面的代码应该都好懂,值得一提的是最后一句,初始化FaceTracker 对象。在初始化景深数据流时,我们使用最低分辨率的景深影像数据流,这样fps会更高,使得结果更加流畅. 最后,我们注册了AllFramesReady事件。

void kinectSensor_AllFramesReady(objectsender, AllFramesReadyEventArgse) 

       {
           // 面部追踪逻辑……
       }

2.2获取脸部数据帧

    在Developer Toolkit中,脸部数据帧使用FaceTrackFrame表示。 为了获取数据,我们需要调用FaceTracker对象的Track方法并传入我们从Kinect中获取的数据,然后FaceTracker对象会对这些数据进行处理来进行面部追踪,部分代码如下:

 

// 获取每一帧数据,然后存储到变量中来
using (ColorImageFrame colorImageFrame = e.OpenColorImageFrame())
{
    if (colorImageFrame == null)
        return;
    colorImageFrame.CopyPixelDataTo(colorPixelData);
}

using (DepthImageFrame depthImageFrame = e.OpenDepthImageFrame())
{
    if (depthImageFrame == null)
        return;
    depthImageFrame.CopyPixelDataTo(depthPixelData);
}

using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
{
    if (skeletonFrame == null)
        return;
    skeletonFrame.CopySkeletonDataTo(skeletonData);
}

//获取处于跟踪状态的骨骼数据对象,如果没有,则返回.
var skeleton = skeletonData.FirstOrDefault(s => s.TrackingState == SkeletonTrackingState.Tracked);
if (skeleton == null)
    return;

   前面的都是数据准备阶段,一旦数据都获取了之后,我们使用FaceTracker对象来对这些数据进行识别,提取面部信息。

// 面部追踪
FaceTrackFrame faceFrame = faceTracker.Track(kinectSensor.ColorStream.Format, 
                                             colorPixelData,
                                             kinectSensor.DepthStream.Format, 
                                             depthPixelData,
                                             skeleton);

    faceFrame是一个FaceTrackFrame类型的变量,代表面部追踪的处理结果,该对象的更详细信息,您可以查看 MSDN ,下面是该对象的OMD图

FaceTracking

    首先,TrackSuccessful 属性只是是否识别出了面部,FaceRect是面部所在的矩形区域,最重要的是下面的几个方法:这些方法可以提供最多87个点来表示面部信息,这些点可以以二维或者三维的形式提供。我们可以提取一些用户的面部表情信息:

  • Get3DShape() 方法返回三维表示的121个点集合.
  • GetProjected3DShape() 方法返回同样数目的以二维形式展现的点的集合,这些点映射到640×480像素的图像上.
  • GetAnimationUnitCoefficients()  返回面部表情的描述值,在下面我们会用到这个值来绘制人物图像.更多的信息,您可以查看 Animation Units on MSDN.
  • GetTriangles() 该方法可以 和Get3DShape 方法一起使用. 他返回一个三角形集合,集合中的每个三角形的顶点都是由 Get3DShape 中的点组成。这些三角形可以用来进行3D建模,就像Developer Toolkit自带例子中的那样,我们可以用它来对整个面部进行动态建模。

 

2.3 绘制脸部

    有了数据之后,我们可以在WPF中用XAML来绘制一个简单的人脸了。在绘制过程中,我们会使用一些变换Transformations (Translations, Rotations, Scales, …) 来表示人的表情的变化,比如说RenderTransform对象可以用来改变对象的渲染方式,我们使用一个Ellipse 对象来代表嘴巴 (Ellipse with x:Name=”Mouth”) :他有一个 ScaleTransform属性,我们可以使用它来改变X轴和Y轴上的缩放,来模拟口型的变化,眉头的模拟也是如此。

<Window x:Class="KinectFaceTracking.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Grid>
                <Canvas x:Name="MainCanvas" Width="500" Height="500">
                    <Canvas.RenderTransform>
                        <TransformGroup>
                            <RotateTransform x:Name="CanvasRotate" CenterX="250" CenterY="250" />

                            <!--<TranslateTransform x:Name="CanvasTranslate" />-->
                        </TransformGroup>
                    </Canvas.RenderTransform>

                    <Ellipse Width="300" Height="300" x:Name="Face"  StrokeThickness="2" Stroke="Black" Canvas.Left="105" Canvas.Top="6" />
                    <Ellipse Width="30" Height="30" x:Name="LeftEye" Stroke="Black" StrokeThickness="2" Canvas.Left="289" Canvas.Top="102" />
                    <Ellipse Canvas.Left="194" Canvas.Top="102" x:Name="RightEye" Height="30" Stroke="Black" StrokeThickness="2" Width="30" />
                    <Ellipse Canvas.Left="224" Canvas.Top="239" Height="18" x:Name="Mouth" Stroke="Black" StrokeThickness="2" Width="64" >
                        <Ellipse.RenderTransform>
                            <ScaleTransform x:Name="MouthScaleTransform" CenterX="32" CenterY="9" ScaleX="1" ScaleY="1"/>
                        </Ellipse.RenderTransform>
                    </Ellipse>

                    <!--<Ellipse Canvas.Left="244" Canvas.Top="151" Height="53" x:Name="Nose" Stroke="Black" StrokeThickness="2" Width="22" />-->
                    <Rectangle Width="70" Stroke="Black" Fill="Black" StrokeThickness="10" Height="5" Canvas.Left="169" Canvas.Top="80">
                        <Rectangle.RenderTransform>
                            <TransformGroup>
                                <TranslateTransform x:Name="RightBrow" />
                                <RotateTransform x:Name="RightBrowRotate" CenterX="50" Angle="0" />
                            </TransformGroup>
                        </Rectangle.RenderTransform>
                    </Rectangle>

                    <Rectangle  Width="70" Stroke="Black" Fill="Black" StrokeThickness="10" Height="5" Canvas.Left="274" Canvas.Top="80" >
                        <Rectangle.RenderTransform>
                            <TransformGroup>
                                <TranslateTransform x:Name="LeftBrow" />
                                <RotateTransform x:Name="LeftBrowRotate" CenterX="20" Angle="0" />
                            </TransformGroup>
                        </Rectangle.RenderTransform>
                    </Rectangle>
                    <Rectangle Canvas.Left="207" Canvas.Top="148" Fill="Black" Height="5" Stroke="Black" StrokeThickness="10" Width="50">
                        <Rectangle.RenderTransform>
                            <TransformGroup>
                                <RotateTransform Angle="-70" CenterX="50" />
                            </TransformGroup>
                        </Rectangle.RenderTransform>
                    </Rectangle>
                    <Rectangle Canvas.Left="246" Canvas.Top="190" Fill="Black" Height="5" Stroke="Black" StrokeThickness="10" Width="15">
                        <Rectangle.RenderTransform>
                            <TransformGroup>
                                <RotateTransform Angle="0" CenterX="50" />
                            </TransformGroup>
                        </Rectangle.RenderTransform>
                    </Rectangle>
                </Canvas>
            </Grid>
        </Grid>
</Window>

    完成之后,一个简单的人脸就绘制完成了,如下:

Kinect face Trackingw

 

2.4 将数据和表现绑定

 

    现在表情绘制完成,我们只需要使用面部追踪的数据来改变各部分的动画参数就可以模拟脸部表情了。

获取数据之前,我们需要检查是否识别出来了脸部。如果识别出来人脸,我们就接着获取动画单位系数(Animation Units coefficients). 这个参数可以告诉哦我们被追踪对象嘴巴是张开还是合拢,还是在笑,眉头紧锁还是笑颜逐开等等…

//如果识别出来,进行进一步处理
if (faceFrame.TrackSuccessful)
{
    // 获取动画单位系数 Animation Units coeffs.
    var AUCoeff = faceFrame.GetAnimationUnitCoefficients();

    var jawLowerer = AUCoeff[AnimationUnit.JawLower];
    jawLowerer = jawLowerer < 0 ? 0 : jawLowerer;
    MouthScaleTransform.ScaleY = jawLowerer * 5 + 0.1;
    MouthScaleTransform.ScaleX = (AUCoeff[AnimationUnit.LipStretcher] + 1);

    LeftBrow.Y = RightBrow.Y = (AUCoeff[AnimationUnit.BrowLower]) * 40;

    RightBrowRotate.Angle = (AUCoeff[AnimationUnit.BrowRaiser] * 20);
    LeftBrowRotate.Angle = -RightBrowRotate.Angle;

    CanvasRotate.Angle = faceFrame.Rotation.Z;
 }

 

 

 

 

 

   上面的动画参数的取值范围都是在-1和1之间。到这里一个简单的面部追踪小程序就完成了,您可以对着Kinect做一些表情试一试变化。下面是我做的一些截图:

Kinect face Tracking1w Kinect face Tracking2w Kinect face Tracking3w

 

 

 

三、结语

    本文简单演示了一个如何使用Kinect进行面部追踪,并使用一个简单的例子来展现了面部追踪需要用到的一些对象,可以看出在Kinect SDK 1.5及之后的版中使用面部追踪功能是比较简单的。 希望本文对您了解Kinect SDK中如何使用面部追踪有所帮助。