Home | History | Annotate | Download | only in cpp
      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