face_recognition.cpp
Go to the documentation of this file.
00001 /* Copyright 2012 Pouyan Ziafati, University of Luxembourg and Utrecht University
00002 
00003 * A ROS simple actionlib server for face recognition in video stream. It provides different face recognition functionalities such as adding training images directly from the video stream, re-training (updating the database to include new training images), recognizing faces in the video stream, etc.
00004 
00005 * All image processing and face recognition functionalities are provided by utilizing the Shervin Emami's c++ source code for face recognition (http://www.shervinemami.info/faceRecognition.html). Face Recognition is performed using Eigenfaces (also called "Principal Component Analysis" or PCA) 
00006 
00007 *License: Attribution-NonCommercial 3.0 Unported (http://creativecommons.org/licenses/by-nc/3.0/)
00008 *You are free:
00009     *to Share — to copy, distribute and transmit the work
00010     *to Remix — to adapt the work
00011 
00012 *Under the following conditions:
00013     *Attribution — You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work).
00014     *Noncommercial — You may not use this work for commercial purposes.
00015 
00016 *With the understanding that:
00017     *Waiver — Any of the above conditions can be waived if you get permission from the copyright holder.
00018     *Public Domain — Where the work or any of its elements is in the public domain under applicable law, that status is in no way affected by the license.
00019     *Other Rights — In no way are any of the following rights affected by the license:
00020         *Your fair dealing or fair use rights, or other applicable copyright exceptions and limitations;
00021         *The author's moral rights;
00022         *Rights other persons may have either in the work itself or in how the work is used, such as publicity or privacy rights.
00023     *Notice — For any reuse or distribution, you must make clear to others the license terms of this work.
00024 
00025 */
00026 
00027 #include <ros/ros.h>
00028 #include <actionlib/server/simple_action_server.h>
00029 #include <face_recognition/FaceRecognitionAction.h>
00030 #include <image_transport/image_transport.h>
00031 #include <cv_bridge/cv_bridge.h>
00032 #include <sensor_msgs/image_encodings.h>
00033 #include <opencv2/imgproc/imgproc.hpp>
00034 #include <opencv2/highgui/highgui.hpp>
00035 #include <vector>
00036 #include <cvaux.h>
00037 #include <cxcore.hpp>
00038 #include <sys/stat.h>
00039 #include <termios.h>
00040 //#include <term.h>
00041 #include <unistd.h>
00042 #include "face_recognition_lib.cpp"
00043 
00044 using namespace std;
00045 
00046 class FaceRecognition
00047 {
00048 public:
00049     
00050   FaceRecognition(std::string name) : 
00051     frl(),
00052     it_(nh_),
00053     pnh_("~"),
00054     as_(nh_, name, boost::bind(&FaceRecognition::executeCB, this, _1), false)
00055   {
00056     //a face recognized with confidence value higher than the confidence_value threshold is accepted as valid.
00057     pnh_.param<double>("confidence_value", confidence_value, 0.88);
00058     //if output screen is shown
00059     pnh_.param<bool>("show_screen_flag", show_screen_flag, true);
00060     ROS_INFO("show_screen_flag: %s", show_screen_flag ? "true" : "false");
00061     //a parameter for the "add_face_images" goal which determines the number of training images for a new face (person) to be acquired from the video stream 
00062     pnh_.param<int>("add_face_number", add_face_number, 25);
00063     add_face_number = 25;
00064     //the number of persons in the training file (train.txt)
00065     person_number=0;
00066     //starting the actionlib server
00067     as_.start();
00068     //if the number of persons in the training file is not equal with the number of persons in the trained database, the database is not updated and user should be notified to retrain the database if new tarining images are to be considered.
00069 
00070     if (show_screen_flag) {
00071       cvNamedWindow("Input", CV_WINDOW_AUTOSIZE);       // output screen
00072       cvInitFont(&font,CV_FONT_HERSHEY_PLAIN, 1.0, 4.0, 2,2,CV_AA);  
00073       textColor = CV_RGB(0,255,255);    // light blue text
00074     }
00075     goal_id_ = -99; 
00076 
00077     if(calcNumTrainingPerson("train.txt")!=frl.nPersons)
00078     {
00079        frl.database_updated=false; 
00080        //ROS_INFO("Alert: Database is not updated, You better (re)train from images!");       //BEFORE
00081        ROS_WARN("Alert: Database is not updated. Please delete \"facedata.xml\" and re-run!"); //AFTER
00082     }
00083     //subscribe to video stream through image transport class
00084     image_sub_ = it_.subscribe("/camera/image_raw", 1, &FaceRecognition::imageCB, this);
00085   }
00086 
00087   ~FaceRecognition(void)
00088   {
00089     if (show_screen_flag) {
00090      cvDestroyWindow("Input");
00091     }
00092   }
00093 
00094 
00095   void executeCB(const face_recognition::FaceRecognitionGoalConstPtr &goal)
00096   {
00097     //check to be sure if the goal should be still persuaded
00098     if( as_.isPreemptRequested() || ros::isShuttingDown() )
00099     {
00100       as_.setPreempted(); 
00101       return;
00102     }
00103     //check if the name of the person has been provided for the add-face-images goal  
00104     if(goal->order_id == 2 && goal->order_argument.empty() ) 
00105     {
00106       ROS_INFO("No name has been provided for the add_person_images goal");
00107       as_.setPreempted();
00108       return;
00109     }
00110     ros::Rate r(4);
00111     //Storing the information about the current goal and reseting feedback and result variables  
00112     goal_argument_ = goal->order_argument;
00113     result_.order_id = goal->order_id;  
00114     feedback_.order_id = goal->order_id;
00115     result_.names.clear();
00116     result_.confidence.clear();
00117     feedback_.names.clear();
00118     feedback_.confidence.clear();
00119     goal_id_ = goal->order_id;
00120     switch(goal_id_)
00121     { 
00122       //(exit) Goal is to exit
00123       case 4: ROS_INFO("exit request");
00124               as_.setSucceeded(result_);
00125               r.sleep();
00126               ros::shutdown(); 
00127               break;
00128       case 3:
00129           //(train_database) Goal is to (re)train the database from training images
00130           if(frl.retrainOnline()) 
00131             as_.setSucceeded(result_);
00132           else 
00133             as_.setAborted(result_);
00134           break;
00135       //(recognize_once) Goal is to recognize the person in the video stream, succeed when the first person is found
00136       case 0:
00137       //(recognize_continuous) Goal is to Continuously recognize persons in the video stream and provide feedback continuously. This goal is persuaded for infinite time
00138       case 1:
00139       //(add_face_images) Goal is to take a number of(add_face_number) images of a person's face from the video stream and save them as training images 
00140       case 2:
00141          
00142          {
00143           if(goal_id_ == 2)
00144             add_face_count=0;
00145           //to synchronize with processes performed in the subscribed function to the video stream (imageCB)
00146           //as far as the goal id is 0, 1 or 2, it's active and there is no preempting request, imageCB function can be called.
00147           while( as_.isActive() && !as_.isPreemptRequested() && !ros::isShuttingDown() )
00148             r.sleep();
00149           mutex_.lock();
00150           if(as_.isActive())    
00151           { 
00152              as_.setPreempted();
00153              ROS_INFO("Goal %d is preempted",goal_id_);
00154           } 
00155           goal_id_ = -99;
00156           mutex_.unlock();       
00157           break;               
00158          }
00159         
00160     }
00161     goal_id_ = -99; 
00162 }
00163 
00164   int calcNumTrainingPerson(const char * filename)
00165   {
00166       FILE * imgListFile = 0;
00167       char imgFilename[512];
00168       int iFace, nFaces=0;
00169       int person_num=0;
00170       // open the input file
00171       if( !(imgListFile = fopen(filename, "r")) )
00172         {
00173            fprintf(stderr, "Can\'t open file %s\n", filename);
00174            return 0;
00175         }
00176       // count the number of faces
00177       while( fgets(imgFilename, 512, imgListFile) ) ++nFaces;
00178       rewind(imgListFile);
00179       //count the number of persons
00180       for(iFace=0; iFace<nFaces; iFace++)
00181       {
00182         char personName[256];
00183         int personNumber;
00184         // read person number (beginning with 1), their name and the image filename.
00185         fscanf(imgListFile, "%d %s %s", &personNumber, personName, imgFilename);
00186         if (personNumber > person_num) 
00187            person_num = personNumber;
00188       }
00189       fclose(imgListFile); 
00190       return (person_num);     
00191   }
00192   void imageCB(const sensor_msgs::ImageConstPtr& msg)
00193   {
00194     //to synchronize with executeCB function.
00195     //as far as the goal id is 0, 1 or 2, it's active and there is no preempting request, imageCB function is proceed.
00196     if (!as_.isActive() || goal_id_ > 2)      return;
00197     if(!mutex_.try_lock()) return;    
00198     if(as_.isPreemptRequested())    
00199     {
00200        ROS_INFO("Goal %d is preempted",goal_id_);
00201        as_.setPreempted();
00202        mutex_.unlock(); return;
00203     }  
00204 
00205     //get the value of show_screen_flag from the parameter server
00206     ros::param::getCached("~show_screen_flag", show_screen_flag); 
00207 
00208     cv_bridge::CvImagePtr cv_ptr;
00209     //convert from ros image format to opencv image format
00210     try
00211     {
00212       cv_ptr = cv_bridge::toCvCopy(msg, "bgr8");
00213     }
00214     catch (cv_bridge::Exception& e)
00215     {
00216       ROS_ERROR("cv_bridge exception: %s", e.what());
00217       as_.setPreempted();
00218       ROS_INFO("Goal %d is preempted",goal_id_);
00219       mutex_.unlock();
00220       return;
00221     }
00222     ros::Rate r(4);   
00223     IplImage img_input = cv_ptr->image;
00224     IplImage *img= cvCloneImage(&img_input); 
00225     IplImage *greyImg;
00226     IplImage *faceImg;
00227     IplImage *sizedImg;
00228     IplImage *equalizedImg;
00229     CvRect faceRect;
00230     // Make sure the image is greyscale, since the Eigenfaces is only done on greyscale image.
00231     greyImg = frl.convertImageToGreyscale(img);
00232     // Perform face detection on the input image, using the given Haar cascade classifier.
00233     faceRect = frl.detectFaceInImage(greyImg,frl.faceCascade);
00234     // Make sure a valid face was detected.
00235     if (faceRect.width < 1) 
00236     {
00237       ROS_INFO("No face was detected in the last frame"); 
00238       if(show_screen_flag)
00239       {
00240         cvPutText(img, text_image.str().c_str(), cvPoint(10, faceRect.y + 50), &font, textColor);
00241         cvShowImage("Input", img);
00242         cvWaitKey(1);
00243       }
00244       cvReleaseImage( &greyImg );cvReleaseImage(&img);
00245       r.sleep(); 
00246       mutex_.unlock(); return;
00247     }
00248     if (show_screen_flag) {
00249       cvRectangle(img, cvPoint(faceRect.x, faceRect.y), cvPoint(faceRect.x + faceRect.width-1, faceRect.y + faceRect.height-1), CV_RGB(0,255,0), 1, 8, 0);
00250     }
00251     faceImg = frl.cropImage(greyImg, faceRect); // Get the detected face image.
00252     // Make sure the image is the same dimensions as the training images.
00253     sizedImg = frl.resizeImage(faceImg, frl.faceWidth, frl.faceHeight);
00254     // Give the image a standard brightness and contrast, in case it was too dark or low contrast.
00255     equalizedImg = cvCreateImage(cvGetSize(sizedImg), 8, 1);    // Create an empty greyscale image
00256     cvEqualizeHist(sizedImg, equalizedImg);
00257     cvReleaseImage( &greyImg );cvReleaseImage( &faceImg );cvReleaseImage( &sizedImg );  
00258     //check again if preempting request is not there!
00259     if(as_.isPreemptRequested())    
00260         {
00261            ROS_INFO("Goal %d is preempted",goal_id_);
00262            cvReleaseImage(&equalizedImg);cvReleaseImage(&img);  
00263            as_.setPreempted(); 
00264            ROS_INFO("Goal %d is preempted",goal_id_);
00265            mutex_.unlock(); return;
00266         }
00267      //goal is add_face_images
00268      if( goal_id_==2  )      
00269      {
00270         if(add_face_count==0)  
00271         {
00272            //assign the correct number for the new person
00273            person_number = calcNumTrainingPerson("train.txt")+1; 
00274         }
00275         char cstr[256];
00276         sprintf(cstr, "data/%d_%s%d.pgm", person_number, &goal_argument_[0], add_face_count+1);
00277         ROS_INFO("Storing the current face of '%s' into image '%s'.", &goal_argument_[0], cstr);
00278         //save the new training image of the person
00279         cvSaveImage(cstr, equalizedImg, NULL);
00280         // Append the new person to the end of the training data.
00281         trainFile = fopen("train.txt", "a");
00282         fprintf(trainFile, "%d %s %s\n", person_number, &goal_argument_[0], cstr);
00283         fclose(trainFile);
00284         if(add_face_count==0)  
00285         {
00286            //get from parameter server how many training imaged should be acquire.
00287            ros::param::getCached("~add_face_number", add_face_number);
00288            if(add_face_number<=0)
00289              {
00290                ROS_INFO("add_face_number parameter is Zero, it is Invalid. One face was added anyway!");
00291                add_face_number=1;
00292              } 
00293            frl.database_updated = false;
00294         }
00295         if (show_screen_flag) {
00296           text_image.str("");
00297           text_image <<"A picture of "<< &goal_argument_[0]<< "was added" <<endl;
00298           cvPutText(img, text_image.str().c_str(), cvPoint( 10, 50), &font, textColor);
00299         }
00300         //check if enough number of training images for the person has been acquired, then the goal is succeed.
00301         if(++add_face_count==add_face_number)   
00302         {
00303            
00304            result_.names.push_back(goal_argument_); 
00305            as_.setSucceeded(result_);
00306            if (show_screen_flag)
00307            {
00308               cvShowImage("Input", img);
00309               cvWaitKey(1);
00310            }
00311            cvReleaseImage(&equalizedImg); cvReleaseImage(&img);
00312            mutex_.unlock(); return;
00313         }   
00314         feedback_.names.clear();
00315         feedback_.confidence.clear();
00316         feedback_.names.push_back(goal_argument_);
00317 //      feedback_.confidence.push_back();
00318         as_.publishFeedback(feedback_);     
00319      }
00320      //goal is to recognize person in the video stream
00321      if( goal_id_<2 )      
00322     {
00323        int iNearest, nearest;
00324        float confidence;
00325        float * projectedTestFace=0;
00326        if(!frl.database_updated)
00327           //ROS_INFO("Alert: Database is not updated, You better (re)train from images!");       //BEFORE
00328          ROS_WARN("Alert: Database is not updated. Please delete \"facedata.xml\" and re-run!"); //AFTER
00329        if(frl.nEigens < 1) 
00330        {
00331           ROS_INFO("NO database available, goal is Aborted");
00332           cvReleaseImage(&equalizedImg);cvReleaseImage(&img);  
00333           ROS_INFO("Goal %d is Aborted",goal_id_);
00334           as_.setAborted(); 
00335           mutex_.unlock(); return;
00336        }
00337         // Project the test images onto the PCA subspace
00338        projectedTestFace = (float *)cvAlloc( frl.nEigens*sizeof(float) );
00339        // project the test image onto the PCA subspace
00340        cvEigenDecomposite(equalizedImg,frl.nEigens,frl.eigenVectArr,0, 0,frl.pAvgTrainImg,projectedTestFace);
00341        // Check which person it is most likely to be.
00342        iNearest = frl.findNearestNeighbor(projectedTestFace, &confidence);
00343        nearest  = frl.trainPersonNumMat->data.i[iNearest];
00344        //get the desired confidence value from the parameter server
00345        ros::param::getCached("~confidence_value", confidence_value);
00346        cvFree(&projectedTestFace);
00347        if(confidence<confidence_value)
00348        {
00349           ROS_INFO("Confidence is less than %f was %f, detected face is not considered.",(float)confidence_value, (float)confidence);
00350           if (show_screen_flag) {
00351             text_image.str("");
00352             text_image << "Confidence is less than "<< confidence_value;
00353             cvPutText(img, text_image.str().c_str(), cvPoint(faceRect.x, faceRect.y + faceRect.height + 25), &font, textColor);
00354           }
00355        }
00356        else
00357        {
00358          if (show_screen_flag) {
00359            text_image.str("");
00360            text_image <<  frl.personNames[nearest-1].c_str()<<" is recognized";
00361            cvPutText(img, text_image.str().c_str(), cvPoint(faceRect.x, faceRect.y + faceRect.height + 25), &font, textColor);
00362          }
00363           //goal is to recognize_once, therefore set as succeeded.
00364           if(goal_id_==0)
00365           {
00366              result_.names.push_back(frl.personNames[nearest-1].c_str());
00367              result_.confidence.push_back(confidence);
00368              as_.setSucceeded(result_);
00369           }
00370           //goal is recognize continuous, provide feedback and continue.
00371           else
00372           {
00373              ROS_INFO("detected %s  confidence %f ",  frl.personNames[nearest-1].c_str(),confidence);              
00374              feedback_.names.clear();
00375              feedback_.confidence.clear();
00376              feedback_.names.push_back(frl.personNames[nearest-1].c_str());
00377              feedback_.confidence.push_back(confidence);
00378              as_.publishFeedback(feedback_);                
00379           }
00380                              
00381        }
00382     
00383     }
00384     if (show_screen_flag)
00385     {
00386        cvShowImage("Input", img);
00387        cvWaitKey(1);
00388     } 
00389     cvReleaseImage(&equalizedImg);   cvReleaseImage(&img);
00390     r.sleep();
00391     mutex_.unlock();
00392     return;             
00393   }
00394 
00395 
00396 protected:
00397     
00398   boost::mutex mutex_; //for synchronization between executeCB and imageCB
00399   std::string goal_argument_; 
00400   int goal_id_;
00401   face_recognition::FaceRecognitionFeedback feedback_; 
00402   face_recognition::FaceRecognitionResult result_;
00403   int add_face_count; //help variable to count the number of training images already taken in the add_face_images goal
00404   FILE *trainFile; 
00405   double confidence_value;//a face recognized with confidence value higher than confidence_value threshold is accepted as valid.
00406   bool   show_screen_flag;//if output window is shown
00407   int    add_face_number; //the number of training images to be taken in add_face_images goal
00408   CvFont font;
00409   CvScalar textColor;
00410   ostringstream text_image;
00411   ros::NodeHandle nh_, pnh_;
00412   FaceRecognitionLib frl; 
00413   image_transport::ImageTransport it_;
00414   image_transport::Subscriber image_sub_;
00415   actionlib::SimpleActionServer<face_recognition::FaceRecognitionAction> as_;
00416   int person_number;     //the number of persons in the train file (train.txt)
00417 };
00418 
00419 
00420 int main(int argc, char** argv)
00421 {
00422   ros::init(argc, argv, "face_recognition");
00423   FaceRecognition face_recognition(ros::this_node::getName());
00424   ros::spin();
00425   return 0;
00426 }
00427 


face_recognition
Author(s): Pouyan Ziafati
autogenerated on Thu Jun 6 2019 18:29:21