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.

pull/2/head
Philipp Wagner 13 years ago
parent 6727e4cb6d
commit ee1b671279
  1. 2
      modules/contrib/include/opencv2/contrib/contrib.hpp
  2. 161
      modules/contrib/src/facerec.cpp
  3. 142
      modules/contrib/src/lda.cpp
  4. 89
      samples/cpp/facerec_demo.cpp

@ -942,8 +942,6 @@ namespace cv
// Deserializes this object from a given cv::FileStorage. // Deserializes this object from a given cv::FileStorage.
virtual void load(const FileStorage& fs) = 0; virtual void load(const FileStorage& fs) = 0;
// Returns eigenvectors (if any)
virtual Mat eigenvectors() const { return Mat(); }
}; };
CV_EXPORTS Ptr<FaceRecognizer> createEigenFaceRecognizer(int num_components = 0); CV_EXPORTS Ptr<FaceRecognizer> createEigenFaceRecognizer(int num_components = 0);

@ -49,25 +49,41 @@ inline void writeFileNodeList(FileStorage& fs, const string& name,
fs << "]"; 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<Mat>) or _InputArray::STD_VECTOR_VECTOR (a std::vector< vector<...> >).";
error(Exception(CV_StsBadArg, error_message, "asRowMatrix", __FILE__, __LINE__));
}
// number of samples // number of samples
int n = (int) src.total(); size_t n = src.total();
// return empty matrix if no data given // return empty matrix if no matrices given
if(n == 0) if(n == 0)
return Mat(); return Mat();
// dimensionality of samples // dimensionality of (reshaped) samples
int d = (int)src.getMat(0).total(); size_t d = src.getMat(0).total();
// create data matrix // create data matrix
Mat data(n, d, rtype); Mat data(n, d, rtype);
// copy data // now copy data
for(int i = 0; i < n; i++) { 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); 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; return data;
} }
// Removes duplicate elements in a given vector. // Removes duplicate elements in a given vector.
template<typename _Tp> template<typename _Tp>
inline vector<_Tp> remove_dups(const vector<_Tp>& src) { inline vector<_Tp> remove_dups(const vector<_Tp>& src) {
@ -160,7 +176,7 @@ public:
train(src, labels); train(src, labels);
} }
~Fisherfaces() { } ~Fisherfaces() {}
// Computes a Fisherfaces model with images in src and corresponding labels // Computes a Fisherfaces model with images in src and corresponding labels
// in labels. // in labels.
@ -180,10 +196,6 @@ public:
// Face Recognition based on Local Binary Patterns. // 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 // Ahonen T, Hadid A. and Pietikäinen M. "Face description with local binary
// patterns: Application to face recognition." IEEE Transactions on Pattern // patterns: Application to face recognition." IEEE Transactions on Pattern
// Analysis and Machine Intelligence, 28(12):2037-2041. // Analysis and Machine Intelligence, 28(12):2037-2041.
@ -208,11 +220,11 @@ public:
// //
// radius, neighbors are used in the local binary patterns creation. // radius, neighbors are used in the local binary patterns creation.
// grid_x, grid_y control the grid size of the spatial histograms. // 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) : LBPH(int radius=1, int neighbors=8, int grid_x=8, int grid_y=8) :
_grid_x(grid_x_), _grid_x(grid_x),
_grid_y(grid_y_), _grid_y(grid_y),
_radius(radius_), _radius(radius),
_neighbors(neighbors_) {} _neighbors(neighbors) {}
// Initializes and computes this LBPH Model. The current implementation is // Initializes and computes this LBPH Model. The current implementation is
// rather fixed as it uses the Extended Local Binary Patterns per default. // 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. // (grid_x=8), (grid_y=8) controls the grid size of the spatial histograms.
LBPH(InputArray src, LBPH(InputArray src,
InputArray labels, InputArray labels,
int radius_=1, int neighbors_=8, int radius=1, int neighbors=8,
int grid_x_=8, int grid_y_=8) : int grid_x=8, int grid_y=8) :
_grid_x(grid_x_), _grid_x(grid_x),
_grid_y(grid_y_), _grid_y(grid_y),
_radius(radius_), _radius(radius),
_neighbors(neighbors_) { _neighbors(neighbors) {
train(src, labels); train(src, labels);
} }
@ -278,22 +290,25 @@ void FaceRecognizer::load(const string& filename) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Eigenfaces // Eigenfaces
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void Eigenfaces::train(InputArray src, InputArray _lbls) { void Eigenfaces::train(InputArray _src, InputArray _local_labels) {
// assert type if(_src.total() == 0) {
if(_lbls.getMat().type() != CV_32SC1) string error_message = format("Empty training data was given. You'll need more than one sample to learn a model.");
CV_Error(CV_StsUnsupportedFormat, "Labels must be given as integer (CV_32SC1)."); 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 // get labels
Mat labels = _lbls.getMat(); Mat labels = _local_labels.getMat();
CV_Assert( labels.type() == CV_32S && (labels.cols == 1 || labels.rows == 1));
// observations in row // observations in row
Mat data = asRowMatrix(src, CV_64FC1); Mat data = asRowMatrix(_src, CV_64FC1);
// number of samples // number of samples
int n = data.rows; int n = data.rows;
// dimensionality of data
//int d = data.cols;
// assert there are as much samples as labels // assert there are as much samples as labels
if((size_t)n != labels.total()) if(static_cast<int>(labels.total()) != n) {
CV_Error(CV_StsBadArg, "The number of samples must equal the number of labels!"); 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 // clip number of components to be valid
if((_num_components <= 0) || (_num_components > n)) if((_num_components <= 0) || (_num_components > n))
_num_components = n; _num_components = n;
@ -307,13 +322,23 @@ void Eigenfaces::train(InputArray src, InputArray _lbls) {
// save projections // save projections
for(int sampleIdx = 0; sampleIdx < data.rows; sampleIdx++) { for(int sampleIdx = 0; sampleIdx < data.rows; sampleIdx++) {
Mat p = subspaceProject(_eigenvectors, _mean, data.row(sampleIdx)); Mat p = subspaceProject(_eigenvectors, _mean, data.row(sampleIdx));
this->_projections.push_back(p); _projections.push_back(p);
} }
} }
int Eigenfaces::predict(InputArray _src) const { int Eigenfaces::predict(InputArray _src) const {
// get data // get data
Mat src = _src.getMat(); 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<int>(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 // project into PCA subspace
Mat q = subspaceProject(_eigenvectors, _mean, src.reshape(1,1)); Mat q = subspaceProject(_eigenvectors, _mean, src.reshape(1,1));
double minDist = DBL_MAX; double minDist = DBL_MAX;
@ -354,25 +379,31 @@ void Eigenfaces::save(FileStorage& fs) const {
// Fisherfaces // Fisherfaces
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void Fisherfaces::train(InputArray src, InputArray _lbls) { void Fisherfaces::train(InputArray src, InputArray _lbls) {
if(_lbls.getMat().type() != CV_32SC1) if(src.total() == 0) {
CV_Error(CV_StsUnsupportedFormat, "Labels must be given as integer (CV_32SC1)."); 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 // get data
Mat labels = _lbls.getMat(); Mat labels = _lbls.getMat();
Mat data = asRowMatrix(src, CV_64FC1); Mat data = asRowMatrix(src, CV_64FC1);
// number of samples
CV_Assert( labels.type() == CV_32S && (labels.cols == 1 || labels.rows == 1)); int N = data.rows;
// make sure labels are passed in correct shape
// dimensionality if(labels.total() != (size_t) N) {
int N = data.rows; // number of samples 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());
//int D = data.cols; // dimension of samples error(cv::Exception(CV_StsBadArg, error_message, "Fisherfaces::train", __FILE__, __LINE__));
// assert correct data alignment } else if(labels.rows != 1 && labels.cols != 1) {
if(labels.total() != (size_t)N) 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);
CV_Error(CV_StsUnsupportedFormat, "Labels must be given as integer (CV_32SC1)."); error(cv::Exception(CV_StsBadArg, error_message, "Fisherfaces::train", __FILE__, __LINE__));
// compute the Fisherfaces }
// Get the number of unique classes
// TODO Provide a cv::Mat version?
vector<int> ll; vector<int> ll;
labels.copyTo(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 // 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); _num_components = (C-1);
@ -398,6 +429,15 @@ void Fisherfaces::train(InputArray src, InputArray _lbls) {
int Fisherfaces::predict(InputArray _src) const { int Fisherfaces::predict(InputArray _src) const {
Mat src = _src.getMat(); 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 // project into LDA subspace
Mat q = subspaceProject(_eigenvectors, _mean, src.reshape(1,1)); Mat q = subspaceProject(_eigenvectors, _mean, src.reshape(1,1));
// find 1-nearest neighbor // 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. // Establish the number of bins.
int histSize = maxVal-minVal+1; int histSize = maxVal-minVal+1;
// Set the ranges. // Set the ranges.
float range[] = { static_cast<float>(minVal), static_cast<float>(maxVal) }; float range[] = { static_cast<float>(minVal), static_cast<float>(maxVal+1) };
const float* histRange = { range }; const float* histRange = { range };
// calc histogram // calc histogram
calcHist(&src, 1, 0, Mat(), result, 1, &histSize, &histRange, true, false); calcHist(&src, 1, 0, Mat(), result, 1, &histSize, &histRange, true, false);
@ -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) { static Mat elbp(InputArray src, int radius, int neighbors) {
@ -633,8 +673,16 @@ void LBPH::save(FileStorage& fs) const {
} }
void LBPH::train(InputArray _src, InputArray _lbls) { void LBPH::train(InputArray _src, InputArray _lbls) {
if(_src.kind() != _InputArray::STD_VECTOR_MAT && _src.kind() != _InputArray::STD_VECTOR_VECTOR) 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."); 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<...> >).";
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 // get the vector of matrices
vector<Mat> src; vector<Mat> src;
_src.getMatVector(src); _src.getMatVector(src);
@ -661,7 +709,6 @@ void LBPH::train(InputArray _src, InputArray _lbls) {
} }
} }
int LBPH::predict(InputArray _src) const { int LBPH::predict(InputArray _src) const {
Mat src = _src.getMat(); Mat src = _src.getMat();
// get the spatial histogram from input image // get the spatial histogram from input image

@ -46,29 +46,46 @@ inline vector<_Tp> remove_dups(const vector<_Tp>& src) {
static Mat argsort(InputArray _src, bool ascending=true) static Mat argsort(InputArray _src, bool ascending=true)
{ {
Mat src = _src.getMat(); Mat src = _src.getMat();
if (src.rows != 1 && src.cols != 1) if (src.rows != 1 && src.cols != 1) {
CV_Error(CV_StsBadArg, "cv::argsort only sorts 1D matrices."); 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); int flags = CV_SORT_EVERY_ROW+(ascending ? CV_SORT_ASCENDING : CV_SORT_DESCENDING);
Mat sorted_indices; Mat sorted_indices;
sortIdx(src.reshape(1,1),sorted_indices,flags); sortIdx(src.reshape(1,1),sorted_indices,flags);
return sorted_indices; 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<Mat>) or _InputArray::STD_VECTOR_VECTOR (a std::vector< vector<...> >).";
error(cv::Exception(CV_StsBadArg, error_message, "asRowMatrix", __FILE__, __LINE__));
}
// number of samples // number of samples
int n = (int) src.total(); size_t n = src.total();
// return empty matrix if no data given // return empty matrix if no matrices given
if(n == 0) if(n == 0)
return Mat(); return Mat();
// dimensionality of samples // dimensionality of (reshaped) samples
int d = (int)src.getMat(0).total(); size_t d = src.getMat(0).total();
// create data matrix // create data matrix
Mat data(n, d, rtype); Mat data(n, d, rtype);
// copy data // now copy data
for(int i = 0; i < n; i++) { 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); 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; 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 // get data matrices
Mat W = _W.getMat(); Mat W = _W.getMat();
Mat mean = _mean.getMat(); Mat mean = _mean.getMat();
Mat src = _src.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 // create temporary matrices
Mat X, Y; 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()); src.convertTo(X, W.type());
// get number of samples and dimension // safe to do, because of above assertion
int n = X.rows; if(!mean.empty()) {
int d = X.cols; for(int i=0; i<n; i++) {
// center the data if correct aligned sample mean is given Mat r_i = X.row(i);
if(mean.total() == (size_t)d) subtract(r_i, mean.reshape(1,1), r_i);
subtract(X, repeat(mean.reshape(1,1), n, 1), X); }
}
// finally calculate projection as Y = (X-mean)*W // finally calculate projection as Y = (X-mean)*W
gemm(X, W, 1.0, Mat(), 0.0, Y); gemm(X, W, 1.0, Mat(), 0.0, Y);
return Y; return Y;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// subspace::reconstruct // cv::subspaceReconstruct
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
Mat subspaceReconstruct(InputArray _W, InputArray _mean, InputArray _src) Mat subspaceReconstruct(InputArray _W, InputArray _mean, InputArray _src)
{ {
@ -185,16 +215,32 @@ Mat subspaceReconstruct(InputArray _W, InputArray _mean, InputArray _src)
Mat W = _W.getMat(); Mat W = _W.getMat();
Mat mean = _mean.getMat(); Mat mean = _mean.getMat();
Mat src = _src.getMat(); Mat src = _src.getMat();
// get number of samples // get number of samples and dimension
int n = src.rows; int n = src.rows;
int d = src.cols;
// make sure the data has the correct shape
if(W.cols != 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::subspaceReconstruct", __FILE__, __LINE__));
}
// make sure mean is correct if not empty
if(!mean.empty() && (mean.total() != (size_t) W.rows)) {
string error_message = format("Wrong mean shape for the given eigenvector matrix. Expected %d, but was %d.", W.cols, mean.total());
error(cv::Exception(CV_StsBadArg, error_message, "cv::subspaceReconstruct", __FILE__, __LINE__));
}
// initalize temporary matrices // initalize temporary matrices
Mat X, Y; Mat X, Y;
// copy data & make sure we are using the correct type // copy data & make sure we are using the correct type
src.convertTo(Y, W.type()); src.convertTo(Y, W.type());
// calculate the reconstruction // calculate the reconstruction
gemm(Y, W, 1.0, Mat(), 0.0, X, GEMM_2_T); gemm(Y, W, 1.0, Mat(), 0.0, X, GEMM_2_T);
if(mean.total() == (size_t) X.cols) // safe to do because of above assertion
add(X, repeat(mean.reshape(1,1), n, 1), X); if(!mean.empty()) {
for(int i=0; i<n; i++) {
Mat r_i = X.row(i);
add(r_i, mean.reshape(1,1), r_i);
}
}
return X; return X;
} }
@ -607,9 +653,7 @@ private:
} }
} }
} }
// Complex vector // Complex vector
} else if (q < 0) { } else if (q < 0) {
int l = n1 - 1; int l = n1 - 1;
@ -898,8 +942,9 @@ public:
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void LDA::save(const string& filename) const { void LDA::save(const string& filename) const {
FileStorage fs(filename, FileStorage::WRITE); FileStorage fs(filename, FileStorage::WRITE);
if (!fs.isOpened()) if (!fs.isOpened()) {
CV_Error(CV_StsError, "File can't be opened for writing!"); CV_Error(CV_StsError, "File can't be opened for writing!");
}
this->save(fs); this->save(fs);
fs.release(); fs.release();
} }
@ -942,25 +987,35 @@ void LDA::lda(InputArray _src, InputArray _lbls) {
vector<int> num2label = remove_dups(labels); vector<int> num2label = remove_dups(labels);
map<int, int> label2num; map<int, int> label2num;
for (size_t i = 0; i < num2label.size(); i++) 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++) for (size_t i = 0; i < labels.size(); i++)
mapped_labels[i] = label2num[labels[i]]; mapped_labels[i] = label2num[labels[i]];
// get sample size, dimension // get sample size, dimension
int N = data.rows; int N = data.rows;
int D = data.cols; int D = data.cols;
// number of unique labels // 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 // throw error if less labels, than samples
if (labels.size() != (size_t)N) if (labels.size() != static_cast<size_t>(N)) {
CV_Error(CV_StsBadArg, "Error: The number of samples must equal the number of labels."); 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 // warn if within-classes scatter matrix becomes singular
if (N < D) if (N < D) {
cout << "Warning: Less observations than feature dimension given!" cout << "Warning: Less observations than feature dimension given!"
<< "Computation will probably fail." << "Computation will probably fail."
<< endl; << endl;
}
// clip number of components to be a valid number // 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); _num_components = (C - 1);
}
// holds the mean over all classes // holds the mean over all classes
Mat meanTotal = Mat::zeros(1, D, data.type()); Mat meanTotal = Mat::zeros(1, D, data.type());
// holds the mean for each class // holds the mean for each class
@ -979,12 +1034,12 @@ void LDA::lda(InputArray _src, InputArray _lbls) {
add(meanClass[classIdx], instance, meanClass[classIdx]); add(meanClass[classIdx], instance, meanClass[classIdx]);
numClass[classIdx]++; numClass[classIdx]++;
} }
// calculate means // calculate total mean
meanTotal.convertTo(meanTotal, meanTotal.type(), meanTotal.convertTo(meanTotal, meanTotal.type(), 1.0 / static_cast<double> (N));
1.0 / static_cast<double> (N)); // calculate class means
for (int i = 0; i < C; i++) for (int i = 0; i < C; i++) {
meanClass[i].convertTo(meanClass[i], meanClass[i].type(), meanClass[i].convertTo(meanClass[i], meanClass[i].type(), 1.0 / static_cast<double> (numClass[i]));
1.0 / static_cast<double> (numClass[i])); }
// subtract class means // subtract class means
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
int classIdx = mapped_labels[i]; int classIdx = mapped_labels[i];
@ -1031,7 +1086,8 @@ void LDA::compute(InputArray _src, InputArray _lbls) {
lda(_src.getMat(), _lbls); lda(_src.getMat(), _lbls);
break; break;
default: 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; break;
} }
} }

@ -16,7 +16,9 @@
* See <http://www.opensource.org/licenses/bsd-license> * See <http://www.opensource.org/licenses/bsd-license>
*/ */
#include "opencv2/opencv.hpp" #include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/contrib/contrib.hpp"
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
@ -38,65 +40,102 @@ static Mat toGrayscale(InputArray _src) {
static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') { static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') {
std::ifstream file(filename.c_str(), ifstream::in); std::ifstream file(filename.c_str(), ifstream::in);
if (!file) if (!file) {
throw std::exception(); string error_message = "No valid input file was given, please check the given filename.";
CV_Error(CV_StsBadArg, error_message);
}
string line, path, classlabel; string line, path, classlabel;
while (getline(file, line)) { while (getline(file, line)) {
stringstream liness(line); stringstream liness(line);
getline(liness, path, separator); getline(liness, path, separator);
getline(liness, classlabel); getline(liness, classlabel);
images.push_back(imread(path, 0)); if(!path.empty() && !classlabel.empty()) {
labels.push_back(atoi(classlabel.c_str())); images.push_back(imread(path, 0));
labels.push_back(atoi(classlabel.c_str()));
}
} }
} }
int main(int argc, const char *argv[]) { 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) { if (argc != 2) {
cout << "usage: " << argv[0] << " <csv.ext>" << endl; cout << "usage: " << argv[0] << " <csv.ext>" << endl;
exit(1); exit(1);
} }
// path to your CSV // Get the path to your CSV.
string fn_csv = string(argv[1]); string fn_csv = string(argv[1]);
// images and corresponding labels // These vectors hold the images and corresponding labels.
vector<Mat> images; vector<Mat> images;
vector<int> labels; vector<int> labels;
// read in the data // Read in the data. This can fail if no valid
// input filename is given.
try { try {
read_csv(fn_csv, images, labels); read_csv(fn_csv, images, labels);
} catch (exception&) { } catch (cv::Exception& e) {
cerr << "Error opening file \"" << fn_csv << "\"." << endl; cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl;
// nothing more we can do
exit(1); exit(1);
} }
// get width and height // Quit if there are not enough images for this demo.
//int width = images[0].cols; 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; 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]; Mat testSample = images[images.size() - 1];
int testLabel = labels[labels.size() - 1]; int testLabel = labels[labels.size() - 1];
// ... and delete last element
images.pop_back(); images.pop_back();
labels.pop_back(); labels.pop_back();
// build the Fisherfaces model // The following lines create an Eigenfaces model for
Ptr<FaceRecognizer> model = createFisherFaceRecognizer(); // 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<FaceRecognizer> model = createEigenFaceRecognizer();
model->train(images, labels); 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); int predicted = model->predict(testSample);
cout << "predicted class = " << predicted << endl; // Show the prediction and actual class of the given
cout << "actual class = " << testLabel << endl; // sample:
// get the eigenvectors string result_message = format("Predicted class=%d / Actual class=%d.", predicted, testLabel);
Mat W = model->eigenvectors(); cout << result_message << endl;
// show first 10 fisherfaces // 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++) { for (int i = 0; i < min(10, W.cols); i++) {
string msg = format("Eigenvalue #%d = %.5f", i, eigenvalues.at<double>(i));
cout << msg << endl;
// get eigenvector #i // get eigenvector #i
Mat ev = W.col(i).clone(); 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)); Mat grayscale = toGrayscale(ev.reshape(1, height));
// show image (with Jet colormap) // Show the image & apply a Jet colormap for better sensing.
Mat cgrayscale; Mat cgrayscale;
applyColorMap(grayscale, cgrayscale, COLORMAP_JET); applyColorMap(grayscale, cgrayscale, COLORMAP_JET);
imshow(format("%d", i), cgrayscale); imshow(format("%d", i), cgrayscale);
} }
waitKey(0); waitKey(0);
return 0; return 0;
} }

Loading…
Cancel
Save