forked from tamerthamoqa/facenet-realtime-face-recognition
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutils.py
288 lines (232 loc) · 10.6 KB
/
utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#!/usr/bin/env python3
import tensorflow as tf
import numpy as np
import glob
import os
from tensorflow.python.platform import gfile
from lib.src.facenet import get_model_filenames
from lib.src.align.detect_face import detect_face # face detection
from lib.src.facenet import load_img
from scipy.misc import imresize, imsave
from collections import defaultdict
from flask import flash
def allowed_file(filename, allowed_set):
"""Checks if filename extension is one of the allowed filename extensions for upload.
Args:
filename: filename of the uploaded file to be checked.
allowed_set: set containing the valid image file extensions.
Returns:
check: boolean value representing if the file extension is in the allowed extension list.
True = file is allowed
False = file not allowed.
"""
check = '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_set
return check
def remove_file_extension(filename):
"""Returns image filename without the file extension for file storage purposes.
Args:
filename: filename of the image file.
Returns:
filename: filename of the image file without the file extension.
"""
filename = os.path.splitext(filename)[0]
return filename
def save_image(img, filename, uploads_path):
"""Saves an image file to the 'uploads' folder.
Args:
img: image file (numpy array).
filename: filename of the image file.
uploads_path: absolute path of the 'uploads/' folder.
"""
try:
imsave(os.path.join(uploads_path, filename), arr=np.squeeze(img))
flash("Image saved!")
except Exception as e:
print(str(e))
return str(e)
def load_model(model):
"""Loads the FaceNet model from its directory path.
Checks if the model is a model directory (containing a metagraph and a checkpoint file)
or if it is a protocol buffer file with a frozen graph.
Note: This is a modified function from the facenet.py load_model() function in the lib directory to return
the graph object.
Args:
model: model path
Returns:
graph: Tensorflow graph object of the model
"""
model_exp = os.path.expanduser(model)
if os.path.isfile(model_exp):
print('Model filename: %s' % model_exp)
with gfile.FastGFile(model_exp, 'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
graph = tf.import_graph_def(graph_def, name='')
return graph
else:
print('Model directory: %s' % model_exp)
meta_file, ckpt_file = get_model_filenames(model_exp)
print('Metagraph file: %s' % meta_file)
print('Checkpoint file: %s' % ckpt_file)
saver = tf.train.import_meta_graph(os.path.join(model_exp, meta_file))
graph = saver.restore(tf.get_default_session(), os.path.join(model_exp, ckpt_file))
return graph
def get_face(img, pnet, rnet, onet, image_size):
"""Crops an image containing a single human face from the input image if it exists; using a Multi-Task Cascading
Convolutional neural network, then resizes the image to the required image size: default = (160 x 160 x 3).
If no face is detected, it returns a null value.
Args:
img: (numpy array) image file
pnet: proposal net, first stage of the MTCNN face detection
rnet: refinement net, second stage of the MTCNN face detection
onet: output net, third stage of the MTCNN face detection
image_size: (int) required square image size
Returns:
face_img: an image containing a face of image_size: default = (160 x 160 x 3)
if no human face is detected a None value is returned instead.
"""
# Default constants from the FaceNet repository implementation of the MTCNN
minsize = 20
threshold = [0.6, 0.7, 0.7]
factor = 0.709
margin = 44
input_image_size = image_size
img_size = np.asarray(img.shape)[0:2]
bounding_boxes, _ = detect_face(
img=img, minsize=minsize, pnet=pnet, rnet=rnet,
onet=onet, threshold=threshold, factor=factor
)
if not len(bounding_boxes) == 0:
for face in bounding_boxes:
det = np.squeeze(face[0:4])
bb = np.zeros(4, dtype=np.int32)
bb[0] = np.maximum(det[0] - margin / 2, 0)
bb[1] = np.maximum(det[1] - margin / 2, 0)
bb[2] = np.minimum(det[2] + margin / 2, img_size[1])
bb[3] = np.minimum(det[3] + margin / 2, img_size[0])
cropped = img[bb[1]: bb[3], bb[0]:bb[2], :]
face_img = imresize(arr=cropped, size=(input_image_size, input_image_size), mode='RGB')
return face_img
else:
return None
def get_faces_live(img, pnet, rnet, onet, image_size):
"""Detects multiple human faces live from web camera frame.
Args:
img: web camera frame.
pnet: proposal net, first stage of the MTCNN face detection.
rnet: refinement net, second stage of the MTCNN face detection.
onet: output net, third stage of the MTCNN face detection.
image_size: (int) required square image size.
Returns:
faces: List containing the cropped human faces.
rects: List containing the rectangle coordinates to be drawn around each human face.
"""
# Default constants from the FaceNet repository implementation of the MTCNN
minsize = 20
threshold = [0.6, 0.7, 0.7]
factor = 0.709
margin = 44
input_image_size = image_size
faces = []
rects = []
img_size = np.asarray(img.shape)[0:2]
bounding_boxes, _ = detect_face(
img=img, minsize=minsize, pnet=pnet, rnet=rnet,
onet=onet, threshold=threshold, factor=factor
)
# If human face(s) is/are detected:
if not len(bounding_boxes) == 0:
for face in bounding_boxes:
if face[4] > 0.50:
det = np.squeeze(face[0:4])
bb = np.zeros(4, dtype=np.int32)
bb[0] = np.maximum(det[0] - margin / 2, 0)
bb[1] = np.maximum(det[1] - margin / 2, 0)
bb[2] = np.minimum(det[2] + margin / 2, img_size[1])
bb[3] = np.minimum(det[3] + margin / 2, img_size[0])
cropped = img[bb[1]:bb[3], bb[0]:bb[2], :]
resized = imresize(arr=cropped, size=(input_image_size, input_image_size), mode='RGB')
faces.append(resized)
rects.append([bb[0], bb[1], bb[2], bb[3]])
return faces, rects
def forward_pass(img, session, images_placeholder, phase_train_placeholder, embeddings, image_size):
"""Feeds an image to the FaceNet model and returns a 128-dimension embedding for facial recognition.
Args:
img: image file (numpy array).
session: The active Tensorflow session.
images_placeholder: placeholder of the 'input:0' tensor of the pre-trained FaceNet model graph.
phase_train_placeholder: placeholder of the 'phase_train:0' tensor of the pre-trained FaceNet model graph.
embeddings: placeholder of the 'embeddings:0' tensor from the pre-trained FaceNet model graph.
image_size: (int) required square image size.
Returns:
embedding: (numpy array) of 128 values after the image is fed to the FaceNet model.
"""
# If there is a human face
if img is not None:
# Normalize the pixel values of the image for noise reduction for better accuracy and resize to desired size
image = load_img(
img=img, do_random_crop=False, do_random_flip=False,
do_prewhiten=True, image_size=image_size
)
# Run forward pass on FaceNet model to calculate embedding
feed_dict = {images_placeholder: image, phase_train_placeholder: False}
embedding = session.run(embeddings, feed_dict=feed_dict)
return embedding
else:
return None
def save_embedding(embedding, filename, embeddings_path):
"""Saves the embedding numpy file to the 'embeddings' folder.
Args:
embedding: numpy array of 128 values after the image is fed to the FaceNet model.
filename: filename of the image file.
embeddings_path: absolute path of the 'embeddings/' folder.
"""
# Save embedding of image using filename
path = os.path.join(embeddings_path, str(filename))
try:
np.save(path, embedding)
except Exception as e:
print(str(e))
def load_embeddings():
"""Loads embedding numpy files in the 'embedding' folder into a defaultdict object and returns it.
Returns:
embedding_dict: defaultdict containing the embedding file name and its numpy array contents.
"""
embedding_dict = defaultdict()
for embedding in glob.iglob(pathname='embeddings/*.npy'):
name = remove_file_extension(embedding)
dict_embedding = np.load(embedding)
embedding_dict[name] = dict_embedding
return embedding_dict
def identify_face(embedding, embedding_dict):
"""Compares its received embedding with the embeddings stored in the 'embeddings' folder by
minimum euclidean distance (norm), the embedding with the least euclidean distance is the predicted class.
If all embeddings have a distance above the distance threshold (1.1), then the class of the image does not exist
in the embeddings folder.
Args:
embedding: (numpy array) containing the embedding that will be compared to the stored embeddings by euclidean
distance.
embedding_dict: (defaultdict) containing the embedding file name and its numpy array contents.
Returns:
result: (string) describes the most likely person that the image looks like, or that the face
does not exist in the database if the resulting euclidean distance is above the threshold.
"""
min_distance = 100
try:
for (name, dict_embedding) in embedding_dict.items():
# Compute euclidean distance between current embedding and the embeddings from the 'embeddings' folder
distance = np.linalg.norm(embedding - dict_embedding)
if distance < min_distance:
min_distance = distance
identity = name
if min_distance <= 1.1:
# remove 'embeddings/' from identity
identity = identity[11:]
result = "It's " + str(identity) + ", the distance is " + str(min_distance)
return result
else:
result = "Not in the database, the distance is " + str(min_distance)
return result
except Exception as e:
print(str(e))
return str(e)