Skip to content

Commit 365c419

Browse files
authored
Update to OpenCV APIs (YuNet -> FaceDetectorYN, SFace -> FaceRecognizerSF) (#6)
* update YuNet and SFace impl with opencv-python api
1 parent feeb407 commit 365c419

File tree

7 files changed

+88
-282
lines changed

7 files changed

+88
-282
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ Hardware Setup:
2929
-->
3030
| Model | Input Size | CPU x86_64 (ms) | CPU ARM (ms) |
3131
|-------|------------|-----------------|--------------|
32-
| [YuNet](./models/face_detection_yunet) | 160x120 | 2.35 | 8.72 |
32+
| [YuNet](./models/face_detection_yunet) | 160x120 | 1.45 | 6.22 |
3333
| [DB](./models/text_detection_db) | 640x480 | 137.38 | 2780.78 |
3434
| [CRNN](./models/text_recognition_crnn) | 100x32 | 50.21 | 234.32 |
35-
| [SFace](./models/face_recognition_sface) | 112x112 | 8.69 | 96.79 |
35+
| [SFace](./models/face_recognition_sface) | 112x112 | 8.65 | 99.20 |
3636
| [PP-ResNet](./models/image_classification_ppresnet) | 224x224 | 56.05 | 602.58
3737
| [PP-HumanSeg](./models/human_segmentation_pphumanseg) | 192x192 | 19.92 | 105.32 |
3838

benchmark/config/face_detection_yunet.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,4 @@ Model:
1919
modelPath: "models/face_detection_yunet/face_detection_yunet.onnx"
2020
confThreshold: 0.6
2121
nmsThreshold: 0.3
22-
topK: 5000
23-
keepTopK: 750
22+
topK: 5000

benchmark/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
numpy==1.21.2
2-
opencv-python==4.5.3.56
2+
opencv-python==4.5.4.58
33
tqdm
44
pyyaml
55
requests

models/face_detection_yunet/demo.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ def str2bool(v):
2525
parser.add_argument('--conf_threshold', type=float, default=0.9, help='Filter out faces of confidence < conf_threshold.')
2626
parser.add_argument('--nms_threshold', type=float, default=0.3, help='Suppress bounding boxes of iou >= nms_threshold.')
2727
parser.add_argument('--top_k', type=int, default=5000, help='Keep top_k bounding boxes before NMS.')
28-
parser.add_argument('--keep_top_k', type=int, default=750, help='Keep keep_top_k bounding boxes after NMS.')
2928
parser.add_argument('--save', '-s', type=str, default=False, help='Set true to save results. This flag is invalid when using camera.')
3029
parser.add_argument('--vis', '-v', type=str2bool, default=True, help='Set true to open a window for result visualization. This flag is invalid when using camera.')
3130
args = parser.parse_args()
@@ -62,8 +61,7 @@ def visualize(image, results, box_color=(0, 255, 0), text_color=(0, 0, 255), fps
6261
inputSize=[320, 320],
6362
confThreshold=args.conf_threshold,
6463
nmsThreshold=args.nms_threshold,
65-
topK=args.top_k,
66-
keepTopK=args.keep_top_k)
64+
topK=args.top_k)
6765

6866
# If input is an image
6967
if args.input is not None:

models/face_detection_yunet/yunet.py

Lines changed: 39 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -10,140 +10,57 @@
1010
import cv2 as cv
1111

1212
class YuNet:
13-
def __init__(self, modelPath, inputSize=[320, 320], confThreshold=0.6, nmsThreshold=0.3, topK=5000, keepTopK=750):
13+
def __init__(self, modelPath, inputSize=[320, 320], confThreshold=0.6, nmsThreshold=0.3, topK=5000, backendId=0, targetId=0):
1414
self._modelPath = modelPath
15-
self._model = cv.dnn.readNet(self._modelPath)
16-
17-
self._inputNames = ''
18-
self._outputNames = ['loc', 'conf', 'iou']
19-
self._inputSize = inputSize # [w, h]
15+
self._inputSize = tuple(inputSize) # [w, h]
2016
self._confThreshold = confThreshold
2117
self._nmsThreshold = nmsThreshold
2218
self._topK = topK
23-
self._keepTopK = keepTopK
24-
25-
self._min_sizes = [[10, 16, 24], [32, 48], [64, 96], [128, 192, 256]]
26-
self._steps = [8, 16, 32, 64]
27-
self._variance = [0.1, 0.2]
19+
self._backendId = backendId
20+
self._targetId = targetId
2821

