facial_features.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. /*
  2. * Author: Samyak Datta (datta[dot]samyak[at]gmail.com)
  3. *
  4. * A program to detect facial feature points using
  5. * Haarcascade classifiers for face, eyes, nose and mouth
  6. *
  7. */
  8. #include "opencv2/objdetect.hpp"
  9. #include "opencv2/highgui.hpp"
  10. #include "opencv2/imgproc.hpp"
  11. #include <iostream>
  12. #include <cstdio>
  13. #include <vector>
  14. #include <algorithm>
  15. using namespace std;
  16. using namespace cv;
  17. // Functions for facial feature detection
  18. static void help(char** argv);
  19. static void detectFaces(Mat&, vector<Rect_<int> >&, string);
  20. static void detectEyes(Mat&, vector<Rect_<int> >&, string);
  21. static void detectNose(Mat&, vector<Rect_<int> >&, string);
  22. static void detectMouth(Mat&, vector<Rect_<int> >&, string);
  23. static void detectFacialFeaures(Mat&, const vector<Rect_<int> >, string, string, string);
  24. string input_image_path;
  25. string face_cascade_path, eye_cascade_path, nose_cascade_path, mouth_cascade_path;
  26. int main(int argc, char** argv)
  27. {
  28. cv::CommandLineParser parser(argc, argv,
  29. "{eyes||}{nose||}{mouth||}{help h||}{@image||}{@facexml||}");
  30. if (parser.has("help"))
  31. {
  32. help(argv);
  33. return 0;
  34. }
  35. input_image_path = parser.get<string>("@image");
  36. face_cascade_path = parser.get<string>("@facexml");
  37. eye_cascade_path = parser.has("eyes") ? parser.get<string>("eyes") : "";
  38. nose_cascade_path = parser.has("nose") ? parser.get<string>("nose") : "";
  39. mouth_cascade_path = parser.has("mouth") ? parser.get<string>("mouth") : "";
  40. if (input_image_path.empty() || face_cascade_path.empty())
  41. {
  42. cout << "IMAGE or FACE_CASCADE are not specified";
  43. return 1;
  44. }
  45. // Load image and cascade classifier files
  46. Mat image;
  47. image = imread(samples::findFile(input_image_path));
  48. // Detect faces and facial features
  49. vector<Rect_<int> > faces;
  50. detectFaces(image, faces, face_cascade_path);
  51. detectFacialFeaures(image, faces, eye_cascade_path, nose_cascade_path, mouth_cascade_path);
  52. imshow("Result", image);
  53. waitKey(0);
  54. return 0;
  55. }
  56. static void help(char** argv)
  57. {
  58. cout << "\nThis file demonstrates facial feature points detection using Haarcascade classifiers.\n"
  59. "The program detects a face and eyes, nose and mouth inside the face."
  60. "The code has been tested on the Japanese Female Facial Expression (JAFFE) database and found"
  61. "to give reasonably accurate results. \n";
  62. cout << "\nUSAGE: " << argv[0] << " [IMAGE] [FACE_CASCADE] [OPTIONS]\n"
  63. "IMAGE\n\tPath to the image of a face taken as input.\n"
  64. "FACE_CASCSDE\n\t Path to a haarcascade classifier for face detection.\n"
  65. "OPTIONS: \nThere are 3 options available which are described in detail. There must be a "
  66. "space between the option and it's argument (All three options accept arguments).\n"
  67. "\t-eyes=<eyes_cascade> : Specify the haarcascade classifier for eye detection.\n"
  68. "\t-nose=<nose_cascade> : Specify the haarcascade classifier for nose detection.\n"
  69. "\t-mouth=<mouth-cascade> : Specify the haarcascade classifier for mouth detection.\n";
  70. cout << "EXAMPLE:\n"
  71. "(1) " << argv[0] << " image.jpg face.xml -eyes=eyes.xml -mouth=mouth.xml\n"
  72. "\tThis will detect the face, eyes and mouth in image.jpg.\n"
  73. "(2) " << argv[0] << " image.jpg face.xml -nose=nose.xml\n"
  74. "\tThis will detect the face and nose in image.jpg.\n"
  75. "(3) " << argv[0] << " image.jpg face.xml\n"
  76. "\tThis will detect only the face in image.jpg.\n";
  77. cout << " \n\nThe classifiers for face and eyes can be downloaded from : "
  78. " \nhttps://github.com/opencv/opencv/tree/4.x/data/haarcascades";
  79. cout << "\n\nThe classifiers for nose and mouth can be downloaded from : "
  80. " \nhttps://github.com/opencv/opencv_contrib/tree/4.x/modules/face/data/cascades\n";
  81. }
  82. static void detectFaces(Mat& img, vector<Rect_<int> >& faces, string cascade_path)
  83. {
  84. CascadeClassifier face_cascade;
  85. face_cascade.load(samples::findFile(cascade_path));
  86. if (!face_cascade.empty())
  87. face_cascade.detectMultiScale(img, faces, 1.15, 3, 0|CASCADE_SCALE_IMAGE, Size(30, 30));
  88. return;
  89. }
  90. static void detectFacialFeaures(Mat& img, const vector<Rect_<int> > faces, string eye_cascade,
  91. string nose_cascade, string mouth_cascade)
  92. {
  93. for(unsigned int i = 0; i < faces.size(); ++i)
  94. {
  95. // Mark the bounding box enclosing the face
  96. Rect face = faces[i];
  97. rectangle(img, Point(face.x, face.y), Point(face.x+face.width, face.y+face.height),
  98. Scalar(255, 0, 0), 1, 4);
  99. // Eyes, nose and mouth will be detected inside the face (region of interest)
  100. Mat ROI = img(Rect(face.x, face.y, face.width, face.height));
  101. // Check if all features (eyes, nose and mouth) are being detected
  102. bool is_full_detection = false;
  103. if( (!eye_cascade.empty()) && (!nose_cascade.empty()) && (!mouth_cascade.empty()) )
  104. is_full_detection = true;
  105. // Detect eyes if classifier provided by the user
  106. if(!eye_cascade.empty())
  107. {
  108. vector<Rect_<int> > eyes;
  109. detectEyes(ROI, eyes, eye_cascade);
  110. // Mark points corresponding to the centre of the eyes
  111. for(unsigned int j = 0; j < eyes.size(); ++j)
  112. {
  113. Rect e = eyes[j];
  114. circle(ROI, Point(e.x+e.width/2, e.y+e.height/2), 3, Scalar(0, 255, 0), -1, 8);
  115. /* rectangle(ROI, Point(e.x, e.y), Point(e.x+e.width, e.y+e.height),
  116. Scalar(0, 255, 0), 1, 4); */
  117. }
  118. }
  119. // Detect nose if classifier provided by the user
  120. double nose_center_height = 0.0;
  121. if(!nose_cascade.empty())
  122. {
  123. vector<Rect_<int> > nose;
  124. detectNose(ROI, nose, nose_cascade);
  125. // Mark points corresponding to the centre (tip) of the nose
  126. for(unsigned int j = 0; j < nose.size(); ++j)
  127. {
  128. Rect n = nose[j];
  129. circle(ROI, Point(n.x+n.width/2, n.y+n.height/2), 3, Scalar(0, 255, 0), -1, 8);
  130. nose_center_height = (n.y + n.height/2);
  131. }
  132. }
  133. // Detect mouth if classifier provided by the user
  134. double mouth_center_height = 0.0;
  135. if(!mouth_cascade.empty())
  136. {
  137. vector<Rect_<int> > mouth;
  138. detectMouth(ROI, mouth, mouth_cascade);
  139. for(unsigned int j = 0; j < mouth.size(); ++j)
  140. {
  141. Rect m = mouth[j];
  142. mouth_center_height = (m.y + m.height/2);
  143. // The mouth should lie below the nose
  144. if( (is_full_detection) && (mouth_center_height > nose_center_height) )
  145. {
  146. rectangle(ROI, Point(m.x, m.y), Point(m.x+m.width, m.y+m.height), Scalar(0, 255, 0), 1, 4);
  147. }
  148. else if( (is_full_detection) && (mouth_center_height <= nose_center_height) )
  149. continue;
  150. else
  151. rectangle(ROI, Point(m.x, m.y), Point(m.x+m.width, m.y+m.height), Scalar(0, 255, 0), 1, 4);
  152. }
  153. }
  154. }
  155. return;
  156. }
  157. static void detectEyes(Mat& img, vector<Rect_<int> >& eyes, string cascade_path)
  158. {
  159. CascadeClassifier eyes_cascade;
  160. eyes_cascade.load(samples::findFile(cascade_path, !cascade_path.empty()));
  161. if (!eyes_cascade.empty())
  162. eyes_cascade.detectMultiScale(img, eyes, 1.20, 5, 0|CASCADE_SCALE_IMAGE, Size(30, 30));
  163. return;
  164. }
  165. static void detectNose(Mat& img, vector<Rect_<int> >& nose, string cascade_path)
  166. {
  167. CascadeClassifier nose_cascade;
  168. nose_cascade.load(samples::findFile(cascade_path, !cascade_path.empty()));
  169. if (!nose_cascade.empty())
  170. nose_cascade.detectMultiScale(img, nose, 1.20, 5, 0|CASCADE_SCALE_IMAGE, Size(30, 30));
  171. return;
  172. }
  173. static void detectMouth(Mat& img, vector<Rect_<int> >& mouth, string cascade_path)
  174. {
  175. CascadeClassifier mouth_cascade;
  176. mouth_cascade.load(samples::findFile(cascade_path, !cascade_path.empty()));
  177. if (!mouth_cascade.empty())
  178. mouth_cascade.detectMultiScale(img, mouth, 1.20, 5, 0|CASCADE_SCALE_IMAGE, Size(30, 30));
  179. return;
  180. }