FaceRecognizer supports updating a model now. Documentation has been updated to refle...
authorPhilipp Wagner <bytefish@gmx.de>
Mon, 30 Jul 2012 17:31:10 +0000 (19:31 +0200)
committerPhilipp Wagner <bytefish@gmx.de>
Mon, 30 Jul 2012 17:31:10 +0000 (19:31 +0200)
modules/contrib/doc/facerec/facerec_api.rst
modules/contrib/include/opencv2/contrib/contrib.hpp
modules/contrib/src/facerec.cpp

index 018a080..6766894 100644 (file)
@@ -19,16 +19,19 @@ a unified access to all face recongition algorithms in OpenCV. ::
 
       // Trains a FaceRecognizer.
       virtual void train(InputArray src, InputArray labels) = 0;
-
+      
+      // Updates a FaceRecognizer.
+      virtual void update(InputArrayOfArrays src, InputArray labels);
+      
       // Gets a prediction from a FaceRecognizer.
       virtual int predict(InputArray src) const = 0;
-
+      
       // Predicts the label and confidence for a given sample.
       virtual void predict(InputArray src, int &label, double &confidence) const = 0;
 
       // Serializes this object to a given filename.
       virtual void save(const string& filename) const;
-
+      
       // Deserializes this object from a given filename.
       virtual void load(const string& filename);
 
@@ -39,6 +42,7 @@ a unified access to all face recongition algorithms in OpenCV. ::
       virtual void load(const FileStorage& fs) = 0;
   };
 
+
 Description
 +++++++++++
 
@@ -99,13 +103,6 @@ If you've set the threshold to ``0.0`` as we did above, then:
 
 is going to yield ``-1`` as predicted label, which states this face is unknown.
 
-Adding new samples to a trained FaceRecognizer
-++++++++++++++++++++++++++++++++++++++++++++++
-
-Adding new images to a trained :ocv:class:`FaceRecognizer` is possible, but only if the :ocv:class:`FaceRecognizer` supports it. For the Eigenfaces and Fisherfaces method each call to :ocv:func:`FaceRecognizer::train` empties the old model and estimates a new model on the given data. This is an algorithmic necessity for these two algorithms, no way around that. Please see the tutorial Guide To Face Recognition with OpenCV for details. If you call :ocv:func:`FaceRecognizer::train` on a LBPH model, the internal model is extended with the new samples.
-
-Please note: A :ocv:class:`FaceRecognizer` does not store your training images (this would be very memory intense), the caller is responsible for maintaining the dataset.
-
 Getting the name of a FaceRecognizer
 +++++++++++++++++++++++++++++++++++++
 
@@ -164,6 +161,50 @@ And finally train it on the given dataset (the face images and labels):
     //
     model->train(images, labels);
 
+FaceRecognizer::update
+----------------------
+
+Updates a FaceRecognizer with given data and associated labels.
+
+.. ocv:function:: void FaceRecognizer::update(InputArray src, InputArray labels)
+
+    :param src: The training images, that means the faces you want to learn. The data has to be given as a ``vector<Mat>``.
+
+    :param labels: The labels corresponding to the images have to be given either as a ``vector<int>`` or a
+
+This method updates a (probably trained) :ocv:class:`FaceRecognizer`, but only if the algorithm supports it. The Local Binary Patterns Histograms (LBPH) recognizer (see :ocv:func:`createLBPHFaceRecognizer`) can be updated. For the Eigenfaces and Fisherfaces method, this is algorithmically not possible and you have to re-estimate the model with :ocv:func:`FaceRecognizer::train`. In any case, a call to train empties the existing model and learns a new model, while update does not delete any model data.
+
+.. code-block:: cpp
+
+    // Create a new LBPH model (it can be updated) and use the default parameters,
+    // this is the most common usage of this specific FaceRecognizer:
+    //
+    Ptr<FaceRecognizer> model =  createLBPHFaceRecognizer();
+    // This is the common interface to train all of the available cv::FaceRecognizer
+    // implementations:
+    //
+    model->train(images, labels);
+    // Some containers to hold new image:
+    vector<Mat> newImages;
+    vector<int> newLabels;
+    // You should add some images to the containers:
+    //
+    // ...
+    //
+    // Now updating the model is as easy as calling:
+    model->update(newImages,newLabels);
+    // This will preserve the old model data and extend the existing model 
+    // with the new features extracted from newImages!
+
+Calling update on an Eigenfaces model (see :ocv:func:`createEigenFaceRecognizer`), which doesn't support updating, will throw an error similar to:
+
+.. code-block:: none
+
+    OpenCV Error: The function/feature is not implemented (This FaceRecognizer (FaceRecognizer.Eigenfaces) does not support updating, you have to use FaceRecognizer::train to update it.) in update, file /home/philipp/git/opencv/modules/contrib/src/facerec.cpp, line 305
+    terminate called after throwing an instance of 'cv::Exception'
+
+Please note: The :ocv:class:`FaceRecognizer` does not store your training images, because this would be very memory intense and it's not the responsibility of te :ocv:class:`FaceRecognizer` to do so. The caller is responsible for maintaining the dataset, he want to work with. 
 FaceRecognizer::predict
 -----------------------
 
