深入C#编程:探索人脸识别技术的实现与应用

更新:11-17 民间故事 我要投稿 纠错 投诉

大家好,感谢邀请,今天来为大家分享一下深入C#编程:探索人脸识别技术的实现与应用的问题,以及和的一些困惑,大家要是还不太明白的话,也没有关系,因为接下来将为大家分享,希望可以帮助到大家,解决大家的问题,下面就开始吧!

下载地址:http://ai.arcsoft.com.cn/product/arcface.html

目前虹软人脸识别引擎拥有三个平台,其中Windows和iOS是基于C++开发的。本文基于Windows版本使用C#实现人脸识别。请注意您下载的平台。

如何使用C# 调用C++ 库

那么,如何使用C#调用C++库呢? C# 提供了两种调用C++ DLL 的技术。

静态调用(DCOM+)

动态调用(P/Invoke)

我们可以将C或C++的函数封装成COM组件,这样在C#中调用时就比较方便,但是COM组件需要注册,多次注册也可能会带来一些问题。同时,在处理C或C++类型和COM组件时,在进行类型转换时也可能会遇到一些麻烦。

动态方式是直接用C#调用C或C++编写的动态链接库。与这些方法相比,P/Invoke 更加方便。

** 因此我们选择P/Invoke方法**

**什么是P/Invoke*

P/Invoke的全称是Platform Invoke(平台调用)。它实际上是一种函数调用机制。通过P/Invoke,我们可以调用非托管DLL中的函数。事实上,许多在.NET基类库中定义的内部类型函数都被调用,这些函数是从Kernel32.dll、User32.dll、gdi32.dll等非托管DLL导出的。

让我们看一个简单的例子

[DllImportAttribute("user32.dll", EntryPoint="SetCursorPos")] [return: MarshalAsAttribute(UnmanagedType.Bool)] //可写与否,定义如何封送返回参数public static extern bool SetCursorPos(int X, int Y) ;

这段代码的目的是调用系统中的方法来获取鼠标参数。

P/INVOKE的流程

关于P/Invoke流程,我在MSDN上找到了一张图片,如下图。

使用P/Invoke调用C/C++方法时,会依次执行以下操作

1 找到包含该函数的非托管DLL

2 将非托管DLL加载到内存中

3. 找到函数在内存中的地址,并根据函数的调用约定将其参数压入堆栈。

4 将控制权转移到非托管函数

注意:只有第一次调用函数时,才会找到并加载非托管DLL,并找到该函数在内存中的地址。当非托管函数产生异常时,P/Invoke 会将异常传递给托管调用者

看起来很复杂,但是使用起来却非常简单。你只需要在C#中重新声明该函数的定义,然后就可以像其他函数一样调用它。

注意:只有第一次调用函数时,才会找到并加载非托管DLL,并找到该函数在内存中的地址。当非托管函数产生异常时,P/Invoke 会将异常传递给托管调用者

看起来很复杂,但是使用起来却非常简单。你只需要在C#中重新声明该函数的定义,然后就可以像其他函数一样调用它。

步骤一:实施人脸检测

我们首先要实现简单的Hello World 函数来检测照片中是否存在人脸,我们称之为静态人脸检测。我们希望程序能够打开一张照片并告诉我们照片中是否有人脸。如果存在人脸,则需要对其进行识别并显示。如果没有,会提示照片中没有人脸。

创建演示项目

项目技术

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

创建项目

我们打开Visual Studio,选择C#语言,创建一个Winfrom项目,项目名称为FaceDetectDemo,路径可以根据需要选择。项目立项后,项目结构如图:

上图中的AFD和dll文件夹后面会用到。项目刚构建时这两个文件夹不可用。

创建视图

通过设计器和工具箱,我们可以构建我们的视图界面,包括一个按钮和两个PictureBox。

我们用较大的一张来显示完整的图片,用较小的一张来显示识别出的人脸信息。

我们将较大的PictureBox命名为pictureBox1和较小的PictureBox2,然后将两者的SizeMode都设置为Zoom,这样我们就可以自动显示照片了。

