close

以下為EMGU Multiple Face Recognition using PCA and Parallel Optimisation筆記

在此, 我會試著用 libemgucv-windows-universal-cuda-2.4.10.1940版本測試,

因為在上去3.0版本並不支援FaceRecognizer

==========================================================

如果你是第一次使用Emgu CV, 請參考Creating Your First EMGU Image Processing Project article

人臉辨識是以Multiple face detection and recognition in real time為基礎

PCA analysis為人臉辨識主要基礎理論,針對理論及其應用進行學習, 原始碼也進行優化並加強其方便和實用性

2.4.9版更新

CascadeClassifier類別: 用來取得人臉定位

FaceRecognizer:允許以Eigen, Fisher, LBPH(Local Binary Pattern Histogram)作為特徵, 並進行分類

對於陌生人(未加入訓練的人), FaceRecognizer有個bug, 後續會進行說明

看文章前先看一下人機介面

image

EMGU the FaceRecognizer如何作用?

image

上面應該是下圖的繼承關係, 其中Egien的門檻值(threshold)不同於Fisher和LBPH的門檻值

image

The Eigen Classifier

FaceRecognizer recognizer = new EigenFaceRecognizer(num_components, threshold);

num_components參數為主軸成分個數, 原作者說這個設定沒有一訂準則, 原則上建議為80個就夠用, 我建議是要看不同主軸個數選擇下, 每個人臉表示的訊號佔原始訊號的能量比例而定, 如果解釋率達99.9%應該部會有太大問題

threshold參數為人臉資料庫的門檻值(差異上限值, distance), 如果超過該門檻值都會被視為陌生人(unkown), 所以如果設定太低, 就變成bug, 全部都視為unkown…@@, 所以原作者將此bug進行處理, 將門檻值設定一個很大的初始值, 這樣所有訓練樣本都不會是unkown, 當辨識完成會傳回Eigen_Distance, 以便使用者決定unknown的門檻值

   1: FaceRecognizer recognizer = new EigenFaceRecognizer(80, double.PositiveInfinity);

以下為Reconise副程式, 輸入一張影像及門檻值

   1: public string Recognise(Image<Gray, Byte> Input_image, int Eigen_Thresh = -1)
   2: {
   3:     if (_IsTrained)
   4:     {
   5:         FaceRecognizer.PredictionResult ER = recognizer.Predict(Input_image);
   6:  
   7: .....
   8:         //Only use the post threshold rule if we are using an Eigen Recognizer 
   9:         //since Fisher and LBHP threshold set during the constructor will work correctly 
  10:         switch (Recognizer_Type)
  11:         {
  12:             case ("EMGU.CV.EigenFaceRecognizer"):
  13:                     if (Eigen_Distance > Eigen_threshold) return Eigen_label;
  14:                     else return "Unknown";
  15:             case ("EMGU.CV.LBPHFaceRecognizer"):
  16:             case ("EMGU.CV.FisherFaceRecognizer"):
  17:             default:
  18:                     return Eigen_label; //the threshold set in training controls unknowns
  19:         }
  20:     }
  21: ......
  22: }
The Fisher Classifier

FaceRecognizer recognizer = new FisherFaceRecognizer(num_components, threshold);

num_components為Linear Discriminant Analysis主軸個數, 原作者建議設定為0, 即保留全部主軸個數(即大於等於訓練樣本個數-1)

threshold為陌生人門檻值, 如果Eigen distance大於該門檻值, 則視為陌生人, 預設值為3500

The Local Binary Pattern Histogram (LBPH) Classifier

FaceRecognizer recognizer = new LBPHFaceRecognizer(radius, neighbors, grid_x, grid_y, threshold);

radius: 建立Circular Local Pattern半徑

neighbors: 鄰居點個數, 建議值為8, 即一般熟知的九宮格

grid_x和grid_y: 水平和垂直網格點數, 數值越大則特徵越細緻但維度也同時增加(計算量變大)

threshold: 為陌生人門檻值, 如果Eigen distance大於該門檻值, 則視為陌生人回傳-1, 和FisherFace相同

   1: FaceRecognizer recognizer = new LBPHFaceRecognizer(1, 8, 8, 8, 100);//50

===================================================================

