收藏本站
我的資料
  購物車 (0)  
親,您的購物車空空的喲~
去購物車結算
   
查看手機網站
其他帳號登錄: 注冊 登錄
北京元亨信科技有限責任公司

咨詢熱線

010-51994498

追求品質,追求卓越

THE PURSUIT OF EXCELLENCE

商城分類
  • 語音識別
  • 語意識別
  • 人臉識別
    識別儀
    攝像頭
    門禁
  • 圖像識別
    攝像頭
    存儲管理
  • AI算法產品
  • 智能機器人成品
  • 智能設備
  • 綜合
  • 智慧工廠
  • 智慧城市
  • 激光掃描
  • 移動推廣
  • 大數據
    數據庫
    服務器
    云計算
    云存儲

C#人臉識別入門篇--提取人臉特征值及人臉識別

來源:簡書作者:隨風而逝的心情網址:https://www.jianshu.com/p/ee88d4ff6082 

如何識別多個人臉

getImg60.png

在開始之前,先解決一個疑問,這個SDK可以識別多個人臉嗎。答案當然是可以的。在上一章節中我們實現了識別單個人臉的功能。

如果要識別多個人臉,需要進行下面的設置。

定義人臉的識別數目范圍

  int nMaxFaceNum = 50;/*定義人臉識別的數目,有效范圍為1-50*/

修改人臉識別的程序。

在上一章節中,我們的方法是只取到識別到的第一個人臉,因此我們只需要一個顯示人臉的地方就可以了。要識別多個人臉,首先就是修改視圖。

3094619-c3fd76b579aa3187.jpg

界面截圖

然后,修改程序為循環。

//識別每一幅圖像

for (int i = 0; i < faceRes.nFace; i++)

