手上有一个项目,需要检验使用本程序的,是否本人!因为在程序使用前,我们都已经做过头像现场采集,所以源头呢是不成问题的,那么人脸检测,人脸比对,怎么办呢?度娘了下,目前流行的几个人脸检测,人脸比对核心,大多都是基于互联网的,但我们的项目是基于本地服务器,那就有点麻烦了,后来找到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里做://然后初始化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线程池做一个固定内存回收机制。

ArcFac_C#_DEMO开发的更多相关文章

  1. java开发支付宝支付详细流程_demo的运行

    首先我要吐槽一下支付宝的开放平台简直就是一个迷宫,赞同的顶一下,下面我把要下载的地址给贴出来要不真不好找: 一.准备工作 1.签名工具下载 https://docs.open.alipay.com/2 ...

  2. 深入理解iOS开发中的BitCode功能

    前言 做iOS开发的朋友们都知道,目前最新的Xcode7,新建项目默认就打开了bitcode设置.而且大部分开发者都被这个突如其来的bitcode功能给坑过导致项目编译失败,而这些因为bitcode而 ...

  3. 使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(九)-- 单元测试

    本篇将结合这个系列的例子的基础上演示在Asp.Net Core里如何使用XUnit结合Moq进行单元测试,同时对整个项目进行集成测试. 第一部分.XUnit 修改 Project.json 文件内容, ...

  4. 【Android 应用开发】 Android 相关代码规范 更新中 ...

    . 简介 : Android 常用的代码结构, 包括包的规范, 测试用例规范, 数据库模块常用编写规范; 参考 : 之前写的一篇博客 [Android 应用开发] Application 使用分析 ; ...

  5. Odoo开发规范

    本文来源:https://www.jianshu.com/p/e892bf01f036 Odoo开发规范 模块结构 文件夹列表及对应作用 data/:演示和数据文件 models/:模型定义 cont ...

  6. 避免重复造轮子的UI自动化测试框架开发

    一懒起来就好久没更新文章了,其实懒也还是因为忙,今年上半年的加班赶上了去年一年的加班,加班不息啊,好了吐槽完就写写一直打算继续的自动化开发 目前各种UI测试框架层出不穷,但是万变不离其宗,驱动PC浏览 ...

  7. App开发:模拟服务器数据接口 - MockApi

    为了方便app开发过程中,不受服务器接口的限制,便于客户端功能的快速测试,可以在客户端实现一个模拟服务器数据接口的MockApi模块.本篇文章就尝试为使用gradle的android项目设计实现Moc ...

  8. 使用HTML5开发Kinect体感游戏

    一.简介 我们要做的是怎样一款游戏? 在前不久成都TGC2016展会上,我们开发了一款<火影忍者手游>的体感游戏,主要模拟手游章节<九尾袭来 >,用户化身四代,与九尾进行对决, ...

  9. Android SwipeRefreshLayout 下拉刷新——Hi_博客 Android App 开发笔记

    以前写下拉刷新 感觉好费劲,要判断ListView是否滚到顶部,还要加载头布局,还要控制 头布局的状态,等等一大堆.感觉麻烦死了.今天学习了SwipeRefreshLayout 的用法,来分享一下,有 ...

随机推荐

  1. [转]如何选择Html.RenderPartial和Html.RenderAction

    Html.RenderPartial与Html.RenderAction这两个方法都是用来在界面上嵌入用户控件的. Html.RenderPartial是直接将用户控件嵌入到界面上: <%Htm ...

  2. Github - 修改语言统计

    前些日子看到有人提到这个问题,于是自己也试着解决了一番,在此记录下来,希望对大家有帮助. Github中创建一个repository后会出现一个统计使用语言的颜色条. 就是下面这个东西: 似乎很多人遇 ...

  3. 一、hbase单机安装

    下文将快速构建并启动单节点hbase,不使用hdfs作为存储,不使用独立的zookeeper hbase官网:http://hbase.apache.org/ 一.JDK环境 hbase需要JDK环境 ...

  4. 网络安全之——DNS欺骗实验

        ---------------发个帖证明一下存在感,希望各位大牛们,别喷我!!谢谢--------------         DNS(域名系统)的作用是把网络地址(域名,以一个字符串的形式) ...

  5. eclipse 更改背景颜色字体

    原文 切一个自己的图: 废话不说,直接入题. 方式一:替换Eclipse的配置文件 其实Eclipse的各种配置都是在文件设置里的,因此只要用一个配置好的模版来替换默认的配置文件,即可将所有配置克隆到 ...

  6. UI设计师必备

    网站大全 国外的花瓣--Pinterest • The world’s catalog of ideas 字体海洋--求字体网提供中文和英文字体库下载.识别与预览服务,找字体的好帮手原创设计UI--站 ...

  7. DB2 Metadata

    http://www.devart.com/dotconnect/db2/docs/MetaData.html Instead of specifying the metadata collectio ...

  8. 牛客Wannafly挑战赛11E 白兔的刁难

    传送门 如果大力推单位根反演就可以获得一个 \(k^2logn\) 的好方法 \[ans_{t}=\frac{1}{k}\sum_{i=0}^{k-1}(w_k^{-t})^i(w_k^i+1)^n\ ...

  9. BZOJ1258 [CQOI2007]三角形

    Description 画一个等边三角形,把三边的中点连接起来,得到四个三角形,把它们称为T1,T2,T3,T4,如图1.把前三个三角形也这样划分,得到12个更小的三角形:T11,T12,T13,T1 ...

  10. bootstrap框架怎么在html页面加载使用

    今天敲代码的时候,正好碰到这个问题. 与大家分享这个解决方法:     1/7 到bootstrap官方网站下载,对于我们开发者来说,直接下载编译和压缩后的CSS.JavaScript文件,另外还包含 ...