-
Notifications
You must be signed in to change notification settings - Fork 0
/
face_identifier.py
136 lines (106 loc) · 4.95 KB
/
face_identifier.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
import cv2
import numpy as np
from utils import cut_rois, resize_input
from ie_module import Module
class FaceIdentifier(Module):
# Taken from the description of the model:
# intel_models/face-reidentification-retail-0095
REFERENCE_LANDMARKS = [
(30.2946 / 96, 51.6963 / 112), # left eye
(65.5318 / 96, 51.5014 / 112), # right eye
(48.0252 / 96, 71.7366 / 112), # nose tip
(33.5493 / 96, 92.3655 / 112), # left lip corner
(62.7299 / 96, 92.2041 / 112)] # right lip corner
UNKNOWN_ID = -1
UNKNOWN_ID_LABEL = "Unknown"
class Result:
def __init__(self, id, distance, desc):
self.id = id
self.distance = distance
self.descriptor = desc
def __init__(self, model, match_threshold=0.5, match_algo='HUNGARIAN'):
super(FaceIdentifier, self).__init__(model)
assert len(model.inputs) == 1, "Expected 1 input blob"
assert len(model.outputs) == 1, "Expected 1 output blob"
self.input_blob = next(iter(model.inputs))
self.output_blob = next(iter(model.outputs))
self.input_shape = model.inputs[self.input_blob].shape
assert len(model.outputs[self.output_blob].shape) == 4 or \
len(model.outputs[self.output_blob].shape) == 2, \
"Expected model output shape [1, n, 1, 1] or [1, n], got %s" % \
(model.outputs[self.output_blob].shape)
self.faces_database = None
self.match_threshold = match_threshold
self.match_algo = match_algo
def set_faces_database(self, database):
self.faces_database = database
def get_identity_label(self, id):
if not self.faces_database or id == self.UNKNOWN_ID:
return self.UNKNOWN_ID_LABEL
return self.faces_database[id].label
def preprocess(self, frame, rois, landmarks):
assert len(frame.shape) == 4, "Frame shape should be [1, c, h, w]"
inputs = cut_rois(frame, rois)
self._align_rois(inputs, landmarks)
inputs = [resize_input(input, self.input_shape) for input in inputs]
return inputs
def enqueue(self, input):
return super(FaceIdentifier, self).enqueue({self.input_blob: input})
def start_async(self, frame, rois, landmarks):
inputs = self.preprocess(frame, rois, landmarks)
for input in inputs:
self.enqueue(input)
def get_threshold(self):
return self.match_threshold
def get_matches(self):
descriptors = self.get_descriptors()
matches = []
if len(descriptors) != 0:
matches = self.faces_database.match_faces(descriptors, self.match_algo)
results = []
unknowns_list = []
for num, match in enumerate(matches):
id = match[0]
distance = match[1]
if self.match_threshold < distance:
id = self.UNKNOWN_ID
unknowns_list.append(num)
results.append(self.Result(id, distance, descriptors[num]))
return results, unknowns_list
def get_descriptors(self):
return [out[self.output_blob].flatten() for out in self.get_outputs()]
@staticmethod
def normalize(array, axis):
mean = array.mean(axis=axis)
array -= mean
std = array.std()
array /= std
return mean, std
@staticmethod
def get_transform(src, dst):
assert np.array_equal(src.shape, dst.shape) and len(src.shape) == 2, \
"2d input arrays are expected, got %s" % (src.shape)
src_col_mean, src_col_std = FaceIdentifier.normalize(src, axis=(0))
dst_col_mean, dst_col_std = FaceIdentifier.normalize(dst, axis=(0))
u, _, vt = np.linalg.svd(np.matmul(src.T, dst))
r = np.matmul(u, vt).T
transform = np.empty((2, 3))
transform[:, 0:2] = r * (dst_col_std / src_col_std)
transform[:, 2] = dst_col_mean.T - \
np.matmul(transform[:, 0:2], src_col_mean.T)
return transform
def _align_rois(self, face_images, face_landmarks):
assert len(face_images) == len(face_landmarks), \
"Input lengths differ, got %s and %s" % \
(len(face_images), len(face_landmarks))
for image, image_landmarks in zip(face_images, face_landmarks):
assert len(image.shape) == 4, "Face image is expected"
image = image[0]
scale = np.array((image.shape[-1], image.shape[-2]))
desired_landmarks = np.array(self.REFERENCE_LANDMARKS, dtype=np.float64) * scale
landmarks = image_landmarks.get_array() * scale
transform = FaceIdentifier.get_transform(desired_landmarks, landmarks)
img = image.transpose((1, 2, 0))
cv2.warpAffine(img, transform, tuple(scale), img,
flags=cv2.WARP_INVERSE_MAP)
image[:] = img.transpose((2, 0, 1))