手记

C#人脸识别入门篇-STEP BY STEP人脸识别—静态照片人脸检测

在上篇文章中我们提到了人脸识别的想法,并且下载到了虹软免费的人脸识别的SDK,然后发现它是C++版本的,经过了一番百度之后发现原来C#可以使用P/Invoke的方式来操作C++的DLL的,而且相当方便。我们今天就来实现它。

项目目标

我们希望先实现我们的简单的Hello World功能,从一张照片中检测人脸是否存在,我们称之为静态人脸检测。我们希望程序能够打开一张照片,告诉我们这张照片中是否有人脸,如果有,就需要识别并显示出来,如果没有,就提示照片中没有人脸。

创建Demo项目

项目技术

我们使用C# 4.0版本,IDE使用Visual Studio 2013,项目就用标准的Winform项目。

建立项目

我们打开Visual Studio,选择C#语言,建立Winfrom项目,项目名称为FaceDetectDemo,路径随便选。立项后,项目结构如图所示:


项目解决方案示意图


上图中的AFD和dll文件夹我们后面就会用到,刚建项目时是没有这两个文件夹的。

建立视图

通过设计器和工具箱,我们可以建立我们的视图界面,包括一个按钮两个PictureBox.
大的那个我们用来显示完整的图片,小的用来显示识别到的人脸信息。
我们把大PicturesBox的那个命名为pictureBox1,小的命名为pictureBox2,然后设置两个的SizeMode均为Zoom, 以方便我们自动显示照片。


项目界面图

下载需要的SDK

这里我们需要虹软提供的SDK中的DLL,如果你还没有下载它,那么现在就是下载的时候了。访问地址http://www.arcsoft.com.cn/ai/arcface.html 在明显的地方找到WIndows版本,填写基本的资料后就可以下载了。
下载的时候有一个版本选择,1:1,1:N之类的,我们选择默认的就可以了,1:N和1:1在人脸识别上是有差别的,但在人脸检测功能上基本上没有差异。
在下载完成的页面上,会显示你申请的APPID和SDK KEY的信息,如下所示

SDK ID和KEY


请确保牢记这些Key,因为接下来的程序中你将需要这些Key,如果忘记了,就登录刚才的那个地址,在用户中心里面可以看到这些Key,当然,你也可以在邮件中查找。

我们打开下载的文件,是一个zip格式的压缩包,我们把它解压。发现里面还有三个包,我们解压其中名为Face_Detection的包。可以看到下面的目录结构


SDK目录结构


命名很清晰,我这里只需要简单说一下。lib中的dll是要拷贝到你的运行目录中的,doc中的PDF相当重要,是SDK的入门指南。samplecode和inc是供C++调用时候用到的参考源码和头文件。这些都是比较重要的。

现在,让我们把dll拖入到我们的应用程序的bin目录.在编辑选项时选择始终复制这个文件到输出目录.
另外我们的SDK是32位系统的,所以我们还需要设置编译选项为x86.


编译选项要设置为x86


至此,项目创建工作顺利完成。

一步一步,根据人脸识别的SDK代码示例来完善项目

现在我们回到上一章节的四个文件夹,我们打开doc文件夹。这里面的pdf文件是我们接下来课程的基础。通读一遍,发现4个函数,3个结构体,然后2个枚举,两个变量类型,还有一段示例代码。我们来一步步定义它们.

自定义数据类型

C/C++ 可以定义自己的类型,打开SDK文档可以发现,这里面几乎没有我们熟悉的int,long,char*这些类型,取而代之是的Mint以及一些其它AFD开头的类型,SDK文档开篇引入了两个基础类型。

typedef MInt32 AFD_FSDK_OrientPriority; 
typedef MInt32 AFD_FSDK_OrientCode;

所有基本类型在平台库中有定义。
定义规则是在ANSIC 中的基本类型前加上字母“M”同时将类型的第一个字母改成大写。

例如“long” 被定义成“MLong”

具体到上面的代码,它的意思是在项目中遇到AFD_FSDK_OrientPriority就认为是Mint32,对应C#就是int,全部的定义在inc文件夹afdcommdef.h头文件中

定义结构体

