vendor: OpenCV 5.0.0 snapshot at 755e50675d97db9b7d449d8bd6b09888646f6c6e
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
By downloading, copying, installing or using the software you agree to this license.
|
||||
If you do not agree to this license, do not download, install,
|
||||
copy or use the software.
|
||||
|
||||
|
||||
License Agreement
|
||||
For Open Source Computer Vision Library
|
||||
(3-clause BSD License)
|
||||
|
||||
Copyright (C) 2000-2015, Intel Corporation, all rights reserved.
|
||||
Copyright (C) 2009-2011, Willow Garage Inc., all rights reserved.
|
||||
Copyright (C) 2009-2015, NVIDIA Corporation, all rights reserved.
|
||||
Copyright (C) 2010-2013, Advanced Micro Devices, Inc., all rights reserved.
|
||||
Copyright (C) 2015, OpenCV Foundation, all rights reserved.
|
||||
Copyright (C) 2015, Itseez Inc., all rights reserved.
|
||||
Third party copyrights are property of their respective owners.
|
||||
|
||||
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 names of the copyright holders nor the names of the contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and
|
||||
any express or implied warranties, including, but not limited to, the implied
|
||||
warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
In no event shall copyright holders or contributors be liable for any direct,
|
||||
indirect, incidental, special, exemplary, or consequential damages
|
||||
(including, but not limited to, procurement of substitute goods or services;
|
||||
loss of use, data, or profits; or business interruption) however caused
|
||||
and on any theory of liability, whether in contract, strict liability,
|
||||
or tort (including negligence or otherwise) arising in any way out of
|
||||
the use of this software, even if advised of the possibility of such damage.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
This file contains implementation of the bio-inspired features (BIF) approach
|
||||
for computing image descriptors, applicable for human age estimation. For more
|
||||
details we refer to [1,2].
|
||||
|
||||
REFERENCES
|
||||
[1] Guo, Guodong, et al. "Human age estimation using bio-inspired features."
|
||||
Computer Vision and Pattern Recognition, 2009. CVPR 2009.
|
||||
[2] Spizhevoi, A. S., and A. V. Bovyrin. "Estimating human age using
|
||||
bio-inspired features and the ranking method." Pattern Recognition and
|
||||
Image Analysis 25.3 (2015): 547-552.
|
||||
*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/face/bif.hpp"
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
// The constants below are taken from paper [1].
|
||||
|
||||
const int kNumBandsMax = 8;
|
||||
|
||||
const cv::Size kCellSizes[kNumBandsMax] = {
|
||||
cv::Size(6,6), cv::Size(8,8), cv::Size(10,10), cv::Size(12,12),
|
||||
cv::Size(14,14), cv::Size(16,16), cv::Size(18,18), cv::Size(20,20)
|
||||
};
|
||||
|
||||
const cv::Size kGaborSize[kNumBandsMax][2] = {
|
||||
{cv::Size(5,5), cv::Size(7,7)}, {cv::Size(9,9), cv::Size(11,11)},
|
||||
{cv::Size(13,13), cv::Size(15,15)}, {cv::Size(17,17), cv::Size(19,19)},
|
||||
{cv::Size(21,21), cv::Size(23,23)}, {cv::Size(25,25), cv::Size(27,27)},
|
||||
{cv::Size(29,29), cv::Size(31,31)}, {cv::Size(33,33), cv::Size(35,35)}
|
||||
};
|
||||
|
||||
const double kGaborGamma = 0.3;
|
||||
|
||||
const double kGaborSigmas[kNumBandsMax][2] = {
|
||||
{2.0, 2.8}, {3.6, 4.5}, {5.4, 6.3}, {7.3, 8.2},
|
||||
{9.2, 10.2}, {11.3, 12.3}, {13.4, 14.6}, {15.8, 17.0}
|
||||
};
|
||||
|
||||
const double kGaborWavelens[kNumBandsMax][2] = {
|
||||
{2.5, 3.5}, {4.6, 5.6}, {6.8, 7.9}, {9.1, 10.3},
|
||||
{11.5, 12.7}, {14.1, 15.4}, {16.8, 18.2}, {19.7, 21.2}
|
||||
};
|
||||
|
||||
class BIFImpl CV_FINAL : public cv::face::BIF {
|
||||
public:
|
||||
BIFImpl(int num_bands, int num_rotations) {
|
||||
initUnits(num_bands, num_rotations);
|
||||
}
|
||||
|
||||
virtual int getNumBands() const CV_OVERRIDE { return num_bands_; }
|
||||
|
||||
virtual int getNumRotations() const CV_OVERRIDE { return num_rotations_; }
|
||||
|
||||
virtual void compute(cv::InputArray image,
|
||||
cv::OutputArray features) const CV_OVERRIDE;
|
||||
|
||||
private:
|
||||
struct UnitParams {
|
||||
cv::Size cell_size;
|
||||
cv::Mat filter1, filter2;
|
||||
};
|
||||
|
||||
void initUnits(int num_bands, int num_rotations);
|
||||
void computeUnit(int unit_idx, const cv::Mat &img, cv::Mat &dst) const;
|
||||
|
||||
int num_bands_;
|
||||
int num_rotations_;
|
||||
std::vector<UnitParams> units_;
|
||||
};
|
||||
|
||||
void BIFImpl::compute(cv::InputArray _image,
|
||||
cv::OutputArray _features) const {
|
||||
cv::Mat image = _image.getMat();
|
||||
CV_Assert(image.type() == CV_32F);
|
||||
|
||||
std::vector<cv::Mat> fea_units(units_.size());
|
||||
int fea_dim = 0;
|
||||
|
||||
for (size_t i = 0; i < units_.size(); ++i) {
|
||||
computeUnit(static_cast<int>(i), image, fea_units[i]);
|
||||
fea_dim += fea_units[i].rows;
|
||||
}
|
||||
|
||||
_features.create(fea_dim, 1, CV_32F);
|
||||
cv::Mat fea = _features.getMat();
|
||||
|
||||
int offset = 0;
|
||||
for (size_t i = 0; i < fea_units.size(); ++i) {
|
||||
cv::Mat roi = fea.rowRange(offset, offset + fea_units[i].rows);
|
||||
fea_units[i].copyTo(roi);
|
||||
offset += fea_units[i].rows;
|
||||
}
|
||||
CV_Assert(offset == fea_dim);
|
||||
}
|
||||
|
||||
void BIFImpl::initUnits(int num_bands, int num_rotations) {
|
||||
CV_Assert(num_bands > 0 && num_bands <= kNumBandsMax);
|
||||
CV_Assert(num_rotations > 0);
|
||||
|
||||
num_bands_ = num_bands;
|
||||
num_rotations_ = num_rotations;
|
||||
|
||||
for (int ri = 0; ri < num_rotations; ++ri) {
|
||||
double angle = CV_PI / num_rotations * ri;
|
||||
|
||||
for (int bi = 0; bi < num_bands; ++bi) {
|
||||
cv::Mat kernel[2];
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
kernel[i] = cv::getGaborKernel(
|
||||
kGaborSize[bi][i], kGaborSigmas[bi][i], angle,
|
||||
kGaborWavelens[bi][i], kGaborGamma, 0, CV_32F);
|
||||
|
||||
// Make variance for the Gaussian part of the Gabor filter
|
||||
// the same across all filters.
|
||||
kernel[i] /= 2 * kGaborSigmas[bi][i] * kGaborSigmas[bi][i]
|
||||
/ kGaborGamma;
|
||||
}
|
||||
|
||||
UnitParams unit;
|
||||
unit.cell_size = kCellSizes[bi];
|
||||
unit.filter1 = kernel[0];
|
||||
unit.filter2 = kernel[1];
|
||||
units_.push_back(unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BIFImpl::computeUnit(int unit_idx, const cv::Mat &img,
|
||||
cv::Mat &dst) const {
|
||||
cv::Mat resp1, resp2;
|
||||
cv::filter2D(img, resp1, CV_32F, units_[unit_idx].filter1);
|
||||
cv::filter2D(img, resp2, CV_32F, units_[unit_idx].filter2);
|
||||
|
||||
cv::Mat resp, sum, sumsq;
|
||||
cv::max(resp1, resp2, resp);
|
||||
cv::integral(resp, sum, sumsq);
|
||||
|
||||
int Hhalf = units_[unit_idx].cell_size.height / 2;
|
||||
int Whalf = units_[unit_idx].cell_size.width / 2;
|
||||
|
||||
int nrows = (resp.rows + Hhalf - 1) / Hhalf;
|
||||
int ncols = (resp.cols + Whalf - 1) / Whalf;
|
||||
dst.create(nrows*ncols, 1, CV_32F);
|
||||
|
||||
for (int pos = 0, yc = 0; yc < resp.rows; yc += Hhalf) {
|
||||
int y0 = std::max(0, yc - Hhalf);
|
||||
int y1 = std::min(resp.rows, yc + Hhalf);
|
||||
|
||||
for (int xc = 0; xc < resp.cols; xc += Whalf, ++pos) {
|
||||
int x0 = std::max(0, xc - Whalf);
|
||||
int x1 = std::min(resp.cols, xc + Whalf);
|
||||
int area = (y1-y0) * (x1-x0);
|
||||
|
||||
double mean = sum.at<double>(y1,x1) - sum.at<double>(y1,x0)
|
||||
- sum.at<double>(y0,x1) + sum.at<double>(y0,x0);
|
||||
mean /= area;
|
||||
|
||||
double sd = sumsq.at<double>(y1,x1) - sumsq.at<double>(y1,x0)
|
||||
- sumsq.at<double>(y0,x1) + sumsq.at<double>(y0,x0);
|
||||
sd = sqrt(std::max(0.0, sd / area - mean * mean));
|
||||
|
||||
dst.at<float>(pos) = static_cast<float>(sd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
cv::Ptr<cv::face::BIF> cv::face::BIF::create(int num_bands, int num_rotations) {
|
||||
return cv::Ptr<cv::face::BIF>(new BIFImpl(num_bands, num_rotations));
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "face_alignmentimpl.hpp"
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace cv{
|
||||
namespace face{
|
||||
|
||||
FacemarkKazemi::~FacemarkKazemi(){}
|
||||
FacemarkKazemiImpl:: ~FacemarkKazemiImpl(){}
|
||||
unsigned long FacemarkKazemiImpl::left(unsigned long index){
|
||||
return 2*index+1;
|
||||
}
|
||||
unsigned long FacemarkKazemiImpl::right(unsigned long index){
|
||||
return 2*index+2;
|
||||
}
|
||||
bool FacemarkKazemiImpl::setFaceDetector(FN_FaceDetector f, void* userData){
|
||||
faceDetector = f;
|
||||
faceDetectorData = userData;
|
||||
//printf("face detector is configured\n");
|
||||
return true;
|
||||
}
|
||||
bool FacemarkKazemiImpl::getFaces(InputArray image, OutputArray faces)
|
||||
{
|
||||
CV_Assert(faceDetector);
|
||||
return faceDetector(image, faces, faceDetectorData);
|
||||
}
|
||||
FacemarkKazemiImpl::FacemarkKazemiImpl(const FacemarkKazemi::Params& parameters) :
|
||||
faceDetector(NULL),
|
||||
faceDetectorData(NULL)
|
||||
{
|
||||
minmeanx=8000.0;
|
||||
maxmeanx=0.0;
|
||||
minmeany=8000.0;
|
||||
maxmeany=0.0;
|
||||
isModelLoaded =false;
|
||||
params = parameters;
|
||||
}
|
||||
FacemarkKazemi::Params::Params(){
|
||||
//These variables are used for training data
|
||||
//These are initialised as described in the research paper
|
||||
//referenced above
|
||||
cascade_depth = 15;
|
||||
tree_depth = 5;
|
||||
num_trees_per_cascade_level = 500;
|
||||
learning_rate = float(0.1);
|
||||
oversampling_amount = 20;
|
||||
num_test_coordinates = 500;
|
||||
lambda = float(0.1);
|
||||
num_test_splits = 20;
|
||||
}
|
||||
bool FacemarkKazemiImpl::convertToActual(Rect r,Mat &warp){
|
||||
Point2f srcTri[3],dstTri[3];
|
||||
srcTri[0]=Point2f(0,0);
|
||||
srcTri[1]=Point2f(1,0);
|
||||
srcTri[2]=Point2f(0,1);
|
||||
dstTri[0]=Point2f((float)r.x,(float)r.y);
|
||||
dstTri[1]=Point2f((float)r.x+r.width,(float)r.y);
|
||||
dstTri[2]=Point2f((float)r.x,(float)r.y+(float)1.3*r.height);
|
||||
warp=getAffineTransform(srcTri,dstTri);
|
||||
return true;
|
||||
}
|
||||
bool FacemarkKazemiImpl::convertToUnit(Rect r,Mat &warp){
|
||||
Point2f srcTri[3],dstTri[3];
|
||||
dstTri[0]=Point2f(0,0);
|
||||
dstTri[1]=Point2f(1,0);
|
||||
dstTri[2]=Point2f(0,1);
|
||||
srcTri[0]=Point2f((float)r.x,(float)r.y);
|
||||
srcTri[1]=Point2f((float)r.x+r.width,(float)r.y);
|
||||
srcTri[2]=Point2f((float)r.x,(float)r.y+(float)1.3*r.height);
|
||||
warp=getAffineTransform(srcTri,dstTri);
|
||||
return true;
|
||||
}
|
||||
bool FacemarkKazemiImpl::setMeanExtreme(){
|
||||
if(meanshape.empty()){
|
||||
String error_message = "Model not loaded properly.No mean shape found.Aborting...";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
for(size_t i=0;i<meanshape.size();i++){
|
||||
if(meanshape[i].x>maxmeanx)
|
||||
maxmeanx = meanshape[i].x;
|
||||
if(meanshape[i].x<minmeanx)
|
||||
minmeanx = meanshape[i].x;
|
||||
if(meanshape[i].y>maxmeany)
|
||||
maxmeany = meanshape[i].y;
|
||||
if(meanshape[i].y<minmeany)
|
||||
minmeany = meanshape[i].y;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool FacemarkKazemiImpl::calcMeanShape (vector< vector<Point2f> >& trainlandmarks,vector<Mat>& trainimages,std::vector<Rect>& faces){
|
||||
//clear the loaded meanshape
|
||||
if(trainimages.empty()||trainlandmarks.size()!=trainimages.size()) {
|
||||
// throw error if no data (or simply return -1?)
|
||||
CV_Error(Error::StsBadArg, "Number of images is not equal to corresponding landmarks. Aborting...");
|
||||
}
|
||||
meanshape.clear();
|
||||
vector<Mat> finalimages;
|
||||
vector< vector<Point2f> > finallandmarks;
|
||||
float xmean[200] = {0.0};
|
||||
//array to store mean of y coordinates
|
||||
float ymean[200] = {0.0};
|
||||
size_t k=0;
|
||||
//loop to calculate mean
|
||||
Mat warp_mat,src,C,D;
|
||||
vector<Rect> facesp;
|
||||
Rect face;
|
||||
for(size_t i = 0;i < trainimages.size();i++){
|
||||
src = trainimages[i].clone();
|
||||
//get bounding rectangle of image for reference
|
||||
//function from facemark class
|
||||
facesp.clear();
|
||||
if(!getFaces(src,facesp)){
|
||||
continue;
|
||||
}
|
||||
if(facesp.size()>1||facesp.empty())
|
||||
continue;
|
||||
face = facesp[0];
|
||||
convertToUnit(face,warp_mat);
|
||||
//loop to bring points to a common reference and adding
|
||||
for(k=0;k<trainlandmarks[i].size();k++){
|
||||
Point2f pt=trainlandmarks[i][k];
|
||||
C = (Mat_<double>(3,1) << pt.x, pt.y, 1);
|
||||
D = warp_mat*C;
|
||||
pt.x = float(D.at<double>(0,0));
|
||||
pt.y = float(D.at<double>(1,0));
|
||||
trainlandmarks[i][k] = pt;
|
||||
xmean[k] = xmean[k]+pt.x;
|
||||
ymean[k] = ymean[k]+pt.y;
|
||||
}
|
||||
finalimages.push_back(trainimages[i]);
|
||||
finallandmarks.push_back(trainlandmarks[i]);
|
||||
faces.push_back(face);
|
||||
}
|
||||
//dividing by size to get mean and initialize meanshape
|
||||
for(size_t i=0;i<k;i++){
|
||||
xmean[i]=xmean[i]/finalimages.size();
|
||||
ymean[i]=ymean[i]/finalimages.size();
|
||||
if(xmean[i]>maxmeanx)
|
||||
maxmeanx = xmean[i];
|
||||
if(xmean[i]<minmeanx)
|
||||
minmeanx = xmean[i];
|
||||
if(ymean[i]>maxmeany)
|
||||
maxmeany = ymean[i];
|
||||
if(ymean[i]<minmeany)
|
||||
minmeany = ymean[i];
|
||||
meanshape.push_back(Point2f(xmean[i],ymean[i]));
|
||||
}
|
||||
trainimages.clear();
|
||||
trainlandmarks.clear();
|
||||
trainimages = finalimages;
|
||||
trainlandmarks = finallandmarks;
|
||||
finalimages.clear();
|
||||
finallandmarks.clear();
|
||||
return true;
|
||||
}
|
||||
bool FacemarkKazemiImpl::scaleData( vector< vector<Point2f> > & trainlandmarks,
|
||||
vector<Mat> & trainimages ,Size s)
|
||||
{
|
||||
if(trainimages.empty()||trainimages.size()!=trainlandmarks.size()){
|
||||
// throw error if no data (or simply return -1?)
|
||||
CV_Error(Error::StsBadArg, "The data is not loaded properly by train function. Aborting...");
|
||||
}
|
||||
float scalex,scaley;
|
||||
//scale all images and their landmarks according to input size
|
||||
for(size_t i=0;i< trainimages.size();i++){
|
||||
//calculating scale for x and y axis
|
||||
scalex=float(s.width)/float(trainimages[i].cols);
|
||||
scaley=float(s.height)/float(trainimages[i].rows);
|
||||
resize(trainimages[i],trainimages[i],s,0,0,INTER_LINEAR_EXACT);
|
||||
for (vector<Point2f>::iterator it = trainlandmarks[i].begin(); it != trainlandmarks[i].end(); it++) {
|
||||
Point2f pt = (*it);
|
||||
pt.x = pt.x*scalex;
|
||||
pt.y = pt.y*scaley;
|
||||
(*it) = pt;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Ptr<FacemarkKazemi> FacemarkKazemi::create(const FacemarkKazemi::Params ¶meters){
|
||||
return Ptr<FacemarkKazemiImpl>(new FacemarkKazemiImpl(parameters));
|
||||
}
|
||||
|
||||
Ptr<Facemark> createFacemarkKazemi() {
|
||||
FacemarkKazemi::Params parameters;
|
||||
return Ptr<FacemarkKazemiImpl>(new FacemarkKazemiImpl(parameters));
|
||||
}
|
||||
}//cv
|
||||
}//face
|
||||
@@ -0,0 +1,177 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#ifndef __OPENCV_FACE_ALIGNMENTIMPL_HPP__
|
||||
#define __OPENCV_FACE_ALIGNMENTIMPL_HPP__
|
||||
#include "opencv2/face.hpp"
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <queue>
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
|
||||
using namespace std;
|
||||
namespace cv{
|
||||
namespace face{
|
||||
/**@brief structure determining split in regression tree
|
||||
*/
|
||||
struct splitr{
|
||||
//!index1 Index of the first coordinates among the test coordinates for deciding split.
|
||||
uint64_t index1=0;
|
||||
//! index2 index of the second coordinate among the test coordinates for deciding split.
|
||||
uint64_t index2=0;
|
||||
//! thresh threshold for deciding the split.
|
||||
float thresh=0;
|
||||
};
|
||||
/** @brief represents a node of the regression tree*/
|
||||
struct node_info{
|
||||
//First pixel coordinate of split
|
||||
long index1;
|
||||
//Second pixel coordinate .split
|
||||
long index2;
|
||||
long depth;
|
||||
long node_no;
|
||||
};
|
||||
/** @brief regression tree structure. Each leaf node is a vector storing residual shape.
|
||||
* The tree is represented as vector of leaves.
|
||||
*/
|
||||
struct tree_node{
|
||||
splitr split;
|
||||
std::vector<Point2f> leaf;
|
||||
};
|
||||
struct regtree{
|
||||
std::vector<tree_node> nodes;
|
||||
};
|
||||
/** @brief Represents a training sample
|
||||
*It contains current shape, difference between actual shape
|
||||
*and current shape. It also stores the image whose shape is being
|
||||
*detected.
|
||||
*/
|
||||
struct training_sample{
|
||||
//! shapeResiduals vector which stores the residual shape remaining to be corrected.
|
||||
std::vector<Point2f> shapeResiduals;
|
||||
//! current_shape vector containing current estimate of the shape
|
||||
std::vector<Point2f> current_shape;
|
||||
//! actual_shape vector containing the actual shape of the face or the ground truth.
|
||||
std::vector<Point2f> actual_shape;
|
||||
//! image A mat object which stores the image.
|
||||
Mat image ;
|
||||
//! pixel_intensities vector containing pixel intensities of the coordinates chosen for testing
|
||||
std::vector<int> pixel_intensities;
|
||||
//! pixel_coordinates vector containing pixel coordinates used for testing
|
||||
std::vector<Point2f> pixel_coordinates;
|
||||
//! bound Rectangle enclosing the face found in the image for training
|
||||
Rect bound;
|
||||
};
|
||||
class FacemarkKazemiImpl : public FacemarkKazemi{
|
||||
|
||||
public:
|
||||
FacemarkKazemiImpl(const FacemarkKazemi::Params& parameters);
|
||||
void loadModel(String fs) CV_OVERRIDE;
|
||||
bool setFaceDetector(FN_FaceDetector f, void* userdata) CV_OVERRIDE;
|
||||
bool getFaces(InputArray image, OutputArray faces) CV_OVERRIDE;
|
||||
bool fit(InputArray image, InputArray faces, OutputArrayOfArrays landmarks ) CV_OVERRIDE;
|
||||
void training(String imageList, String groundTruth);
|
||||
bool training(vector<Mat>& images, vector< vector<Point2f> >& landmarks,string filename,Size scale,string modelFilename) CV_OVERRIDE;
|
||||
// Destructor for the class.
|
||||
virtual ~FacemarkKazemiImpl() CV_OVERRIDE;
|
||||
|
||||
virtual void read( const FileNode& ) CV_OVERRIDE {}
|
||||
virtual void write( FileStorage& ) const CV_OVERRIDE {}
|
||||
|
||||
protected:
|
||||
FacemarkKazemi::Params params;
|
||||
float minmeanx;
|
||||
float maxmeanx;
|
||||
float minmeany;
|
||||
float maxmeany;
|
||||
bool isModelLoaded;
|
||||
/* meanshape This is a vector which stores the mean shape of all the images used in training*/
|
||||
std::vector<Point2f> meanshape;
|
||||
std::vector< std::vector<regtree> > loaded_forests;
|
||||
std::vector< std::vector<Point2f> > loaded_pixel_coordinates;
|
||||
FN_FaceDetector faceDetector;
|
||||
void* faceDetectorData;
|
||||
bool findNearestLandmarks(std::vector< std::vector<int> >& nearest);
|
||||
/*Extract left node of the current node in the regression tree*/
|
||||
unsigned long left(unsigned long index);
|
||||
// Extract the right node of the current node in the regression tree
|
||||
unsigned long right(unsigned long index);
|
||||
// This function randomly generates test splits to get the best split.
|
||||
splitr getTestSplits(std::vector<Point2f> pixel_coordinates,int seed);
|
||||
// This function writes a split node to the XML file storing the trained model
|
||||
void writeSplit(std::ofstream& os, const splitr& split);
|
||||
// This function writes a leaf node to the binary file storing the trained model
|
||||
void writeLeaf(std::ofstream& os, const std::vector<Point2f> &leaf);
|
||||
// This function writes a tree to the binary file containing the model
|
||||
void writeTree(std::ofstream &f,regtree tree);
|
||||
// This function saves the pixel coordinates to a binary file
|
||||
void writePixels(std::ofstream& f,int index);
|
||||
// This function saves model to the binary file
|
||||
bool saveModel(String filename);
|
||||
// This funcrion reads pixel coordinates from the model file
|
||||
void readPixels(std::ifstream& is,uint64_t index);
|
||||
//This function reads the split node of the tree from binary file
|
||||
void readSplit(std::ifstream& is, splitr &vec);
|
||||
//This function reads a leaf node of the tree.
|
||||
void readLeaf(std::ifstream& is, std::vector<Point2f> &leaf);
|
||||
/* This function generates pixel intensities of the randomly generated test coordinates used to decide the split.
|
||||
*/
|
||||
bool getPixelIntensities(Mat img,std::vector<Point2f> pixel_coordinates_,std::vector<int>& pixel_intensities_,Rect face);
|
||||
//This function initialises the training parameters.
|
||||
bool setTrainingParameters(String filename);
|
||||
//This function finds a warp matrix that warp the pixels from the normalised space to the actual space
|
||||
bool convertToActual(Rect r,Mat &warp);
|
||||
//This function finds a warp matrix that warps the pixels from the actual space to normaluised space
|
||||
bool convertToUnit(Rect r,Mat &warp);
|
||||
/** @brief This function calculates mean shape while training.
|
||||
* This function is only called when new training data is supplied by the train function.
|
||||
*@param trainlandmarks A vector of type cv::Point2f which stores the landmarks of corresponding images.
|
||||
*@param trainimages A vector of type cv::Mat which stores the images which serve as training data.
|
||||
*@param faces A vector of type cv::Rect which stores the bounding recatngle of each training image
|
||||
*@returns A boolean value. It returns true if mean shape is found successfully else returns false.
|
||||
*/
|
||||
bool calcMeanShape(std::vector< std::vector<Point2f> > & trainlandmarks,std::vector<Mat>& trainimages,std::vector<Rect>& faces);
|
||||
/** @brief This functions scales the annotations to a common size which is considered same for all images.
|
||||
* @param trainlandmarks A vector of type cv::Point2f stores the landmarks of the corresponding training images.
|
||||
* @param trainimages A vector of type cv::Mat which stores the images which are to be scaled.
|
||||
* @param s A variable of type cv::Size stores the common size to which all the images are scaled.
|
||||
* @returns A boolean value. It returns true when data is scaled properly else returns false.
|
||||
*/
|
||||
bool scaleData(std::vector< std::vector<Point2f> >& trainlandmarks,
|
||||
std::vector<Mat>& trainimages , Size s=Size(460,460) );
|
||||
// This function gets the landmarks in the meanshape nearest to the pixel coordinates.
|
||||
unsigned long getNearestLandmark (Point2f pixels );
|
||||
// This function gets the relative position of the test pixel coordinates relative to the current shape.
|
||||
bool getRelativePixels(std::vector<Point2f> sample,std::vector<Point2f>& pixel_coordinates , std::vector<int> nearest_landmark = std::vector<int>());
|
||||
// This function partitions samples according to the split
|
||||
unsigned long divideSamples (splitr split,std::vector<training_sample>& samples,unsigned long start,unsigned long end);
|
||||
// This function fits a regression tree according to the shape residuals calculated to give weak learners for GBT algorithm.
|
||||
bool buildRegtree(regtree &tree,std::vector<training_sample>& samples,std::vector<Point2f> pixel_coordinates);
|
||||
// This function greedily decides the best split among the test splits generated.
|
||||
bool getBestSplit(std::vector<Point2f> pixel_coordinates, std::vector<training_sample>& samples,unsigned long start ,
|
||||
unsigned long end,splitr& split,std::vector< std::vector<Point2f> >& sum,long node_no);
|
||||
// This function randomly generates test coordinates for each level of cascade.
|
||||
void getTestCoordinates ();
|
||||
// This function implements gradient boosting by fitting regression trees
|
||||
std::vector<regtree> gradientBoosting(std::vector<training_sample>& samples,std::vector<Point2f> pixel_coordinates);
|
||||
// This function creates training sample by randomly assigning a current shape from set of shapes available.
|
||||
void createLeafNode(regtree& tree,long node_no,std::vector<Point2f> assign);
|
||||
// This function creates a split node in the regression tree.
|
||||
void createSplitNode(regtree& tree, splitr split,long node_no);
|
||||
// This function prepares the training samples
|
||||
bool createTrainingSamples(std::vector<training_sample> &samples,std::vector<Mat> images,std::vector< std::vector<Point2f> > landmarks,
|
||||
std::vector<Rect> rectangle);
|
||||
//This function generates a split
|
||||
bool generateSplit(std::queue<node_info>& curr,std::vector<Point2f> pixel_coordinates, std::vector<training_sample>& samples,
|
||||
splitr &split , std::vector< std::vector<Point2f> >& sum);
|
||||
bool setMeanExtreme();
|
||||
//friend class getRelShape;
|
||||
friend class getRelPixels;
|
||||
};
|
||||
}//face
|
||||
}//cv
|
||||
#endif
|
||||
@@ -0,0 +1,100 @@
|
||||
#include "opencv2/face.hpp"
|
||||
#include "face_utils.hpp"
|
||||
#include "precomp.hpp"
|
||||
|
||||
using namespace cv;
|
||||
using namespace face;
|
||||
|
||||
int BasicFaceRecognizer::getNumComponents() const
|
||||
{
|
||||
return _num_components;
|
||||
}
|
||||
|
||||
void BasicFaceRecognizer::setNumComponents(int val)
|
||||
{
|
||||
_num_components = val;
|
||||
}
|
||||
|
||||
double BasicFaceRecognizer::getThreshold() const
|
||||
{
|
||||
return _threshold;
|
||||
}
|
||||
|
||||
void BasicFaceRecognizer::setThreshold(double val)
|
||||
{
|
||||
_threshold = val;
|
||||
}
|
||||
|
||||
std::vector<cv::Mat> BasicFaceRecognizer::getProjections() const
|
||||
{
|
||||
return _projections;
|
||||
}
|
||||
|
||||
cv::Mat BasicFaceRecognizer::getLabels() const
|
||||
{
|
||||
return _labels;
|
||||
}
|
||||
|
||||
cv::Mat BasicFaceRecognizer::getEigenValues() const
|
||||
{
|
||||
return _eigenvalues;
|
||||
}
|
||||
|
||||
cv::Mat BasicFaceRecognizer::getEigenVectors() const
|
||||
{
|
||||
return _eigenvectors;
|
||||
}
|
||||
|
||||
cv::Mat BasicFaceRecognizer::getMean() const
|
||||
{
|
||||
return _mean;
|
||||
}
|
||||
|
||||
void BasicFaceRecognizer::read(const FileNode& fs)
|
||||
{
|
||||
//read matrices
|
||||
double _t = 0;
|
||||
fs["threshold"] >> _t; // older versions might not have "threshold"
|
||||
if (_t !=0)
|
||||
_threshold = _t; // be careful, not to overwrite DBL_MAX with 0 !
|
||||
fs["num_components"] >> _num_components;
|
||||
fs["mean"] >> _mean;
|
||||
fs["eigenvalues"] >> _eigenvalues;
|
||||
fs["eigenvectors"] >> _eigenvectors;
|
||||
// read sequences
|
||||
readFileNodeList(fs["projections"], _projections);
|
||||
fs["labels"] >> _labels;
|
||||
const FileNode& fn = fs["labelsInfo"];
|
||||
if (fn.type() == FileNode::SEQ)
|
||||
{
|
||||
_labelsInfo.clear();
|
||||
for (FileNodeIterator it = fn.begin(); it != fn.end();)
|
||||
{
|
||||
LabelInfo item;
|
||||
it >> item;
|
||||
_labelsInfo.insert(std::make_pair(item.label, item.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BasicFaceRecognizer::write(FileStorage& fs) const
|
||||
{
|
||||
// write matrices
|
||||
fs << "threshold" << _threshold;
|
||||
fs << "num_components" << _num_components;
|
||||
fs << "mean" << _mean;
|
||||
fs << "eigenvalues" << _eigenvalues;
|
||||
fs << "eigenvectors" << _eigenvectors;
|
||||
// write sequences
|
||||
writeFileNodeList(fs, "projections", _projections);
|
||||
fs << "labels" << _labels;
|
||||
fs << "labelsInfo" << "[";
|
||||
for (std::map<int, String>::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++)
|
||||
fs << LabelInfo(it->first, it->second);
|
||||
fs << "]";
|
||||
}
|
||||
|
||||
bool BasicFaceRecognizer::empty() const
|
||||
{
|
||||
return (_labels.empty());
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#ifndef __OPENCV_FACE_UTILS_HPP
|
||||
#define __OPENCV_FACE_UTILS_HPP
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
using namespace cv;
|
||||
|
||||
inline 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< std::vector<...> >).";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
// number of samples
|
||||
size_t n = src.total();
|
||||
// return empty matrix if no matrices given
|
||||
if(n == 0)
|
||||
return Mat();
|
||||
// dimensionality of (reshaped) samples
|
||||
size_t d = src.getMat(0).total();
|
||||
// create data matrix
|
||||
Mat data((int)n, (int)d, rtype);
|
||||
// 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 #%u! Expected %zu was %zu.", i, d, src.getMat(i).total());
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
// get a hold of the current row
|
||||
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);
|
||||
} else {
|
||||
src.getMat(i).clone().reshape(1, 1).convertTo(xi, rtype, alpha, beta);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// Reads a sequence from a FileNode::SEQ with type _Tp into a result vector.
|
||||
template<typename _Tp>
|
||||
inline void readFileNodeList(const FileNode& fn, std::vector<_Tp>& result) {
|
||||
if (fn.type() == FileNode::SEQ) {
|
||||
for (FileNodeIterator it = fn.begin(); it != fn.end();) {
|
||||
_Tp item;
|
||||
it >> item;
|
||||
result.push_back(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Writes the a list of given items to a cv::FileStorage.
|
||||
template<typename _Tp>
|
||||
inline void writeFileNodeList(FileStorage& fs, const String& name,
|
||||
const std::vector<_Tp>& items) {
|
||||
// typedefs
|
||||
typedef typename std::vector<_Tp>::const_iterator constVecIterator;
|
||||
// write the elements in item to fs
|
||||
fs << name << "[";
|
||||
for (constVecIterator it = items.begin(); it != items.end(); ++it) {
|
||||
fs << *it;
|
||||
}
|
||||
fs << "]";
|
||||
}
|
||||
|
||||
// Utility structure to load/save face label info (a pair of int and string) via FileStorage
|
||||
struct LabelInfo
|
||||
{
|
||||
LabelInfo():label(-1), value("") {}
|
||||
LabelInfo(int _label, const String &_value): label(_label), value(_value) {}
|
||||
int label;
|
||||
String value;
|
||||
void write(cv::FileStorage& fs) const
|
||||
{
|
||||
fs << "{" << "label" << label << "value" << value << "}";
|
||||
}
|
||||
void read(const cv::FileNode& node)
|
||||
{
|
||||
label = (int)node["label"];
|
||||
value = (String)node["value"];
|
||||
}
|
||||
std::ostream& operator<<(std::ostream& out)
|
||||
{
|
||||
out << "{ label = " << label << ", " << "value = " << value.c_str() << "}";
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
inline void write(cv::FileStorage& fs, const String&, const LabelInfo& x)
|
||||
{
|
||||
x.write(fs);
|
||||
}
|
||||
|
||||
inline void read(const cv::FileNode& node, LabelInfo& x, const LabelInfo& default_value = LabelInfo())
|
||||
{
|
||||
if(node.empty())
|
||||
x = default_value;
|
||||
else
|
||||
x.read(node);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,265 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
/*
|
||||
This file was part of GSoC Project: Facemark API for OpenCV
|
||||
Final report: https://gist.github.com/kurnianggoro/74de9121e122ad0bd825176751d47ecc
|
||||
Student: Laksono Kurnianggoro
|
||||
Mentor: Delia Passalacqua
|
||||
*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/face/facemark_train.hpp"
|
||||
|
||||
/*dataset parser*/
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <stdlib.h> /* atoi */
|
||||
|
||||
namespace cv {
|
||||
namespace face {
|
||||
|
||||
using namespace std;
|
||||
|
||||
CParams::CParams(String s, double sf, int minN, Size minSz, Size maxSz){
|
||||
cascade = s;
|
||||
scaleFactor = sf;
|
||||
minNeighbors = minN;
|
||||
minSize = minSz;
|
||||
maxSize = maxSz;
|
||||
|
||||
if (!face_cascade.load(cascade))
|
||||
{
|
||||
CV_Error_(Error::StsBadArg, ("Error loading face_cascade: %s", cascade.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
bool getFaces(InputArray image, OutputArray faces, CParams* params)
|
||||
{
|
||||
CV_Assert(params);
|
||||
Mat gray;
|
||||
std::vector<Rect> roi;
|
||||
|
||||
cvtColor(image.getMat(), gray, COLOR_BGR2GRAY);
|
||||
equalizeHist(gray, gray);
|
||||
|
||||
params->face_cascade.detectMultiScale( gray, roi, params->scaleFactor, params->minNeighbors, CASCADE_SCALE_IMAGE, params->minSize, params->maxSize);
|
||||
|
||||
Mat(roi).copyTo(faces);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loadDatasetList(String imageList, String groundTruth, std::vector<String> & images, std::vector<String> & landmarks){
|
||||
std::string line;
|
||||
|
||||
/*clear the output containers*/
|
||||
images.clear();
|
||||
landmarks.clear();
|
||||
|
||||
/*open the files*/
|
||||
std::ifstream infile;
|
||||
infile.open(imageList.c_str(), std::ios::in);
|
||||
std::ifstream ss_gt;
|
||||
ss_gt.open(groundTruth.c_str(), std::ios::in);
|
||||
if ((!infile) || !(ss_gt)) {
|
||||
printf("No valid input file was given, please check the given filename.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*load the images path*/
|
||||
while (getline (infile, line)){
|
||||
images.push_back(line);
|
||||
}
|
||||
|
||||
/*load the points*/
|
||||
while (getline (ss_gt, line)){
|
||||
landmarks.push_back(line);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loadTrainingData(String filename, std::vector<String> & images, OutputArray _facePoints, char delim, float offset){
|
||||
std::string line;
|
||||
std::string item;
|
||||
std::vector<Point2f> pts;
|
||||
std::vector<float> raw;
|
||||
|
||||
// FIXIT
|
||||
std::vector<std::vector<Point2f> > & facePoints =
|
||||
*(std::vector<std::vector<Point2f> >*) _facePoints.getObj();
|
||||
|
||||
std::ifstream infile;
|
||||
infile.open(filename.c_str(), std::ios::in);
|
||||
if (!infile) {
|
||||
CV_Error_(Error::StsBadArg, ("No valid input file was given, please check the given filename: %s", filename.c_str()));
|
||||
}
|
||||
|
||||
/*clear the output containers*/
|
||||
images.clear();
|
||||
facePoints.clear();
|
||||
|
||||
/*the main loading process*/
|
||||
while (getline (infile, line)){
|
||||
std::istringstream ss(line); // string stream for the current line
|
||||
|
||||
/*pop the image path*/
|
||||
getline (ss, item, delim);
|
||||
images.push_back(item);
|
||||
|
||||
/*load all numbers*/
|
||||
raw.clear();
|
||||
while (getline (ss, item, delim)){
|
||||
raw.push_back((float)atof(item.c_str()));
|
||||
}
|
||||
|
||||
/*convert to opencv points*/
|
||||
pts.clear();
|
||||
for(unsigned i = 0;i< raw.size();i+=2){
|
||||
pts.push_back(Point2f(raw[i]+offset,raw[i+1]+offset));
|
||||
}
|
||||
facePoints.push_back(pts);
|
||||
} // main loading process
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loadTrainingData(String imageList, String groundTruth, std::vector<String> & images, OutputArray _facePoints, float offset){
|
||||
std::string line;
|
||||
std::vector<Point2f> facePts;
|
||||
|
||||
// FIXIT
|
||||
std::vector<std::vector<Point2f> > & facePoints =
|
||||
*(std::vector<std::vector<Point2f> >*) _facePoints.getObj();
|
||||
|
||||
/*clear the output containers*/
|
||||
images.clear();
|
||||
facePoints.clear();
|
||||
|
||||
/*load the images path*/
|
||||
std::ifstream infile;
|
||||
infile.open(imageList.c_str(), std::ios::in);
|
||||
if (!infile) {
|
||||
CV_Error_(Error::StsBadArg, ("No valid input file was given, please check the given filename: %s", imageList.c_str()));
|
||||
}
|
||||
|
||||
while (getline (infile, line)){
|
||||
images.push_back(line);
|
||||
}
|
||||
|
||||
/*load the points*/
|
||||
std::ifstream ss_gt(groundTruth.c_str());
|
||||
while (getline (ss_gt, line)){
|
||||
facePts.clear();
|
||||
loadFacePoints(line, facePts, offset);
|
||||
facePoints.push_back(facePts);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loadFacePoints(String filename, OutputArray points, float offset){
|
||||
vector<Point2f> pts;
|
||||
|
||||
std::string line, item;
|
||||
std::ifstream infile(filename.c_str());
|
||||
|
||||
/*pop the version*/
|
||||
std::getline(infile, line);
|
||||
CV_Assert(line.compare(0,7,"version")==0);
|
||||
|
||||
/*pop the number of points*/
|
||||
std::getline(infile, line);
|
||||
CV_Assert(line.compare(0,8,"n_points")==0);
|
||||
|
||||
/*get the number of points*/
|
||||
std::string item_npts;
|
||||
int npts;
|
||||
|
||||
std::istringstream linestream(line);
|
||||
linestream>>item_npts>>npts;
|
||||
|
||||
/*pop out '{' character*/
|
||||
std::getline(infile, line);
|
||||
|
||||
/*main process*/
|
||||
int cnt = 0;
|
||||
std::string x, y;
|
||||
pts.clear();
|
||||
while (std::getline(infile, line) && cnt<npts )
|
||||
{
|
||||
cnt+=1;
|
||||
|
||||
std::istringstream ss(line);
|
||||
ss>>x>>y;
|
||||
pts.push_back(Point2f((float)atof(x.c_str())+offset,(float)atof(y.c_str())+offset));
|
||||
|
||||
}
|
||||
|
||||
Mat(pts).copyTo(points);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getFacesHAAR(InputArray image, OutputArray faces, const String& face_cascade_name)
|
||||
{
|
||||
Mat gray;
|
||||
vector<Rect> roi;
|
||||
CascadeClassifier face_cascade;
|
||||
CV_Assert(face_cascade.load(face_cascade_name) && "Can't loading face_cascade");
|
||||
cvtColor(image.getMat(), gray, COLOR_BGR2GRAY);
|
||||
equalizeHist(gray, gray);
|
||||
face_cascade.detectMultiScale(gray, roi, 1.1, 2, 0|CASCADE_SCALE_IMAGE, Size(30, 30));
|
||||
Mat(roi).copyTo(faces);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loadTrainingData(vector<String> filename,vector< vector<Point2f> >
|
||||
& trainlandmarks,vector<String> & trainimages)
|
||||
{
|
||||
string img;
|
||||
vector<Point2f> temp;
|
||||
string s;
|
||||
string tok;
|
||||
vector<string> coordinates;
|
||||
ifstream f1;
|
||||
for(unsigned long j=0;j<filename.size();j++){
|
||||
f1.open(filename[j].c_str(),ios::in);
|
||||
if(!f1.is_open()){
|
||||
cout<<filename[j]<<endl;
|
||||
CV_Error(Error::StsError, "File can't be opened for reading!");
|
||||
}
|
||||
//get the path of the image whose landmarks have to be detected
|
||||
getline(f1,img);
|
||||
//push the image paths in the vector
|
||||
trainimages.push_back(img);
|
||||
img.clear();
|
||||
while(getline(f1,s)){
|
||||
Point2f pt;
|
||||
stringstream ss(s); // Turn the string into a stream.
|
||||
while(getline(ss, tok,',')) {
|
||||
coordinates.push_back(tok);
|
||||
tok.clear();
|
||||
}
|
||||
pt.x = (float)atof(coordinates[0].c_str());
|
||||
pt.y = (float)atof(coordinates[1].c_str());
|
||||
coordinates.clear();
|
||||
temp.push_back(pt);
|
||||
}
|
||||
trainlandmarks.push_back(temp);
|
||||
temp.clear();
|
||||
f1.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void drawFacemarks(InputOutputArray image, InputArray points, Scalar color){
|
||||
Mat img = image.getMat();
|
||||
vector<Point2f> pts = points.getMat();
|
||||
for(size_t i=0;i<pts.size();i++){
|
||||
circle(img, pts[i],3, color,-1);
|
||||
}
|
||||
}
|
||||
} /* namespace face */
|
||||
} /* namespace cv */
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
namespace face
|
||||
{
|
||||
|
||||
std::vector<int> FaceRecognizer::getLabelsByString(const String &str) const
|
||||
{
|
||||
std::vector<int> labels;
|
||||
for (std::map<int, String>::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++)
|
||||
{
|
||||
size_t found = (it->second).find(str);
|
||||
if (found != String::npos)
|
||||
labels.push_back(it->first);
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
String FaceRecognizer::getLabelInfo(int label) const
|
||||
{
|
||||
std::map<int, String>::const_iterator iter(_labelsInfo.find(label));
|
||||
return iter != _labelsInfo.end() ? iter->second : "";
|
||||
}
|
||||
|
||||
void FaceRecognizer::setLabelInfo(int label, const String &strInfo)
|
||||
{
|
||||
_labelsInfo[label] = strInfo;
|
||||
}
|
||||
|
||||
void FaceRecognizer::update(InputArrayOfArrays src, InputArray labels)
|
||||
{
|
||||
CV_UNUSED(src);
|
||||
CV_UNUSED(labels);
|
||||
String error_msg = format("This FaceRecognizer does not support updating, you have to use FaceRecognizer::train to update it.");
|
||||
CV_Error(Error::StsNotImplemented, error_msg);
|
||||
}
|
||||
|
||||
void FaceRecognizer::read(const String &filename)
|
||||
{
|
||||
FileStorage fs(filename, FileStorage::READ);
|
||||
if (!fs.isOpened())
|
||||
CV_Error(Error::StsError, "File can't be opened for reading!");
|
||||
this->read(fs.getFirstTopLevelNode());
|
||||
fs.release();
|
||||
}
|
||||
|
||||
void FaceRecognizer::write(const String &filename) const
|
||||
{
|
||||
FileStorage fs(filename, FileStorage::WRITE);
|
||||
if (!fs.isOpened())
|
||||
CV_Error(Error::StsError, "File can't be opened for writing!");
|
||||
fs << getDefaultName() << "{";
|
||||
this->write(fs);
|
||||
fs << "}";
|
||||
fs.release();
|
||||
}
|
||||
|
||||
int FaceRecognizer::predict(InputArray src) const {
|
||||
int _label;
|
||||
double _dist;
|
||||
predict(src, _label, _dist);
|
||||
return _label;
|
||||
}
|
||||
|
||||
void FaceRecognizer::predict(InputArray src, CV_OUT int &label, CV_OUT double &confidence) const {
|
||||
Ptr<StandardCollector> collector = StandardCollector::create(getThreshold());
|
||||
predict(src, collector);
|
||||
label = collector->getMinLabel();
|
||||
confidence = collector->getMinDist();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
namespace cv { namespace face {
|
||||
|
||||
// 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),
|
||||
// 711–720.
|
||||
class Fisherfaces: public FisherFaceRecognizer
|
||||
{
|
||||
public:
|
||||
// Initializes an empty Fisherfaces model.
|
||||
Fisherfaces(int num_components = 0, double threshold = DBL_MAX)
|
||||
//: BasicFaceRecognizer(num_components, threshold)
|
||||
{
|
||||
_num_components = num_components;
|
||||
_threshold = threshold;
|
||||
}
|
||||
|
||||
// Computes a Fisherfaces 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_fisherfaces";
|
||||
}
|
||||
};
|
||||
|
||||
// Removes duplicate elements in a given vector.
|
||||
template<typename _Tp>
|
||||
inline std::vector<_Tp> remove_dups(const std::vector<_Tp>& src) {
|
||||
typedef typename std::set<_Tp>::const_iterator constSetIterator;
|
||||
typedef typename std::vector<_Tp>::const_iterator constVecIterator;
|
||||
std::set<_Tp> set_elems;
|
||||
for (constVecIterator it = src.begin(); it != src.end(); ++it)
|
||||
set_elems.insert(*it);
|
||||
std::vector<_Tp> elems;
|
||||
for (constSetIterator it = set_elems.begin(); it != set_elems.end(); ++it)
|
||||
elems.push_back(*it);
|
||||
return elems;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Fisherfaces
|
||||
//------------------------------------------------------------------------------
|
||||
void Fisherfaces::train(InputArrayOfArrays 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.");
|
||||
CV_Error(Error::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());
|
||||
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 Fisherfaces 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 data
|
||||
Mat labels = _lbls.getMat();
|
||||
Mat data = asRowMatrix(src, CV_64FC1);
|
||||
// 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)=%zu.", N, labels.total());
|
||||
CV_Error(Error::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=%d, cols=%d.", labels.rows, labels.cols);
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
// clear existing model data
|
||||
_labels.release();
|
||||
_projections.clear();
|
||||
// safely copy from cv::Mat to std::vector
|
||||
std::vector<int> ll;
|
||||
for(unsigned int i = 0; i < labels.total(); i++) {
|
||||
ll.push_back(labels.at<int>(i));
|
||||
}
|
||||
// get the 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);
|
||||
// perform a PCA and keep (N-C) components
|
||||
PCA pca(data, Mat(), PCA::DATA_AS_ROW, (N-C));
|
||||
// project the data and perform a LDA on it
|
||||
LDA lda(pca.project(data),labels, _num_components);
|
||||
// store the total mean vector
|
||||
_mean = pca.mean.reshape(1,1);
|
||||
// store labels
|
||||
_labels = labels.clone();
|
||||
// store the eigenvalues of the discriminants
|
||||
lda.eigenvalues().convertTo(_eigenvalues, CV_64FC1);
|
||||
// Now calculate the projection matrix as pca.eigenvectors * lda.eigenvectors.
|
||||
// Note: OpenCV stores the eigenvectors by row, so we need to transpose it!
|
||||
gemm(pca.eigenvectors, lda.eigenvectors(), 1.0, Mat(), 0.0, _eigenvectors, GEMM_1_T);
|
||||
// store the projections of the original data
|
||||
for(int sampleIdx = 0; sampleIdx < data.rows; sampleIdx++) {
|
||||
Mat p = LDA::subspaceProject(_eigenvectors, _mean, data.row(sampleIdx));
|
||||
_projections.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
void Fisherfaces::predict(InputArray _src, Ptr<PredictCollector> collector) 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?";
|
||||
CV_Error(Error::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 %zu.", _eigenvectors.rows, src.total());
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
// project into LDA subspace
|
||||
Mat q = LDA::subspaceProject(_eigenvectors, _mean, src.reshape(1,1));
|
||||
// find 1-nearest neighbor
|
||||
collector->init((int)_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<FisherFaceRecognizer> FisherFaceRecognizer::create(int num_components, double threshold)
|
||||
{
|
||||
return makePtr<Fisherfaces>(num_components, threshold);
|
||||
}
|
||||
|
||||
} }
|
||||
@@ -0,0 +1,288 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "face_alignmentimpl.hpp"
|
||||
#include <fstream>
|
||||
#include <ctime>
|
||||
|
||||
using namespace std;
|
||||
namespace cv{
|
||||
namespace face{
|
||||
bool FacemarkKazemiImpl :: findNearestLandmarks( vector< vector<int> >& nearest){
|
||||
if(meanshape.empty()||loaded_pixel_coordinates.empty()){
|
||||
String error_message = "Model not loaded properly.Aborting...";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return false;
|
||||
}
|
||||
nearest.resize(loaded_pixel_coordinates.size());
|
||||
for(unsigned long i=0 ; i< loaded_pixel_coordinates.size(); i++){
|
||||
for(unsigned long j = 0;j<loaded_pixel_coordinates[i].size();j++){
|
||||
nearest[i].push_back(getNearestLandmark(loaded_pixel_coordinates[i][j]));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void FacemarkKazemiImpl :: readSplit(ifstream& is, splitr &vec)
|
||||
{
|
||||
is.read((char*)&vec.index1, sizeof(vec.index1));
|
||||
is.read((char*)&vec.index2, sizeof(vec.index2));
|
||||
is.read((char*)&vec.thresh, sizeof(vec.thresh));
|
||||
uint32_t dummy_ = 0;
|
||||
is.read((char*)&dummy_, sizeof(dummy_)); // buggy writer structure alignment
|
||||
CV_CheckEQ((int)(sizeof(vec.index1) + sizeof(vec.index2) + sizeof(vec.thresh) + sizeof(dummy_)), 24, "Invalid build configuration");
|
||||
}
|
||||
void FacemarkKazemiImpl :: readLeaf(ifstream& is, vector<Point2f> &leaf)
|
||||
{
|
||||
uint64_t size;
|
||||
is.read((char*)&size, sizeof(size));
|
||||
leaf.resize((size_t)size);
|
||||
is.read((char*)&leaf[0], leaf.size() * sizeof(Point2f));
|
||||
}
|
||||
void FacemarkKazemiImpl :: readPixels(ifstream& is,uint64_t index)
|
||||
{
|
||||
is.read((char*)&loaded_pixel_coordinates[(unsigned long)index][0], loaded_pixel_coordinates[(unsigned long)index].size() * sizeof(Point2f));
|
||||
}
|
||||
void FacemarkKazemiImpl :: loadModel(String filename){
|
||||
if(filename.empty()){
|
||||
String error_message = "No filename found.Aborting....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return ;
|
||||
}
|
||||
ifstream f(filename.c_str(),ios::binary);
|
||||
if(!f.is_open()){
|
||||
String error_message = "No file with given name found.Aborting....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return ;
|
||||
}
|
||||
uint64_t len;
|
||||
f.read((char*)&len, sizeof(len));
|
||||
char* temp = new char[(size_t)len+1];
|
||||
f.read(temp, len);
|
||||
temp[len] = '\0';
|
||||
string s(temp);
|
||||
delete [] temp;
|
||||
if(s.compare("cascade_depth")!=0){
|
||||
String error_message = "Data not saved properly.Aborting.....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return ;
|
||||
}
|
||||
uint64_t cascade_size;
|
||||
f.read((char*)&cascade_size,sizeof(cascade_size));
|
||||
loaded_forests.resize((unsigned long)cascade_size);
|
||||
f.read((char*)&len, sizeof(len));
|
||||
temp = new char[(unsigned long)len+1];
|
||||
f.read(temp, len);
|
||||
temp[len] = '\0';
|
||||
s = string(temp);
|
||||
delete [] temp;
|
||||
if(s.compare("pixel_coordinates")!=0){
|
||||
String error_message = "Data not saved properly.Aborting.....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return ;
|
||||
}
|
||||
loaded_pixel_coordinates.resize((unsigned long)cascade_size);
|
||||
uint64_t num_pixels;
|
||||
f.read((char*)&num_pixels,sizeof(num_pixels));
|
||||
for(unsigned long i=0 ; i < cascade_size ; i++){
|
||||
loaded_pixel_coordinates[i].resize((unsigned long)num_pixels);
|
||||
readPixels(f,i);
|
||||
}
|
||||
f.read((char*)&len, sizeof(len));
|
||||
temp = new char[(unsigned long)len+1];
|
||||
f.read(temp, len);
|
||||
temp[len] = '\0';
|
||||
s = string(temp);
|
||||
delete [] temp;
|
||||
if(s.compare("mean_shape")!=0){
|
||||
String error_message = "Data not saved properly.Aborting.....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return ;
|
||||
}
|
||||
uint64_t mean_shape_size;
|
||||
f.read((char*)&mean_shape_size,sizeof(mean_shape_size));
|
||||
meanshape.resize((unsigned long)mean_shape_size);
|
||||
f.read((char*)&meanshape[0], meanshape.size() * sizeof(Point2f));
|
||||
if(!setMeanExtreme())
|
||||
exit(0);
|
||||
f.read((char*)&len, sizeof(len));
|
||||
temp = new char[(unsigned long)len+1];
|
||||
f.read(temp, len);
|
||||
temp[len] = '\0';
|
||||
s = string(temp);
|
||||
delete [] temp;
|
||||
if(s.compare("num_trees")!=0){
|
||||
String error_message = "Data not saved properly.Aborting.....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return ;
|
||||
}
|
||||
uint64_t num_trees;
|
||||
f.read((char*)&num_trees,sizeof(num_trees));
|
||||
for(unsigned long i=0;i<cascade_size;i++){
|
||||
for(unsigned long j=0;j<num_trees;j++){
|
||||
regtree tree;
|
||||
f.read((char*)&len, sizeof(len));
|
||||
char* temp2 = new char[(unsigned long)len+1];
|
||||
f.read(temp2, len);
|
||||
temp2[len] = '\0';
|
||||
s =string(temp2);
|
||||
delete [] temp2;
|
||||
if(s.compare("num_nodes")!=0){
|
||||
String error_message = "Data not saved properly.Aborting.....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return ;
|
||||
}
|
||||
uint64_t num_nodes;
|
||||
f.read((char*)&num_nodes,sizeof(num_nodes));
|
||||
tree.nodes.resize((unsigned long)num_nodes+1);
|
||||
for(unsigned long k=0; k < num_nodes ; k++){
|
||||
f.read((char*)&len, sizeof(len));
|
||||
char* temp3 = new char[(unsigned long)len+1];
|
||||
f.read(temp3, len);
|
||||
temp3[len] = '\0';
|
||||
s =string(temp3);
|
||||
delete [] temp3;
|
||||
tree_node node;
|
||||
if(s.compare("split")==0){
|
||||
splitr split;
|
||||
readSplit(f,split);
|
||||
node.split = split;
|
||||
node.leaf.clear();
|
||||
}
|
||||
else if(s.compare("leaf")==0){
|
||||
vector<Point2f> leaf;
|
||||
readLeaf(f,leaf);
|
||||
node.leaf = leaf;
|
||||
}
|
||||
else{
|
||||
String error_message = "Data not saved properly.Aborting.....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return ;
|
||||
}
|
||||
tree.nodes[k]=node;
|
||||
}
|
||||
loaded_forests[i].push_back(tree);
|
||||
}
|
||||
}
|
||||
f.close();
|
||||
isModelLoaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy the contents of a corners vector to an OutputArray, settings its size.
|
||||
*/
|
||||
static void _copyVector2Output(std::vector< std::vector< Point2f > > &vec, OutputArrayOfArrays out)
|
||||
{
|
||||
out.create((int)vec.size(), 1, CV_32FC2);
|
||||
|
||||
if (out.isMatVector()) {
|
||||
for (unsigned int i = 0; i < vec.size(); i++) {
|
||||
out.create(68, 1, CV_32FC2, i);
|
||||
Mat &m = out.getMatRef(i);
|
||||
Mat(Mat(vec[i]).t()).copyTo(m);
|
||||
}
|
||||
}
|
||||
else if (out.isUMatVector()) {
|
||||
for (unsigned int i = 0; i < vec.size(); i++) {
|
||||
out.create(68, 1, CV_32FC2, i);
|
||||
UMat &m = out.getUMatRef(i);
|
||||
Mat(Mat(vec[i]).t()).copyTo(m);
|
||||
}
|
||||
}
|
||||
else if (out.kind() == _OutputArray::STD_VECTOR_VECTOR) {
|
||||
for (unsigned int i = 0; i < vec.size(); i++) {
|
||||
out.create(68, 1, CV_32FC2, i);
|
||||
Mat m = out.getMat(i);
|
||||
Mat(Mat(vec[i]).t()).copyTo(m);
|
||||
}
|
||||
}
|
||||
else {
|
||||
CV_Error(cv::Error::StsNotImplemented,
|
||||
"Only Mat vector, UMat vector, and vector<vector> OutputArrays are currently supported.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool FacemarkKazemiImpl::fit(InputArray img, InputArray roi, OutputArrayOfArrays _landmarks)
|
||||
{
|
||||
if(!isModelLoaded){
|
||||
String error_message = "No model loaded. Aborting....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return false;
|
||||
}
|
||||
Mat image = img.getMat();
|
||||
Mat roimat = roi.getMat();
|
||||
std::vector<Rect> faces = roimat.reshape(4, roimat.rows);
|
||||
std::vector<std::vector<Point2f> > shapes;
|
||||
shapes.resize(faces.size());
|
||||
|
||||
if(image.empty()){
|
||||
String error_message = "No image found.Aborting..";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return false;
|
||||
}
|
||||
if(faces.empty()){
|
||||
String error_message = "No faces found.Aborting..";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return false;
|
||||
}
|
||||
if(meanshape.empty()||loaded_forests.empty()||loaded_pixel_coordinates.empty()){
|
||||
String error_message = "Model not loaded properly.Aborting...";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return false;
|
||||
}
|
||||
if(loaded_forests.size()==0){
|
||||
String error_message = "Model not loaded properly.Aboerting...";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return false;
|
||||
}
|
||||
if(loaded_pixel_coordinates.size()==0){
|
||||
String error_message = "Model not loaded properly.Aboerting...";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
return false;
|
||||
}
|
||||
vector< vector<int> > nearest_landmarks;
|
||||
findNearestLandmarks(nearest_landmarks);
|
||||
tree_node curr_node;
|
||||
vector<Point2f> pixel_relative;
|
||||
vector<int> pixel_intensity;
|
||||
Mat warp_mat;
|
||||
for(size_t e=0;e<faces.size();e++){
|
||||
shapes[e]=meanshape;
|
||||
convertToActual(faces[e],warp_mat);
|
||||
for(size_t i=0;i<loaded_forests.size();i++){
|
||||
pixel_intensity.clear();
|
||||
pixel_relative = loaded_pixel_coordinates[i];
|
||||
getRelativePixels(shapes[e],pixel_relative,nearest_landmarks[i]);
|
||||
getPixelIntensities(image,pixel_relative,pixel_intensity,faces[e]);
|
||||
for(size_t j=0;j<loaded_forests[i].size();j++){
|
||||
regtree tree = loaded_forests[i][j];
|
||||
curr_node = tree.nodes[0];
|
||||
unsigned long curr_node_index = 0;
|
||||
while(curr_node.leaf.size()==0)
|
||||
{
|
||||
if ((float)pixel_intensity[(unsigned long)curr_node.split.index1] - (float)pixel_intensity[(unsigned long)curr_node.split.index2] > curr_node.split.thresh)
|
||||
{
|
||||
curr_node_index=left(curr_node_index);
|
||||
} else
|
||||
curr_node_index=right(curr_node_index);
|
||||
curr_node = tree.nodes[curr_node_index];
|
||||
}
|
||||
for(size_t p=0;p<curr_node.leaf.size();p++){
|
||||
shapes[e][p]=shapes[e][p] + curr_node.leaf[p];
|
||||
}
|
||||
}
|
||||
}
|
||||
for(unsigned long j=0;j<shapes[e].size();j++){
|
||||
Mat C = (Mat_<double>(3,1) << shapes[e][j].x, shapes[e][j].y, 1);
|
||||
Mat D = warp_mat*C;
|
||||
shapes[e][j].x=float(D.at<double>(0,0));
|
||||
shapes[e][j].y=float(D.at<double>(1,0));
|
||||
}
|
||||
}
|
||||
_copyVector2Output(shapes, _landmarks);
|
||||
return true;
|
||||
}
|
||||
}//cv
|
||||
}//face
|
||||
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
namespace cv { namespace face {
|
||||
|
||||
// Face Recognition based on Local Binary Patterns.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
class LBPH : public LBPHFaceRecognizer
|
||||
{
|
||||
private:
|
||||
int _grid_x;
|
||||
int _grid_y;
|
||||
int _radius;
|
||||
int _neighbors;
|
||||
double _threshold;
|
||||
|
||||
std::vector<Mat> _histograms;
|
||||
Mat _labels;
|
||||
|
||||
// Computes a LBPH model with images in src and
|
||||
// corresponding labels in labels, possibly preserving
|
||||
// old model data.
|
||||
void train(InputArrayOfArrays src, InputArray labels, bool preserveData);
|
||||
|
||||
|
||||
public:
|
||||
using FaceRecognizer::read;
|
||||
using FaceRecognizer::write;
|
||||
|
||||
// Initializes this LBPH Model. The current implementation is rather fixed
|
||||
// as it uses the Extended Local Binary Patterns per default.
|
||||
//
|
||||
// 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 gridx=8, int gridy=8,
|
||||
double threshold = DBL_MAX) :
|
||||
_grid_x(gridx),
|
||||
_grid_y(gridy),
|
||||
_radius(radius_),
|
||||
_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.
|
||||
//
|
||||
// (radius=1), (neighbors=8) are used in the local binary patterns creation.
|
||||
// (grid_x=8), (grid_y=8) controls the grid size of the spatial histograms.
|
||||
LBPH(InputArrayOfArrays src,
|
||||
InputArray labels,
|
||||
int radius_=1, int neighbors_=8,
|
||||
int gridx=8, int gridy=8,
|
||||
double threshold = DBL_MAX) :
|
||||
_grid_x(gridx),
|
||||
_grid_y(gridy),
|
||||
_radius(radius_),
|
||||
_neighbors(neighbors_),
|
||||
_threshold(threshold) {
|
||||
train(src, labels);
|
||||
}
|
||||
|
||||
~LBPH() CV_OVERRIDE { }
|
||||
|
||||
// Computes a LBPH model with images in src and
|
||||
// corresponding labels in labels.
|
||||
void train(InputArrayOfArrays src, InputArray labels) CV_OVERRIDE;
|
||||
|
||||
// Updates this LBPH model with images in src and
|
||||
// corresponding labels in labels.
|
||||
void update(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;
|
||||
|
||||
// See FaceRecognizer::write.
|
||||
void read(const FileNode& fn) CV_OVERRIDE;
|
||||
|
||||
// See FaceRecognizer::save.
|
||||
void write(FileStorage& fs) const CV_OVERRIDE;
|
||||
|
||||
bool empty() const CV_OVERRIDE {
|
||||
return (_labels.empty());
|
||||
}
|
||||
String getDefaultName() const CV_OVERRIDE
|
||||
{
|
||||
return "opencv_lbphfaces";
|
||||
}
|
||||
|
||||
inline int getGridX() const CV_OVERRIDE { return _grid_x; }
|
||||
inline void setGridX(int val) CV_OVERRIDE { _grid_x = val; }
|
||||
inline int getGridY() const CV_OVERRIDE { return _grid_y; }
|
||||
inline void setGridY(int val) CV_OVERRIDE { _grid_y = val; }
|
||||
inline int getRadius() const CV_OVERRIDE { return _radius; }
|
||||
inline void setRadius(int val) CV_OVERRIDE { _radius = val; }
|
||||
inline int getNeighbors() const CV_OVERRIDE { return _neighbors; }
|
||||
inline void setNeighbors(int val) CV_OVERRIDE { _neighbors = val; }
|
||||
inline double getThreshold() const CV_OVERRIDE { return _threshold; }
|
||||
inline void setThreshold(double val) CV_OVERRIDE { _threshold = val; }
|
||||
inline std::vector<cv::Mat> getHistograms() const CV_OVERRIDE { return _histograms; }
|
||||
inline cv::Mat getLabels() const CV_OVERRIDE { return _labels; }
|
||||
};
|
||||
|
||||
|
||||
void LBPH::read(const FileNode& fs) {
|
||||
double _t = 0;
|
||||
fs["threshold"] >> _t; // older versions might not have "threshold"
|
||||
if (_t !=0)
|
||||
_threshold = _t; // be careful, not to overwrite DBL_MAX with 0 !
|
||||
fs["radius"] >> _radius;
|
||||
fs["neighbors"] >> _neighbors;
|
||||
fs["grid_x"] >> _grid_x;
|
||||
fs["grid_y"] >> _grid_y;
|
||||
//read matrices
|
||||
readFileNodeList(fs["histograms"], _histograms);
|
||||
fs["labels"] >> _labels;
|
||||
const FileNode& fn = fs["labelsInfo"];
|
||||
if (fn.type() == FileNode::SEQ)
|
||||
{
|
||||
_labelsInfo.clear();
|
||||
for (FileNodeIterator it = fn.begin(); it != fn.end();)
|
||||
{
|
||||
LabelInfo item;
|
||||
it >> item;
|
||||
_labelsInfo.insert(std::make_pair(item.label, item.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See FaceRecognizer::save.
|
||||
void LBPH::write(FileStorage& fs) const {
|
||||
fs << "threshold" << _threshold;
|
||||
fs << "radius" << _radius;
|
||||
fs << "neighbors" << _neighbors;
|
||||
fs << "grid_x" << _grid_x;
|
||||
fs << "grid_y" << _grid_y;
|
||||
// write matrices
|
||||
writeFileNodeList(fs, "histograms", _histograms);
|
||||
fs << "labels" << _labels;
|
||||
fs << "labelsInfo" << "[";
|
||||
for (std::map<int, String>::const_iterator it = _labelsInfo.begin(); it != _labelsInfo.end(); it++)
|
||||
fs << LabelInfo(it->first, it->second);
|
||||
fs << "]";
|
||||
}
|
||||
|
||||
void LBPH::train(InputArrayOfArrays _in_src, InputArray _in_labels) {
|
||||
this->train(_in_src, _in_labels, false);
|
||||
}
|
||||
|
||||
void LBPH::update(InputArrayOfArrays _in_src, InputArray _in_labels) {
|
||||
// got no data, just return
|
||||
if(_in_src.total() == 0)
|
||||
return;
|
||||
|
||||
this->train(_in_src, _in_labels, true);
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// LBPH
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <typename _Tp> static
|
||||
void olbp_(InputArray _src, OutputArray _dst) {
|
||||
// get matrices
|
||||
Mat src = _src.getMat();
|
||||
// allocate memory for result
|
||||
_dst.create(src.rows-2, src.cols-2, CV_8UC1);
|
||||
Mat dst = _dst.getMat();
|
||||
// zero the result matrix
|
||||
dst.setTo(0);
|
||||
// calculate patterns
|
||||
for(int i=1;i<src.rows-1;i++) {
|
||||
for(int j=1;j<src.cols-1;j++) {
|
||||
_Tp center = src.at<_Tp>(i,j);
|
||||
unsigned char code = 0;
|
||||
code |= (src.at<_Tp>(i-1,j-1) >= center) << 7;
|
||||
code |= (src.at<_Tp>(i-1,j) >= center) << 6;
|
||||
code |= (src.at<_Tp>(i-1,j+1) >= center) << 5;
|
||||
code |= (src.at<_Tp>(i,j+1) >= center) << 4;
|
||||
code |= (src.at<_Tp>(i+1,j+1) >= center) << 3;
|
||||
code |= (src.at<_Tp>(i+1,j) >= center) << 2;
|
||||
code |= (src.at<_Tp>(i+1,j-1) >= center) << 1;
|
||||
code |= (src.at<_Tp>(i,j-1) >= center) << 0;
|
||||
dst.at<unsigned char>(i-1,j-1) = code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// cv::elbp
|
||||
//------------------------------------------------------------------------------
|
||||
template <typename _Tp> static
|
||||
inline void elbp_(InputArray _src, OutputArray _dst, int radius, int neighbors) {
|
||||
//get matrices
|
||||
Mat src = _src.getMat();
|
||||
// allocate memory for result
|
||||
_dst.create(src.rows-2*radius, src.cols-2*radius, CV_32SC1);
|
||||
Mat dst = _dst.getMat();
|
||||
// zero
|
||||
dst.setTo(0);
|
||||
for(int n=0; n<neighbors; n++) {
|
||||
// sample points
|
||||
float x = static_cast<float>(radius * cos(2.0*CV_PI*n/static_cast<float>(neighbors)));
|
||||
float y = static_cast<float>(-radius * sin(2.0*CV_PI*n/static_cast<float>(neighbors)));
|
||||
// relative indices
|
||||
int fx = static_cast<int>(floor(x));
|
||||
int fy = static_cast<int>(floor(y));
|
||||
int cx = static_cast<int>(ceil(x));
|
||||
int cy = static_cast<int>(ceil(y));
|
||||
// fractional part
|
||||
float ty = y - fy;
|
||||
float tx = x - fx;
|
||||
// set interpolation weights
|
||||
float w1 = (1 - tx) * (1 - ty);
|
||||
float w2 = tx * (1 - ty);
|
||||
float w3 = (1 - tx) * ty;
|
||||
float w4 = tx * ty;
|
||||
// iterate through your data
|
||||
for(int i=radius; i < src.rows-radius;i++) {
|
||||
for(int j=radius;j < src.cols-radius;j++) {
|
||||
// calculate interpolated value
|
||||
float t = static_cast<float>(w1*src.at<_Tp>(i+fy,j+fx) + w2*src.at<_Tp>(i+fy,j+cx) + w3*src.at<_Tp>(i+cy,j+fx) + w4*src.at<_Tp>(i+cy,j+cx));
|
||||
// floating point precision, so check some machine-dependent epsilon
|
||||
dst.at<int>(i-radius,j-radius) += ((t > src.at<_Tp>(i,j)) || (std::abs(t-src.at<_Tp>(i,j)) < std::numeric_limits<float>::epsilon())) << n;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void elbp(InputArray src, OutputArray dst, int radius, int neighbors)
|
||||
{
|
||||
int type = src.type();
|
||||
switch (type) {
|
||||
case CV_8SC1: elbp_<char>(src,dst, radius, neighbors); break;
|
||||
case CV_8UC1: elbp_<unsigned char>(src, dst, radius, neighbors); break;
|
||||
case CV_16SC1: elbp_<short>(src,dst, radius, neighbors); break;
|
||||
case CV_16UC1: elbp_<unsigned short>(src,dst, radius, neighbors); break;
|
||||
case CV_32SC1: elbp_<int>(src,dst, radius, neighbors); break;
|
||||
case CV_32FC1: elbp_<float>(src,dst, radius, neighbors); break;
|
||||
case CV_64FC1: elbp_<double>(src,dst, radius, neighbors); break;
|
||||
default:
|
||||
String error_msg = format("Using Original Local Binary Patterns for feature extraction only works on single-channel images (given %d). Please pass the image data as a grayscale image!", type);
|
||||
CV_Error(Error::StsNotImplemented, error_msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static Mat
|
||||
histc_(const Mat& src, int minVal=0, int maxVal=255, bool normed=false)
|
||||
{
|
||||
Mat result;
|
||||
// Establish the number of bins.
|
||||
int histSize = maxVal-minVal+1;
|
||||
// Set the ranges.
|
||||
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);
|
||||
// normalize
|
||||
if(normed) {
|
||||
result /= (int)src.total();
|
||||
}
|
||||
return result.reshape(1,1);
|
||||
}
|
||||
|
||||
static Mat histc(InputArray _src, int minVal, int maxVal, bool normed)
|
||||
{
|
||||
Mat src = _src.getMat();
|
||||
switch (src.type()) {
|
||||
case CV_8SC1:
|
||||
return histc_(Mat_<float>(src), minVal, maxVal, normed);
|
||||
break;
|
||||
case CV_8UC1:
|
||||
return histc_(src, minVal, maxVal, normed);
|
||||
break;
|
||||
case CV_16SC1:
|
||||
return histc_(Mat_<float>(src), minVal, maxVal, normed);
|
||||
break;
|
||||
case CV_16UC1:
|
||||
return histc_(src, minVal, maxVal, normed);
|
||||
break;
|
||||
case CV_32SC1:
|
||||
return histc_(Mat_<float>(src), minVal, maxVal, normed);
|
||||
break;
|
||||
case CV_32FC1:
|
||||
return histc_(src, minVal, maxVal, normed);
|
||||
break;
|
||||
}
|
||||
CV_Error(Error::StsUnmatchedFormats, "This type is not implemented yet.");
|
||||
}
|
||||
|
||||
|
||||
static Mat spatial_histogram(InputArray _src, int numPatterns,
|
||||
int grid_x, int grid_y, bool /*normed*/)
|
||||
{
|
||||
Mat src = _src.getMat();
|
||||
// calculate LBP patch size
|
||||
int width = src.cols/grid_x;
|
||||
int height = src.rows/grid_y;
|
||||
// allocate memory for the spatial histogram
|
||||
Mat result = Mat::zeros(grid_x * grid_y, numPatterns, CV_32FC1);
|
||||
// return matrix with zeros if no data was given
|
||||
if(src.empty())
|
||||
return result.reshape(1,1);
|
||||
// initial result_row
|
||||
int resultRowIdx = 0;
|
||||
// iterate through grid
|
||||
for(int i = 0; i < grid_y; i++) {
|
||||
for(int j = 0; j < grid_x; j++) {
|
||||
Mat src_cell = Mat(src, Range(i*height,(i+1)*height), Range(j*width,(j+1)*width));
|
||||
Mat cell_hist = histc(src_cell, 0, (numPatterns-1), true);
|
||||
// copy to the result matrix
|
||||
Mat result_row = result.row(resultRowIdx);
|
||||
cell_hist.reshape(1,1).convertTo(result_row, CV_32FC1);
|
||||
// increase row count in result matrix
|
||||
resultRowIdx++;
|
||||
}
|
||||
}
|
||||
// return result as reshaped feature vector
|
||||
return result.reshape(1,1);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// wrapper to cv::elbp (extended local binary patterns)
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static Mat elbp(InputArray src, int radius, int neighbors) {
|
||||
Mat dst;
|
||||
elbp(src, dst, radius, neighbors);
|
||||
return dst;
|
||||
}
|
||||
|
||||
void LBPH::train(InputArrayOfArrays _in_src, InputArray _in_labels, bool preserveData) {
|
||||
if(_in_src.kind() != _InputArray::STD_VECTOR_MAT && _in_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< std::vector<...> >).";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
if(_in_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::StsUnsupportedFormat, error_message);
|
||||
} else if(_in_labels.getMat().type() != CV_32SC1) {
|
||||
String error_message = format("Labels must be given as integer (CV_32SC1). Expected %d, but was %d.", CV_32SC1, _in_labels.type());
|
||||
CV_Error(Error::StsUnsupportedFormat, error_message);
|
||||
}
|
||||
// get the vector of matrices
|
||||
std::vector<Mat> src;
|
||||
_in_src.getMatVector(src);
|
||||
// get the label matrix
|
||||
Mat labels = _in_labels.getMat();
|
||||
// check if data is well- aligned
|
||||
if(labels.total() != src.size()) {
|
||||
String error_message = format("The number of samples (src) must equal the number of labels (labels). Was len(samples)=%zu, len(labels)=%zu.", src.size(), _labels.total());
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
// if this model should be trained without preserving old data, delete old model data
|
||||
if(!preserveData) {
|
||||
_labels.release();
|
||||
_histograms.clear();
|
||||
}
|
||||
// append labels to _labels matrix
|
||||
for(size_t labelIdx = 0; labelIdx < labels.total(); labelIdx++) {
|
||||
_labels.push_back(labels.at<int>((int)labelIdx));
|
||||
}
|
||||
// store the spatial histograms of the original data
|
||||
for(size_t sampleIdx = 0; sampleIdx < src.size(); sampleIdx++) {
|
||||
// calculate lbp image
|
||||
Mat lbp_image = elbp(src[sampleIdx], _radius, _neighbors);
|
||||
// get spatial histogram from this lbp image
|
||||
Mat p = spatial_histogram(
|
||||
lbp_image, /* lbp_image */
|
||||
static_cast<int>(std::pow(2.0, static_cast<double>(_neighbors))), /* number of possible patterns */
|
||||
_grid_x, /* grid size x */
|
||||
_grid_y, /* grid size y */
|
||||
true);
|
||||
// add to templates
|
||||
_histograms.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
void LBPH::predict(InputArray _src, Ptr<PredictCollector> collector) const {
|
||||
if(_histograms.empty()) {
|
||||
// throw error if no data (or simply return -1?)
|
||||
String error_message = "This LBPH model is not computed yet. Did you call the train method?";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
Mat src = _src.getMat();
|
||||
// get the spatial histogram from input image
|
||||
Mat lbp_image = elbp(src, _radius, _neighbors);
|
||||
Mat query = spatial_histogram(
|
||||
lbp_image, /* lbp_image */
|
||||
static_cast<int>(std::pow(2.0, static_cast<double>(_neighbors))), /* number of possible patterns */
|
||||
_grid_x, /* grid size x */
|
||||
_grid_y, /* grid size y */
|
||||
true /* normed histograms */);
|
||||
// find 1-nearest neighbor
|
||||
collector->init((int)_histograms.size());
|
||||
for (size_t sampleIdx = 0; sampleIdx < _histograms.size(); sampleIdx++) {
|
||||
double dist = compareHist(_histograms[sampleIdx], query, HISTCMP_CHISQR_ALT);
|
||||
int label = _labels.at<int>((int)sampleIdx);
|
||||
if (!collector->collect(label, dist))return;
|
||||
}
|
||||
}
|
||||
|
||||
Ptr<LBPHFaceRecognizer> LBPHFaceRecognizer::create(int radius, int neighbors,
|
||||
int grid_x, int grid_y, double threshold)
|
||||
{
|
||||
return makePtr<LBPH>(radius, neighbors, grid_x, grid_y, threshold);
|
||||
}
|
||||
|
||||
}}
|
||||
@@ -0,0 +1,270 @@
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/face/mace.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace face {
|
||||
|
||||
|
||||
//
|
||||
//! Rearrange the quadrants of Fourier image
|
||||
//! so that the origin is at the image center
|
||||
//
|
||||
static void shiftDFT(const Mat &src, Mat &dst)
|
||||
{
|
||||
Size size = src.size();
|
||||
|
||||
if (dst.empty() || (dst.size().width != size.width || dst.size().height != size.height))
|
||||
{
|
||||
dst.create(src.size(), src.type());
|
||||
}
|
||||
|
||||
int cx = size.width/2;
|
||||
int cy = size.height/2; // image center
|
||||
|
||||
Mat q1 = src(Rect(0, 0, cx,cy));
|
||||
Mat q2 = src(Rect(cx,0, cx,cy));
|
||||
Mat q3 = src(Rect(cx,cy,cx,cy));
|
||||
Mat q4 = src(Rect(0, cy,cx,cy));
|
||||
Mat d1 = dst(Rect(0, 0, cx,cy));
|
||||
Mat d2 = dst(Rect(cx,0, cx,cy));
|
||||
Mat d3 = dst(Rect(cx,cy,cx,cy));
|
||||
Mat d4 = dst(Rect(0, cy,cx,cy));
|
||||
|
||||
if (src.data != dst.data){
|
||||
q3.copyTo(d1);
|
||||
q4.copyTo(d2);
|
||||
q1.copyTo(d3);
|
||||
q2.copyTo(d4);
|
||||
} else {
|
||||
Mat tmp;
|
||||
q3.copyTo(tmp);
|
||||
q1.copyTo(d3);
|
||||
tmp.copyTo(d1);
|
||||
q4.copyTo(tmp);
|
||||
q2.copyTo(d4);
|
||||
tmp.copyTo(d2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Computes 64-bit "cyclic redundancy check" sum, as specified in ECMA-182
|
||||
static uint64 crc64( const uchar* data, size_t size, uint64 crc0=0 )
|
||||
{
|
||||
static uint64 table[256];
|
||||
static bool initialized = false;
|
||||
|
||||
if( !initialized )
|
||||
{
|
||||
for( int i = 0; i < 256; i++ )
|
||||
{
|
||||
uint64 c = i;
|
||||
for( int j = 0; j < 8; j++ )
|
||||
c = ((c & 1) ? CV_BIG_UINT(0xc96c5795d7870f42) : 0) ^ (c >> 1);
|
||||
table[i] = c;
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
uint64 crc = ~crc0;
|
||||
for( size_t idx = 0; idx < size; idx++ )
|
||||
crc = table[(uchar)crc ^ data[idx]] ^ (crc >> 8);
|
||||
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
struct MACEImpl CV_FINAL : MACE {
|
||||
Mat_<Vec2d> maceFilter; // filled from compute()
|
||||
Mat convFilter; // optional random convolution (cancellable)
|
||||
int IMGSIZE; // images will get resized to this
|
||||
double threshold; // minimal "sameness" threshold from the train images
|
||||
|
||||
|
||||
MACEImpl(int siz) : IMGSIZE(siz), threshold(DBL_MAX) {}
|
||||
|
||||
void salt(const String &passphrase) CV_OVERRIDE {
|
||||
theRNG().state = ((int64)crc64((uchar*)passphrase.c_str(), passphrase.size()));
|
||||
convFilter.create(IMGSIZE, IMGSIZE, CV_64F);
|
||||
randn(convFilter, 0, 1.0/(IMGSIZE*IMGSIZE));
|
||||
}
|
||||
|
||||
|
||||
Mat dftImage(Mat img) const {
|
||||
Mat gray;
|
||||
resize(img, gray, Size(IMGSIZE,IMGSIZE)) ;
|
||||
if (gray.channels() > 1)
|
||||
cvtColor(gray, gray, COLOR_BGR2GRAY);
|
||||
equalizeHist(gray, gray);
|
||||
gray.convertTo(gray, CV_64F);
|
||||
if (! convFilter.empty()) { // optional, but unfortunately, it has to happen after resize/equalize ops.
|
||||
filter2D(gray, gray, CV_64F, convFilter);
|
||||
}
|
||||
Mat input[2] = {gray, Mat(gray.size(), gray.type(), 0.0)};
|
||||
Mat complexInput;
|
||||
merge(input, 2, complexInput);
|
||||
|
||||
Mat dftImg(IMGSIZE*2, IMGSIZE*2, CV_64FC2, 0.0);
|
||||
complexInput.copyTo(dftImg(Rect(0,0,IMGSIZE,IMGSIZE)));
|
||||
|
||||
dft(dftImg, dftImg);
|
||||
return dftImg;
|
||||
}
|
||||
|
||||
|
||||
// compute the mace filter: `h = D(-1) * X * (X(+) * D(-1) * X)(-1) * C`
|
||||
void compute(std::vector<Mat> images) {
|
||||
return compute(images, false);
|
||||
}
|
||||
void compute(std::vector<Mat> images, bool isdft) {
|
||||
int size = (int)images.size();
|
||||
int IMGSIZE_2X = IMGSIZE * 2;
|
||||
int TOTALPIXEL = IMGSIZE_2X * IMGSIZE_2X;
|
||||
|
||||
Mat_<double> D(TOTALPIXEL, 1, 0.0);
|
||||
Mat_<Vec2d> S(TOTALPIXEL, size, Vec2d(0,0));
|
||||
Mat_<Vec2d> SPLUS(size, TOTALPIXEL, Vec2d(0,0));
|
||||
for (int i=0; i<size; i++) {
|
||||
Mat_<Vec2d> dftImg = isdft ? images[i] : dftImage(images[i]);
|
||||
for (int l=0; l<IMGSIZE_2X; l++) {
|
||||
for (int m=0; m<IMGSIZE_2X; m++) {
|
||||
int j = l * IMGSIZE_2X + m;
|
||||
Vec2d s = dftImg(l, m);
|
||||
S(j, i) = s;
|
||||
SPLUS(i, j) = Vec2d(s[0], -s[1]);
|
||||
D(j, 0) += (s[0]*s[0]) + (s[1]*s[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Mat_<double> DSQ; cv::sqrt(D, DSQ);
|
||||
Mat_<double> DINV = TOTALPIXEL * size / DSQ;
|
||||
|
||||
Mat_<Vec2d> DINV_S(TOTALPIXEL, size);
|
||||
Mat_<Vec2d> SPLUS_DINV(size, TOTALPIXEL);
|
||||
for (int l=0; l<size; l++) {
|
||||
for (int m=0; m<TOTALPIXEL; m++) {
|
||||
DINV_S(m, l) = S(m,l) * DINV(m,0);
|
||||
SPLUS_DINV(l, m) = SPLUS(l,m) * DINV(m,0);
|
||||
}
|
||||
}
|
||||
|
||||
Mat_<Vec2d> SPLUS_DINV_S = SPLUS_DINV * S;
|
||||
Mat_<double> SPLUS_DINV_S_INV_1(2*size, 2*size, 0.0);
|
||||
for (int l=0; l<size; l++) {
|
||||
for (int m=0; m<size; m++) {
|
||||
Vec2d s = SPLUS_DINV_S(l, m);
|
||||
SPLUS_DINV_S_INV_1(l, m) = s[0];
|
||||
SPLUS_DINV_S_INV_1(l+size, m+size) = s[0];
|
||||
SPLUS_DINV_S_INV_1(l, m+size) = s[1];
|
||||
SPLUS_DINV_S_INV_1(l+size, m) = -s[1];
|
||||
}
|
||||
}
|
||||
invert(SPLUS_DINV_S_INV_1, SPLUS_DINV_S_INV_1);
|
||||
|
||||
Mat_<Vec2d> SPLUS_DINV_S_INV(size, size);
|
||||
for (int l=0; l<size; l++) {
|
||||
for (int m=0; m<size; m++) {
|
||||
SPLUS_DINV_S_INV(l, m) = Vec2d(SPLUS_DINV_S_INV_1(l,m), SPLUS_DINV_S_INV_1(l,m+size));
|
||||
}
|
||||
}
|
||||
|
||||
Mat_<Vec2d> Hmace = DINV_S * SPLUS_DINV_S_INV;
|
||||
Mat_<Vec2d> C(size, 1, Vec2d(1,0));
|
||||
maceFilter = Mat(Hmace * C).reshape(2,IMGSIZE_2X);
|
||||
}
|
||||
|
||||
// get the lowest (worst) positive train correlation,
|
||||
// our lower bound threshold for the "same()" test later
|
||||
double computeThreshold(const std::vector<Mat> &images, bool isdft) const {
|
||||
double best=DBL_MAX;
|
||||
for (size_t i=0; i<images.size(); i++) {
|
||||
double d = correlate(images[i], isdft);
|
||||
if (d < best) {
|
||||
best = d;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
// convolute macefilter and dft image,
|
||||
// calculate the peak to sidelobe ratio
|
||||
// on the real part of the inverse dft
|
||||
double correlate(const Mat &img) const {
|
||||
return correlate(img, false);
|
||||
}
|
||||
double correlate(const Mat &img, bool isdft) const {
|
||||
if (maceFilter.empty()) return -1; // not trained.
|
||||
int IMGSIZE_2X = IMGSIZE * 2;
|
||||
Mat dftImg = isdft ? img : dftImage(img);
|
||||
mulSpectrums(dftImg, maceFilter, dftImg, DFT_ROWS, true);
|
||||
dft(dftImg, dftImg, DFT_INVERSE|DFT_SCALE, 0);
|
||||
Mat chn[2];
|
||||
split(dftImg, chn);
|
||||
Mat_<double> re;
|
||||
shiftDFT(chn[0], re);
|
||||
double m1,M1;
|
||||
minMaxLoc(re, &m1, &M1, 0, 0);
|
||||
double peakCorrPlaneEnergy = M1 / sqrt(sum(re)[0]);
|
||||
re -= m1;
|
||||
|
||||
// circle mask for the sidelobe area
|
||||
Mat mask(IMGSIZE_2X, IMGSIZE_2X, CV_8U, Scalar(0));
|
||||
int rad_1 = int(floor((double)(45.0/64.0)*(double)IMGSIZE));
|
||||
int rad_2 = int(floor((double)(27.0/64.0)*(double)IMGSIZE));
|
||||
circle(mask, Point(IMGSIZE,IMGSIZE), rad_1, Scalar(255), -1);
|
||||
circle(mask, Point(IMGSIZE,IMGSIZE), rad_2, Scalar(0), -1);
|
||||
|
||||
Scalar mean, dev;
|
||||
meanStdDev(re, mean, dev, mask);
|
||||
double peak = re(IMGSIZE, IMGSIZE);
|
||||
double peakToSideLobeRatio = (peak - mean[0]) / dev[0];
|
||||
|
||||
return 100.0 * peakToSideLobeRatio * peakCorrPlaneEnergy;
|
||||
}
|
||||
|
||||
// MACE interface
|
||||
void train(InputArrayOfArrays input) CV_OVERRIDE {
|
||||
std::vector<Mat> images, dftImg;
|
||||
input.getMatVector(images);
|
||||
for (size_t i=0; i<images.size(); i++) { // cache dft images
|
||||
dftImg.push_back(dftImage(images[i]));
|
||||
}
|
||||
compute(dftImg, true);
|
||||
threshold = computeThreshold(dftImg, true);
|
||||
}
|
||||
bool same(InputArray img) const CV_OVERRIDE {
|
||||
return correlate(img.getMat()) >= threshold;
|
||||
}
|
||||
|
||||
// cv::Algorithm:
|
||||
bool empty() const CV_OVERRIDE {
|
||||
return maceFilter.empty() || IMGSIZE == 0;
|
||||
}
|
||||
String getDefaultName () const CV_OVERRIDE {
|
||||
return String("MACE");
|
||||
}
|
||||
void clear() CV_OVERRIDE {
|
||||
maceFilter.release();
|
||||
convFilter.release();
|
||||
}
|
||||
void write(cv::FileStorage &fs) const CV_OVERRIDE {
|
||||
fs << "mace" << maceFilter;
|
||||
fs << "conv" << convFilter;
|
||||
fs << "threshold" << threshold;
|
||||
}
|
||||
void read(const cv::FileNode &fn) CV_OVERRIDE {
|
||||
fn["mace"] >> maceFilter;
|
||||
fn["conv"] >> convFilter;
|
||||
fn["threshold"] >> threshold;
|
||||
IMGSIZE = maceFilter.cols/2;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
cv::Ptr<MACE> MACE::create(int siz) {
|
||||
return makePtr<MACEImpl>(siz);
|
||||
}
|
||||
cv::Ptr<MACE> MACE::load(const String &filename, const String &objname) {
|
||||
return Algorithm::load<MACE>(filename, objname);
|
||||
}
|
||||
|
||||
} /* namespace face */
|
||||
} /* namespace cv */
|
||||
@@ -0,0 +1,59 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's 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.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef __OPENCV_PRECOMP_H__
|
||||
#define __OPENCV_PRECOMP_H__
|
||||
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/geometry.hpp"
|
||||
#include "opencv2/core/utility.hpp"
|
||||
#include "opencv2/core/private.hpp"
|
||||
#include "opencv2/core/persistence.hpp"
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <limits>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
By downloading, copying, installing or using the software you agree to this license.
|
||||
If you do not agree to this license, do not download, install,
|
||||
copy or use the software.
|
||||
|
||||
|
||||
License Agreement
|
||||
For Open Source Computer Vision Library
|
||||
(3-clause BSD License)
|
||||
|
||||
Copyright (C) 2000-2015, Intel Corporation, all rights reserved.
|
||||
Copyright (C) 2009-2011, Willow Garage Inc., all rights reserved.
|
||||
Copyright (C) 2009-2015, NVIDIA Corporation, all rights reserved.
|
||||
Copyright (C) 2010-2013, Advanced Micro Devices, Inc., all rights reserved.
|
||||
Copyright (C) 2015, OpenCV Foundation, all rights reserved.
|
||||
Copyright (C) 2015, Itseez Inc., all rights reserved.
|
||||
Third party copyrights are property of their respective owners.
|
||||
|
||||
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 names of the copyright holders nor the names of the contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and
|
||||
any express or implied warranties, including, but not limited to, the implied
|
||||
warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
In no event shall copyright holders or contributors be liable for any direct,
|
||||
indirect, incidental, special, exemplary, or consequential damages
|
||||
(including, but not limited to, procurement of substitute goods or services;
|
||||
loss of use, data, or profits; or business interruption) however caused
|
||||
and on any theory of liability, whether in contract, strict liability,
|
||||
or tort (including negligence or otherwise) arising in any way out of
|
||||
the use of this software, even if advised of the possibility of such damage.
|
||||
*/
|
||||
#include "opencv2/face/predict_collector.hpp"
|
||||
|
||||
namespace cv {namespace face {
|
||||
|
||||
static std::pair<int, double> toPair(const StandardCollector::PredictResult & val) {
|
||||
return std::make_pair(val.label, val.distance);
|
||||
}
|
||||
|
||||
static bool pairLess(const std::pair<int, double> & lhs, const std::pair<int, double> & rhs) {
|
||||
return lhs.second < rhs.second;
|
||||
}
|
||||
|
||||
//===================================
|
||||
|
||||
StandardCollector::StandardCollector(double threshold_) : threshold(threshold_) {
|
||||
init(0);
|
||||
}
|
||||
|
||||
void StandardCollector::init(size_t size) {
|
||||
minRes = PredictResult();
|
||||
data.clear();
|
||||
data.reserve(size);
|
||||
}
|
||||
|
||||
bool StandardCollector::collect(int label, double dist) {
|
||||
if (dist < threshold)
|
||||
{
|
||||
PredictResult res(label, dist);
|
||||
if (res.distance < minRes.distance)
|
||||
minRes = res;
|
||||
data.push_back(res);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int StandardCollector::getMinLabel() const {
|
||||
return minRes.label;
|
||||
}
|
||||
|
||||
double StandardCollector::getMinDist() const {
|
||||
return minRes.distance;
|
||||
}
|
||||
|
||||
std::vector< std::pair<int, double> > StandardCollector::getResults(bool sorted) const {
|
||||
std::vector< std::pair<int, double> > res(data.size());
|
||||
std::transform(data.begin(), data.end(), res.begin(), &toPair);
|
||||
if (sorted)
|
||||
{
|
||||
std::sort(res.begin(), res.end(), &pairLess);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::map<int, double> StandardCollector::getResultsMap() const {
|
||||
std::map<int, double> res;
|
||||
for (std::vector<PredictResult>::const_iterator i = data.begin(); i != data.end(); ++i) {
|
||||
std::map<int, double>::iterator j = res.find(i->label);
|
||||
if (j == res.end()) {
|
||||
res.insert(toPair(*i));
|
||||
} else if (i->distance < j->second) {
|
||||
j->second = i->distance;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Ptr<StandardCollector> StandardCollector::create(double threshold) {
|
||||
return makePtr<StandardCollector>(threshold);
|
||||
}
|
||||
|
||||
}} // cv::face::
|
||||
@@ -0,0 +1,309 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "face_alignmentimpl.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace cv{
|
||||
namespace face{
|
||||
//Threading helper classes
|
||||
class doSum : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
doSum(vector<training_sample>* samples_,vector<Point2f>* sum_) :
|
||||
samples(samples_),
|
||||
sum(sum_)
|
||||
{
|
||||
}
|
||||
virtual void operator()( const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for (int j = range.start; j < range.end; ++j){
|
||||
for(unsigned long k=0;k<(*samples)[j].shapeResiduals.size();k++){
|
||||
(*sum)[k]=(*sum)[k]+(*samples)[j].shapeResiduals[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
vector<training_sample>* samples;
|
||||
vector<Point2f>* sum;
|
||||
};
|
||||
class modifySamples : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
modifySamples(vector<training_sample>* samples_,vector<Point2f>* temp_) :
|
||||
samples(samples_),
|
||||
temp(temp_)
|
||||
{
|
||||
}
|
||||
virtual void operator()( const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for (int j = range.start; j < range.end; ++j){
|
||||
for(unsigned long k=0;k<(*samples)[j].shapeResiduals.size();k++){
|
||||
(*samples)[j].shapeResiduals[k]=(*samples)[j].shapeResiduals[k]-(*temp)[k];
|
||||
(*samples)[j].current_shape[k]=(*samples)[j].actual_shape[k]-(*samples)[j].shapeResiduals[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
vector<training_sample>* samples;
|
||||
vector<Point2f>* temp;
|
||||
};
|
||||
class splitSamples : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
splitSamples(vector<training_sample>* samples_,vector< vector<Point2f> >* leftsumresiduals_,vector<unsigned long>* left_count_,unsigned long* num_test_splits_,vector<splitr>* feats_) :
|
||||
samples(samples_),
|
||||
leftsumresiduals(leftsumresiduals_),
|
||||
left_count(left_count_),
|
||||
num_test_splits(num_test_splits_),
|
||||
feats(feats_)
|
||||
{
|
||||
}
|
||||
virtual void operator()( const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for (int i = range.start; i < range.end; ++i){
|
||||
for(unsigned long j=0;j<*(num_test_splits);j++){
|
||||
(*left_count)[j]++;
|
||||
if ((float)(*samples)[i].pixel_intensities[(unsigned long)(*feats)[j].index1] - (float)(*samples)[i].pixel_intensities[(unsigned long)(*feats)[j].index2] > (*feats)[j].thresh){
|
||||
for(unsigned long k=0;k<(*samples)[i].shapeResiduals.size();k++){
|
||||
(*leftsumresiduals)[j][k]=(*leftsumresiduals)[j][k]+(*samples)[i].shapeResiduals[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
vector<training_sample>* samples;
|
||||
vector< vector<Point2f> >* leftsumresiduals;
|
||||
vector<unsigned long>* left_count;
|
||||
unsigned long* num_test_splits;
|
||||
vector<splitr>* feats;
|
||||
};
|
||||
splitr FacemarkKazemiImpl::getTestSplits(vector<Point2f> pixel_coordinates,int seed)
|
||||
{
|
||||
splitr feat;
|
||||
//generates splits whose probability is above a particular threshold.
|
||||
//P(u,v)=e^(-distance/lambda) as described in the research paper
|
||||
//cited above. This helps to select closer pixels hence make efficient
|
||||
//splits.
|
||||
double probability;
|
||||
double check;
|
||||
RNG rng(seed);
|
||||
do
|
||||
{
|
||||
//select random pixel coordinate
|
||||
feat.index1 = rng.uniform(0,params.num_test_coordinates);
|
||||
//select another random coordinate
|
||||
feat.index2 = rng.uniform(0,params.num_test_coordinates);
|
||||
Point2f pt = pixel_coordinates[(unsigned long)feat.index1]-pixel_coordinates[(unsigned long)feat.index2];
|
||||
double distance = sqrt((pt.x*pt.x)+(pt.y*pt.y));
|
||||
//calculate the probability
|
||||
probability = exp(-distance/params.lambda);
|
||||
check = rng.uniform(double(0),double(1));
|
||||
}
|
||||
while(check>probability||feat.index1==feat.index2);
|
||||
feat.thresh =(float)(((rng.uniform(double(0),double(1)))*256 - 128)/2.0);
|
||||
return feat;
|
||||
}
|
||||
bool FacemarkKazemiImpl:: getBestSplit(vector<Point2f> pixel_coordinates, vector<training_sample>& samples,unsigned long start ,
|
||||
unsigned long end,splitr& split,vector< vector<Point2f> >& sum,long node_no)
|
||||
{
|
||||
if(samples[0].shapeResiduals.size()!=samples[0].current_shape.size()){
|
||||
String error_message = "Error while generating split.Residuals are not complete.Aborting....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
//This vector stores the matrices where each matrix represents
|
||||
//sum of the residuals of shapes of samples which go to the left
|
||||
//child after split
|
||||
vector< vector<Point2f> > leftsumresiduals;
|
||||
leftsumresiduals.resize(params.num_test_splits);
|
||||
vector<splitr> feats;
|
||||
//generate random splits and selects the best split amongst them.
|
||||
for (unsigned long i = 0; i < params.num_test_splits; ++i){
|
||||
feats.push_back(getTestSplits(pixel_coordinates,i+(int)time(0)));
|
||||
leftsumresiduals[i].resize(samples[0].shapeResiduals.size());
|
||||
}
|
||||
vector<unsigned long> left_count;
|
||||
left_count.resize(params.num_test_splits);
|
||||
parallel_for_(Range(start,end),splitSamples(&samples,&leftsumresiduals,&left_count,¶ms.num_test_splits,&feats));
|
||||
//Selecting the best split
|
||||
double best_score =-1;
|
||||
unsigned long best_feat = 0;
|
||||
double score = -1;
|
||||
vector<Point2f> right_sum;
|
||||
right_sum.resize(sum[node_no].size());
|
||||
vector<Point2f> left_sum;
|
||||
left_sum.resize(sum[node_no].size());
|
||||
unsigned long right_cnt;
|
||||
for(unsigned long i=0;i<leftsumresiduals.size();i++){
|
||||
right_cnt = (end-start+1)-left_count[i];
|
||||
for(unsigned long k=0;k<leftsumresiduals[i].size();k++){
|
||||
if (right_cnt!=0){
|
||||
right_sum[k].x=(sum[node_no][k].x-leftsumresiduals[i][k].x)/right_cnt;
|
||||
right_sum[k].y=(sum[node_no][k].y-leftsumresiduals[i][k].y)/right_cnt;
|
||||
}
|
||||
else
|
||||
right_sum[k]=Point2f(0,0);
|
||||
if(left_count[i]!=0){
|
||||
left_sum[k].x=leftsumresiduals[i][k].x/left_count[i];
|
||||
left_sum[k].y=leftsumresiduals[i][k].y/left_count[i];
|
||||
}
|
||||
else
|
||||
left_sum[k]=Point2f(0,0);
|
||||
}
|
||||
Point2f pt1(0,0);
|
||||
Point2f pt2(0,0);
|
||||
for(unsigned long k=0;k<left_sum.size();k++){
|
||||
pt1.x = pt1.x + (float)(left_sum[k].x*left_sum[k].x);
|
||||
pt2.x = pt2.x + (float)(right_sum[k].x*right_sum[k].x);
|
||||
pt1.y = pt1.y + (float)(left_sum[k].y*left_sum[k].y);
|
||||
pt2.y = pt2.y + (float)(right_sum[k].y*right_sum[k].y);
|
||||
}
|
||||
score = (double)sqrt(pt1.x+pt1.y)*(double)left_count[i] + (double)sqrt(pt2.x+pt2.y)*(double)right_cnt;
|
||||
if(score > best_score){
|
||||
best_score = score;
|
||||
best_feat = i;
|
||||
}
|
||||
}
|
||||
sum[2*node_no+1] = leftsumresiduals[best_feat];
|
||||
sum[2*node_no+2].resize(sum[node_no].size());
|
||||
for(unsigned long k=0;k<sum[node_no].size();k++){
|
||||
sum[2*node_no+2][k].x = sum[node_no][k].x-sum[2*node_no+1][k].x;
|
||||
sum[2*node_no+2][k].y = sum[node_no][k].y-sum[2*node_no+1][k].y;
|
||||
}
|
||||
split = feats[best_feat];
|
||||
return true;
|
||||
}
|
||||
void FacemarkKazemiImpl::createSplitNode(regtree& tree, splitr split,long node_no){
|
||||
tree_node node;
|
||||
node.split = split;
|
||||
node.leaf.clear();
|
||||
tree.nodes[node_no]=node;
|
||||
}
|
||||
void FacemarkKazemiImpl::createLeafNode(regtree& tree,long node_no,vector<Point2f> assign){
|
||||
tree_node node;
|
||||
node.split.index1 = (uint64_t)(-1);
|
||||
node.split.index2 = (uint64_t)(-1);
|
||||
node.leaf = assign;
|
||||
tree.nodes[node_no] = node;
|
||||
}
|
||||
bool FacemarkKazemiImpl :: generateSplit(queue<node_info>& curr,vector<Point2f> pixel_coordinates, vector<training_sample>& samples,
|
||||
splitr &split , vector< vector<Point2f> >& sum){
|
||||
|
||||
long start = curr.front().index1;
|
||||
long end = curr.front().index2;
|
||||
long _depth = curr.front().depth;
|
||||
long node_no =curr.front().node_no;
|
||||
curr.pop();
|
||||
if(start == end)
|
||||
return false;
|
||||
getBestSplit(pixel_coordinates,samples,start,end,split,sum,node_no);
|
||||
long mid = divideSamples(split, samples, start, end);
|
||||
//cout<<mid<<endl;
|
||||
if(mid==start||mid==end+1)
|
||||
return false;
|
||||
node_info _left,_right;
|
||||
_left.index1 = start;
|
||||
_left.index2 = mid-1;
|
||||
_left.depth = _depth +1;
|
||||
_left.node_no = 2*node_no+1;
|
||||
_right.index1 = mid;
|
||||
_right.index2 = end;
|
||||
_right.depth = _depth +1;
|
||||
_right.node_no = 2*node_no+2;
|
||||
curr.push(_left);
|
||||
curr.push(_right);
|
||||
return true;
|
||||
}
|
||||
bool FacemarkKazemiImpl :: buildRegtree(regtree& tree,vector<training_sample>& samples,vector<Point2f> pixel_coordinates){
|
||||
if(samples.size()==0){
|
||||
String error_message = "Error while building regression tree.Empty samples. Aborting....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
if(pixel_coordinates.size()==0){
|
||||
String error_message = "Error while building regression tree.No pixel coordinates. Aborting....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
queue<node_info> curr;
|
||||
node_info parent;
|
||||
vector< vector<Point2f> > sum;
|
||||
const long numNodes =(long)pow(2,params.tree_depth);
|
||||
const long numSplitNodes = numNodes/2 - 1;
|
||||
sum.resize(numNodes+1);
|
||||
sum[0].resize(samples[0].shapeResiduals.size());
|
||||
parallel_for_(cv::Range(0,(int)samples.size()), doSum(&(samples),&(sum[0])));
|
||||
parent.index1=0;
|
||||
parent.index2=(long)samples.size()-1;
|
||||
parent.node_no=0;
|
||||
parent.depth=0;
|
||||
curr.push(parent);
|
||||
tree.nodes.resize(numNodes+1);
|
||||
//Total number of split nodes
|
||||
while(!curr.empty()){
|
||||
pair<long,long> range= make_pair(curr.front().index1,curr.front().index2);
|
||||
long node_no = curr.front().node_no;
|
||||
splitr split = {0, 0, 0};
|
||||
//generate a split
|
||||
if(node_no<=numSplitNodes){
|
||||
if(generateSplit(curr,pixel_coordinates,samples,split,sum)){
|
||||
createSplitNode(tree,split,node_no);
|
||||
}
|
||||
//create leaf
|
||||
else{
|
||||
long count = range.second-range.first +1;
|
||||
vector<Point2f> temp;
|
||||
temp.resize(samples[range.first].shapeResiduals.size());
|
||||
parallel_for_(Range(range.first, range.second), doSum(&(samples),&(temp)));
|
||||
for(unsigned long k=0;k<temp.size();k++){
|
||||
temp[k].x=(temp[k].x/count)*params.learning_rate;
|
||||
temp[k].y=(temp[k].y/count)*params.learning_rate;
|
||||
}
|
||||
// Modify current shape according to the weak learners.
|
||||
parallel_for_(Range(range.first,range.second), modifySamples(&(samples),&(temp)));
|
||||
createLeafNode(tree,node_no,temp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned long count = range.second-range.first +1;
|
||||
vector<Point2f> temp;
|
||||
temp.resize(samples[range.first].shapeResiduals.size());
|
||||
parallel_for_(Range(range.first, range.second), doSum(&(samples),&(temp)));
|
||||
for(unsigned long k=0;k<temp.size();k++){
|
||||
temp[k].x=(temp[k].x/count)*params.learning_rate;
|
||||
temp[k].y=(temp[k].y/count)*params.learning_rate;
|
||||
}
|
||||
// Modify current shape according to the weak learners.
|
||||
parallel_for_(Range(range.first,range.second), modifySamples(&(samples),&(temp)));
|
||||
createLeafNode(tree,node_no,temp);
|
||||
curr.pop();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
unsigned long FacemarkKazemiImpl::divideSamples (splitr split,vector<training_sample>& samples,unsigned long start,unsigned long end)
|
||||
{
|
||||
if(samples.size()==0){
|
||||
String error_message = "Error while dividing samples. Sample array empty. Aborting....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
unsigned long i = start;
|
||||
training_sample temp;
|
||||
//partition samples according to the split
|
||||
for (unsigned long j = start; j < end; ++j)
|
||||
{
|
||||
if ((float)samples[j].pixel_intensities[(unsigned long)split.index1] - (float)samples[j].pixel_intensities[(unsigned long)split.index2] > split.thresh)
|
||||
{
|
||||
temp=samples[i];
|
||||
samples[i]=samples[j];
|
||||
samples[j]=temp;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}//cv
|
||||
}//face
|
||||
@@ -0,0 +1,345 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "face_alignmentimpl.hpp"
|
||||
#include "opencv2/geometry.hpp"
|
||||
#include <climits>
|
||||
|
||||
using namespace std;
|
||||
namespace cv{
|
||||
namespace face{
|
||||
// Threading helper classes
|
||||
class getDiffShape : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
getDiffShape(vector<training_sample>* samples_) :
|
||||
samples(samples_)
|
||||
{
|
||||
}
|
||||
virtual void operator()( const cv::Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for(size_t j = (size_t)range.start; j < (size_t)range.end; ++j){
|
||||
(*samples)[j].shapeResiduals.resize((*samples)[j].current_shape.size());
|
||||
for(unsigned long k=0;k<(*samples)[j].current_shape.size();k++)
|
||||
(*samples)[j].shapeResiduals[k]=(*samples)[j].actual_shape[k]-(*samples)[j].current_shape[k];
|
||||
}
|
||||
}
|
||||
private:
|
||||
vector<training_sample>* samples;
|
||||
};
|
||||
class getRelPixels : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
getRelPixels(vector<training_sample>* samples_,FacemarkKazemiImpl& object_) :
|
||||
samples(samples_),
|
||||
object(object_)
|
||||
{
|
||||
}
|
||||
virtual void operator()( const cv::Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for (size_t j = (size_t)range.start; j < (size_t)range.end; ++j){
|
||||
object.getRelativePixels(((*samples)[j]).current_shape,((*samples)[j]).pixel_coordinates);
|
||||
}
|
||||
}
|
||||
private:
|
||||
vector<training_sample>* samples;
|
||||
FacemarkKazemiImpl& object;
|
||||
};
|
||||
//This function initialises the training parameters.
|
||||
bool FacemarkKazemiImpl::setTrainingParameters(String filename){
|
||||
cout << "Reading Training Parameters " << endl;
|
||||
FileStorage fs;
|
||||
fs.open(filename, FileStorage::READ);
|
||||
if (!fs.isOpened())
|
||||
{ String error_message = "Error while opening configuration file.Aborting..";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
int cascade_depth_;
|
||||
int tree_depth_;
|
||||
int num_trees_per_cascade_level_;
|
||||
float learning_rate_;
|
||||
int oversampling_amount_;
|
||||
int num_test_coordinates_;
|
||||
float lambda_;
|
||||
int num_test_splits_;
|
||||
fs["cascade_depth"]>> cascade_depth_;
|
||||
fs["tree_depth"]>> tree_depth_;
|
||||
fs["num_trees_per_cascade_level"] >> num_trees_per_cascade_level_;
|
||||
fs["learning_rate"] >> learning_rate_;
|
||||
fs["oversampling_amount"] >> oversampling_amount_;
|
||||
fs["num_test_coordinates"] >> num_test_coordinates_;
|
||||
fs["lambda"] >> lambda_;
|
||||
fs["num_test_splits"] >> num_test_splits_;
|
||||
params.cascade_depth = (unsigned long)cascade_depth_;
|
||||
params.tree_depth = (unsigned long) tree_depth_;
|
||||
params.num_trees_per_cascade_level = (unsigned long) num_trees_per_cascade_level_;
|
||||
params.learning_rate = (float) learning_rate_;
|
||||
params.oversampling_amount = (unsigned long) oversampling_amount_;
|
||||
params.num_test_coordinates = (unsigned long) num_test_coordinates_;
|
||||
params.lambda = (float) lambda_;
|
||||
params.num_test_splits = (unsigned long) num_test_splits_;
|
||||
fs.release();
|
||||
cout<<"Parameters loaded"<<endl;
|
||||
return true;
|
||||
}
|
||||
void FacemarkKazemiImpl::getTestCoordinates ()
|
||||
{
|
||||
for(unsigned long i = 0; i < params.cascade_depth; ++i){
|
||||
vector<Point2f> temp;
|
||||
RNG rng = theRNG();
|
||||
for(unsigned long j = 0; j < params.num_test_coordinates; ++j)
|
||||
{
|
||||
Point2f pt;
|
||||
pt.x = (float)rng.uniform(minmeanx,maxmeanx);
|
||||
pt.y = (float)rng.uniform(minmeany,maxmeany);
|
||||
temp.push_back(pt);
|
||||
}
|
||||
loaded_pixel_coordinates.push_back(temp);
|
||||
}
|
||||
}
|
||||
unsigned long FacemarkKazemiImpl:: getNearestLandmark(Point2f pixel)
|
||||
{
|
||||
if(meanshape.empty()) {
|
||||
// throw error if no data (or simply return -1?)
|
||||
String error_message = "The data is not loaded properly by train function. Aborting...";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
float dist=float(INT_MAX);
|
||||
unsigned long index =0;
|
||||
for(unsigned long i=0;i<meanshape.size();i++){
|
||||
Point2f pt = meanshape[i]-pixel;
|
||||
if(sqrt(pt.x*pt.x+pt.y*pt.y)<dist){
|
||||
dist=sqrt(pt.x*pt.x+pt.y*pt.y);
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
bool FacemarkKazemiImpl :: getRelativePixels(vector<Point2f> sample,vector<Point2f>& pixel_coordinates,std::vector<int> nearest){
|
||||
if(sample.size()!=meanshape.size()){
|
||||
String error_message = "Error while finding relative shape. Aborting....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
Mat transform_mat;
|
||||
transform_mat = estimateAffinePartial2D(meanshape, sample);
|
||||
unsigned long index;
|
||||
for (unsigned long i = 0;i<pixel_coordinates.size();i++) {
|
||||
if(!nearest.empty())
|
||||
index = nearest[i];
|
||||
index = getNearestLandmark(pixel_coordinates[i]);
|
||||
pixel_coordinates[i] = pixel_coordinates[i] - meanshape[index];
|
||||
Mat C = (Mat_<double>(3,1) << pixel_coordinates[i].x, pixel_coordinates[i].y, 0);
|
||||
if(!transform_mat.empty()){
|
||||
Mat D =transform_mat*C;
|
||||
pixel_coordinates[i].x = float((D.at<double>(0,0)));
|
||||
pixel_coordinates[i].y = float((D.at<double>(1,0)));
|
||||
}
|
||||
pixel_coordinates[i] = pixel_coordinates[i] + sample[index];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool FacemarkKazemiImpl::getPixelIntensities(Mat img,vector<Point2f> pixel_coordinates,vector<int>& pixel_intensities,Rect face){
|
||||
if(pixel_coordinates.size()==0){
|
||||
String error_message = "No pixel coordinates found. Aborting.....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
Mat transform_mat;
|
||||
convertToActual(face,transform_mat);
|
||||
Mat dst = img.clone();
|
||||
Mat C,D;
|
||||
for(size_t j=0;j<pixel_coordinates.size();j++){
|
||||
C = (Mat_<double>(3,1) << pixel_coordinates[j].x, pixel_coordinates[j].y, 1);
|
||||
D = transform_mat*C;
|
||||
pixel_coordinates[j].x = float(D.at<double>(0,0));
|
||||
pixel_coordinates[j].y = float(D.at<double>(1,0));
|
||||
}
|
||||
int val;
|
||||
for(unsigned long j=0;j<pixel_coordinates.size();j++){
|
||||
if(pixel_coordinates[j].x>0&&pixel_coordinates[j].x<img.cols&&pixel_coordinates[j].y>0&&pixel_coordinates[j].y<img.rows){
|
||||
Vec3b val1 = img.at<Vec3b>((int)pixel_coordinates[j].y,(int)pixel_coordinates[j].x);
|
||||
val = (int)(val1[0]+val1[1]+val1[2])/3;
|
||||
}
|
||||
else
|
||||
val = 0;
|
||||
pixel_intensities.push_back(val);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
vector<regtree> FacemarkKazemiImpl::gradientBoosting(vector<training_sample>& samples,vector<Point2f> pixel_coordinates){
|
||||
vector<regtree> forest;
|
||||
vector<Point2f> meanresidual;
|
||||
meanresidual.resize(samples[0].shapeResiduals.size());
|
||||
for(unsigned long i=0;i<samples.size();i++){
|
||||
for(unsigned long j=0;j<samples[i].shapeResiduals.size();j++){
|
||||
meanresidual[j]=meanresidual[j]+samples[i].shapeResiduals[j];
|
||||
}
|
||||
}
|
||||
for(unsigned long i=0;i<meanresidual.size();i++){
|
||||
meanresidual[i].x=(meanresidual[i].x)/samples.size();
|
||||
meanresidual[i].y=(meanresidual[i].y)/samples.size();
|
||||
}
|
||||
for(unsigned long i=0;i<samples.size();i++){
|
||||
for(unsigned long j=0;j<samples[i].shapeResiduals.size();j++)
|
||||
samples[i].shapeResiduals[j]=samples[i].shapeResiduals[j]-meanresidual[j];
|
||||
}
|
||||
for(unsigned long i=0;i<params.num_trees_per_cascade_level;i++){
|
||||
regtree tree;
|
||||
buildRegtree(tree,samples,pixel_coordinates);
|
||||
forest.push_back(tree);
|
||||
}
|
||||
return forest;
|
||||
}
|
||||
bool FacemarkKazemiImpl::createTrainingSamples(vector<training_sample> &samples,vector<Mat> images,vector< vector<Point2f> > landmarks,vector<Rect> rectangle){
|
||||
unsigned long in=0;
|
||||
samples.resize(params.oversampling_amount*images.size());
|
||||
for(unsigned long i=0;i<images.size();i++){
|
||||
for(unsigned long j=0;j<params.oversampling_amount;j++){
|
||||
samples[in].image=images[i];
|
||||
samples[in].actual_shape = landmarks[i];
|
||||
samples[in].bound = rectangle[i];
|
||||
unsigned long rindex=i;
|
||||
if(in%2==0)
|
||||
samples[in].current_shape = meanshape;
|
||||
else{
|
||||
RNG rng(in);
|
||||
rindex =(unsigned long)rng.uniform(0,(int)landmarks.size()-1);
|
||||
samples[in].current_shape = landmarks[rindex];
|
||||
}
|
||||
in++;
|
||||
}
|
||||
}
|
||||
parallel_for_(Range(0,(int)samples.size()),getDiffShape(&samples));
|
||||
return true;
|
||||
}
|
||||
void FacemarkKazemiImpl :: writeLeaf(ofstream& os, const vector<Point2f> &leaf)
|
||||
{
|
||||
uint64_t size = leaf.size();
|
||||
os.write((char*)&size, sizeof(size));
|
||||
os.write((char*)&leaf[0], leaf.size() * sizeof(Point2f));
|
||||
}
|
||||
void FacemarkKazemiImpl :: writeSplit(ofstream& os, const splitr& vec)
|
||||
{
|
||||
os.write((char*)&vec.index1, sizeof(vec.index1));
|
||||
os.write((char*)&vec.index2, sizeof(vec.index2));
|
||||
os.write((char*)&vec.thresh, sizeof(vec.thresh));
|
||||
uint32_t dummy_ = 0;
|
||||
os.write((char*)&dummy_, sizeof(dummy_)); // buggy original writer structure alignment
|
||||
CV_CheckEQ((int)(sizeof(vec.index1) + sizeof(vec.index2) + sizeof(vec.thresh) + sizeof(dummy_)), 24, "Invalid build configuration");
|
||||
|
||||
}
|
||||
void FacemarkKazemiImpl :: writeTree(ofstream &f,regtree tree)
|
||||
{
|
||||
string s("num_nodes");
|
||||
uint64_t len = s.size();
|
||||
f.write((char*)&len, sizeof(len));
|
||||
f.write(s.c_str(), len);
|
||||
uint64_t num_nodes = tree.nodes.size();
|
||||
f.write((char*)&num_nodes,sizeof(num_nodes));
|
||||
for(size_t i=0;i<tree.nodes.size();i++){
|
||||
if(tree.nodes[i].leaf.empty()){
|
||||
s = string("split");
|
||||
len = s.size();
|
||||
f.write((char*)&len, sizeof(len));
|
||||
f.write(s.c_str(), len);
|
||||
writeSplit(f,tree.nodes[i].split);
|
||||
}
|
||||
else{
|
||||
s = string("leaf");
|
||||
len = s.size();
|
||||
f.write((char*)&len, sizeof(len));
|
||||
f.write(s.c_str(), len);
|
||||
writeLeaf(f,tree.nodes[i].leaf);
|
||||
}
|
||||
}
|
||||
}
|
||||
void FacemarkKazemiImpl :: writePixels(ofstream& f,int index){
|
||||
f.write((char*)&loaded_pixel_coordinates[index][0], loaded_pixel_coordinates[index].size() * sizeof(Point2f));
|
||||
}
|
||||
bool FacemarkKazemiImpl :: saveModel(String filename){
|
||||
ofstream f(filename.c_str(),ios::binary);
|
||||
if(!f.is_open()){
|
||||
String error_message = "Error while opening file to write model. Aborting....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
if(loaded_forests.size()!=loaded_pixel_coordinates.size()){
|
||||
String error_message = "Incorrect training data. Aborting....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
string s("cascade_depth");
|
||||
uint64_t len = s.size();
|
||||
f.write((char*)&len, sizeof(len));
|
||||
f.write(s.c_str(), len);
|
||||
uint64_t cascade_size = loaded_forests.size();
|
||||
f.write((char*)&cascade_size,sizeof(cascade_size));
|
||||
s = string("pixel_coordinates");
|
||||
len = s.size();
|
||||
f.write((char*)&len, sizeof(len));
|
||||
f.write(s.c_str(), len);
|
||||
uint64_t num_pixels = loaded_pixel_coordinates[0].size();
|
||||
f.write((char*)&num_pixels,sizeof(num_pixels));
|
||||
for(unsigned long i=0;i< loaded_pixel_coordinates.size();i++){
|
||||
writePixels(f,i);
|
||||
}
|
||||
s = string("mean_shape");
|
||||
uint64_t len1 = s.size();
|
||||
f.write((char*)&len1, sizeof(len1));
|
||||
f.write(s.c_str(), len1);
|
||||
uint64_t mean_shape_size = meanshape.size();
|
||||
f.write((char*)&mean_shape_size,sizeof(mean_shape_size));
|
||||
f.write((char*)&meanshape[0], meanshape.size() * sizeof(Point2f));
|
||||
s = string("num_trees");
|
||||
len = s.size();
|
||||
f.write((char*)&len, sizeof(len));
|
||||
f.write(s.c_str(), len);
|
||||
uint64_t num_trees = loaded_forests[0].size();
|
||||
f.write((char*)&num_trees,sizeof(num_trees));
|
||||
for(unsigned long i=0 ; i<loaded_forests.size() ; i++){
|
||||
for(unsigned long j=0 ; j<loaded_forests[i].size() ; j++){
|
||||
writeTree(f,loaded_forests[i][j]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void FacemarkKazemiImpl::training(String imageList, String groundTruth){
|
||||
imageList.clear();
|
||||
groundTruth.clear();
|
||||
String error_message = "Less arguments than required";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
bool FacemarkKazemiImpl::training(vector<Mat>& images, vector< vector<Point2f> >& landmarks,string filename,Size scale,string modelFilename){
|
||||
if(!setTrainingParameters(filename)){
|
||||
String error_message = "Error while loading training parameters";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
vector<Rect> rectangles;
|
||||
scaleData(landmarks,images,scale);
|
||||
calcMeanShape(landmarks,images,rectangles);
|
||||
if(images.size()!=landmarks.size()){
|
||||
// throw error if no data (or simply return -1?)
|
||||
String error_message = "The data is not loaded properly. Aborting training function....";
|
||||
CV_Error(Error::StsBadArg, error_message);
|
||||
}
|
||||
vector<training_sample> samples;
|
||||
getTestCoordinates();
|
||||
createTrainingSamples(samples,images,landmarks,rectangles);
|
||||
images.clear();
|
||||
landmarks.clear();
|
||||
rectangles.clear();
|
||||
for(unsigned long i=0;i< params.cascade_depth;i++){
|
||||
cout<<"Training regressor "<<i<<"..."<<endl;
|
||||
for (std::vector<training_sample>::iterator it = samples.begin(); it != samples.end(); it++) {
|
||||
(*it).pixel_coordinates = loaded_pixel_coordinates[i];
|
||||
}
|
||||
parallel_for_(Range(0,(int)samples.size()),getRelPixels(&samples,*this));
|
||||
for (std::vector<training_sample>::iterator it = samples.begin(); it != samples.end(); it++) {
|
||||
getPixelIntensities((*it).image,(*it).pixel_coordinates,(*it).pixel_intensities,(*it).bound);
|
||||
}
|
||||
loaded_forests.push_back(gradientBoosting(samples,loaded_pixel_coordinates[i]));
|
||||
}
|
||||
saveModel(modelFilename);
|
||||
return true;
|
||||
}
|
||||
}//cv
|
||||
}//face
|
||||
Reference in New Issue
Block a user