Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

530 lines
19KB

  1. using Emgu.CV;
  2. using Emgu.CV.Dnn;
  3. using Emgu.CV.Face;
  4. using Emgu.CV.Structure;
  5. using System.Drawing;
  6. using System.Text;
  7. using ToolsServices;
  8. using static System.Net.Mime.MediaTypeNames;
  9. namespace FaceRecognition
  10. {
  11. public class FaceRecognitionService
  12. {
  13. #region Variables privées statiques
  14. private static string NomFichierData = FichiersInternesService.ParamsFaceRecognition;
  15. #endregion
  16. #region Variables privées
  17. private readonly string _PathDataTrainSet = @"D:\_TrainingData\FaceRecognition\TrainSet";
  18. private readonly string _PathModel = @"Models";
  19. private readonly string _NomModelDetect = @"face_detection_yunet_2023mar.onnx";
  20. private readonly string _NomModel = @"model_reconnaissance.yml";
  21. private readonly string _FaceHaarXML = @"haarcascade_frontalface_alt.xml";
  22. private readonly int _ReSizeFaceImg = 300;
  23. private readonly int _ReSizeImage = 400;
  24. private readonly float _ScaleFactor = 0.9f;
  25. private readonly System.Drawing.Size _MinSizeDetect = new(30, 30);
  26. private readonly int _DistanceMini = 40;
  27. private readonly MCvScalar _MCvScalarColorGreen = new(0, 255, 0);
  28. private readonly MCvScalar _MCvScalarColorRed = new(0, 0, 255);
  29. private readonly int _SizeBorder = 2;
  30. private readonly LBPHFaceRecognizer _FaceRecognizer = new();
  31. private readonly FaceDetectorYN _FaceNet;
  32. private List<int> _LstLabels = new();
  33. private List<string> _LstLabelsString = new();
  34. private readonly System.Drawing.Size _InputSize = new(300, 300);
  35. #endregion
  36. #region Constructeur
  37. public FaceRecognitionService(bool isCuda = false)
  38. {
  39. var parametres = LoadParametres();
  40. _PathDataTrainSet = parametres.Path_TrainSet;
  41. // Créer un objet CascadeClassifier pour détecter les visages
  42. if (isCuda)
  43. {
  44. _FaceNet = new FaceDetectorYN(TrtFindFileFaceModelDetect(), "", _InputSize, backendId: Emgu.CV.Dnn.Backend.Cuda, targetId: Target.Cuda);
  45. }
  46. else
  47. {
  48. _FaceNet = new FaceDetectorYN(TrtFindFileFaceModelDetect(), "", _InputSize, backendId: Emgu.CV.Dnn.Backend.Default, targetId: Target.Cpu);
  49. }
  50. }
  51. #endregion
  52. #region Gestion des paramètres TrainSet et TestSet
  53. public static ParametresFaceRecognitionService LoadParametres()
  54. {
  55. LoggerService.LogInfo("ParametresFaceRecognitionService.LoadParametres");
  56. ParametresFaceRecognitionService SelectedItem = new();
  57. try
  58. {
  59. if (File.Exists(NomFichierData))
  60. {
  61. string[] lignes = File.ReadAllLines(NomFichierData);
  62. if (lignes.Length > 0)
  63. SelectedItem.Path_TrainSet = lignes[0];
  64. if (lignes.Length > 1)
  65. SelectedItem.Path_TestSet = lignes[1];
  66. }
  67. return SelectedItem;
  68. }
  69. catch
  70. {
  71. return new();
  72. }
  73. }
  74. public static bool SaveParametres(ParametresFaceRecognitionService selectedItem)
  75. {
  76. LoggerService.LogInfo("ParametresFaceRecognitionService.SaveParametres");
  77. try
  78. {
  79. StringBuilder sb = new();
  80. sb.AppendLine(selectedItem.Path_TrainSet);
  81. sb.AppendLine(selectedItem.Path_TestSet);
  82. File.WriteAllText(NomFichierData, sb.ToString());
  83. return true;
  84. }
  85. catch
  86. {
  87. return false;
  88. }
  89. }
  90. #endregion
  91. #region Méthodes publiques
  92. public bool CapturePhoto(string filename)
  93. {
  94. try
  95. {
  96. var inputImage = new Mat(filename);
  97. var inputClone = inputImage.Clone();
  98. var lstRetour = new List<string>();
  99. if (!File.Exists(filename))
  100. {
  101. throw new Exception($"Fichier manquant : {filename}.");
  102. }
  103. _FaceRecognizer.Read(TrtFindFileModel());
  104. // Détecter les visages dans l'image
  105. var w = (int)(_ReSizeImage * 3 * _ScaleFactor);
  106. var h = (int)(_ReSizeImage * 3 * inputImage.Height / inputImage.Width * _ScaleFactor);
  107. CvInvoke.Resize(inputImage, inputImage, new System.Drawing.Size(w, h));
  108. var faces = DetectMultiFaces(inputImage);
  109. if (faces.Length == 0)
  110. {
  111. inputImage = inputClone.Clone();
  112. w = (int)(_ReSizeImage * 2 * _ScaleFactor);
  113. h = (int)(_ReSizeImage * 2 * inputImage.Height / inputImage.Width * _ScaleFactor);
  114. CvInvoke.Resize(inputImage, inputImage, new System.Drawing.Size(w, h));
  115. faces = DetectMultiFaces(inputImage);
  116. }
  117. if (faces.Length == 0)
  118. {
  119. inputImage = inputClone.Clone();
  120. w = (int)(_ReSizeImage * _ScaleFactor);
  121. h = (int)(_ReSizeImage * inputImage.Height / inputImage.Width * _ScaleFactor);
  122. CvInvoke.Resize(inputImage, inputImage, new System.Drawing.Size(w, h));
  123. faces = DetectMultiFaces(inputImage);
  124. }
  125. // Parcourir chaque visage détecté
  126. foreach (var face in faces)
  127. {
  128. // Extraire la région du visage de l'image
  129. var faceImage = new Mat(inputImage, face);
  130. if (System.IO.File.Exists(filename))
  131. System.IO.File.Delete(filename);
  132. CvInvoke.Imwrite(filename, faceImage);
  133. }
  134. return true;
  135. }
  136. catch
  137. {
  138. return false;
  139. }
  140. }
  141. public (bool, string) Training(bool isPhotoVisible)
  142. {
  143. return TrtTraining(isPhotoVisible);
  144. }
  145. public List<string> Predict(string filename)
  146. {
  147. (var lst, _) = TrtPredict(new(filename));
  148. return lst;
  149. }
  150. public List<string> Predict(Mat inputImage)
  151. {
  152. (var lst, _) = TrtPredict(inputImage);
  153. return lst;
  154. }
  155. public List<string> PredictLiveNoShow()
  156. {
  157. List<string> lst = new();
  158. var videoCapture = new Emgu.CV.VideoCapture(0);
  159. try
  160. {
  161. int i = 0;
  162. while (true)
  163. {
  164. try
  165. {
  166. i++;
  167. Mat mat = new();
  168. videoCapture.Read(mat);
  169. // Préparation de l'affichage de l'image
  170. (lst, _) = TrtPredict(mat, false, true);
  171. if (i > 4)
  172. break;
  173. }
  174. catch (Exception ex)
  175. {
  176. Console.WriteLine($"Erreur : {ex.Message}");
  177. }
  178. }
  179. }
  180. catch
  181. {
  182. }
  183. return lst;
  184. }
  185. public void PredictLive()
  186. {
  187. var videoCapture = new Emgu.CV.VideoCapture(0);
  188. try
  189. {
  190. while (true)
  191. {
  192. try
  193. {
  194. Mat mat = new();
  195. Mat matOutput;
  196. videoCapture.Read(mat);
  197. // Préparation de l'affichage de l'image
  198. (_, matOutput) = TrtPredict(mat, false, true);
  199. CvInvoke.Imshow("frame", matOutput);
  200. // affichage et saisie d'un code clavier (Q ou ECHAP)
  201. if (CvInvoke.WaitKey(1) == (int)ConsoleKey.Q || CvInvoke.WaitKey(1) == (int)ConsoleKey.Escape)
  202. break;
  203. }
  204. catch (Exception ex)
  205. {
  206. Console.WriteLine($"Erreur : {ex.Message}");
  207. }
  208. }
  209. }
  210. catch
  211. {
  212. }
  213. finally
  214. {
  215. // Ne pas oublier de fermer le flux et la fenetre
  216. CvInvoke.WaitKey(0);
  217. CvInvoke.DestroyAllWindows();
  218. }
  219. }
  220. #endregion
  221. #region Méthodes privées
  222. private (bool, string) TrtTraining(bool isPhotoVisible)
  223. {
  224. bool isReturnOK = true;
  225. string msgReturn = "";
  226. try
  227. {
  228. // Créer des listes pour stocker les images de formation et les étiquettes correspondantes
  229. var trainingImages = TrtFindImagesAndLabels(true, isPhotoVisible);
  230. // Entraîner le reconnaiseur avec les images de formation et les étiquettes
  231. _FaceRecognizer.Train(trainingImages.ToArray(), _LstLabels.ToArray());
  232. for (int i = 0; i < _LstLabels.Count; i++)
  233. {
  234. _FaceRecognizer.SetLabelInfo(_LstLabels[i], _LstLabelsString[i]);
  235. }
  236. // Enregistrer le modèle entraîné
  237. _FaceRecognizer.Write(Path.Combine(_PathModel, _NomModel));
  238. }
  239. catch (Exception ex)
  240. {
  241. isReturnOK = false;
  242. msgReturn = ex.Message;
  243. }
  244. return (isReturnOK, msgReturn);
  245. }
  246. private (List<string>, Mat) TrtPredict(Mat inputImage, bool isAffichage = true, bool isOnlyFirstName=true)
  247. {
  248. var inputClone = inputImage.Clone();
  249. var lstRetour = new List<string>();
  250. string filename = TrtFindFileModel();
  251. if (!File.Exists(filename))
  252. {
  253. throw new Exception($"Fichier manquant : {filename}.");
  254. }
  255. _FaceRecognizer.Read(TrtFindFileModel());
  256. // Convertir l'image en niveaux de gris pour faciliter la détection des visages
  257. var grayImage = new Mat();
  258. // Détecter les visages dans l'image
  259. var w = (int)(_ReSizeImage * 3 * _ScaleFactor);
  260. var h = (int)(_ReSizeImage * 3 * inputImage.Height / inputImage.Width * _ScaleFactor);
  261. CvInvoke.Resize(inputImage, inputImage, new System.Drawing.Size(w, h));
  262. var faces = DetectMultiFaces(inputImage);
  263. if (faces.Length == 0)
  264. {
  265. inputImage = inputClone.Clone();
  266. w = (int)(_ReSizeImage * 2 * _ScaleFactor);
  267. h = (int)(_ReSizeImage * 2 * inputImage.Height / inputImage.Width * _ScaleFactor);
  268. CvInvoke.Resize(inputImage, inputImage, new System.Drawing.Size(w, h));
  269. faces = DetectMultiFaces(inputImage);
  270. }
  271. if (faces.Length == 0)
  272. {
  273. inputImage = inputClone.Clone();
  274. w = (int)(_ReSizeImage * _ScaleFactor);
  275. h = (int)(_ReSizeImage * inputImage.Height / inputImage.Width * _ScaleFactor);
  276. CvInvoke.Resize(inputImage, inputImage, new System.Drawing.Size(w, h));
  277. faces = DetectMultiFaces(inputImage);
  278. }
  279. CvInvoke.CvtColor(inputImage, grayImage, Emgu.CV.CvEnum.ColorConversion.Bgr2Gray);
  280. // Parcourir chaque visage détecté
  281. foreach (var face in faces)
  282. {
  283. // Extraire la région du visage de l'image en niveaux de gris
  284. var faceImage = new Mat(grayImage, face);
  285. CvInvoke.Resize(faceImage, faceImage, new System.Drawing.Size(_ReSizeFaceImg, _ReSizeFaceImg));
  286. // Reconnaître le visage à partir de l'image redimensionnée
  287. var predict = _FaceRecognizer.Predict(faceImage);
  288. if (predict.Distance < _DistanceMini)
  289. {
  290. var nameComplet = _FaceRecognizer.GetLabelInfo(predict.Label);
  291. var name = "";
  292. if (isOnlyFirstName)
  293. {
  294. name = nameComplet.Split('_')[0];
  295. }
  296. else
  297. {
  298. name = nameComplet.Replace("_", " ");
  299. }
  300. lstRetour.Add(name);
  301. CvInvoke.Rectangle(inputImage, face, _MCvScalarColorGreen, _SizeBorder);
  302. //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);
  303. CvInvoke.PutText(inputImage, name, new System.Drawing.Point(face.X, face.Y), Emgu.CV.CvEnum.FontFace.HersheySimplex, 1.0, _MCvScalarColorGreen, _SizeBorder);
  304. 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);
  305. }
  306. else
  307. {
  308. //var name = "???";
  309. //lstRetour.Add(name);
  310. CvInvoke.Rectangle(inputImage, face, _MCvScalarColorRed, _SizeBorder);
  311. 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);
  312. }
  313. }
  314. if (isAffichage)
  315. {
  316. CvInvoke.Imshow("reconnaissance", inputImage);
  317. CvInvoke.WaitKey(0);
  318. CvInvoke.DestroyAllWindows();
  319. }
  320. return (lstRetour, inputImage);
  321. }
  322. private List<Mat> TrtFindImagesAndLabels(bool isTraining, bool isPhotoVisible)
  323. {
  324. // Créer des listes pour stocker les images de formation et les étiquettes correspondantes
  325. var lstTrainingImages = new List<Mat>();
  326. if(System.IO.Directory.Exists(_PathDataTrainSet) == false)
  327. {
  328. throw new Exception($"Le dossier n'existe pas : {_PathDataTrainSet}");
  329. }
  330. try
  331. {
  332. _LstLabels = new();
  333. _LstLabelsString = new();
  334. var dirs = Directory.GetDirectories(_PathDataTrainSet);
  335. int numDir = 1;
  336. foreach (var dir in dirs)
  337. {
  338. var files = Directory.GetFiles(dir);
  339. foreach (var file in files)
  340. {
  341. var dernierDir = dir.Split(@"\");
  342. var nomDernierDir = dernierDir[^1];
  343. if (isTraining)
  344. {
  345. var image = new Mat(file);
  346. var grayImage = new Mat();
  347. //var w = (int)(image.Width * _ScaleFactor);
  348. //var h = (int)(image.Height * _ScaleFactor);
  349. var w = (int)(_ReSizeImage * _ScaleFactor);
  350. var h = (int)(_ReSizeImage * image.Height / image.Width * _ScaleFactor);
  351. CvInvoke.Resize(image, image, new System.Drawing.Size(w, h));
  352. var faces = DetectMultiFaces(image);
  353. CvInvoke.CvtColor(image, grayImage, Emgu.CV.CvEnum.ColorConversion.Bgr2Gray);
  354. if (faces.Length > 0)
  355. {
  356. var face = new Mat(grayImage, faces[0]);
  357. CvInvoke.Resize(face, face, new System.Drawing.Size(_ReSizeFaceImg, _ReSizeFaceImg));
  358. lstTrainingImages.Add(face);
  359. _LstLabels.Add(numDir);
  360. _LstLabelsString.Add(nomDernierDir);
  361. if (isPhotoVisible)
  362. {
  363. CvInvoke.PutText(image, nomDernierDir, new System.Drawing.Point(faces[0].X, faces[0].Y), Emgu.CV.CvEnum.FontFace.HersheySimplex, 1.0, _MCvScalarColorGreen, _SizeBorder);
  364. CvInvoke.Rectangle(image, faces[0], _MCvScalarColorGreen, _SizeBorder);
  365. }
  366. }
  367. else
  368. {
  369. Console.WriteLine("Visage non détecté");
  370. }
  371. if (isPhotoVisible)
  372. {
  373. CvInvoke.Imshow("reconnaissance", image);
  374. CvInvoke.WaitKey(0);
  375. CvInvoke.DestroyAllWindows();
  376. }
  377. }
  378. else
  379. {
  380. _LstLabels.Add(numDir);
  381. _LstLabelsString.Add(nomDernierDir);
  382. }
  383. }
  384. numDir++;
  385. }
  386. }
  387. catch (Exception ex)
  388. {
  389. throw new Exception(ex.Message);
  390. }
  391. return lstTrainingImages;
  392. }
  393. public Rectangle[] DetectMultiFaces(Mat img)
  394. {
  395. var lstRect = new List<Rectangle>();
  396. _FaceNet.InputSize = new System.Drawing.Size(img.Width, img.Height);
  397. var outputFaces = new Mat();
  398. _FaceNet.Detect(img, outputFaces);
  399. var detectionArray = outputFaces.GetData();
  400. if (detectionArray is null)
  401. {
  402. return new Rectangle[0];
  403. }
  404. var max = detectionArray.GetLength(0);
  405. Parallel.For(0, max, i =>
  406. {
  407. var confidence = (float)((Single)detectionArray.GetValue(i, 14)!);
  408. if (confidence > 0.5)
  409. {
  410. // Coordonnées 2 points qui tracent un rectangle englobe le visage
  411. var x1 = (int)((Single)detectionArray.GetValue(i, 0)!);
  412. var y1 = (int)((Single)detectionArray.GetValue(i, 1)!);
  413. var x2 = (int)((Single)detectionArray.GetValue(i, 2)!);
  414. var y2 = (int)((Single)detectionArray.GetValue(i, 3)!);
  415. lstRect.Add(new System.Drawing.Rectangle(x1, y1, x2, y2));
  416. }
  417. });
  418. return lstRect.ToArray();
  419. }
  420. private string TrtFindFileFaceHaarXML()
  421. {
  422. return Path.Combine(_PathModel, _FaceHaarXML);
  423. }
  424. private string TrtFindFileFaceModelDetect()
  425. {
  426. return Path.Combine(_PathModel, _NomModelDetect);
  427. }
  428. private string TrtFindFileModel()
  429. {
  430. return Path.Combine(_PathModel, _NomModel);
  431. }
  432. #endregion
  433. }
  434. }