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
// 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);
......
......@@ -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), 71–86.
class Eigenfaces : public FaceRecognizer
......@@ -124,10 +140,10 @@ public:
// See FaceRecognizer::save.
void save(FileStorage& fs) const;
AlgorithmInfo* info() const;
};
// Belhumeur, P. N., Hespanha, J., and Kriegman, D. "Eigenfaces vs. Fisher-
// faces: Recognition using class specific linear projection.". IEEE
// Transactions on Pattern Analysis and Machine Intelligence 19, 7 (1997),
......@@ -160,7 +176,7 @@ public:
train(src, labels);
}
~Fisherfaces() { }
~Fisherfaces() {}
// Computes a Fisherfaces model with images in src and corresponding labels
// in labels.
......@@ -180,10 +196,6 @@ public:
// Face Recognition based on Local Binary Patterns.
//
// TODO Allow to change the distance metric.
// TODO Allow to change LBP computation (Extended LBP used right now).
// TODO Optimize, Optimize, Optimize!
//
// Ahonen T, Hadid A. and Pietikäinen M. "Face description with local binary
// patterns: Application to face recognition." IEEE Transactions on Pattern
// Analysis and Machine Intelligence, 28(12):2037-2041.
......@@ -208,11 +220,11 @@ public:
//
// radius, neighbors are used in the local binary patterns creation.
// grid_x, grid_y control the grid size of the spatial histograms.
LBPH(int radius_=1, int neighbors_=8, int grid_x_=8, int grid_y_=8) :
_grid_x(grid_x_),
_grid_y(grid_y_),
_radius(radius_),
_neighbors(neighbors_) {}
LBPH(int radius=1, int neighbors=8, int grid_x=8, int grid_y=8) :
_grid_x(grid_x),
_grid_y(grid_y),
_radius(radius),
_neighbors(neighbors) {}
// Initializes and computes this LBPH Model. The current implementation is
// rather fixed as it uses the Extended Local Binary Patterns per default.
......@@ -221,12 +233,12 @@ public:
// (grid_x=8), (grid_y=8) controls the grid size of the spatial histograms.
LBPH(InputArray src,
InputArray labels,
int radius_=1, int neighbors_=8,
int grid_x_=8, int grid_y_=8) :
_grid_x(grid_x_),
_grid_y(grid_y_),
_radius(radius_),
_neighbors(neighbors_) {
int radius=1, int neighbors=8,
int grid_x=8, int grid_y=8) :
_grid_x(grid_x),
_grid_y(grid_y),
_radius(radius),
_neighbors(neighbors) {
train(src, labels);
}
......@@ -278,22 +290,25 @@ void FaceRecognizer::load(const string& filename) {
//------------------------------------------------------------------------------
// Eigenfaces
//------------------------------------------------------------------------------
void Eigenfaces::train(InputArray src, InputArray _lbls) {
// assert type
if(_lbls.getMat().type() != CV_32SC1)
CV_Error(CV_StsUnsupportedFormat, "Labels must be given as integer (CV_32SC1).");
void Eigenfaces::train(InputArray _src, InputArray _local_labels) {
if(_src.total() == 0) {
string error_message = format("Empty training data was given. You'll need more than one sample to learn a model.");
error(Exception(CV_StsUnsupportedFormat, error_message, "Eigenfaces::train", __FILE__, __LINE__));
} else if(_local_labels.getMat().type() != CV_32SC1) {
string error_message = format("Labels must be given as integer (CV_32SC1). Expected %d, but was %d.", CV_32SC1, _local_labels.type());
error(Exception(CV_StsUnsupportedFormat, error_message, "Eigenfaces::train", __FILE__, __LINE__));
}
// get labels
Mat labels = _lbls.getMat();
CV_Assert( labels.type() == CV_32S && (labels.cols == 1 || labels.rows == 1));
Mat labels = _local_labels.getMat();
// observations in row
Mat data = asRowMatrix(src, CV_64FC1);
Mat data = asRowMatrix(_src, CV_64FC1);
// number of samples
int n = data.rows;
// dimensionality of data
//int d = data.cols;
int n = data.rows;
// assert there are as much samples as labels
if((size_t)n != labels.total())
CV_Error(CV_StsBadArg, "The number of samples must equal the number of labels!");
if(static_cast<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();
......
......@@ -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;
}
}
......
......@@ -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;
}
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