using Emgu.CV; using Emgu.CV.Dnn; using Emgu.CV.Face; using Emgu.CV.Structure; using System.Drawing; using System.Text; using ToolsServices; using static System.Net.Mime.MediaTypeNames; namespace FaceRecognition { public class FaceRecognitionService { #region Variables privées statiques private static string NomFichierData = FichiersInternesService.ParamsFaceRecognition; #endregion #region Variables privées private readonly string _PathDataTrainSet = @"D:\_TrainingData\FaceRecognition\TrainSet"; private readonly string _PathModel = @"Models"; private readonly string _NomModelDetect = @"face_detection_yunet_2023mar.onnx"; private readonly string _NomModel = @"model_reconnaissance.yml"; private readonly string _FaceHaarXML = @"haarcascade_frontalface_alt.xml"; private readonly int _ReSizeFaceImg = 300; private readonly int _ReSizeImage = 400; private readonly float _ScaleFactor = 0.9f; private readonly System.Drawing.Size _MinSizeDetect = new(30, 30); private readonly int _DistanceMini = 40; private readonly MCvScalar _MCvScalarColorGreen = new(0, 255, 0); private readonly MCvScalar _MCvScalarColorRed = new(0, 0, 255); private readonly int _SizeBorder = 2; private readonly LBPHFaceRecognizer _FaceRecognizer = new(); private readonly FaceDetectorYN _FaceNet; private List _LstLabels = new(); private List _LstLabelsString = new(); private readonly System.Drawing.Size _InputSize = new(300, 300); #endregion #region Constructeur public FaceRecognitionService(bool isCuda = false) { var parametres = LoadParametres(); _PathDataTrainSet = parametres.Path_TrainSet; // Créer un objet CascadeClassifier pour détecter les visages if (isCuda) { _FaceNet = new FaceDetectorYN(TrtFindFileFaceModelDetect(), "", _InputSize, backendId: Emgu.CV.Dnn.Backend.Cuda, targetId: Target.Cuda); } else { _FaceNet = new FaceDetectorYN(TrtFindFileFaceModelDetect(), "", _InputSize, backendId: Emgu.CV.Dnn.Backend.Default, targetId: Target.Cpu); } } #endregion #region Gestion des paramètres TrainSet et TestSet public static ParametresFaceRecognitionService LoadParametres() { LoggerService.LogInfo("ParametresFaceRecognitionService.LoadParametres"); ParametresFaceRecognitionService SelectedItem = new(); try { if (File.Exists(NomFichierData)) { string[] lignes = File.ReadAllLines(NomFichierData); if (lignes.Length > 0) SelectedItem.Path_TrainSet = lignes[0]; if (lignes.Length > 1) SelectedItem.Path_TestSet = lignes[1]; } return SelectedItem; } catch { return new(); } } public static bool SaveParametres(ParametresFaceRecognitionService selectedItem) { LoggerService.LogInfo("ParametresFaceRecognitionService.SaveParametres"); try { StringBuilder sb = new(); sb.AppendLine(selectedItem.Path_TrainSet); sb.AppendLine(selectedItem.Path_TestSet); File.WriteAllText(NomFichierData, sb.ToString()); return true; } catch { return false; } } #endregion #region Méthodes publiques public bool CapturePhoto(string filename) { try { var inputImage = new Mat(filename); var inputClone = inputImage.Clone(); var lstRetour = new List(); if (!File.Exists(filename)) { throw new Exception($"Fichier manquant : {filename}."); } _FaceRecognizer.Read(TrtFindFileModel()); // Détecter les visages dans l'image var w = (int)(_ReSizeImage * 3 * _ScaleFactor); var h = (int)(_ReSizeImage * 3 * inputImage.Height / inputImage.Width * _ScaleFactor); CvInvoke.Resize(inputImage, inputImage, new System.Drawing.Size(w, h)); var faces = DetectMultiFaces(inputImage); if (faces.Length == 0) { inputImage = inputClone.Clone(); w = (int)(_ReSizeImage * 2 * _ScaleFactor); h = (int)(_ReSizeImage * 2 * inputImage.Height / inputImage.Width * _ScaleFactor); CvInvoke.Resize(inputImage, inputImage, new System.Drawing.Size(w, h)); faces = DetectMultiFaces(inputImage); } if (faces.Length == 0) { inputImage = inputClone.Clone(); w = (int)(_ReSizeImage * _ScaleFactor); h = (int)(_ReSizeImage * inputImage.Height / inputImage.Width * _ScaleFactor); CvInvoke.Resize(inputImage, inputImage, new System.Drawing.Size(w, h)); faces = DetectMultiFaces(inputImage); } // Parcourir chaque visage détecté foreach (var face in faces) { // Extraire la région du visage de l'image var faceImage = new Mat(inputImage, face); if (System.IO.File.Exists(filename)) System.IO.File.Delete(filename); CvInvoke.Imwrite(filename, faceImage); } return true; } catch { return false; } } public (bool, string) Training(bool isPhotoVisible) { return TrtTraining(isPhotoVisible); } public List Predict(string filename) { (var lst, _) = TrtPredict(new(filename)); return lst; } public List Predict(Mat inputImage) { (var lst, _) = TrtPredict(inputImage); return lst; } public List PredictLiveNoShow() { List lst = new(); var videoCapture = new Emgu.CV.VideoCapture(0); try { int i = 0; while (true) { try { i++; Mat mat = new(); videoCapture.Read(mat); // Préparation de l'affichage de l'image (lst, _) = TrtPredict(mat, false, true); if (i > 4) break; } catch (Exception ex) { Console.WriteLine($"Erreur : {ex.Message}"); } } } catch { } return lst; } public void PredictLive() { var videoCapture = new Emgu.CV.VideoCapture(0); try { while (true) { try { Mat mat = new(); Mat matOutput; videoCapture.Read(mat); // Préparation de l'affichage de l'image (_, matOutput) = TrtPredict(mat, false, true); CvInvoke.Imshow("frame", matOutput); // affichage et saisie d'un code clavier (Q ou ECHAP) if (CvInvoke.WaitKey(1) == (int)ConsoleKey.Q || CvInvoke.WaitKey(1) == (int)ConsoleKey.Escape) break; } catch (Exception ex) { Console.WriteLine($"Erreur : {ex.Message}"); } } } catch { } finally { // Ne pas oublier de fermer le flux et la fenetre CvInvoke.WaitKey(0); CvInvoke.DestroyAllWindows(); } } #endregion #region Méthodes privées private (bool, string) TrtTraining(bool isPhotoVisible) { bool isReturnOK = true; string msgReturn = ""; try { // Créer des listes pour stocker les images de formation et les étiquettes correspondantes var trainingImages = TrtFindImagesAndLabels(true, isPhotoVisible); // Entraîner le reconnaiseur avec les images de formation et les étiquettes _FaceRecognizer.Train(trainingImages.ToArray(), _LstLabels.ToArray()); for (int i = 0; i < _LstLabels.Count; i++) { _FaceRecognizer.SetLabelInfo(_LstLabels[i], _LstLabelsString[i]); } // Enregistrer le modèle entraîné _FaceRecognizer.Write(Path.Combine(_PathModel, _NomModel)); } catch (Exception ex) { isReturnOK = false; msgReturn = ex.Message; } return (isReturnOK, msgReturn); } private (List, Mat) TrtPredict(Mat inputImage, bool isAffichage = true, bool isOnlyFirstName=true) { var inputClone = inputImage.Clone(); var lstRetour = new List(); string filename = TrtFindFileModel(); if (!File.Exists(filename)) { throw new Exception($"Fichier manquant : {filename}."); } _FaceRecognizer.Read(TrtFindFileModel()); // Convertir l'image en niveaux de gris pour faciliter la détection des visages var grayImage = new Mat(); // Détecter les visages dans l'image var w = (int)(_ReSizeImage * 3 * _ScaleFactor); var h = (int)(_ReSizeImage * 3 * inputImage.Height / inputImage.Width * _ScaleFactor); CvInvoke.Resize(inputImage, inputImage, new System.Drawing.Size(w, h)); var faces = DetectMultiFaces(inputImage); if (faces.Length == 0) { inputImage = inputClone.Clone(); w = (int)(_ReSizeImage * 2 * _ScaleFactor); h = (int)(_ReSizeImage * 2 * inputImage.Height / inputImage.Width * _ScaleFactor); CvInvoke.Resize(inputImage, inputImage, new System.Drawing.Size(w, h)); faces = DetectMultiFaces(inputImage); } if (faces.Length == 0) { inputImage = inputClone.Clone(); w = (int)(_ReSizeImage * _ScaleFactor); h = (int)(_ReSizeImage * inputImage.Height / inputImage.Width * _ScaleFactor); CvInvoke.Resize(inputImage, inputImage, new System.Drawing.Size(w, h)); faces = DetectMultiFaces(inputImage); } CvInvoke.CvtColor(inputImage, grayImage, Emgu.CV.CvEnum.ColorConversion.Bgr2Gray); // Parcourir chaque visage détecté foreach (var face in faces) { // Extraire la région du visage de l'image en niveaux de gris var faceImage = new Mat(grayImage, face); CvInvoke.Resize(faceImage, faceImage, new System.Drawing.Size(_ReSizeFaceImg, _ReSizeFaceImg)); // Reconnaître le visage à partir de l'image redimensionnée var predict = _FaceRecognizer.Predict(faceImage); if (predict.Distance < _DistanceMini) { var nameComplet = _FaceRecognizer.GetLabelInfo(predict.Label); var name = ""; if (isOnlyFirstName) { name = nameComplet.Split('_')[0]; } else { name = nameComplet.Replace("_", " "); } lstRetour.Add(name); CvInvoke.Rectangle(inputImage, face, _MCvScalarColorGreen, _SizeBorder); //CvInvoke.PutText(inputImage, name + " - " + predict.Distance.ToString("0.0"), new System.Drawing.Point(face.X, face.Y), Emgu.CV.CvEnum.FontFace.HersheySimplex, 1.0, _MCvScalarColor, _SizeBorder); CvInvoke.PutText(inputImage, name, new System.Drawing.Point(face.X, face.Y), Emgu.CV.CvEnum.FontFace.HersheySimplex, 1.0, _MCvScalarColorGreen, _SizeBorder); CvInvoke.PutText(inputImage, predict.Distance.ToString("0.0"), new System.Drawing.Point(face.X, face.Y + 30), Emgu.CV.CvEnum.FontFace.HersheySimplex, 0.75, _MCvScalarColorGreen, _SizeBorder); } else { //var name = "???"; //lstRetour.Add(name); CvInvoke.Rectangle(inputImage, face, _MCvScalarColorRed, _SizeBorder); CvInvoke.PutText(inputImage, predict.Distance.ToString("0.0"), new System.Drawing.Point(face.X, face.Y), Emgu.CV.CvEnum.FontFace.HersheySimplex, 1.0, _MCvScalarColorRed, _SizeBorder); } } if (isAffichage) { CvInvoke.Imshow("reconnaissance", inputImage); CvInvoke.WaitKey(0); CvInvoke.DestroyAllWindows(); } return (lstRetour, inputImage); } private List TrtFindImagesAndLabels(bool isTraining, bool isPhotoVisible) { // Créer des listes pour stocker les images de formation et les étiquettes correspondantes var lstTrainingImages = new List(); if(System.IO.Directory.Exists(_PathDataTrainSet) == false) { throw new Exception($"Le dossier n'existe pas : {_PathDataTrainSet}"); } try { _LstLabels = new(); _LstLabelsString = new(); var dirs = Directory.GetDirectories(_PathDataTrainSet); int numDir = 1; foreach (var dir in dirs) { var files = Directory.GetFiles(dir); foreach (var file in files) { var dernierDir = dir.Split(@"\"); var nomDernierDir = dernierDir[^1]; if (isTraining) { var image = new Mat(file); var grayImage = new Mat(); //var w = (int)(image.Width * _ScaleFactor); //var h = (int)(image.Height * _ScaleFactor); var w = (int)(_ReSizeImage * _ScaleFactor); var h = (int)(_ReSizeImage * image.Height / image.Width * _ScaleFactor); CvInvoke.Resize(image, image, new System.Drawing.Size(w, h)); var faces = DetectMultiFaces(image); CvInvoke.CvtColor(image, grayImage, Emgu.CV.CvEnum.ColorConversion.Bgr2Gray); if (faces.Length > 0) { var face = new Mat(grayImage, faces[0]); CvInvoke.Resize(face, face, new System.Drawing.Size(_ReSizeFaceImg, _ReSizeFaceImg)); lstTrainingImages.Add(face); _LstLabels.Add(numDir); _LstLabelsString.Add(nomDernierDir); if (isPhotoVisible) { CvInvoke.PutText(image, nomDernierDir, new System.Drawing.Point(faces[0].X, faces[0].Y), Emgu.CV.CvEnum.FontFace.HersheySimplex, 1.0, _MCvScalarColorGreen, _SizeBorder); CvInvoke.Rectangle(image, faces[0], _MCvScalarColorGreen, _SizeBorder); } } else { Console.WriteLine("Visage non détecté"); } if (isPhotoVisible) { CvInvoke.Imshow("reconnaissance", image); CvInvoke.WaitKey(0); CvInvoke.DestroyAllWindows(); } } else { _LstLabels.Add(numDir); _LstLabelsString.Add(nomDernierDir); } } numDir++; } } catch (Exception ex) { throw new Exception(ex.Message); } return lstTrainingImages; } public Rectangle[] DetectMultiFaces(Mat img) { var lstRect = new List(); _FaceNet.InputSize = new System.Drawing.Size(img.Width, img.Height); var outputFaces = new Mat(); _FaceNet.Detect(img, outputFaces); var detectionArray = outputFaces.GetData(); if (detectionArray is null) { return new Rectangle[0]; } var max = detectionArray.GetLength(0); Parallel.For(0, max, i => { var confidence = (float)((Single)detectionArray.GetValue(i, 14)!); if (confidence > 0.5) { // Coordonnées 2 points qui tracent un rectangle englobe le visage var x1 = (int)((Single)detectionArray.GetValue(i, 0)!); var y1 = (int)((Single)detectionArray.GetValue(i, 1)!); var x2 = (int)((Single)detectionArray.GetValue(i, 2)!); var y2 = (int)((Single)detectionArray.GetValue(i, 3)!); lstRect.Add(new System.Drawing.Rectangle(x1, y1, x2, y2)); } }); return lstRect.ToArray(); } private string TrtFindFileFaceHaarXML() { return Path.Combine(_PathModel, _FaceHaarXML); } private string TrtFindFileFaceModelDetect() { return Path.Combine(_PathModel, _NomModelDetect); } private string TrtFindFileModel() { return Path.Combine(_PathModel, _NomModel); } #endregion } }