多语言展示
当前在线:351今日阅读:113今日分享:31

C#人脸识别入门篇—静态照片人脸检测(下)

实现人脸识别C#静态照片人脸检测下篇,因为经验字数限制所以分为上下篇。
工具/原料
1

ArcFace人脸识别SDK

2

C# 4.0版本

3

Visual Studio 2013

4

Winform项目

方法/步骤
1

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);至此,我们的基础数据结构已创建完毕.

2

实现图片读取和人脸识别功能我们来实现我们的图片读取和人脸识别功能,这个章节中,会包含大量的细节及互操作的内容。

3

基础知识介绍其实关于P/Invoke的操作我们前面的代码已经讲解了很多。也基本把我们用到的结构体和函数定义出来,我们知道 指针映射为IntPtr, 引用类变量映射为IntPtr, char *可以映摄为字符串 结构体,和数组如果从IntPtr中取数据呢,我们需要使用的一个类叫Marshal(Marshal类提供了一个方法集合,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法,我们将会在下面开发进程中频繁使用这个类的多个方法。  例如:在定义一个指针类型变量时IntPtr,我们需要使用Marshal.AllocHGlobal为其分配内存,得到IntPtr变量,在分配内存时,我们需要使用Marshal.SizeOf计算需要分配的内存的大小。然后调用Marshal.  StructureToPtr为变量赋值)

4

初始化引擎根据我们的SDK说明文档,在使用引擎之前需要先初始化。出于简单我就把初始化代码的部分放在Form1的构造函数内。而把引擎作为类的实例变量定义。 我们在构造函数中添加初始化的代码。定义人脸识别引擎IntPtr detectEngine = IntPtr.Zero;

5

定义人脸识别引擎参数我们可以根据sampleCode定义我们人脸识别所需要的参数 首先,定义Engine运行需要的内存,宽容度,人脸的数目以及有效的人脸角度。int detectSize = 40 * 1024 * 1024;int nScale = 50;int nMaxFaceNum = 10;string appId = '你申请到的APPID';string sdkFDKey = '你申请到的FDKEY';

6

初始始化引擎内存缓冲区在示例代码中,我们可以得到引擎在初始化时,需要指定缓冲区。 在C#中,可以使用pMem = Marshal.AllocHGlobal(detectSize);

7

初始始化引擎针对人脸角度的检测范围,直接传递为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();      }

8

实现业务逻辑接下来,我们找到我们的btnLoadImage方法,在这里填写我们的业务处理逻辑。 1.读取一个jpg的文件,并加载的pictureBox1中显示出来, 2.然后调用我们的引擎的AFD_FSDK_StillImageFaceDetection方法,检查出人脸的位置。 3最后我们利用GDI+,把检测到的人脸部分提取出位置显示到PictureBox2中, 4.把pictureBox1中的图片,添加上识别的红框,完成人脸检测的效果。

9

打开图片加载图片比较简单,我们调用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);           }

10

检测并标记人脸终于到正题了,很兴奋,对吧。不过还是没有思路,因为我们不知道如何来调用那个引擎。这个时候我们必须参考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锁定到系统内存中,获得BitmapDataBitmapData 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的注解。

11

识别人脸回到我们的这个方法,我们继续人脸识别的过程首先,我们把获取到的图像信息存起来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。

12

标出识别到的人脸信息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.来看一下这里面用到的两上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;}

13

点击运行现在你可以点击运行你的项目了,如果没有任何问题,你的将会看到下面的画面。如果出现问题,你需要根据返回的错误码进行查找。引擎初始化失败  一般是APPID和APPKEY不对,你需要确保你到下载的地方申请了正确的APPID和KEY,并且注意平台是Windows平台的。初始化失败可以通过返回值进行查看,他们官网上也会有一个错误代码表。对照查表一般会解决问题。找不到DLL 首先请保证你把DLL拷贝到对应的目录下面,其次要确定设置输出选项为拷贝到输出目录。 内存不能读或者写  这个是C++的尿性,也是C#程序员不多见的报错,主要检查相关参数是否传入正确。还要注意,如果人脸检测返回的值不为0,获取到的人脸数目会是一个比较大的随机数。这个时候如果用循环读取,就会出现地址越界的情况。最后来一张华仔的图镇楼

推荐信息