{

   MRECT rect = (MRECT)Marshal.PtrToStructure(faceRes.rcFace + Marshal.SizeOf(typeof(MRECT)) * i, typeof(MRECT));

  Image image = CutFace(bitmap, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);

  if (i == 0)

  {

   /*第一個識別到的人臉保存在原位置*/

      this.pictureBox2.Image = image;

       this.pictureBox2.Tag = faceImageName[i];

   }

  else

  {

       /*后面識別到的人臉按順序并排顯示在下面,使用臨時創建PictureBox控件的方式顯示圖片內容*/

      PictureBox tempPicture = new PictureBox();

      tempPicture.Width = 100;

      tempPicture.Height = 120;

      tempPicture.SizeMode = PictureBoxSizeMode.Zoom;

      tempPicture.Location = new System.Drawing.Point(10 + ((i-1) % 7) * 120, 10 + ((i-1) / 7) * 120);

      tempPicture.Image = image;

      tempPicture.Tag =faceImageName[i];

      this.panel1.Controls.Add(tempPicture);

  }

一步步實現人臉識別

先來看一下我們這節的效果

3094619-507265ebb72e0643.png

效果圖

本節我們主要講解如何根據識別到的人臉信息提取人臉數據特征,并在此基礎上講解一下如何做人臉識別

在人臉識別領域,首先是檢測是否有人臉,人臉的區域是哪里,然后對這個區域進行特征點提取,在提取結束后,告訴計算機,這個人臉是誰。

3094619-cbeab3fc67710c23.png

image

計算機把這些特征信息和人臉的名稱保存下來,就形成了人臉庫,在識別人臉時,計算機通過一定的算法,檢索庫中是否有匹配到的人臉結果,給出相似度數據。當人臉的相似度數據達到一定的數值時,就可以認為同一張人臉。

相似度通常是一個0-1的小數。一般來說,數值越大,表示兩個人越相近。

注:不同人臉引擎的人臉相似度不具有可比性,例如,我們從Face ++ 拿到的同一個人的人臉相似度可能會在0.8-0.9,虹軟的只能在0.6-0.8之間,這并不能說明Face ++ ,它們只是算法的標準不同,例如,虹軟在不同人臉0.1-0.2的時候,Face++達到了0.3-0.5

人臉檢測并建立人臉庫的過程如下

3094619-b9cd9f2fcfa4de7d.png

建立人臉庫

通過人臉檢測或者人臉跟蹤,獲取到人臉信息并識別人臉的過程如下:

3094619-62e99ee3f5edb57f.png

從人臉庫中識別

本次教程我們以目錄結構作為人臉的存檔方式,每張人臉對應一張人臉標識和一個人臉特征。人臉標識和特征使用同一個文件名稱來關聯,例如人臉a.jpg的特征用a.dat來表示。

好,我們開始我們的課程

集成人臉識別SDK庫

我們本次使用到的虹軟的SDK包中,提供了人臉識別的庫,它的名字叫face_recongnition.dll,我們找到它的SDK文檔。

來建立各個結構體和APIC#映射。

首先是結構體

從本節開始,我們不再講解原始SDK文檔中的數據結構和C#數組結構如何映射的,也不再講解P/Invoke的知識,如果需要了解相關知識,請參考我們上篇文檔的相關內容。

AFR_FSDK_FaceInput

   public struct AFR_FSDK_FaceInput

   {

       public  MRECT rcFace;

       public int lOrient;

   }

這個結構體是FD識別的輸出結構體,我們在上一章節標記人臉時使用了此結構體。

AFR_FSDK_FaceModel

 public  struct AFR_FSDK_FaceModel

   {

    public IntPtr pbFeature;

    public int lFeatureSize;

   }

這個結構體是人臉模型數據,也就是我們說的人臉特征。人臉識別就基于這個結構。

參數名

說明

pbFeature

提取到的臉部特征

lFeatureSize

特征信息長度

其中pbFeature是人臉數據,虹軟當前版本的人臉數據為一個20K大小的二進制數組,在使用時,我們把它保存為byte[]數組。

AFR_FSDK_Version

  public struct AFR_FSDK_Version

   {

     

       public int lCodebase;

       public int lMajor;

       public int lMinor;

       public int lBuild;

       public int lFeatureLevel;

       public string Version;

       public string BuildDate;

       public string CopyRight;

   }

定義識別方法類

我們將SDK中的對應方法提取到C#類中,和上面的章節保持一致,我們稱之為AFRFunction。

  public class AFRFunction

   {

      /**

       *Init Engine

       */

      [System.Runtime.InteropServices.DllImportAttribute("libarcsoft_fsdk_face_recognition.dll", EntryPoint = "AFR_FSDK_InitialEngine", CallingConvention = CallingConvention.Cdecl)]

      public static extern int AFR_FSDK_InitialEngine(string AppId, string SDKKey,  System.IntPtr pMem, int lMemSize, ref System.IntPtr phEngine);

       /**

       * 提取人臉特征值

       */

      [System.Runtime.InteropServices.DllImportAttribute("libarcsoft_fsdk_face_recognition.dll", EntryPoint = "AFR_FSDK_ExtractFRFeature", CallingConvention = CallingConvention.Cdecl)]

      public static extern int AFR_FSDK_ExtractFRFeature(System.IntPtr hEngine,  System.IntPtr pInputImage,  System.IntPtr pFaceRes, System.IntPtr pFaceModels);

       /*

       * 比較兩個人臉特征值之間的相似度

       **/

      [System.Runtime.InteropServices.DllImportAttribute("libarcsoft_fsdk_face_recognition.dll", EntryPoint = "AFR_FSDK_FacePairMatching", CallingConvention = CallingConvention.Cdecl)]

      public static extern int AFR_FSDK_FacePairMatching(System.IntPtr hEngine, ref System.IntPtr reffeature, ref System.IntPtr probefeature, ref float pfSimilScore);

       /**

       *銷毀引擎

       */

      [System.Runtime.InteropServices.DllImportAttribute("libarcsoft_fsdk_face_recognition.dll", EntryPoint = "AFR_FSDK_UninitialEngine", CallingConvention = CallingConvention.Cdecl)]

      public static extern int AFR_FSDK_UninitialEngine(System.IntPtr hEngine);

       /**

       *獲取人臉的版本號

       */

      [System.Runtime.InteropServices.DllImportAttribute("libarcsoft_fsdk_face_recognition.dll", EntryPoint = "AFR_FSDK_GetVersion", CallingConvention = CallingConvention.Cdecl)]

      public static extern System.IntPtr AFR_FSDK_GetVersion(System.IntPtr hEngine);

 

  }

開始之前的準備

定義人臉庫的位置

本次我們使用簡單的基于目錄存儲人臉庫

private String FaceLibraryPath = "G:\\Test\\";

定義人臉識別引擎的變量

IntPtr detectEngine = IntPtr.Zero;

//新增人臉識別引擎的定義

IntPtr recognizeEngine = IntPtr.Zero;

在構造函數中我們對人臉識別引擎進行初始化

int detectSize = 40 * 1024 * 1024;

int nScale = 50;

int nMaxFaceNum = 50;

IntPtr pMem = Marshal.AllocHGlobal(detectSize);

IntPtr pMemRecongnize = Marshal.AllocHGlobal(detectSize);

注意:detectSize為人臉識別的內存大小,一般來說,你可以根據你的應用程序的規模來設置一個適當的數值,數值過小會報內存不足的ERROR。

int retCode2 = AFR.AFRFunction.AFR_FSDK_InitialEngine(appId, sdkFRKey, pMemRecongnize, detectSize, ref recognizeEngine);

if (retCode2 != 0)

{

   MessageBox.Show("引擎FR初始化失敗:錯誤碼為:" + retCode2);

   this.Close();

}

這里需要注意FR Key,虹軟這次開源了111NSDK,不同的SDK,其對應的KEY是不一樣的。

提取人臉特征值

我們來提取人臉特征值。打開我們的checkAndMarkFace方法。

人臉特征值是一個二進制的byte數組,其內容對虹軟來說是屬于技術機密,里面保存了人臉的特征。這里的特征可以在人臉相似度比較時用到,人臉的特征包含了人臉的關鍵點信息??上У氖?,虹軟這方面并沒有開源。同樣的,人臉的相似度比較算法也沒有開源。不過不開源也有不開源的好處,至少我們用起來不用擔心這里面的細節。

首先,我們定義一個變量數組,用于保存圖片名稱的數組。這里我們簡單的對每個識別到的人臉,用GUID命名。

在我們上一節的,輸出識別到的人臉數據之前,我們增加一下我們的業務邏輯。找到下面的代碼

if (faceRes.nFace > 0)

我們在后面增加定義

//定義用到保存識別到的圖片的名稱的數組

List<string> faceImageName = new List<string>(faceRes.nFace);

for (int i = 0; i < faceRes.nFace; i++)

{

   faceImageName.Add(Guid.NewGuid().ToString());

}

在識別到的每個人臉以后,我們把識別到的人臉保存下來

Image image = CutFace(bitmap, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);

image.Save(FaceLibraryPath+faceImageName[i]+".jpg",ImageFormat.Jpeg);

如何進行人臉特征值的讀取

人臉特征值依賴于人臉識別的結果,其原理是利用識別到的人臉區域信息,在原圖中對人臉部分進行運算,輸出人臉的特征數據。
通過前面的定義,可以知道人臉特征提取函數的需要的參數信息如下

· recognizeEngine:人臉識別引擎

· offInputPtr:輸入的圖像信息,和FD的信息相同。同為ASVLOFFSCREEN結構體,我們可以直接使用上一步已經定義好的這個變量。

· faceInputPtr:人臉區域信息,包括人臉的角度信息,以及人臉的坐標范圍,對應的參數類型為MRECT,也就是在FD中識別到的人臉的區域坐標,

· 輸出參數為faceModel結構體。包括長度信息和人臉特征數組

我們來一步步解決。

定義faceInput結構體并指定它的引用互操作類型

AFR_FSDK_FaceInput faceinput = new AFR_FSDK_FaceInput();

faceinput.lOrient =(int)Marshal.PtrToStructure( faceRes.lfaceOrient,typeof(int));

 

MRECT rect = (MRECT)Marshal.PtrToStructure(faceRes.rcFace + Marshal.SizeOf(typeof(MRECT)) * i, typeof(MRECT));

faceinput.rcFace = rect;

IntPtr faceInputPtr = Marshal.AllocHGlobal(Marshal.SizeOf(faceinput));

               Marshal.StructureToPtr(faceinput, faceInputPtr, false);

定義faceModel變量用于保存識別到的特征值信息

AFR_FSDK_FaceModel faceModel = new AFR_FSDK_FaceModel();

IntPtr faceModelPtr = Marshal.AllocHGlobal(Marshal.SizeOf(faceModel));

調用FR引擎進行特征信息提取

  int ret = AFRFunction.AFR_FSDK_ExtractFRFeature(recognizeEngine, offInputPtr, faceInputPtr,

                       faceModelPtr);

如果ret=0,則提取成功,我們再調用Marshal的方法將對應的信息取出來

faceModel = (AFR_FSDK_FaceModel) Marshal.PtrToStructure(faceModelPtr, typeof (AFR_FSDK_FaceModel));

Marshal.FreeHGlobal(faceModelPtr);

byte[] featureContent = new byte[faceModel.lFeatureSize];

Marshal.Copy(faceModel.pbFeature, featureContent, 0, faceModel.lFeatureSize);

保存獲取到的結果,為了后面的匹配方便,和圖片命名保持一致

System.IO.File.WriteAllBytes(FaceLibraryPath+faceImageName[i]+".dat",featureContent);

通過圖像庫識別圖像中的特征

現在我們要做的是人臉識別功能呢,我們想要的功能是,打開一張照片,如果里面有人臉,那么我們就識別這個人臉是否已經在我們的人臉庫中出現過,如果已經出現,就顯示人臉的圖像編號。

依然打開項目,增加一個按鈕。識別人臉,并增加一個pictureBox用于保存匹配到的人臉的對應的人臉信息。雙擊剛才新加的按鈕進入事件處理代碼編輯窗口。

為了不增加重新提取特征臉的工作量,我們將上一步獲取到的特征臉重用。在上一步中,對識別到的人臉的第一個保存在了pictureBox中,并把相關的特征信息保存在對應命名的dat文件中。在保存時,使用

        this.pictureBox2.Tag = faceImageName[i];

保存圖像特征數據的文件名,因此在這里我們使用

string faceFeaturePath = pictureBox2.Tag as string;

獲取圖像文件名。

這里我們需要讀文件,讀取這個特征信息。

C# 讀取二進制文件和寫二進制文件都相當的方便,你可以使用C#的序列化操作把變量保為dat文件,然后使用反操作把文件重新讀取以初始化對象。這里使用的是簡單的二進制讀取的方法,當然你也可以嘗試序列化來完成這個操作。

byte[] sourceFeature = System.IO.File.ReadAllBytes(FaceLibraryPath + faceFeaturePath + ".dat");

接下來我們要使用人臉匹配的方法來進行匹配。這里使用的方法是AFR_FSDK_FacePairMatching
方法。再來看一下這個方法的定義

參數名稱

輸入輸出

說明

hEngine

[in]

引擎 handle

reffeature

[in]

已有臉部特征信息

probefeature

[in]

被比較的臉部特征信息

pfSimilScore

[out]

相似程度數值

我們先來定義被比較的臉部信息。這里原來的參數名稱有點拗口,我們使用localFaceModel來定義本地的

AFR_FSDK_FaceModel localFaceModels = new AFR_FSDK_FaceModel();

IntPtr sourceFeaturePtr = Marshal.AllocHGlobal(sourceFeature.Length);

Marshal.Copy(sourceFeature, 0, sourceFeaturePtr, sourceFeature.Length);

localFaceModels.lFeatureSize = sourceFeature.Length;

localFaceModels.pbFeature = sourceFeaturePtr;

由于使用了文件保存人臉特征信息,因此我們的人臉遍歷算法就變得很簡單了。我們這里使用1:1的方法。

我們直接使用存儲的人臉信息來進行搜索,方法自然是先遍歷讀取所有特征數據,提取特征值并進行比較

    foreach (var b in System.IO.Directory.GetFiles(FaceLibraryPath,"*.dat"))

               {

                   byte[] libaryFeature = System.IO.File.ReadAllBytes(b);

                   float result=0f;

                   //TODO:構造AFR_FSDK_FaceModel,調用API,獲取比較結果

                   if (result>0.7&&result<0.99)

                   {

                       //  MessageBox.Show(b);

                       Image image = Image.FromFile(b.Replace(".dat",".jpg"));

                       this.pictureBox3.Image = new Bitmap(image);

                       MessageBox.Show(result.ToString());

                       break;

                   }

               }

我們來完成TODO的部分
首先我們定義庫Model和本地Model的結構體指針
定義庫的指針

IntPtr libaryFeaturePtr = Marshal.AllocHGlobal(libaryFeature.Length);

Marshal.Copy(libaryFeature, 0, libaryFeaturePtr, libaryFeature.Length);

AFR_FSDK_FaceModel libraryFaceModel = new AFR_FSDK_FaceModel();

libraryFaceModel.lFeatureSize = libaryFeature.Length;

libraryFaceModel.pbFeature = libaryFeaturePtr;

IntPtr firstPtr = Marshal.AllocHGlobal(Marshal.SizeOf(localFaceModels));

Marshal.StructureToPtr(localFaceModels, firstPtr, false);

定義本地Model的指針

IntPtr firstPtr = Marshal.AllocHGlobal(Marshal.SizeOf(localFaceModels));

Marshal.StructureToPtr(localFaceModels, firstPtr, false);

調用方法輸出匹配結果

int ret = AFRFunction.AFR_FSDK_FacePairMatching(recognizeEngine, firstPtr, secondPtr, ref result);

從這里可以看出,人臉識別并沒有特別高深的地方,其基礎理論依然是特征值匹配搜索的理論,
雖然這里面的難點是特征值的提取和匹配算法,但因為虹軟已經免費給我們提供了對應的SDK,我們只需要調用相關的接口就可能了。如果要提高人臉匹配的速度,除了可以聯系虹軟尋找技術支持以外,也可以利用我們在其它算法方面的積累來嘗試解決方案。

后記

本次我們學習了人臉特征的提取和人臉特征的保存,實際上,在業務系統中,人臉通常是保存在數據庫中的,并且在匹配的時候,為了性能考慮,更多的是把特征保存在內存中,20K的特征值如果在2GB的業務系統中,可以很輕松的保存10W+的特征信息。人臉檢測和識別是CPU密集型和內存密集型的應用,保持良好的計算機配置有助于提高識別的性能,離線SDK的良好擴展性也為我們提高系統的性能提供了可行性。

這兩章節都是從靜態圖像出發的人臉檢測和人臉識別,從下一章節開始,我們將先從視頻的人臉識別講起。然后結合攝像頭的實時圖像采集,我們來講解一下簡單的人臉識別門禁系統的實現。請繼續關注。


會員登錄
登錄
其他帳號登錄:
我的資料
我的收藏
購物車
0
留言
回到頂部