test_face.cpp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. // This file is part of OpenCV project.
  2. // It is subject to the license terms in the LICENSE file found in the top-level directory
  3. // of this distribution and at http://opencv.org/license.html.
  4. #include "test_precomp.hpp"
  5. namespace opencv_test { namespace {
  6. // label format:
  7. // image_name
  8. // num_face
  9. // face_1
  10. // face_..
  11. // face_num
  12. std::map<std::string, Mat> blobFromTXT(const std::string& path, int numCoords)
  13. {
  14. std::ifstream ifs(path.c_str());
  15. CV_Assert(ifs.is_open());
  16. std::map<std::string, Mat> gt;
  17. Mat faces;
  18. int faceNum = -1;
  19. int faceCount = 0;
  20. for (std::string line, key; getline(ifs, line); )
  21. {
  22. std::istringstream iss(line);
  23. if (line.find(".png") != std::string::npos)
  24. {
  25. // Get filename
  26. iss >> key;
  27. }
  28. else if (line.find(" ") == std::string::npos)
  29. {
  30. // Get the number of faces
  31. iss >> faceNum;
  32. }
  33. else
  34. {
  35. // Get faces
  36. Mat face(1, numCoords, CV_32FC1);
  37. for (int j = 0; j < numCoords; j++)
  38. {
  39. iss >> face.at<float>(0, j);
  40. }
  41. faces.push_back(face);
  42. faceCount++;
  43. }
  44. if (faceCount == faceNum)
  45. {
  46. // Store faces
  47. gt[key] = faces;
  48. faces.release();
  49. faceNum = -1;
  50. faceCount = 0;
  51. }
  52. }
  53. return gt;
  54. }
  55. TEST(Objdetect_face_detection, regression)
  56. {
  57. // Pre-set params
  58. float scoreThreshold = 0.7f;
  59. float matchThreshold = 0.9f;
  60. float l2disThreshold = 5.0f;
  61. int numLM = 5;
  62. int numCoords = 4 + 2 * numLM;
  63. // Load ground truth labels
  64. std::map<std::string, Mat> gt = blobFromTXT(findDataFile("dnn_face/detection/cascades_labels.txt"), numCoords);
  65. // for (auto item: gt)
  66. // {
  67. // std::cout << item.first << " " << item.second.size() << std::endl;
  68. // }
  69. // Initialize detector
  70. std::string model = findDataFile("dnn/onnx/models/yunet-202109.onnx", false);
  71. Ptr<FaceDetectorYN> faceDetector = FaceDetectorYN::create(model, "", Size(300, 300));
  72. faceDetector->setScoreThreshold(0.7f);
  73. // Detect and match
  74. for (auto item: gt)
  75. {
  76. std::string imagePath = findDataFile("cascadeandhog/images/" + item.first);
  77. Mat image = imread(imagePath);
  78. // Set input size
  79. faceDetector->setInputSize(image.size());
  80. // Run detection
  81. Mat faces;
  82. faceDetector->detect(image, faces);
  83. // std::cout << item.first << " " << item.second.rows << " " << faces.rows << std::endl;
  84. // Match bboxes and landmarks
  85. std::vector<bool> matchedItem(item.second.rows, false);
  86. for (int i = 0; i < faces.rows; i++)
  87. {
  88. if (faces.at<float>(i, numCoords) < scoreThreshold)
  89. continue;
  90. bool boxMatched = false;
  91. std::vector<bool> lmMatched(numLM, false);
  92. cv::Rect2f resBox(faces.at<float>(i, 0), faces.at<float>(i, 1), faces.at<float>(i, 2), faces.at<float>(i, 3));
  93. for (int j = 0; j < item.second.rows && !boxMatched; j++)
  94. {
  95. if (matchedItem[j])
  96. continue;
  97. // Retrieve bbox and compare IoU
  98. cv::Rect2f gtBox(item.second.at<float>(j, 0), item.second.at<float>(j, 1), item.second.at<float>(j, 2), item.second.at<float>(j, 3));
  99. double interArea = (resBox & gtBox).area();
  100. double iou = interArea / (resBox.area() + gtBox.area() - interArea);
  101. if (iou >= matchThreshold)
  102. {
  103. boxMatched = true;
  104. matchedItem[j] = true;
  105. }
  106. // Match landmarks if bbox is matched
  107. if (!boxMatched)
  108. continue;
  109. for (int lmIdx = 0; lmIdx < numLM; lmIdx++)
  110. {
  111. float gtX = item.second.at<float>(j, 4 + 2 * lmIdx);
  112. float gtY = item.second.at<float>(j, 4 + 2 * lmIdx + 1);
  113. float resX = faces.at<float>(i, 4 + 2 * lmIdx);
  114. float resY = faces.at<float>(i, 4 + 2 * lmIdx + 1);
  115. float l2dis = cv::sqrt((gtX - resX) * (gtX - resX) + (gtY - resY) * (gtY - resY));
  116. if (l2dis <= l2disThreshold)
  117. {
  118. lmMatched[lmIdx] = true;
  119. }
  120. }
  121. }
  122. EXPECT_TRUE(boxMatched) << "In image " << item.first << ", cannot match resBox " << resBox << " with any ground truth.";
  123. if (boxMatched)
  124. {
  125. EXPECT_TRUE(std::all_of(lmMatched.begin(), lmMatched.end(), [](bool v) { return v; })) << "In image " << item.first << ", resBox " << resBox << " matched but its landmarks failed to match.";
  126. }
  127. }
  128. }
  129. }
  130. TEST(Objdetect_face_recognition, regression)
  131. {
  132. // Pre-set params
  133. float score_thresh = 0.9f;
  134. float nms_thresh = 0.3f;
  135. double cosine_similar_thresh = 0.363;
  136. double l2norm_similar_thresh = 1.128;
  137. // Load ground truth labels
  138. std::ifstream ifs(findDataFile("dnn_face/recognition/cascades_label.txt").c_str());
  139. CV_Assert(ifs.is_open());
  140. std::set<std::string> fSet;
  141. std::map<std::string, Mat> featureMap;
  142. std::map<std::pair<std::string, std::string>, int> gtMap;
  143. for (std::string line, key; getline(ifs, line);)
  144. {
  145. std::string fname1, fname2;
  146. int label;
  147. std::istringstream iss(line);
  148. iss>>fname1>>fname2>>label;
  149. // std::cout<<fname1<<" "<<fname2<<" "<<label<<std::endl;
  150. fSet.insert(fname1);
  151. fSet.insert(fname2);
  152. gtMap[std::make_pair(fname1, fname2)] = label;
  153. }
  154. // Initialize detector
  155. std::string detect_model = findDataFile("dnn/onnx/models/yunet-202109.onnx", false);
  156. Ptr<FaceDetectorYN> faceDetector = FaceDetectorYN::create(detect_model, "", Size(150, 150), score_thresh, nms_thresh);
  157. std::string recog_model = findDataFile("dnn/onnx/models/face_recognizer_fast.onnx", false);
  158. Ptr<FaceRecognizerSF> faceRecognizer = FaceRecognizerSF::create(recog_model, "");
  159. // Detect and match
  160. for (auto fname: fSet)
  161. {
  162. std::string imagePath = findDataFile("dnn_face/recognition/" + fname);
  163. Mat image = imread(imagePath);
  164. Mat faces;
  165. faceDetector->detect(image, faces);
  166. Mat aligned_face;
  167. faceRecognizer->alignCrop(image, faces.row(0), aligned_face);
  168. Mat feature;
  169. faceRecognizer->feature(aligned_face, feature);
  170. featureMap[fname] = feature.clone();
  171. }
  172. for (auto item: gtMap)
  173. {
  174. Mat feature1 = featureMap[item.first.first];
  175. Mat feature2 = featureMap[item.first.second];
  176. int label = item.second;
  177. double cos_score = faceRecognizer->match(feature1, feature2, FaceRecognizerSF::DisType::FR_COSINE);
  178. double L2_score = faceRecognizer->match(feature1, feature2, FaceRecognizerSF::DisType::FR_NORM_L2);
  179. EXPECT_TRUE(label == 0 ? cos_score <= cosine_similar_thresh : cos_score > cosine_similar_thresh) << "Cosine match result of images " << item.first.first << " and " << item.first.second << " is different from ground truth (score: "<< cos_score <<";Thresh: "<< cosine_similar_thresh <<").";
  180. EXPECT_TRUE(label == 0 ? L2_score > l2norm_similar_thresh : L2_score <= l2norm_similar_thresh) << "L2norm match result of images " << item.first.first << " and " << item.first.second << " is different from ground truth (score: "<< L2_score <<";Thresh: "<< l2norm_similar_thresh <<").";
  181. }
  182. }
  183. }} // namespace