手上有一个项目,须要检验使用本程序的,是否本人!由于在程序使用前,咱们都已经作过头像现场采集,因此源头呢是不成问题的,那么人脸检测,人脸比对,怎么办呢?度娘了下,目前流行的几我的脸检测,人脸比对核心,大多都是基于互联网的,但咱们的项目是基于本地服务器,那就有点麻烦了,后来找到ArcFace.它的核心容许本地调用,那就好办了,马上去了虹软的开放平台,看论坛,下DEMO;我当时下的是这个:ArcFace C#DEMOphp
本觉得能够一路顺风的就能够把项目搞定了,不想…噩梦才刚刚开始呢…且听我细细道来:算法
首先说下个人调用逻辑; 项目里有一个采集端(每一个业务窗口),负责采集现场人像,并经过ArcFace人脸检测,特征提取,获取到.dat比对源(ServiceFaceModels),而后存到数据库(blob);数据库
项目里的应用端(用户手机),随机时间的调用摄像头,采集到被比对图片;并对该记录进行标记;windows
项目时比对端(服务器),定时询问数据库,哪些被标记记录须要比对,而后经过数据库记录,找到该图片,并经过ArcFace人脸检测,特征提取,获取到.dat被比对源(LocalFaceModels) 而后将这两个源在内存中进行比对,得分高于0.7的,就经过;服务器
前两端就很少说了,都是一些常规的操做.重点讲下比对端(服务器);多线程
先说我作的第一个版本,作的是一个控制台程序;app
//首先定义了一个调用类; MatchUserFace;它里边包含了初始化,人脸检测,特征提取,人脸比对,以及一些辅助方法;异步
//而后在Program里定义了一个委托,这个委托的做用,就是可以让我能够带参数进去ArcFace的检测与比对核心;函数
public delegate bool MatchHandler(string userid, string studyid, string photoid, string photopath);
//最后个人Program里边,就是作一个递归,去不断的问数据库拿被标志须要进行核对的记录,拿到图片后,就进行比对; QueryDataFile(string upstate);spa
下边这段就是在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里没有处理到的问题:
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线程池作一个固定内存回收机制; 最后上传一下几个示例片断吧,由于个中涉及到一些数据库操做,我整个工程就不上传了