ArcFac_C#_DEMO開發(fā)

手上有一個(gè)項(xiàng)目,需要檢驗(yàn)使用本程序的,是否本人!因?yàn)樵诔绦蚴褂们?,我們都已?jīng)做過頭像現(xiàn)場(chǎng)采集,所以源頭呢是不成問題的,那么人臉檢測(cè),人臉比對(duì),怎么辦呢?度娘了下,目前流行的幾個(gè)人臉檢測(cè),人臉比對(duì)核心,大多都是基于互聯(lián)網(wǎng)的,但我們的項(xiàng)目是基于本地服務(wù)器,那就有點(diǎn)麻煩了,后來找到ArcFace. 它的核心允許本地調(diào)用,那就好辦了,立刻去了官網(wǎng),看論壇,下DEMO;我當(dāng)時(shí)下的是這個(gè):ArcFace C#DEMO

本以為可以一帆風(fēng)順的就可以把項(xiàng)目搞定了,不想...噩夢(mèng)才剛剛開始呢...且聽我細(xì)細(xì)道來:

首先說下我的調(diào)用邏輯;
項(xiàng)目里有一個(gè)采集端(每個(gè)業(yè)務(wù)窗口),負(fù)責(zé)采集現(xiàn)場(chǎng)人像,并通過ArcFace人臉檢測(cè),特征提取,獲取到.dat比對(duì)源(ServiceFaceModels),然后存到數(shù)據(jù)庫(blob);
項(xiàng)目里的應(yīng)用端(用戶手機(jī)),隨機(jī)時(shí)間的調(diào)用攝像頭,采集到被比對(duì)圖片;并對(duì)該記錄進(jìn)行標(biāo)記;
項(xiàng)目時(shí)比對(duì)端(服務(wù)器),定時(shí)詢問數(shù)據(jù)庫,哪些被標(biāo)記記錄需要比對(duì),然后通過數(shù)據(jù)庫記錄,找到該圖片,并通過ArcFace人臉檢測(cè),特征提取,獲取到.dat被比對(duì)源(LocalFaceModels) 然后將這兩個(gè)源在內(nèi)存中進(jìn)行比對(duì),得分高于0.7的,就通過;

前兩端就不多說了,都是一些常規(guī)的操作。重點(diǎn)講下比對(duì)端(服務(wù)器);
先說我做的第一個(gè)版本,做的是一個(gè)控制臺(tái)程序;
//首先定義了一個(gè)調(diào)用類;
MatchUserFace;它里邊包含了初始化,人臉檢測(cè),特征提取,人臉比對(duì),以及一些輔助方法;

//然后在Program里定義了一個(gè)委托,這個(gè)委托的作用,就是能夠讓我可以帶參數(shù)進(jìn)去ArcFace的檢測(cè)與比對(duì)核心;

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

//最后我的Program里邊,就是做一個(gè)遞歸(這是個(gè)罪人),去不斷的問數(shù)據(jù)庫拿被標(biāo)志需要進(jìn)行核對(duì)的記錄,拿到圖片后,就進(jìn)行比對(duì);
QueryDataFile(string upstate);下邊這段就是在QueryDataFile();去實(shí)現(xiàn)異步調(diào)用比對(duì)核心;

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);

下邊這段就是異步的結(jié)果回調(diào);

 static void CallbackFunc(IAsyncResult result)
        {
            MatchHandler handler = (MatchHandler)((AsyncResult)result).AsyncDelegate;
            bool match = handler.EndInvoke(result);
            string strmatch = string.Empty;
            if (match)
            {
                strmatch = " 比對(duì)結(jié)果:OK";
            }
            else
            {

                strmatch = " 比對(duì)結(jié)果:NO";
            }
            Console.WriteLine(result.AsyncState + strmatch);
            GC.Collect();
        }

寫好了,發(fā)布到服務(wù)器上,還想著中午吃個(gè)雞腿獎(jiǎng)勵(lì)下自己;不想...發(fā)布后不到兩小時(shí),小弟來說:服務(wù)器是不是出問題了,下邊所有業(yè)務(wù)窗口訪問速度嚴(yán)重延遲...立馬跑到機(jī)房去看,一看沒毛病呀,所有的服務(wù)都好好的,沒有卦死..再打開資源監(jiān)視器一看,靠...那個(gè)比對(duì)端一下吃3個(gè)多G的內(nèi)存,而且還在不斷上升中...立馬停掉,然后再問小弟,下邊業(yè)務(wù)是否正常,他回復(fù)正常了...那么說,就是我寫的這個(gè)比對(duì)端有問題了!改!!!

第二個(gè)版本,

下了機(jī)房看代碼...左看右看,沒有哪不對(duì)呀,一步步按步就班的...毫無頭緒時(shí),就想,是不是服務(wù)器內(nèi)存不夠而已,打申請(qǐng)拿了64G回來。再開程序也是一樣吃的很緊,但是下邊業(yè)務(wù)窗口倒是不延時(shí),看來內(nèi)存增大還是有好處的...呵...;但是源頭問題還是沒解決,不行的呀!到了晚飯時(shí),一道靈光拍進(jìn)腦門,我看到代碼里我是每異步調(diào)用一次,就初始化一次ArcFace的SDK。我就想,是不是這個(gè)原因?qū)е履兀啃薷姆椒?,去試?!
//把那個(gè)委托改成如下:

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);

