You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
309 lines
12 KiB
309 lines
12 KiB
// This file is part of OpenCV project. |
|
// It is subject to the license terms in the LICENSE file found in the top-level directory |
|
// of this distribution and at http://opencv.org/license.html. |
|
|
|
#include "precomp.hpp" |
|
#include "face_alignmentimpl.hpp" |
|
|
|
using namespace std; |
|
|
|
namespace cv{ |
|
namespace face{ |
|
//Threading helper classes |
|
class doSum : public ParallelLoopBody |
|
{ |
|
public: |
|
doSum(vector<training_sample>* samples_,vector<Point2f>* sum_) : |
|
samples(samples_), |
|
sum(sum_) |
|
{ |
|
} |
|
virtual void operator()( const Range& range) const CV_OVERRIDE |
|
{ |
|
for (int j = range.start; j < range.end; ++j){ |
|
for(unsigned long k=0;k<(*samples)[j].shapeResiduals.size();k++){ |
|
(*sum)[k]=(*sum)[k]+(*samples)[j].shapeResiduals[k]; |
|
} |
|
} |
|
} |
|
private: |
|
vector<training_sample>* samples; |
|
vector<Point2f>* sum; |
|
}; |
|
class modifySamples : public ParallelLoopBody |
|
{ |
|
public: |
|
modifySamples(vector<training_sample>* samples_,vector<Point2f>* temp_) : |
|
samples(samples_), |
|
temp(temp_) |
|
{ |
|
} |
|
virtual void operator()( const Range& range) const CV_OVERRIDE |
|
{ |
|
for (int j = range.start; j < range.end; ++j){ |
|
for(unsigned long k=0;k<(*samples)[j].shapeResiduals.size();k++){ |
|
(*samples)[j].shapeResiduals[k]=(*samples)[j].shapeResiduals[k]-(*temp)[k]; |
|
(*samples)[j].current_shape[k]=(*samples)[j].actual_shape[k]-(*samples)[j].shapeResiduals[k]; |
|
} |
|
} |
|
} |
|
private: |
|
vector<training_sample>* samples; |
|
vector<Point2f>* temp; |
|
}; |
|
class splitSamples : public ParallelLoopBody |
|
{ |
|
public: |
|
splitSamples(vector<training_sample>* samples_,vector< vector<Point2f> >* leftsumresiduals_,vector<unsigned long>* left_count_,unsigned long* num_test_splits_,vector<splitr>* feats_) : |
|
samples(samples_), |
|
leftsumresiduals(leftsumresiduals_), |
|
left_count(left_count_), |
|
num_test_splits(num_test_splits_), |
|
feats(feats_) |
|
{ |
|
} |
|
virtual void operator()( const Range& range) const CV_OVERRIDE |
|
{ |
|
for (int i = range.start; i < range.end; ++i){ |
|
for(unsigned long j=0;j<*(num_test_splits);j++){ |
|
(*left_count)[j]++; |
|
if ((float)(*samples)[i].pixel_intensities[(unsigned long)(*feats)[j].index1] - (float)(*samples)[i].pixel_intensities[(unsigned long)(*feats)[j].index2] > (*feats)[j].thresh){ |
|
for(unsigned long k=0;k<(*samples)[i].shapeResiduals.size();k++){ |
|
(*leftsumresiduals)[j][k]=(*leftsumresiduals)[j][k]+(*samples)[i].shapeResiduals[k]; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
private: |
|
vector<training_sample>* samples; |
|
vector< vector<Point2f> >* leftsumresiduals; |
|
vector<unsigned long>* left_count; |
|
unsigned long* num_test_splits; |
|
vector<splitr>* feats; |
|
}; |
|
splitr FacemarkKazemiImpl::getTestSplits(vector<Point2f> pixel_coordinates,int seed) |
|
{ |
|
splitr feat; |
|
//generates splits whose probability is above a particular threshold. |
|
//P(u,v)=e^(-distance/lambda) as described in the research paper |
|
//cited above. This helps to select closer pixels hence make efficient |
|
//splits. |
|
double probability; |
|
double check; |
|
RNG rng(seed); |
|
do |
|
{ |
|
//select random pixel coordinate |
|
feat.index1 = rng.uniform(0,params.num_test_coordinates); |
|
//select another random coordinate |
|
feat.index2 = rng.uniform(0,params.num_test_coordinates); |
|
Point2f pt = pixel_coordinates[(unsigned long)feat.index1]-pixel_coordinates[(unsigned long)feat.index2]; |
|
double distance = sqrt((pt.x*pt.x)+(pt.y*pt.y)); |
|
//calculate the probability |
|
probability = exp(-distance/params.lambda); |
|
check = rng.uniform(double(0),double(1)); |
|
} |
|
while(check>probability||feat.index1==feat.index2); |
|
feat.thresh =(float)(((rng.uniform(double(0),double(1)))*256 - 128)/2.0); |
|
return feat; |
|
} |
|
bool FacemarkKazemiImpl:: getBestSplit(vector<Point2f> pixel_coordinates, vector<training_sample>& samples,unsigned long start , |
|
unsigned long end,splitr& split,vector< vector<Point2f> >& sum,long node_no) |
|
{ |
|
if(samples[0].shapeResiduals.size()!=samples[0].current_shape.size()){ |
|
String error_message = "Error while generating split.Residuals are not complete.Aborting...."; |
|
CV_Error(Error::StsBadArg, error_message); |
|
} |
|
//This vector stores the matrices where each matrix represents |
|
//sum of the residuals of shapes of samples which go to the left |
|
//child after split |
|
vector< vector<Point2f> > leftsumresiduals; |
|
leftsumresiduals.resize(params.num_test_splits); |
|
vector<splitr> feats; |
|
//generate random splits and selects the best split amongst them. |
|
for (unsigned long i = 0; i < params.num_test_splits; ++i){ |
|
feats.push_back(getTestSplits(pixel_coordinates,i+(int)time(0))); |
|
leftsumresiduals[i].resize(samples[0].shapeResiduals.size()); |
|
} |
|
vector<unsigned long> left_count; |
|
left_count.resize(params.num_test_splits); |
|
parallel_for_(Range(start,end),splitSamples(&samples,&leftsumresiduals,&left_count,¶ms.num_test_splits,&feats)); |
|
//Selecting the best split |
|
double best_score =-1; |
|
unsigned long best_feat = 0; |
|
double score = -1; |
|
vector<Point2f> right_sum; |
|
right_sum.resize(sum[node_no].size()); |
|
vector<Point2f> left_sum; |
|
left_sum.resize(sum[node_no].size()); |
|
unsigned long right_cnt; |
|
for(unsigned long i=0;i<leftsumresiduals.size();i++){ |
|
right_cnt = (end-start+1)-left_count[i]; |
|
for(unsigned long k=0;k<leftsumresiduals[i].size();k++){ |
|
if (right_cnt!=0){ |
|
right_sum[k].x=(sum[node_no][k].x-leftsumresiduals[i][k].x)/right_cnt; |
|
right_sum[k].y=(sum[node_no][k].y-leftsumresiduals[i][k].y)/right_cnt; |
|
} |
|
else |
|
right_sum[k]=Point2f(0,0); |
|
if(left_count[i]!=0){ |
|
left_sum[k].x=leftsumresiduals[i][k].x/left_count[i]; |
|
left_sum[k].y=leftsumresiduals[i][k].y/left_count[i]; |
|
} |
|
else |
|
left_sum[k]=Point2f(0,0); |
|
} |
|
Point2f pt1(0,0); |
|
Point2f pt2(0,0); |
|
for(unsigned long k=0;k<left_sum.size();k++){ |
|
pt1.x = pt1.x + (float)(left_sum[k].x*left_sum[k].x); |
|
pt2.x = pt2.x + (float)(right_sum[k].x*right_sum[k].x); |
|
pt1.y = pt1.y + (float)(left_sum[k].y*left_sum[k].y); |
|
pt2.y = pt2.y + (float)(right_sum[k].y*right_sum[k].y); |
|
} |
|
score = (double)sqrt(pt1.x+pt1.y)*(double)left_count[i] + (double)sqrt(pt2.x+pt2.y)*(double)right_cnt; |
|
if(score > best_score){ |
|
best_score = score; |
|
best_feat = i; |
|
} |
|
} |
|
sum[2*node_no+1] = leftsumresiduals[best_feat]; |
|
sum[2*node_no+2].resize(sum[node_no].size()); |
|
for(unsigned long k=0;k<sum[node_no].size();k++){ |
|
sum[2*node_no+2][k].x = sum[node_no][k].x-sum[2*node_no+1][k].x; |
|
sum[2*node_no+2][k].y = sum[node_no][k].y-sum[2*node_no+1][k].y; |
|
} |
|
split = feats[best_feat]; |
|
return true; |
|
} |
|
void FacemarkKazemiImpl::createSplitNode(regtree& tree, splitr split,long node_no){ |
|
tree_node node; |
|
node.split = split; |
|
node.leaf.clear(); |
|
tree.nodes[node_no]=node; |
|
} |
|
void FacemarkKazemiImpl::createLeafNode(regtree& tree,long node_no,vector<Point2f> assign){ |
|
tree_node node; |
|
node.split.index1 = (uint64_t)(-1); |
|
node.split.index2 = (uint64_t)(-1); |
|
node.leaf = assign; |
|
tree.nodes[node_no] = node; |
|
} |
|
bool FacemarkKazemiImpl :: generateSplit(queue<node_info>& curr,vector<Point2f> pixel_coordinates, vector<training_sample>& samples, |
|
splitr &split , vector< vector<Point2f> >& sum){ |
|
|
|
long start = curr.front().index1; |
|
long end = curr.front().index2; |
|
long _depth = curr.front().depth; |
|
long node_no =curr.front().node_no; |
|
curr.pop(); |
|
if(start == end) |
|
return false; |
|
getBestSplit(pixel_coordinates,samples,start,end,split,sum,node_no); |
|
long mid = divideSamples(split, samples, start, end); |
|
//cout<<mid<<endl; |
|
if(mid==start||mid==end+1) |
|
return false; |
|
node_info _left,_right; |
|
_left.index1 = start; |
|
_left.index2 = mid-1; |
|
_left.depth = _depth +1; |
|
_left.node_no = 2*node_no+1; |
|
_right.index1 = mid; |
|
_right.index2 = end; |
|
_right.depth = _depth +1; |
|
_right.node_no = 2*node_no+2; |
|
curr.push(_left); |
|
curr.push(_right); |
|
return true; |
|
} |
|
bool FacemarkKazemiImpl :: buildRegtree(regtree& tree,vector<training_sample>& samples,vector<Point2f> pixel_coordinates){ |
|
if(samples.size()==0){ |
|
String error_message = "Error while building regression tree.Empty samples. Aborting...."; |
|
CV_Error(Error::StsBadArg, error_message); |
|
} |
|
if(pixel_coordinates.size()==0){ |
|
String error_message = "Error while building regression tree.No pixel coordinates. Aborting...."; |
|
CV_Error(Error::StsBadArg, error_message); |
|
} |
|
queue<node_info> curr; |
|
node_info parent; |
|
vector< vector<Point2f> > sum; |
|
const long numNodes =(long)pow(2,params.tree_depth); |
|
const long numSplitNodes = numNodes/2 - 1; |
|
sum.resize(numNodes+1); |
|
sum[0].resize(samples[0].shapeResiduals.size()); |
|
parallel_for_(cv::Range(0,(int)samples.size()), doSum(&(samples),&(sum[0]))); |
|
parent.index1=0; |
|
parent.index2=(long)samples.size()-1; |
|
parent.node_no=0; |
|
parent.depth=0; |
|
curr.push(parent); |
|
tree.nodes.resize(numNodes+1); |
|
//Total number of split nodes |
|
while(!curr.empty()){ |
|
pair<long,long> range= make_pair(curr.front().index1,curr.front().index2); |
|
long node_no = curr.front().node_no; |
|
splitr split = {0, 0, 0}; |
|
//generate a split |
|
if(node_no<=numSplitNodes){ |
|
if(generateSplit(curr,pixel_coordinates,samples,split,sum)){ |
|
createSplitNode(tree,split,node_no); |
|
} |
|
//create leaf |
|
else{ |
|
long count = range.second-range.first +1; |
|
vector<Point2f> temp; |
|
temp.resize(samples[range.first].shapeResiduals.size()); |
|
parallel_for_(Range(range.first, range.second), doSum(&(samples),&(temp))); |
|
for(unsigned long k=0;k<temp.size();k++){ |
|
temp[k].x=(temp[k].x/count)*params.learning_rate; |
|
temp[k].y=(temp[k].y/count)*params.learning_rate; |
|
} |
|
// Modify current shape according to the weak learners. |
|
parallel_for_(Range(range.first,range.second), modifySamples(&(samples),&(temp))); |
|
createLeafNode(tree,node_no,temp); |
|
} |
|
} |
|
else |
|
{ |
|
unsigned long count = range.second-range.first +1; |
|
vector<Point2f> temp; |
|
temp.resize(samples[range.first].shapeResiduals.size()); |
|
parallel_for_(Range(range.first, range.second), doSum(&(samples),&(temp))); |
|
for(unsigned long k=0;k<temp.size();k++){ |
|
temp[k].x=(temp[k].x/count)*params.learning_rate; |
|
temp[k].y=(temp[k].y/count)*params.learning_rate; |
|
} |
|
// Modify current shape according to the weak learners. |
|
parallel_for_(Range(range.first,range.second), modifySamples(&(samples),&(temp))); |
|
createLeafNode(tree,node_no,temp); |
|
curr.pop(); |
|
} |
|
} |
|
return true; |
|
} |
|
unsigned long FacemarkKazemiImpl::divideSamples (splitr split,vector<training_sample>& samples,unsigned long start,unsigned long end) |
|
{ |
|
if(samples.size()==0){ |
|
String error_message = "Error while dividing samples. Sample array empty. Aborting...."; |
|
CV_Error(Error::StsBadArg, error_message); |
|
} |
|
unsigned long i = start; |
|
training_sample temp; |
|
//partition samples according to the split |
|
for (unsigned long j = start; j < end; ++j) |
|
{ |
|
if ((float)samples[j].pixel_intensities[(unsigned long)split.index1] - (float)samples[j].pixel_intensities[(unsigned long)split.index2] > split.thresh) |
|
{ |
|
temp=samples[i]; |
|
samples[i]=samples[j]; |
|
samples[j]=temp; |
|
++i; |
|
} |
|
} |
|
return i; |
|
} |
|
}//cv |
|
}//face
|
|
|