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<...> >).";
+ 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
{
private:
int _num_components;
+ double _threshold;
vector<Mat> _projections;
Mat _labels;
Mat _eigenvectors;
using FaceRecognizer::load;
// Initializes an empty Eigenfaces model.
- Eigenfaces(int num_components = 0) :
- _num_components(num_components) { }
+ Eigenfaces(int num_components = 0, double threshold = DBL_MAX) :
+ _num_components(num_components),
+ _threshold(threshold) {}
// Initializes and computes an Eigenfaces model with images in src and
// corresponding labels in labels. num_components will be kept for
// classification.
Eigenfaces(InputArray src, InputArray labels,
- int num_components = 0) :
- _num_components(num_components) {
+ int num_components = 0,
+ double threshold = DBL_MAX) :
+ _num_components(num_components),
+ _threshold(threshold) {
train(src, labels);
}
// Predicts the label of a query image in src.
int predict(InputArray src) const;
+ // Predicts the label and confidence for a given sample.
+ void predict(InputArray _src, int &label, double &dist) const;
+
// See FaceRecognizer::load.
void load(const FileStorage& fs);
{
private:
int _num_components;
+ double _threshold;
Mat _eigenvectors;
Mat _eigenvalues;
Mat _mean;
using FaceRecognizer::load;
// Initializes an empty Fisherfaces model.
- Fisherfaces(int num_components = 0) :
- _num_components(num_components) {}
+ Fisherfaces(int num_components = 0, double threshold = DBL_MAX) :
+ _num_components(num_components),
+ _threshold(threshold) {}
// Initializes and computes a Fisherfaces model with images in src and
// corresponding labels in labels. num_components will be kept for
// classification.
Fisherfaces(InputArray src,
InputArray labels,
- int num_components = 0) :
- _num_components(num_components) {
+ int num_components = 0,
+ double threshold = DBL_MAX) :
+ _num_components(num_components),
+ _threshold(threshold) {
train(src, labels);
}
// Predicts the label of a query image in src.
int predict(InputArray src) const;
+ // Predicts the label and confidence for a given sample.
+ void predict(InputArray _src, int &label, double &dist) const;
+
// See FaceRecognizer::load.
virtual void load(const FileStorage& fs);
int _grid_y;
int _radius;
int _neighbors;
+ double _threshold;
vector<Mat> _histograms;
Mat _labels;
//
// 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) :
+ LBPH(int radius=1, int neighbors=8, int grid_x=8, int grid_y=8, double threshold = DBL_MAX) :
_grid_x(grid_x),
_grid_y(grid_y),
_radius(radius),
- _neighbors(neighbors) {}
+ _neighbors(neighbors),
+ _threshold(threshold) {}
// Initializes and computes this LBPH Model. The current implementation is
// rather fixed as it uses the Extended Local Binary Patterns per default.
LBPH(InputArray src,
InputArray labels,
int radius=1, int neighbors=8,
- int grid_x=8, int grid_y=8) :
+ int grid_x=8, int grid_y=8,
+ double threshold = DBL_MAX) :
_grid_x(grid_x),
_grid_y(grid_y),
_radius(radius),
- _neighbors(neighbors) {
+ _neighbors(neighbors),
+ _threshold(threshold) {
train(src, labels);
}
// Predicts the label of a query image in src.
int predict(InputArray src) const;
+ // Predicts the label and confidence for a given sample.
+ void predict(InputArray _src, int &label, double &dist) const;
+
// See FaceRecognizer::load.
void load(const FileStorage& fs);
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__));
+ CV_Error(CV_StsBadArg, error_message);
} 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__));
+ CV_Error(CV_StsBadArg, error_message);
}
// get labels
Mat labels = _local_labels.getMat();
// assert there are as much samples as 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__));
+ CV_Error(CV_StsBadArg, error_message);
}
// clip number of components to be valid
if((_num_components <= 0) || (_num_components > n))
}
}
-int Eigenfaces::predict(InputArray _src) const {
+void Eigenfaces::predict(InputArray _src, int &minClass, double &minDist) 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__));
+ CV_Error(CV_StsError, error_message);
} 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__));
+ CV_Error(CV_StsBadArg, error_message);
}
// project into PCA subspace
Mat q = subspaceProject(_eigenvectors, _mean, src.reshape(1,1));
- double minDist = DBL_MAX;
- int minClass = -1;
+ minDist = DBL_MAX;
+ minClass = -1;
for(size_t sampleIdx = 0; sampleIdx < _projections.size(); sampleIdx++) {
double dist = norm(_projections[sampleIdx], q, NORM_L2);
- if(dist < minDist) {
+ if((dist < minDist) && (dist < _threshold)) {
minDist = dist;
minClass = _labels.at<int>(sampleIdx);
}
}
- return minClass;
+}
+
+int Eigenfaces::predict(InputArray _src) const {
+ int label;
+ double dummy;
+ predict(_src, label, dummy);
+ return label;
}
void Eigenfaces::load(const FileStorage& fs) {
void Fisherfaces::train(InputArray src, InputArray _lbls) {
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__));
+ CV_Error(CV_StsBadArg, error_message);
} 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__));
+ CV_Error(CV_StsBadArg, error_message);
}
// get data
Mat labels = _lbls.getMat();
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__));
+ 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());
+ CV_Error(CV_StsBadArg, error_message);
} 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__));
+ CV_Error(CV_StsBadArg, error_message);
}
// Get the number of unique classes
// TODO Provide a cv::Mat version?
}
}
-int Fisherfaces::predict(InputArray _src) const {
+void Fisherfaces::predict(InputArray _src, int &minClass, double &minDist) 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__));
+ CV_Error(CV_StsBadArg, error_message);
} 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__));
+ CV_Error(CV_StsBadArg, error_message);
}
// project into LDA subspace
Mat q = subspaceProject(_eigenvectors, _mean, src.reshape(1,1));
// find 1-nearest neighbor
- double minDist = DBL_MAX;
- int minClass = -1;
+ minDist = DBL_MAX;
+ minClass = -1;
for(size_t sampleIdx = 0; sampleIdx < _projections.size(); sampleIdx++) {
double dist = norm(_projections[sampleIdx], q, NORM_L2);
- if(dist < minDist) {
+ if((dist < minDist) && (dist < _threshold)) {
minDist = dist;
minClass = _labels.at<int>(sampleIdx);
}
}
- return minClass;
}
+int Fisherfaces::predict(InputArray _src) const {
+ int label;
+ double dummy;
+ predict(_src, label, dummy);
+ return label;
+}
// See FaceRecognizer::load.
void Fisherfaces::load(const FileStorage& fs) {
void LBPH::train(InputArray _src, InputArray _lbls) {
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__));
+ CV_Error(CV_StsBadArg, error_message);
} 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__));
+ CV_Error(CV_StsUnsupportedFormat, error_message);
} 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__));
+ CV_Error(CV_StsUnsupportedFormat, error_message);
}
// get the vector of matrices
vector<Mat> src;
// turn the label matrix into a vector
Mat labels = _lbls.getMat();
CV_Assert( labels.type() == CV_32S && (labels.cols == 1 || labels.rows == 1));
- if(labels.total() != src.size())
+ if(labels.total() != src.size()) {
CV_Error(CV_StsUnsupportedFormat, "The number of labels must equal the number of samples.");
+ }
// store given labels
labels.copyTo(_labels);
// store the spatial histograms of the original data
}
}
-int LBPH::predict(InputArray _src) const {
+void LBPH::predict(InputArray _src, int &minClass, double &minDist) const {
Mat src = _src.getMat();
// get the spatial histogram from input image
Mat lbp_image = elbp(src, _radius, _neighbors);
_grid_y, /* grid size y */
true /* normed histograms */);
// find 1-nearest neighbor
- double minDist = DBL_MAX;
- int minClass = -1;
+ minDist = DBL_MAX;
+ minClass = -1;
for(size_t sampleIdx = 0; sampleIdx < _histograms.size(); sampleIdx++) {
double dist = compareHist(_histograms[sampleIdx], query, CV_COMP_CHISQR);
- if(dist < minDist) {
+ if((dist < minDist) && (dist < _threshold)) {
minDist = dist;
minClass = _labels.at<int>(sampleIdx);
}
}
- return minClass;
}
+
+int LBPH::predict(InputArray _src) const {
+ int label;
+ double dummy;
+ predict(_src, label, dummy);
+ return label;
+}
+
-
-Ptr<FaceRecognizer> createEigenFaceRecognizer(int num_components)
+Ptr<FaceRecognizer> createEigenFaceRecognizer(int num_components, double threshold)
{
- return new Eigenfaces(num_components);
+ return new Eigenfaces(num_components, threshold);
}
-Ptr<FaceRecognizer> createFisherFaceRecognizer(int num_components)
+Ptr<FaceRecognizer> createFisherFaceRecognizer(int num_components, double threshold)
{
- return new Fisherfaces(num_components);
+ return new Fisherfaces(num_components, threshold);
}
Ptr<FaceRecognizer> createLBPHFaceRecognizer(int radius, int neighbors,
- int grid_x, int grid_y)
+ int grid_x, int grid_y, double threshold)
{
- return new LBPH(radius, neighbors, grid_x, grid_y);
+ return new LBPH(radius, neighbors, grid_x, grid_y, threshold);
}
CV_INIT_ALGORITHM(Eigenfaces, "FaceRecognizer.Eigenfaces",
obj.info()->addParam(obj, "ncomponents", obj._num_components);
+ obj.info()->addParam(obj, "threshold", obj._threshold);
obj.info()->addParam(obj, "projections", obj._projections, true);
obj.info()->addParam(obj, "labels", obj._labels, true);
obj.info()->addParam(obj, "eigenvectors", obj._eigenvectors, true);
CV_INIT_ALGORITHM(Fisherfaces, "FaceRecognizer.Fisherfaces",
obj.info()->addParam(obj, "ncomponents", obj._num_components);
+ obj.info()->addParam(obj, "threshold", obj._threshold);
obj.info()->addParam(obj, "projections", obj._projections, true);
obj.info()->addParam(obj, "labels", obj._labels, true);
obj.info()->addParam(obj, "eigenvectors", obj._eigenvectors, true);
obj.info()->addParam(obj, "neighbors", obj._neighbors);
obj.info()->addParam(obj, "grid_x", obj._grid_x);
obj.info()->addParam(obj, "grid_y", obj._grid_y);
+ obj.info()->addParam(obj, "threshold", obj._threshold);
obj.info()->addParam(obj, "histograms", obj._histograms, true);
obj.info()->addParam(obj, "labels", obj._labels, true));
Mat src = _src.getMat();
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__));
+ CV_Error(CV_StsBadArg, error_message);
}
int flags = CV_SORT_EVERY_ROW+(ascending ? CV_SORT_ASCENDING : CV_SORT_DESCENDING);
Mat sorted_indices;
// 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__));
+ CV_Error(CV_StsBadArg, error_message);
}
// number of samples
size_t n = src.total();
// 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__));
+ CV_Error(CV_StsBadArg, error_message);
}
// get a hold of the current row
Mat xi = data.row(i);
}
static void sortMatrixColumnsByIndices(InputArray _src, InputArray _indices, OutputArray _dst) {
- if(_indices.getMat().type() != CV_32SC1)
+ if(_indices.getMat().type() != CV_32SC1) {
CV_Error(CV_StsUnsupportedFormat, "cv::sortColumnsByIndices only works on integer indices!");
+ }
Mat src = _src.getMat();
vector<int> indices = _indices.getMat();
_dst.create(src.rows, src.cols, src.type());
// 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__));
+ CV_Error(CV_StsBadArg, error_message);
}
// 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__));
+ CV_Error(CV_StsBadArg, error_message);
}
// create temporary matrices
Mat X, Y;
// 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__));
+ CV_Error(CV_StsBadArg, error_message);
}
// 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__));
+ CV_Error(CV_StsBadArg, error_message);
}
// initalize temporary matrices
Mat X, Y;
// 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__));
+ CV_Error(CV_StsBadArg, error_message);
}
// throw error if less labels, than samples
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__));
+ CV_Error(CV_StsBadArg, error_message);
}
// warn if within-classes scatter matrix becomes singular
if (N < D) {
break;
default:
string error_message= format("InputArray Datatype %d is not supported.", _src.kind());
- error(cv::Exception(CV_StsBadArg, error_message, "LDA::compute", __FILE__, __LINE__));
+ CV_Error(CV_StsBadArg, error_message);
break;
}
}
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) {
- string error_message = "No valid input file was given, please check the given filename.";
+ string error_message = "No valid input file was given, please check the given filename.";
CV_Error(CV_StsBadArg, error_message);
}
string line, path, classlabel;
int main(int argc, const char *argv[]) {
// Check for valid command line arguments, print usage
- // if no arguments were given.
+ // if no arguments were given.
if (argc != 2) {
cout << "usage: " << argv[0] << " <csv.ext>" << endl;
exit(1);
}
// 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);
+ 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
// 10 principal components (read Eigenfaces), then call
// the factory method like this:
//
- // cv::createEigenFaceRecognizer(10);
+ // cv::createEigenFaceRecognizer(10);
+ //
+ // If you want to create a FaceRecognizer with a
+ // confidennce threshold, call it with:
+ //
+ // cv::createEigenFaceRecognizer(10, 123.0);
+ //
Ptr<FaceRecognizer> model = createEigenFaceRecognizer();
model->train(images, labels);
// The following line predicts the label of a given
- // test image. In this example no thresholding is
- // done.
- int predicted = model->predict(testSample);
- // Show the prediction and actual class of the given
- // sample:
- string result_message = format("Predicted class=%d / Actual class=%d.", predicted, testLabel);
+ // test image:
+ int predictedLabel = model->predict(testSample);
+ //
+ // To get the confidence of a prediction call it with:
+ //
+ // model with:
+ // int predictedLabel = -1;
+ // double confidence = 0.0;
+ // model->predict(testSample, predictedLabel, confidence);
+ //
+ string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, 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:
+ // Sometimes you'll need to get/set 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.
+ //
+ // First we'll use it to set the threshold of the FaceRecognizer
+ // without retraining the model:
+ //
+ model->set("threshold", 0.0);
+ // Now the threshold is of this model is 0.0. A prediction
+ // now returns -1, as it's impossible to have a distance
+ // below it
+ //
+ predictedLabel = model->predict(testSample);
+ cout << "Predicted class = " << predictedLabel << endl;
+ // Now 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"):
+ // And we can do the same to display the Eigenvectors (read 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;
+ 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 & normalize to [0...255] for imshow.