下载所需的SDK

这里我们需要ArcSoft提供的SDK中的DLL。如果您还没有下载,现在就下载吧。访问地址http://www.arcsoft.com.cn/ai/arcface.html,在明显的地方找到WIndows版本。填写基本信息后即可下载。

下载的时候有版本选择,比如1:1、1:N等,我们选择默认的即可。 1:N和1:1在人脸识别上存在差异,但在人脸检测功能上基本相同。没有什么区别。

下载完成页面会显示您申请的APPID和SDK KEY信息,如下图。

请务必记住这些密钥,因为在以下过程中您将需要这些密钥。如果您忘记了,请登录刚才的地址。您可以在用户中心看到这些密钥。当然,您也可以在电子邮件中找到它们。

我们打开下载的文件,这是一个zip格式的压缩包,然后解压。我们发现里面多了三个包,我们解压名为Face_Detection的包。可以看到下面的目录结构

命名很清楚,这里只需要简单提一下。需要将lib中的dll复制到您的运行目录中。 doc中的PDF非常重要,是SDK的入门指南。 SampleCode和inc是调用C++时使用的参考源代码和头文件。这些都是比较重要的。

现在,让我们将dll 拖到应用程序的bin 目录中。选择编辑选项时始终将此文件复制到输出目录。

另外,我们的SDK是针对32位系统的,所以我们还需要将编译选项设置为x86。

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

一步步基于人脸识别的SDK代码示例完善项目

现在我们回到上一章中的四个文件夹,我们打开doc文件夹。这里的pdf 文件是我们下一课程的基础。读完之后,我发现了4 个函数、3 个结构体、2 个枚举、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

该结构用于存储面部信息。我们可以从文档中得到它的定义如下:

类型定义结构{

MRECT *rcFace;

MLong nFace;

AFD_FSDK_OrientCode * lfaceOrient;

} AFD_FSDK_FACERES, * LPAFD_FSDK_FACERES;

根据我们上一节所说的,我们可以知道这个MLong和long类似,而rcFace和lfaceOrient是两个指针。那么C#中如何使用指针呢?直接使用不安全代码肯定是可以的,但是这里我们使用IntPtr。

IntPtr简介

IntPtr 是特定于平台的类型,用于表示指针或句柄。这实际上说明了以下两个事实。 IntPtr 可用于表示指针或句柄。它是特定于平台的类型。主要用在两个地方:

(1) C#调用WIN32 API时

(2)当C#调用C/C++编写的DLL时(其实和1一样,不过这个一般是我们和别人合作开发的时候用的)

我们可以这样理解,IntPtr可以在C++中互换指针

根据刚才提到的定义规则,转换成C#语言的定义如下:

公共结构AFD_FSDK_FACERES