由于C并不是面向对象的语言,结构体作为可以自定义的类型,在一定程度的代替了我们C#中的类和对象,我们来一步步定义这些结构体。

AFD_FSDK_FACERES

这个结构体是用来存储脸部信息的,我们可以从文档中得到它的定义如下:

typedef struct{ MRECT * rcFace; 
MLong nFace; 
AFD_FSDK_OrientCode * lfaceOrient; 
} AFD_FSDK_FACERES, * LPAFD_FSDK_FACERES;

根据我们上一节中的内容,可以知道这个MLong类似于long,rcFace和lfaceOrient则是两个指针。那么在C#中如何使用指针呢,直接用unsafe code肯定是可以的,不过这里我们使用IntPtr.

IntPtr的简介
IntPtr用于表示指针或句柄的平台特定类型。这个其实说出了这样两个事实,IntPtr 可以用来表示指针或句柄、它是一个平台特定类型,它主要用在两个地方:
(1)C#调用WIN32 API时
(2)C#调用C/C++写的DLL时(其实和1相同,只是这个一般是我们在和他人合作开发时经常用到)
我们可以这样子理解,IntPtr就可以互换C++中的指针

我们根据刚才所说的定义规则,换算成C#语言的定义如下:

public struct AFD_FSDK_FACERES{public int nFace;public IntPtr rcFace;public IntPtr lfaceOrient;
}

注意:nface虽然C++中是long,但对应到C#中可不long,而是int.在32位程序中int和long占用的内存大小都是4Byte=32bit,其表示的大小都是:-2147483648~2147483647。

MRECT

我们在SDK文档中注意到rcFace的类型是MRect*  这里的* 说明这是一个指针类型,因此我们在定义这个类的时候使用了IntPtr,但是MRect是一个结构体,我们可在inc文件夹下面的amcomdef.h下面找到了它的定义.

typedef struct __tag_rect{
    MInt32 left;
    MInt32 top;
    MInt32 right;
    MInt32 bottom;
} MRECT, *PMRECT;

这个类型比较简单,C#版定义如下:

public struct MRECT
    {
        public int left;        public int top;        public int right;        public int bottom;
    }

AFD_FSDK_VERSION

这个结构体定义的是我们API的版本信息,同样的我们来查看一下它的SDK的定义

typedef struct { 
MInt32 lCodebase;
MInt32 lMajor; 
MInt32 lMinor; 
MInt32 lBuild; 
MPChar Version; 
MPChar BuildDate; 
MPChar CopyRight; 
} AFD_FSDK_Version;

根据SDK开始约定,我们可以知道Mint32相当于int,MPChar相当于char*,这些自定义的变量类型可以在inc/comdef.h中查找,因此我们的对应的C#版本如下:

    //定义FD的版本号
  public struct AFD_FSDK_Version
    {
        public int lCodebase;        public int lMajor;        public int lMinor;        public int lBuild;        public IntPtr Version;        public IntPtr BuildDate;        public IntPtr CopyRight;
    }

AFD_FSDK_ORIENTCODE

接下来我们来定义枚举,这里面用到的枚举有以下两个:AFD_FSDK_OrientPriority和AFD_FSDK_OrientCode,枚举比较简单。我们只需要把十六进制转换为10进制就可以了。
根据SDK文档,我们需要定义的类型如下:

//定义人脸检查结果中人脸的角度public enum AFD_FSDK_OrientCode
    {
        AFD_FSDK_FOC_0 = 1,
        AFD_FSDK_FOC_90 = 2,
        AFD_FSDK_FOC_270 = 3,
        AFD_FSDK_FOC_180 = 4,
        AFD_FSDK_FOC_30 = 5,
        AFD_FSDK_FOC_60 = 6,
        AFD_FSDK_FOC_120 = 7,
        AFD_FSDK_FOC_150 = 8,
        AFD_FSDK_FOC_210 = 9,
        AFD_FSDK_FOC_240 = 10,
        AFD_FSDK_FOC_300 = 11,
        AFD_FSDK_FOC_330 = 12}

AFD_FSDK_ORIENTPRIORITY

