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
main(int argc,char ** argv)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
setCommandOptions(vector<string> & args,int argc,char ** argv)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
getCommandOption(const vector<string> & args,const string & opt)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
doesCmdOptionExist(const vector<string> & args,const string & opt)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
help()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
detectFaces(Mat & img,vector<Rect_<int>> & faces,string cascade_path)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
detectFacialFeaures(Mat & img,const vector<Rect_<int>> faces,string eye_cascade,string nose_cascade,string mouth_cascade)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
detectEyes(Mat & img,vector<Rect_<int>> & eyes,string cascade_path)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
detectNose(Mat & img,vector<Rect_<int>> & nose,string cascade_path)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
detectMouth(Mat & img,vector<Rect_<int>> & mouth,string cascade_path)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