{

公共int nFace;

公共IntPtr rcFace;

公共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 结构__tag_rect

{

MInt32 左;

MInt32 顶部;

MInt32 右;

MInt32 底部;

} MRECT,*PMRECT;

这种类型比较简单。 C#版本定义如下:

公共结构MRECT

{

公共int 左;

公共整数顶部;

公共int 权利;

公共int 底部;

}

AFD_FSDK_VERSION

这个结构体定义了我们API的版本信息。同样,我们来看看它的SDK的定义。

类型定义结构

{

MInt32 l代码库;

MInt32 l 大调;

MInt32 l次要;

MInt32 lBuild;

MPChar 版本;

MPChar 构建日期;

MPChar 版权所有;

AFD_FSDK_版本;

根据SDK启动协议可知,Mint32相当于int,MPChar相当于char*。这些自定义变量类型可以在inc/comdef.h中找到,所以我们对应的C#版本如下:

//定义FD的版本号

公共结构AFD_FSDK_Version

{

公共int l代码库;

公共int lMajor;

公共int lMinor;

公共int lBuild;

公共IntPtr 版本;

公共IntPtr 构建日期;

公共IntPtr 版权所有;

}

AFD_FSDK_ORIENTCODE

接下来我们定义枚举。这里使用了两个枚举:AFD_FSDK_OrientPriority 和AFD_FSDK_OrientCode。枚举比较简单。我们只需要将十六进制转换为十进制即可。

根据SDK文档,我们需要定义的类型如下:

//定义人脸检查结果中人脸的角度

公共枚举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

定义人脸角度检测范围

公共枚举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

}

关闭屏幕的ASVL

该结构是用于人脸识别的关键结构。我只是在定义函数时发现了这一点。回来重新定义它。 SDK文档中没有这个,但是我们可以在示例代码中看到。我想看一下LPASVLOFFSCREEN 的定义。在我们的SDK 的inc 文件夹中,我们找到了一个名为asvloffscreen.h 的文件。当我们打开文件时,我们可以找到里面的主要定义

typedef 结构__tag_ASVL_OFFSCREEN

{

MUInt32 u32PixelArrayFormat;

MInt32 i32宽度;

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 属性。元帅属性使用起来相当困难,如何转换也是一件复杂的事情。这时候我们就需要用到微软的神器了。 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头文件中找到它们的转换定义。

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

公共结构ASVLOFFSCREEN

{

公共int u32PixelArrayFormat;

公共int i32Width;

公共int i32Height;

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

公共System.IntPtr[] ppu8Plane;

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

公共int[] pi32Pitch;

}

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

定义API函数

查看SDK文档可以看到FD一共提供了3种方法。我们定义一个类来包含这些方法

新建AFD文件夹,定义AFDFunction类,该类包含SDK中提供的所有方法。

AFD_FSDK_INITIALFACEENGINE

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

原型

MRESULT AFD_FSDK_InitialFaceEngine(

MPCharAppId,

MPChar SDK密钥,

兆字节*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] 引擎句柄

iOrientPriority [in] 所需的人脸检测角度范围

nScale [in] 数字表示的最小面部尺寸有效值范围[2,50] 建议值16。该尺寸是面部与图像长边的比例。例如,如果用户想要检测的最小人脸尺寸是图像长度的1/8,那么nScale应该设置为8

nMaxFaceNum [in] 用户期望引擎检测到的最大人脸数量。有效值范围[1,50]

如果成功,则返回MOK,如果失败,则返回MRCode。 MOK是一个值为0的int,MRCode是一个定义。可以在inc 文件夹中的merror.h 中找到。

使用刚刚提供的工件,我们可以如下定义该函数:

[DllImport("libarcsoft_fsdk_face_detection.dll", EntryPoint="AFD_FSDK_InitialFaceEngine", CallingConvention=CallingConvention.Cdecl)]

公共静态extern int AFD_FSDK_InitialFaceEngine(string appId, string sdkKey, IntPtr pMem, int lMemSize, ref IntPtr pEngine, int iOrientPriority, int nScale, int nMaxFaceNum);

CallingConversion 该属性用于定义C++ 函数调用的方式。

Cdecl 调用者清除堆栈。这使您能够使用可变参数调用函数(例如Printf),从而使它们可用于接受可变数量参数的方法。

FastCall 不支持此调用约定。

StdCall 被调用者清除堆栈。这是使用平台调用调用非托管函数的默认约定。

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

Winapi 这个成员实际上并不是一个调用约定,而是使用默认的平台调用约定。例如,Windows 上的默认值为StdCall,Windowshttp://CE.NET 上的默认值为Cdecl。

默认情况下,C 和C++ 使用Cdecl 调用,因此我们只需在调用DLL 时指定该值即可。

AFD_FSDK_STILLIMAGEFACEDETECTION

这个方法是我们的核心方法。它的功能正如我们所料,就是读取输入图像,检测是否有人脸内容,并输出人脸的结果信息。让我们看看基本定义。

MRESULT AFD_FSDK_StillImageFaceDetection(

MHandle hEngine,

LPASVLOFFSCREEN pImgData,

LPAFD_FSDK_FACERES pFaceRes

);

hEngine [in] 发动机手柄

pImgData [in] 待检测的图像信息

pFaceRes [out] 人脸检测结果

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

[DllImport("libarcsoft_fsdk_face_detection.dll", CallingConvention=CallingConvention.Cdecl)]

公共静态extern int AFD_FSDK_StillImageFaceDetection(IntPtr pEngine, IntPtr pImgData, ref IntPtr pFaceRes);

AFD_FSDK_GETVERSION

初始化后的方法是GetVersion,其作用是获取SDK的版本信息。

原型

常量AFD_FSDK_Version * AFD_FSDK_GetVersion(

MHandle hEngine

);

这种方法比较简单。该参数是对Engine的引用,其返回值是我们最初定义的Version结构。

[DllImport("libarcsoft_fsdk_face_detection.dll", CallingConvention=CallingConvention.Cdecl)]

公共静态extern int AFD_FSDK_StillImageFaceDetection(IntPtr pEngine, IntPtr pImgData, ref IntPtr pFaceRes);

[DllImport("libarcsoft_fsdk_face_detection.dll", CallingConvention=CallingConvention.Cdecl)]

公共静态extern int AFD_FSDK_UninitialFaceEngine(IntPtr pEngine);

至此,我们的基本数据结构就已经创建完成了。

实现图像读取和人脸识别功能

让我们来实现我们的图像读取和人脸识别功能。本章将包含大量细节和互操作性内容。

基础知识介绍

其实我们在前面的代码中已经解释了很多关于P/Invoke的操作。我们还基本上定义了我们使用的结构和函数。我们知道

映射到IntPtr 的指针,

引用类变量映射到IntPtr,

char* 可以映射为字符串

结构

,和数组如果从IntPtr中取数据呢,我们需要使用的一个类叫Marshal 我们来看一下MSDN上的介绍 https://msdn.microsoft.com/zh-cn/library/system.runtime.interopservices.marshal(v=vs.110).aspx Marshal类提供了一个方法集合,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法,我们将会在下面开发进程中频繁使用这个类的多个方法。 例如:在定义一个指针类型变量时IntPtr,我们需要使用Marshal.AllocHGlobal为其分配内存,得到IntPtr变量,在分配内存时,我们需要使用Marshal.SizeOf计算需要分配的内存的大小。然后调用Marshal. StructureToPtr为变量赋值 让我们带着这些概念开始我们下面的内容。 初始化引擎 根据我们的SDK说明文档,在使用引擎之前需要先初始化。出于简单我就把初始化代码的部分放在Form1的构造函数内。而把引擎作为类的实例变量定义。 我们在构造函数中添加初始化的代码。 定义人脸识别引擎 IntPtr detectEngine = IntPtr.Zero; 定义人脸识别引擎参数 我们可以根据sampleCode定义我们人脸识别所需要的参数 首先,定义Engine运行需要的内存,宽容度,人脸的数目以及有效的人脸角度。 int detectSize = 40 * 1024 * 1024; int nScale = 50; int nMaxFaceNum = 10; string appId = "你申请到的APPID"; string sdkFDKey = "你申请到的FDKEY"; 初始始化引擎内存缓冲区 在示例代码中,我们可以得到引擎在初始化时,需要指定缓冲区。 在C#中,可以使用 pMem = Marshal.AllocHGlobal(detectSize); 初始始化引擎 针对人脸角度的检测范围,直接传递为AFD_FSDK_OrientPriority.AFD_FSDK_OPF_0_HIGHER_EXT, 变量定义完成后,我们就可以调用我们的初始化方法了。返回值为int类型,通过返回的类型,可以得知是否能够调用成功。 int retCode = AFDFunction.AFD_FSDK_InitialFaceEngine(appId, sdkFDKey, pMem, detectSize, out detectEngine, (int)AFD_FSDK_OrientPriority.AFD_FSDK_OPF_0_HIGHER_EXT, nScale, nMaxFaceNum);     if (retCode != 0)       {           MessageBox.Show("引擎初始化失败:错误码为:" + retCode);           this.Close();       } 实现业务逻辑 接下来,我们找到我们的btnLoadImage方法,在这里填写我们的业务处理逻辑。 1.读取一个jpg的文件,并加载的pictureBox1中显示出来, 2.然后调用我们的引擎的AFD_FSDK_StillImageFaceDetection方法,检查出人脸的位置。 3最后我们利用GDI+,把检测到的人脸部分提取出位置显示到PictureBox2中, 4.把pictureBox1中的图片,添加上识别的红框,完成人脸检测的效果。 打开图片 加载图片比较简单,我们调用OpenFileDialog方法,打开一个图片文件,并显示到pictureBox1中 OpenFileDialog openFile = new OpenFileDialog(); openFile.Filter = "图片文件|*.bmp;*.jpg;*.jpeg;*.png|所有文件|*.*;"; openFile.Multiselect = false; openFile.FileName = ""; if (openFile.ShowDialog() == DialogResult.OK)             {  Image image = Image.FromFile(openFile.FileName);                 this.pictureBox1.Image = new Bitmap(image); //TODO:完成下面的方法                     checkAndMarkFace(this.pictureBox1.Image);             } 检测并标记人脸 终于到正题了,很兴奋,对吧。不过还是没有思路,因为我们不知道如何来调用那个引擎。这个时候我们必须参考samplecode,通过sampleCode我们可以得知,首先我们需要读取图片的内容到BMP格式,而且这个BMP格式必须为ASVL_PAF_RGB24_B8G8R8,标准的Image中的Bitmap就是这个格式,读取bitmap中的所有图像信息存入ASVLOFFSCREEN的offInput中,这时候SampleCode中的代码是从文件中读取的,我们要直接从Bitmap中读取,这里面还是有一些不一样的。我们首先来看一下这个读取的代码 private byte[] readBmp(Bitmap image, ref int width, ref int height, ref int pitch) {//将Bitmap锁定到系统内存中,获得BitmapData BitmapData data = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);            //位图中第一个像素数据的地址。它也可以看成是位图中的第一个扫描行 IntPtr ptr = data.Scan0; //定义数组长度 int soureBitArrayLength = data.Height * Math.Abs(data.Stride); byte[] sourceBitArray = new byte[soureBitArrayLength]; //将bitmap中的内容拷贝到ptr_bgr数组中 Marshal.Copy(ptr, sourceBitArray, 0, soureBitArrayLength);            width = data.Width; height = data.Height; pitch = Math.Abs(data.Stride);   int line = width * 3;   int bgr_len = line * height;   byte[] destBitArray = new byte[bgr_len]; for (int i = 0; i< height; ++i) {   Array.Copy(sourceBitArray, i * pitch, destBitArray, i * line, line); } pitch = line;

image.UnlockBits(data); return destBitArray; } 有关这部分的内容,可以参考微软关于BitmapData的注解。https://msdn.microsoft.com/zh-cn/library/system.drawing.imaging.bitmapdata.aspx 识别人脸 回到我们的这个方法,我们继续人脸识别的过程首先,我们把获取到的图像信息存起来 byte[] imageData = readBmp(bitmap, ref width, ref height, ref pitch); 通过前面的过程,我们知道,我们的代码中的传入图像的参数类型是ASVLOFFSCREEN指针。通过查看ASVLOFFSCREEN类型。我们可以发现,u32PixelArrayFormat为需要图像的格式。这个是因为我们准备使用BMP位图,因此我们直接使用ASVL_PAF_RGB24_B8G8R8格式通过查询可知定义的值为513. i32Width和i32Height则为识别图像的大小。ppu8Plane为一个批向byte数组的指针数组,这里面会保存我们刚刚转换后的图片数据。而pi32Pitch则是为每一个图像指定了pitch大小,在结构中,一次人脸识别工作,可以传递四幅图片。 我们先来把byte[]数组转化为C++识别的数组类型。 IntPtr imageDataPtr = Marshal.AllocHGlobal(imageData.Length); Marshal.Copy(imageData, 0, imageDataPtr, imageData.Length); 接下来是根据刚才的分析,我们设置的ASVLOFFSCREEN的结构体类型 ASVLOFFSCREEN offInput = new ASVLOFFSCREEN(); offInput.u32PixelArrayFormat = 513; offInput.ppu8Plane = new IntPtr[4]; offInput.ppu8Plane[0] = imageDataPtr; offInput.i32Width = width; offInput.i32Height = height; offInput.pi32Pitch = new int[4]; offInput.pi32Pitch[0] = pitch; 由于方法中需要是的一个结构体的指针,因此,我们还需要调用Marshal. AllocHGlobal方法创建指针,并使用Marshal.StructureToPtr进行初始化。 IntPtr offInputPtr = Marshal.AllocHGlobal(Marshal.SizeOf(offInput)); Marshal.StructureToPtr(offInput, offInputPtr, false); 由于接口还需要一个结构体保存返回的人脸数据,我们来定义它 AFD_FSDK_FACERES faceRes = new AFD_FSDK_FACERES(); 同人脸数据一样,我们需要把这个结构体转换为指针类型。 IntPtr faceResPtr = Marshal.AllocHGlobal(Marshal.SizeOf(faceRes)); 这个是返回值,因此我们不需要对内容进行初始化。我们直接调用引擎 int detectResult = FaceDllImport.AFD_FSDK_StillImageFaceDetection(detectEngine, offInputPtr, ref faceResPtr); 如果成功返回detectResult会返回0,也就是0 这个时候,返回为0并不意味着找到了人脸,具体的人脸信息还需要在我们的AFD_FSDK_FACERES结构休中查找。 使用Marshal.PtrToStructure批获得的指针类型转化为结构体类型。 faceRes = (AFD_FSDK_FACERES) Marshal.PtrToStructure(faceResPtr, typeof(AFD_FSDK_FACERES)); 根据前端的结构体定义部分的数据,我们可以发现其中AFD_FSDK_FACERES.nFace属性为识别到的人脸的数目。faceRes.rcFace则为识别到的人脸的数据。nFace可以直接转化为int。 标出识别到的人脸信息 AFD_FSDK_FACERES中的rcFace是一个结构体指针,因此我们使用Marshal.PtrToStructure将其转化为结构体。 MRECT rect = (MRECT)Marshal.PtrToStructure(faceRes.rcFace , typeof(MRECT)); 通过获得这个rect信息,就可以得到我们需要的人脸的位置数据了,包括人脸矩形的在上角和右下角的坐标。然后我们就可以利用这些数据来重新创建一个位图 Image image = CutFace(bitmap, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); 将位图显示到图片控件上 this.pictureBox2.Image = image; 然后我们想像Demo中的一样,标出人脸的位置。我们就可以使用这样的方法。 this.pictureBox1.Image=  DrawRectangleInPicture(pictureBox1.Image, new Point(rect.left, rect.top), new Point(rect.right, rect.bottom), Color.Red, 2, DashStyle.Dash); 来看一下这里面用到的两上C#方法比较简单,纯属C#代码,比较简单 public static Bitmap CutFace(Bitmap srcImage, int StartX, int StartY, int iWidth, int iHeight)         {             if (srcImage == null)             {                 return null;             }             int w = srcImage.Width;             int h = srcImage.Height;             if (StartX >= w || StartY >= h)             { return null;             }             if (StartX + iWidth >w)             {                 iWidth = w - StartX;             }             if (StartY + iHeight >h)             {                 iHeight = h - StartY;             }             try             {                 Bitmap bmpOut = new Bitmap(iWidth, iHeight, PixelFormat.Format24bppRgb);                 Graphics g = Graphics.FromImage(bmpOut);                 g.DrawImage(srcImage, new Rectangle(0, 0, iWidth, iHeight), new Rectangle(StartX, StartY, iWidth, iHeight), GraphicsUnit.Pixel);                 g.Dispose();                 return bmpOut;             }             catch             { return null;             }         } private  Image DrawRectangleInPicture(Image bmp, Point p0, Point p1, Color RectColor, int LineWidth, DashStyle ds) { if (bmp == null) return null; Graphics g = Graphics.FromImage(bmp); Brush brush = new SolidBrush(RectColor); Pen pen = new Pen(brush, LineWidth); pen.DashStyle = ds; g.DrawRectangle(pen, new Rectangle(p0.X, p0.Y, Math.Abs(p0.X - p1.X), Math.Abs(p0.Y - p1.Y))); g.Dispose(); return bmp; } 点击运行 现在你可以点击运行你的项目了,如果没有任何问题,你的将会看到下面的画面。 如果出现问题,你需要根据返回的错误码进行查找。 引擎初始化失败 一般是APPID和APPKEY不对,你需要确保你到下载的地方申请了正确的APPID和KEY,并且注意平台是Windows平台的。初始化失败可以通过返回值进行查看,他们官网上也会有一个错误代码表。对照查表一般会解决问题。 找不到DLL 首先请保证你把DLL拷贝到对应的目录下面,其次要确定设置输出选项为拷贝到输出目录。 内存不能读或者写 这个是C++的尿性,也是C#程序员不多见的报错,主要检查相关参数是否传入正确。还要注意,如果人脸检测返回的值不为0,获取到的人脸数目会是一个比较大的随机数。这个时候如果用循环读取,就会出现地址越界的情况。

用户评论

浅笑√倾城

学习的人脸识别,C# 开发太棒了!

    有13位网友表示赞同!

独角戏°

终于找到可以用来做人脸识别的方案啦,期待看看效果。

    有10位网友表示赞同!

七级床震

虹软的引擎一直很有名啊,希望这个教程能详细讲解如何使用。

    有11位网友表示赞同!

淡淡の清香

之前了解过C#开发人脸识别,但没实际操作过,这篇文章很有帮助!

    有11位网友表示赞同!

花容月貌

想做一些人脸处理应用程序,不知道这款引擎怎么样?

    有18位网友表示赞同!

苏莫晨

最近在研究深度学习的人脸识别,可以看看这个教程参考一下。

    有11位网友表示赞同!

全网暗恋者

C# 人脸识别实现挺酷的,感觉很有实战应用价值。

    有5位网友表示赞同!

残留の笑颜

希望能看到一些项目案例,更直观地了解人脸识别的效果。

    有14位网友表示赞同!

有一种中毒叫上瘾成咆哮i

学习新技术一直是挑战自己,期待这个教程能让我掌握人脸识别技能。

    有17位网友表示赞同!

冷落了♂自己·

做一些安全验证的项目很需要人脸识别, 这篇文章正好解决了我的痛点!

    有9位网友表示赞同!

凝残月

对虹软的人脸识别引擎比较好奇,这个教程可以让我更深入了解一下。

    有8位网友表示赞同!

秘密

C# 开发人脸识别感觉挺有意思,有机会试试看。

    有14位网友表示赞同!

咆哮

学习人脸识别技术可以用C#来实现,可以尝试一下!

    有8位网友表示赞同!

拽年很骚

这篇文章能让我了解如何在 C# 中运用虹软的人脸识别引擎吗?

    有19位网友表示赞同!

安好如初

希望能有更多关于人脸识别的教程和资源分享!

    有16位网友表示赞同!

良人凉人

对人脸识别技术很有兴趣,这个教程看起来很实用!

    有10位网友表示赞同!

无所谓

想学习的人脸识别基础知识,可以先看一看这段教程。

    有18位网友表示赞同!

眷恋

C# 开发的项目类型有很多,现在还有人脸识别,真是日新月异啊!

    有6位网友表示赞同!

Hello爱情风

感谢分享这个很有价值的编程教程!

    有9位网友表示赞同!

何必锁我心

相信通过学习这篇教程,我就能实现自己的人脸识别项目啦!

    有17位网友表示赞同!

【深入C#编程:探索人脸识别技术的实现与应用】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活

上一篇:Unity AR教程(五):EasyAR 3D物体追踪与OBJ模型应用 下一篇:探寻神秘:银发女巫魔杖传奇故事(第三部分)