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.

This commit is contained in:
Philipp Wagner 2012-06-10 11:57:33 +00:00
parent 6727e4cb6d
commit ee1b671279
4 changed files with 282 additions and 142 deletions

View File

@ -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<FaceRecognizer> createEigenFaceRecognizer(int num_components = 0);

View File

@ -22,7 +22,7 @@ namespace cv
{
using std::set;
// Reads a sequence from a FileNode::SEQ with type _Tp into a result vector.
template<typename _Tp>
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<Mat>) 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<typename _Tp>
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), 7186.
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<int>(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<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
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<int> 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<float>(minVal), static_cast<float>(maxVal) };
float range[] = { static_cast<float>(minVal), static_cast<float>(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<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
vector<Mat> 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<FaceRecognizer> createEigenFaceRecognizer(int num_components)
{
return new Eigenfaces(num_components);
}
Ptr<FaceRecognizer> createFisherFaceRecognizer(int num_components)
{
return new Fisherfaces(num_components);
}
Ptr<FaceRecognizer> 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<Algorithm> efaces = createEigenfaces(), ffaces = createFisherfaces(), lbph = createLBPH();

View File

@ -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<Mat>) 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; i<n; i++) {
Mat r_i = X.row(i);
subtract(r_i, mean.reshape(1,1), r_i);
}
}
// finally calculate projection as Y = (X-mean)*W
gemm(X, W, 1.0, Mat(), 0.0, Y);
return Y;
}
//------------------------------------------------------------------------------
// subspace::reconstruct
// cv::subspaceReconstruct
//------------------------------------------------------------------------------
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 mean = _mean.getMat();
Mat src = _src.getMat();
// get number of samples
// get number of samples and dimension
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
Mat X, Y;
// copy data & make sure we are using the correct type
src.convertTo(Y, W.type());
// calculate the reconstruction
gemm(Y, W, 1.0, Mat(), 0.0, X, GEMM_2_T);
if(mean.total() == (size_t) X.cols)
add(X, repeat(mean.reshape(1,1), n, 1), X);
// safe to do because of above assertion
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;
}
@ -607,9 +653,7 @@ private:
}
}
}
// Complex vector
} else if (q < 0) {
int l = n1 - 1;
@ -898,8 +942,9 @@ public:
//------------------------------------------------------------------------------
void LDA::save(const string& filename) const {
FileStorage fs(filename, FileStorage::WRITE);
if (!fs.isOpened())
if (!fs.isOpened()) {
CV_Error(CV_StsError, "File can't be opened for writing!");
}
this->save(fs);
fs.release();
}
@ -942,25 +987,35 @@ void LDA::lda(InputArray _src, InputArray _lbls) {
vector<int> num2label = remove_dups(labels);
map<int, int> 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<size_t>(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<double> (N));
for (int i = 0; i < C; i++)
meanClass[i].convertTo(meanClass[i], meanClass[i].type(),
1.0 / static_cast<double> (numClass[i]));
// calculate total mean
meanTotal.convertTo(meanTotal, meanTotal.type(), 1.0 / static_cast<double> (N));
// calculate class means
for (int i = 0; i < C; i++) {
meanClass[i].convertTo(meanClass[i], meanClass[i].type(), 1.0 / static_cast<double> (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;
}
}

View File

@ -16,7 +16,9 @@
* 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 <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 = ';') {
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] << " <csv.ext>" << 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<Mat> images;
vector<int> 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<FaceRecognizer> 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<FaceRecognizer> 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<double>(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;
}