From ee1b671279e85bb4c988e175e86270be173c6fb7 Mon Sep 17 00:00:00 2001 From: Philipp Wagner Date: Sun, 10 Jun 2012 11:57:33 +0000 Subject: [PATCH] Several exceptions added to the available FaceRecognizer classes and helper methods, so wrong input data is reported to the user. facerec_demo.cpp updated to latest cv::Algorithm changes and commented. --- .../contrib/include/opencv2/contrib/contrib.hpp | 2 - modules/contrib/src/facerec.cpp | 191 +++++++++++++-------- modules/contrib/src/lda.cpp | 142 ++++++++++----- samples/cpp/facerec_demo.cpp | 89 +++++++--- 4 files changed, 282 insertions(+), 142 deletions(-) diff --git a/modules/contrib/include/opencv2/contrib/contrib.hpp b/modules/contrib/include/opencv2/contrib/contrib.hpp index 875dd89..6a95b7a 100644 --- a/modules/contrib/include/opencv2/contrib/contrib.hpp +++ b/modules/contrib/include/opencv2/contrib/contrib.hpp @@ -942,8 +942,6 @@ namespace cv // Deserializes this object from a given cv::FileStorage. virtual void load(const FileStorage& fs) = 0; - // Returns eigenvectors (if any) - virtual Mat eigenvectors() const { return Mat(); } }; CV_EXPORTS Ptr createEigenFaceRecognizer(int num_components = 0); diff --git a/modules/contrib/src/facerec.cpp b/modules/contrib/src/facerec.cpp index a77f606..6d61fb3 100644 --- a/modules/contrib/src/facerec.cpp +++ b/modules/contrib/src/facerec.cpp @@ -22,7 +22,7 @@ namespace cv { using std::set; - + // Reads a sequence from a FileNode::SEQ with type _Tp into a result vector. template inline void readFileNodeList(const FileNode& fn, vector<_Tp>& result) { @@ -48,26 +48,42 @@ inline void writeFileNodeList(FileStorage& fs, const string& name, } fs << "]"; } - -static Mat asRowMatrix(InputArrayOfArrays src, int rtype, double alpha=1, double beta=0) -{ + +static Mat asRowMatrix(InputArrayOfArrays src, int rtype, double alpha=1, double beta=0) { + // make sure the input data is a vector of matrices or vector of vector + if(src.kind() != _InputArray::STD_VECTOR_MAT && src.kind() != _InputArray::STD_VECTOR_VECTOR) { + string error_message = "The data is expected as InputArray::STD_VECTOR_MAT (a std::vector) or _InputArray::STD_VECTOR_VECTOR (a std::vector< vector<...> >)."; + error(Exception(CV_StsBadArg, error_message, "asRowMatrix", __FILE__, __LINE__)); + } // number of samples - int n = (int) src.total(); - // return empty matrix if no data given + size_t n = src.total(); + // return empty matrix if no matrices given if(n == 0) return Mat(); - // dimensionality of samples - int d = (int)src.getMat(0).total(); + // dimensionality of (reshaped) samples + size_t d = src.getMat(0).total(); // create data matrix Mat data(n, d, rtype); - // copy data - for(int i = 0; i < n; i++) { + // now copy data + for(unsigned int i = 0; i < n; i++) { + // make sure data can be reshaped, throw exception if not! + if(src.getMat(i).total() != d) { + string error_message = format("Wrong number of elements in matrix #%d! Expected %d was %d.", i, d, src.getMat(i).total()); + error(Exception(CV_StsBadArg, error_message, "cv::asRowMatrix", __FILE__, __LINE__)); + } + // get a hold of the current row Mat xi = data.row(i); - src.getMat(i).reshape(1, 1).convertTo(xi, rtype, alpha, beta); + // make reshape happy by cloning for non-continuous matrices + if(src.getMat(i).isContinuous()) { + src.getMat(i).reshape(1, 1).convertTo(xi, rtype, alpha, beta); + } else { + src.getMat(i).clone().reshape(1, 1).convertTo(xi, rtype, alpha, beta); + } } return data; } + // Removes duplicate elements in a given vector. template inline vector<_Tp> remove_dups(const vector<_Tp>& src) { @@ -82,7 +98,7 @@ inline vector<_Tp> remove_dups(const vector<_Tp>& src) { return elems; } - + // Turk, M., and Pentland, A. "Eigenfaces for recognition.". Journal of // Cognitive Neuroscience 3 (1991), 71–86. class Eigenfaces : public FaceRecognizer @@ -124,10 +140,10 @@ public: // See FaceRecognizer::save. void save(FileStorage& fs) const; - + AlgorithmInfo* info() const; }; - + // Belhumeur, P. N., Hespanha, J., and Kriegman, D. "Eigenfaces vs. Fisher- // faces: Recognition using class specific linear projection.". IEEE // Transactions on Pattern Analysis and Machine Intelligence 19, 7 (1997), @@ -160,7 +176,7 @@ public: train(src, labels); } - ~Fisherfaces() { } + ~Fisherfaces() {} // Computes a Fisherfaces model with images in src and corresponding labels // in labels. @@ -180,10 +196,6 @@ public: // Face Recognition based on Local Binary Patterns. // -// TODO Allow to change the distance metric. -// TODO Allow to change LBP computation (Extended LBP used right now). -// TODO Optimize, Optimize, Optimize! -// // Ahonen T, Hadid A. and Pietikäinen M. "Face description with local binary // patterns: Application to face recognition." IEEE Transactions on Pattern // Analysis and Machine Intelligence, 28(12):2037-2041. @@ -208,11 +220,11 @@ public: // // radius, neighbors are used in the local binary patterns creation. // grid_x, grid_y control the grid size of the spatial histograms. - LBPH(int radius_=1, int neighbors_=8, int grid_x_=8, int grid_y_=8) : - _grid_x(grid_x_), - _grid_y(grid_y_), - _radius(radius_), - _neighbors(neighbors_) {} + LBPH(int radius=1, int neighbors=8, int grid_x=8, int grid_y=8) : + _grid_x(grid_x), + _grid_y(grid_y), + _radius(radius), + _neighbors(neighbors) {} // Initializes and computes this LBPH Model. The current implementation is // rather fixed as it uses the Extended Local Binary Patterns per default. @@ -221,12 +233,12 @@ public: // (grid_x=8), (grid_y=8) controls the grid size of the spatial histograms. LBPH(InputArray src, InputArray labels, - int radius_=1, int neighbors_=8, - int grid_x_=8, int grid_y_=8) : - _grid_x(grid_x_), - _grid_y(grid_y_), - _radius(radius_), - _neighbors(neighbors_) { + int radius=1, int neighbors=8, + int grid_x=8, int grid_y=8) : + _grid_x(grid_x), + _grid_y(grid_y), + _radius(radius), + _neighbors(neighbors) { train(src, labels); } @@ -278,22 +290,25 @@ void FaceRecognizer::load(const string& filename) { //------------------------------------------------------------------------------ // Eigenfaces //------------------------------------------------------------------------------ -void Eigenfaces::train(InputArray src, InputArray _lbls) { - // assert type - if(_lbls.getMat().type() != CV_32SC1) - CV_Error(CV_StsUnsupportedFormat, "Labels must be given as integer (CV_32SC1)."); +void Eigenfaces::train(InputArray _src, InputArray _local_labels) { + if(_src.total() == 0) { + string error_message = format("Empty training data was given. You'll need more than one sample to learn a model."); + error(Exception(CV_StsUnsupportedFormat, error_message, "Eigenfaces::train", __FILE__, __LINE__)); + } else if(_local_labels.getMat().type() != CV_32SC1) { + string error_message = format("Labels must be given as integer (CV_32SC1). Expected %d, but was %d.", CV_32SC1, _local_labels.type()); + error(Exception(CV_StsUnsupportedFormat, error_message, "Eigenfaces::train", __FILE__, __LINE__)); + } // get labels - Mat labels = _lbls.getMat(); - CV_Assert( labels.type() == CV_32S && (labels.cols == 1 || labels.rows == 1)); + Mat labels = _local_labels.getMat(); // observations in row - Mat data = asRowMatrix(src, CV_64FC1); + Mat data = asRowMatrix(_src, CV_64FC1); // number of samples - int n = data.rows; - // dimensionality of data - //int d = data.cols; + int n = data.rows; // assert there are as much samples as labels - if((size_t)n != labels.total()) - CV_Error(CV_StsBadArg, "The number of samples must equal the number of labels!"); + if(static_cast(labels.total()) != n) { + string error_message = format("The number of samples (src) must equal the number of labels (labels)! len(src)=%d, len(labels)=%d.", n, labels.total()); + error(Exception(CV_StsBadArg, error_message, "Eigenfaces::train", __FILE__, __LINE__)); + } // clip number of components to be valid if((_num_components <= 0) || (_num_components > n)) _num_components = n; @@ -307,13 +322,23 @@ void Eigenfaces::train(InputArray src, InputArray _lbls) { // save projections for(int sampleIdx = 0; sampleIdx < data.rows; sampleIdx++) { Mat p = subspaceProject(_eigenvectors, _mean, data.row(sampleIdx)); - this->_projections.push_back(p); + _projections.push_back(p); } } int Eigenfaces::predict(InputArray _src) const { // get data Mat src = _src.getMat(); + // make sure the user is passing correct data + if(_projections.empty()) { + // throw error if no data (or simply return -1?) + string error_message = "This Eigenfaces model is not computed yet. Did you call Eigenfaces::train?"; + error(cv::Exception(CV_StsError, error_message, "Eigenfaces::predict", __FILE__, __LINE__)); + } else if(_eigenvectors.rows != static_cast(src.total())) { + // check data alignment just for clearer exception messages + string error_message = format("Wrong input image size. Reason: Training and Test images must be of equal size! Expected an image with %d elements, but got %d.", _eigenvectors.rows, src.total()); + error(cv::Exception(CV_StsError, error_message, "Eigenfaces::predict", __FILE__, __LINE__)); + } // project into PCA subspace Mat q = subspaceProject(_eigenvectors, _mean, src.reshape(1,1)); double minDist = DBL_MAX; @@ -354,25 +379,31 @@ void Eigenfaces::save(FileStorage& fs) const { // Fisherfaces //------------------------------------------------------------------------------ void Fisherfaces::train(InputArray src, InputArray _lbls) { - if(_lbls.getMat().type() != CV_32SC1) - CV_Error(CV_StsUnsupportedFormat, "Labels must be given as integer (CV_32SC1)."); + if(src.total() == 0) { + string error_message = format("Empty training data was given. You'll need more than one sample to learn a model."); + error(cv::Exception(CV_StsUnsupportedFormat, error_message, "cv::Eigenfaces::train", __FILE__, __LINE__)); + } 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()); + error(cv::Exception(CV_StsUnsupportedFormat, error_message, "cv::Fisherfaces::train", __FILE__, __LINE__)); + } // get data Mat labels = _lbls.getMat(); Mat data = asRowMatrix(src, CV_64FC1); - - CV_Assert( labels.type() == CV_32S && (labels.cols == 1 || labels.rows == 1)); - - // dimensionality - int N = data.rows; // number of samples - //int D = data.cols; // dimension of samples - // assert correct data alignment - if(labels.total() != (size_t)N) - CV_Error(CV_StsUnsupportedFormat, "Labels must be given as integer (CV_32SC1)."); - // compute the Fisherfaces - + // number of samples + int N = data.rows; + // make sure labels are passed in correct shape + if(labels.total() != (size_t) N) { + string error_message = format("The number of samples (src) must equal the number of labels (labels)! len(src)=%d, len(labels)=%d.", N, labels.total()); + error(cv::Exception(CV_StsBadArg, error_message, "Fisherfaces::train", __FILE__, __LINE__)); + } else if(labels.rows != 1 && labels.cols != 1) { + string error_message = format("Expected the labels in a matrix with one row or column! Given dimensions are rows=%s, cols=%d.", labels.rows, labels.cols); + error(cv::Exception(CV_StsBadArg, error_message, "Fisherfaces::train", __FILE__, __LINE__)); + } + // Get the number of unique classes + // TODO Provide a cv::Mat version? vector ll; labels.copyTo(ll); - int C = (int)remove_dups(ll).size(); // number of unique classes + int C = (int) remove_dups(ll).size(); // clip number of components to be a valid number if((_num_components <= 0) || (_num_components > (C-1))) _num_components = (C-1); @@ -398,6 +429,15 @@ void Fisherfaces::train(InputArray src, InputArray _lbls) { int Fisherfaces::predict(InputArray _src) const { Mat src = _src.getMat(); + // check data alignment just for clearer exception messages + if(_projections.empty()) { + // throw error if no data (or simply return -1?) + string error_message = "This Fisherfaces model is not computed yet. Did you call Fisherfaces::train?"; + error(cv::Exception(CV_StsError, error_message, "Fisherfaces::predict", __FILE__, __LINE__)); + } else if(src.total() != (size_t) _eigenvectors.rows) { + string error_message = format("Wrong input image size. Reason: Training and Test images must be of equal size! Expected an image with %d elements, but got %d.", _eigenvectors.rows, src.total()); + error(cv::Exception(CV_StsError, error_message, "Fisherfaces::predict", __FILE__, __LINE__)); + } // project into LDA subspace Mat q = subspaceProject(_eigenvectors, _mean, src.reshape(1,1)); // find 1-nearest neighbor @@ -531,7 +571,7 @@ histc_(const Mat& src, int minVal=0, int maxVal=255, bool normed=false) // Establish the number of bins. int histSize = maxVal-minVal+1; // Set the ranges. - float range[] = { static_cast(minVal), static_cast(maxVal) }; + float range[] = { static_cast(minVal), static_cast(maxVal+1) }; const float* histRange = { range }; // calc histogram calcHist(&src, 1, 0, Mat(), result, 1, &histSize, &histRange, true, false); @@ -570,7 +610,7 @@ static Mat histc(InputArray _src, int minVal, int maxVal, bool normed) return Mat(); } - + static Mat spatial_histogram(InputArray _src, int numPatterns, int grid_x, int grid_y, bool normed) { @@ -602,7 +642,7 @@ static Mat spatial_histogram(InputArray _src, int numPatterns, } //------------------------------------------------------------------------------ -// cv::elbp, cv::olbp, cv::varlbp wrapper +// wrapper to cv::elbp (extended local binary patterns) //------------------------------------------------------------------------------ static Mat elbp(InputArray src, int radius, int neighbors) { @@ -610,7 +650,7 @@ static Mat elbp(InputArray src, int radius, int neighbors) { elbp(src, dst, radius, neighbors); return dst; } - + void LBPH::load(const FileStorage& fs) { fs["radius"] >> _radius; fs["neighbors"] >> _neighbors; @@ -633,8 +673,16 @@ void LBPH::save(FileStorage& fs) const { } void LBPH::train(InputArray _src, InputArray _lbls) { - if(_src.kind() != _InputArray::STD_VECTOR_MAT && _src.kind() != _InputArray::STD_VECTOR_VECTOR) - CV_Error(CV_StsUnsupportedFormat, "LBPH::train expects InputArray::STD_VECTOR_MAT or _InputArray::STD_VECTOR_VECTOR."); + if(_src.kind() != _InputArray::STD_VECTOR_MAT && _src.kind() != _InputArray::STD_VECTOR_VECTOR) { + string error_message = "The images are expected as InputArray::STD_VECTOR_MAT (a std::vector) or _InputArray::STD_VECTOR_VECTOR (a std::vector< vector<...> >)."; + error(Exception(CV_StsBadArg, error_message, "LBPH::train", __FILE__, __LINE__)); + } else if(_src.total() == 0) { + string error_message = format("Empty training data was given. You'll need more than one sample to learn a model."); + error(Exception(CV_StsUnsupportedFormat, error_message, "LBPH::train", __FILE__, __LINE__)); + } 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()); + error(Exception(CV_StsUnsupportedFormat, error_message, "LBPH::train", __FILE__, __LINE__)); + } // get the vector of matrices vector src; _src.getMatVector(src); @@ -661,7 +709,6 @@ void LBPH::train(InputArray _src, InputArray _lbls) { } } - int LBPH::predict(InputArray _src) const { Mat src = _src.getMat(); // get the spatial histogram from input image @@ -684,24 +731,24 @@ int LBPH::predict(InputArray _src) const { } return minClass; } - - + + Ptr createEigenFaceRecognizer(int num_components) { return new Eigenfaces(num_components); } - + Ptr createFisherFaceRecognizer(int num_components) { return new Fisherfaces(num_components); } - + Ptr createLBPHFaceRecognizer(int radius, int neighbors, int grid_x, int grid_y) { return new LBPH(radius, neighbors, grid_x, grid_y); } - + CV_INIT_ALGORITHM(Eigenfaces, "FaceRecognizer.Eigenfaces", obj.info()->addParam(obj, "ncomponents", obj._num_components); obj.info()->addParam(obj, "projections", obj._projections, true); @@ -716,8 +763,8 @@ CV_INIT_ALGORITHM(Fisherfaces, "FaceRecognizer.Fisherfaces", obj.info()->addParam(obj, "labels", obj._labels, true); obj.info()->addParam(obj, "eigenvectors", obj._eigenvectors, true); obj.info()->addParam(obj, "eigenvalues", obj._eigenvalues, true); - obj.info()->addParam(obj, "mean", obj._mean, true)); - + obj.info()->addParam(obj, "mean", obj._mean, true)); + CV_INIT_ALGORITHM(LBPH, "FaceRecognizer.LBPH", obj.info()->addParam(obj, "radius", obj._radius); obj.info()->addParam(obj, "neighbors", obj._neighbors); @@ -725,7 +772,7 @@ CV_INIT_ALGORITHM(LBPH, "FaceRecognizer.LBPH", obj.info()->addParam(obj, "grid_y", obj._grid_y); obj.info()->addParam(obj, "histograms", obj._histograms, true); obj.info()->addParam(obj, "labels", obj._labels, true)); - + bool initModule_contrib() { Ptr efaces = createEigenfaces(), ffaces = createFisherfaces(), lbph = createLBPH(); diff --git a/modules/contrib/src/lda.cpp b/modules/contrib/src/lda.cpp index 0c95382..f3864ea 100644 --- a/modules/contrib/src/lda.cpp +++ b/modules/contrib/src/lda.cpp @@ -46,29 +46,46 @@ inline vector<_Tp> remove_dups(const vector<_Tp>& src) { static Mat argsort(InputArray _src, bool ascending=true) { Mat src = _src.getMat(); - if (src.rows != 1 && src.cols != 1) - CV_Error(CV_StsBadArg, "cv::argsort only sorts 1D matrices."); + if (src.rows != 1 && src.cols != 1) { + string error_message = "Wrong shape of input matrix! Expected a matrix with one row or column."; + error(cv::Exception(CV_StsBadArg, error_message, "argsort", __FILE__, __LINE__)); + } int flags = CV_SORT_EVERY_ROW+(ascending ? CV_SORT_ASCENDING : CV_SORT_DESCENDING); Mat sorted_indices; sortIdx(src.reshape(1,1),sorted_indices,flags); return sorted_indices; } -static Mat asRowMatrix(InputArrayOfArrays src, int rtype, double alpha=1, double beta=0) -{ +static Mat asRowMatrix(InputArrayOfArrays src, int rtype, double alpha=1, double beta=0) { + // make sure the input data is a vector of matrices or vector of vector + if(src.kind() != _InputArray::STD_VECTOR_MAT && src.kind() != _InputArray::STD_VECTOR_VECTOR) { + string error_message = "The data is expected as InputArray::STD_VECTOR_MAT (a std::vector) or _InputArray::STD_VECTOR_VECTOR (a std::vector< vector<...> >)."; + error(cv::Exception(CV_StsBadArg, error_message, "asRowMatrix", __FILE__, __LINE__)); + } // number of samples - int n = (int) src.total(); - // return empty matrix if no data given + size_t n = src.total(); + // return empty matrix if no matrices given if(n == 0) return Mat(); - // dimensionality of samples - int d = (int)src.getMat(0).total(); + // dimensionality of (reshaped) samples + size_t d = src.getMat(0).total(); // create data matrix Mat data(n, d, rtype); - // copy data - for(int i = 0; i < n; i++) { + // now copy data + for(size_t i = 0; i < n; i++) { + // make sure data can be reshaped, throw exception if not! + if(src.getMat(i).total() != d) { + string error_message = format("Wrong number of elements in matrix #%d! Expected %d was %d.", i, d, src.getMat(i).total()); + error(cv::Exception(CV_StsBadArg, error_message, "cv::asRowMatrix", __FILE__, __LINE__)); + } + // get a hold of the current row Mat xi = data.row(i); - src.getMat(i).reshape(1, 1).convertTo(xi, rtype, alpha, beta); + // make reshape happy by cloning for non-continuous matrices + if(src.getMat(i).isContinuous()) { + src.getMat(i).reshape(1, 1).convertTo(xi, rtype, alpha, beta); + } else { + src.getMat(i).clone().reshape(1, 1).convertTo(xi, rtype, alpha, beta); + } } return data; } @@ -153,31 +170,44 @@ static bool isSymmetric(InputArray src, double eps=1e-16) //------------------------------------------------------------------------------ -// subspace::project +// cv::subspaceProject //------------------------------------------------------------------------------ -Mat subspaceProject(InputArray _W, InputArray _mean, InputArray _src) -{ +Mat subspaceProject(InputArray _W, InputArray _mean, InputArray _src) { // get data matrices Mat W = _W.getMat(); Mat mean = _mean.getMat(); Mat src = _src.getMat(); + // get number of samples and dimension + int n = src.rows; + int d = src.cols; + // make sure the data has the correct shape + if(W.rows != d) { + string error_message = format("Wrong shapes for given matrices. Was size(src) = (%d,%d), size(W) = (%d,%d).", src.rows, src.cols, W.rows, W.cols); + error(cv::Exception(CV_StsBadArg, error_message, "cv::subspace::project", __FILE__, __LINE__)); + } + // make sure mean is correct if not empty + if(!mean.empty() && (mean.total() != (size_t) d)) { + string error_message = format("Wrong mean shape for the given data matrix. Expected %d, but was %d.", d, mean.total()); + error(cv::Exception(CV_StsBadArg, error_message, "cv::subspace::project", __FILE__, __LINE__)); + } // create temporary matrices Mat X, Y; - // copy data & make sure we are using the correct type + // make sure you operate on correct type src.convertTo(X, W.type()); - // get number of samples and dimension - int n = X.rows; - int d = X.cols; - // center the data if correct aligned sample mean is given - if(mean.total() == (size_t)d) - subtract(X, repeat(mean.reshape(1,1), n, 1), X); + // safe to do, because of above assertion + if(!mean.empty()) { + for(int i=0; isave(fs); fs.release(); } @@ -942,25 +987,35 @@ void LDA::lda(InputArray _src, InputArray _lbls) { vector num2label = remove_dups(labels); map label2num; for (size_t i = 0; i < num2label.size(); i++) - label2num[num2label[i]] = (int)i; + label2num[num2label[i]] = i; for (size_t i = 0; i < labels.size(); i++) mapped_labels[i] = label2num[labels[i]]; // get sample size, dimension int N = data.rows; int D = data.cols; // number of unique labels - int C = (int)num2label.size(); + int C = num2label.size(); + // we can't do a LDA on one class, what do you + // want to separate from each other then? + if(C == 1) { + string error_message = "At least two classes are needed to perform a LDA. Reason: Only one class was given!"; + error(cv::Exception(CV_StsBadArg, error_message, "cv::LDA::lda", __FILE__, __LINE__)); + } // throw error if less labels, than samples - if (labels.size() != (size_t)N) - CV_Error(CV_StsBadArg, "Error: The number of samples must equal the number of labels."); + if (labels.size() != static_cast(N)) { + string error_message = format("The number of samples must equal the number of labels. Given %d labels, %d samples. ", labels.size(), N); + error(cv::Exception(CV_StsBadArg, error_message, "LDA::lda", __FILE__, __LINE__)); + } // warn if within-classes scatter matrix becomes singular - if (N < D) + if (N < D) { cout << "Warning: Less observations than feature dimension given!" - << "Computation will probably fail." - << endl; + << "Computation will probably fail." + << endl; + } // clip number of components to be a valid number - if ((_num_components <= 0) || (_num_components > (C - 1))) + if ((_num_components <= 0) || (_num_components > (C - 1))) { _num_components = (C - 1); + } // holds the mean over all classes Mat meanTotal = Mat::zeros(1, D, data.type()); // holds the mean for each class @@ -979,12 +1034,12 @@ void LDA::lda(InputArray _src, InputArray _lbls) { add(meanClass[classIdx], instance, meanClass[classIdx]); numClass[classIdx]++; } - // calculate means - meanTotal.convertTo(meanTotal, meanTotal.type(), - 1.0 / static_cast (N)); - for (int i = 0; i < C; i++) - meanClass[i].convertTo(meanClass[i], meanClass[i].type(), - 1.0 / static_cast (numClass[i])); + // calculate total mean + meanTotal.convertTo(meanTotal, meanTotal.type(), 1.0 / static_cast (N)); + // calculate class means + for (int i = 0; i < C; i++) { + meanClass[i].convertTo(meanClass[i], meanClass[i].type(), 1.0 / static_cast (numClass[i])); + } // subtract class means for (int i = 0; i < N; i++) { int classIdx = mapped_labels[i]; @@ -1031,7 +1086,8 @@ void LDA::compute(InputArray _src, InputArray _lbls) { lda(_src.getMat(), _lbls); break; default: - CV_Error(CV_StsNotImplemented, "This data type is not supported by subspace::LDA::compute."); + string error_message= format("InputArray Datatype %d is not supported.", _src.kind()); + error(cv::Exception(CV_StsBadArg, error_message, "LDA::compute", __FILE__, __LINE__)); break; } } diff --git a/samples/cpp/facerec_demo.cpp b/samples/cpp/facerec_demo.cpp index 0b238ef..c329cc4 100644 --- a/samples/cpp/facerec_demo.cpp +++ b/samples/cpp/facerec_demo.cpp @@ -16,7 +16,9 @@ * See */ -#include "opencv2/opencv.hpp" +#include "opencv2/core/core.hpp" +#include "opencv2/highgui/highgui.hpp" +#include "opencv2/contrib/contrib.hpp" #include #include @@ -38,65 +40,102 @@ static Mat toGrayscale(InputArray _src) { static void read_csv(const string& filename, vector& images, vector& labels, char separator = ';') { std::ifstream file(filename.c_str(), ifstream::in); - if (!file) - throw std::exception(); + if (!file) { + string error_message = "No valid input file was given, please check the given filename."; + CV_Error(CV_StsBadArg, error_message); + } string line, path, classlabel; while (getline(file, line)) { stringstream liness(line); getline(liness, path, separator); getline(liness, classlabel); - images.push_back(imread(path, 0)); - labels.push_back(atoi(classlabel.c_str())); + if(!path.empty() && !classlabel.empty()) { + images.push_back(imread(path, 0)); + labels.push_back(atoi(classlabel.c_str())); + } } } int main(int argc, const char *argv[]) { - // check for command line arguments + // Check for valid command line arguments, print usage + // if no arguments were given. if (argc != 2) { cout << "usage: " << argv[0] << " " << endl; exit(1); } - // path to your CSV + // Get the path to your CSV. string fn_csv = string(argv[1]); - // images and corresponding labels + // These vectors hold the images and corresponding labels. vector images; vector labels; - // read in the data + // Read in the data. This can fail if no valid + // input filename is given. try { read_csv(fn_csv, images, labels); - } catch (exception&) { - cerr << "Error opening file \"" << fn_csv << "\"." << endl; + } catch (cv::Exception& e) { + cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl; + // nothing more we can do exit(1); } - // get width and height - //int width = images[0].cols; + // Quit if there are not enough images for this demo. + if(images.size() <= 1) { + string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!"; + CV_Error(CV_StsError, error_message); + } + // Get the height from the first image. We'll need this + // later in code to reshape the images to their original + // size: int height = images[0].rows; - // get test instances + // The following lines simply get the last images from + // your dataset and remove it from the vector. This is + // done, so that the training data (which we learn the + // cv::FaceRecognizer on) and the test data we test + // the model with, do not overlap. Mat testSample = images[images.size() - 1]; int testLabel = labels[labels.size() - 1]; - // ... and delete last element images.pop_back(); labels.pop_back(); - // build the Fisherfaces model - Ptr model = createFisherFaceRecognizer(); + // The following lines create an Eigenfaces model for + // face recognition and train it with the images and + // labels read from the given CSV file. + // This here is a full PCA, if you just want to keep + // 10 principal components (read Eigenfaces), then call + // the factory method like this: + // + // cv::createEigenFaceRecognizer(10); + Ptr model = createEigenFaceRecognizer(); model->train(images, labels); - // test model + // The following line predicts the label of a given + // test image. In this example no thresholding is + // done. int predicted = model->predict(testSample); - cout << "predicted class = " << predicted << endl; - cout << "actual class = " << testLabel << endl; - // get the eigenvectors - Mat W = model->eigenvectors(); - // show first 10 fisherfaces + // Show the prediction and actual class of the given + // sample: + string result_message = format("Predicted class=%d / Actual class=%d.", predicted, testLabel); + cout << result_message << endl; + // Sometimes you'll need to get some internal model data, + // which isn't exposed by the public cv::FaceRecognizer. + // Since each cv::FaceRecognizer is derived from a + // cv::Algorithm, you can query the data. + // + // Here is how to get the eigenvalues of this Eigenfaces model: + Mat eigenvalues = model->getMat("eigenvalues"); + // And we can do the same to display the Eigenvectors ("Eigenfaces"): + Mat W = model->getMat("eigenvectors"); + // From this we will display the (at most) first 10 Eigenfaces: for (int i = 0; i < min(10, W.cols); i++) { + string msg = format("Eigenvalue #%d = %.5f", i, eigenvalues.at(i)); + cout << msg << endl; // get eigenvector #i Mat ev = W.col(i).clone(); - // reshape to original size AND normalize between [0...255] + // Reshape to original size & normalize to [0...255] for imshow. Mat grayscale = toGrayscale(ev.reshape(1, height)); - // show image (with Jet colormap) + // Show the image & apply a Jet colormap for better sensing. Mat cgrayscale; applyColorMap(grayscale, cgrayscale, COLORMAP_JET); imshow(format("%d", i), cgrayscale); } waitKey(0); + return 0; } -- 2.7.4