人脸识别ArcFace C#DEMO 开发应用全过程

2023-11-30 20:18

本文主要是介绍人脸识别ArcFace C#DEMO 开发应用全过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

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

首先说下我的调用逻辑;
项目里有一个采集端(每个业务窗口),负责采集现场人像,并通过ArcFace人脸检测,特征提取,获取到.dat比对源(ServiceFaceModels),然后存到数据库(blob);
项目里的应用端(用户手机),随机时间的调用摄像头,采集到被比对图片;并对该记录进行标记;
项目时比对端(服务器),定时询问数据库,哪些被标记记录需要比对,然后通过数据库记录,找到该图片,并通过ArcFace人脸检测,特征提取,获取到.dat被比对源(LocalFaceModels) 然后将这两个源在内存中进行比对,得分高于0.7的,就通过;

前两端就不多说了,都是一些常规的操作.重点讲下比对端(服务器);
先说我做的第一个版本,做的是一个控制台程序;
//首先定义了一个调用类;
MatchUserFace;它里边包含了初始化,人脸检测,特征提取,人脸比对,以及一些辅助方法;

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

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

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

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

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

 static void CallbackFunc(IAsyncResult result){MatchHandler handler = (MatchHandler)((AsyncResult)result).AsyncDelegate;bool match = handler.EndInvoke(result);string strmatch = string.Empty;if (match){strmatch = " 比对结果:OK";}else{strmatch = " 比对结果:NO";}Console.WriteLine(result.AsyncState + strmatch);GC.Collect();}

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

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

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

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

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

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

  MatchHandler handler = new MatchHandler(MatchUserFace.GetAndMatchImage);string Identification = string.Format("USERID:{0} STUDYID:{1} PHID:{2}", userid, studyid, photoid);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 调用类我作了如下修改:

    private static byte[] detectAndExtractFeature(Image imageParam, out Image facerect,IntPtr RecognizeEngine, IntPtr DetectEngine){byte[] feature = null; facerect = null;try{int width = 0; int height = 0; int pitch = 0;Bitmap bitmap = new Bitmap(imageParam);byte[] imageData = getBGR(bitmap, ref width, ref height, ref pitch);IntPtr imageDataPtr = Marshal.AllocHGlobal(imageData.Length);Marshal.Copy(imageData, 0, imageDataPtr, imageData.Length);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;AFD_FSDK_FACERES faceRes = new AFD_FSDK_FACERES();IntPtr offInputPtr = Marshal.AllocHGlobal(Marshal.SizeOf(offInput));Marshal.StructureToPtr(offInput, offInputPtr, false);IntPtr faceResPtr = Marshal.AllocHGlobal(Marshal.SizeOf(faceRes));//人脸检测int detectResult = AFDFunction.AFD_FSDK_StillImageFaceDetection(DetectEngine, offInputPtr, ref faceResPtr);if (detectResult == 0){try{object obj = Marshal.PtrToStructure(faceResPtr, typeof(AFD_FSDK_FACERES));faceRes = (AFD_FSDK_FACERES)obj;for (int i = 0; i < faceRes.nFace; i++){MRECT rect = (MRECT)Marshal.PtrToStructure(faceRes.rcFace + Marshal.SizeOf(typeof(MRECT)) * i, typeof(MRECT));int orient = (int)Marshal.PtrToStructure(faceRes.lfaceOrient + Marshal.SizeOf(typeof(int)) * i, typeof(int));if (i == 0){facerect = CutFace(bitmap, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);}}}catch (Exception ex){LogNetWriter.Error("人脸检测时出错:" + ex.Message);}}if (faceRes.nFace > 0){try{AFR_FSDK_FaceInput faceResult = new AFR_FSDK_FaceInput();int orient = (int)Marshal.PtrToStructure(faceRes.lfaceOrient, typeof(int));faceResult.lOrient = orient;faceResult.rcFace = new MRECT();MRECT rect = (MRECT)Marshal.PtrToStructure(faceRes.rcFace, typeof(MRECT));faceResult.rcFace = rect;IntPtr faceResultPtr = Marshal.AllocHGlobal(Marshal.SizeOf(faceResult));Marshal.StructureToPtr(faceResult, faceResultPtr, false);AFR_FSDK_FaceModel localFaceModels = new AFR_FSDK_FaceModel();IntPtr localFaceModelsPtr = Marshal.AllocHGlobal(Marshal.SizeOf(localFaceModels));int extractResult = AFRFunction.AFR_FSDK_ExtractFRFeature(RecognizeEngine, offInputPtr, faceResultPtr, localFaceModelsPtr);if (extractResult == 0){Marshal.FreeHGlobal(faceResultPtr);Marshal.FreeHGlobal(offInputPtr);object objFeature = Marshal.PtrToStructure(localFaceModelsPtr, typeof(AFR_FSDK_FaceModel));Marshal.FreeHGlobal(localFaceModelsPtr);localFaceModels = (AFR_FSDK_FaceModel)objFeature;feature = new byte[localFaceModels.lFeatureSize];Marshal.Copy(localFaceModels.pbFeature, feature, 0, localFaceModels.lFeatureSize);localFaceModels = new AFR_FSDK_FaceModel();}}catch (Exception ex){LogNetWriter.Error("提取特征时出错:" + ex.Message);}}bitmap.Dispose();imageData = null;Marshal.FreeHGlobal(imageDataPtr);//Marshal.FreeHGlobal(faceResPtr);offInput = new ASVLOFFSCREEN();faceRes = new AFD_FSDK_FACERES();}catch (Exception ex){LogNetWriter.Error("识别人脸并提取人脸特征出错:" + ex.Message);}return feature;}

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

  Marshal.FreeHGlobal(firstFeaturePtr);Marshal.FreeHGlobal(secondFeaturePtr);Marshal.FreeHGlobal(firstPtr);Marshal.FreeHGlobal(secondPtr);

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

 //这里就是一条线程在做处理string strmatch = string.Empty;ControlExeClass _ControlExeClass = new Model.ControlExeClass();//这个方法是调一个EXE,EXE的内容是:ControlExeClass.cs;//做的任务就是把图片进行人脸检测,人脸特征提取,人脸识别;bool bo = _ControlExeClass.ControlExe(userid, studyid, photoid, pathstr);if (bo){iCheck_OK++;label5.Text = iCheck_OK.ToString();strmatch = "  比对结果:OK";}else{strmatch = "  比对结果:NO";}string dates = "   比对时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");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版修改如下:

 private static void CycleData(){while (true){if (_DoWork){break;}else{QueryDataFile("U");Thread.Sleep(1500);}Thread.Sleep(2000);}}

至此所有问题解决!!!哦…忘说了,我后来没有单独调用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 开发应用全过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/438402

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

2. c#从不同cs的文件调用函数

1.文件目录如下: 2. Program.cs文件的主函数如下 using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using System.Windows.Forms;namespace datasAnalysis{internal static

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

zoj3820(树的直径的应用)

题意:在一颗树上找两个点,使得所有点到选择与其更近的一个点的距离的最大值最小。 思路:如果是选择一个点的话,那么点就是直径的中点。现在考虑两个点的情况,先求树的直径,再把直径最中间的边去掉,再求剩下的两个子树中直径的中点。 代码如下: #include <stdio.h>#include <string.h>#include <algorithm>#include <map>#