以下為PCA處理步驟, 建議參考Matthre & Alex的Face Recognition Using Eigenfaces[5]

  • Stage 1: Subtract the Mean of the data from each variable (our adjusted data)
  • 取臉的列(row)方向, 將全部人臉同一列的數值進行平均, 譬如第一列, 將100張人臉的同一列像數值取平均,如果是取行方向也可以, 記得後續covariance matrix計算記得調整

    假設每個人臉2維影像資料轉成1維, 則一張人臉100×100影像大小, 則可以變成10,000x1的行向量, 再針對100張人臉構成矩陣X(10,000x100),sum(X, 2)/100, 得到平均影像其維度為10,000x1

  • Stage 2: Calculate and form a covariance Matrix
  • 觀察每張影像資料和平均影像的變化程度

    Face_Recognition/EQ1.jpg

  • Stage 3: Calculate Eigenvectors and Eigenvalues from the covariance Matrix
  • Stage 4: Chose a Feature Vector (a fancy name for a matrix of vectors)
  • Stage 5: Multiply the transposed Feature Vectors by the transposed adjusted data

====================================================================

Classifier_Train.cs

架構如下

   1: Vaiables
   2:  
   3: Constructors
   4:  
   5: Public
   6:  
   7: Private

先看Constructors段落, 如下

一個預設建構子Classifier_Train(), 另一個傳入要訓練的目錄夾路徑

image

預設路徑: LoadTrainingData(應用程式執行目錄\\TrainedFaces)

   1: public Classifier_Train()
   2: {
   3:     _IsTrained = LoadTrainingData(Application.StartupPath + "\\TrainedFaces");
   4: }

user指定目錄Training_Folder

   1: public Classifier_Train(string Training_Folder)
   2: {
   3:     _IsTrained = LoadTrainingData(Training_Folder);
   4: }

接下來看一下Varaibles段落

FaceRecognizer類別只在2.4.x版支援@@

   1: //Eigen
   2: //EigenObjectRecognizer recognizer;
   3:  FaceRecognizer recognizer;
   4:  
   5: //training variables
   6:  List<Image<Gray, byte>> trainingImages = new List<Image<Gray, byte>>();//Images
   7:  //TODO: see if this can be combined in Ditionary format this will remove support for old data
   8:  List<string> Names_List = new List<string>(); //labels
   9:  List<int> Names_List_ID = new List<int>();
  10:  int ContTrain, NumLabels;
  11:  float Eigen_Distance = 0;
  12:  string Eigen_label;
  13:  int Eigen_threshold = 2000;
  14:  
  15: //Class Variables
  16: string Error;
  17:  bool _IsTrained = false;
  18:  
  19: public string Recognizer_Type = "EMGU.CV.EigenFaceRecognizer";


List<Image<Gray, byte>> trainingImages 主要將訓練影像堆疊在此變數中

List<string> Names_List 堆疊標籤名稱, 即標記人臉名稱

List<int> Names_List_ID 推疊名稱ID

接下來看 Public段落

   1: /// <summary>
   2: /// Retrains the recognizer witout resetting variables like recognizer type.
   3: /// </summary>
   4: /// <returns></returns>
   5: public bool Retrain()
   6: {
   7:     return _IsTrained = LoadTrainingData(Application.StartupPath + "\\TrainedFaces");
   8: }
   9: /// <summary>
  10: /// Retrains the recognizer witout resetting variables like recognizer type.
  11: /// Takes String input to a different location for training data.
  12: /// </summary>
  13: /// <returns></returns>
  14: public bool Retrain(string Training_Folder)
  15: {
  16:     return _IsTrained = LoadTrainingData(Training_Folder);
  17: }

辨識核心

   1: /// <summary>
   2: /// Recognise a Grayscale Image using the trained Eigen Recogniser
   3: /// </summary>
   4: /// <param name="Input_image"></param>
   5: /// <returns></returns>
   6: public string Recognise(Image<Gray, byte> Input_image, int Eigen_Thresh = -1)
   7: {
   8:     if (_IsTrained)
   9:     {
  10:         FaceRecognizer.PredictionResult ER = recognizer.Predict(Input_image);
  11:  
  12:         if (ER.Label == -1)
  13:         {
  14:             Eigen_label = "Unknown";
  15:             Eigen_Distance = 0;
  16:             return Eigen_label;
  17:         }
  18:         else
  19:         {
  20:             Eigen_label = Names_List[ER.Label];
  21:             Eigen_Distance = (float)ER.Distance;
  22:             if (Eigen_Thresh > -1) Eigen_threshold = Eigen_Thresh;
  23:  
  24:             //Only use the post threshold rule if we are using an Eigen Recognizer 
  25:             //since Fisher and LBHP threshold set during the constructor will work correctly 
  26:             switch (Recognizer_Type)
  27:             {
  28:                 case ("EMGU.CV.EigenFaceRecognizer"):
  29:                         if (Eigen_Distance > Eigen_threshold) return Eigen_label;
  30:                         else return "Unknown";
  31:                 case ("EMGU.CV.LBPHFaceRecognizer"):
  32:                 case ("EMGU.CV.FisherFaceRecognizer"):
  33:                 default:
  34:                         return Eigen_label; //the threshold set in training controls unknowns
  35:             }
  36:  
  37:            
  38:             
  39:            
  40:         }
  41:  
  42:     }
  43:     else return "";
  44: }

