Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hackassin
GitHub Repository: hackassin/learnopencv
Path: blob/master/FaceAverage/faceAverage.cpp
3118 views
1
/*
2
* Copyright (c) 2016 Satya Mallick <[email protected]>
3
* All rights reserved. No warranty, explicit or implicit, provided.
4
*/
5
6
#include <opencv2/opencv.hpp>
7
#include <iostream>
8
#include <fstream>
9
#include <dirent.h>
10
#include <stdlib.h>
11
#include <algorithm>
12
#include <vector>
13
using namespace cv;
14
using namespace std;
15
16
// Compute similarity transform given two pairs of corresponding points.
17
// OpenCV requires 3 pairs of corresponding points.
18
// We are faking the third one.
19
void similarityTransform(std::vector<cv::Point2f>& inPoints, std::vector<cv::Point2f>& outPoints, cv::Mat &tform)
20
{
21
22
double s60 = sin(60 * M_PI / 180.0);
23
double c60 = cos(60 * M_PI / 180.0);
24
25
vector <Point2f> inPts = inPoints;
26
vector <Point2f> outPts = outPoints;
27
28
inPts.push_back(cv::Point2f(0,0));
29
outPts.push_back(cv::Point2f(0,0));
30
31
32
inPts[2].x = c60 * (inPts[0].x - inPts[1].x) - s60 * (inPts[0].y - inPts[1].y) + inPts[1].x;
33
inPts[2].y = s60 * (inPts[0].x - inPts[1].x) + c60 * (inPts[0].y - inPts[1].y) + inPts[1].y;
34
35
outPts[2].x = c60 * (outPts[0].x - outPts[1].x) - s60 * (outPts[0].y - outPts[1].y) + outPts[1].x;
36
outPts[2].y = s60 * (outPts[0].x - outPts[1].x) + c60 * (outPts[0].y - outPts[1].y) + outPts[1].y;
37
38
39
tform = cv::estimateRigidTransform(inPts, outPts, false);
40
}
41
42
43
44
45
// Read points from list of text file
46
void readPoints(vector<string> pointsFileNames, vector<vector<Point2f> > &pointsVec){
47
48
for(size_t i = 0; i < pointsFileNames.size(); i++)
49
{
50
vector<Point2f> points;
51
ifstream ifs(pointsFileNames[i]);
52
float x, y;
53
while(ifs >> x >> y)
54
points.push_back(Point2f((float)x, (float)y));
55
56
pointsVec.push_back(points);
57
}
58
59
}
60
61
62
// Read names from the directory
63
void readFileNames(string dirName, vector<string> &imageFnames, vector<string> &ptsFnames)
64
{
65
DIR *dir;
66
struct dirent *ent;
67
int count = 0;
68
69
//image extensions
70
string imgExt = "jpg";
71
string txtExt = "txt";
72
vector<string> files;
73
74
if ((dir = opendir (dirName.c_str())) != NULL)
75
{
76
/* print all the files and directories within directory */
77
while ((ent = readdir (dir)) != NULL)
78
{
79
if(strcmp(ent->d_name,".") == 0 || strcmp(ent->d_name,"..") == 0 )
80
{
81
//count++;
82
continue;
83
}
84
string temp_name = ent->d_name;
85
files.push_back(temp_name);
86
87
}
88
std::sort(files.begin(),files.end());
89
for(int it=0;it<files.size();it++)
90
{
91
string path = dirName;
92
string fname=files[it];
93
94
95
if (fname.find(imgExt, (fname.length() - imgExt.length())) != std::string::npos)
96
{
97
path.append(fname);
98
imageFnames.push_back(path);
99
}
100
else if (fname.find(txtExt, (fname.length() - txtExt.length())) != std::string::npos)
101
{
102
path.append(fname);
103
ptsFnames.push_back(path);
104
}
105
106
}
107
closedir (dir);
108
}
109
110
}
111
112
// Calculate Delaunay triangles for set of points
113
// Returns the vector of indices of 3 points for each triangle
114
static void calculateDelaunayTriangles(Rect rect, vector<Point2f> &points, vector< vector<int> > &delaunayTri){
115
116
// Create an instance of Subdiv2D
117
Subdiv2D subdiv(rect);
118
119
// Insert points into subdiv
120
for( vector<Point2f>::iterator it = points.begin(); it != points.end(); it++)
121
subdiv.insert(*it);
122
123
vector<Vec6f> triangleList;
124
subdiv.getTriangleList(triangleList);
125
vector<Point2f> pt(3);
126
vector<int> ind(3);
127
128
for( size_t i = 0; i < triangleList.size(); i++ )
129
{
130
Vec6f t = triangleList[i];
131
pt[0] = Point2f(t[0], t[1]);
132
pt[1] = Point2f(t[2], t[3]);
133
pt[2] = Point2f(t[4], t[5 ]);
134
135
if ( rect.contains(pt[0]) && rect.contains(pt[1]) && rect.contains(pt[2])){
136
for(int j = 0; j < 3; j++)
137
for(size_t k = 0; k < points.size(); k++)
138
if(abs(pt[j].x - points[k].x) < 1.0 && abs(pt[j].y - points[k].y) < 1)
139
ind[j] = k;
140
141
delaunayTri.push_back(ind);
142
}
143
}
144
145
}
146
147
// Apply affine transform calculated using srcTri and dstTri to src
148
void applyAffineTransform(Mat &warpImage, Mat &src, vector<Point2f> &srcTri, vector<Point2f> &dstTri)
149
{
150
// Given a pair of triangles, find the affine transform.
151
Mat warpMat = getAffineTransform( srcTri, dstTri );
152
153
// Apply the Affine Transform just found to the src image
154
warpAffine( src, warpImage, warpMat, warpImage.size(), INTER_LINEAR, BORDER_REFLECT_101);
155
}
156
157
158
// Warps and alpha blends triangular regions from img1 and img2 to img
159
void warpTriangle(Mat &img1, Mat &img2, vector<Point2f> t1, vector<Point2f> t2)
160
{
161
// Find bounding rectangle for each triangle
162
Rect r1 = boundingRect(t1);
163
Rect r2 = boundingRect(t2);
164
165
// Offset points by left top corner of the respective rectangles
166
vector<Point2f> t1Rect, t2Rect;
167
vector<Point> t2RectInt;
168
for(int i = 0; i < 3; i++)
169
{
170
//tRect.push_back( Point2f( t[i].x - r.x, t[i].y - r.y) );
171
t2RectInt.push_back( Point((int)(t2[i].x - r2.x), (int)(t2[i].y - r2.y)) ); // for fillConvexPoly
172
173
t1Rect.push_back( Point2f( t1[i].x - r1.x, t1[i].y - r1.y) );
174
t2Rect.push_back( Point2f( t2[i].x - r2.x, t2[i].y - r2.y) );
175
}
176
177
// Get mask by filling triangle
178
Mat mask = Mat::zeros(r2.height, r2.width, CV_32FC3);
179
fillConvexPoly(mask, t2RectInt, Scalar(1.0, 1.0, 1.0), 16, 0);
180
181
// Apply warpImage to small rectangular patches
182
Mat img1Rect, img2Rect;
183
img1(r1).copyTo(img1Rect);
184
185
Mat warpImage = Mat::zeros(r2.height, r2.width, img1Rect.type());
186
187
applyAffineTransform(warpImage, img1Rect, t1Rect, t2Rect);
188
189
// Copy triangular region of the rectangular patch to the output image
190
multiply(warpImage,mask, warpImage);
191
multiply(img2(r2), Scalar(1.0,1.0,1.0) - mask, img2(r2));
192
img2(r2) = img2(r2) + warpImage;
193
194
}
195
196
// Constrains points to be inside boundary
197
void constrainPoint(Point2f &p, Size sz)
198
{
199
p.x = min(max( (double)p.x, 0.0), (double)(sz.width - 1));
200
p.y = min(max( (double)p.y, 0.0), (double)(sz.height - 1));
201
202
}
203
204
205
206
int main( int argc, char** argv)
207
{
208
// Directory containing images.
209
string dirName = "presidents";
210
211
// Add slash to directory name if missing
212
if (!dirName.empty() && dirName.back() != '/')
213
dirName += '/';
214
215
// Dimensions of output image
216
int w = 600;
217
int h = 600;
218
219
// Read images in the directory
220
vector<string> imageNames, ptsNames;
221
readFileNames(dirName, imageNames, ptsNames);
222
//cout << imageNames.size() << ptsNames.size();
223
224
// Exit program if no images or pts are found or if the number of image files does not match with the number of point files
225
if(imageNames.empty() || ptsNames.empty() || imageNames.size() != ptsNames.size()){
226
exit(EXIT_FAILURE);
227
}
228
229
230
// Read points
231
vector<vector<Point2f> > allPoints;
232
readPoints(ptsNames, allPoints);
233
234
int n = allPoints[0].size();
235
//cout << n<< endl;
236
237
// Read images
238
vector<Mat> images;
239
for(size_t i = 0; i < imageNames.size(); i++)
240
{
241
Mat img = imread(imageNames[i]);
242
243
img.convertTo(img, CV_32FC3, 1/255.0);
244
245
if(!img.data)
246
{
247
cout << "image " << imageNames[i] << " not read properly" << endl;
248
}
249
else
250
{
251
images.push_back(img);
252
}
253
}
254
255
if(images.empty())
256
{
257
cout << "No images found " << endl;
258
exit(EXIT_FAILURE);
259
}
260
261
int numImages = images.size();
262
263
264
// Eye corners
265
vector<Point2f> eyecornerDst, eyecornerSrc;
266
eyecornerDst.push_back(Point2f( 0.3*w, h/3));
267
eyecornerDst.push_back(Point2f( 0.7*w, h/3));
268
269
eyecornerSrc.push_back(Point2f(0,0));
270
eyecornerSrc.push_back(Point2f(0,0));
271
272
// Space for normalized images and points.
273
vector <Mat> imagesNorm;
274
vector < vector <Point2f> > pointsNorm;
275
276
// Space for average landmark points
277
vector <Point2f> pointsAvg(allPoints[0].size());
278
279
// 8 Boundary points for Delaunay Triangulation
280
vector <Point2f> boundaryPts;
281
boundaryPts.push_back(Point2f(0,0));
282
boundaryPts.push_back(Point2f(w/2, 0));
283
boundaryPts.push_back(Point2f(w-1,0));
284
boundaryPts.push_back(Point2f(w-1, h/2));
285
boundaryPts.push_back(Point2f(w-1, h-1));
286
boundaryPts.push_back(Point2f(w/2, h-1));
287
boundaryPts.push_back(Point2f(0, h-1));
288
boundaryPts.push_back(Point2f(0, h/2));
289
290
// Warp images and trasnform landmarks to output coordinate system,
291
// and find average of transformed landmarks.
292
293
for(size_t i = 0; i < images.size(); i++)
294
{
295
296
vector <Point2f> points = allPoints[i];
297
298
// The corners of the eyes are the landmarks number 36 and 45
299
eyecornerSrc[0] = allPoints[i][36];
300
eyecornerSrc[1] = allPoints[i][45];
301
302
// Calculate similarity transform
303
Mat tform;
304
similarityTransform(eyecornerSrc, eyecornerDst, tform);
305
306
// Apply similarity transform to input image and landmarks
307
Mat img = Mat::zeros(h, w, CV_32FC3);
308
warpAffine(images[i], img, tform, img.size());
309
transform( points, points, tform);
310
311
// Calculate average landmark locations
312
for ( size_t j = 0; j < points.size(); j++)
313
{
314
pointsAvg[j] += points[j] * ( 1.0 / numImages);
315
}
316
317
// Append boundary points. Will be used in Delaunay Triangulation
318
for ( size_t j = 0; j < boundaryPts.size(); j++)
319
{
320
points.push_back(boundaryPts[j]);
321
}
322
323
pointsNorm.push_back(points);
324
imagesNorm.push_back(img);
325
326
327
}
328
329
// Append boundary points to average points.
330
for ( size_t j = 0; j < boundaryPts.size(); j++)
331
{
332
pointsAvg.push_back(boundaryPts[j]);
333
}
334
335
336
337
// Calculate Delaunay triangles
338
Rect rect(0, 0, w, h);
339
vector< vector<int> > dt;
340
calculateDelaunayTriangles(rect, pointsAvg, dt);
341
342
// Space for output image
343
Mat output = Mat::zeros(h, w, CV_32FC3);
344
Size size(w,h);
345
346
// Warp input images to average image landmarks
347
348
for(size_t i = 0; i < numImages; i++)
349
{
350
Mat img = Mat::zeros(h, w, CV_32FC3);
351
// Transform triangles one by one
352
for(size_t j = 0; j < dt.size(); j++)
353
{
354
// Input and output points corresponding to jth triangle
355
vector<Point2f> tin, tout;
356
for(int k = 0; k < 3; k++)
357
{
358
Point2f pIn = pointsNorm[i][dt[j][k]];
359
constrainPoint(pIn, size);
360
361
Point2f pOut = pointsAvg[dt[j][k]];
362
constrainPoint(pOut,size);
363
364
tin.push_back(pIn);
365
tout.push_back(pOut);
366
}
367
368
warpTriangle(imagesNorm[i], img, tin, tout);
369
}
370
371
// Add image intensities for averaging
372
output = output + img;
373
374
}
375
376
// Divide by numImages to get average
377
output = output / (double)numImages;
378
379
// Display result
380
imshow("image", output);
381
waitKey(0);
382
383
return 0;
384
}
385
386