vendor: OpenCV 5.0.0 snapshot at 755e50675d97db9b7d449d8bd6b09888646f6c6e

This commit is contained in:
Gitea Mirror Bot
2026-08-22 00:11:13 +08:00
commit 12022378a3
3872 changed files with 2513409 additions and 0 deletions
+221
View File
@@ -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));
}
+139
View File
@@ -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), 7186.
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);
}
}
}
+194
View File
@@ -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 &parameters){
return Ptr<FacemarkKazemiImpl>(new FacemarkKazemiImpl(parameters));
}
Ptr<Facemark> createFacemarkKazemi() {
FacemarkKazemi::Params parameters;
return Ptr<FacemarkKazemiImpl>(new FacemarkKazemiImpl(parameters));
}
}//cv
}//face
+177
View File
@@ -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
+100
View File
@@ -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());
}
+108
View File
@@ -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
+265
View File
@@ -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
+93
View File
@@ -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();
}
}
}
+158
View File
@@ -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),
// 711720.
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);
}
} }
+288
View File
@@ -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
+432
View File
@@ -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);
}
}}
+270
View File
@@ -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 */
+59
View File
@@ -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
+114
View File
@@ -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::
+309
View File
@@ -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,&params.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
+345
View File
@@ -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