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:
parent
6727e4cb6d
commit
ee1b671279
@ -942,8 +942,6 @@ namespace cv
|
|||||||
// Deserializes this object from a given cv::FileStorage.
|
// Deserializes this object from a given cv::FileStorage.
|
||||||
virtual void load(const FileStorage& fs) = 0;
|
virtual void load(const FileStorage& fs) = 0;
|
||||||
|
|
||||||
// Returns eigenvectors (if any)
|
|
||||||
virtual Mat eigenvectors() const { return Mat(); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CV_EXPORTS Ptr<FaceRecognizer> createEigenFaceRecognizer(int num_components = 0);
|
CV_EXPORTS Ptr<FaceRecognizer> createEigenFaceRecognizer(int num_components = 0);
|
||||||
|
@ -49,25 +49,41 @@ inline void writeFileNodeList(FileStorage& fs, const string& name,
|
|||||||
fs << "]";
|
fs << "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
static Mat asRowMatrix(InputArrayOfArrays src, int rtype, double alpha=1, double beta=0)
|
static Mat asRowMatrix(InputArrayOfArrays src, int rtype, double alpha=1, double beta=0) {
|
||||||
{
|
// make sure the input data is a vector of matrices or vector of vector
|
||||||
|
if(src.kind() != _InputArray::STD_VECTOR_MAT && src.kind() != _InputArray::STD_VECTOR_VECTOR) {
|
||||||
|
string error_message = "The data is expected as InputArray::STD_VECTOR_MAT (a std::vector<Mat>) or _InputArray::STD_VECTOR_VECTOR (a std::vector< vector<...> >).";
|
||||||
|
error(Exception(CV_StsBadArg, error_message, "asRowMatrix", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
// number of samples
|
// number of samples
|
||||||
int n = (int) src.total();
|
size_t n = src.total();
|
||||||
// return empty matrix if no data given
|
// return empty matrix if no matrices given
|
||||||
if(n == 0)
|
if(n == 0)
|
||||||
return Mat();
|
return Mat();
|
||||||
// dimensionality of samples
|
// dimensionality of (reshaped) samples
|
||||||
int d = (int)src.getMat(0).total();
|
size_t d = src.getMat(0).total();
|
||||||
// create data matrix
|
// create data matrix
|
||||||
Mat data(n, d, rtype);
|
Mat data(n, d, rtype);
|
||||||
// copy data
|
// now copy data
|
||||||
for(int i = 0; i < n; i++) {
|
for(unsigned int i = 0; i < n; i++) {
|
||||||
|
// make sure data can be reshaped, throw exception if not!
|
||||||
|
if(src.getMat(i).total() != d) {
|
||||||
|
string error_message = format("Wrong number of elements in matrix #%d! Expected %d was %d.", i, d, src.getMat(i).total());
|
||||||
|
error(Exception(CV_StsBadArg, error_message, "cv::asRowMatrix", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
|
// get a hold of the current row
|
||||||
Mat xi = data.row(i);
|
Mat xi = data.row(i);
|
||||||
|
// 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);
|
src.getMat(i).reshape(1, 1).convertTo(xi, rtype, alpha, beta);
|
||||||
|
} else {
|
||||||
|
src.getMat(i).clone().reshape(1, 1).convertTo(xi, rtype, alpha, beta);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Removes duplicate elements in a given vector.
|
// Removes duplicate elements in a given vector.
|
||||||
template<typename _Tp>
|
template<typename _Tp>
|
||||||
inline vector<_Tp> remove_dups(const vector<_Tp>& src) {
|
inline vector<_Tp> remove_dups(const vector<_Tp>& src) {
|
||||||
@ -160,7 +176,7 @@ public:
|
|||||||
train(src, labels);
|
train(src, labels);
|
||||||
}
|
}
|
||||||
|
|
||||||
~Fisherfaces() { }
|
~Fisherfaces() {}
|
||||||
|
|
||||||
// Computes a Fisherfaces model with images in src and corresponding labels
|
// Computes a Fisherfaces model with images in src and corresponding labels
|
||||||
// in labels.
|
// in labels.
|
||||||
@ -180,10 +196,6 @@ public:
|
|||||||
|
|
||||||
// Face Recognition based on Local Binary Patterns.
|
// Face Recognition based on Local Binary Patterns.
|
||||||
//
|
//
|
||||||
// TODO Allow to change the distance metric.
|
|
||||||
// TODO Allow to change LBP computation (Extended LBP used right now).
|
|
||||||
// TODO Optimize, Optimize, Optimize!
|
|
||||||
//
|
|
||||||
// Ahonen T, Hadid A. and Pietikäinen M. "Face description with local binary
|
// Ahonen T, Hadid A. and Pietikäinen M. "Face description with local binary
|
||||||
// patterns: Application to face recognition." IEEE Transactions on Pattern
|
// patterns: Application to face recognition." IEEE Transactions on Pattern
|
||||||
// Analysis and Machine Intelligence, 28(12):2037-2041.
|
// Analysis and Machine Intelligence, 28(12):2037-2041.
|
||||||
@ -208,11 +220,11 @@ public:
|
|||||||
//
|
//
|
||||||
// radius, neighbors are used in the local binary patterns creation.
|
// radius, neighbors are used in the local binary patterns creation.
|
||||||
// grid_x, grid_y control the grid size of the spatial histograms.
|
// grid_x, grid_y control the grid size of the spatial histograms.
|
||||||
LBPH(int radius_=1, int neighbors_=8, int grid_x_=8, int grid_y_=8) :
|
LBPH(int radius=1, int neighbors=8, int grid_x=8, int grid_y=8) :
|
||||||
_grid_x(grid_x_),
|
_grid_x(grid_x),
|
||||||
_grid_y(grid_y_),
|
_grid_y(grid_y),
|
||||||
_radius(radius_),
|
_radius(radius),
|
||||||
_neighbors(neighbors_) {}
|
_neighbors(neighbors) {}
|
||||||
|
|
||||||
// Initializes and computes this LBPH Model. The current implementation is
|
// Initializes and computes this LBPH Model. The current implementation is
|
||||||
// rather fixed as it uses the Extended Local Binary Patterns per default.
|
// rather fixed as it uses the Extended Local Binary Patterns per default.
|
||||||
@ -221,12 +233,12 @@ public:
|
|||||||
// (grid_x=8), (grid_y=8) controls the grid size of the spatial histograms.
|
// (grid_x=8), (grid_y=8) controls the grid size of the spatial histograms.
|
||||||
LBPH(InputArray src,
|
LBPH(InputArray src,
|
||||||
InputArray labels,
|
InputArray labels,
|
||||||
int radius_=1, int neighbors_=8,
|
int radius=1, int neighbors=8,
|
||||||
int grid_x_=8, int grid_y_=8) :
|
int grid_x=8, int grid_y=8) :
|
||||||
_grid_x(grid_x_),
|
_grid_x(grid_x),
|
||||||
_grid_y(grid_y_),
|
_grid_y(grid_y),
|
||||||
_radius(radius_),
|
_radius(radius),
|
||||||
_neighbors(neighbors_) {
|
_neighbors(neighbors) {
|
||||||
train(src, labels);
|
train(src, labels);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,22 +290,25 @@ void FaceRecognizer::load(const string& filename) {
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Eigenfaces
|
// Eigenfaces
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
void Eigenfaces::train(InputArray src, InputArray _lbls) {
|
void Eigenfaces::train(InputArray _src, InputArray _local_labels) {
|
||||||
// assert type
|
if(_src.total() == 0) {
|
||||||
if(_lbls.getMat().type() != CV_32SC1)
|
string error_message = format("Empty training data was given. You'll need more than one sample to learn a model.");
|
||||||
CV_Error(CV_StsUnsupportedFormat, "Labels must be given as integer (CV_32SC1).");
|
error(Exception(CV_StsUnsupportedFormat, error_message, "Eigenfaces::train", __FILE__, __LINE__));
|
||||||
|
} else if(_local_labels.getMat().type() != CV_32SC1) {
|
||||||
|
string error_message = format("Labels must be given as integer (CV_32SC1). Expected %d, but was %d.", CV_32SC1, _local_labels.type());
|
||||||
|
error(Exception(CV_StsUnsupportedFormat, error_message, "Eigenfaces::train", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
// get labels
|
// get labels
|
||||||
Mat labels = _lbls.getMat();
|
Mat labels = _local_labels.getMat();
|
||||||
CV_Assert( labels.type() == CV_32S && (labels.cols == 1 || labels.rows == 1));
|
|
||||||
// observations in row
|
// observations in row
|
||||||
Mat data = asRowMatrix(src, CV_64FC1);
|
Mat data = asRowMatrix(_src, CV_64FC1);
|
||||||
// number of samples
|
// number of samples
|
||||||
int n = data.rows;
|
int n = data.rows;
|
||||||
// dimensionality of data
|
|
||||||
//int d = data.cols;
|
|
||||||
// assert there are as much samples as labels
|
// assert there are as much samples as labels
|
||||||
if((size_t)n != labels.total())
|
if(static_cast<int>(labels.total()) != n) {
|
||||||
CV_Error(CV_StsBadArg, "The number of samples must equal the number of labels!");
|
string error_message = format("The number of samples (src) must equal the number of labels (labels)! len(src)=%d, len(labels)=%d.", n, labels.total());
|
||||||
|
error(Exception(CV_StsBadArg, error_message, "Eigenfaces::train", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
// clip number of components to be valid
|
// clip number of components to be valid
|
||||||
if((_num_components <= 0) || (_num_components > n))
|
if((_num_components <= 0) || (_num_components > n))
|
||||||
_num_components = n;
|
_num_components = n;
|
||||||
@ -307,13 +322,23 @@ void Eigenfaces::train(InputArray src, InputArray _lbls) {
|
|||||||
// save projections
|
// save projections
|
||||||
for(int sampleIdx = 0; sampleIdx < data.rows; sampleIdx++) {
|
for(int sampleIdx = 0; sampleIdx < data.rows; sampleIdx++) {
|
||||||
Mat p = subspaceProject(_eigenvectors, _mean, data.row(sampleIdx));
|
Mat p = subspaceProject(_eigenvectors, _mean, data.row(sampleIdx));
|
||||||
this->_projections.push_back(p);
|
_projections.push_back(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int Eigenfaces::predict(InputArray _src) const {
|
int Eigenfaces::predict(InputArray _src) const {
|
||||||
// get data
|
// get data
|
||||||
Mat src = _src.getMat();
|
Mat src = _src.getMat();
|
||||||
|
// make sure the user is passing correct data
|
||||||
|
if(_projections.empty()) {
|
||||||
|
// throw error if no data (or simply return -1?)
|
||||||
|
string error_message = "This Eigenfaces model is not computed yet. Did you call Eigenfaces::train?";
|
||||||
|
error(cv::Exception(CV_StsError, error_message, "Eigenfaces::predict", __FILE__, __LINE__));
|
||||||
|
} else if(_eigenvectors.rows != static_cast<int>(src.total())) {
|
||||||
|
// check data alignment just for clearer exception messages
|
||||||
|
string error_message = format("Wrong input image size. Reason: Training and Test images must be of equal size! Expected an image with %d elements, but got %d.", _eigenvectors.rows, src.total());
|
||||||
|
error(cv::Exception(CV_StsError, error_message, "Eigenfaces::predict", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
// project into PCA subspace
|
// project into PCA subspace
|
||||||
Mat q = subspaceProject(_eigenvectors, _mean, src.reshape(1,1));
|
Mat q = subspaceProject(_eigenvectors, _mean, src.reshape(1,1));
|
||||||
double minDist = DBL_MAX;
|
double minDist = DBL_MAX;
|
||||||
@ -354,25 +379,31 @@ void Eigenfaces::save(FileStorage& fs) const {
|
|||||||
// Fisherfaces
|
// Fisherfaces
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
void Fisherfaces::train(InputArray src, InputArray _lbls) {
|
void Fisherfaces::train(InputArray src, InputArray _lbls) {
|
||||||
if(_lbls.getMat().type() != CV_32SC1)
|
if(src.total() == 0) {
|
||||||
CV_Error(CV_StsUnsupportedFormat, "Labels must be given as integer (CV_32SC1).");
|
string error_message = format("Empty training data was given. You'll need more than one sample to learn a model.");
|
||||||
|
error(cv::Exception(CV_StsUnsupportedFormat, error_message, "cv::Eigenfaces::train", __FILE__, __LINE__));
|
||||||
|
} else if(_lbls.getMat().type() != CV_32SC1) {
|
||||||
|
string error_message = format("Labels must be given as integer (CV_32SC1). Expected %d, but was %d.", CV_32SC1, _lbls.type());
|
||||||
|
error(cv::Exception(CV_StsUnsupportedFormat, error_message, "cv::Fisherfaces::train", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
// get data
|
// get data
|
||||||
Mat labels = _lbls.getMat();
|
Mat labels = _lbls.getMat();
|
||||||
Mat data = asRowMatrix(src, CV_64FC1);
|
Mat data = asRowMatrix(src, CV_64FC1);
|
||||||
|
// number of samples
|
||||||
CV_Assert( labels.type() == CV_32S && (labels.cols == 1 || labels.rows == 1));
|
int N = data.rows;
|
||||||
|
// make sure labels are passed in correct shape
|
||||||
// dimensionality
|
if(labels.total() != (size_t) N) {
|
||||||
int N = data.rows; // number of samples
|
string error_message = format("The number of samples (src) must equal the number of labels (labels)! len(src)=%d, len(labels)=%d.", N, labels.total());
|
||||||
//int D = data.cols; // dimension of samples
|
error(cv::Exception(CV_StsBadArg, error_message, "Fisherfaces::train", __FILE__, __LINE__));
|
||||||
// assert correct data alignment
|
} else if(labels.rows != 1 && labels.cols != 1) {
|
||||||
if(labels.total() != (size_t)N)
|
string error_message = format("Expected the labels in a matrix with one row or column! Given dimensions are rows=%s, cols=%d.", labels.rows, labels.cols);
|
||||||
CV_Error(CV_StsUnsupportedFormat, "Labels must be given as integer (CV_32SC1).");
|
error(cv::Exception(CV_StsBadArg, error_message, "Fisherfaces::train", __FILE__, __LINE__));
|
||||||
// compute the Fisherfaces
|
}
|
||||||
|
// Get the number of unique classes
|
||||||
|
// TODO Provide a cv::Mat version?
|
||||||
vector<int> ll;
|
vector<int> ll;
|
||||||
labels.copyTo(ll);
|
labels.copyTo(ll);
|
||||||
int C = (int)remove_dups(ll).size(); // number of unique classes
|
int C = (int) remove_dups(ll).size();
|
||||||
// clip number of components to be a valid number
|
// clip number of components to be a valid number
|
||||||
if((_num_components <= 0) || (_num_components > (C-1)))
|
if((_num_components <= 0) || (_num_components > (C-1)))
|
||||||
_num_components = (C-1);
|
_num_components = (C-1);
|
||||||
@ -398,6 +429,15 @@ void Fisherfaces::train(InputArray src, InputArray _lbls) {
|
|||||||
|
|
||||||
int Fisherfaces::predict(InputArray _src) const {
|
int Fisherfaces::predict(InputArray _src) const {
|
||||||
Mat src = _src.getMat();
|
Mat src = _src.getMat();
|
||||||
|
// check data alignment just for clearer exception messages
|
||||||
|
if(_projections.empty()) {
|
||||||
|
// throw error if no data (or simply return -1?)
|
||||||
|
string error_message = "This Fisherfaces model is not computed yet. Did you call Fisherfaces::train?";
|
||||||
|
error(cv::Exception(CV_StsError, error_message, "Fisherfaces::predict", __FILE__, __LINE__));
|
||||||
|
} else if(src.total() != (size_t) _eigenvectors.rows) {
|
||||||
|
string error_message = format("Wrong input image size. Reason: Training and Test images must be of equal size! Expected an image with %d elements, but got %d.", _eigenvectors.rows, src.total());
|
||||||
|
error(cv::Exception(CV_StsError, error_message, "Fisherfaces::predict", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
// project into LDA subspace
|
// project into LDA subspace
|
||||||
Mat q = subspaceProject(_eigenvectors, _mean, src.reshape(1,1));
|
Mat q = subspaceProject(_eigenvectors, _mean, src.reshape(1,1));
|
||||||
// find 1-nearest neighbor
|
// find 1-nearest neighbor
|
||||||
@ -531,7 +571,7 @@ histc_(const Mat& src, int minVal=0, int maxVal=255, bool normed=false)
|
|||||||
// Establish the number of bins.
|
// Establish the number of bins.
|
||||||
int histSize = maxVal-minVal+1;
|
int histSize = maxVal-minVal+1;
|
||||||
// Set the ranges.
|
// Set the ranges.
|
||||||
float range[] = { static_cast<float>(minVal), static_cast<float>(maxVal) };
|
float range[] = { static_cast<float>(minVal), static_cast<float>(maxVal+1) };
|
||||||
const float* histRange = { range };
|
const float* histRange = { range };
|
||||||
// calc histogram
|
// calc histogram
|
||||||
calcHist(&src, 1, 0, Mat(), result, 1, &histSize, &histRange, true, false);
|
calcHist(&src, 1, 0, Mat(), result, 1, &histSize, &histRange, true, false);
|
||||||
@ -602,7 +642,7 @@ static Mat spatial_histogram(InputArray _src, int numPatterns,
|
|||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// cv::elbp, cv::olbp, cv::varlbp wrapper
|
// wrapper to cv::elbp (extended local binary patterns)
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
static Mat elbp(InputArray src, int radius, int neighbors) {
|
static Mat elbp(InputArray src, int radius, int neighbors) {
|
||||||
@ -633,8 +673,16 @@ void LBPH::save(FileStorage& fs) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LBPH::train(InputArray _src, InputArray _lbls) {
|
void LBPH::train(InputArray _src, InputArray _lbls) {
|
||||||
if(_src.kind() != _InputArray::STD_VECTOR_MAT && _src.kind() != _InputArray::STD_VECTOR_VECTOR)
|
if(_src.kind() != _InputArray::STD_VECTOR_MAT && _src.kind() != _InputArray::STD_VECTOR_VECTOR) {
|
||||||
CV_Error(CV_StsUnsupportedFormat, "LBPH::train expects InputArray::STD_VECTOR_MAT or _InputArray::STD_VECTOR_VECTOR.");
|
string error_message = "The images are expected as InputArray::STD_VECTOR_MAT (a std::vector<Mat>) or _InputArray::STD_VECTOR_VECTOR (a std::vector< vector<...> >).";
|
||||||
|
error(Exception(CV_StsBadArg, error_message, "LBPH::train", __FILE__, __LINE__));
|
||||||
|
} else if(_src.total() == 0) {
|
||||||
|
string error_message = format("Empty training data was given. You'll need more than one sample to learn a model.");
|
||||||
|
error(Exception(CV_StsUnsupportedFormat, error_message, "LBPH::train", __FILE__, __LINE__));
|
||||||
|
} else if(_lbls.getMat().type() != CV_32SC1) {
|
||||||
|
string error_message = format("Labels must be given as integer (CV_32SC1). Expected %d, but was %d.", CV_32SC1, _lbls.type());
|
||||||
|
error(Exception(CV_StsUnsupportedFormat, error_message, "LBPH::train", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
// get the vector of matrices
|
// get the vector of matrices
|
||||||
vector<Mat> src;
|
vector<Mat> src;
|
||||||
_src.getMatVector(src);
|
_src.getMatVector(src);
|
||||||
@ -661,7 +709,6 @@ void LBPH::train(InputArray _src, InputArray _lbls) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int LBPH::predict(InputArray _src) const {
|
int LBPH::predict(InputArray _src) const {
|
||||||
Mat src = _src.getMat();
|
Mat src = _src.getMat();
|
||||||
// get the spatial histogram from input image
|
// get the spatial histogram from input image
|
||||||
|
@ -46,29 +46,46 @@ inline vector<_Tp> remove_dups(const vector<_Tp>& src) {
|
|||||||
static Mat argsort(InputArray _src, bool ascending=true)
|
static Mat argsort(InputArray _src, bool ascending=true)
|
||||||
{
|
{
|
||||||
Mat src = _src.getMat();
|
Mat src = _src.getMat();
|
||||||
if (src.rows != 1 && src.cols != 1)
|
if (src.rows != 1 && src.cols != 1) {
|
||||||
CV_Error(CV_StsBadArg, "cv::argsort only sorts 1D matrices.");
|
string error_message = "Wrong shape of input matrix! Expected a matrix with one row or column.";
|
||||||
|
error(cv::Exception(CV_StsBadArg, error_message, "argsort", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
int flags = CV_SORT_EVERY_ROW+(ascending ? CV_SORT_ASCENDING : CV_SORT_DESCENDING);
|
int flags = CV_SORT_EVERY_ROW+(ascending ? CV_SORT_ASCENDING : CV_SORT_DESCENDING);
|
||||||
Mat sorted_indices;
|
Mat sorted_indices;
|
||||||
sortIdx(src.reshape(1,1),sorted_indices,flags);
|
sortIdx(src.reshape(1,1),sorted_indices,flags);
|
||||||
return sorted_indices;
|
return sorted_indices;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Mat asRowMatrix(InputArrayOfArrays src, int rtype, double alpha=1, double beta=0)
|
static Mat asRowMatrix(InputArrayOfArrays src, int rtype, double alpha=1, double beta=0) {
|
||||||
{
|
// make sure the input data is a vector of matrices or vector of vector
|
||||||
|
if(src.kind() != _InputArray::STD_VECTOR_MAT && src.kind() != _InputArray::STD_VECTOR_VECTOR) {
|
||||||
|
string error_message = "The data is expected as InputArray::STD_VECTOR_MAT (a std::vector<Mat>) or _InputArray::STD_VECTOR_VECTOR (a std::vector< vector<...> >).";
|
||||||
|
error(cv::Exception(CV_StsBadArg, error_message, "asRowMatrix", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
// number of samples
|
// number of samples
|
||||||
int n = (int) src.total();
|
size_t n = src.total();
|
||||||
// return empty matrix if no data given
|
// return empty matrix if no matrices given
|
||||||
if(n == 0)
|
if(n == 0)
|
||||||
return Mat();
|
return Mat();
|
||||||
// dimensionality of samples
|
// dimensionality of (reshaped) samples
|
||||||
int d = (int)src.getMat(0).total();
|
size_t d = src.getMat(0).total();
|
||||||
// create data matrix
|
// create data matrix
|
||||||
Mat data(n, d, rtype);
|
Mat data(n, d, rtype);
|
||||||
// copy data
|
// now copy data
|
||||||
for(int i = 0; i < n; i++) {
|
for(size_t i = 0; i < n; i++) {
|
||||||
|
// make sure data can be reshaped, throw exception if not!
|
||||||
|
if(src.getMat(i).total() != d) {
|
||||||
|
string error_message = format("Wrong number of elements in matrix #%d! Expected %d was %d.", i, d, src.getMat(i).total());
|
||||||
|
error(cv::Exception(CV_StsBadArg, error_message, "cv::asRowMatrix", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
|
// get a hold of the current row
|
||||||
Mat xi = data.row(i);
|
Mat xi = data.row(i);
|
||||||
|
// 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);
|
src.getMat(i).reshape(1, 1).convertTo(xi, rtype, alpha, beta);
|
||||||
|
} else {
|
||||||
|
src.getMat(i).clone().reshape(1, 1).convertTo(xi, rtype, alpha, beta);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -153,31 +170,44 @@ static bool isSymmetric(InputArray src, double eps=1e-16)
|
|||||||
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// subspace::project
|
// cv::subspaceProject
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
Mat subspaceProject(InputArray _W, InputArray _mean, InputArray _src)
|
Mat subspaceProject(InputArray _W, InputArray _mean, InputArray _src) {
|
||||||
{
|
|
||||||
// get data matrices
|
// get data matrices
|
||||||
Mat W = _W.getMat();
|
Mat W = _W.getMat();
|
||||||
Mat mean = _mean.getMat();
|
Mat mean = _mean.getMat();
|
||||||
Mat src = _src.getMat();
|
Mat src = _src.getMat();
|
||||||
|
// get number of samples and dimension
|
||||||
|
int n = src.rows;
|
||||||
|
int d = src.cols;
|
||||||
|
// make sure the data has the correct shape
|
||||||
|
if(W.rows != d) {
|
||||||
|
string error_message = format("Wrong shapes for given matrices. Was size(src) = (%d,%d), size(W) = (%d,%d).", src.rows, src.cols, W.rows, W.cols);
|
||||||
|
error(cv::Exception(CV_StsBadArg, error_message, "cv::subspace::project", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
|
// make sure mean is correct if not empty
|
||||||
|
if(!mean.empty() && (mean.total() != (size_t) d)) {
|
||||||
|
string error_message = format("Wrong mean shape for the given data matrix. Expected %d, but was %d.", d, mean.total());
|
||||||
|
error(cv::Exception(CV_StsBadArg, error_message, "cv::subspace::project", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
// create temporary matrices
|
// create temporary matrices
|
||||||
Mat X, Y;
|
Mat X, Y;
|
||||||
// copy data & make sure we are using the correct type
|
// make sure you operate on correct type
|
||||||
src.convertTo(X, W.type());
|
src.convertTo(X, W.type());
|
||||||
// get number of samples and dimension
|
// safe to do, because of above assertion
|
||||||
int n = X.rows;
|
if(!mean.empty()) {
|
||||||
int d = X.cols;
|
for(int i=0; i<n; i++) {
|
||||||
// center the data if correct aligned sample mean is given
|
Mat r_i = X.row(i);
|
||||||
if(mean.total() == (size_t)d)
|
subtract(r_i, mean.reshape(1,1), r_i);
|
||||||
subtract(X, repeat(mean.reshape(1,1), n, 1), X);
|
}
|
||||||
|
}
|
||||||
// finally calculate projection as Y = (X-mean)*W
|
// finally calculate projection as Y = (X-mean)*W
|
||||||
gemm(X, W, 1.0, Mat(), 0.0, Y);
|
gemm(X, W, 1.0, Mat(), 0.0, Y);
|
||||||
return Y;
|
return Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// subspace::reconstruct
|
// cv::subspaceReconstruct
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
Mat subspaceReconstruct(InputArray _W, InputArray _mean, InputArray _src)
|
Mat subspaceReconstruct(InputArray _W, InputArray _mean, InputArray _src)
|
||||||
{
|
{
|
||||||
@ -185,16 +215,32 @@ Mat subspaceReconstruct(InputArray _W, InputArray _mean, InputArray _src)
|
|||||||
Mat W = _W.getMat();
|
Mat W = _W.getMat();
|
||||||
Mat mean = _mean.getMat();
|
Mat mean = _mean.getMat();
|
||||||
Mat src = _src.getMat();
|
Mat src = _src.getMat();
|
||||||
// get number of samples
|
// get number of samples and dimension
|
||||||
int n = src.rows;
|
int n = src.rows;
|
||||||
|
int d = src.cols;
|
||||||
|
// make sure the data has the correct shape
|
||||||
|
if(W.cols != d) {
|
||||||
|
string error_message = format("Wrong shapes for given matrices. Was size(src) = (%d,%d), size(W) = (%d,%d).", src.rows, src.cols, W.rows, W.cols);
|
||||||
|
error(cv::Exception(CV_StsBadArg, error_message, "cv::subspaceReconstruct", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
|
// make sure mean is correct if not empty
|
||||||
|
if(!mean.empty() && (mean.total() != (size_t) W.rows)) {
|
||||||
|
string error_message = format("Wrong mean shape for the given eigenvector matrix. Expected %d, but was %d.", W.cols, mean.total());
|
||||||
|
error(cv::Exception(CV_StsBadArg, error_message, "cv::subspaceReconstruct", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
// initalize temporary matrices
|
// initalize temporary matrices
|
||||||
Mat X, Y;
|
Mat X, Y;
|
||||||
// copy data & make sure we are using the correct type
|
// copy data & make sure we are using the correct type
|
||||||
src.convertTo(Y, W.type());
|
src.convertTo(Y, W.type());
|
||||||
// calculate the reconstruction
|
// calculate the reconstruction
|
||||||
gemm(Y, W, 1.0, Mat(), 0.0, X, GEMM_2_T);
|
gemm(Y, W, 1.0, Mat(), 0.0, X, GEMM_2_T);
|
||||||
if(mean.total() == (size_t) X.cols)
|
// safe to do because of above assertion
|
||||||
add(X, repeat(mean.reshape(1,1), n, 1), X);
|
if(!mean.empty()) {
|
||||||
|
for(int i=0; i<n; i++) {
|
||||||
|
Mat r_i = X.row(i);
|
||||||
|
add(r_i, mean.reshape(1,1), r_i);
|
||||||
|
}
|
||||||
|
}
|
||||||
return X;
|
return X;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -607,9 +653,7 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complex vector
|
// Complex vector
|
||||||
|
|
||||||
} else if (q < 0) {
|
} else if (q < 0) {
|
||||||
int l = n1 - 1;
|
int l = n1 - 1;
|
||||||
|
|
||||||
@ -898,8 +942,9 @@ public:
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
void LDA::save(const string& filename) const {
|
void LDA::save(const string& filename) const {
|
||||||
FileStorage fs(filename, FileStorage::WRITE);
|
FileStorage fs(filename, FileStorage::WRITE);
|
||||||
if (!fs.isOpened())
|
if (!fs.isOpened()) {
|
||||||
CV_Error(CV_StsError, "File can't be opened for writing!");
|
CV_Error(CV_StsError, "File can't be opened for writing!");
|
||||||
|
}
|
||||||
this->save(fs);
|
this->save(fs);
|
||||||
fs.release();
|
fs.release();
|
||||||
}
|
}
|
||||||
@ -942,25 +987,35 @@ void LDA::lda(InputArray _src, InputArray _lbls) {
|
|||||||
vector<int> num2label = remove_dups(labels);
|
vector<int> num2label = remove_dups(labels);
|
||||||
map<int, int> label2num;
|
map<int, int> label2num;
|
||||||
for (size_t i = 0; i < num2label.size(); i++)
|
for (size_t i = 0; i < num2label.size(); i++)
|
||||||
label2num[num2label[i]] = (int)i;
|
label2num[num2label[i]] = i;
|
||||||
for (size_t i = 0; i < labels.size(); i++)
|
for (size_t i = 0; i < labels.size(); i++)
|
||||||
mapped_labels[i] = label2num[labels[i]];
|
mapped_labels[i] = label2num[labels[i]];
|
||||||
// get sample size, dimension
|
// get sample size, dimension
|
||||||
int N = data.rows;
|
int N = data.rows;
|
||||||
int D = data.cols;
|
int D = data.cols;
|
||||||
// number of unique labels
|
// number of unique labels
|
||||||
int C = (int)num2label.size();
|
int C = num2label.size();
|
||||||
|
// we can't do a LDA on one class, what do you
|
||||||
|
// want to separate from each other then?
|
||||||
|
if(C == 1) {
|
||||||
|
string error_message = "At least two classes are needed to perform a LDA. Reason: Only one class was given!";
|
||||||
|
error(cv::Exception(CV_StsBadArg, error_message, "cv::LDA::lda", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
// throw error if less labels, than samples
|
// throw error if less labels, than samples
|
||||||
if (labels.size() != (size_t)N)
|
if (labels.size() != static_cast<size_t>(N)) {
|
||||||
CV_Error(CV_StsBadArg, "Error: The number of samples must equal the number of labels.");
|
string error_message = format("The number of samples must equal the number of labels. Given %d labels, %d samples. ", labels.size(), N);
|
||||||
|
error(cv::Exception(CV_StsBadArg, error_message, "LDA::lda", __FILE__, __LINE__));
|
||||||
|
}
|
||||||
// warn if within-classes scatter matrix becomes singular
|
// warn if within-classes scatter matrix becomes singular
|
||||||
if (N < D)
|
if (N < D) {
|
||||||
cout << "Warning: Less observations than feature dimension given!"
|
cout << "Warning: Less observations than feature dimension given!"
|
||||||
<< "Computation will probably fail."
|
<< "Computation will probably fail."
|
||||||
<< endl;
|
<< endl;
|
||||||
|
}
|
||||||
// clip number of components to be a valid number
|
// clip number of components to be a valid number
|
||||||
if ((_num_components <= 0) || (_num_components > (C - 1)))
|
if ((_num_components <= 0) || (_num_components > (C - 1))) {
|
||||||
_num_components = (C - 1);
|
_num_components = (C - 1);
|
||||||
|
}
|
||||||
// holds the mean over all classes
|
// holds the mean over all classes
|
||||||
Mat meanTotal = Mat::zeros(1, D, data.type());
|
Mat meanTotal = Mat::zeros(1, D, data.type());
|
||||||
// holds the mean for each class
|
// holds the mean for each class
|
||||||
@ -979,12 +1034,12 @@ void LDA::lda(InputArray _src, InputArray _lbls) {
|
|||||||
add(meanClass[classIdx], instance, meanClass[classIdx]);
|
add(meanClass[classIdx], instance, meanClass[classIdx]);
|
||||||
numClass[classIdx]++;
|
numClass[classIdx]++;
|
||||||
}
|
}
|
||||||
// calculate means
|
// calculate total mean
|
||||||
meanTotal.convertTo(meanTotal, meanTotal.type(),
|
meanTotal.convertTo(meanTotal, meanTotal.type(), 1.0 / static_cast<double> (N));
|
||||||
1.0 / static_cast<double> (N));
|
// calculate class means
|
||||||
for (int i = 0; i < C; i++)
|
for (int i = 0; i < C; i++) {
|
||||||
meanClass[i].convertTo(meanClass[i], meanClass[i].type(),
|
meanClass[i].convertTo(meanClass[i], meanClass[i].type(), 1.0 / static_cast<double> (numClass[i]));
|
||||||
1.0 / static_cast<double> (numClass[i]));
|
}
|
||||||
// subtract class means
|
// subtract class means
|
||||||
for (int i = 0; i < N; i++) {
|
for (int i = 0; i < N; i++) {
|
||||||
int classIdx = mapped_labels[i];
|
int classIdx = mapped_labels[i];
|
||||||
@ -1031,7 +1086,8 @@ void LDA::compute(InputArray _src, InputArray _lbls) {
|
|||||||
lda(_src.getMat(), _lbls);
|
lda(_src.getMat(), _lbls);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
CV_Error(CV_StsNotImplemented, "This data type is not supported by subspace::LDA::compute.");
|
string error_message= format("InputArray Datatype %d is not supported.", _src.kind());
|
||||||
|
error(cv::Exception(CV_StsBadArg, error_message, "LDA::compute", __FILE__, __LINE__));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,9 @@
|
|||||||
* See <http://www.opensource.org/licenses/bsd-license>
|
* See <http://www.opensource.org/licenses/bsd-license>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "opencv2/opencv.hpp"
|
#include "opencv2/core/core.hpp"
|
||||||
|
#include "opencv2/highgui/highgui.hpp"
|
||||||
|
#include "opencv2/contrib/contrib.hpp"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@ -38,65 +40,102 @@ static Mat toGrayscale(InputArray _src) {
|
|||||||
|
|
||||||
static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') {
|
static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') {
|
||||||
std::ifstream file(filename.c_str(), ifstream::in);
|
std::ifstream file(filename.c_str(), ifstream::in);
|
||||||
if (!file)
|
if (!file) {
|
||||||
throw std::exception();
|
string error_message = "No valid input file was given, please check the given filename.";
|
||||||
|
CV_Error(CV_StsBadArg, error_message);
|
||||||
|
}
|
||||||
string line, path, classlabel;
|
string line, path, classlabel;
|
||||||
while (getline(file, line)) {
|
while (getline(file, line)) {
|
||||||
stringstream liness(line);
|
stringstream liness(line);
|
||||||
getline(liness, path, separator);
|
getline(liness, path, separator);
|
||||||
getline(liness, classlabel);
|
getline(liness, classlabel);
|
||||||
|
if(!path.empty() && !classlabel.empty()) {
|
||||||
images.push_back(imread(path, 0));
|
images.push_back(imread(path, 0));
|
||||||
labels.push_back(atoi(classlabel.c_str()));
|
labels.push_back(atoi(classlabel.c_str()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, const char *argv[]) {
|
int main(int argc, const char *argv[]) {
|
||||||
// check for command line arguments
|
// Check for valid command line arguments, print usage
|
||||||
|
// if no arguments were given.
|
||||||
if (argc != 2) {
|
if (argc != 2) {
|
||||||
cout << "usage: " << argv[0] << " <csv.ext>" << endl;
|
cout << "usage: " << argv[0] << " <csv.ext>" << endl;
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
// path to your CSV
|
// Get the path to your CSV.
|
||||||
string fn_csv = string(argv[1]);
|
string fn_csv = string(argv[1]);
|
||||||
// images and corresponding labels
|
// These vectors hold the images and corresponding labels.
|
||||||
vector<Mat> images;
|
vector<Mat> images;
|
||||||
vector<int> labels;
|
vector<int> labels;
|
||||||
// read in the data
|
// Read in the data. This can fail if no valid
|
||||||
|
// input filename is given.
|
||||||
try {
|
try {
|
||||||
read_csv(fn_csv, images, labels);
|
read_csv(fn_csv, images, labels);
|
||||||
} catch (exception&) {
|
} catch (cv::Exception& e) {
|
||||||
cerr << "Error opening file \"" << fn_csv << "\"." << endl;
|
cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl;
|
||||||
|
// nothing more we can do
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
// get width and height
|
// Quit if there are not enough images for this demo.
|
||||||
//int width = images[0].cols;
|
if(images.size() <= 1) {
|
||||||
|
string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!";
|
||||||
|
CV_Error(CV_StsError, error_message);
|
||||||
|
}
|
||||||
|
// Get the height from the first image. We'll need this
|
||||||
|
// later in code to reshape the images to their original
|
||||||
|
// size:
|
||||||
int height = images[0].rows;
|
int height = images[0].rows;
|
||||||
// get test instances
|
// The following lines simply get the last images from
|
||||||
|
// your dataset and remove it from the vector. This is
|
||||||
|
// done, so that the training data (which we learn the
|
||||||
|
// cv::FaceRecognizer on) and the test data we test
|
||||||
|
// the model with, do not overlap.
|
||||||
Mat testSample = images[images.size() - 1];
|
Mat testSample = images[images.size() - 1];
|
||||||
int testLabel = labels[labels.size() - 1];
|
int testLabel = labels[labels.size() - 1];
|
||||||
// ... and delete last element
|
|
||||||
images.pop_back();
|
images.pop_back();
|
||||||
labels.pop_back();
|
labels.pop_back();
|
||||||
// build the Fisherfaces model
|
// The following lines create an Eigenfaces model for
|
||||||
Ptr<FaceRecognizer> model = createFisherFaceRecognizer();
|
// face recognition and train it with the images and
|
||||||
|
// labels read from the given CSV file.
|
||||||
|
// This here is a full PCA, if you just want to keep
|
||||||
|
// 10 principal components (read Eigenfaces), then call
|
||||||
|
// the factory method like this:
|
||||||
|
//
|
||||||
|
// cv::createEigenFaceRecognizer(10);
|
||||||
|
Ptr<FaceRecognizer> model = createEigenFaceRecognizer();
|
||||||
model->train(images, labels);
|
model->train(images, labels);
|
||||||
// test model
|
// The following line predicts the label of a given
|
||||||
|
// test image. In this example no thresholding is
|
||||||
|
// done.
|
||||||
int predicted = model->predict(testSample);
|
int predicted = model->predict(testSample);
|
||||||
cout << "predicted class = " << predicted << endl;
|
// Show the prediction and actual class of the given
|
||||||
cout << "actual class = " << testLabel << endl;
|
// sample:
|
||||||
// get the eigenvectors
|
string result_message = format("Predicted class=%d / Actual class=%d.", predicted, testLabel);
|
||||||
Mat W = model->eigenvectors();
|
cout << result_message << endl;
|
||||||
// show first 10 fisherfaces
|
// Sometimes you'll need to get some internal model data,
|
||||||
|
// which isn't exposed by the public cv::FaceRecognizer.
|
||||||
|
// Since each cv::FaceRecognizer is derived from a
|
||||||
|
// cv::Algorithm, you can query the data.
|
||||||
|
//
|
||||||
|
// Here is how to get the eigenvalues of this Eigenfaces model:
|
||||||
|
Mat eigenvalues = model->getMat("eigenvalues");
|
||||||
|
// And we can do the same to display the Eigenvectors ("Eigenfaces"):
|
||||||
|
Mat W = model->getMat("eigenvectors");
|
||||||
|
// From this we will display the (at most) first 10 Eigenfaces:
|
||||||
for (int i = 0; i < min(10, W.cols); i++) {
|
for (int i = 0; i < min(10, W.cols); i++) {
|
||||||
|
string msg = format("Eigenvalue #%d = %.5f", i, eigenvalues.at<double>(i));
|
||||||
|
cout << msg << endl;
|
||||||
// get eigenvector #i
|
// get eigenvector #i
|
||||||
Mat ev = W.col(i).clone();
|
Mat ev = W.col(i).clone();
|
||||||
// reshape to original size AND normalize between [0...255]
|
// Reshape to original size & normalize to [0...255] for imshow.
|
||||||
Mat grayscale = toGrayscale(ev.reshape(1, height));
|
Mat grayscale = toGrayscale(ev.reshape(1, height));
|
||||||
// show image (with Jet colormap)
|
// Show the image & apply a Jet colormap for better sensing.
|
||||||
Mat cgrayscale;
|
Mat cgrayscale;
|
||||||
applyColorMap(grayscale, cgrayscale, COLORMAP_JET);
|
applyColorMap(grayscale, cgrayscale, COLORMAP_JET);
|
||||||
imshow(format("%d", i), cgrayscale);
|
imshow(format("%d", i), cgrayscale);
|
||||||
}
|
}
|
||||||
waitKey(0);
|
waitKey(0);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user