Commit ee1b6712 authored by Philipp Wagner's avatar Philipp Wagner

Several exceptions added to the available FaceRecognizer classes and helper…

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.
parent 6727e4cb
...@@ -942,8 +942,6 @@ namespace cv ...@@ -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);
......
...@@ -22,7 +22,7 @@ namespace cv ...@@ -22,7 +22,7 @@ namespace cv
{ {
using std::set; using std::set;
// Reads a sequence from a FileNode::SEQ with type _Tp into a result vector. // Reads a sequence from a FileNode::SEQ with type _Tp into a result vector.
template<typename _Tp> template<typename _Tp>
inline void readFileNodeList(const FileNode& fn, vector<_Tp>& result) { inline void readFileNodeList(const FileNode& fn, vector<_Tp>& result) {
...@@ -48,26 +48,42 @@ inline void writeFileNodeList(FileStorage& fs, const string& name, ...@@ -48,26 +48,42 @@ 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) {
...@@ -82,7 +98,7 @@ inline vector<_Tp> remove_dups(const vector<_Tp>& src) { ...@@ -82,7 +98,7 @@ inline vector<_Tp> remove_dups(const vector<_Tp>& src) {
return elems; return elems;
} }
// Turk, M., and Pentland, A. "Eigenfaces for recognition.". Journal of // Turk, M., and Pentland, A. "Eigenfaces for recognition.". Journal of
// Cognitive Neuroscience 3 (1991), 71–86. // Cognitive Neuroscience 3 (1991), 71–86.
class Eigenfaces : public FaceRecognizer class Eigenfaces : public FaceRecognizer
...@@ -124,10 +140,10 @@ public: ...@@ -124,10 +140,10 @@ public:
// See FaceRecognizer::save. // See FaceRecognizer::save.
void save(FileStorage& fs) const; void save(FileStorage& fs) const;
AlgorithmInfo* info() const; AlgorithmInfo* info() const;
}; };
// Belhumeur, P. N., Hespanha, J., and Kriegman, D. "Eigenfaces vs. Fisher- // Belhumeur, P. N., Hespanha, J., and Kriegman, D. "Eigenfaces vs. Fisher-
// faces: Recognition using class specific linear projection.". IEEE // faces: Recognition using class specific linear projection.". IEEE
// Transactions on Pattern Analysis and Machine Intelligence 19, 7 (1997), // Transactions on Pattern Analysis and Machine Intelligence 19, 7 (1997),
...@@ -160,7 +176,7 @@ public: ...@@ -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: ...@@ -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: ...@@ -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: ...@@ -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) { ...@@ -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) { ...@@ -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 { ...@@ -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) { ...@@ -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) ...@@ -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);
...@@ -570,7 +610,7 @@ static Mat histc(InputArray _src, int minVal, int maxVal, bool normed) ...@@ -570,7 +610,7 @@ static Mat histc(InputArray _src, int minVal, int maxVal, bool normed)
return Mat(); return Mat();
} }
static Mat spatial_histogram(InputArray _src, int numPatterns, static Mat spatial_histogram(InputArray _src, int numPatterns,
int grid_x, int grid_y, bool normed) int grid_x, int grid_y, bool normed)
{ {
...@@ -602,7 +642,7 @@ static Mat spatial_histogram(InputArray _src, int numPatterns, ...@@ -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) {
...@@ -610,7 +650,7 @@ 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); elbp(src, dst, radius, neighbors);
return dst; return dst;
} }
void LBPH::load(const FileStorage& fs) { void LBPH::load(const FileStorage& fs) {
fs["radius"] >> _radius; fs["radius"] >> _radius;
fs["neighbors"] >> _neighbors; fs["neighbors"] >> _neighbors;
...@@ -633,8 +673,16 @@ void LBPH::save(FileStorage& fs) const { ...@@ -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) { ...@@ -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
...@@ -684,24 +731,24 @@ int LBPH::predict(InputArray _src) const { ...@@ -684,24 +731,24 @@ int LBPH::predict(InputArray _src) const {
} }
return minClass; return minClass;
} }
Ptr<FaceRecognizer> createEigenFaceRecognizer(int num_components) Ptr<FaceRecognizer> createEigenFaceRecognizer(int num_components)
{ {
return new Eigenfaces(num_components); return new Eigenfaces(num_components);
} }
Ptr<FaceRecognizer> createFisherFaceRecognizer(int num_components) Ptr<FaceRecognizer> createFisherFaceRecognizer(int num_components)
{ {
return new Fisherfaces(num_components); return new Fisherfaces(num_components);
} }
Ptr<FaceRecognizer> createLBPHFaceRecognizer(int radius, int neighbors, Ptr<FaceRecognizer> createLBPHFaceRecognizer(int radius, int neighbors,
int grid_x, int grid_y) int grid_x, int grid_y)
{ {
return new LBPH(radius, neighbors, grid_x, grid_y); return new LBPH(radius, neighbors, grid_x, grid_y);
} }
CV_INIT_ALGORITHM(Eigenfaces, "FaceRecognizer.Eigenfaces", CV_INIT_ALGORITHM(Eigenfaces, "FaceRecognizer.Eigenfaces",
obj.info()->addParam(obj, "ncomponents", obj._num_components); obj.info()->addParam(obj, "ncomponents", obj._num_components);
obj.info()->addParam(obj, "projections", obj._projections, true); obj.info()->addParam(obj, "projections", obj._projections, true);
...@@ -716,8 +763,8 @@ CV_INIT_ALGORITHM(Fisherfaces, "FaceRecognizer.Fisherfaces", ...@@ -716,8 +763,8 @@ CV_INIT_ALGORITHM(Fisherfaces, "FaceRecognizer.Fisherfaces",
obj.info()->addParam(obj, "labels", obj._labels, true); obj.info()->addParam(obj, "labels", obj._labels, true);
obj.info()->addParam(obj, "eigenvectors", obj._eigenvectors, true); obj.info()->addParam(obj, "eigenvectors", obj._eigenvectors, true);
obj.info()->addParam(obj, "eigenvalues", obj._eigenvalues, 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", CV_INIT_ALGORITHM(LBPH, "FaceRecognizer.LBPH",
obj.info()->addParam(obj, "radius", obj._radius); obj.info()->addParam(obj, "radius", obj._radius);
obj.info()->addParam(obj, "neighbors", obj._neighbors); obj.info()->addParam(obj, "neighbors", obj._neighbors);
...@@ -725,7 +772,7 @@ CV_INIT_ALGORITHM(LBPH, "FaceRecognizer.LBPH", ...@@ -725,7 +772,7 @@ CV_INIT_ALGORITHM(LBPH, "FaceRecognizer.LBPH",
obj.info()->addParam(obj, "grid_y", obj._grid_y); obj.info()->addParam(obj, "grid_y", obj._grid_y);
obj.info()->addParam(obj, "histograms", obj._histograms, true); obj.info()->addParam(obj, "histograms", obj._histograms, true);
obj.info()->addParam(obj, "labels", obj._labels, true)); obj.info()->addParam(obj, "labels", obj._labels, true));
bool initModule_contrib() bool initModule_contrib()
{ {
Ptr<Algorithm> efaces = createEigenfaces(), ffaces = createFisherfaces(), lbph = createLBPH(); Ptr<Algorithm> efaces = createEigenfaces(), ffaces = createFisherfaces(), lbph = createLBPH();
......
...@@ -46,29 +46,46 @@ inline vector<_Tp> remove_dups(const vector<_Tp>& src) { ...@@ -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) ...@@ -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) ...@@ -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: ...@@ -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: ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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 @@ ...@@ -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) { ...@@ -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;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment