手上有一个项目,需要检验使用本程序的,是否本人!因为在程序使用前,我们都已经做过头像现场采集,所以源头呢是不成问题的,那么人脸检测,人脸比对,怎么办呢?度娘了下,目前流行的几个人脸检测,人脸比对核心,大多都是基于互联网的,但我们的项目是基于本地服务器,那就有点麻烦了,后来找到ArcFace.它的核心允许本地调用,那就好办了,立刻去了虹软的开放平台,看论坛,下DEMO;我当时下的是这个:ArcFace C#DEMO

本以为可以一帆风顺的就可以把项目搞定了,不想…噩梦才刚刚开始呢…且听我细细道来:

首先说下我的调用逻辑; 项目里有一个采集端(每个业务窗口),负责采集现场人像,并通过ArcFace人脸检测,特征提取,获取到.dat比对源(ServiceFaceModels),然后存到数据库(blob);

项目里的应用端(用户手机),随机时间的调用摄像头,采集到被比对图片;并对该记录进行标记;

项目时比对端(服务器),定时询问数据库,哪些被标记记录需要比对,然后通过数据库记录,找到该图片,并通过ArcFace人脸检测,特征提取,获取到.dat被比对源(LocalFaceModels) 然后将这两个源在内存中进行比对,得分高于0.7的,就通过;

前两端就不多说了,都是一些常规的操作.重点讲下比对端(服务器);

先说我做的第一个版本,做的是一个控制台程序;

//首先定义了一个调用类; MatchUserFace;它里边包含了初始化,人脸检测,特征提取,人脸比对,以及一些辅助方法;

//然后在Program里定义了一个委托,这个委托的作用,就是能够让我可以带参数进去ArcFace的检测与比对核心;

  1. public delegate bool MatchHandler(string userid, string studyid, string photoid, string photopath);

//最后我的Program里边,就是做一个递归,去不断的问数据库拿被标志需要进行核对的记录,拿到图片后,就进行比对; QueryDataFile(string upstate);

下边这段就是在QueryDataFile();去实现异步调用比对核心;

  1. MatchHandler handler = new MatchHandler(MatchUserFace.GetAndMatchImage);
  2. string Identification = string.Format("USERID:{0} STUDYID:{1} PHID:{2}", userid, studyid, photoid);
  3. IAsyncResult result = handler.BeginInvoke(userid, studyid, photoid, pathstr, RecognizeEngine, DetectEngine, new AsyncCallback(CallbackFunc), Identification);

下边这段就是异步的结果回调;

  1. static void CallbackFunc(IAsyncResult result)
  2. {
  3. MatchHandler handler = (MatchHandler)((AsyncResult)result).AsyncDelegate;
  4. bool match = handler.EndInvoke(result);
  5. string strmatch = string.Empty;
  6. if (match)
  7. {
  8. strmatch = " 比对结果:OK";
  9. }
  10. else
  11. {
  12.  
  13. strmatch = " 比对结果:NO";
  14. }
  15. Console.WriteLine(result.AsyncState + strmatch);
  16. GC.Collect();
  17. }

  

写好了,发布到服务器上,还想着中午吃个鸡腿奖励下自己;不想…发布后不到两小时,小弟来说:服务器是不是出问题了,下边所有业务窗口访问速度严重延迟…立马跑到机房去看,一看没毛病呀,所有的服务都好好的,没有卦死..再打开资源监视器一看,靠…那个比对端一下吃3个多G的内存,而且还在不断上升中…立马停掉,然后再问小弟,下边业务是否正常,他回复正常了…那么说,就是我写的这个比对端有问题了!改!!!

第二个版本,

下了机房看代码…左看右看,没有哪不对呀,一步步按步就班的…毫无头绪时,就想,是不是服务器内存不够而已,打申请拿了64G回来.再开程序也是一样吃的很紧,但是下边业务窗口倒是不延时,看来内存增大还是有好处的…呵…;但是源头问题还是没解决,不行的呀!到了晚饭时,一道灵光拍进脑门,我看到代码里我是每异步调用一次,就初始化一次ArcFace的SDK.我就想,是不是这个原因导致呢?修改方法,去试试!! //把那个委托改成如下:

  1. public delegate bool MatchHandler(string userid, string studyid, string photoid, string photopath, IntPtr RecognizeEngine, IntPtr DetectEngine);

//然后初始化SDK放到了Program里做:

  1. string appId = "4yHjnxK94FCK6L7HaJieWawSLubnANXXXXX";
  2. string sdkFDKey = "7S6Xp4mtroLnjTt7qDYnd2dqHXXXXX";
  3. string sdkFRKey = "7S6Xp4mtroLnjTt7qDYnd2dxSgXXXXX";
  4. int retCode = AFDFunction.AFD_FSDK_InitialFaceEngine(appId, sdkFDKey, pMem, detectSize, ref DetectEngine, 5, nScale, nMaxFaceNum);
  5. int retCode2 = AFRFunction.AFR_FSDK_InitialEngine(appId, sdkFRKey, pMemRecongnize, detectSize, ref RecognizeEngine); 

//最后把异步调用的方法改成如下:

  1. MatchHandler handler = new MatchHandler(MatchUserFace.GetAndMatchImage);
  2. string Identification = string.Format("USERID:{0} STUDYID:{1} PHID:{2}", userid, studyid, photoid);
  3. IAsyncResult result = handler.BeginInvoke(userid, studyid, photoid, pathstr, RecognizeEngine, DetectEngine, new AsyncCallback(CallbackFunc), Identification);

再次发布到服务器.然后再到资源监视器去看,哟…线程数不高了而且增长的还不快…好开心!!以为搞好了;就回宿舍睡觉去了!!不想…睡得迷糊的时候,我们的客服小妹妹的电话就打到我这了,我说什么事,她说现在大面积反映用户比对不了?what?我说不可能吧,是不是当地电信故障呀?我自己拿手机试了下,真的不行呀!!!快速赶回办公室远程看了下服务器,我的乖乖…比对端卦了!!!我再看日志,日志没有捕捉到程序异常,只是捕到了个:Value cannot be null.Parameter name: source;我吃你大米了,我刨你家玉米地了,为啥要这么对我!重启比对端,然后都可以正常运作了…我决定在这监视这个比对端,在资源监视器我到是发现了一个:w3wp.exe它在不断的涨内存(这是要划重点的)想想这可已经是深夜了.果不出其然,运行了大概两个多小时后,程序又卦了.我的乖乖,为啥会这样呢,一时半会也想不出办法呀!我也总不能呆在服务器旁它停了,我就重启吧! 第二天致电虹软,反映了程序会运行一段时间就会卦掉,虹软这边也提出了很多宝贵意见,