設定Eigen門檻值

   1: /// <summary>
   2:  /// Sets the threshold confidence value for string Recognise(Image<Gray, byte> Input_image) to be used.
   3:  /// </summary>
   4:  public int Set_Eigen_Threshold
   5:  {
   6:      set
   7:      {
   8:          //NOTE: This is still not working correctley 
   9:          //recognizer.EigenDistanceThreshold = value;
  10:          Eigen_threshold = value;
  11:      }
  12:  }

取得辨識結果對應的標籤(名稱)

   1:  
   2:    /// <summary>
   3:    /// Returns a string containg the recognised persons name
   4:    /// </summary>
   5:    public string Get_Eigen_Label
   6:    {
   7:        get
   8:        {
   9:            return Eigen_label;
  10:        }
  11:    }

取得Eigen Distance(差異度)

   1: /// <summary>
   2: /// Returns a float confidence value for potential false clasifications
   3: /// </summary>
   4: public float Get_Eigen_Distance
   5: {
   6:     get
   7:     {
   8:         //get eigenDistance
   9:         return Eigen_Distance;
  10:     }
  11: }

取得錯誤訊息

   1: /// <summary>
   2: /// Returns a string contatining any error that has occured
   3: /// </summary>
   4: public string Get_Error
   5: {
   6:     get { return Error; }
   7: }

儲存recognizer至某個參數檔案(xml)

   1: /// <summary>
   2: /// Saves the trained Eigen Recogniser to specified location
   3: /// </summary>
   4: /// <param name="filename"></param>
   5: public void Save_Eigen_Recogniser(string filename)
   6: {
   7:     recognizer.Save(filename);
   8:  
   9:     //save label data as this isn't saved with the network
  10:     string direct = Path.GetDirectoryName(filename);
  11:     FileStream Label_Data = File.OpenWrite(direct + "/Labels.xml");
  12:     using (XmlWriter writer = XmlWriter.Create(Label_Data))
  13:     {
  14:         writer.WriteStartDocument();
  15:         writer.WriteStartElement("Labels_For_Recognizer_sequential");
  16:         for (int i = 0; i < Names_List.Count; i++)
  17:         {
  18:             writer.WriteStartElement("LABEL");
  19:             writer.WriteElementString("POS", i.ToString());
  20:             writer.WriteElementString("NAME", Names_List[i]);
  21:             writer.WriteEndElement();
  22:         }
  23:  
  24:         writer.WriteEndElement();
  25:         writer.WriteEndDocument();
  26:     }
  27:     Label_Data.Close();
  28: }

