人臉識別ArcFace C#DEMO 開發應用全過程

手上有一個項目,須要檢驗使用本程序的,是否本人!由於在程序使用前,咱們都已經作過頭像現場採集,因此源頭呢是不成問題的,那麼人臉檢測,人臉比對,怎麼辦呢?度娘了下,目前流行的幾我的臉檢測,人臉比對核心,大多都是基於互聯網的,但咱們的項目是基於本地服務器,那就有點麻煩了,後來找到ArcFace.它的核心容許本地調用,那就好辦了,馬上去了他們的網站下載sdk,看論壇,下DEMO;我當時下的是這個:ArcFace C#DEMO算法

本覺得能夠一路順風的就能夠把項目搞定了,不想…噩夢纔剛剛開始呢…且聽我細細道來:數據庫

首先說下個人調用邏輯;windows

項目裏有一個採集端(每一個業務窗口),負責採集現場人像,並經過ArcFace人臉檢測,特徵提取,獲取到.dat比對源(ServiceFaceModels),而後存到數據庫(blob); 項目裏的應用端(用戶手機),隨機時間的調用攝像頭,採集到被比對圖片;並對該記錄進行標記; 項目時比對端(服務器),定時詢問數據庫,哪些被標記記錄須要比對,而後經過數據庫記錄,找到該圖片,並經過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); 下邊這段就是在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線程池作一個固定內存回收機制;

最後上傳一下幾個示例片斷吧,由於箇中涉及到一些數據庫操做,我整個工程就不上傳了:
相關文章
相關標籤/搜索