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 9 #include "opencv2/objdetect/objdetect.hpp" 10 #include "opencv2/highgui/highgui.hpp" 11 #include "opencv2/imgproc/imgproc.hpp" 12 13 #include <iostream> 14 #include <cstdio> 15 #include <vector> 16 #include <algorithm> 17 18 using namespace std; 19 using namespace cv; 20 21 // Functions to parse command-line arguments 22 static string getCommandOption(const vector<string>&, const string&); 23 static void setCommandOptions(vector<string>&, int, char**); 24 static bool doesCmdOptionExist(const vector<string>& , const string&); 25 26 // Functions for facial feature detection 27 static void help(); 28 static void detectFaces(Mat&, vector<Rect_<int> >&, string); 29 static void detectEyes(Mat&, vector<Rect_<int> >&, string); 30 static void detectNose(Mat&, vector<Rect_<int> >&, string); 31 static void detectMouth(Mat&, vector<Rect_<int> >&, string); 32 static void detectFacialFeaures(Mat&, const vector<Rect_<int> >, string, string, string); 33 34 string input_image_path; 35 string face_cascade_path, eye_cascade_path, nose_cascade_path, mouth_cascade_path; 36 37 int main(int argc, char** argv) 38 { 39 if(argc < 3) 40 { 41 help(); 42 return 1; 43 } 44 45 // Extract command-line options 46 vector<string> args; 47 setCommandOptions(args, argc, argv); 48 49 input_image_path = argv[1]; 50 face_cascade_path = argv[2]; 51 eye_cascade_path = (doesCmdOptionExist(args, "-eyes")) ? getCommandOption(args, "-eyes") : ""; 52 nose_cascade_path = (doesCmdOptionExist(args, "-nose")) ? getCommandOption(args, "-nose") : ""; 53 mouth_cascade_path = (doesCmdOptionExist(args, "-mouth")) ? getCommandOption(args, "-mouth") : ""; 54 55 // Load image and cascade classifier files 56 Mat image; 57 image = imread(input_image_path); 58 59 // Detect faces and facial features 60 vector<Rect_<int> > faces; 61 detectFaces(image, faces, face_cascade_path); 62 detectFacialFeaures(image, faces, eye_cascade_path, nose_cascade_path, mouth_cascade_path); 63 64 imshow("Result", image); 65 66 waitKey(0); 67 return 0; 68 } 69 70 void setCommandOptions(vector<string>& args, int argc, char** argv) 71 { 72 for(int i = 1; i < argc; ++i) 73 { 74 args.push_back(argv[i]); 75 } 76 return; 77 } 78 79 string getCommandOption(const vector<string>& args, const string& opt) 80 { 81 string answer; 82 vector<string>::const_iterator it = find(args.begin(), args.end(), opt); 83 if(it != args.end() && (++it != args.end())) 84 answer = *it; 85 return answer; 86 } 87 88 bool doesCmdOptionExist(const vector<string>& args, const string& opt) 89 { 90 vector<string>::const_iterator it = find(args.begin(), args.end(), opt); 91 return (it != args.end()); 92 } 93 94 static void help() 95 { 96 cout << "\nThis file demonstrates facial feature points detection using Haarcascade classifiers.\n" 97 "The program detects a face and eyes, nose and mouth inside the face." 98 "The code has been tested on the Japanese Female Facial Expression (JAFFE) database and found" 99 "to give reasonably accurate results. \n"; 100 101 cout << "\nUSAGE: ./cpp-example-facial_features [IMAGE] [FACE_CASCADE] [OPTIONS]\n" 102 "IMAGE\n\tPath to the image of a face taken as input.\n" 103 "FACE_CASCSDE\n\t Path to a haarcascade classifier for face detection.\n" 104 "OPTIONS: \nThere are 3 options available which are described in detail. There must be a " 105 "space between the option and it's argument (All three options accept arguments).\n" 106 "\t-eyes : Specify the haarcascade classifier for eye detection.\n" 107 "\t-nose : Specify the haarcascade classifier for nose detection.\n" 108 "\t-mouth : Specify the haarcascade classifier for mouth detection.\n"; 109 110 111 cout << "EXAMPLE:\n" 112 "(1) ./cpp-example-facial_features image.jpg face.xml -eyes eyes.xml -mouth mouth.xml\n" 113 "\tThis will detect the face, eyes and mouth in image.jpg.\n" 114 "(2) ./cpp-example-facial_features image.jpg face.xml -nose nose.xml\n" 115 "\tThis will detect the face and nose in image.jpg.\n" 116 "(3) ./cpp-example-facial_features image.jpg face.xml\n" 117 "\tThis will detect only the face in image.jpg.\n"; 118 119 cout << " \n\nThe classifiers for face and eyes can be downloaded from : " 120 " \nhttps://github.com/Itseez/opencv/tree/master/data/haarcascades"; 121 122 cout << "\n\nThe classifiers for nose and mouth can be downloaded from : " 123 " \nhttps://github.com/Itseez/opencv_contrib/tree/master/modules/face/data/cascades\n"; 124 } 125 126 static void detectFaces(Mat& img, vector<Rect_<int> >& faces, string cascade_path) 127 { 128 CascadeClassifier face_cascade; 129 face_cascade.load(cascade_path); 130 131 face_cascade.detectMultiScale(img, faces, 1.15, 3, 0|CASCADE_SCALE_IMAGE, Size(30, 30)); 132 return; 133 } 134 135 static void detectFacialFeaures(Mat& img, const vector<Rect_<int> > faces, string eye_cascade, 136 string nose_cascade, string mouth_cascade) 137 { 138 for(unsigned int i = 0; i < faces.size(); ++i) 139 { 140 // Mark the bounding box enclosing the face 141 Rect face = faces[i]; 142 rectangle(img, Point(face.x, face.y), Point(face.x+face.width, face.y+face.height), 143 Scalar(255, 0, 0), 1, 4); 144 145 // Eyes, nose and mouth will be detected inside the face (region of interest) 146 Mat ROI = img(Rect(face.x, face.y, face.width, face.height)); 147 148 // Check if all features (eyes, nose and mouth) are being detected 149 bool is_full_detection = false; 150 if( (!eye_cascade.empty()) && (!nose_cascade.empty()) && (!mouth_cascade.empty()) ) 151 is_full_detection = true; 152 153 // Detect eyes if classifier provided by the user 154 if(!eye_cascade.empty()) 155 { 156 vector<Rect_<int> > eyes; 157 detectEyes(ROI, eyes, eye_cascade); 158 159 // Mark points corresponding to the centre of the eyes 160 for(unsigned int j = 0; j < eyes.size(); ++j) 161 { 162 Rect e = eyes[j]; 163 circle(ROI, Point(e.x+e.width/2, e.y+e.height/2), 3, Scalar(0, 255, 0), -1, 8); 164 /* rectangle(ROI, Point(e.x, e.y), Point(e.x+e.width, e.y+e.height), 165 Scalar(0, 255, 0), 1, 4); */ 166 } 167 } 168 169 // Detect nose if classifier provided by the user 170 double nose_center_height = 0.0; 171 if(!nose_cascade.empty()) 172 { 173 vector<Rect_<int> > nose; 174 detectNose(ROI, nose, nose_cascade); 175 176 // Mark points corresponding to the centre (tip) of the nose 177 for(unsigned int j = 0; j < nose.size(); ++j) 178 { 179 Rect n = nose[j]; 180 circle(ROI, Point(n.x+n.width/2, n.y+n.height/2), 3, Scalar(0, 255, 0), -1, 8); 181 nose_center_height = (n.y + n.height/2); 182 } 183 } 184 185 // Detect mouth if classifier provided by the user 186 double mouth_center_height = 0.0; 187 if(!mouth_cascade.empty()) 188 { 189 vector<Rect_<int> > mouth; 190 detectMouth(ROI, mouth, mouth_cascade); 191 192 for(unsigned int j = 0; j < mouth.size(); ++j) 193 { 194 Rect m = mouth[j]; 195 mouth_center_height = (m.y + m.height/2); 196 197 // The mouth should lie below the nose 198 if( (is_full_detection) && (mouth_center_height > nose_center_height) ) 199 { 200 rectangle(ROI, Point(m.x, m.y), Point(m.x+m.width, m.y+m.height), Scalar(0, 255, 0), 1, 4); 201 } 202 else if( (is_full_detection) && (mouth_center_height <= nose_center_height) ) 203 continue; 204 else 205 rectangle(ROI, Point(m.x, m.y), Point(m.x+m.width, m.y+m.height), Scalar(0, 255, 0), 1, 4); 206 } 207 } 208 209 } 210 211 return; 212 } 213 214 static void detectEyes(Mat& img, vector<Rect_<int> >& eyes, string cascade_path) 215 { 216 CascadeClassifier eyes_cascade; 217 eyes_cascade.load(cascade_path); 218 219 eyes_cascade.detectMultiScale(img, eyes, 1.20, 5, 0|CASCADE_SCALE_IMAGE, Size(30, 30)); 220 return; 221 } 222 223 static void detectNose(Mat& img, vector<Rect_<int> >& nose, string cascade_path) 224 { 225 CascadeClassifier nose_cascade; 226 nose_cascade.load(cascade_path); 227 228 nose_cascade.detectMultiScale(img, nose, 1.20, 5, 0|CASCADE_SCALE_IMAGE, Size(30, 30)); 229 return; 230 } 231 232 static void detectMouth(Mat& img, vector<Rect_<int> >& mouth, string cascade_path) 233 { 234 CascadeClassifier mouth_cascade; 235 mouth_cascade.load(cascade_path); 236 237 mouth_cascade.detectMultiScale(img, mouth, 1.20, 5, 0|CASCADE_SCALE_IMAGE, Size(30, 30)); 238 return; 239 } 240