載入參數檔案至recognizer

   1: /// <summary>
   2:     /// Loads the trained Eigen Recogniser from specified location
   3:     /// </summary>
   4:     /// <param name="filename"></param>
   5:     public void Load_Eigen_Recogniser(string filename)
   6:     {
   7:         //Lets get the recogniser type from the file extension
   8:         string ext = Path.GetExtension(filename);
   9:         switch (ext)
  10:         {
  11:             case (".LBPH"):
  12:                 Recognizer_Type = "EMGU.CV.LBPHFaceRecognizer";
  13:                 recognizer = new LBPHFaceRecognizer(1, 8, 8, 8, 100);//50
  14:                 break;
  15:             case (".FFR"):
  16:                 Recognizer_Type = "EMGU.CV.FisherFaceRecognizer";
  17:                 recognizer = new FisherFaceRecognizer(0, 3500);//4000
  18:                 break;
  19:             case (".EFR"):
  20:                 Recognizer_Type = "EMGU.CV.EigenFaceRecognizer";
  21:                 recognizer = new EigenFaceRecognizer(80, double.PositiveInfinity);
  22:                 break;
  23:         }
  24:  
  25:         //introduce error checking
  26:         recognizer.Load(filename);
  27:  
  28:         //Now load the labels
  29:         string direct = Path.GetDirectoryName(filename);
  30:         Names_List.Clear();
  31:         if (File.Exists(direct + "/Labels.xml"))
  32:         {
  33:             FileStream filestream = File.OpenRead(direct + "/Labels.xml");
  34:             long filelength = filestream.Length;
  35:             byte[] xmlBytes = new byte[filelength];
  36:             filestream.Read(xmlBytes, 0, (int)filelength);
  37:             filestream.Close();
  38:  
  39:             MemoryStream xmlStream = new MemoryStream(xmlBytes);
  40:  
  41:             using (XmlReader xmlreader = XmlTextReader.Create(xmlStream))
  42:             {
  43:                 while (xmlreader.Read())
  44:                 {
  45:                     if (xmlreader.IsStartElement())
  46:                     {
  47:                         switch (xmlreader.Name)
  48:                         {
  49:                             case "NAME":
  50:                                 if (xmlreader.Read())
  51:                                 {
  52:                                     Names_List.Add(xmlreader.Value.Trim());
  53:                                 }
  54:                                 break;
  55:                         }
  56:                     }
  57:                 }
  58:             }
  59:             ContTrain = NumLabels;
  60:         }
  61:         _IsTrained = true;
  62:  
  63:     }

資源釋放

   1: /// Dispose of Class call Garbage Collector
   2: /// </summary>
   3: public void Dispose()
   4: {
   5:     recognizer = null;
   6:     trainingImages = null;
   7:     Names_List = null;
   8:     Error = null;
   9:     GC.Collect();
  10: }

Private段落

LoadTrainingData載入全部訓練照片及對應名稱(標籤)

   1: /// <summary>
   2:     /// Loads the traing data given a (string) folder location
   3:     /// </summary>
   4:     /// <param name="Folder_location"></param>
   5:     /// <returns></returns>
   6:     private bool LoadTrainingData(string Folder_location)
   7:     {
   8:         if (File.Exists(Folder_location +"\\TrainedLabels.xml"))
   9:         {
  10:             try
  11:             {
  12:                 //message_bar.Text = "";
  13:                 Names_List.Clear();
  14:                 Names_List_ID.Clear();
  15:                 trainingImages.Clear();
  16:                 FileStream filestream = File.OpenRead(Folder_location + "\\TrainedLabels.xml");
  17:                 long filelength = filestream.Length;
  18:                 byte[] xmlBytes = new byte[filelength];
  19:                 filestream.Read(xmlBytes, 0, (int)filelength);
  20:                 filestream.Close();
  21:  
  22:                 MemoryStream xmlStream = new MemoryStream(xmlBytes);
  23:  
  24:                 using (XmlReader xmlreader = XmlTextReader.Create(xmlStream))
  25:                 {
  26:                     while (xmlreader.Read())
  27:                     {
  28:                         if (xmlreader.IsStartElement())
  29:                         {
  30:                             switch (xmlreader.Name)
  31:                             {
  32:                                 case "NAME":
  33:                                     if (xmlreader.Read())
  34:                                     {
  35:                                         Names_List_ID.Add(Names_List.Count); //0, 1, 2, 3....
  36:                                         Names_List.Add(xmlreader.Value.Trim());
  37:                                         NumLabels += 1;
  38:                                     }
  39:                                     break;
  40:                                 case "FILE":
  41:                                     if (xmlreader.Read())
  42:                                     {
  43:                                         //PROBLEM HERE IF TRAININGG MOVED
  44:                                         trainingImages.Add(new Image<Gray, byte>(Application.StartupPath + "\\TrainedFaces\\" + xmlreader.Value.Trim()));
  45:                                     }
  46:                                     break;
  47:                             }
  48:                         }
  49:                     }
  50:                 }
  51:                 ContTrain = NumLabels;
  52:  
  53:                 if (trainingImages.ToArray().Length != 0)
  54:                 {
  55:  
  56:                     //Eigen face recognizer
  57:                     //Parameters:    
  58:                     //      num_components – The number of components (read: Eigenfaces) kept for this Prinicpal 
  59:                     //          Component Analysis. As a hint: There’s no rule how many components (read: Eigenfaces) 
  60:                     //          should be kept for good reconstruction capabilities. It is based on your input data, 
  61:                     //          so experiment with the number. Keeping 80 components should almost always be sufficient.
  62:                     //
  63:                     //      threshold – The threshold applied in the prediciton. This still has issues as it work inversly to LBH and Fisher Methods.
  64:                     //          if you use 0.0 recognizer.Predict will always return -1 or unknown if you use 5000 for example unknow won't be reconised.
  65:                     //          As in previous versions I ignore the built in threhold methods and allow a match to be found i.e. double.PositiveInfinity
  66:                     //          and then use the eigen distance threshold that is return to elliminate unknowns. 
  67:                     //
  68:                     //NOTE: The following causes the confusion, sinc two rules are used. 
  69:                     //--------------------------------------------------------------------------------------------------------------------------------------
  70:                     //Eigen Uses
  71:                     //          0 - X = unknown
  72:                     //          > X = Recognised
  73:                     //
  74:                     //Fisher and LBPH Use
  75:                     //          0 - X = Recognised
  76:                     //          > X = Unknown
  77:                     //
  78:                     // Where X = Threshold value
  79:  
  80:  
  81:                     switch (Recognizer_Type)
  82:                     {
  83:                         case ("EMGU.CV.LBPHFaceRecognizer"):
  84:                             recognizer = new LBPHFaceRecognizer(1, 8, 8, 8, 100);//50
  85:                             break;
  86:                         case ("EMGU.CV.FisherFaceRecognizer"):
  87:                             recognizer = new FisherFaceRecognizer(0, 3500);//4000
  88:                             break;
  89:                         case("EMGU.CV.EigenFaceRecognizer"):
  90:                         default:
  91:                             recognizer = new EigenFaceRecognizer(80, double.PositiveInfinity);
  92:                             break;
  93:                     }
  94:  
  95:                     recognizer.Train(trainingImages.ToArray(), Names_List_ID.ToArray());
  96:                     // Recognizer_Type = recognizer.GetType();
  97:                     // string v = recognizer.ToString(); //EMGU.CV.FisherFaceRecognizer || EMGU.CV.EigenFaceRecognizer || EMGU.CV.LBPHFaceRecognizer
  98:  
  99:                     return true;
 100:                 }
 101:                 else return false;
 102:             }
 103:             catch (Exception ex)
 104:             {
 105:                 Error = ex.ToString();
 106:                 return false;
 107:             }
 108:         }
 109:         else return false;
 110:     }

