140 lines
5.7 KiB
C++
140 lines
5.7 KiB
C++
/*
|
||
* Copyright (c) 2011,2012. Philipp Wagner <bytefish[at]gmx[dot]de>.
|
||
* Released to public domain under terms of the BSD Simplified license.
|
||
*
|
||
* Redistribution and use in source and binary forms, with or without
|
||
* modification, are permitted provided that the following conditions are met:
|
||
* * Redistributions of source code must retain the above copyright
|
||
* notice, this list of conditions and the following disclaimer.
|
||
* * Redistributions in binary form must reproduce the above copyright
|
||
* notice, this list of conditions and the following disclaimer in the
|
||
* documentation and/or other materials provided with the distribution.
|
||
* * Neither the name of the organization nor the names of its contributors
|
||
* may be used to endorse or promote products derived from this software
|
||
* without specific prior written permission.
|
||
*
|
||
* See <http://www.opensource.org/licenses/bsd-license>
|
||
*/
|
||
#include "precomp.hpp"
|
||
#include <opencv2/face.hpp>
|
||
#include "face_utils.hpp"
|
||
#include <set>
|
||
#include <limits>
|
||
#include <iostream>
|
||
|
||
namespace cv
|
||
{
|
||
namespace face
|
||
{
|
||
|
||
// Turk, M., and Pentland, A. "Eigenfaces for recognition.". Journal of
|
||
// Cognitive Neuroscience 3 (1991), 71–86.
|
||
class Eigenfaces : public EigenFaceRecognizer
|
||
{
|
||
|
||
public:
|
||
// Initializes an empty Eigenfaces model.
|
||
Eigenfaces(int num_components = 0, double threshold = DBL_MAX)
|
||
//: BasicFaceRecognizerImpl(num_components, threshold)
|
||
{
|
||
_num_components = num_components;
|
||
_threshold = threshold;
|
||
}
|
||
|
||
// Computes an Eigenfaces model with images in src and corresponding labels
|
||
// in labels.
|
||
void train(InputArrayOfArrays src, InputArray labels) CV_OVERRIDE;
|
||
|
||
// Send all predict results to caller side for custom result handling
|
||
void predict(InputArray src, Ptr<PredictCollector> collector) const CV_OVERRIDE;
|
||
String getDefaultName() const CV_OVERRIDE
|
||
{
|
||
return "opencv_eigenfaces";
|
||
}
|
||
};
|
||
|
||
//------------------------------------------------------------------------------
|
||
// Eigenfaces
|
||
//------------------------------------------------------------------------------
|
||
void Eigenfaces::train(InputArrayOfArrays _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.");
|
||
CV_Error(Error::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());
|
||
CV_Error(Error::StsBadArg, error_message);
|
||
}
|
||
// make sure data has correct size
|
||
if(_src.total() > 1) {
|
||
for(int i = 1; i < static_cast<int>(_src.total()); i++) {
|
||
if(_src.getMat(i-1).total() != _src.getMat(i).total()) {
|
||
String error_message = format("In the Eigenfaces method all input samples (training images) must be of equal size! Expected %zu pixels, but was %zu pixels.", _src.getMat(i-1).total(), _src.getMat(i).total());
|
||
CV_Error(Error::StsUnsupportedFormat, error_message);
|
||
}
|
||
}
|
||
}
|
||
// get labels
|
||
Mat labels = _local_labels.getMat();
|
||
// observations in row
|
||
Mat data = asRowMatrix(_src, CV_64FC1);
|
||
|
||
// number of samples
|
||
int n = data.rows;
|
||
// 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)=%zu.", n, labels.total());
|
||
CV_Error(Error::StsBadArg, error_message);
|
||
}
|
||
// clear existing model data
|
||
_labels.release();
|
||
_projections.clear();
|
||
// clip number of components to be valid
|
||
if((_num_components <= 0) || (_num_components > n))
|
||
_num_components = n;
|
||
|
||
// perform the PCA
|
||
PCA pca(data, Mat(), PCA::DATA_AS_ROW, _num_components);
|
||
// copy the PCA results
|
||
_mean = pca.mean.reshape(1,1); // store the mean vector
|
||
_eigenvalues = pca.eigenvalues.clone(); // eigenvalues by row
|
||
transpose(pca.eigenvectors, _eigenvectors); // eigenvectors by column
|
||
// store labels for prediction
|
||
_labels = labels.clone();
|
||
// save projections
|
||
for(int sampleIdx = 0; sampleIdx < data.rows; sampleIdx++) {
|
||
Mat p = LDA::subspaceProject(_eigenvectors, _mean, data.row(sampleIdx));
|
||
_projections.push_back(p);
|
||
}
|
||
}
|
||
|
||
void Eigenfaces::predict(InputArray _src, Ptr<PredictCollector> collector) 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?";
|
||
CV_Error(Error::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 %zu.", _eigenvectors.rows, src.total());
|
||
CV_Error(Error::StsBadArg, error_message);
|
||
}
|
||
// project into PCA subspace
|
||
Mat q = LDA::subspaceProject(_eigenvectors, _mean, src.reshape(1, 1));
|
||
collector->init(_projections.size());
|
||
for (size_t sampleIdx = 0; sampleIdx < _projections.size(); sampleIdx++) {
|
||
double dist = norm(_projections[sampleIdx], q, NORM_L2);
|
||
int label = _labels.at<int>((int)sampleIdx);
|
||
if (!collector->collect(label, dist))return;
|
||
}
|
||
}
|
||
|
||
Ptr<EigenFaceRecognizer> EigenFaceRecognizer::create(int num_components, double threshold)
|
||
{
|
||
return makePtr<Eigenfaces>(num_components, threshold);
|
||
}
|
||
|
||
}
|
||
}
|