29-
# Generate priors
30-
self._priorGen()
22+
self._model = cv.FaceDetectorYN.create(
23+
model=self._modelPath,
24+
config="",
25+
input_size=self._inputSize,
26+
score_threshold=self._confThreshold,
27+
nms_threshold=self._nmsThreshold,
28+
top_k=self._topK,
29+
backend_id=self._backendId,
30+
target_id=self._targetId)
3131

3232
@property
3333
def name(self):
3434
return self.__class__.__name__
3535

36-
def setBackend(self, backend):
37-
self._model.setPreferableBackend(backend)
38-
39-
def setTarget(self, target):
40-
self._model.setPreferableTarget(target)
36+
def setBackend(self, backendId):
37+
self._backendId = backendId
38+
self._model = cv.FaceDetectorYN.create(
39+
model=self._modelPath,
40+
config="",
41+
input_size=self._inputSize,
42+
score_threshold=self._confThreshold,
43+
nms_threshold=self._nmsThreshold,
44+
top_k=self._topK,
45+
backend_id=self._backendId,
46+
target_id=self._targetId)
47+
48+
def setTarget(self, targetId):
49+
self._targetId = targetId
50+
self._model = cv.FaceDetectorYN.create(
51+
model=self._modelPath,
52+
config="",
53+
input_size=self._inputSize,
54+
score_threshold=self._confThreshold,
55+
nms_threshold=self._nmsThreshold,
56+
top_k=self._topK,
57+
backend_id=self._backendId,
58+
target_id=self._targetId)
4159

4260
def setInputSize(self, input_size):
43-
self._inputSize = input_size # [w, h]
44-
45-
# Regenerate priors
46-
self._priorGen()
47-
48-
def _preprocess(self, image):
49-
return cv.dnn.blobFromImage(image)
61+
self._model.setInputSize(tuple(input_size))
5062