//最后把異步調(diào)用的方法改成如下:

  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);

再次發(fā)布到服務(wù)器。然后再到資源監(jiān)視器去看,喲...線程數(shù)不高了而且增長(zhǎng)的還不快...好開心??!以為搞好了;就回宿舍睡覺去了?。〔幌?..睡得迷糊的時(shí)候,我們的客服小妹妹的電話就打到我這了,我說什么事,她說現(xiàn)在大面積反映用戶比對(duì)不了?what?我說不可能吧,是不是當(dāng)?shù)仉娦殴收涎??我自己拿手機(jī)試了下,真的不行呀?。?!快速趕回辦公室遠(yuǎn)程看了下服務(wù)器,我的乖乖...比對(duì)端卦了?。。∥以倏慈罩?,日志沒有捕捉到程序異常,只是捕到了個(gè):Value cannot be null.Parameter name: source;我吃你大米了,我刨你家玉米地了,為啥要這么對(duì)我!重啟比對(duì)端,然后都可以正常運(yùn)作了...我決定在這監(jiān)視這個(gè)比對(duì)端,在資源監(jiān)視器我到是發(fā)現(xiàn)了一個(gè):w3wp.exe它在不斷的漲內(nèi)存(這是要?jiǎng)澲攸c(diǎn)的)想想這可已經(jīng)是深夜了。果不出其然,運(yùn)行了大概兩個(gè)多小時(shí)后,程序又卦了。我的乖乖,為啥會(huì)這樣呢,一時(shí)半會(huì)也想不出辦法呀!我也總不能呆在服務(wù)器旁它停了,我就重啟吧!
第二天致電虹軟,反映了程序會(huì)運(yùn)行一段時(shí)間就會(huì)卦掉,虹軟這邊也提出了很多寶貴意見,
1.先著眼把捕捉到的那個(gè)錯(cuò)誤,查出來,看看是否處理好了,程序還會(huì)不會(huì)卦;那我就在程序里增加了日志打印,還真就發(fā)現(xiàn)了幾個(gè)在DEMO里沒有處理到的問題:
<1>每個(gè)Marshal.AllocHGlobal,用完以后,一定要釋放;
<2>AFD_FSDK_StillImageFaceDetection;AFR_FSDK_ExtractFRFeature;這兩個(gè)函數(shù)要判斷返回值是否等于0;
所以 MatchUserFace 調(diào)用類我作了如下修改:

    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));

                //人臉檢測(cè)
                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("人臉檢測(cè)時(shí)出錯(cuò):" + 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("提取特征時(shí)出錯(cuò):" + 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("識(shí)別人臉并提取人臉特征出錯(cuò):" + ex.Message);
            }
            return feature;
        }

當(dāng)然了,比對(duì)的時(shí)候也作了一些修改,就是當(dāng)比對(duì)完了以后,就做了指針釋放;

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

經(jīng)過這一次修改后,再發(fā)布到服務(wù)器,喲...不錯(cuò)哦..運(yùn)行的時(shí)間久了...但還是會(huì)卦,而且那個(gè)w3wp.exe還是會(huì)不斷的拉內(nèi)存;這個(gè)版本的運(yùn)行時(shí)間可以達(dá)到4小左右了;我就想總得有個(gè)解決辦法吧;再次致電虹軟,再次反映這個(gè)問題,虹軟這邊給我的建議就是不要去進(jìn)行多線程,我想想也對(duì),要把邏輯簡(jiǎn)單化,我就把識(shí)別核心打包成一個(gè)EXE.然后在Program里調(diào)用這個(gè)EXE.意思就是每當(dāng)我有需要識(shí)別的圖片,我就調(diào)一個(gè)EXE.然后EXE處理完以后,就自我釋放了...
于是我改了第三版:

 //這里就是一條線程在做處理
                        string strmatch = string.Empty;
                        ControlExeClass _ControlExeClass = new Model.ControlExeClass();
                        //這個(gè)方法是調(diào)一個(gè)EXE,EXE的內(nèi)容是:ControlExeClass.cs;
                        //做的任務(wù)就是把圖片進(jìn)行人臉檢測(cè),人臉特征提取,人臉識(shí)別;
                        bool bo = _ControlExeClass.ControlExe(userid, studyid, photoid, pathstr);
                        if (bo)
                        {
                            iCheck_OK++;
                            label5.Text = iCheck_OK.ToString();
                            strmatch = "  比對(duì)結(jié)果:OK";
                        }
                        else
                        {

                            strmatch = "  比對(duì)結(jié)果:NO";
                        }
                        string dates = "   比對(duì)時(shí)間:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

                        textBox1.Text += "  USERID:" + userid + "  STUDYID:" + studyid + "  PHID:" + photoid + strmatch + dates + Environment.NewLine;

