手上有一个项目, 需要检验使用本程序的, 是否本人! 因为在程序使用前, 我们都已经做过头像现场采集, 所以源头呢是不成问题的, 那么人脸检测, 人脸比对, 怎么办呢? 度娘了下, 目前流行的几个人脸检测, 人脸比对核心, 大多都是基于互联网的, 但我们的项目是基于本地服务器, 那就有点麻烦了, 后来找到 ArcFace. 它的核心允许本地调用, 那就好办了, 立刻去了官网, 看论坛, 下 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 里没有处理到的问题:
每个 Marshal.AllocHGlobal, 用完以后, 一定要释放;
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 线程池做一个固定内存回收机制; 最后上传一下几个示例片段吧, 因为个中涉及到一些数据库操作, 我整个工程就不上传了
来源: https://www.cnblogs.com/bodaren/p/9697494.html