定义脸部角度的检测范围

    public enum AFD_FSDK_OrientPriority
    {
        AFD_FSDK_OPF_0_ONLY=1,
        AFD_FSDK_OPF_90_ONLY=2,
        AFD_FSDK_OPF_270_ONLY=3,
        AFD_FSDK_OPF_180_ONLY=4,
        AFD_FSDK_OPF_0_HIGHER_EXT=5
    }

ASVLOFFSCREEN

这个结构体是用来进行人脸识别的关键结构,我当初就是在定义函数时才发现这个没有。又跑回来重新定义的。这个在SDK文档中没有,但是我们在示例代码中能够看到。我看来看看一下LPASVLOFFSCREEN的定义。在我们SDK的inc文件夹中,我们找到了一个名为asvloffscreen.h的文件。我们把文件打开,可以发现里面的主要定义

typedef struct __tag_ASVL_OFFSCREEN{
    MUInt32 u32PixelArrayFormat;
    MInt32  i32Width;
    MInt32  i32Height;
    MUInt8* ppu8Plane[4];
    MInt32  pi32Pitch[4];
}ASVLOFFSCREEN, *LPASVLOFFSCREEN;

u32PixelArrayFormat:像素数组的格式
ppu8Plane[4]为一个指针数组
pi32Pitch[4]为一整形数组

如何定义数组
数组的定义没有我们想象中的那么简单。在C++中定义数组的时候,是指定了数组的长度的,而C#中定义数组时,是不指定长度的。这只是一个问题,另一个问题是因为C#的数据和C++的数据布局方式有很大的不同,在P/Invoke和COM Interop当中必须要在C#和C++之间传递数据,有的时候,CLR或者说.NET能够自动在两种编程语言之间转换数据,有的时候又不行,这时候就需要程序员来帮忙告诉.NET怎样转换数据了。这个转换的方式是指定MarshalAs属性。Marshal属性相当难用,如何转换是一个复杂的事情,这个时个我们需要请出微软的神器。P/Invoke Interop Assistant,你可以去下面的链接下载这个神器 http://download.microsoft.com/download/f/2/7/f279e71e-efb0-4155-873d-5554a0608523/CLRInsideOut2008_01.exe

通过P/Invoke Interop Assistant的帮忙,我们可以知道应该这样子定义这个结构体。


利用工具完成定义


使用这个工具时,需要注意的是要把我们结构体中的类型转化为标准的C类型,我们可以在inc的amcomdef.h头文件中找到它们的转换定义。

我们来看一下最终的这个结构体的定义

  public struct ASVLOFFSCREEN
    {
        public int u32PixelArrayFormat;        public int i32Width;        public int i32Height;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = System.Runtime.InteropServices.UnmanagedType.SysUInt)]        public System.IntPtr[] ppu8Plane;

       
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = System.Runtime.InteropServices.UnmanagedType.I4)]        public int[] pi32Pitch;

}

现在你可以用这个工具来定义我们接来所需要用到的所有数据结构,也可以用来定义API函数。

定义API函数

查看SDK文档,可以看到FD共提供了3个方法。我们定义一个类来包含这些方法
新建AFD文件夹,定义AFDFunction类,里面包含SDK中提供的所有方法。

AFD_FSDK_INITIALFACEENGINE

我们先来看一下第一个方法,初始化SDK引擎,在SDK文档中可以看到它的原型定义如下:
原型

MRESULT AFD_FSDK_InitialFaceEngine( 
MPChar AppId, 
MPChar SDKKey, 
MByte *pMem, 
MInt32 lMemSize, 
MHandle *pEngine, 
AFD_FSDK_OrientPriority iOrientPriority, 
MInt32 nScale, 
MInt32 nMaxFaceNum 
);

我们来看一下它的参数列表

  • AppId    [in]    用户申请SDK时获取的App Id

  • SDKKey   [in]    用户申请SDK时获取的SDK Key

  • pMem     [in]    分配给引擎使用的内存地址

  • lMemSize     [in]    分配给引擎使用的内存大小

  • pEngine  [out]   引擎handle

  • iOrientPriority  [in]    期望的脸部检测角度范围

  • nScale   [in]    用于数值表示的最小人脸尺寸 有效值范围[2,50] 推荐值 16。该尺寸是人脸相对于所在图片的长边的占比。例如,如果用户想检测到的最小人脸尺寸是图片长度的1/8,那么这个nScale就应该设置为8

  • nMaxFaceNum  [in]    用户期望引擎最多能检测出的人脸数 有效值范围[1,50]