然后那個(gè)EXE就是沿用MatchUserFace調(diào)用類,在EXE的主線程里完成調(diào)用;還別說,用了這個(gè)方法后,內(nèi)存不拉升了,而且w3wp.exe上漲,也只是在EXE工作的一剎那上來,EXE干完活后,它就會(huì)生成一個(gè)新的w3wp.exe,舊的w3wp.exe那個(gè)會(huì)被注銷掉...嘩...想想就開心,終于如愿解決了問題,,但....當(dāng)一個(gè)人覺得越順利時(shí),往往大麻煩就會(huì)來了。正如我覺得上天不會(huì)對(duì)我那么好一樣,運(yùn)行了大概一天后,程序還是卦了。蒼天呀,大地呀,我到底做錯(cuò)了什么....
正在我一籌莫展時(shí),我就老記恨這個(gè)w3wp.exe,到底是什么東東,好,度娘下徹底了解下它。
度娘是這么形容它的:
w3wp.exe是在IIS(因特網(wǎng)信息服務(wù)器)與應(yīng)用程序池相關(guān)聯(lián)的一個(gè)進(jìn)程,如果你有多個(gè)應(yīng)用程序池,就會(huì)有對(duì)應(yīng)的多個(gè)w3wp.exe的進(jìn)程實(shí)例運(yùn)行。這個(gè)進(jìn)程用來分配大量的系統(tǒng)資源。
好,既然說我的IIS里的應(yīng)用程序池,那我就對(duì)我的應(yīng)用程序池進(jìn)行固定內(nèi)存回收不就好了嘛;我就對(duì)線程池做了一個(gè)固定內(nèi)存回收,當(dāng)達(dá)到400000KB時(shí)就做一次回收。
這一下設(shè)置做下去后,的確是立竿見影的,當(dāng)EXE工作時(shí)w3wp.exe就從來沒高過400000KB;我想這一下應(yīng)該徹底解決了吧;可是....程序還是卦了....我是真的不得上天倦顧呀...
一連幾天毫無頭緒,唉...上下壓力都好大呀。搞得我肚子也不舒服,就去廁所蹲了個(gè)坑,還別說,這個(gè)坑,含金量特高。又一道靈光打進(jìn)了我的腦門,我想呀,是不是我的遞歸出現(xiàn)了問題呢???我就回去看了下代碼,我的遞歸邏輯是沒有問題的呀,一步步有板有眼,這是怎么回事呢,我又度娘了下,關(guān)于C#的遞歸,是這么形容的:一個(gè)算法中,由于遞歸調(diào)用次數(shù)過多,堆棧是會(huì)溢出。遞歸使用的內(nèi)存大小累計(jì)達(dá)4G,系統(tǒng)就會(huì)進(jìn)行內(nèi)存回收。 至于何時(shí)收,怎么收,就是windows的事情了。乖乖...既然有這么一個(gè)限定,我不用不就好了嘛,我就用死詢還不好嗎?
所以第4版修改如下:

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

                Thread.Sleep(2000);
            }

        }

至此所有問題解決!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!哦.....忘說了,我后來沒有單獨(dú)調(diào)用EXE這種方法了,改成了第一版的控制臺(tái)程序,其結(jié)果是一樣的;
現(xiàn)在...呵呵...我可是十分輕松著座在大班椅上,喝著奶茶,身邊座著小秘,我說,她打的這篇文章...呵....開玩笑了,文章里每個(gè)字都是我自己親手敲的,同時(shí)也十分感謝虹軟能提供這么優(yōu)秀的SDK供我使用,更要感謝虹軟的技術(shù)支持,給我莫大的幫助;
最后總結(jié)幾點(diǎn):
1.SDK可以只初始化一次,然后ref傳參進(jìn)結(jié)構(gòu)體,就可以一直用下去;
2.每個(gè)Marshal.AllocHGlobal,用完以后,一定要釋放;
3.可以異步回調(diào)進(jìn)行;
4.AFRFunction.AFR_FSDK_ExtractFRFeature;
AFDFunction.AFD_FSDK_StillImageFaceDetection;
這兩個(gè)函數(shù)要判斷返回值是否等于0;
5.最最最重要一點(diǎn),禁使用遞歸去調(diào)用;寧愿用死詢代替;(因?yàn)檫@個(gè)就是導(dǎo)致我程序死掉的主因),因?yàn)檫f歸要是深度太大,而且次數(shù)過多,累計(jì)內(nèi)存使用達(dá)4G以上,系統(tǒng)就會(huì)做一次線程與內(nèi)存回收,至于怎么收,何時(shí)收就是不定時(shí)的,所以一定不要用遞歸,這個(gè)是我在C#官方看到對(duì)于遞歸的解釋;
6.如果是使用windows服務(wù)器進(jìn)行虹軟SDK的;建議IIS線程池做一個(gè)固定內(nèi)存回收機(jī)制。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容