=======================================================

image

UI外觀如下

image

加入參考Emgu.CV Emgu.CV.UI Emgu.Util

C:\Emgu\libemgucv-windows-universal-cuda-2.4.10.1940\bin\  支援FaceRecognizer

C:\Emgu\emgucv-windows-universal-cuda 3.0.0.2131\bin\       支援FaceRecognizer

image

   1: using Emgu.CV.UI;
   2: using Emgu.CV;
   3: using Emgu.CV.Structure;
   4: using Emgu.CV.CvEnum;
   5:  
   6: using System.IO;
   7: using System.Xml;
   8: using System.Runtime.InteropServices;
   9: using System.Threading;
  10: using System.Security.Principal;
  11: using System.Threading.Tasks;
  12: using Microsoft.Win32.SafeHandles;

在專案中新增一個資料夾Cascade

複製C:\Emgu\emgucv-windows-universal-cuda 3.0.0.2131\bin\haarcascade_frontalface_default.xml

到Cascade目錄下

image

專案新增x64目錄並加入C:\Emgu\emgucv-windows-universal-cuda 2.4.10.1940\bin\x64目錄下所有dll檔案

image

加入Classifier_Train.cs至專案

image

Variable段落

   1: #region variables
   2:       Image<Bgr, Byte> currentFrame; //current image aquired from webcam for display
   3:       Image<Gray, byte> result, TrainedFace = null; //used to store the result image and trained face
   4:       Image<Gray, byte> gray_frame = null; //grayscale current image aquired from webcam for processing
   5:  
   6:       Capture grabber; //This is our capture variable
   7:  
   8:       public CascadeClassifier Face = new CascadeClassifier(Application.StartupPath + "/Cascades/haarcascade_frontalface_default.xml");//Our face detection method 
   9:  
  10:       MCvFont font = new MCvFont(FONT.CV_FONT_HERSHEY_COMPLEX, 0.5, 0.5); //Our fount for writing within the frame
  11:  
  12:       int NumLabels;
  13:       
  14:       //Classifier with default training location
  15:       Classifier_Train Eigen_Recog = new Classifier_Train();
  16:  
  17:       #endregion

