alignfaces 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- alignfaces/__init__.py +15 -0
- alignfaces/aperture_tools.py +213 -0
- alignfaces/contrast_tools.py +106 -0
- alignfaces/contrast_tools_.py +106 -0
- alignfaces/data/shape_predictor_68_face_landmarks.dat +0 -0
- alignfaces/face_landmarks.py +233 -0
- alignfaces/make_aligned_faces.py +1217 -0
- alignfaces/make_aligned_faces_.py +1209 -0
- alignfaces/make_files.py +42 -0
- alignfaces/make_files_.py +42 -0
- alignfaces/make_files_OLD.py +86 -0
- alignfaces/phase_cong_3.py +524 -0
- alignfaces/plot_tools.py +170 -0
- alignfaces/procrustes_tools.py +217 -0
- alignfaces/tests/R/align_reference.csv +1 -0
- alignfaces/tests/R/align_shapes.csv +40 -0
- alignfaces/tests/R/input_shapes.csv +40 -0
- alignfaces/tests/__init__.py +0 -0
- alignfaces/tests/_test_pawarp.py +267 -0
- alignfaces/tests/test_procrustes_tools.py +569 -0
- alignfaces/tests/test_warp_tools.py +316 -0
- alignfaces/warp_tools.py +279 -0
- alignfaces-1.0.1.dist-info/METADATA +135 -0
- alignfaces-1.0.1.dist-info/RECORD +27 -0
- alignfaces-1.0.1.dist-info/WHEEL +5 -0
- alignfaces-1.0.1.dist-info/licenses/LICENSE.txt +13 -0
- alignfaces-1.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1217 @@
|
|
|
1
|
+
from .face_landmarks import (facial_landmarks, pull_jawline_to_inside_of_hairline,
|
|
2
|
+
unpack_dict_to_vector_xy, pack_vector_xy_as_dict)
|
|
3
|
+
|
|
4
|
+
from math import floor, ceil
|
|
5
|
+
|
|
6
|
+
# import matplotlib.pyplot as plt
|
|
7
|
+
import numpy as np
|
|
8
|
+
import math
|
|
9
|
+
import os.path
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
# image io and grayscale conversion
|
|
13
|
+
from PIL import Image as PilImage
|
|
14
|
+
|
|
15
|
+
# image translation and rotation
|
|
16
|
+
from skimage.transform import SimilarityTransform, warp
|
|
17
|
+
|
|
18
|
+
# image scaling
|
|
19
|
+
import dlib
|
|
20
|
+
|
|
21
|
+
# Procrustes analysis
|
|
22
|
+
# import procrustesTool as pt
|
|
23
|
+
# from .procrustes_tools import procrustes_tools as pt
|
|
24
|
+
from .procrustes_tools import (get_translation,
|
|
25
|
+
generalized_procrustes_analysis,
|
|
26
|
+
translate, get_rotation_scale,
|
|
27
|
+
procrustes_analysis)
|
|
28
|
+
|
|
29
|
+
# from .apertureTools import (fit_ellipse_semi_minor, make_ellipse_map,
|
|
30
|
+
# make_circle_map, make_map_below_y,
|
|
31
|
+
# make_moss_egg, make_four_channel_image)
|
|
32
|
+
|
|
33
|
+
from .aperture_tools import (fit_ellipse_semi_minor, make_ellipse_map,
|
|
34
|
+
make_moss_egg, make_four_channel_image)
|
|
35
|
+
|
|
36
|
+
from .warp_tools import pawarp
|
|
37
|
+
|
|
38
|
+
from .contrast_tools import contrast_stretch
|
|
39
|
+
|
|
40
|
+
from .plot_tools import (plot_faces_with_landmarks,
|
|
41
|
+
plot_faces_with_landmarks_one_by_one,
|
|
42
|
+
fit_ellipse_to_2D_cloud,
|
|
43
|
+
plot_ellipse)
|
|
44
|
+
|
|
45
|
+
# import pdb # TEMPORARY FOR DEBUGGING
|
|
46
|
+
# Final adjustments and saving image
|
|
47
|
+
from skimage import exposure
|
|
48
|
+
from skimage.io import imsave
|
|
49
|
+
|
|
50
|
+
# files
|
|
51
|
+
import pickle
|
|
52
|
+
import os
|
|
53
|
+
import json
|
|
54
|
+
import io
|
|
55
|
+
import csv
|
|
56
|
+
|
|
57
|
+
# testing
|
|
58
|
+
import sys
|
|
59
|
+
|
|
60
|
+
# additional functions
|
|
61
|
+
from .make_files import get_source_files, clone_directory_tree
|
|
62
|
+
|
|
63
|
+
# adjust_size = 'default'
|
|
64
|
+
# size_value = None
|
|
65
|
+
# start_fresh = True
|
|
66
|
+
# iris_by_coherence = False
|
|
67
|
+
|
|
68
|
+
# -----------------------------------------------------------------------------
|
|
69
|
+
# Support function
|
|
70
|
+
def get_rotation_matrix_2d(center, angle):
|
|
71
|
+
(x0, y0) = center[0], center[1]
|
|
72
|
+
M = np.array([[np.cos(angle), -np.sin(angle),
|
|
73
|
+
x0*(1 - np.cos(angle)) + y0*np.sin(angle)],
|
|
74
|
+
[np.sin(angle), np.cos(angle),
|
|
75
|
+
y0*(1 - np.cos(angle)) - x0*np.sin(angle)],
|
|
76
|
+
[0, 0, 1]])
|
|
77
|
+
return M
|
|
78
|
+
|
|
79
|
+
# -----------------------------------------------------------------------------
|
|
80
|
+
# adjust_size, size_value -> only in 1st section, which is not necessary here.
|
|
81
|
+
# color_of_result -> 303
|
|
82
|
+
#
|
|
83
|
+
# actually required:
|
|
84
|
+
# MotherDir, start_fresh, iris_by_coherence
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_landmarks(MotherDir, file_prefix, file_postfix, include_jaw=False,
|
|
88
|
+
start_fresh=True):
|
|
89
|
+
|
|
90
|
+
# files = make_files(MotherDir, file_prefix, file_postfix)
|
|
91
|
+
# source_files = files[0]
|
|
92
|
+
# output_files = files[1]
|
|
93
|
+
source_files = get_source_files(MotherDir, FilePrefix=file_prefix,
|
|
94
|
+
FilePostfix=file_postfix)
|
|
95
|
+
|
|
96
|
+
# adjust_size='default', size_value=None, color_of_result='grayscale'
|
|
97
|
+
|
|
98
|
+
# MotherDir = "/Users/carl/Studies/Collaborations/Eugenie/RawFacesCleaned/"
|
|
99
|
+
# MotherDir = "/Users/carl/Studies/your_project_here/faces/"
|
|
100
|
+
# viewpoint = 'all' # 'front', 'left', 'right'
|
|
101
|
+
|
|
102
|
+
# Copy directory structure for output
|
|
103
|
+
MotherBits = MotherDir.split(os.path.sep)
|
|
104
|
+
go_back = -len(MotherBits[-2]) - 1
|
|
105
|
+
GrannyDir = MotherDir[0:go_back]
|
|
106
|
+
file_results_temporary = MotherDir + "results_temporary.dat"
|
|
107
|
+
file_results = MotherDir + "results.dat"
|
|
108
|
+
|
|
109
|
+
if start_fresh:
|
|
110
|
+
try:
|
|
111
|
+
with open(file_results_temporary, "rb") as f:
|
|
112
|
+
print(file_results_temporary + "\t exists.\nRemoving ...")
|
|
113
|
+
os.remove(file_results_temporary)
|
|
114
|
+
except IOError as error:
|
|
115
|
+
print(error)
|
|
116
|
+
|
|
117
|
+
# To do: User interface for getting this, or simple arguments into script
|
|
118
|
+
keys_to_remove_for_alignment = ['JAWLINE_POINTS']
|
|
119
|
+
|
|
120
|
+
# try:
|
|
121
|
+
# f = open(file_results_aligned, "rb")
|
|
122
|
+
# data2 = []
|
|
123
|
+
# for _ in range(pickle.load(f)):
|
|
124
|
+
# data2.append(pickle.load(f))
|
|
125
|
+
# LabelToCoordinateIndex = data2[0]
|
|
126
|
+
# output_files = data2[1]
|
|
127
|
+
# FinalLandmarks = data2[2]
|
|
128
|
+
# f.close()
|
|
129
|
+
# except:
|
|
130
|
+
###########################################################################
|
|
131
|
+
# input and output files
|
|
132
|
+
# with open(GrannyDir + 'input_files.data', 'rb') as filehandle:
|
|
133
|
+
# source_files = pickle.load(filehandle)
|
|
134
|
+
# with open(GrannyDir + 'output_files.data', 'rb') as filehandle:
|
|
135
|
+
# output_files = pickle.load(filehandle)
|
|
136
|
+
|
|
137
|
+
###########################################################################
|
|
138
|
+
# Main
|
|
139
|
+
|
|
140
|
+
# Obtain facial landmarks and center-point for all faces
|
|
141
|
+
try:
|
|
142
|
+
with open(file_results_temporary, "rb") as f:
|
|
143
|
+
data_in = []
|
|
144
|
+
for _ in range(pickle.load(f)):
|
|
145
|
+
data_in.append(pickle.load(f))
|
|
146
|
+
AllSubjectsLandmarksDict = data_in[0]
|
|
147
|
+
AllSubjectsLandmarksDictAllFeature = data_in[1]
|
|
148
|
+
MeanXY = data_in[2]
|
|
149
|
+
LandmarkFailureFile = data_in[3]
|
|
150
|
+
LandmarkFailureIndex = data_in[4]
|
|
151
|
+
StartingIndex = len(MeanXY) + len(LandmarkFailureFile)
|
|
152
|
+
IrisPoints = data_in[5]
|
|
153
|
+
except IOError as error:
|
|
154
|
+
print(error)
|
|
155
|
+
AllSubjectsLandmarksDict = []
|
|
156
|
+
AllSubjectsLandmarksDictAllFeature = []
|
|
157
|
+
MeanXY = []
|
|
158
|
+
LandmarkFailureFile = []
|
|
159
|
+
LandmarkFailureIndex = []
|
|
160
|
+
StartingIndex = 0
|
|
161
|
+
IrisPoints = []
|
|
162
|
+
|
|
163
|
+
# for infile, outfile, facei in zip(source_files[StartingIndex:], output_files[StartingIndex:], range(StartingIndex, len(source_files))):
|
|
164
|
+
for infile, facei in zip(source_files[StartingIndex:], range(StartingIndex, len(source_files))):
|
|
165
|
+
# image = cv2.imread(infile)
|
|
166
|
+
# gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
167
|
+
gray = np.array(PilImage.open(infile).convert("L"))
|
|
168
|
+
Landmarks = facial_landmarks(gray)
|
|
169
|
+
|
|
170
|
+
if Landmarks is None:
|
|
171
|
+
print("Landmarks is None: " + str(facei))
|
|
172
|
+
LandmarkFailureFile.append(infile)
|
|
173
|
+
LandmarkFailureIndex.append(facei)
|
|
174
|
+
# AllSubjectsLandmarksDictAllFeature.append(np.array(CoordinateVector))
|
|
175
|
+
#
|
|
176
|
+
# AllSubjectsLandmarksDict.append(np.array(CoordinateVector))
|
|
177
|
+
#
|
|
178
|
+
# MeanXY.append(np.array([None, None]))
|
|
179
|
+
else:
|
|
180
|
+
le = Landmarks['LEFT_EYE_POINTS']
|
|
181
|
+
re = Landmarks['RIGHT_EYE_POINTS']
|
|
182
|
+
mle = le.mean(axis=0)
|
|
183
|
+
mre = re.mean(axis=0)
|
|
184
|
+
temp_dict = {0: (int(mre[1]), int(mre[0])),
|
|
185
|
+
1: (int(mle[1]), int(mle[0]))}
|
|
186
|
+
IrisPoints.append(temp_dict)
|
|
187
|
+
|
|
188
|
+
Landmarks = pull_jawline_to_inside_of_hairline(Landmarks, gray)
|
|
189
|
+
CoordinateVector, LabelToCoordinateIndexAll = unpack_dict_to_vector_xy(Landmarks)
|
|
190
|
+
AllSubjectsLandmarksDictAllFeature.append(np.array(CoordinateVector))
|
|
191
|
+
|
|
192
|
+
for this_key in keys_to_remove_for_alignment:
|
|
193
|
+
Landmarks.pop(this_key, None)
|
|
194
|
+
|
|
195
|
+
CoordinateVector, LabelToCoordinateIndex = unpack_dict_to_vector_xy(Landmarks)
|
|
196
|
+
AllSubjectsLandmarksDict.append(np.array(CoordinateVector))
|
|
197
|
+
|
|
198
|
+
mean_x, mean_y = get_translation(CoordinateVector)
|
|
199
|
+
MeanXY.append(np.array([mean_x, mean_y]))
|
|
200
|
+
|
|
201
|
+
f = open(file_results_temporary, "wb")
|
|
202
|
+
data = [AllSubjectsLandmarksDict, AllSubjectsLandmarksDictAllFeature, MeanXY, LandmarkFailureFile, LandmarkFailureIndex, IrisPoints]
|
|
203
|
+
pickle.dump(len(data), f)
|
|
204
|
+
for value in data:
|
|
205
|
+
pickle.dump(value, f)
|
|
206
|
+
f.close()
|
|
207
|
+
|
|
208
|
+
print(f'{facei:5d}', end="\r")
|
|
209
|
+
|
|
210
|
+
# Remove files with failed landmark-detection.
|
|
211
|
+
# for bi, bf in zip(LandmarkFailureIndex, LandmarkFailureFile):
|
|
212
|
+
# print("Removing " + source_files[bi])
|
|
213
|
+
# print(" which is " + bf)
|
|
214
|
+
# del source_files[bi]
|
|
215
|
+
# del output_files[bi]
|
|
216
|
+
SF = np.asarray(source_files)
|
|
217
|
+
# OF = np.asarray(output_files)
|
|
218
|
+
# keep_locs = (set([l for l in range(len(output_files))]) -
|
|
219
|
+
# set(LandmarkFailureIndex))
|
|
220
|
+
keep_locs = (set([l for l in range(len(source_files))]) -
|
|
221
|
+
set(LandmarkFailureIndex))
|
|
222
|
+
source_files = list(SF[list(keep_locs)])
|
|
223
|
+
# output_files = list(OF[list(keep_locs)])
|
|
224
|
+
|
|
225
|
+
# Rewrite those files to disk (source directory)
|
|
226
|
+
# with open(MotherDir + 'input_files.data', 'wb') as filehandle:
|
|
227
|
+
# pickle.dump(source_files, filehandle)
|
|
228
|
+
# with open(MotherDir + 'output_files.data', 'wb') as filehandle:
|
|
229
|
+
# pickle.dump(output_files, filehandle)
|
|
230
|
+
|
|
231
|
+
# Create new file that lists any failures
|
|
232
|
+
# with open(MotherDir + 'excluded_files.data', 'wb') as filehandle:
|
|
233
|
+
# pickle.dump(LandmarkFailureFile, filehandle)
|
|
234
|
+
|
|
235
|
+
# -----------------------------------------------------------------------------
|
|
236
|
+
# Produce a single JSON file that contains all landmark data
|
|
237
|
+
lmd = len(MotherDir)
|
|
238
|
+
# Combine into single dictionary
|
|
239
|
+
features = ['LEFT_EYEBROW', 'LEFT_EYE',
|
|
240
|
+
'RIGHT_EYEBROW', 'RIGHT_EYE',
|
|
241
|
+
'NOSE', 'MOUTH_OUTLINE',
|
|
242
|
+
'MOUTH_INNER', 'JAWLINE']
|
|
243
|
+
landmarks = dict()
|
|
244
|
+
number_photos = len(source_files)
|
|
245
|
+
for outfile, i in zip(source_files, range(0, number_photos)):
|
|
246
|
+
# LandmarksThisGuy = FinalLandmarks[i]
|
|
247
|
+
LandmarksThisGuy = AllSubjectsLandmarksDictAllFeature[i]
|
|
248
|
+
ThisSubjectLandmarksDict = pack_vector_xy_as_dict(LandmarksThisGuy.tolist(),
|
|
249
|
+
LabelToCoordinateIndexAll)
|
|
250
|
+
fullpath = source_files[i]
|
|
251
|
+
# partpath = fullpath[fullpath.index('aligned') + len('aligned'):]
|
|
252
|
+
partpath = fullpath[lmd:]
|
|
253
|
+
this_photo = dict()
|
|
254
|
+
for fi in range(len(features)):
|
|
255
|
+
this_array = ThisSubjectLandmarksDict[features[fi] + '_POINTS']
|
|
256
|
+
# this_array = AllSubjectsLandmarksDictAllFeature[features[fi] + '_POINTS']
|
|
257
|
+
this_photo[features[fi].lower()] = \
|
|
258
|
+
list(this_array.flatten().astype(float))
|
|
259
|
+
# list(this_array.flatten().astype(np.float))
|
|
260
|
+
# TO DO
|
|
261
|
+
# append iris points [x, y] to this_photo
|
|
262
|
+
# take from IrisPoints
|
|
263
|
+
#
|
|
264
|
+
# IrisPoints is list [p1, p2, ... pn] for n people, where
|
|
265
|
+
# p_i is dictionary
|
|
266
|
+
# p_i{0} = (y, x)_for right eye
|
|
267
|
+
# p_i{1} = (y, x) for left eye
|
|
268
|
+
this_photo['left_iris'] = [float(IrisPoints[i][1][1]),
|
|
269
|
+
float(IrisPoints[i][1][0])]
|
|
270
|
+
this_photo['right_iris'] = [float(IrisPoints[i][0][1]),
|
|
271
|
+
float(IrisPoints[i][0][0])]
|
|
272
|
+
landmarks[partpath] = this_photo
|
|
273
|
+
|
|
274
|
+
# export to JSON
|
|
275
|
+
json_file = MotherDir + 'landmarks.txt'
|
|
276
|
+
with open(json_file, 'w') as outfile:
|
|
277
|
+
json.dump(landmarks, outfile)
|
|
278
|
+
|
|
279
|
+
# End of chunk for Getting Landmarks
|
|
280
|
+
#
|
|
281
|
+
# Output:
|
|
282
|
+
# source_files, output_files, AllSubjectsLandmarksDict,
|
|
283
|
+
# AllSubjectsLandmarksDictAllFeature, MeanXY, LandmarkFailureFile,
|
|
284
|
+
# LandmarkFailureIndex, IrisPoints
|
|
285
|
+
# files = []
|
|
286
|
+
# files.append(source_files)
|
|
287
|
+
# files.append(output_files)
|
|
288
|
+
if include_jaw:
|
|
289
|
+
listOfLandmarks = AllSubjectsLandmarksDictAllFeature
|
|
290
|
+
else:
|
|
291
|
+
listOfLandmarks = AllSubjectsLandmarksDict
|
|
292
|
+
print("\n\n------------------------------------------------------------\n")
|
|
293
|
+
print("\nThere should now be a JSON-formatted" +
|
|
294
|
+
"file called landmarks.txt in:\n")
|
|
295
|
+
print("\t" + MotherDir + "\n")
|
|
296
|
+
print("Please examine the contents.\n\n")
|
|
297
|
+
print("\n--------------------------------------------------------------\n")
|
|
298
|
+
return source_files, listOfLandmarks, MeanXY, IrisPoints
|
|
299
|
+
# -------------------------------------------------------------------------
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def get_landmark_features(source_dir, output_dir="", exclude_features=['jawline',
|
|
303
|
+
'left_iris', 'right_iris', 'mouth_inner']):
|
|
304
|
+
# Returns a DICT with lots of useful stuff, like eye_distances.
|
|
305
|
+
# AllSubjectsLandmarksDict is landmarks for all faces.
|
|
306
|
+
# exclude_features refers to this; does not affect calculation of other stuff.
|
|
307
|
+
|
|
308
|
+
# Determine if landmarks.txt exists in source_dir.
|
|
309
|
+
# Load or return error if does not exist.
|
|
310
|
+
full_landmark_filename = source_dir + 'landmarks.txt'
|
|
311
|
+
exists = os.path.isfile(full_landmark_filename)
|
|
312
|
+
if exists:
|
|
313
|
+
with io.open(full_landmark_filename, 'r') as f:
|
|
314
|
+
imported_landmarks = json.loads(f.readlines()[0].strip())
|
|
315
|
+
else:
|
|
316
|
+
print(['JSON file landmarks.txt does not exist in ' + source_dir])
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
# Initialize results
|
|
320
|
+
eye_distances = []
|
|
321
|
+
eye_radians = []
|
|
322
|
+
eye_midpoints = []
|
|
323
|
+
mouth2eye_distances = []
|
|
324
|
+
mouth2eye_radians = []
|
|
325
|
+
nose_tips = []
|
|
326
|
+
|
|
327
|
+
# Combine source_dir and output_dir with keys of imported_landmarks to get
|
|
328
|
+
# source_files and output_files.
|
|
329
|
+
#
|
|
330
|
+
# Also obtaining corresponding LOL as appropriate
|
|
331
|
+
# input to alignment functions.
|
|
332
|
+
source_files = []
|
|
333
|
+
output_files = []
|
|
334
|
+
LOL = []
|
|
335
|
+
MeanXY = []
|
|
336
|
+
IrisPoints = []
|
|
337
|
+
for rest_of_file_name in imported_landmarks.keys():
|
|
338
|
+
source_files.append(source_dir + rest_of_file_name)
|
|
339
|
+
output_files.append(output_dir + rest_of_file_name)
|
|
340
|
+
|
|
341
|
+
# Convert imported_landmarks -> LOL (like listOfLandmarks)
|
|
342
|
+
dict_of_coordinates = imported_landmarks[rest_of_file_name]
|
|
343
|
+
this_person = np.array([], dtype=np.int64).reshape(0,)
|
|
344
|
+
this_iris = dict()
|
|
345
|
+
|
|
346
|
+
# Get specific features.
|
|
347
|
+
dickeys = dict_of_coordinates.keys()
|
|
348
|
+
if ('left_iris' in dickeys) and ('right_iris' in dickeys):
|
|
349
|
+
left_iris = np.array(dict_of_coordinates['left_iris'])
|
|
350
|
+
right_iris = np.array(dict_of_coordinates['right_iris'])
|
|
351
|
+
else:
|
|
352
|
+
ex = np.array(dict_of_coordinates['left_eye'][0::2]).mean()
|
|
353
|
+
ey = np.array(dict_of_coordinates['left_eye'][1::2]).mean()
|
|
354
|
+
left_iris = np.array([ex, ey])
|
|
355
|
+
ex = np.array(dict_of_coordinates['right_eye'][0::2]).mean()
|
|
356
|
+
ey = np.array(dict_of_coordinates['right_eye'][1::2]).mean()
|
|
357
|
+
right_iris = np.array([ex, ey])
|
|
358
|
+
|
|
359
|
+
mouth_inner = dict_of_coordinates['mouth_inner']
|
|
360
|
+
mouth_outline = dict_of_coordinates['mouth_outline']
|
|
361
|
+
nx = dict_of_coordinates['nose'][0::2]
|
|
362
|
+
ny = dict_of_coordinates['nose'][1::2]
|
|
363
|
+
NOSE_TIP = np.array([nx[6], ny[6]])
|
|
364
|
+
nose_tips.append(NOSE_TIP)
|
|
365
|
+
|
|
366
|
+
eye_vector = left_iris - right_iris
|
|
367
|
+
EYE_DIST = np.sqrt(((eye_vector)**2).sum())
|
|
368
|
+
EYE_RAD = np.arctan2(eye_vector[1], eye_vector[0])
|
|
369
|
+
trans_eye = np.array([(EYE_DIST / 2) * np.cos(EYE_RAD),
|
|
370
|
+
(EYE_DIST / 2) * np.sin(EYE_RAD)])
|
|
371
|
+
EYE_MID = right_iris + trans_eye
|
|
372
|
+
eye_distances.append(EYE_DIST)
|
|
373
|
+
eye_radians.append(EYE_RAD)
|
|
374
|
+
eye_midpoints.append(EYE_MID)
|
|
375
|
+
|
|
376
|
+
mix = np.array(mouth_inner[0::2]).mean()
|
|
377
|
+
miy = np.array(mouth_inner[1::2]).mean()
|
|
378
|
+
mox = np.array(mouth_outline[0::2]).mean()
|
|
379
|
+
moy = np.array(mouth_outline[1::2]).mean()
|
|
380
|
+
mcx, mcy = (mox + mix) / 2, (moy + miy) / 2
|
|
381
|
+
mouth = np.array([mcx, mcy])
|
|
382
|
+
|
|
383
|
+
mouth_vector = mouth - EYE_MID
|
|
384
|
+
MOUTH_DIST = np.sqrt(((mouth_vector)**2).sum())
|
|
385
|
+
MOUTH_RAD = np.arctan2(mouth_vector[1], mouth_vector[0])
|
|
386
|
+
mouth2eye_distances.append(MOUTH_DIST)
|
|
387
|
+
mouth2eye_radians.append(MOUTH_RAD)
|
|
388
|
+
|
|
389
|
+
# Concatenation of landmarks (with some exclusions)
|
|
390
|
+
for this_feature in dict_of_coordinates.keys():
|
|
391
|
+
if not (this_feature in exclude_features):
|
|
392
|
+
these_coords = dict_of_coordinates[this_feature]
|
|
393
|
+
this_person = np.hstack((this_person, np.array(these_coords)))
|
|
394
|
+
if (this_feature == 'left_iris'):
|
|
395
|
+
these_coords = dict_of_coordinates[this_feature]
|
|
396
|
+
this_iris[1] = np.array([these_coords[1], these_coords[0]])
|
|
397
|
+
if (this_feature == 'right_iris'):
|
|
398
|
+
these_coords = dict_of_coordinates[this_feature]
|
|
399
|
+
this_iris[0] = np.array([these_coords[1], these_coords[0]])
|
|
400
|
+
LOL.append(this_person)
|
|
401
|
+
IrisPoints.append(this_iris)
|
|
402
|
+
xj = this_person[0::2]
|
|
403
|
+
yj = this_person[1::2]
|
|
404
|
+
MeanXY.append(np.array([int(xj.mean()), int(yj.mean())]))
|
|
405
|
+
|
|
406
|
+
AllSubjectsLandmarksDict = LOL
|
|
407
|
+
|
|
408
|
+
# Vectors of indices for each facial feature
|
|
409
|
+
for remove_this_feature in exclude_features:
|
|
410
|
+
del dict_of_coordinates[remove_this_feature]
|
|
411
|
+
CoordinateVector, LabelToCoordinateIndex = unpack_dict_to_vector_xy(dict_of_coordinates)
|
|
412
|
+
|
|
413
|
+
# Package final results.
|
|
414
|
+
landmark_features = dict()
|
|
415
|
+
landmark_features['eye_distances'] = eye_distances
|
|
416
|
+
landmark_features['eye_radians'] = eye_radians
|
|
417
|
+
landmark_features['eye_midpoints'] = eye_midpoints
|
|
418
|
+
landmark_features['mouth2eye_distances'] = mouth2eye_distances
|
|
419
|
+
landmark_features['mouth2eye_radians'] = mouth2eye_radians
|
|
420
|
+
landmark_features['nose_tips'] = nose_tips
|
|
421
|
+
|
|
422
|
+
landmark_features['AllSubjectsLandmarksDict'] = AllSubjectsLandmarksDict
|
|
423
|
+
landmark_features['IrisPoints'] = IrisPoints
|
|
424
|
+
landmark_features['MeanXY'] = MeanXY
|
|
425
|
+
landmark_features['LabelToCoordinateIndex'] = LabelToCoordinateIndex
|
|
426
|
+
|
|
427
|
+
files = []
|
|
428
|
+
files.append(source_files)
|
|
429
|
+
files.append(output_files)
|
|
430
|
+
return landmark_features, files
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def landmarks_report(source_dir, file_prefix="", file_postfix="jpg"):
|
|
434
|
+
numbers = {}
|
|
435
|
+
infiles = get_source_files(source_dir, file_prefix, file_postfix)
|
|
436
|
+
rel_paths = [fp[len(source_dir):] for fp in infiles]
|
|
437
|
+
num_total_images = len(rel_paths)
|
|
438
|
+
|
|
439
|
+
full_path_inaccurate = source_dir + "bad-landmarks.csv"
|
|
440
|
+
if os.path.isfile(full_path_inaccurate):
|
|
441
|
+
with open(full_path_inaccurate) as csv_file:
|
|
442
|
+
csv_reader = csv.reader(csv_file, delimiter=',')
|
|
443
|
+
for row in csv_reader:
|
|
444
|
+
num_detected_but_removed = len(row);
|
|
445
|
+
else:
|
|
446
|
+
num_detected_but_removed = 0;
|
|
447
|
+
|
|
448
|
+
landmark_features, files = get_landmark_features(source_dir, source_dir)
|
|
449
|
+
num_in_landmarks_file = len(files[0])
|
|
450
|
+
num_failed_detections = (num_total_images - num_in_landmarks_file -
|
|
451
|
+
num_detected_but_removed)
|
|
452
|
+
numbers['num_total_images'] = num_total_images
|
|
453
|
+
numbers['num_detected_but_removed'] = num_detected_but_removed
|
|
454
|
+
numbers['num_failed_detections'] = num_failed_detections
|
|
455
|
+
|
|
456
|
+
print("Total input images " + str(num_total_images))
|
|
457
|
+
print("Number of images with a failed face detection " + str(num_failed_detections))
|
|
458
|
+
print("Number of images with a landmark inaccuracy " + str(num_detected_but_removed))
|
|
459
|
+
|
|
460
|
+
return numbers
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def exclude_files_with_bad_landmarks(source_dir):
|
|
464
|
+
full_bad_filename = source_dir + "bad-landmarks.csv"
|
|
465
|
+
exists = os.path.isfile(full_bad_filename)
|
|
466
|
+
if exists:
|
|
467
|
+
with open(full_bad_filename) as f:
|
|
468
|
+
reader = csv.reader(f)
|
|
469
|
+
remove_these_files = list(reader)[0]
|
|
470
|
+
else:
|
|
471
|
+
print(['CSV file bad-landmarks.csv does not exist in ' + source_dir])
|
|
472
|
+
return None
|
|
473
|
+
|
|
474
|
+
full_landmark_filename = source_dir + "landmarks.txt"
|
|
475
|
+
exists = os.path.isfile(full_landmark_filename)
|
|
476
|
+
if exists:
|
|
477
|
+
with io.open(full_landmark_filename, 'r') as f:
|
|
478
|
+
imported_landmarks = json.loads(f.readlines()[0].strip())
|
|
479
|
+
else:
|
|
480
|
+
print(['JSON file landmarks.txt does not exist in ' + source_dir])
|
|
481
|
+
return None
|
|
482
|
+
|
|
483
|
+
# First save original with a new name just in case
|
|
484
|
+
full_landmark_filename = source_dir + "landmarks-original.txt"
|
|
485
|
+
with open(full_landmark_filename, 'w') as outfile:
|
|
486
|
+
json.dump(imported_landmarks, outfile)
|
|
487
|
+
|
|
488
|
+
# Now delete unwanted entries and save as landmarks.txt
|
|
489
|
+
for this_file in remove_these_files:
|
|
490
|
+
del imported_landmarks[this_file.strip()]
|
|
491
|
+
full_landmark_filename = source_dir + "landmarks.txt"
|
|
492
|
+
with open(full_landmark_filename, 'w') as outfile:
|
|
493
|
+
json.dump(imported_landmarks, outfile)
|
|
494
|
+
return None
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def tilt_radians_to_ensure_eyes_level(the_shape, LabelToCoordinateIndex):
|
|
498
|
+
x_locs = LabelToCoordinateIndex["left_eye"][::2]
|
|
499
|
+
y_locs = LabelToCoordinateIndex["left_eye"][1::2]
|
|
500
|
+
left_center = np.array([the_shape[x_locs].mean(), the_shape[y_locs].mean()])
|
|
501
|
+
x_locs = LabelToCoordinateIndex["right_eye"][::2]
|
|
502
|
+
y_locs = LabelToCoordinateIndex["right_eye"][1::2]
|
|
503
|
+
right_center = np.array([the_shape[x_locs].mean(), the_shape[y_locs].mean()])
|
|
504
|
+
eye_vector = left_center - right_center
|
|
505
|
+
clock_wise_norm = (-eye_vector[1], eye_vector[0])
|
|
506
|
+
eye_tilt_radians = np.arctan2(clock_wise_norm[1], clock_wise_norm[0])
|
|
507
|
+
rotate_to_level_radians = np.pi / 2 - eye_tilt_radians
|
|
508
|
+
return rotate_to_level_radians
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def align_procrustes(source_dir, file_prefix='', file_postfix='jpg',
|
|
512
|
+
exclude_features=['jawline', 'left_iris', 'right_iris',
|
|
513
|
+
'mouth_inner'], include_features=None,
|
|
514
|
+
adjust_size='default', size_value=None,
|
|
515
|
+
color_of_result='grayscale'):
|
|
516
|
+
# NOTE: include_features overrides exclude_features
|
|
517
|
+
|
|
518
|
+
# files, output_dir = make_files(source_dir, file_prefix, file_postfix,
|
|
519
|
+
# new_dir="aligned")
|
|
520
|
+
# source_files = files[0]
|
|
521
|
+
# output_files = files[1]
|
|
522
|
+
|
|
523
|
+
output_dir = clone_directory_tree(source_dir, new_dir="aligned",
|
|
524
|
+
FilePrefix=file_prefix,
|
|
525
|
+
FilePostfix=file_postfix)
|
|
526
|
+
|
|
527
|
+
if include_features != None:
|
|
528
|
+
features = ['left_eyebrow', 'left_eye', 'right_eyebrow', 'right_eye',
|
|
529
|
+
'nose', 'mouth_outline', 'mouth_inner', 'jawline',
|
|
530
|
+
'left_iris', 'right_iris']
|
|
531
|
+
exclude_features = list(set(features) - set(include_features))
|
|
532
|
+
|
|
533
|
+
landmark_features, files = get_landmark_features(source_dir, output_dir,
|
|
534
|
+
exclude_features)
|
|
535
|
+
source_files = files[0]
|
|
536
|
+
output_files = files[1]
|
|
537
|
+
# if len((set(source_files) - set(source_files_))) !=0:
|
|
538
|
+
# print("source_files returned by make_files() and get_landmark_features() are different.")
|
|
539
|
+
# print(source_files[0] + "\n" + source_files_[0])
|
|
540
|
+
# return
|
|
541
|
+
# source_files = files[0]
|
|
542
|
+
# output_files = files[1]
|
|
543
|
+
AllSubjectsLandmarksDict = landmark_features['AllSubjectsLandmarksDict']
|
|
544
|
+
IrisPoints = landmark_features['IrisPoints']
|
|
545
|
+
MeanXY = landmark_features['MeanXY']
|
|
546
|
+
|
|
547
|
+
LabelToCoordinateIndex = landmark_features['LabelToCoordinateIndex']
|
|
548
|
+
|
|
549
|
+
# # Determine if landmarks.txt exists in source_dir.
|
|
550
|
+
# # Load or return error if does not exist.
|
|
551
|
+
# full_landmark_filename = source_dir + 'landmarks.txt'
|
|
552
|
+
# exists = os.path.isfile(full_landmark_filename)
|
|
553
|
+
# if exists:
|
|
554
|
+
# with io.open(full_landmark_filename, 'r') as f:
|
|
555
|
+
# imported_landmarks = json.loads(f.readlines()[0].strip())
|
|
556
|
+
# else:
|
|
557
|
+
# print(['JSON file landmarks.txt does not exist in ' + source_dir])
|
|
558
|
+
# return
|
|
559
|
+
#
|
|
560
|
+
# # Combine source_dir and output_dir with keys of imported_landmarks to get
|
|
561
|
+
# # source_files and output_files.
|
|
562
|
+
# #
|
|
563
|
+
# # Also obtaining corresponding LOL as appropriate input
|
|
564
|
+
# # to alignment functions.
|
|
565
|
+
# # exclude_features = ['jawline', 'left_iris', 'right_iris']
|
|
566
|
+
# source_files = []
|
|
567
|
+
# output_files = []
|
|
568
|
+
# LOL = []
|
|
569
|
+
# MeanXY = []
|
|
570
|
+
# IrisPoints = []
|
|
571
|
+
# for rest_of_file_name in imported_landmarks.keys():
|
|
572
|
+
# source_files.append(source_dir + rest_of_file_name)
|
|
573
|
+
# output_files.append(output_dir + rest_of_file_name)
|
|
574
|
+
#
|
|
575
|
+
# # Convert imported_landmarks -> LOL (like listOfLandmarks)
|
|
576
|
+
# dict_of_coordinates = imported_landmarks[rest_of_file_name]
|
|
577
|
+
# this_person = np.array([], dtype=np.int64).reshape(0,)
|
|
578
|
+
# this_iris = dict()
|
|
579
|
+
# for this_feature in dict_of_coordinates.keys():
|
|
580
|
+
# if not (this_feature in exclude_features):
|
|
581
|
+
# these_coords = dict_of_coordinates[this_feature]
|
|
582
|
+
# this_person = np.hstack((this_person, np.array(these_coords)))
|
|
583
|
+
# if (this_feature == 'left_iris'):
|
|
584
|
+
# these_coords = dict_of_coordinates[this_feature]
|
|
585
|
+
# this_iris[1] = (these_coords[1], these_coords[0])
|
|
586
|
+
# if (this_feature == 'right_iris'):
|
|
587
|
+
# these_coords = dict_of_coordinates[this_feature]
|
|
588
|
+
# this_iris[0] = (these_coords[1], these_coords[0])
|
|
589
|
+
# LOL.append(this_person)
|
|
590
|
+
# IrisPoints.append(this_iris)
|
|
591
|
+
# xj = this_person[0::2]
|
|
592
|
+
# yj = this_person[1::2]
|
|
593
|
+
# MeanXY.append(np.array([int(xj.mean()), int(yj.mean())]))
|
|
594
|
+
#
|
|
595
|
+
# AllSubjectsLandmarksDict = LOL
|
|
596
|
+
|
|
597
|
+
# IrisPoints is list [p1, p2, ... pn] for n people, where
|
|
598
|
+
# p_i is dictionary
|
|
599
|
+
# p_i{0} = (y, x)_for right eye
|
|
600
|
+
# p_i{1} = (y, x) for left eye
|
|
601
|
+
|
|
602
|
+
# -------------------------------------------------------------------------
|
|
603
|
+
# EYE_DISTANCE, IMAGE_WIDTH, IMAGE_HEIGHT not used until 269-323
|
|
604
|
+
# EYE_DISTANCE -> Procrustes scaling of template 269
|
|
605
|
+
# IMAGE_WIDTH, IMAGE_HEIGHT -> padding/cropping 322-23
|
|
606
|
+
#
|
|
607
|
+
# rest of variables within are local to this section.
|
|
608
|
+
assert adjust_size in ['set_eye_distance', 'set_image_width',
|
|
609
|
+
'set_image_height', 'default']
|
|
610
|
+
if adjust_size == 'default':
|
|
611
|
+
assert size_value is None
|
|
612
|
+
|
|
613
|
+
# values for setting fixed proportions
|
|
614
|
+
_EYE_DISTANCE = 94
|
|
615
|
+
_IMAGE_WIDTH = 300 - 20
|
|
616
|
+
_IMAGE_HEIGHT = 338 + 80 - 30
|
|
617
|
+
|
|
618
|
+
if adjust_size == 'default':
|
|
619
|
+
EYE_DISTANCE = _EYE_DISTANCE
|
|
620
|
+
IMAGE_WIDTH = _IMAGE_WIDTH
|
|
621
|
+
IMAGE_HEIGHT = _IMAGE_HEIGHT
|
|
622
|
+
elif adjust_size == 'set_eye_distance':
|
|
623
|
+
EYE_DISTANCE = size_value
|
|
624
|
+
IMAGE_WIDTH = round((_IMAGE_WIDTH / _EYE_DISTANCE) * EYE_DISTANCE)
|
|
625
|
+
IMAGE_HEIGHT = round((_IMAGE_HEIGHT / _EYE_DISTANCE) * EYE_DISTANCE)
|
|
626
|
+
elif adjust_size == 'set_image_width':
|
|
627
|
+
IMAGE_WIDTH = size_value
|
|
628
|
+
EYE_DISTANCE = round((_EYE_DISTANCE / _IMAGE_WIDTH) * IMAGE_WIDTH)
|
|
629
|
+
IMAGE_HEIGHT = round((_IMAGE_HEIGHT / _IMAGE_WIDTH) * IMAGE_WIDTH)
|
|
630
|
+
elif adjust_size == 'set_image_height':
|
|
631
|
+
IMAGE_HEIGHT = size_value
|
|
632
|
+
EYE_DISTANCE = round((_EYE_DISTANCE / _IMAGE_HEIGHT) * IMAGE_HEIGHT)
|
|
633
|
+
IMAGE_WIDTH = round((_IMAGE_WIDTH / _IMAGE_HEIGHT) * IMAGE_HEIGHT)
|
|
634
|
+
# -------------------------------------------------------------------------
|
|
635
|
+
|
|
636
|
+
# -------------------------------------------------------------------------
|
|
637
|
+
# Get eyedist and jawdist.
|
|
638
|
+
# Need IrisPoints and AllSubjectsLandmarksDictAllFeature.
|
|
639
|
+
|
|
640
|
+
# NOTES ON ORIGINAL FORMAT OF IrisPoints
|
|
641
|
+
#
|
|
642
|
+
# IrisPoints is list [p1, p2, ... pn] for n people, where
|
|
643
|
+
# p_i is dictionary
|
|
644
|
+
# p_i{0} = (y, x)_for right eye
|
|
645
|
+
# p_i{1} = (y, x) for left eye
|
|
646
|
+
|
|
647
|
+
number_so_far = len(IrisPoints)
|
|
648
|
+
# jawdist = np.empty((number_so_far, 1))
|
|
649
|
+
eyedist = np.empty((number_so_far, 1))
|
|
650
|
+
for this_face in range(0, number_so_far):
|
|
651
|
+
# lx = AllSubjectsLandmarksDictAllFeature[this_face][0::2]
|
|
652
|
+
# ly = AllSubjectsLandmarksDictAllFeature[this_face][1::2]
|
|
653
|
+
ey = [IrisPoints[this_face][0][0], IrisPoints[this_face][1][0]]
|
|
654
|
+
ex = [IrisPoints[this_face][0][1], IrisPoints[this_face][1][1]]
|
|
655
|
+
# JX = lx[0:17:16]
|
|
656
|
+
# JY = ly[0:17:16]
|
|
657
|
+
# JD = abs(np.diff(JX + JY * 1j))
|
|
658
|
+
ED = abs(np.diff(np.array(ex) + np.array(ey) * 1j))
|
|
659
|
+
# jawdist[this_face, 0] = JD[0]
|
|
660
|
+
eyedist[this_face, 0] = ED[0]
|
|
661
|
+
# -------------------------------------------------------------------------
|
|
662
|
+
|
|
663
|
+
# -------------------------------------------------------------------------
|
|
664
|
+
# Generalised Procrustes analysis
|
|
665
|
+
#
|
|
666
|
+
# Need:
|
|
667
|
+
# AllSubjectsLandmarksDict
|
|
668
|
+
# eyedist, EYE_DISTANCE
|
|
669
|
+
# source_files, output_files
|
|
670
|
+
# color_of_result
|
|
671
|
+
# IMAGE_WIDTH, IMAGE_HEIGHT
|
|
672
|
+
#
|
|
673
|
+
# To do:
|
|
674
|
+
# Rotate mean_shape_proper_scale by
|
|
675
|
+
# (a) line between mouth-center and midpoint of eyes.
|
|
676
|
+
# (b) line between eyes
|
|
677
|
+
# (c) leave as is
|
|
678
|
+
#
|
|
679
|
+
# Scale
|
|
680
|
+
# (a) line between mouth-center and midpoint of eyes.
|
|
681
|
+
# (b) line between eyes [CURRENT]
|
|
682
|
+
#
|
|
683
|
+
# Actually, the use of specialized GP function is not necessary hereself.
|
|
684
|
+
# I simply need the mean shape, which is simple to calculate and whose
|
|
685
|
+
# values for each landmark are independent.
|
|
686
|
+
#
|
|
687
|
+
# I acutally code the rest myself without specialized GP.
|
|
688
|
+
# Check to see if that's actually correct.
|
|
689
|
+
#
|
|
690
|
+
# True but:
|
|
691
|
+
# new_shapes is useful for comparing with landmarks of aligned images.
|
|
692
|
+
# mean_shape, new_shapes = pt.generalized_procrustes_analysis(AllSubjectsLandmarksDict)
|
|
693
|
+
mean_shape, new_shapes, shit = generalized_procrustes_analysis(AllSubjectsLandmarksDict)
|
|
694
|
+
# pdb.set_trace() # TEMPORARY FOR DEBUGGING
|
|
695
|
+
# # mean_shape_with_jawline, variable_not_used = pt.generalized_procrustes_analysis(AllSubjectsLandmarksDictAllFeature)
|
|
696
|
+
# mean_shape_with_jawline, shit, shat = pt.generalized_procrustes_analysis(AllSubjectsLandmarksDictAllFeature)
|
|
697
|
+
#
|
|
698
|
+
# # Set mean shape to have a head width desired for all of the faces
|
|
699
|
+
# LandmarksOfCenteredMeanShape = pack_vector_xy_as_dict(mean_shape_with_jawline.tolist(), LabelToCoordinateIndexAll)
|
|
700
|
+
# HeadWidthOfMeanShape = LandmarksOfCenteredMeanShape['JAWLINE_POINTS'][:,0].max() - LandmarksOfCenteredMeanShape['JAWLINE_POINTS'][:,0].min()
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
# LandmarksOfCenteredMeanShape = pack_vector_xy_as_dict(mean_shape.tolist(), LabelToCoordinateIndex)
|
|
704
|
+
# LE = LandmarksOfCenteredMeanShape['LEFT_EYE_POINTS'].mean(axis=0)
|
|
705
|
+
# RE = LandmarksOfCenteredMeanShape['RIGHT_EYE_POINTS'].mean(axis=0)
|
|
706
|
+
# EyeDistOfMeanShape = np.sqrt(((RE-LE)**2).sum())
|
|
707
|
+
# print([EyeDistOfMeanShape, MeanEyeDist])
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
# EYE_DISTANCE = HeadWidthConstant * EyeDistToHeadWidthRatio
|
|
711
|
+
|
|
712
|
+
# Incorrect denominator for normalization:
|
|
713
|
+
# MeanEyeDist = eyedist.mean()
|
|
714
|
+
# mean_shape_proper_scale = mean_shape * (EYE_DISTANCE / MeanEyeDist)
|
|
715
|
+
# or course, it should be the actual distance within mean_shape!!!
|
|
716
|
+
|
|
717
|
+
have_both_eyes = ("right_eye" in list(LabelToCoordinateIndex) and
|
|
718
|
+
"left_eye" in list(LabelToCoordinateIndex))
|
|
719
|
+
|
|
720
|
+
if have_both_eyes:
|
|
721
|
+
left_eye = mean_shape[LabelToCoordinateIndex["left_eye"]]
|
|
722
|
+
right_eye = mean_shape[LabelToCoordinateIndex["right_eye"]]
|
|
723
|
+
LX = left_eye[::2].mean()
|
|
724
|
+
LY = left_eye[1::2].mean()
|
|
725
|
+
RX = right_eye[::2].mean()
|
|
726
|
+
RY = right_eye[1::2].mean()
|
|
727
|
+
mean_shape_eye_dist = np.sqrt((LX-RX)**2 + (LY-RY)**2)
|
|
728
|
+
mean_shape_proper_scale = mean_shape * (EYE_DISTANCE / mean_shape_eye_dist)
|
|
729
|
+
LabelToCoordinateIndex = landmark_features['LabelToCoordinateIndex']
|
|
730
|
+
angle_constant_rad = tilt_radians_to_ensure_eyes_level(mean_shape_proper_scale, LabelToCoordinateIndex)
|
|
731
|
+
angle_constant = angle_constant_rad * 180 / math.pi
|
|
732
|
+
else:
|
|
733
|
+
mean_shape_proper_scale = mean_shape
|
|
734
|
+
angle_constant = 0
|
|
735
|
+
|
|
736
|
+
# Now obtain precise scale and angle change for AllSubjectsLandmarksDict[i] --> mean_shape_proper_scale
|
|
737
|
+
# FinalLandmarks = []
|
|
738
|
+
# FinalLandmarksLimited = []
|
|
739
|
+
# LastLandmarkFailureFile = []
|
|
740
|
+
# LastLandmarkFailureIndex = []
|
|
741
|
+
current_working_dir = os.getcwd()
|
|
742
|
+
for infile, outfile, CenterOnThisFace in zip(source_files, output_files, range(0,len(source_files))):
|
|
743
|
+
# for infile, outfile, CenterOnThisFace in zip(source_files[0:50], output_files[0:50], range(0, 50)):
|
|
744
|
+
|
|
745
|
+
################################
|
|
746
|
+
# from procrustes_analysis.py
|
|
747
|
+
temp_sh = np.copy(AllSubjectsLandmarksDict[CenterOnThisFace])
|
|
748
|
+
translate(temp_sh)
|
|
749
|
+
|
|
750
|
+
# get scale and rotation
|
|
751
|
+
scale, theta = get_rotation_scale(mean_shape_proper_scale, temp_sh)
|
|
752
|
+
scale = 1 / scale
|
|
753
|
+
angle_degree = (theta * 180 / math.pi) + angle_constant
|
|
754
|
+
################################
|
|
755
|
+
|
|
756
|
+
# 0. load image to be aligned
|
|
757
|
+
# image = cv2.imread(infile)
|
|
758
|
+
image = np.array(PilImage.open(infile))
|
|
759
|
+
|
|
760
|
+
if color_of_result == 'grayscale':
|
|
761
|
+
# input_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
762
|
+
input_image = np.array(PilImage.open(infile).convert("L"))
|
|
763
|
+
else:
|
|
764
|
+
input_image = image
|
|
765
|
+
|
|
766
|
+
# 1. translate center of landmarks to center of image
|
|
767
|
+
rows, cols = input_image.shape[0], input_image.shape[1]
|
|
768
|
+
Cy, Cx = rows/2, cols/2
|
|
769
|
+
DeltaX = Cx - MeanXY[CenterOnThisFace][0]
|
|
770
|
+
DeltaY = Cy - MeanXY[CenterOnThisFace][1]
|
|
771
|
+
# M = np.float32([[1, 0, DeltaX], [0, 1, DeltaY]])
|
|
772
|
+
# translated_image = cv2.warpAffine(input_image, M, (cols, rows))
|
|
773
|
+
tform = SimilarityTransform(translation=(-DeltaX, -DeltaY))
|
|
774
|
+
translated_image = warp(input_image, tform, preserve_range=True)
|
|
775
|
+
|
|
776
|
+
# 2. rotate to match orientation of mean shape
|
|
777
|
+
# cv_rotation_matrix = cv2.get_rotation_matrix_2d((Cx, Cy), -angle_degree, 1)
|
|
778
|
+
# rotated_image = cv2.warpAffine(translated_image,
|
|
779
|
+
# cv_rotation_matrix, (cols, rows))
|
|
780
|
+
angle_radians = angle_degree * np.pi / 180
|
|
781
|
+
M = get_rotation_matrix_2d((Cx, Cy), -angle_radians)
|
|
782
|
+
tform = SimilarityTransform(matrix=M)
|
|
783
|
+
rotated_image = warp(translated_image, tform, preserve_range=True).astype(np.uint8)
|
|
784
|
+
|
|
785
|
+
# 3. scale to match size of mean shape & recalculate image center
|
|
786
|
+
# scaled_image = cv2.resize(rotated_image, (0, 0), fx=scale, fy=scale)
|
|
787
|
+
oldrows, oldcols = rotated_image.shape[0], rotated_image.shape[1]
|
|
788
|
+
scaled_image = dlib.resize_image(rotated_image, int(oldrows * scale),
|
|
789
|
+
int(oldcols * scale))
|
|
790
|
+
|
|
791
|
+
DeltaY = EYE_DISTANCE * 5 / 12 # Can add additional options here
|
|
792
|
+
tform = SimilarityTransform(translation=(0, -DeltaY))
|
|
793
|
+
cropped_image = warp(scaled_image, tform, preserve_range=True).astype(np.uint8)
|
|
794
|
+
|
|
795
|
+
# Additional shift if mouth landmarks excluded from alignment.
|
|
796
|
+
ema = "mouth_inner" in exclude_features
|
|
797
|
+
emb = "mouth_outline" in exclude_features
|
|
798
|
+
if (ema & emb):
|
|
799
|
+
DeltaY = EYE_DISTANCE * 29 / 94
|
|
800
|
+
tform = SimilarityTransform(translation=(0, DeltaY))
|
|
801
|
+
cropped_image = warp(cropped_image, tform, preserve_range=True).astype(np.uint8)
|
|
802
|
+
|
|
803
|
+
rows, cols = cropped_image.shape[0], cropped_image.shape[1]
|
|
804
|
+
Cy, Cx = rows/2, cols/2
|
|
805
|
+
|
|
806
|
+
# 4. crop to be 256 x 256 -- centered on (Cy, Cx)
|
|
807
|
+
Cy = int(Cy)
|
|
808
|
+
Cx = int(Cx)
|
|
809
|
+
# cropped_gray = scaled_gray[Cy-128:Cy+128, Cx-128:Cx+128]
|
|
810
|
+
# cropped_gray = scaled_gray[Cy-144-15:Cy+144-15, Cx-128:Cx+128]
|
|
811
|
+
add_vertical = IMAGE_HEIGHT - cropped_image.shape[0]
|
|
812
|
+
add_horizontal = IMAGE_WIDTH - cropped_image.shape[1]
|
|
813
|
+
# if (add_vertical > 0) or (add_horizontal > 0):
|
|
814
|
+
# pad_tuple = ((floor(add_vertical/2), ceil(add_vertical/2)), (floor(add_horizontal/2), ceil(add_horizontal/2)))
|
|
815
|
+
# cropped_gray = np.pad(scaled_gray, pad_tuple, 'constant', constant_values=((0,0),(0,0)))
|
|
816
|
+
# else:
|
|
817
|
+
# cropped_gray = scaled_gray
|
|
818
|
+
|
|
819
|
+
# cropped_image = scaled_image
|
|
820
|
+
|
|
821
|
+
if cropped_image.ndim == 2:
|
|
822
|
+
if (add_vertical > 0):
|
|
823
|
+
pad = ((floor(add_vertical/2), ceil(add_vertical/2)), (0, 0))
|
|
824
|
+
cropped_image = np.pad(cropped_image, pad,
|
|
825
|
+
'constant', constant_values=((0, 0), (0, 0)))
|
|
826
|
+
elif (add_vertical < 0):
|
|
827
|
+
pre_clip = floor(abs(add_vertical)/2)
|
|
828
|
+
pos_clip = ceil(abs(add_vertical)/2)
|
|
829
|
+
cropped_image = cropped_image[pre_clip: -pos_clip, :]
|
|
830
|
+
if (add_horizontal > 0):
|
|
831
|
+
pad = ((0, 0), (floor(add_horizontal/2), ceil(add_horizontal/2)))
|
|
832
|
+
cropped_image = np.pad(cropped_image, pad,
|
|
833
|
+
'constant', constant_values=((0, 0), (0, 0)))
|
|
834
|
+
elif (add_horizontal < 0):
|
|
835
|
+
pre_clip = floor(abs(add_horizontal)/2)
|
|
836
|
+
pos_clip = ceil(abs(add_horizontal)/2)
|
|
837
|
+
cropped_image = cropped_image[:, pre_clip: -pos_clip]
|
|
838
|
+
else: # new lines to account for third channel of an RGB image
|
|
839
|
+
if (add_vertical > 0):
|
|
840
|
+
pad = ((floor(add_vertical/2), ceil(add_vertical/2)), (0, 0), (0, 0))
|
|
841
|
+
cropped_image = np.pad(cropped_image, pad,
|
|
842
|
+
'constant', constant_values=((0, 0), (0, 0), (0, 0)))
|
|
843
|
+
elif (add_vertical < 0):
|
|
844
|
+
pre_clip = floor(abs(add_vertical)/2)
|
|
845
|
+
pos_clip = ceil(abs(add_vertical)/2)
|
|
846
|
+
cropped_image = cropped_image[pre_clip: -pos_clip, :, :]
|
|
847
|
+
if (add_horizontal > 0):
|
|
848
|
+
pad = ((0, 0), (floor(add_horizontal/2), ceil(add_horizontal/2)), (0, 0))
|
|
849
|
+
cropped_image = np.pad(cropped_image, pad,
|
|
850
|
+
'constant', constant_values=((0, 0), (0, 0), (0, 0)))
|
|
851
|
+
elif (add_horizontal < 0):
|
|
852
|
+
pre_clip = floor(abs(add_horizontal)/2)
|
|
853
|
+
pos_clip = ceil(abs(add_horizontal)/2)
|
|
854
|
+
cropped_image = cropped_image[:, pre_clip: -pos_clip, :]
|
|
855
|
+
|
|
856
|
+
rows, cols = cropped_image.shape[0], cropped_image.shape[1]
|
|
857
|
+
Cy, Cx = rows/2, cols/2
|
|
858
|
+
|
|
859
|
+
# Rescale intensity to [0 255]
|
|
860
|
+
final_image = exposure.rescale_intensity(cropped_image)
|
|
861
|
+
|
|
862
|
+
DirBits = outfile.split(os.path.sep)
|
|
863
|
+
go_back = -len(DirBits[-1])
|
|
864
|
+
save_to_dir = outfile[0:go_back]
|
|
865
|
+
save_to_this = DirBits[-1]
|
|
866
|
+
|
|
867
|
+
if color_of_result == 'rgb':
|
|
868
|
+
# aligned_gray = cv2.cvtColor(final_image, cv2.COLOR_BGR2GRAY)
|
|
869
|
+
# final_image = cv2.cvtColor(final_image, cv2.COLOR_BGR2RGB)
|
|
870
|
+
aligned_gray = np.array(PilImage.fromarray(final_image).convert("L"))
|
|
871
|
+
final_image = np.array(PilImage.fromarray(final_image))
|
|
872
|
+
else:
|
|
873
|
+
aligned_gray = final_image
|
|
874
|
+
|
|
875
|
+
os.chdir(save_to_dir)
|
|
876
|
+
imsave(save_to_this, final_image)
|
|
877
|
+
os.chdir(current_working_dir)
|
|
878
|
+
print("\n\n------------------------------------------------------------\n")
|
|
879
|
+
print("\nThis directory:\n")
|
|
880
|
+
print("\t" + output_dir + "\n")
|
|
881
|
+
print("should now be populated with aligned faces.\n")
|
|
882
|
+
print("Please examine the contents.\n\n")
|
|
883
|
+
print("\n--------------------------------------------------------------\n")
|
|
884
|
+
# Write a specs.csv file to record constant image dimensions
|
|
885
|
+
fieldnames = ["adjust_size", "image_height", "image_width", "eye_distance"]
|
|
886
|
+
with open(output_dir + "specs.csv", mode="w") as csv_file:
|
|
887
|
+
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
|
|
888
|
+
writer.writeheader()
|
|
889
|
+
writer.writerow({"adjust_size": adjust_size, "image_height":IMAGE_HEIGHT, "image_width":IMAGE_WIDTH,"eye_distance":EYE_DISTANCE})
|
|
890
|
+
# return mean_shape, new_shapes, source_files
|
|
891
|
+
return output_dir
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
def place_aperture(source_dir, file_prefix='', file_postfix='jpg',
|
|
895
|
+
aperture_type="MossEgg", no_save=False, contrast_norm="max",
|
|
896
|
+
color_of_result='grayscale'):
|
|
897
|
+
|
|
898
|
+
source_dir = str(Path(source_dir)) + os.sep
|
|
899
|
+
|
|
900
|
+
if color_of_result == 'rgb':
|
|
901
|
+
assert (contrast_norm=="max") or (contrast_norm==None)
|
|
902
|
+
|
|
903
|
+
# files, output_dir = make_files(source_dir, file_prefix, file_postfix,
|
|
904
|
+
# new_dir="windowed")
|
|
905
|
+
# source_files = files[0]
|
|
906
|
+
# output_files = files[1]
|
|
907
|
+
|
|
908
|
+
output_dir = clone_directory_tree(source_dir, new_dir="windowed",
|
|
909
|
+
FilePrefix=file_prefix,
|
|
910
|
+
FilePostfix=file_postfix)
|
|
911
|
+
|
|
912
|
+
landmark_features, files = get_landmark_features(source_dir, output_dir)
|
|
913
|
+
source_files = files[0]
|
|
914
|
+
output_files = files[1]
|
|
915
|
+
|
|
916
|
+
# if len((set(source_files) - set(source_files_))) !=0:
|
|
917
|
+
# print("source_files returned by make_files() and get_landmark_features() are different.")
|
|
918
|
+
# print(source_files[0] + "\n" + source_files_[0])
|
|
919
|
+
# return
|
|
920
|
+
|
|
921
|
+
shapes = np.array(landmark_features['AllSubjectsLandmarksDict'])
|
|
922
|
+
# source_files = files[0]
|
|
923
|
+
FilePostfix = source_files[0].split("/")[-1].split(".")[-1]
|
|
924
|
+
|
|
925
|
+
# Points for all faces, mean face, and center of all landmarks.
|
|
926
|
+
mean_shape = shapes.mean(axis=0)
|
|
927
|
+
MX, MY = mean_shape[0::2], mean_shape[1::2]
|
|
928
|
+
CX, CY = MX.mean(), MY.mean()
|
|
929
|
+
# size = cv2.imread(source_files[0]).shape[0:2]
|
|
930
|
+
size = np.array(PilImage.open(source_files[0]).convert("L")).shape[0:2]
|
|
931
|
+
|
|
932
|
+
aperture_good = True
|
|
933
|
+
if aperture_type == "Ellipse":
|
|
934
|
+
X = shapes[:, 0::2].reshape(-1,)
|
|
935
|
+
Y = shapes[:, 1::2].reshape(-1,)
|
|
936
|
+
|
|
937
|
+
# Longest vertical length of ellipse that fits within image.
|
|
938
|
+
if (size[0] / 2) < CY:
|
|
939
|
+
ellipse_height = (size[0] - CY) * 2
|
|
940
|
+
elif (size[0] / 2) > CY:
|
|
941
|
+
ellipse_height = CY * 2
|
|
942
|
+
else:
|
|
943
|
+
ellipse_height = size[0]
|
|
944
|
+
semi_major = ellipse_height / 2
|
|
945
|
+
|
|
946
|
+
semi_minor = fit_ellipse_semi_minor(semi_major=semi_major,
|
|
947
|
+
landmarks=(X, Y),
|
|
948
|
+
center=(CX, CY))
|
|
949
|
+
|
|
950
|
+
the_aperture = make_ellipse_map(semi_minor, semi_major,
|
|
951
|
+
(CX, CY), size, soften=True)
|
|
952
|
+
elif aperture_type == "MossEgg":
|
|
953
|
+
the_aperture = make_moss_egg(landmark_features, (CX, CY),
|
|
954
|
+
size, fraction_width=47/100, soften=True)[0]
|
|
955
|
+
else:
|
|
956
|
+
print("Error: aperture_type should be Ellipse or MossEgg.")
|
|
957
|
+
aperture_good = False
|
|
958
|
+
# the_aperture = []
|
|
959
|
+
if no_save:
|
|
960
|
+
return the_aperture
|
|
961
|
+
if aperture_good:
|
|
962
|
+
# # Need this to create all subfolders in ./in_aperture
|
|
963
|
+
# files = make_files(source_dir, FilePrefix="",
|
|
964
|
+
# FilePostfix=FilePostfix, new_dir="windowed")
|
|
965
|
+
# output_files = files[1]
|
|
966
|
+
if color_of_result=='grayscale':
|
|
967
|
+
for infile, outfile in zip(source_files, output_files):
|
|
968
|
+
# image = cv2.imread(infile)
|
|
969
|
+
# gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
970
|
+
gray = np.array(PilImage.open(infile).convert("L"))
|
|
971
|
+
gray = contrast_stretch(gray, inner_locs=(the_aperture * 255) > 16,
|
|
972
|
+
type=contrast_norm)
|
|
973
|
+
BGRA = make_four_channel_image(img=gray, aperture=the_aperture)
|
|
974
|
+
|
|
975
|
+
out_png = outfile.split(".")[0] + ".png"
|
|
976
|
+
print(outfile)
|
|
977
|
+
print(out_png)
|
|
978
|
+
# cv2.imwrite(out_png, BGRA)
|
|
979
|
+
PilImage.fromarray(BGRA).save(out_png)
|
|
980
|
+
elif color_of_result=='rgb':
|
|
981
|
+
for infile, outfile in zip(source_files, output_files):
|
|
982
|
+
# rgb = cv2.imread(infile)
|
|
983
|
+
rgb = np.array(PilImage.open(infile))
|
|
984
|
+
rgb = contrast_stretch(rgb, inner_locs=None, type=contrast_norm)
|
|
985
|
+
BGRA = make_four_channel_image(img=rgb, aperture=the_aperture)
|
|
986
|
+
|
|
987
|
+
out_png = outfile.split(".")[0] + ".png"
|
|
988
|
+
print(outfile)
|
|
989
|
+
print(out_png)
|
|
990
|
+
# cv2.imwrite(out_png, BGRA)
|
|
991
|
+
PilImage.fromarray(BGRA).save(out_png)
|
|
992
|
+
print("\n\n--------------------------------------------------------\n")
|
|
993
|
+
print("\nThere should now be a bunch of faces set in apertures in:\n")
|
|
994
|
+
print("\t" + output_dir + "\n")
|
|
995
|
+
print("Please examine the contents.\n\n")
|
|
996
|
+
print("\n----------------------------------------------------------\n")
|
|
997
|
+
return the_aperture, output_dir
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
def get_mean_image(file_path, max_inner_face_contrast=False):
|
|
1001
|
+
file_path = str(Path(file_path))
|
|
1002
|
+
with io.open(file_path + os.sep + '/landmarks.txt', 'r') as f:
|
|
1003
|
+
imported_landmarks = json.loads(f.readlines()[0].strip())
|
|
1004
|
+
guys = list(imported_landmarks.keys())
|
|
1005
|
+
gray_0 = np.array(PilImage.open(file_path + os.sep + guys[0]).convert("L"))
|
|
1006
|
+
shape_0 = gray_0.shape
|
|
1007
|
+
|
|
1008
|
+
if max_inner_face_contrast:
|
|
1009
|
+
# Normalize contrast within aperture, common mean of 127.5
|
|
1010
|
+
# the_aperture = place_aperture(file_path, file_path, no_save=True)
|
|
1011
|
+
the_aperture = place_aperture(file_path, no_save=True)
|
|
1012
|
+
inner_map = (the_aperture * 255) > 16
|
|
1013
|
+
gray_0 = contrast_stretch(gray_0, inner_locs=inner_map, type="mean_127") - 127.5
|
|
1014
|
+
else:
|
|
1015
|
+
# UINT8 to double. Center and normalize
|
|
1016
|
+
gray_0 = gray_0.astype(float)
|
|
1017
|
+
gray_0 -= gray_0.mean()
|
|
1018
|
+
gray_0 = gray_0 / gray_0.std()
|
|
1019
|
+
|
|
1020
|
+
mean_image = gray_0
|
|
1021
|
+
for guy in guys[1:]:
|
|
1022
|
+
gray = np.array(PilImage.open(file_path + os.sep + guy).convert("L"))
|
|
1023
|
+
if gray.shape==shape_0:
|
|
1024
|
+
|
|
1025
|
+
if max_inner_face_contrast:
|
|
1026
|
+
# Normalize contrast within aperture, common mean of 127.5
|
|
1027
|
+
gray = contrast_stretch(gray, inner_locs=inner_map, type="mean_127") - 127.5
|
|
1028
|
+
else:
|
|
1029
|
+
# UINT8 to double. Center and normalize
|
|
1030
|
+
gray = gray.astype(float)
|
|
1031
|
+
gray -= gray.mean()
|
|
1032
|
+
gray = gray / gray.std()
|
|
1033
|
+
|
|
1034
|
+
mean_image += gray
|
|
1035
|
+
else:
|
|
1036
|
+
print("_get_mean_image() requires that all images are same dimensions!!")
|
|
1037
|
+
mean_image = None
|
|
1038
|
+
return mean_image
|
|
1039
|
+
# print("Go back to [0-255]")
|
|
1040
|
+
mean_image = mean_image / len(guys)
|
|
1041
|
+
return mean_image
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
def warp_to_mean_landmarks(source_dir, file_prefix='', file_postfix='jpg'):
|
|
1045
|
+
|
|
1046
|
+
output_dir = clone_directory_tree(source_dir, new_dir="warp-to-mean",
|
|
1047
|
+
FilePrefix=file_prefix,
|
|
1048
|
+
FilePostfix=file_postfix)
|
|
1049
|
+
|
|
1050
|
+
landmark_features, files = get_landmark_features(source_dir, output_dir,
|
|
1051
|
+
exclude_features=['jawline',
|
|
1052
|
+
'left_iris', 'right_iris'])
|
|
1053
|
+
source_files = files[0]
|
|
1054
|
+
L = np.array(landmark_features['AllSubjectsLandmarksDict'])
|
|
1055
|
+
# source_files = files[0]
|
|
1056
|
+
|
|
1057
|
+
# Image size and number of images
|
|
1058
|
+
# size = cv2.imread(source_files[0]).shape[0:2]
|
|
1059
|
+
size = np.array(PilImage.open(source_files[0]).convert("L")).shape[0:2]
|
|
1060
|
+
num_faces = len(source_files)
|
|
1061
|
+
|
|
1062
|
+
# Points for all faces, mean face, and center of all landmarks.
|
|
1063
|
+
ML = L.mean(axis=0)
|
|
1064
|
+
bx = ML[::2]
|
|
1065
|
+
by = ML[1::2]
|
|
1066
|
+
base = (bx, by)
|
|
1067
|
+
|
|
1068
|
+
# Important -> imarray and target (landmarks)
|
|
1069
|
+
original_images = np.zeros((num_faces, size[0], size[1]))
|
|
1070
|
+
for i, of in enumerate(source_files):
|
|
1071
|
+
# image = cv2.imread(of)
|
|
1072
|
+
# original_images[i, :, :] = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
1073
|
+
original_images[i, :, :] = np.array(PilImage.open(of).convert("L"))
|
|
1074
|
+
tx = L[i, ::2]
|
|
1075
|
+
ty = L[i, 1::2]
|
|
1076
|
+
target = (tx, ty)
|
|
1077
|
+
|
|
1078
|
+
base = np.transpose(np.array(base))
|
|
1079
|
+
|
|
1080
|
+
warped_to_mean = np.zeros((num_faces, size[0], size[1]))
|
|
1081
|
+
for i, of in enumerate(source_files):
|
|
1082
|
+
im = original_images[i, :, :]
|
|
1083
|
+
im -= im.min()
|
|
1084
|
+
im = im / im.max()
|
|
1085
|
+
|
|
1086
|
+
tx = L[i, ::2]
|
|
1087
|
+
ty = L[i, 1::2]
|
|
1088
|
+
target = (tx, ty)
|
|
1089
|
+
target = np.transpose(np.array(target))
|
|
1090
|
+
|
|
1091
|
+
warpim, tri, inpix, fwdwarpix = pawarp(im, base, target,
|
|
1092
|
+
interp='bilin')
|
|
1093
|
+
warped_to_mean[i, :, :] = warpim
|
|
1094
|
+
|
|
1095
|
+
# Write to file
|
|
1096
|
+
if not os.path.isdir(output_dir):
|
|
1097
|
+
os.mkdir(output_dir)
|
|
1098
|
+
for i, im in enumerate(warped_to_mean):
|
|
1099
|
+
sim = (im * 255).astype("uint8")
|
|
1100
|
+
out_png = output_dir + os.path.sep + "P" + str(i) + ".png"
|
|
1101
|
+
# cv2.imwrite(out_png, sim)
|
|
1102
|
+
PilImage.fromarray(sim).save(out_png)
|
|
1103
|
+
|
|
1104
|
+
# Estimate landmarks.
|
|
1105
|
+
get_landmarks(output_dir, "P", "png")
|
|
1106
|
+
|
|
1107
|
+
# Get aperture for inner-face
|
|
1108
|
+
# the_aperture = place_aperture(output_dir, output_dir, no_save=True)
|
|
1109
|
+
the_aperture = place_aperture(output_dir, file_prefix='P', file_postfix='png', no_save=True)
|
|
1110
|
+
inner_map = (the_aperture * 255) > 16
|
|
1111
|
+
|
|
1112
|
+
# Normalize contrast within aperture, common mean of 127.5
|
|
1113
|
+
WTMN = np.zeros((num_faces, size[0], size[1]))
|
|
1114
|
+
for i, im in enumerate(warped_to_mean):
|
|
1115
|
+
nim = contrast_stretch(im, inner_locs=inner_map, type="mean_127")
|
|
1116
|
+
out_png = output_dir + os.path.sep + "N" + str(i) + ".png"
|
|
1117
|
+
# cv2.imwrite(out_png, nim)
|
|
1118
|
+
PilImage.fromarray(nim).save(out_png)
|
|
1119
|
+
WTMN[i, :, :] = nim
|
|
1120
|
+
|
|
1121
|
+
# Mean of warped and normalized faces, and save to file
|
|
1122
|
+
AverageOfMorphs = contrast_stretch(WTMN.mean(axis=0), type="max")
|
|
1123
|
+
out_png = output_dir + os.path.sep + "MEAN-N.png"
|
|
1124
|
+
# cv2.imwrite(out_png, AverageOfMorphs)
|
|
1125
|
+
PilImage.fromarray(AverageOfMorphs).save(out_png)
|
|
1126
|
+
|
|
1127
|
+
return original_images, WTMN
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
def morph_between_two_faces(source_dir, do_these, num_morphs, file_prefix='',
|
|
1131
|
+
file_postfix='jpg', new_dir = "morphed",
|
|
1132
|
+
weight_texture=True):
|
|
1133
|
+
assert len(do_these) == 2
|
|
1134
|
+
|
|
1135
|
+
output_dir = clone_directory_tree(source_dir, new_dir=new_dir,
|
|
1136
|
+
FilePrefix=file_prefix,
|
|
1137
|
+
FilePostfix=file_postfix)
|
|
1138
|
+
|
|
1139
|
+
landmark_features, files = get_landmark_features(source_dir, output_dir)
|
|
1140
|
+
lms = np.array(landmark_features['AllSubjectsLandmarksDict'])
|
|
1141
|
+
SF = files[0]
|
|
1142
|
+
|
|
1143
|
+
# select 2 faces in list do_these
|
|
1144
|
+
lms = lms[do_these, :]
|
|
1145
|
+
source_files = [SF[i] for i in do_these]
|
|
1146
|
+
|
|
1147
|
+
# Image size and number of images
|
|
1148
|
+
# size = cv2.imread(source_files[0]).shape[0:2]
|
|
1149
|
+
size = np.array(PilImage.open(source_files[0]).convert("L")).shape[0:2]
|
|
1150
|
+
num_faces = 2
|
|
1151
|
+
|
|
1152
|
+
# # Points for all faces, mean face, and center of all landmarks.
|
|
1153
|
+
# ML = lms.mean(axis=0)
|
|
1154
|
+
# bx = ML[::2]
|
|
1155
|
+
# by = ML[1::2]
|
|
1156
|
+
# base = (bx, by)
|
|
1157
|
+
|
|
1158
|
+
original_images = np.zeros((num_faces, size[0], size[1]))
|
|
1159
|
+
F, L = [], []
|
|
1160
|
+
for i, of in enumerate(source_files):
|
|
1161
|
+
# image = cv2.imread(of)
|
|
1162
|
+
# im = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
1163
|
+
im = np.array(PilImage.open(of).convert("L"))
|
|
1164
|
+
im -= im.min()
|
|
1165
|
+
F.append(im / im.max())
|
|
1166
|
+
|
|
1167
|
+
tx = lms[i, ::2]
|
|
1168
|
+
ty = lms[i, 1::2]
|
|
1169
|
+
target = (tx, ty)
|
|
1170
|
+
L.append(np.transpose(np.array(target))) # point by [x,y]
|
|
1171
|
+
|
|
1172
|
+
p = [i/(num_morphs-1) for i in range(num_morphs)]
|
|
1173
|
+
face_array = np.zeros((num_morphs, F[0].shape[0], F[0].shape[1]))
|
|
1174
|
+
for i in range(num_morphs):
|
|
1175
|
+
N = p[i]*L[0] + (1-p[i])*L[1]
|
|
1176
|
+
F0, tri, inpix, fwdwarpix = pawarp(F[0], N, L[0], interp='bilin')
|
|
1177
|
+
F1, tri, inpix, fwdwarpix = pawarp(F[1], N, L[1], interp='bilin')
|
|
1178
|
+
if weight_texture:
|
|
1179
|
+
M = p[i]*F0 + (1-p[i])*F1
|
|
1180
|
+
else:
|
|
1181
|
+
M = (F0 + F1) / 2
|
|
1182
|
+
face_array[i, :, :] = M
|
|
1183
|
+
|
|
1184
|
+
# From face A to B
|
|
1185
|
+
face_array =np.flip(face_array, axis=0)
|
|
1186
|
+
|
|
1187
|
+
# Write to morphed faces to file
|
|
1188
|
+
if not os.path.isdir(output_dir):
|
|
1189
|
+
os.mkdir(output_dir)
|
|
1190
|
+
for i, im in enumerate(face_array):
|
|
1191
|
+
sim = (im * 255).astype("uint8")
|
|
1192
|
+
out_png = output_dir + os.path.sep + "P" + str(i) + ".png"
|
|
1193
|
+
# cv2.imwrite(out_png, sim)
|
|
1194
|
+
PilImage.fromarray(sim).save(out_png)
|
|
1195
|
+
|
|
1196
|
+
# Estimate landmarks.
|
|
1197
|
+
get_landmarks(output_dir, "P", "png")
|
|
1198
|
+
|
|
1199
|
+
# Get aperture for inner-face
|
|
1200
|
+
# the_aperture = place_aperture(output_dir, output_dir, no_save=True)
|
|
1201
|
+
the_aperture = place_aperture(output_dir, file_prefix='P', file_postfix='png', no_save=True)
|
|
1202
|
+
inner_map = (the_aperture * 255) > 16
|
|
1203
|
+
|
|
1204
|
+
# Normalize contrast within aperture, common mean of 127.5
|
|
1205
|
+
WTMN = np.zeros((num_morphs, size[0], size[1]))
|
|
1206
|
+
for i, im in enumerate(face_array):
|
|
1207
|
+
im[im>1] = 1
|
|
1208
|
+
im[im<0] = 0
|
|
1209
|
+
nim = contrast_stretch(im, inner_locs=inner_map, type="mean_127")
|
|
1210
|
+
out_png = output_dir + os.path.sep + "N" + str(i) + ".png"
|
|
1211
|
+
# cv2.imwrite(out_png, nim)
|
|
1212
|
+
PilImage.fromarray(nim).save(out_png)
|
|
1213
|
+
WTMN[i, :, :] = nim
|
|
1214
|
+
|
|
1215
|
+
return WTMN, p, output_dir
|
|
1216
|
+
# -----------------------------------------------------------------------------
|
|
1217
|
+
# -----------------------------------------------------------------------------
|