@@ -176,8 +217,6 @@ FaceRecognizer::predict
     :param label: The predicted label for the given image.
     :param confidence: Associated confidence (e.g. distance) for the predicted label.
 
-
-
 The suffix ``const`` means that prediction does not affect the internal model
 state, so the method can be safely called from within different threads.
 
@@ -260,7 +299,7 @@ Notes:
 
 * Training and prediction must be done on grayscale images, use :ocv:func:`cvtColor` to convert between the color spaces.
 * **THE EIGENFACES METHOD MAKES THE ASSUMPTION, THAT THE TRAINING AND TEST IMAGES ARE OF EQUAL SIZE.** (caps-lock, because I got so many mails asking for this). You have to make sure your input data has the correct shape, else a meaningful exception is thrown. Use :ocv:func:`resize` to resize the images.
-* A call to :ocv:func:`FaceRecognizer::train` empties the Eigenfaces model and re-estimates a model on given data.
+* This model does not support updating.
 
 Model internal data:
 ++++++++++++++++++++
@@ -287,7 +326,7 @@ Notes:
 
 * Training and prediction must be done on grayscale images, use :ocv:func:`cvtColor` to convert between the color spaces.
 * **THE FISHERFACES METHOD MAKES THE ASSUMPTION, THAT THE TRAINING AND TEST IMAGES ARE OF EQUAL SIZE.** (caps-lock, because I got so many mails asking for this). You have to make sure your input data has the correct shape, else a meaningful exception is thrown. Use :ocv:func:`resize` to resize the images.
-* A call to :ocv:func:`FaceRecognizer::train` empties the Fisherfaces model and re-estimates a model on given data.
+* This model does not support updating.
 
 Model internal data:
 ++++++++++++++++++++
@@ -316,7 +355,7 @@ Notes:
 ++++++
 
 * The Circular Local Binary Patterns (used in training and prediction) expect the data given as grayscale images, use :ocv:func:`cvtColor` to convert between the color spaces.
-* A call to :ocv:func:`FaceRecognizer::train` extends the LBPH model with given data.
+* This model supports updating.
 
 Model internal data:
 ++++++++++++++++++++
index 8800c3e..9f8ed9d 100644 (file)
@@ -927,6 +927,9 @@ namespace cv
         // Trains a FaceRecognizer.
         CV_WRAP virtual void train(InputArrayOfArrays src, InputArray labels) = 0;
 
+        // Updates a FaceRecognizer.
+        CV_WRAP virtual void update(InputArrayOfArrays src, InputArray labels);
+
         // Gets a prediction from a FaceRecognizer.
         virtual int predict(InputArray src) const = 0;
 
index 250706a..183338a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011. Philipp Wagner <bytefish[at]gmx[dot]de>.
+ * Copyright (c) 2011,2012. Philipp Wagner <bytefish[at]gmx[dot]de>.
  * Released to public domain under terms of the BSD Simplified license.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -197,10 +197,10 @@ public:
     void predict(InputArray _src, int &label, double &dist) const;
 
     // See FaceRecognizer::load.
-    virtual void load(const FileStorage& fs);
+    void load(const FileStorage& fs);
 
     // See FaceRecognizer::save.
-    virtual void save(FileStorage& fs) const;
+    void save(FileStorage& fs) const;
 
     AlgorithmInfo* info() const;
 };