定義兩個攝影機影像擷取事件: FrameGrabber_Standard和FrameGrabber_Parrellel

   1: void FrameGrabber_Standard(object sender, EventArgs e)
   2: {
   3: }
   4: void FrameGrabber_Parrellel(object sender, EventArgs e)
   5: {
   6: }

先來看FrameGrabber_Standard

人臉框選會往內縮一定比例, 以去除背景雜訊(非人臉)

   1: //Camera Start Stop
   2:        void FrameGrabber_Standard(object sender, EventArgs e)
   3:        {
   4:            //Get the current frame form capture device
   5:            currentFrame = grabber.QueryFrame().Resize(320, 240, Emgu.CV.CvEnum.INTER.CV_INTER_CUBIC);
   6:            double scaleFactor = 1.2;
   7:            int minNeighbors = 10;
   8:            Size minSize = new Size(30,30);
   9:            Size maxSize = new Size(130, 130);
  10:            double scale_x = 0.15, scale_y = 0.12, scale_h = 0.15, scale_w = 0.2;  // 人臉矩形縮放比例
  11:            int normal_wid = 100;                                                  // 正規化寬度
  12:            int normal_hei = 100;
  13:            if (currentFrame != null)
  14:            {
  15:                gray_frame = currentFrame.Convert<Gray, byte>();
  16:                
  17:                //Face Detector
  18:                //Rectangle[] facesDetected = Face.DetectMultiScale(gray_frame, 1.2, 10, new Size(50, 50), Size.Empty);
  19:                Rectangle[] facesDetected = Face.DetectMultiScale(gray_frame, scaleFactor, minNeighbors, minSize, maxSize);
  20:                //Action for each element detected
  21:                for (int i = 0; i < facesDetected.Length; i++)// (Rectangle face_found in facesDetected)
  22:                {
  23:                    //This will focus in on the face from the haar results its not perfect but it will remove a majoriy
  24:                    //of the background noise
  25:                    facesDetected[i].X += (int)(facesDetected[i].Height * scale_x);
  26:                    facesDetected[i].Y += (int)(facesDetected[i].Width * scale_y);
  27:                    facesDetected[i].Height -= (int)(facesDetected[i].Height * scale_h);
  28:                    facesDetected[i].Width -= (int)(facesDetected[i].Width * scale_w);
  29:  
  30:                    result = currentFrame.Copy(facesDetected[i]).Convert<Gray, byte>().Resize(normal_wid, normal_hei, Emgu.CV.CvEnum.INTER.CV_INTER_CUBIC);
  31:                    result._EqualizeHist();
  32:                    //draw the face detected in the 0th (gray) channel with blue color
  33:                    currentFrame.Draw(facesDetected[i], new Bgr(Color.Blue), 2);
  34:                    currentFrame.Draw((i+1).ToString() + " ", ref font2, new Point(facesDetected[i].X + 10, facesDetected[i].Y - 2), new Bgr(Color.Yellow));
  35:                    if (Eigen_Recog.IsTrained)
  36:                    {
  37:                        string name = Eigen_Recog.Recognise(result);
  38:                        int match_value = (int)Eigen_Recog.Get_Eigen_Distance;
  39:  
  40:                        //Draw the label for each face detected and recognized
  41:                        currentFrame.Draw(name + " ", ref font, new Point(facesDetected[i].X - 2, facesDetected[i].Y - 2), new Bgr(Color.LightGreen));
  42:                        ADD_Face_Found(result, name, match_value);
  43:                    }
  44:                }
  45:                //Show the faces procesed and recognized
  46:                image_PICBX.Image = currentFrame.ToBitmap();
  47:            }
  48:  
  49:        }


參考資料:

  1. 2.Multiple face detection and recognition in real time

  2. 3.EMGU Multiple Face Recognition using PCA and Parallel Optimisation

  3. 4.Fisherfaces

  4. 5.Face Recognition Using Eigenfaces

  5. 6.https://en.wikipedia.org/wiki/Eigenface

  6. 7.EmguCV with C# - Tutorial List

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 me1237guy 的頭像
    me1237guy

    天天向上

    me1237guy 發表在 痞客邦 留言(2) 人氣()