Martlgap commited on
Commit
7c2803a
1 Parent(s): 9888e4a

testing on hugging

Browse files
.DS_Store CHANGED
Binary files a/.DS_Store and b/.DS_Store differ
 
models/.DS_Store DELETED
Binary file (6.15 kB)
 
models/mobileNet.tflite DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:6c19b789f661caa8da735566490bfd8895beffb2a1ec97a56b126f0539991aa6
3
- size 8210384
 
 
 
 
pyproject.toml ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ [tool.black]
2
+ line-length = 120
3
+ target-version = ['py38']
requirements.txt CHANGED
@@ -8,4 +8,6 @@ streamlit-webrtc
8
  matplotlib
9
  streamlit-toggle-switch
10
  tflite-runtime
11
- twilio
 
 
 
8
  matplotlib
9
  streamlit-toggle-switch
10
  tflite-runtime
11
+ twilio
12
+ tqdm
13
+ plotly
tools/annotation.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+
4
+
5
+ class Annotation:
6
+ def __init__(self, draw_bbox=True, draw_landmarks=True, draw_name=True, upscale=True):
7
+ self.bbox = draw_bbox
8
+ self.landmarks = draw_landmarks
9
+ self.name = draw_name
10
+ self.upscale = upscale
11
+
12
+ def __call__(self, frame, detections, identities, matches, gallery):
13
+ shape = np.asarray(frame.shape[:2][::-1])
14
+ if self.upscale:
15
+ frame = cv2.resize(frame, (1920, 1080))
16
+ upscale_factor = np.asarray([1920 / shape[0], 1080 / shape[1]])
17
+ shape = np.asarray(frame.shape[:2][::-1])
18
+ else:
19
+ upscale_factor = np.asarray([1, 1])
20
+
21
+ frame.flags.writeable = True
22
+
23
+ for detection in detections:
24
+ # Draw Landmarks
25
+ if self.landmarks:
26
+ for landmark in detection.landmarks:
27
+ cv2.circle(
28
+ frame,
29
+ (landmark * upscale_factor).astype(int),
30
+ 2,
31
+ (255, 255, 255),
32
+ -1,
33
+ )
34
+
35
+ # Draw Bounding Box
36
+ if self.bbox:
37
+ cv2.rectangle(
38
+ frame,
39
+ (detection.bbox[0] * upscale_factor).astype(int),
40
+ (detection.bbox[1] * upscale_factor).astype(int),
41
+ (255, 0, 0),
42
+ 2,
43
+ )
44
+
45
+ # Draw Index
46
+ cv2.putText(
47
+ frame,
48
+ str(detection.idx),
49
+ (
50
+ ((detection.bbox[1][0] + 2) * upscale_factor[0]).astype(int),
51
+ ((detection.bbox[1][1] + 2) * upscale_factor[1]).astype(int),
52
+ ),
53
+ cv2.LINE_AA,
54
+ 0.5,
55
+ (0, 0, 0),
56
+ 2,
57
+ )
58
+
59
+ # Draw Name
60
+ if self.name:
61
+ for match in matches:
62
+ try:
63
+ detection = detections[identities[match.identity_idx].detection_idx]
64
+ except:
65
+ print("Identity IDX: ", match.identity_idx)
66
+ print("Len(Detections): ", len(detections))
67
+ print("Len(Identites): ", len(identities))
68
+ print("Detection IDX: ", identities[match.identity_idx].detection_idx)
69
+
70
+ # print("Detections: ", detections)
71
+
72
+ cv2.rectangle(
73
+ frame,
74
+ (detection.bbox[0] * upscale_factor).astype(int),
75
+ (detection.bbox[1] * upscale_factor).astype(int),
76
+ (0, 255, 0),
77
+ 2,
78
+ )
79
+
80
+ cv2.rectangle(
81
+ frame,
82
+ (
83
+ (detection.bbox[0][0] * upscale_factor[0]).astype(int),
84
+ (detection.bbox[0][1] * upscale_factor[1] - (shape[1] // 25)).astype(int),
85
+ ),
86
+ (
87
+ (detection.bbox[1][0] * upscale_factor[0]).astype(int),
88
+ (detection.bbox[0][1] * upscale_factor[1]).astype(int),
89
+ ),
90
+ (255, 255, 255),
91
+ -1,
92
+ )
93
+
94
+ cv2.putText(
95
+ frame,
96
+ gallery[match.gallery_idx].name,
97
+ (
98
+ ((detection.bbox[0][0] + shape[0] // 400) * upscale_factor[0]).astype(int),
99
+ ((detection.bbox[0][1] - shape[1] // 100) * upscale_factor[1]).astype(int),
100
+ ),
101
+ cv2.LINE_AA,
102
+ 0.5,
103
+ (0, 0, 0),
104
+ 2,
105
+ )
106
+
107
+ return frame
tools/face_detection.py ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tflite_runtime.interpreter as tflite
2
+ import cv2
3
+ import numpy as np
4
+ from .utils import tflite_inference
5
+ from .nametypes import Detection
6
+ from .utils import get_file
7
+
8
+
9
+ BASE_URL = "https://github.com/Martlgap/FaceIDLight/releases/download/v.0.1/"
10
+
11
+ FILE_HASHES = {
12
+ "o_net": "768385d570300648b7b881acbd418146522b79b4771029bb2e684bdd8c764b9f",
13
+ "p_net": "530183192e24f7cc86b6706e1eb600482c4ed4306399ac939c472e3957bae15e",
14
+ "r_net": "5ec33b065eb2802bc4c2575d21feff1a56958d854785bc3e2907d3b7ace861a2",
15
+ }
16
+
17
+
18
+ class StageStatus:
19
+ """
20
+ Keeps status between MTCNN stages
21
+ """
22
+
23
+ def __init__(self, pad_result: tuple = None, width=0, height=0):
24
+ self.width = width
25
+ self.height = height
26
+ self.dy = self.edy = self.dx = self.edx = self.y = self.ey = self.x = self.ex = self.tmp_w = self.tmp_h = []
27
+
28
+ if pad_result is not None:
29
+ self.update(pad_result)
30
+
31
+ def update(self, pad_result: tuple):
32
+ s = self
33
+ s.dy, s.edy, s.dx, s.edx, s.y, s.ey, s.x, s.ex, s.tmp_w, s.tmp_h = pad_result
34
+
35
+
36
+ class FaceDetection:
37
+ """
38
+ Allows to perform MTCNN Detection ->
39
+ a) Detection of faces (with the confidence probability)
40
+ b) Detection of keypoints (left eye, right eye, nose, mouth_left, mouth_right)
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ min_face_size: int = 40,
46
+ steps_threshold: list = None,
47
+ scale_factor: float = 0.7,
48
+ min_detections_conf: float = 0.9,
49
+ ):
50
+ """
51
+ Initializes the MTCNN.
52
+ :param min_face_size: minimum size of the face to detect
53
+ :param steps_threshold: step's thresholds values
54
+ :param scale_factor: scale factor
55
+ """
56
+ if steps_threshold is None:
57
+ steps_threshold = [0.6, 0.7, 0.7] # original mtcnn values [0.6, 0.7, 0.7]
58
+ self._min_face_size = min_face_size
59
+ self._steps_threshold = steps_threshold
60
+ self._scale_factor = scale_factor
61
+ self.min_detections_conf = min_detections_conf
62
+ self.p_net = tflite.Interpreter(model_path=get_file(BASE_URL + "p_net.tflite", FILE_HASHES["p_net"]))
63
+ self.r_net = tflite.Interpreter(model_path=get_file(BASE_URL + "r_net.tflite", FILE_HASHES["r_net"]))
64
+ self.o_net = tflite.Interpreter(model_path=get_file(BASE_URL + "o_net.tflite", FILE_HASHES["o_net"]))
65
+
66
+ def __call__(self, frame):
67
+ """
68
+ Detects bounding boxes from the specified image.
69
+ :param img: image to process
70
+ :return: list containing all the bounding boxes detected with their keypoints.
71
+
72
+ From MTCNN:
73
+ # Total boxes (bBoxes for faces)
74
+ # 1. dim -> Number of found Faces
75
+ # 2. dim -> x_min, y_min, x_max, y_max, score
76
+
77
+ # Points (Landmarks left eye, right eye, nose, left mouth, right mouth)
78
+ # 1. dim -> Number of found Faces
79
+ # 2. dim -> x1, x2, x3, x4, x5, y2, y2, y3, y4, y5 Coordinates
80
+ """
81
+
82
+ height, width, _ = frame.shape
83
+ stage_status = StageStatus(width=width, height=height)
84
+ m = 12 / self._min_face_size
85
+ min_layer = np.amin([height, width]) * m
86
+ scales = self.__compute_scale_pyramid(m, min_layer)
87
+
88
+ # We pipe here each of the stages
89
+ total_boxes, stage_status = self.__stage1(frame, scales, stage_status)
90
+ total_boxes, stage_status = self.__stage2(frame, total_boxes, stage_status)
91
+ bboxes, points = self.__stage3(frame, total_boxes, stage_status)
92
+
93
+ # Sort by location (to prevent flickering)
94
+ sort_idx = np.argsort(bboxes[:, 0])
95
+ bboxes = bboxes[sort_idx]
96
+ points = points[sort_idx]
97
+
98
+ # Transform to better shape and points now inside bbox
99
+ detections = []
100
+ cnt = 0
101
+ for i in range(bboxes.shape[0]):
102
+ conf = bboxes[i, -1].astype(np.float32)
103
+ if conf > self.min_detections_conf:
104
+ bboxes_c = np.reshape(bboxes[i, :-1], [2, 2]).astype(np.float32)
105
+ points_c = np.reshape(points[i], [2, 5]).transpose().astype(np.float32)
106
+ detections.append(
107
+ Detection(
108
+ idx=cnt,
109
+ bbox=list(bboxes_c),
110
+ landmarks=list(points_c),
111
+ confidence=conf,
112
+ )
113
+ )
114
+ cnt += 1
115
+ return frame, detections
116
+
117
+ def __compute_scale_pyramid(self, m, min_layer):
118
+ scales = []
119
+ factor_count = 0
120
+
121
+ while min_layer >= 12:
122
+ scales += [m * np.power(self._scale_factor, factor_count)]
123
+ min_layer = min_layer * self._scale_factor
124
+ factor_count += 1
125
+
126
+ return scales
127
+
128
+ @staticmethod
129
+ def __scale_image(image, scale: float):
130
+ """
131
+ Scales the image to a given scale.
132
+ :param image:
133
+ :param scale:
134
+ :return:
135
+ """
136
+ height, width, _ = image.shape
137
+
138
+ width_scaled = int(np.ceil(width * scale))
139
+ height_scaled = int(np.ceil(height * scale))
140
+
141
+ im_data = cv2.resize(image, (width_scaled, height_scaled), interpolation=cv2.INTER_AREA)
142
+
143
+ # Normalize the image's pixels
144
+ im_data_normalized = (im_data - 127.5) * 0.0078125
145
+
146
+ return im_data_normalized
147
+
148
+ @staticmethod
149
+ def __generate_bounding_box(imap, reg, scale, t):
150
+ # use heatmap to generate bounding boxes
151
+ stride = 2
152
+ cellsize = 12
153
+
154
+ imap = np.transpose(imap)
155
+ dx1 = np.transpose(reg[:, :, 0])
156
+ dy1 = np.transpose(reg[:, :, 1])
157
+ dx2 = np.transpose(reg[:, :, 2])
158
+ dy2 = np.transpose(reg[:, :, 3])
159
+
160
+ y, x = np.where(imap >= t)
161
+
162
+ if y.shape[0] == 1:
163
+ dx1 = np.flipud(dx1)
164
+ dy1 = np.flipud(dy1)
165
+ dx2 = np.flipud(dx2)
166
+ dy2 = np.flipud(dy2)
167
+
168
+ score = imap[(y, x)]
169
+ reg = np.transpose(np.vstack([dx1[(y, x)], dy1[(y, x)], dx2[(y, x)], dy2[(y, x)]]))
170
+
171
+ if reg.size == 0:
172
+ reg = np.empty(shape=(0, 3))
173
+
174
+ bb = np.transpose(np.vstack([y, x]))
175
+
176
+ q1 = np.fix((stride * bb + 1) / scale)
177
+ q2 = np.fix((stride * bb + cellsize) / scale)
178
+ boundingbox = np.hstack([q1, q2, np.expand_dims(score, 1), reg])
179
+
180
+ return boundingbox, reg
181
+
182
+ @staticmethod
183
+ def __nms(boxes, threshold, method):
184
+ """
185
+ Non Maximum Suppression.
186
+
187
+ :param boxes: np array with bounding boxes.
188
+ :param threshold:
189
+ :param method: NMS method to apply. Available values ('Min', 'Union')
190
+ :return:
191
+ """
192
+ if boxes.size == 0:
193
+ return np.empty((0, 3))
194
+
195
+ x1 = boxes[:, 0]
196
+ y1 = boxes[:, 1]
197
+ x2 = boxes[:, 2]
198
+ y2 = boxes[:, 3]
199
+ s = boxes[:, 4]
200
+
201
+ area = (x2 - x1 + 1) * (y2 - y1 + 1)
202
+ sorted_s = np.argsort(s)
203
+
204
+ pick = np.zeros_like(s, dtype=np.int16)
205
+ counter = 0
206
+ while sorted_s.size > 0:
207
+ i = sorted_s[-1]
208
+ pick[counter] = i
209
+ counter += 1
210
+ idx = sorted_s[0:-1]
211
+
212
+ xx1 = np.maximum(x1[i], x1[idx])
213
+ yy1 = np.maximum(y1[i], y1[idx])
214
+ xx2 = np.minimum(x2[i], x2[idx])
215
+ yy2 = np.minimum(y2[i], y2[idx])
216
+
217
+ w = np.maximum(0.0, xx2 - xx1 + 1)
218
+ h = np.maximum(0.0, yy2 - yy1 + 1)
219
+
220
+ inter = w * h
221
+
222
+ if method == "Min":
223
+ o = inter / np.minimum(area[i], area[idx])
224
+ else:
225
+ o = inter / (area[i] + area[idx] - inter)
226
+
227
+ sorted_s = sorted_s[np.where(o <= threshold)]
228
+
229
+ pick = pick[0:counter]
230
+
231
+ return pick
232
+
233
+ @staticmethod
234
+ def __pad(total_boxes, w, h):
235
+ # compute the padding coordinates (pad the bounding boxes to square)
236
+ tmp_w = (total_boxes[:, 2] - total_boxes[:, 0] + 1).astype(np.int32)
237
+ tmp_h = (total_boxes[:, 3] - total_boxes[:, 1] + 1).astype(np.int32)
238
+ numbox = total_boxes.shape[0]
239
+
240
+ dx = np.ones(numbox, dtype=np.int32)
241
+ dy = np.ones(numbox, dtype=np.int32)
242
+ edx = tmp_w.copy().astype(np.int32)
243
+ edy = tmp_h.copy().astype(np.int32)
244
+
245
+ x = total_boxes[:, 0].copy().astype(np.int32)
246
+ y = total_boxes[:, 1].copy().astype(np.int32)
247
+ ex = total_boxes[:, 2].copy().astype(np.int32)
248
+ ey = total_boxes[:, 3].copy().astype(np.int32)
249
+
250
+ tmp = np.where(ex > w)
251
+ edx.flat[tmp] = np.expand_dims(-ex[tmp] + w + tmp_w[tmp], 1)
252
+ ex[tmp] = w
253
+
254
+ tmp = np.where(ey > h)
255
+ edy.flat[tmp] = np.expand_dims(-ey[tmp] + h + tmp_h[tmp], 1)
256
+ ey[tmp] = h
257
+
258
+ tmp = np.where(x < 1)
259
+ dx.flat[tmp] = np.expand_dims(2 - x[tmp], 1)
260
+ x[tmp] = 1
261
+
262
+ tmp = np.where(y < 1)
263
+ dy.flat[tmp] = np.expand_dims(2 - y[tmp], 1)
264
+ y[tmp] = 1
265
+
266
+ return dy, edy, dx, edx, y, ey, x, ex, tmp_w, tmp_h
267
+
268
+ @staticmethod
269
+ def __rerec(bbox):
270
+ # convert bbox to square
271
+ height = bbox[:, 3] - bbox[:, 1]
272
+ width = bbox[:, 2] - bbox[:, 0]
273
+ max_side_length = np.maximum(width, height)
274
+ bbox[:, 0] = bbox[:, 0] + width * 0.5 - max_side_length * 0.5
275
+ bbox[:, 1] = bbox[:, 1] + height * 0.5 - max_side_length * 0.5
276
+ bbox[:, 2:4] = bbox[:, 0:2] + np.transpose(np.tile(max_side_length, (2, 1)))
277
+ return bbox
278
+
279
+ @staticmethod
280
+ def __bbreg(boundingbox, reg):
281
+ # calibrate bounding boxes
282
+ if reg.shape[1] == 1:
283
+ reg = np.reshape(reg, (reg.shape[2], reg.shape[3]))
284
+
285
+ w = boundingbox[:, 2] - boundingbox[:, 0] + 1
286
+ h = boundingbox[:, 3] - boundingbox[:, 1] + 1
287
+ b1 = boundingbox[:, 0] + reg[:, 0] * w
288
+ b2 = boundingbox[:, 1] + reg[:, 1] * h
289
+ b3 = boundingbox[:, 2] + reg[:, 2] * w
290
+ b4 = boundingbox[:, 3] + reg[:, 3] * h
291
+ boundingbox[:, 0:4] = np.transpose(np.vstack([b1, b2, b3, b4]))
292
+ return boundingbox
293
+
294
+ def __stage1(self, image, scales: list, stage_status: StageStatus):
295
+ """
296
+ First stage of the MTCNN.
297
+ :param image:
298
+ :param scales:
299
+ :param stage_status:
300
+ :return:
301
+ """
302
+ total_boxes = np.empty((0, 9))
303
+ status = stage_status
304
+
305
+ for scale in scales:
306
+ scaled_image = self.__scale_image(image, scale)
307
+
308
+ img_x = np.expand_dims(scaled_image, 0)
309
+ img_y = np.transpose(img_x, (0, 2, 1, 3))
310
+
311
+ out = tflite_inference(self.p_net, img_y)
312
+
313
+ out0 = np.transpose(out[0], (0, 2, 1, 3))
314
+ out1 = np.transpose(out[1], (0, 2, 1, 3))
315
+
316
+ boxes, _ = self.__generate_bounding_box(
317
+ out1[0, :, :, 1].copy(),
318
+ out0[0, :, :, :].copy(),
319
+ scale,
320
+ self._steps_threshold[0],
321
+ )
322
+
323
+ # inter-scale nms
324
+ pick = self.__nms(boxes.copy(), 0.5, "Union")
325
+ if boxes.size > 0 and pick.size > 0:
326
+ boxes = boxes[pick, :]
327
+ total_boxes = np.append(total_boxes, boxes, axis=0)
328
+
329
+ numboxes = total_boxes.shape[0]
330
+
331
+ if numboxes > 0:
332
+ pick = self.__nms(total_boxes.copy(), 0.7, "Union")
333
+ total_boxes = total_boxes[pick, :]
334
+
335
+ regw = total_boxes[:, 2] - total_boxes[:, 0]
336
+ regh = total_boxes[:, 3] - total_boxes[:, 1]
337
+
338
+ qq1 = total_boxes[:, 0] + total_boxes[:, 5] * regw
339
+ qq2 = total_boxes[:, 1] + total_boxes[:, 6] * regh
340
+ qq3 = total_boxes[:, 2] + total_boxes[:, 7] * regw
341
+ qq4 = total_boxes[:, 3] + total_boxes[:, 8] * regh
342
+
343
+ total_boxes = np.transpose(np.vstack([qq1, qq2, qq3, qq4, total_boxes[:, 4]]))
344
+ total_boxes = self.__rerec(total_boxes.copy())
345
+
346
+ total_boxes[:, 0:4] = np.fix(total_boxes[:, 0:4]).astype(np.int32)
347
+ status = StageStatus(
348
+ self.__pad(total_boxes.copy(), stage_status.width, stage_status.height),
349
+ width=stage_status.width,
350
+ height=stage_status.height,
351
+ )
352
+
353
+ return total_boxes, status
354
+
355
+ def __stage2(self, img, total_boxes, stage_status: StageStatus):
356
+ """
357
+ Second stage of the MTCNN.
358
+ :param img:
359
+ :param total_boxes:
360
+ :param stage_status:
361
+ :return:
362
+ """
363
+
364
+ num_boxes = total_boxes.shape[0]
365
+ if num_boxes == 0:
366
+ return total_boxes, stage_status
367
+
368
+ # second stage
369
+ tempimg = np.zeros(shape=(24, 24, 3, num_boxes))
370
+
371
+ for k in range(0, num_boxes):
372
+ tmp = np.zeros((int(stage_status.tmp_h[k]), int(stage_status.tmp_w[k]), 3))
373
+
374
+ tmp[
375
+ stage_status.dy[k] - 1 : stage_status.edy[k],
376
+ stage_status.dx[k] - 1 : stage_status.edx[k],
377
+ :,
378
+ ] = img[
379
+ stage_status.y[k] - 1 : stage_status.ey[k],
380
+ stage_status.x[k] - 1 : stage_status.ex[k],
381
+ :,
382
+ ]
383
+
384
+ if tmp.shape[0] > 0 and tmp.shape[1] > 0 or tmp.shape[0] == 0 and tmp.shape[1] == 0:
385
+ tempimg[:, :, :, k] = cv2.resize(tmp, (24, 24), interpolation=cv2.INTER_AREA)
386
+
387
+ else:
388
+ return np.empty(shape=(0,)), stage_status
389
+
390
+ tempimg = (tempimg - 127.5) * 0.0078125
391
+ tempimg1 = np.transpose(tempimg, (3, 1, 0, 2))
392
+
393
+ out = tflite_inference(self.r_net, tempimg1)
394
+
395
+ out0 = np.transpose(out[0])
396
+ out1 = np.transpose(out[1])
397
+
398
+ score = out1[1, :]
399
+
400
+ ipass = np.where(score > self._steps_threshold[1])
401
+
402
+ total_boxes = np.hstack([total_boxes[ipass[0], 0:4].copy(), np.expand_dims(score[ipass].copy(), 1)])
403
+
404
+ mv = out0[:, ipass[0]]
405
+
406
+ if total_boxes.shape[0] > 0:
407
+ pick = self.__nms(total_boxes, 0.7, "Union")
408
+ total_boxes = total_boxes[pick, :]
409
+ total_boxes = self.__bbreg(total_boxes.copy(), np.transpose(mv[:, pick]))
410
+ total_boxes = self.__rerec(total_boxes.copy())
411
+
412
+ return total_boxes, stage_status
413
+
414
+ def __stage3(self, img, total_boxes, stage_status: StageStatus):
415
+ """
416
+ Third stage of the MTCNN.
417
+
418
+ :param img:
419
+ :param total_boxes:
420
+ :param stage_status:
421
+ :return:
422
+ """
423
+ num_boxes = total_boxes.shape[0]
424
+ if num_boxes == 0:
425
+ return total_boxes, np.empty(shape=(0,))
426
+
427
+ total_boxes = np.fix(total_boxes).astype(np.int32)
428
+
429
+ status = StageStatus(
430
+ self.__pad(total_boxes.copy(), stage_status.width, stage_status.height),
431
+ width=stage_status.width,
432
+ height=stage_status.height,
433
+ )
434
+
435
+ tempimg = np.zeros((48, 48, 3, num_boxes))
436
+
437
+ for k in range(0, num_boxes):
438
+ tmp = np.zeros((int(status.tmp_h[k]), int(status.tmp_w[k]), 3))
439
+
440
+ tmp[status.dy[k] - 1 : status.edy[k], status.dx[k] - 1 : status.edx[k], :] = img[
441
+ status.y[k] - 1 : status.ey[k], status.x[k] - 1 : status.ex[k], :
442
+ ]
443
+
444
+ if tmp.shape[0] > 0 and tmp.shape[1] > 0 or tmp.shape[0] == 0 and tmp.shape[1] == 0:
445
+ tempimg[:, :, :, k] = cv2.resize(tmp, (48, 48), interpolation=cv2.INTER_AREA)
446
+ else:
447
+ return np.empty(shape=(0,)), np.empty(shape=(0,))
448
+
449
+ tempimg = (tempimg - 127.5) * 0.0078125
450
+ tempimg1 = np.transpose(tempimg, (3, 1, 0, 2))
451
+
452
+ out = tflite_inference(self.o_net, tempimg1)
453
+ out0 = np.transpose(out[0])
454
+ out1 = np.transpose(out[1])
455
+ out2 = np.transpose(out[2])
456
+
457
+ score = out2[1, :]
458
+
459
+ points = out1
460
+
461
+ ipass = np.where(score > self._steps_threshold[2])
462
+
463
+ points = points[:, ipass[0]]
464
+
465
+ total_boxes = np.hstack([total_boxes[ipass[0], 0:4].copy(), np.expand_dims(score[ipass].copy(), 1)])
466
+
467
+ mv = out0[:, ipass[0]]
468
+
469
+ w = total_boxes[:, 2] - total_boxes[:, 0] + 1
470
+ h = total_boxes[:, 3] - total_boxes[:, 1] + 1
471
+
472
+ points[0:5, :] = np.tile(w, (5, 1)) * points[0:5, :] + np.tile(total_boxes[:, 0], (5, 1)) - 1
473
+ points[5:10, :] = np.tile(h, (5, 1)) * points[5:10, :] + np.tile(total_boxes[:, 1], (5, 1)) - 1
474
+
475
+ if total_boxes.shape[0] > 0:
476
+ total_boxes = self.__bbreg(total_boxes.copy(), np.transpose(mv))
477
+ pick = self.__nms(total_boxes.copy(), 0.7, "Min")
478
+ total_boxes = total_boxes[pick, :]
479
+ points = points[:, pick]
480
+
481
+ return total_boxes, points.transpose()
tools/face_recognition.py CHANGED
@@ -1,203 +1,114 @@
1
- from .nametypes import Detection, Identity
 
 
2
  import numpy as np
3
  import cv2
4
- from sklearn.metrics.pairwise import cosine_distances
5
  from skimage.transform import SimilarityTransform
6
-
7
-
8
- def detect_faces(frame, model):
9
- # Process the frame with MediaPipe Face Mesh
10
- results = model.process(frame)
11
-
12
- # Get the Bounding Boxes from the detected faces
13
- detections = []
14
- if results.multi_face_landmarks:
15
- for face in results.multi_face_landmarks:
16
- xs = [landmark.x for landmark in face.landmark]
17
- ys = [landmark.y for landmark in face.landmark]
18
- bbox = [min(xs), min(ys), max(xs), max(ys)]
19
-
20
- FIVE_LANDMARKS = [470, 475, 1, 57, 287]
21
-
22
- landmarks = [
23
- [face.landmark[i].x, face.landmark[i].y] for i in FIVE_LANDMARKS
 
 
 
 
 
 
 
 
 
 
 
24
  ]
 
 
 
 
25
 
26
- detections.append(Detection(bbox=bbox, landmarks=landmarks))
27
- return detections
28
-
29
-
30
- def align(img, landmarks, target_size=(112, 112)):
31
- # Transform to Landmark-Coordinates from relative landmark positions
32
- dst = np.asarray(landmarks) * img.shape[:2][::-1]
33
-
34
- # Target Landmarks-Coordinates from ArcFace Paper
35
- src = np.array(
36
- [
37
- [38.2946, 51.6963],
38
- [73.5318, 51.5014],
39
- [56.0252, 71.7366],
40
- [41.5493, 92.3655],
41
- [70.7299, 92.2041],
42
- ],
43
- dtype=np.float32,
44
- )
45
 
46
- # Estimate the transformation matrix
47
- tform = SimilarityTransform()
48
- tform.estimate(dst, src)
49
- tmatrix = tform.params[0:2, :]
50
 
51
- # Apply the transformation matrix
52
- img = cv2.warpAffine(img, tmatrix, target_size, borderValue=0.0)
53
 
54
- return img
55
-
56
-
57
- def align_faces(img, detections):
58
- updated_detections = []
59
- for detection in detections:
60
- updated_detections.append(
61
- detection._replace(face=align(img, detection.landmarks))
62
- )
63
- return updated_detections
64
-
65
-
66
- # TODO Error when uploading image while running!
67
- def inference(detections, model):
68
- updated_detections = []
69
- faces = [detection.face for detection in detections if detection.face is not None]
70
-
71
- if len(faces) > 0:
72
- faces = np.asarray(faces).astype(np.float32) / 255
73
- model.resize_tensor_input(model.get_input_details()[0]["index"], faces.shape)
74
- model.allocate_tensors()
75
- model.set_tensor(model.get_input_details()[0]["index"], faces)
76
- model.invoke()
77
- embs = [model.get_tensor(elem["index"]) for elem in model.get_output_details()][
78
- 0
79
- ]
80
 
 
 
81
  for idx, detection in enumerate(detections):
82
- updated_detections.append(detection._replace(embedding=embs[idx]))
83
- return updated_detections
84
-
85
-
86
- def recognize_faces(detections, gallery, thresh=0.67):
87
- if len(gallery) == 0 or len(detections) == 0:
88
- return detections
89
-
90
- gallery_embs = np.asarray([identity.embedding for identity in gallery])
91
- detection_embs = np.asarray([detection.embedding for detection in detections])
92
-
93
- cos_distances = cosine_distances(detection_embs, gallery_embs)
94
-
95
- updated_detections = []
96
- for idx, detection in enumerate(detections):
97
- idx_min = np.argmin(cos_distances[idx])
98
- if thresh and cos_distances[idx][idx_min] > thresh:
99
- dist = cos_distances[idx][idx_min]
100
- pred = None
101
- else:
102
- dist = cos_distances[idx][idx_min]
103
- pred = idx_min
104
- updated_detections.append(
105
- detection._replace(
106
- name=gallery[pred]
107
- .name.split(".jpg")[0]
108
- .split(".png")[0]
109
- .split(".jpeg")[0]
110
- if pred is not None
111
- else None,
112
- embedding_match=gallery[pred].embedding if pred is not None else None,
113
- face_match=gallery[pred].image if pred is not None else None,
114
- distance=dist,
115
- )
116
- )
117
-
118
- return updated_detections
119
-
120
-
121
- def process_gallery(files, face_detection_model, face_recognition_model):
122
- gallery = []
123
- for file in files:
124
- file_bytes = np.asarray(bytearray(file.read()), dtype=np.uint8)
125
- img = cv2.cvtColor(
126
- cv2.imdecode(file_bytes, cv2.IMREAD_COLOR), cv2.COLOR_BGR2RGB
127
- )
128
-
129
- detections = detect_faces(img, face_detection_model)
130
-
131
- # We accept only one face per image!
132
- if detections == []:
133
- continue
134
- elif len(detections) > 1:
135
- detections = detections[:1]
136
-
137
- detections = align_faces(img, detections)
138
- detections = inference(detections, face_recognition_model)
139
-
140
- gallery.append(
141
- Identity(
142
- name=file.name,
143
- embedding=detections[0].embedding,
144
- image=detections[0].face,
145
- )
146
- )
147
-
148
- return gallery
149
-
150
-
151
- def draw_detections(
152
- frame, detections, bbox=True, landmarks=True, name=True):
153
- shape = np.asarray(frame.shape[:2][::-1])
154
-
155
- for detection in detections:
156
- # Draw Landmarks
157
- if landmarks:
158
- for landmark in detection.landmarks:
159
- cv2.circle(
160
- frame,
161
- (np.asarray(landmark) * shape).astype(int),
162
- 2,
163
- (0, 0, 255),
164
- -1,
165
  )
166
-
167
- # Draw Bounding Box
168
- if bbox:
169
- cv2.rectangle(
170
- frame,
171
- (np.asarray(detection.bbox[:2]) * shape).astype(int),
172
- (np.asarray(detection.bbox[2:]) * shape).astype(int),
173
- (0, 255, 0),
174
- 2,
175
- )
176
-
177
- # Draw Name
178
- if name:
179
- cv2.rectangle(
180
- frame,
181
- (
182
- int(detection.bbox[0] * shape[0]),
183
- int(detection.bbox[1] * shape[1] - (shape[1] // 25)),
184
- ),
185
- (int(detection.bbox[2] * shape[0]), int(detection.bbox[1] * shape[1])),
186
- (255, 255, 255),
187
- -1,
188
- )
189
-
190
- cv2.putText(
191
- frame,
192
- detection.name,
193
- (
194
- int(detection.bbox[0] * shape[0] + shape[0] // 400),
195
- int(detection.bbox[1] * shape[1] - shape[1] // 100),
196
- ),
197
- cv2.LINE_AA,
198
- 0.5,
199
- (0, 0, 0),
200
- 2,
201
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
- return frame
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .utils import tflite_inference
2
+ from .nametypes import Identity, Match
3
+ from sklearn.metrics.pairwise import cosine_distances
4
  import numpy as np
5
  import cv2
 
6
  from skimage.transform import SimilarityTransform
7
+ from .utils import get_file
8
+ import tflite_runtime.interpreter as tflite
9
+ from typing import Literal
10
+
11
+
12
+ BASE_URL = "https://github.com/Martlgap/FaceIDLight/releases/download/v.0.1/"
13
+
14
+ FILE_HASHES = {
15
+ "mobileNet": "6c19b789f661caa8da735566490bfd8895beffb2a1ec97a56b126f0539991aa6",
16
+ "resNet": "f4d8b0194957a3ad766135505fc70a91343660151a8103bbb6c3b8ac34dbb4e2",
17
+ }
18
+
19
+
20
+ class FaceRecognition:
21
+ def __init__(
22
+ self,
23
+ min_similarity: float = 0.67,
24
+ model_name: Literal["mobileNet", "resNet50"] = "mobileNet",
25
+ ):
26
+ self.min_similarity = min_similarity
27
+ self.model = tflite.Interpreter(model_path=get_file(BASE_URL + f"{model_name}.tflite", FILE_HASHES[model_name]))
28
+
29
+ def __call__(self, frame, detections):
30
+ # Align Faces
31
+ faces, faces_aligned = [], []
32
+ for detection in detections:
33
+ face = frame[
34
+ int(detection.bbox[0][1]) : int(detection.bbox[1][1]),
35
+ int(detection.bbox[0][0]) : int(detection.bbox[1][0]),
36
  ]
37
+ try:
38
+ face = cv2.resize(face, (112, 112))
39
+ except:
40
+ face = np.zeros((112, 112, 3))
41
 
42
+ faces.append(face)
43
+ faces_aligned.append(self.align(frame, detection.landmarks))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
+ # Do Inference
46
+ if len(faces_aligned) == 0:
47
+ return []
 
48
 
49
+ # Normalize images from [0, 255] to [0, 1]
50
+ faces_aligned_norm = np.asarray(faces_aligned).astype(np.float32) / 255.0
51
 
52
+ embs_det = tflite_inference(self.model, faces_aligned_norm)
53
+ embs_det = np.asarray(embs_det[0])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
+ # Save Identities
56
+ identities = []
57
  for idx, detection in enumerate(detections):
58
+ identities.append(
59
+ Identity(
60
+ detection_idx=detection.idx,
61
+ embedding=embs_det[idx],
62
+ face_aligned=faces_aligned[idx],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  )
65
+ return identities
66
+
67
+ def find_matches(self, identities, gallery):
68
+ if len(gallery) == 0 or len(identities) == 0:
69
+ return []
70
+
71
+ # Get Embeddings
72
+ embs_gal = np.asarray([identity.embedding for identity in gallery])
73
+ embs_det = np.asarray([identity.embedding for identity in identities])
74
+
75
+ # Calculate Cosine Distances
76
+ cos_distances = cosine_distances(embs_det, embs_gal)
77
+
78
+ # Find Matches
79
+ matches = []
80
+ for ident_idx, identity in enumerate(identities):
81
+ dist_to_identity = cos_distances[ident_idx]
82
+ idx_min = np.argmin(dist_to_identity)
83
+ if dist_to_identity[idx_min] < self.min_similarity:
84
+ matches.append(
85
+ Match(
86
+ identity_idx=identity.detection_idx,
87
+ gallery_idx=idx_min,
88
+ distance=dist_to_identity[idx_min],
89
+ name=gallery[idx_min].name,
90
+ )
91
+ )
92
 
93
+ # Sort Matches by identity_idx
94
+ matches = sorted(matches, key=lambda match: match.gallery_idx)
95
+
96
+ return matches
97
+
98
+ @staticmethod
99
+ def align(img, landmarks_source, target_size=(112, 112)):
100
+ landmarks_target = np.array(
101
+ [
102
+ [38.2946, 51.6963],
103
+ [73.5318, 51.5014],
104
+ [56.0252, 71.7366],
105
+ [41.5493, 92.3655],
106
+ [70.7299, 92.2041],
107
+ ],
108
+ dtype=np.float32,
109
+ )
110
+ tform = SimilarityTransform()
111
+ tform.estimate(landmarks_source, landmarks_target)
112
+ tmatrix = tform.params[0:2, :]
113
+ face_aligned = cv2.warpAffine(img, tmatrix, target_size, borderValue=0.0)
114
+ return face_aligned
tools/nametypes.py CHANGED
@@ -3,14 +3,17 @@ import numpy as np
3
 
4
 
5
  class Detection(NamedTuple):
6
- bbox: List[int]
7
- landmarks: List[List[int]]
 
 
 
 
 
 
8
  name: str = None
9
- face: np.ndarray = None
10
  embedding: np.ndarray = None
11
- embedding_match: np.ndarray = None
12
- face_match: np.ndarray = None
13
- distance: float = None
14
 
15
 
16
  class Stats(NamedTuple):
@@ -18,13 +21,13 @@ class Stats(NamedTuple):
18
  resolution: List[int] = [None, None, None]
19
  num_faces: int = 0
20
  detection: float = None
21
- alignment: float = None
22
- inference: float = None
23
  recognition: float = None
24
- drawing: float = None
 
25
 
26
 
27
- class Identity(NamedTuple):
28
- name: str
29
- embedding: np.ndarray
30
- image: np.ndarray
 
 
3
 
4
 
5
  class Detection(NamedTuple):
6
+ idx: int = None
7
+ bbox: List[List[float]] = None
8
+ landmarks: List[List[float]] = None
9
+ confidence: float = None
10
+
11
+
12
+ class Identity(NamedTuple):
13
+ detection_idx: int = None
14
  name: str = None
 
15
  embedding: np.ndarray = None
16
+ face_aligned: np.ndarray = None
 
 
17
 
18
 
19
  class Stats(NamedTuple):
 
21
  resolution: List[int] = [None, None, None]
22
  num_faces: int = 0
23
  detection: float = None
 
 
24
  recognition: float = None
25
+ matching: float = None
26
+ annotation: float = None
27
 
28
 
29
+ class Match(NamedTuple):
30
+ identity_idx: int = None
31
+ gallery_idx: int = None
32
+ distance: float = None
33
+ name: str = None
tools/pca.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sklearn.decomposition import PCA
2
+ import numpy as np
3
+ import plotly.express as px
4
+
5
+
6
+ def pca(matches, identities, gallery, dim=3):
7
+ """
8
+ Perform PCA on embeddings.
9
+ Args:
10
+ embeddings: np.array of shape (n_embeddings, 512)
11
+ Returns:
12
+ embeddings_pca: np.array of shape (n_embeddings, 3)
13
+ """
14
+
15
+ # Get Gallery and Detection Embeddings and stich them together in groups
16
+ embeddings = np.concatenate(
17
+ [[gallery[match.gallery_idx].embedding, identities[match.identity_idx].embedding] for match in matches],
18
+ axis=0,
19
+ )
20
+
21
+ # Get Identity Names and stich them together in groups
22
+ identity_names = np.concatenate(
23
+ [[gallery[match.gallery_idx].name, gallery[match.gallery_idx].name] for match in matches],
24
+ axis=0,
25
+ )
26
+
27
+ # Do 3D PCA
28
+ pca = PCA(n_components=dim)
29
+ pca.fit(embeddings)
30
+ embeddings_pca = pca.transform(embeddings)
31
+
32
+ if dim == 3:
33
+ fig = px.scatter_3d(
34
+ embeddings_pca,
35
+ x=0,
36
+ y=1,
37
+ z=2,
38
+ opacity=0.7,
39
+ color=identity_names,
40
+ color_discrete_sequence=px.colors.qualitative.Vivid,
41
+ )
42
+ fig.update_traces(marker=dict(size=4))
43
+ elif dim == 2:
44
+ fig = px.scatter(
45
+ embeddings_pca,
46
+ x=0,
47
+ y=1,
48
+ opacity=0.7,
49
+ color=identity_names,
50
+ color_discrete_sequence=px.colors.qualitative.Vivid,
51
+ )
52
+ fig.update_traces(marker=dict(size=4))
53
+ fig.update_xaxes(showgrid=True)
54
+ fig.update_yaxes(showgrid=True)
55
+ else:
56
+ raise ValueError("dim must be either 2 or 3")
57
+ fig.update_layout(margin=dict(l=0, r=0, b=0, t=0))
58
+
59
+ return fig
tools/utils.py CHANGED
@@ -1,13 +1,16 @@
1
  import logging
2
  import os
3
- import urllib.request
4
- from pathlib import Path
5
  import streamlit as st
6
  from twilio.rest import Client
7
  import os
8
- import cv2
9
  import numpy as np
10
  import hashlib
 
 
 
 
 
 
11
 
12
 
13
  logger = logging.getLogger(__name__)
@@ -25,9 +28,7 @@ def get_ice_servers(name="twilio"):
25
  account_sid = os.environ["TWILIO_ACCOUNT_SID"]
26
  auth_token = os.environ["TWILIO_AUTH_TOKEN"]
27
  except KeyError:
28
- logger.warning(
29
- "Twilio credentials are not set. Fallback to a free STUN server from Google."
30
- )
31
  return [{"urls": ["stun:stun.l.google.com:19302"]}]
32
 
33
  client = Client(account_sid, auth_token)
@@ -41,9 +42,7 @@ def get_ice_servers(name="twilio"):
41
  username = os.environ["METERED_USERNAME"]
42
  credential = os.environ["METERED_CREDENTIAL"]
43
  except KeyError:
44
- logger.warning(
45
- "Metered credentials are not set. Fallback to a free STUN server from Google."
46
- )
47
  return [{"urls": ["stun:stun.l.google.com:19302"]}]
48
 
49
  ice_servers = [
@@ -78,70 +77,6 @@ def get_ice_servers(name="twilio"):
78
  raise ValueError(f"Unknown name: {name}")
79
 
80
 
81
- def get_hash(filepath):
82
- hasher = hashlib.sha256()
83
- with open(filepath, "rb") as file:
84
- for chunk in iter(lambda: file.read(65535), b""):
85
- hasher.update(chunk)
86
- return hasher.hexdigest()
87
-
88
-
89
- def download_file(url, model_path: Path, file_hash=None):
90
- if model_path.exists():
91
- if file_hash:
92
- hasher = hashlib.sha256()
93
- with open(model_path, "rb") as file:
94
- for chunk in iter(lambda: file.read(65535), b""):
95
- hasher.update(chunk)
96
- if not hasher.hexdigest() == file_hash:
97
- print(
98
- "A local file was found, but it seems to be incomplete or outdated because the file hash does not "
99
- "match the original value of "
100
- + file_hash
101
- + " so data will be downloaded."
102
- )
103
- download = True
104
- else:
105
- print("Using a verified local file.")
106
- download = False
107
- else:
108
- model_path.mkdir(parents=True, exist_ok=True)
109
- print("Downloading data ...")
110
- download = True
111
-
112
- if download:
113
- # These are handles to two visual elements to animate.
114
- weights_warning, progress_bar = None, None
115
- try:
116
- weights_warning = st.warning("Downloading %s..." % url)
117
- progress_bar = st.progress(0)
118
- with open(model_path, "wb") as output_file:
119
- with urllib.request.urlopen(url) as response:
120
- length = int(response.info()["Content-Length"])
121
- counter = 0.0
122
- MEGABYTES = 2.0**20.0
123
- while True:
124
- data = response.read(8192)
125
- if not data:
126
- break
127
- counter += len(data)
128
- output_file.write(data)
129
-
130
- # We perform animation by overwriting the elements.
131
- weights_warning.warning(
132
- "Downloading %s... (%6.2f/%6.2f MB)"
133
- % (url, counter / MEGABYTES, length / MEGABYTES)
134
- )
135
- progress_bar.progress(min(counter / length, 1.0))
136
-
137
- # Finally, we remove these visual elements by calling .empty().
138
- finally:
139
- if weights_warning is not None:
140
- weights_warning.empty()
141
- if progress_bar is not None:
142
- progress_bar.empty()
143
-
144
-
145
  # Function to format floats within a list
146
  def format_dflist(val):
147
  if isinstance(val, list):
@@ -156,20 +91,74 @@ def format_dflist(val):
156
  return val
157
 
158
 
159
- def display_match(d):
160
- im = np.concatenate([d.face, d.face_match])
161
- border_size = 2
162
- border = cv2.copyMakeBorder(
163
- im,
164
- top=border_size,
165
- bottom=border_size,
166
- left=border_size,
167
- right=border_size,
168
- borderType=cv2.BORDER_CONSTANT,
169
- value=(255, 255, 120),
170
- )
171
- return border
172
-
173
-
174
  def rgb(r, g, b):
175
  return "#{:02x}{:02x}{:02x}".format(r, g, b)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import logging
2
  import os
 
 
3
  import streamlit as st
4
  from twilio.rest import Client
5
  import os
 
6
  import numpy as np
7
  import hashlib
8
+ import tempfile
9
+ import os
10
+ import hashlib
11
+ from tqdm import tqdm
12
+ from zipfile import ZipFile
13
+ from urllib.request import urlopen
14
 
15
 
16
  logger = logging.getLogger(__name__)
 
28
  account_sid = os.environ["TWILIO_ACCOUNT_SID"]
29
  auth_token = os.environ["TWILIO_AUTH_TOKEN"]
30
  except KeyError:
31
+ logger.warning("Twilio credentials are not set. Fallback to a free STUN server from Google.")
 
 
32
  return [{"urls": ["stun:stun.l.google.com:19302"]}]
33
 
34
  client = Client(account_sid, auth_token)
 
42
  username = os.environ["METERED_USERNAME"]
43
  credential = os.environ["METERED_CREDENTIAL"]
44
  except KeyError:
45
+ logger.warning("Metered credentials are not set. Fallback to a free STUN server from Google.")
 
 
46
  return [{"urls": ["stun:stun.l.google.com:19302"]}]
47
 
48
  ice_servers = [
 
77
  raise ValueError(f"Unknown name: {name}")
78
 
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  # Function to format floats within a list
81
  def format_dflist(val):
82
  if isinstance(val, list):
 
91
  return val
92
 
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  def rgb(r, g, b):
95
  return "#{:02x}{:02x}{:02x}".format(r, g, b)
96
+
97
+
98
+ def tflite_inference(model, img):
99
+ """Inferences an image through the model with tflite interpreter on CPU
100
+ :param model: a tflite.Interpreter loaded with a model
101
+ :param img: image
102
+ :return: list of outputs of the model
103
+ """
104
+ # Check if img is np.ndarray
105
+ if not isinstance(img, np.ndarray):
106
+ img = np.asarray(img)
107
+
108
+ # Check if dim is 4
109
+ if len(img.shape) == 3:
110
+ img = np.expand_dims(img, axis=0)
111
+
112
+ input_details = model.get_input_details()
113
+ output_details = model.get_output_details()
114
+ model.resize_tensor_input(input_details[0]["index"], img.shape)
115
+ model.allocate_tensors()
116
+ model.set_tensor(input_details[0]["index"], img.astype(np.float32))
117
+ model.invoke()
118
+ return [model.get_tensor(elem["index"]) for elem in output_details]
119
+
120
+
121
+ def get_file(origin, file_hash, is_zip=False):
122
+ tmp_file = os.path.join(tempfile.gettempdir(), "FaceIDLight", origin.split("/")[-1])
123
+ os.makedirs(os.path.dirname(tmp_file), exist_ok=True)
124
+ if not os.path.exists(tmp_file):
125
+ download = True
126
+ else:
127
+ hasher = hashlib.sha256()
128
+ with open(tmp_file, "rb") as file:
129
+ for chunk in iter(lambda: file.read(65535), b""):
130
+ hasher.update(chunk)
131
+ if not hasher.hexdigest() == file_hash:
132
+ print(
133
+ "A local file was found, but it seems to be incomplete or outdated because the file hash does not "
134
+ "match the original value of " + file_hash + " so data will be downloaded."
135
+ )
136
+ download = True
137
+ else:
138
+ download = False
139
+
140
+ if download:
141
+ response = urlopen(origin)
142
+ with tqdm.wrapattr(
143
+ open(tmp_file, "wb"),
144
+ "write",
145
+ miniters=1,
146
+ desc="Downloading " + origin.split("/")[-1] + " to: " + tmp_file,
147
+ total=getattr(response, "length", None),
148
+ ) as file:
149
+ for chunk in response:
150
+ file.write(chunk)
151
+ file.close()
152
+ if is_zip:
153
+ with ZipFile(tmp_file, "r") as zipObj:
154
+ zipObj.extractall(tmp_file.split(".")[0])
155
+ tmp_file = os.path.join(tmp_file.split(".")[0])
156
+ return tmp_file
157
+
158
+
159
+ def get_hash(filepath):
160
+ hasher = hashlib.sha256()
161
+ with open(filepath, "rb") as file:
162
+ for chunk in iter(lambda: file.read(65535), b""):
163
+ hasher.update(chunk)
164
+ return hasher.hexdigest()