mirror of
https://github.com/opencv/opencv.git
synced 2025-01-18 14:13:15 +08:00
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.
|
||||
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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user