5163
def infer(self, image):
52-
assert image.shape[0] == self._inputSize[1], '{} (height of input image) != {} (preset height)'.format(image.shape[0], self._inputSize[1])
53-
assert image.shape[1] == self._inputSize[0], '{} (width of input image) != {} (preset width)'.format(image.shape[1], self._inputSize[0])
54-
55-
# Preprocess
56-
inputBlob = self._preprocess(image)
57-
5864
# Forward
59-
self._model.setInput(inputBlob, self._inputNames)
60-
outputBlob = self._model.forward(self._outputNames)
61-
62-
# Postprocess
63-
results = self._postprocess(outputBlob)
64-
65-
return results
66-
67-
def _postprocess(self, outputBlob):
68-
# Decode
69-
dets = self._decode(outputBlob)
70-
71-
# NMS
72-
keepIdx = cv.dnn.NMSBoxes(
73-
bboxes=dets[:, 0:4].tolist(),
74-
scores=dets[:, -1].tolist(),
75-
score_threshold=self._confThreshold,
76-
nms_threshold=self._nmsThreshold,
77-
top_k=self._topK
78-
) # box_num x class_num
79-
if len(keepIdx) > 0:
80-
dets = dets[keepIdx]
81-
dets = np.squeeze(dets, axis=1)
82-
return dets[:self._keepTopK]
83-
else:
84-
return np.empty(shape=(0, 15))
85-
86-
def _priorGen(self):
87-
w, h = self._inputSize
88-
feature_map_2th = [int(int((h + 1) / 2) / 2),
89-
int(int((w + 1) / 2) / 2)]
90-
feature_map_3th = [int(feature_map_2th[0] / 2),
91-
int(feature_map_2th[1] / 2)]
92-
feature_map_4th = [int(feature_map_3th[0] / 2),
93-
int(feature_map_3th[1] / 2)]
94-
feature_map_5th = [int(feature_map_4th[0] / 2),
95-
int(feature_map_4th[1] / 2)]
96-
feature_map_6th = [int(feature_map_5th[0] / 2),
97-
int(feature_map_5th[1] / 2)]
98-
99-
feature_maps = [feature_map_3th, feature_map_4th,
100-
feature_map_5th, feature_map_6th]
101-
102-
priors = []
103-
for k, f in enumerate(feature_maps):
104-
min_sizes = self._min_sizes[k]
105-
for i, j in product(range(f[0]), range(f[1])): # i->h, j->w
106-
for min_size in min_sizes:
107-
s_kx = min_size / w
108-
s_ky = min_size / h
109-
110-
cx = (j + 0.5) * self._steps[k] / w
111-
cy = (i + 0.5) * self._steps[k] / h
112-
113-
priors.append([cx, cy, s_kx, s_ky])
114-
self.priors = np.array(priors, dtype=np.float32)
115-
116-
def _decode(self, outputBlob):
117-
loc, conf, iou = outputBlob
118-
# get score
119-
cls_scores = conf[:, 1]
120-
iou_scores = iou[:, 0]
121-
# clamp
122-
_idx = np.where(iou_scores < 0.)
123-
iou_scores[_idx] = 0.
124-
_idx = np.where(iou_scores > 1.)
125-
iou_scores[_idx] = 1.
126-
scores = np.sqrt(cls_scores * iou_scores)
127-
scores = scores[:, np.newaxis]
128-
129-
scale = np.array(self._inputSize)
130-
131-
# get bboxes
132-
bboxes = np.hstack((
133-
(self.priors[:, 0:2] + loc[:, 0:2] * self._variance[0] * self.priors[:, 2:4]) * scale,
134-
(self.priors[:, 2:4] * np.exp(loc[:, 2:4] * self._variance)) * scale
135-
))
136-
# (x_c, y_c, w, h) -> (x1, y1, w, h)
137-
bboxes[:, 0:2] -= bboxes[:, 2:4] / 2
138-
139-
# get landmarks
140-
landmarks = np.hstack((
141-
(self.priors[:, 0:2] + loc[:, 4: 6] * self._variance[0] * self.priors[:, 2:4]) * scale,
142-
(self.priors[:, 0:2] + loc[:, 6: 8] * self._variance[0] * self.priors[:, 2:4]) * scale,
143-
(self.priors[:, 0:2] + loc[:, 8:10] * self._variance[0] * self.priors[:, 2:4]) * scale,
144-
(self.priors[:, 0:2] + loc[:, 10:12] * self._variance[0] * self.priors[:, 2:4]) * scale,
145-
(self.priors[:, 0:2] + loc[:, 12:14] * self._variance[0] * self.priors[:, 2:4]) * scale
146-
))
147-
148-
dets = np.hstack((bboxes, landmarks, scores))
149-
return dets
65+
faces = self._model.detect(image)
66+
return faces[1]

models/face_recognition_sface/demo.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,13 @@ def str2bool(v):
3535

3636
if __name__ == '__main__':
3737
# Instantiate SFace for face recognition
38-
recognizer = SFace(modelPath=args.model)
38+
recognizer = SFace(modelPath=args.model, disType=args.dis_type)
3939
# Instantiate YuNet for face detection
4040
detector = YuNet(modelPath='../face_detection_yunet/face_detection_yunet.onnx',
4141
inputSize=[320, 320],
4242
confThreshold=0.9,
4343
nmsThreshold=0.3,
44-
topK=5000,
45-
keepTopK=750)
44+
topK=5000)
4645

4746
img1 = cv.imread(args.input1)
4847
img2 = cv.imread(args.input2)
@@ -56,16 +55,5 @@ def str2bool(v):
5655
assert face2.shape[0] > 0, 'Cannot find a face in {}'.format(args.input2)
5756

5857
# Match
59-
distance = recognizer.match(img1, face1[0][:-1], img2, face2[0][:-1], args.dis_type)
60-
print(distance)
61-
if args.dis_type == 0:
62-
dis_type = 'Cosine'
63-
threshold = 0.363
64-
result = 'same identity' if distance >= threshold else 'different identity'
65-
elif args.dis_type == 1:
66-
dis_type = 'Norm-L2'
67-
threshold = 1.128
68-
result = 'same identity' if distance <= threshold else 'different identity'
69-
else:
70-
raise NotImplementedError()
71-
print('Using {} distance, threshold {}: {}.'.format(dis_type, threshold, result))
58+
result = recognizer.match(img1, face1[0][:-1], img2, face2[0][:-1])
59+
print('Result: {}.'.format('same identity' if result else 'different identities'))

0 commit comments

Comments
 (0)