如果成功返回MOK,失败返回MRCode,MOK是一个int型的值为0,MRCOde是一个定义。可以在inc文件 夹中的merror.h中找到。
通过刚才提供的神器,我们可以定义这个函数如下:

 [DllImport("libarcsoft_fsdk_face_detection.dll", EntryPoint = "AFD_FSDK_InitialFaceEngine", CallingConvention = CallingConvention.Cdecl)]public static extern int AFD_FSDK_InitialFaceEngine(string appId, string sdkKey, IntPtr pMem, int lMemSize, ref IntPtr pEngine, int iOrientPriority, int nScale, int nMaxFaceNum);

CallingConvertion这个属性用于定义C++函数调用的方式。

  • Cdecl   调用方清理堆栈。这使您能够调用具有 varargs 的函数(如 Printf),使之可用于接受可变数目的参数的方法。

  • FastCall    不支持此调用约定。

  • StdCall 被调用方清理堆栈。这是使用平台 invoke 调用非托管函数的默认约定。

  • ThisCall    第一个参数是 this 指针,它存储在寄存器 ECX 中。其他参数被推送到堆栈上。此调用约定用于对从非托管 DLL 导出的类调用方法。

  • Winapi  此成员实际上不是调用约定,而是使用了默认平台调用约定。例如,在 Windows 上默认为 StdCall,在 Windows CE.NET 上默认为 Cdecl。

默认情况下,C和C++使用的Cdecl调用,因此我们在调用DLL时指定这个值就可以。

AFD_FSDK_STILLIMAGEFACEDETECTION

这个方法是我们的核心方法,它的功能如我们所料,就是通过读取输入的图像,检测是否存在人脸内容并输出人脸的结果信息。我们来看一下基础定义。

MRESULT AFD_FSDK_StillImageFaceDetection( 
MHandle hEngine, 
LPASVLOFFSCREEN pImgData, 
LPAFD_FSDK_FACERES pFaceRes 
);

hEngine     [in]    引擎handle
pImgData    [in]    待检测的图像信息
pFaceRes    [out]   人脸检测结果

和初始化类似,第一个参数是hEngine引用,第二个参数pImgData是要检测的图形信息,第三个参数pFaceRes是一个输出参数,获取人脸的检测结果。需要注意的是里面的参数类型,第一个MHandle对应的是引擎的引用,这个没有问题,第二个是LPASVLOFFSCREEN 它是指向ASVLOFFSCREEN的一个结构体指针,同样LPAFD_FSDK_FACERES也是一个指针,我们知道指针对应的都是IntPtr,定义如下:

 [DllImport("libarcsoft_fsdk_face_detection.dll", CallingConvention = CallingConvention.Cdecl)]public static extern int AFD_FSDK_StillImageFaceDetection(IntPtr pEngine, IntPtr pImgData, ref IntPtr pFaceRes);

AFD_FSDK_GETVERSION

初始化之后的方法是GetVersion,功能就是获取SDK的版本信息。
原型

const AFD_FSDK_Version * AFD_FSDK_GetVersion( 
MHandle hEngine 
);

这个方法比较简单,参数就是Engine的引用,其返回值为Version结构体,我们在最初的时候已经定义完成。

[DllImport("libarcsoft_fsdk_face_detection.dll", CallingConvention = CallingConvention.Cdecl)]        public static extern int AFD_FSDK_StillImageFaceDetection(IntPtr pEngine, IntPtr pImgData, ref IntPtr pFaceRes);


[DllImport("libarcsoft_fsdk_face_detection.dll", CallingConvention = CallingConvention.Cdecl)]        public static extern int AFD_FSDK_UninitialFaceEngine(IntPtr pEngine);



作者:随风而逝的心情
链接:https://www.jianshu.com/p/888f7b1d09ad


0人推荐
随时随地看视频
慕课网APP