1.先着眼把捕捉到的那个错误,查出来,看看是否处理好了,程序还会不会卦;那我就在程序里增加了日志打印,还真就发现了几个在DEMO里没有处理到的问题:

  1. 每个Marshal.AllocHGlobal,用完以后,一定要释放;
  2. AFD_FSDK_StillImageFaceDetection;AFR_FSDK_ExtractFRFeature;这两个函数要判断返回值是否等于0; 所以 MatchUserFace 调用类我作了如下修改
  1. private static byte[] detectAndExtractFeature(Image imageParam, out Image facerect,
  2. IntPtr RecognizeEngine, IntPtr DetectEngine)
  3. {
  4. byte[] feature = null; facerect = null;
  5.  
  6. try
  7. {
  8. int width = 0; int height = 0; int pitch = 0;
  9. Bitmap bitmap = new Bitmap(imageParam);
  10. byte[] imageData = getBGR(bitmap, ref width, ref height, ref pitch);
  11. IntPtr imageDataPtr = Marshal.AllocHGlobal(imageData.Length);
  12. Marshal.Copy(imageData, 0, imageDataPtr, imageData.Length);
  13.  
  14. ASVLOFFSCREEN offInput = new ASVLOFFSCREEN();
  15. offInput.u32PixelArrayFormat = 513;
  16. offInput.ppu8Plane = new IntPtr[4];
  17. offInput.ppu8Plane[0] = imageDataPtr;
  18. offInput.i32Width = width;
  19. offInput.i32Height = height;
  20. offInput.pi32Pitch = new int[4];
  21. offInput.pi32Pitch[0] = pitch;
  22. AFD_FSDK_FACERES faceRes = new AFD_FSDK_FACERES();
  23. IntPtr offInputPtr = Marshal.AllocHGlobal(Marshal.SizeOf(offInput));
  24. Marshal.StructureToPtr(offInput, offInputPtr, false);
  25. IntPtr faceResPtr = Marshal.AllocHGlobal(Marshal.SizeOf(faceRes));
  26.  
  27. //人脸检测
  28. int detectResult = AFDFunction.AFD_FSDK_StillImageFaceDetection(DetectEngine, offInputPtr, ref faceResPtr);
  29. if (detectResult == 0)
  30. {
  31. try
  32. {
  33. object obj = Marshal.PtrToStructure(faceResPtr, typeof(AFD_FSDK_FACERES));
  34. faceRes = (AFD_FSDK_FACERES)obj;
  35. for (int i = 0; i < faceRes.nFace; i++)
  36. {
  37. MRECT rect = (MRECT)Marshal.PtrToStructure(faceRes.rcFace + Marshal.SizeOf(typeof(MRECT)) * i, typeof(MRECT));
  38. int orient = (int)Marshal.PtrToStructure(faceRes.lfaceOrient + Marshal.SizeOf(typeof(int)) * i, typeof(int));
  39. if (i == 0)
  40. {
  41. facerect = CutFace(bitmap, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
  42. }
  43. }
  44. }
  45. catch (Exception ex)
  46. {
  47.  
  48. LogNetWriter.Error("人脸检测时出错:" + ex.Message);
  49. }
  50.  
  51. }
  52.  
  53. if (faceRes.nFace > 0)
  54. {
  55. try
  56. {
  57. AFR_FSDK_FaceInput faceResult = new AFR_FSDK_FaceInput();
  58. int orient = (int)Marshal.PtrToStructure(faceRes.lfaceOrient, typeof(int));
  59. faceResult.lOrient = orient;
  60. faceResult.rcFace = new MRECT();
  61. MRECT rect = (MRECT)Marshal.PtrToStructure(faceRes.rcFace, typeof(MRECT));
  62. faceResult.rcFace = rect;
  63. IntPtr faceResultPtr = Marshal.AllocHGlobal(Marshal.SizeOf(faceResult));
  64. Marshal.StructureToPtr(faceResult, faceResultPtr, false);
  65.  
  66. AFR_FSDK_FaceModel localFaceModels = new AFR_FSDK_FaceModel();
  67. IntPtr localFaceModelsPtr = Marshal.AllocHGlobal(Marshal.SizeOf(localFaceModels));
  68. int extractResult = AFRFunction.AFR_FSDK_ExtractFRFeature(RecognizeEngine, offInputPtr, faceResultPtr, localFaceModelsPtr);
  69. if (extractResult == 0)
  70. {
  71. Marshal.FreeHGlobal(faceResultPtr);
  72. Marshal.FreeHGlobal(offInputPtr);
  73.  
  74. object objFeature = Marshal.PtrToStructure(localFaceModelsPtr, typeof(AFR_FSDK_FaceModel));
  75.  
  76. Marshal.FreeHGlobal(localFaceModelsPtr);
  77.  
  78. localFaceModels = (AFR_FSDK_FaceModel)objFeature;
  79. feature = new byte[localFaceModels.lFeatureSize];
  80. Marshal.Copy(localFaceModels.pbFeature, feature, 0, localFaceModels.lFeatureSize);
  81.  
  82. localFaceModels = new AFR_FSDK_FaceModel();
  83. }
  84. }
  85. catch (Exception ex)
  86. {
  87.  
  88. LogNetWriter.Error("提取特征时出错:" + ex.Message);
  89. }
  90.  
  91. }
  92.  
  93. bitmap.Dispose();
  94. imageData = null;
  95. Marshal.FreeHGlobal(imageDataPtr);
  96. //Marshal.FreeHGlobal(faceResPtr);
  97. offInput = new ASVLOFFSCREEN();
  98. faceRes = new AFD_FSDK_FACERES();
  99. }
  100. catch (Exception ex)
  101. {
  102. LogNetWriter.Error("识别人脸并提取人脸特征出错:" + ex.Message);
  103. }
  104. return feature;
  105. }

当然了,比对的时候也作了一些修改,就是当比对完了以后,就做了指针释放;

  1. Marshal.FreeHGlobal(firstFeaturePtr);
  2. Marshal.FreeHGlobal(secondFeaturePtr);
  3. Marshal.FreeHGlobal(firstPtr);
  4. Marshal.FreeHGlobal(secondPtr);

经过这一次修改后,再发布到服务器,哟…不错哦..运行的时间久了…但还是会卦,而且那个w3wp.exe还是会不断的拉内存;这个版本的运行时间可以达到4小左右了;我就想总得有个解决办法吧;再次致电虹软,再次反映这个问题,虹软这边给我的建议就是不要去进行多线程,我想想也对,要把逻辑简单化,我就把识别核心打包成一个EXE.然后在Program里调用这个EXE.意思就是每当我有需要识别的图片,我就调一个EXE.然后EXE处理完以后,就自我释放了… 于是我改了第三版:

  1. //这里就是一条线程在做处理
  2. string strmatch = string.Empty;
  3. ControlExeClass _ControlExeClass = new Model.ControlExeClass();
  4. //这个方法是调一个EXE,EXE的内容是:ControlExeClass.cs;
  5. //做的任务就是把图片进行人脸检测,人脸特征提取,人脸识别;
  6. bool bo = _ControlExeClass.ControlExe(userid, studyid, photoid, pathstr);
  7. if (bo)
  8. {
  9. iCheck_OK++;
  10. label5.Text = iCheck_OK.ToString();
  11. strmatch = " 比对结果:OK";
  12. }
  13. else
  14. {
  15.  
  16. strmatch = " 比对结果:NO";
  17. }
  18. string dates = " 比对时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
  19.  
  20. textBox1.Text += " USERID:" + userid + " STUDYID:" + studyid + " PHID:" + photoid + strmatch + dates + Environment.NewLine;

  

然后那个EXE就是沿用MatchUserFace调用类,在EXE的主线程里完成调用;还别说,用了这个方法后,内存不拉升了,而且w3wp.exe上涨,也只是在EXE工作的一刹那上来,EXE干完活后,它就会生成一个新的w3wp.exe,旧的w3wp.exe那个会被注销掉…哗…想想就开心,终于如愿解决了问题,但….当一个人觉得越顺利时,往往大麻烦就会来了.正如我觉得上天不会对我那么好一样,运行了大概一天后,程序还是卦了.苍天呀,大地呀,我到底做错了什么…. 正在我一筹莫展时,我就老记恨这个w3wp.exe,到底是什么东东,好,度娘下彻底了解下它. 度娘是这么形容它的: w3wp.exe是在IIS(因特网信息服务器)与应用程序池相关联的一个进程,如果你有多个应用程序池,就会有对应的多个w3wp.exe的进程实例运行。这个进程用来分配大量的系统资源。 好,既然说我的IIS里的应用程序池,那我就对我的应用程序池进行固定内存回收不就好了嘛;我就对线程池做了一个固定内存回收,当达到400000KB时就做一次回收. 这一下设置做下去后,的确是立竿见影的,当EXE工作时w3wp.exe就从来没高过400000KB;我想这一下应该彻底解决了吧;可是….程序还是卦了….我是真的不得上天倦顾呀… 一连几天毫无头绪,胡子长一脸了,也没心思刮,领导这边还想刮我骨头呢…唉…上下压力都好大呀.搞得我肚子也不舒服,就去厕所蹲了个坑,还别说,这个坑,含金量特高.又一道灵光打进了我的脑门,我想呀,是不是我的递归出现了问题呢???我就回去看了下代码,我的递归逻辑是没有问题的呀,一步步有板有眼,这是怎么回事呢,我又度娘了下,关于C#的递归,是这么形容的:一个算法中,由于递归调用次数过多,堆栈是会溢出。递归使用的内存大小累计达4G,系统就会进行内存回收. 至于何时收,怎么收,就是windows的事情了.乖乖…既然有这么一个限定,我不用不就好了嘛,我就用死询还不好吗? 所以第4版修改如下:

  1. private static void CycleData()
  2. {
  3. while (true)
  4. {
  5. if (_DoWork)
  6. {
  7. break;
  8. }
  9. else
  10. {
  11. QueryDataFile("U");
  12. Thread.Sleep(1500);
  13. }
  14.  
  15. Thread.Sleep(2000);
  16. }
  17.  
  18. }

  

至此所有问题解决!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!哦…..忘说了,我后来没有单独调用EXE这种方法了,改成了第一版的控制台程序,其结果是一样的;

现在…呵呵…我可是十分轻松着座在大班椅上,喝着奶茶,身边座着小秘,我说,她打的这篇文章…呵….开玩笑了,文章里每个字都是我自己亲手敲的,同时也十分感谢虹软能提供这么优秀的SDK供我使用,更要感谢虹软的技术支持,给我莫大的帮助; 最后总结几点:

1.SDK可以只初始化一次,然后ref传参进结构体,就可以一直用下去;

2.每个Marshal.AllocHGlobal,用完以后,一定要释放;

3.可以异步回调进行;

4.AFRFunction.AFR_FSDK_ExtractFRFeature; AFDFunction.AFD_FSDK_StillImageFaceDetection; 这两个函数要判断返回值是否等于0;

5.最最最重要一点,严禁使用递归去调用;宁愿用死询代替;(因为这个就是导致我程序死掉的主因),因为递归要是深度太大,而且次数过多,累计内存使用达4G以上,系统就会做一次线程与内存回收,至于怎么收,何时收就是不定时的,所以一定不要用递归,这个是我在C#官方看到对于递归的解释;

6.如果是使用windows服务器进行虹软SDK的;建议IIS线程池做一个固定内存回收机制; 最后上传一下几个示例片段吧,因为个中涉及到一些数据库操作,我整个工程就不上传了

人脸识别ArcFace C#DEMO 开发应用全过程的更多相关文章

  1. 基于Arcface 免费离线人脸识别 2.0 Demo C#

    本来打算做个C#版demo,但没用成功.使用虹软最新人脸识别技术开发完成 过程如下: 1. 传入一张单人脸照片: 2.调用检测人脸函数ASFDetectFaces,成功返回人脸信息的指针: 3.使用 ...

  2. C# ArcFace 免费人脸识别 2.0 demo

    **配置过程:** 1. 到[虹软官网](https://ai.arcsoft.com.cn/index.htm?utm_source=csdn&utm_medium=referral)下载S ...

  3. 人脸识别 ArcFace Demo [Windows]

    Arcsoft ArcfaceDemo for Windows, VS2013 C++   使用虹软技术开发完成 使用步骤: 1.下载SDK包,32位Windows平台将五个SDK包里lib中的文件到 ...

  4. C++ 虹软人脸识别 ArcFace 2.0 Demo

    环境配置: 开发环境:Win10 + VS 2013 SDK版本:ArcFace v2.0 OpenCV版本:2.4.9 平台配置: x64.x86下Release.Debug SDK 下载地址:戳这 ...

  5. 虹软人脸识别——官方 Qt Demo 移植到 Linux

    一.前言 最近需要在 Linux 平台下开发一个人脸识别相关的应用,用到了虹软的人脸识别 SDK.之前在 Windows 平台用过,感觉不错,SDK 里面还带了 Demo 可以快速看到效果.打开 Li ...

  6. 人脸识别最新开发经验demo分享

    本来打算做个C#版demo,但没用成功,基于虹软的免费人脸识别技术 过程如下: 1. 传入一张单人脸照片: 2.调用检测人脸函数ASFDetectFaces,成功返回人脸信息的指针: 3.使用 Mar ...

  7. Android静态图片人脸识别的完整demo(附完整源码)

    Demo功能:利用android自带的人脸识别进行识别,标记出眼睛和人脸位置.点击按键后进行人脸识别,完毕后显示到imageview上. 第一部分:布局文件activity_main.xml < ...

  8. 基于百度AI人脸识别技术的Demo

    编写demo之前首先浏览官方API:http://ai.baidu.com/docs#/Face-API/top 下面是源码: package com.examsafety.test; import ...

  9. 人脸识别分析小Demo

    人脸识别分析 调用 腾讯AI人脸识别接口 测试应用 纯py文件测试照片 # -*- coding: utf-8 -*- import json from tencentcloud.common imp ...

随机推荐

  1. 【递归打卡2】求两个有序数组的第K小数

    [题目] 给定两个有序数组arr1和arr2,已知两个数组的长度分别为 m1 和 m2,求两个数组中的第 K 小数.要求时间复杂度O(log(m1 + m2)). [举例] 例如 arr1 = [1, ...

  2. 《k8s-1.13版本源码分析》-测试环境搭建(k8s-1.13版本单节点环境搭建)

    本文原始地址(gitbook格式):https://farmer-hutao.github.io/k8s-source-code-analysis/prepare/debug-environment. ...

  3. PHP中反射的简单实用(动态代理)

    <?php class mysql{ function connect($db){ echo "连接mysql数据库${db[0]} \r\n"; } } class ora ...

  4. 结合JDK源码看设计模式——装饰者模式

    定义 在不改变原有对象的基础之上,将功能附加到对象上 适用场景 扩展一个类的功能 动态的给对象增加功能,当功能不需要的时候能够动态删除 详解 在看到定义的时候,可能很多人会想,这不就是继承吗?的确很像 ...

  5. GIS之家小专栏

    专栏简介:WebGIS开发者@GIS之家,一直混迹GIS行业,关注WebGIS开发方向,在本专栏中,分享WebGIS入门开发系列技术文章 核心内容: arcgis api 3.x for js开发系列 ...

  6. Centos 配置开机启动脚本启动 docker 容器

    Centos 配置开机启动脚本启动 docker 容器 Intro 我们的 Centos 服务器上部署了好多个 docker 容器,因故重启的时候就会导致还得手动去手动重启这些 docker 容器,为 ...

  7. 2018-02-24 项目/教程中使用母语命名的"问题"

    早先试图找使用中文命名代码的项目, 但所获寥寥: 索引: 用中文编写代码的实用开源项目 · Issue #6 · program-in-chinese/overview. 更不用说教程了: 索引: 用 ...

  8. cmd wevtutil 读取远程日志错误,Error:在没有配置的 DNS 服务器响应之后,名称 Server23.localdomain 的名称解析超时。

    想要根据xml文件筛选器读取远程主机最新的几条日志,结果老是提示: Error : wevtutil qe SystemQuery.xml /f:text /rd: /sq:true /r:\\*** ...

  9. C# 4.0 的 Visual Studio 2010 示例

    C# 4.0 的 Visual Studio 2010 示例 我们将 C# 示例分为两种不同的类别: 语言示例 LINQ 示例 语言示例 语言示例帮助您熟悉各种 C# 语言功能.这些示例包含在 Lan ...

  10. 移动设备分辨率(终于弄懂了为什么移动端设计稿总是640px和750px)

    在我开始写移动端页面至今,一直有2个疑问困扰着我,我只知道结果但不知道为什么 问题1:为什么设计师给的设计稿总是640px或750px(现在一般以Phone6为基准,给的750px) 问题2:为什么我 ...