@@ -223,6 +223,12 @@ private:
     vector<Mat> _histograms;
     Mat _labels;
 
+    // Computes a LBPH model with images in src and
+    // corresponding labels in labels, possibly preserving
+    // old model data.
+    void train(InputArrayOfArrays src, InputArray labels, bool preserveData);
+
+
 public:
     using FaceRecognizer::save;
     using FaceRecognizer::load;
@@ -265,6 +271,10 @@ public:
     // corresponding labels in labels.
     void train(InputArrayOfArrays src, InputArray labels);
 
+    // Updates this LBPH model with images in src and
+    // corresponding labels in labels.
+    void update(InputArrayOfArrays src, InputArray labels);
+
     // Predicts the label of a query image in src.
     int predict(InputArray src) const;
 
@@ -290,6 +300,11 @@ public:
 //------------------------------------------------------------------------------
 // FaceRecognizer
 //------------------------------------------------------------------------------
+void FaceRecognizer::update(InputArrayOfArrays, InputArray) {
+    string error_msg = format("This FaceRecognizer (%s) does not support updating, you have to use FaceRecognizer::train to update it.", this->name().c_str());
+    CV_Error(CV_StsNotImplemented, error_msg);
+}
+
 void FaceRecognizer::save(const string& filename) const {
     FileStorage fs(filename, FileStorage::WRITE);
     if (!fs.isOpened())
@@ -727,28 +742,45 @@ void LBPH::save(FileStorage& fs) const {
     fs << "labels" << _labels;
 }
 
-void LBPH::train(InputArrayOfArrays _src, InputArray _lbls) {
-    if(_src.kind() != _InputArray::STD_VECTOR_MAT && _src.kind() != _InputArray::STD_VECTOR_VECTOR) {
+void LBPH::train(InputArrayOfArrays _in_src, InputArray _in_labels) {
+    this->train(_in_src, _in_labels, false);
+}
+
+void LBPH::update(InputArrayOfArrays _in_src, InputArray _in_labels) {
+    // got no data, just return
+    if(_in_src.total() == 0)
+        return;
+
+    this->train(_in_src, _in_labels, true);
+}
+
+void LBPH::train(InputArrayOfArrays _in_src, InputArray _in_labels, bool preserveData) {
+    if(_in_src.kind() != _InputArray::STD_VECTOR_MAT && _in_src.kind() != _InputArray::STD_VECTOR_VECTOR) {
         string error_message = "The images are expected as InputArray::STD_VECTOR_MAT (a std::vector<Mat>) or _InputArray::STD_VECTOR_VECTOR (a std::vector< vector<...> >).";
         CV_Error(CV_StsBadArg, error_message);
     }
-    if(_src.total() == 0) {
+    if(_in_src.total() == 0) {
         string error_message = format("Empty training data was given. You'll need more than one sample to learn a model.");
         CV_Error(CV_StsUnsupportedFormat, error_message);
-    } else if(_lbls.getMat().type() != CV_32SC1) {
-        string error_message = format("Labels must be given as integer (CV_32SC1). Expected %d, but was %d.", CV_32SC1, _lbls.type());
+    } else if(_in_labels.getMat().type() != CV_32SC1) {
+        string error_message = format("Labels must be given as integer (CV_32SC1). Expected %d, but was %d.", CV_32SC1, _in_labels.type());
         CV_Error(CV_StsUnsupportedFormat, error_message);
     }
     // get the vector of matrices
     vector<Mat> src;
-    _src.getMatVector(src);
+    _in_src.getMatVector(src);
     // get the label matrix
-    Mat labels = _lbls.getMat();
+    Mat labels = _in_labels.getMat();
     // check if data is well- aligned
     if(labels.total() != src.size()) {
         string error_message = format("The number of samples (src) must equal the number of labels (labels). Was len(samples)=%d, len(labels)=%d.", src.size(), _labels.total());
         CV_Error(CV_StsBadArg, error_message);
     }
+    // if this model should be trained without preserving old data, delete old model data
+    if(!preserveData) {
+        _labels.release();
+        _histograms.clear();
+    }
     // append labels to _labels matrix
     for(size_t labelIdx = 0; labelIdx < labels.total(); labelIdx++) {
         _labels.push_back(labels.at<int>((int)labelIdx));