edgefirst-validator 4.2.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.
- deepview/modelpack/utils/argmax.py +16 -0
- edgefirst/validator/__init__.py +1 -0
- edgefirst/validator/__main__.py +375 -0
- edgefirst/validator/datasets/__init__.py +118 -0
- edgefirst/validator/datasets/cache.py +296 -0
- edgefirst/validator/datasets/core.py +250 -0
- edgefirst/validator/datasets/darknet.py +446 -0
- edgefirst/validator/datasets/database.py +1067 -0
- edgefirst/validator/datasets/instance/__init__.py +4 -0
- edgefirst/validator/datasets/instance/core.py +222 -0
- edgefirst/validator/datasets/instance/detection.py +145 -0
- edgefirst/validator/datasets/instance/multitask.py +80 -0
- edgefirst/validator/datasets/instance/segmentation.py +120 -0
- edgefirst/validator/datasets/utils/fetch.py +682 -0
- edgefirst/validator/datasets/utils/readers.py +425 -0
- edgefirst/validator/datasets/utils/transformations.py +1695 -0
- edgefirst/validator/evaluators/__init__.py +17 -0
- edgefirst/validator/evaluators/callbacks/__init__.py +3 -0
- edgefirst/validator/evaluators/callbacks/core.py +192 -0
- edgefirst/validator/evaluators/callbacks/plots.py +900 -0
- edgefirst/validator/evaluators/callbacks/studio.py +234 -0
- edgefirst/validator/evaluators/core.py +257 -0
- edgefirst/validator/evaluators/detection.py +749 -0
- edgefirst/validator/evaluators/multitask.py +270 -0
- edgefirst/validator/evaluators/parameters/__init__.py +53 -0
- edgefirst/validator/evaluators/parameters/core.py +554 -0
- edgefirst/validator/evaluators/parameters/dataset.py +239 -0
- edgefirst/validator/evaluators/parameters/model.py +338 -0
- edgefirst/validator/evaluators/parameters/validation.py +528 -0
- edgefirst/validator/evaluators/segmentation.py +729 -0
- edgefirst/validator/evaluators/utils/__init__.py +3 -0
- edgefirst/validator/evaluators/utils/classify.py +292 -0
- edgefirst/validator/evaluators/utils/match.py +262 -0
- edgefirst/validator/evaluators/utils/timer.py +132 -0
- edgefirst/validator/metrics/__init__.py +9 -0
- edgefirst/validator/metrics/data/__init__.py +7 -0
- edgefirst/validator/metrics/data/label.py +668 -0
- edgefirst/validator/metrics/data/metrics.py +759 -0
- edgefirst/validator/metrics/data/plots.py +476 -0
- edgefirst/validator/metrics/data/stats.py +507 -0
- edgefirst/validator/metrics/detection.py +595 -0
- edgefirst/validator/metrics/segmentation.py +173 -0
- edgefirst/validator/metrics/utils/math.py +717 -0
- edgefirst/validator/publishers/__init__.py +3 -0
- edgefirst/validator/publishers/console.py +147 -0
- edgefirst/validator/publishers/studio.py +128 -0
- edgefirst/validator/publishers/tensorboard.py +119 -0
- edgefirst/validator/publishers/utils/logger.py +111 -0
- edgefirst/validator/publishers/utils/table.py +403 -0
- edgefirst/validator/runners/__init__.py +8 -0
- edgefirst/validator/runners/core.py +727 -0
- edgefirst/validator/runners/deepviewrt.py +177 -0
- edgefirst/validator/runners/hailo.py +263 -0
- edgefirst/validator/runners/keras.py +150 -0
- edgefirst/validator/runners/kinara.py +265 -0
- edgefirst/validator/runners/offline.py +228 -0
- edgefirst/validator/runners/onnx.py +241 -0
- edgefirst/validator/runners/processing/decode.py +320 -0
- edgefirst/validator/runners/processing/dvapi.py +4192 -0
- edgefirst/validator/runners/processing/nms.py +637 -0
- edgefirst/validator/runners/processing/outputs.py +507 -0
- edgefirst/validator/runners/tensorrt.py +321 -0
- edgefirst/validator/runners/tflite.py +221 -0
- edgefirst/validator/validate.py +843 -0
- edgefirst/validator/visualize/__init__.py +3 -0
- edgefirst/validator/visualize/detection.py +623 -0
- edgefirst/validator/visualize/segmentation.py +281 -0
- edgefirst/validator/visualize/utils/plots.py +635 -0
- edgefirst_validator-4.2.1.dist-info/METADATA +111 -0
- edgefirst_validator-4.2.1.dist-info/RECORD +73 -0
- edgefirst_validator-4.2.1.dist-info/WHEEL +5 -0
- edgefirst_validator-4.2.1.dist-info/entry_points.txt +2 -0
- edgefirst_validator-4.2.1.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def nms(
|
|
7
|
+
boxes: np.ndarray,
|
|
8
|
+
scores: np.ndarray,
|
|
9
|
+
masks: np.ndarray = None,
|
|
10
|
+
iou_threshold: float = 0.70,
|
|
11
|
+
score_threshold: float = 0.001,
|
|
12
|
+
max_detections: int = 300,
|
|
13
|
+
class_agnostic: bool = False,
|
|
14
|
+
clip_boxes: bool = False,
|
|
15
|
+
nms_type: str = "tensorflow"
|
|
16
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
17
|
+
"""
|
|
18
|
+
Deploy the NMS algorithm on object detection outputs. The NMS algorithm
|
|
19
|
+
can be specified using "tensorflow" by default. Otherwise, "numpy"
|
|
20
|
+
or "torch" are possible options.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
boxes: np.ndarray
|
|
25
|
+
Normalized input boxes to the NMS with shape (n, 4)
|
|
26
|
+
in [xmin, ymin, xmax, ymax] format.
|
|
27
|
+
scores: np.ndarray
|
|
28
|
+
Input scores to the NMS (n, ).
|
|
29
|
+
masks: np.ndarray
|
|
30
|
+
(Optional) Instance segmentation masks to also
|
|
31
|
+
to also be filtered during NMS.
|
|
32
|
+
iou_threshold: float
|
|
33
|
+
This is the IoU threshold for the NMS. Higher values
|
|
34
|
+
are less strict in filtering overlapping detections.
|
|
35
|
+
score_threshold: float
|
|
36
|
+
The confidence score threshold for the NMS. Filters to accept
|
|
37
|
+
more confident detections based on this threshold.
|
|
38
|
+
max_detections: int
|
|
39
|
+
The maximum number of boxes to be selected by NMS per class.
|
|
40
|
+
class_agnostic: bool
|
|
41
|
+
Run class-agnostic NMS. Default includes class.
|
|
42
|
+
clip_boxes: bool
|
|
43
|
+
If set to True, boxes will be clipped between 0 and 1. If False,
|
|
44
|
+
the coordinates are kept as it is.
|
|
45
|
+
nms_type: str
|
|
46
|
+
By default the Tensorflow NMS algorithm is deployed by
|
|
47
|
+
specifying "tensorflow" in this parameter. Otherwise, "numpy"
|
|
48
|
+
or "torch" are possible options.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
boxes : np.ndarray
|
|
53
|
+
This contains only the valid bounding boxes post NMS.
|
|
54
|
+
classes : np.ndarray
|
|
55
|
+
This contains only the valid classes post NMS.
|
|
56
|
+
scores : np.ndarray
|
|
57
|
+
This contains only the valid scores post NMS.
|
|
58
|
+
masks: np.ndarray
|
|
59
|
+
This contains only the valid instance segmentation masks
|
|
60
|
+
post NMS if it exists. Otherwise, None is returned.
|
|
61
|
+
|
|
62
|
+
Raises
|
|
63
|
+
------
|
|
64
|
+
ImportError
|
|
65
|
+
Depending on the type of NMS specified, the TensorFlow or
|
|
66
|
+
PyTorch libraries are needed to use the NMS.
|
|
67
|
+
"""
|
|
68
|
+
if nms_type == "tensorflow":
|
|
69
|
+
return tensorflow_combined_nms(
|
|
70
|
+
boxes=boxes,
|
|
71
|
+
scores=scores,
|
|
72
|
+
masks=masks,
|
|
73
|
+
iou_threshold=iou_threshold,
|
|
74
|
+
score_threshold=score_threshold,
|
|
75
|
+
max_detections=max_detections,
|
|
76
|
+
class_agnostic=class_agnostic,
|
|
77
|
+
clip_boxes=clip_boxes
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
# Reshape boxes and scores and compute classes.
|
|
81
|
+
scores = np.reshape(scores, (boxes.shape[0], -1))
|
|
82
|
+
boxes = np.reshape(boxes, (-1, 4))
|
|
83
|
+
classes = np.argmax(scores, axis=1).astype(np.int32)
|
|
84
|
+
|
|
85
|
+
# Prefilter boxes and scores by minimum score
|
|
86
|
+
max_scores = np.max(scores, axis=1)
|
|
87
|
+
mask = max_scores >= score_threshold
|
|
88
|
+
|
|
89
|
+
# Prefilter the boxes, scores and classes IDs.
|
|
90
|
+
scores = max_scores[mask]
|
|
91
|
+
boxes = boxes[mask]
|
|
92
|
+
classes = classes[mask]
|
|
93
|
+
if masks is not None:
|
|
94
|
+
masks = masks[mask]
|
|
95
|
+
|
|
96
|
+
if nms_type == "torch":
|
|
97
|
+
keep = torch_nms(
|
|
98
|
+
boxes=boxes,
|
|
99
|
+
scores=scores,
|
|
100
|
+
iou_threshold=iou_threshold,
|
|
101
|
+
max_detections=max_detections
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
keep = numpy_nms(
|
|
105
|
+
boxes=boxes,
|
|
106
|
+
scores=scores,
|
|
107
|
+
iou_threshold=iou_threshold,
|
|
108
|
+
max_detections=max_detections
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Filter boxes, scores, and classes.
|
|
112
|
+
if len(keep):
|
|
113
|
+
boxes = np.reshape(boxes[keep], (-1, 4))
|
|
114
|
+
scores = np.reshape(scores[keep], (boxes.shape[0],))
|
|
115
|
+
classes = np.reshape(classes[keep], (boxes.shape[0],))
|
|
116
|
+
if masks is not None:
|
|
117
|
+
masks = masks[keep]
|
|
118
|
+
|
|
119
|
+
return boxes, classes, scores, masks
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def tensorflow_combined_nms(
|
|
123
|
+
boxes: np.ndarray,
|
|
124
|
+
scores: np.ndarray,
|
|
125
|
+
masks: np.ndarray = None,
|
|
126
|
+
iou_threshold: float = 0.001,
|
|
127
|
+
score_threshold: float = 0.70,
|
|
128
|
+
max_detections: int = 300,
|
|
129
|
+
class_agnostic: bool = False,
|
|
130
|
+
clip_boxes: bool = False
|
|
131
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
132
|
+
"""
|
|
133
|
+
Return output of the TensorFlow NMS.
|
|
134
|
+
https://www.tensorflow.org/api_docs/python/tf/image/combined_non_max_suppression
|
|
135
|
+
By default, class-aware NMS is deployed. However, class-agnostic NMS
|
|
136
|
+
can be specified.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
boxes: np.ndarray
|
|
141
|
+
Normalized input boxes to the NMS with shape (n, 4)
|
|
142
|
+
in [xmin, ymin, xmax, ymax] format.
|
|
143
|
+
scores: np.ndarray
|
|
144
|
+
Input scores to the NMS (n, ).
|
|
145
|
+
masks: np.ndarray
|
|
146
|
+
(Optional) Instance segmentation masks to also
|
|
147
|
+
to also be filtered during NMS.
|
|
148
|
+
iou_threshold: float
|
|
149
|
+
This is the IoU threshold for the NMS. Higher values
|
|
150
|
+
are less strict in filtering overlapping detections.
|
|
151
|
+
score_threshold: float
|
|
152
|
+
The confidence score threshold for the NMS. Filters to accept
|
|
153
|
+
more confident detections based on this threshold.
|
|
154
|
+
max_detections: int
|
|
155
|
+
The maximum number of boxes to be selected by NMS per class.
|
|
156
|
+
class_agnostic: bool
|
|
157
|
+
Run class-agnostic NMS. Default includes class.
|
|
158
|
+
clip_boxes: bool
|
|
159
|
+
If set to True, boxes will be clipped between 0 and 1. If False,
|
|
160
|
+
the coordinates are kept as it is.
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
boxes : np.ndarray
|
|
165
|
+
This contains only the valid bounding boxes post NMS.
|
|
166
|
+
classes : np.ndarray
|
|
167
|
+
This contains only the valid classes post NMS.
|
|
168
|
+
scores : np.ndarray
|
|
169
|
+
This contains only the valid scores post NMS.
|
|
170
|
+
masks: np.ndarray
|
|
171
|
+
This contains only the valid instance segmentation masks
|
|
172
|
+
post NMS if it exists. Otherwise, None is returned.
|
|
173
|
+
|
|
174
|
+
Raises
|
|
175
|
+
------
|
|
176
|
+
ImportError
|
|
177
|
+
Raised if TensorFlow is not installed in the system
|
|
178
|
+
which is needed to run the NMS.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
import tensorflow as tf # type: ignore
|
|
183
|
+
except ImportError:
|
|
184
|
+
raise ImportError("TensorFlow is needed to use `tensorflow_nms`.")
|
|
185
|
+
|
|
186
|
+
if class_agnostic:
|
|
187
|
+
# Sort boxes by score.
|
|
188
|
+
boxes = tf.reshape(boxes, [-1, 4])
|
|
189
|
+
N = boxes.shape[0]
|
|
190
|
+
scores = tf.reshape(scores, [N, -1])
|
|
191
|
+
nms_scores = tf.reduce_max(scores, axis=1)
|
|
192
|
+
classes = tf.argmax(scores, axis=1)
|
|
193
|
+
|
|
194
|
+
keep = tensorflow_nms(
|
|
195
|
+
boxes=boxes,
|
|
196
|
+
scores=nms_scores,
|
|
197
|
+
iou_threshold=iou_threshold,
|
|
198
|
+
score_threshold=score_threshold,
|
|
199
|
+
max_detections=max_detections
|
|
200
|
+
)
|
|
201
|
+
boxes = tf.gather(boxes, keep).numpy()
|
|
202
|
+
scores = tf.gather(nms_scores, keep).numpy()
|
|
203
|
+
classes = tf.gather(classes, keep).numpy().astype(np.int32)
|
|
204
|
+
if masks is not None:
|
|
205
|
+
masks = tf.gather(masks, keep).numpy()
|
|
206
|
+
|
|
207
|
+
else:
|
|
208
|
+
N = boxes.shape[0]
|
|
209
|
+
boxes = np.reshape(boxes, (1, N, 1, 4))
|
|
210
|
+
scores = np.expand_dims(scores, 0)
|
|
211
|
+
|
|
212
|
+
# Get maximum class score per anchor.
|
|
213
|
+
per_anchor_scores = tf.reduce_max(scores, axis=-1) # shape (1, 8400)
|
|
214
|
+
per_anchor_scores = per_anchor_scores[0].numpy()
|
|
215
|
+
|
|
216
|
+
boxes, scores, classes, valid_boxes = \
|
|
217
|
+
tf.image.combined_non_max_suppression(
|
|
218
|
+
boxes=boxes,
|
|
219
|
+
scores=scores,
|
|
220
|
+
max_output_size_per_class=max_detections,
|
|
221
|
+
max_total_size=max_detections,
|
|
222
|
+
iou_threshold=iou_threshold,
|
|
223
|
+
score_threshold=score_threshold,
|
|
224
|
+
clip_boxes=clip_boxes
|
|
225
|
+
)
|
|
226
|
+
valid_boxes = valid_boxes.numpy()[0]
|
|
227
|
+
boxes = boxes.numpy()[0]
|
|
228
|
+
classes = classes.numpy()[0]
|
|
229
|
+
scores = scores.numpy()[0]
|
|
230
|
+
|
|
231
|
+
boxes = boxes[:valid_boxes]
|
|
232
|
+
scores = scores[:valid_boxes]
|
|
233
|
+
classes = classes[:valid_boxes].astype(np.int32)
|
|
234
|
+
|
|
235
|
+
if masks is not None:
|
|
236
|
+
# Keep top-k scores (e.g., the top 100 used in NMS).
|
|
237
|
+
sorted_indices = np.argsort(per_anchor_scores)[::-1]
|
|
238
|
+
top_indices = sorted_indices[:valid_boxes]
|
|
239
|
+
masks = masks[top_indices]
|
|
240
|
+
|
|
241
|
+
return boxes, classes, scores, masks
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def tensorflow_nms(
|
|
245
|
+
boxes: np.ndarray,
|
|
246
|
+
scores: np.ndarray,
|
|
247
|
+
iou_threshold: float = 0.70,
|
|
248
|
+
score_threshold: float = 0.001,
|
|
249
|
+
max_detections: int = 300
|
|
250
|
+
):
|
|
251
|
+
"""
|
|
252
|
+
Return output from single class TensorFlow NMS.
|
|
253
|
+
https://www.tensorflow.org/api_docs/python/tf/image/non_max_suppression
|
|
254
|
+
|
|
255
|
+
Parameters
|
|
256
|
+
----------
|
|
257
|
+
boxes: np.ndarray
|
|
258
|
+
Normalized input boxes to the NMS with shape (n, 4)
|
|
259
|
+
in [xmin, ymin, xmax, ymax] format.
|
|
260
|
+
scores: np.ndarray
|
|
261
|
+
Input scores to the NMS (n, ).
|
|
262
|
+
iou_threshold: float
|
|
263
|
+
This is the IoU threshold for the NMS. Higher values
|
|
264
|
+
are less strict in filtering overlapping detections.
|
|
265
|
+
score_threshold: float
|
|
266
|
+
The confidence score threshold for the NMS. Filters to accept
|
|
267
|
+
more confident detections based on this threshold.
|
|
268
|
+
max_detections: int
|
|
269
|
+
The maximum number of boxes to be selected by NMS per class.
|
|
270
|
+
|
|
271
|
+
Returns
|
|
272
|
+
-------
|
|
273
|
+
Tensor
|
|
274
|
+
This contains the indices of the boxes to keep.
|
|
275
|
+
|
|
276
|
+
Raises
|
|
277
|
+
------
|
|
278
|
+
ImportError
|
|
279
|
+
Raised if TensorFlow is not installed in the system
|
|
280
|
+
which is needed to run the NMS.
|
|
281
|
+
"""
|
|
282
|
+
try:
|
|
283
|
+
import tensorflow as tf # type: ignore
|
|
284
|
+
except ImportError:
|
|
285
|
+
raise ImportError("TensorFlow is needed to use `tensorflow_nms`.")
|
|
286
|
+
|
|
287
|
+
return tf.image.non_max_suppression(
|
|
288
|
+
boxes=boxes,
|
|
289
|
+
scores=scores,
|
|
290
|
+
max_output_size=max_detections,
|
|
291
|
+
iou_threshold=iou_threshold,
|
|
292
|
+
score_threshold=score_threshold
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def torch_nms(
|
|
297
|
+
boxes: np.ndarray,
|
|
298
|
+
scores: np.ndarray,
|
|
299
|
+
iou_threshold: float,
|
|
300
|
+
max_detections: int = 300
|
|
301
|
+
):
|
|
302
|
+
"""
|
|
303
|
+
Return output from single class torchvision NMS.
|
|
304
|
+
https://docs.pytorch.org/vision/0.9/ops.html#torchvision.ops.nms
|
|
305
|
+
|
|
306
|
+
Parameters
|
|
307
|
+
----------
|
|
308
|
+
boxes: np.ndarray
|
|
309
|
+
Normalized input boxes to the NMS with shape (n, 4)
|
|
310
|
+
in [xmin, ymin, xmax, ymax] format.
|
|
311
|
+
scores: np.ndarray
|
|
312
|
+
Input scores to the NMS (n, ).
|
|
313
|
+
iou_threshold: float
|
|
314
|
+
This is the IoU threshold for the NMS. Higher values
|
|
315
|
+
are less strict in filtering overlapping detections.
|
|
316
|
+
max_detections: int
|
|
317
|
+
The maximum number of boxes to be selected by NMS per class.
|
|
318
|
+
|
|
319
|
+
Returns
|
|
320
|
+
-------
|
|
321
|
+
torch.Tensor
|
|
322
|
+
This contains the indices of the boxes to keep.
|
|
323
|
+
|
|
324
|
+
Raises
|
|
325
|
+
------
|
|
326
|
+
ImportError
|
|
327
|
+
Raised if PyTorch NMS is not installed.
|
|
328
|
+
"""
|
|
329
|
+
try:
|
|
330
|
+
import torch # type: ignore
|
|
331
|
+
import torchvision # type: ignore
|
|
332
|
+
except ImportError:
|
|
333
|
+
raise ImportError(
|
|
334
|
+
"Torch and Torchvision is needed to use `torch_nms`.")
|
|
335
|
+
|
|
336
|
+
i = torchvision.ops.nms(torch.tensor(
|
|
337
|
+
boxes), torch.tensor(scores), iou_threshold)
|
|
338
|
+
|
|
339
|
+
if i.shape[0] > max_detections: # limit detections
|
|
340
|
+
i = i[:max_detections] # This limits detections.
|
|
341
|
+
return i
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def numpy_nms(
|
|
345
|
+
boxes: np.ndarray,
|
|
346
|
+
scores: np.ndarray,
|
|
347
|
+
iou_threshold: float = 0.70,
|
|
348
|
+
max_detections: int = 300,
|
|
349
|
+
eps: float = 1e-7
|
|
350
|
+
) -> np.ndarray:
|
|
351
|
+
"""
|
|
352
|
+
Single class NMS implemented in NumPy.
|
|
353
|
+
Method taken from:: https://github.com/Megvii-BaseDetection/YOLOX/blob/main/yolox/utils/demo_utils.py#L57
|
|
354
|
+
Original source from:: https://github.com/amusi/Non-Maximum-Suppression/blob/master/nms.py
|
|
355
|
+
|
|
356
|
+
Parameters
|
|
357
|
+
----------
|
|
358
|
+
boxes: np.ndarray
|
|
359
|
+
Normalized input boxes to the NMS with shape (n, 4)
|
|
360
|
+
in [xmin, ymin, xmax, ymax] format.
|
|
361
|
+
scores: np.ndarray
|
|
362
|
+
Input scores to the NMS (n, ).
|
|
363
|
+
iou_threshold: float
|
|
364
|
+
This is the IoU threshold for the NMS. Higher values
|
|
365
|
+
are less strict in filtering overlapping detections.
|
|
366
|
+
max_detections: int
|
|
367
|
+
The maximum number of boxes to be selected by NMS per class.
|
|
368
|
+
eps: float
|
|
369
|
+
Scalar to avoid division by zeros.
|
|
370
|
+
|
|
371
|
+
Returns
|
|
372
|
+
-------
|
|
373
|
+
np.ndarray
|
|
374
|
+
This contains the indices of the boxes to keep.
|
|
375
|
+
"""
|
|
376
|
+
if len(boxes) == 0:
|
|
377
|
+
return np.array([], dtype=np.int32)
|
|
378
|
+
|
|
379
|
+
x1 = boxes[:, 0]
|
|
380
|
+
y1 = boxes[:, 1]
|
|
381
|
+
x2 = boxes[:, 2]
|
|
382
|
+
y2 = boxes[:, 3]
|
|
383
|
+
|
|
384
|
+
# Calculate areas (remove the +1 for normalized coordinates)
|
|
385
|
+
areas = (x2 - x1) * (y2 - y1)
|
|
386
|
+
|
|
387
|
+
# Sort by scores in descending order
|
|
388
|
+
order = scores.argsort()[::-1]
|
|
389
|
+
|
|
390
|
+
keep = []
|
|
391
|
+
while order.size > 0:
|
|
392
|
+
i = order[0]
|
|
393
|
+
keep.append(i)
|
|
394
|
+
|
|
395
|
+
if len(keep) >= max_detections:
|
|
396
|
+
break
|
|
397
|
+
|
|
398
|
+
# Calculate intersection coordinates
|
|
399
|
+
xx1 = np.maximum(x1[i], x1[order[1:]])
|
|
400
|
+
yy1 = np.maximum(y1[i], y1[order[1:]])
|
|
401
|
+
xx2 = np.minimum(x2[i], x2[order[1:]])
|
|
402
|
+
yy2 = np.minimum(y2[i], y2[order[1:]])
|
|
403
|
+
|
|
404
|
+
# Calculate intersection area (remove +1 for normalized coords)
|
|
405
|
+
w = np.maximum(0.0, xx2 - xx1)
|
|
406
|
+
h = np.maximum(0.0, yy2 - yy1)
|
|
407
|
+
inter = w * h
|
|
408
|
+
|
|
409
|
+
# Calculate IoU
|
|
410
|
+
union = areas[i] + areas[order[1:]] - inter
|
|
411
|
+
iou = inter / (union + eps)
|
|
412
|
+
|
|
413
|
+
# Keep boxes with IoU less than threshold
|
|
414
|
+
inds = np.where(iou <= iou_threshold)[0]
|
|
415
|
+
order = order[inds + 1]
|
|
416
|
+
|
|
417
|
+
return np.array(keep, dtype=np.int32)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def multiclass_nms_class_aware(
|
|
421
|
+
boxes: np.ndarray,
|
|
422
|
+
scores: np.ndarray,
|
|
423
|
+
iou_threshold: float = 0.45,
|
|
424
|
+
score_threshold: float = 0.10,
|
|
425
|
+
max_detections: int = 300,
|
|
426
|
+
nms_type: str = "numpy"
|
|
427
|
+
) -> np.ndarray:
|
|
428
|
+
"""
|
|
429
|
+
This is the YOLOx Multiclass NMS implemented in NumPy. Class-aware version.
|
|
430
|
+
Source:: https://github.com/Megvii-BaseDetection/YOLOX/blob/main/yolox/utils/demo_utils.py#L96
|
|
431
|
+
|
|
432
|
+
Parameters
|
|
433
|
+
----------
|
|
434
|
+
boxes: np.ndarray
|
|
435
|
+
Normalized input boxes to the NMS with shape (n, 4)
|
|
436
|
+
in [xmin, ymin, xmax, ymax] format.
|
|
437
|
+
scores: np.ndarray
|
|
438
|
+
Input scores to the NMS (n, ).
|
|
439
|
+
iou_threshold: float
|
|
440
|
+
This is the IoU threshold for the NMS. Higher values
|
|
441
|
+
are less strict in filtering overlapping detections.
|
|
442
|
+
score_threshold: float
|
|
443
|
+
The confidence score threshold for the NMS. Filters to accept
|
|
444
|
+
more confident detections based on this threshold.
|
|
445
|
+
max_detections: int
|
|
446
|
+
The maximum number of boxes to be selected by NMS per class.
|
|
447
|
+
nms_type: str
|
|
448
|
+
Specify the type of NMS algorithm. By default, using a simple case
|
|
449
|
+
using NumPy. Otherwise, other options include 'tensorflow' and 'torch'.
|
|
450
|
+
|
|
451
|
+
Returns
|
|
452
|
+
-------
|
|
453
|
+
np.ndarray
|
|
454
|
+
Post-NMS detections (number of detections, 6) which contains
|
|
455
|
+
(xyxy, score, class) a total of 6 columns.
|
|
456
|
+
"""
|
|
457
|
+
final_dets = []
|
|
458
|
+
num_classes = scores.shape[1]
|
|
459
|
+
for cls_ind in range(num_classes):
|
|
460
|
+
cls_scores = scores[:, cls_ind]
|
|
461
|
+
valid_score_mask = cls_scores > score_threshold
|
|
462
|
+
if valid_score_mask.sum() == 0:
|
|
463
|
+
continue
|
|
464
|
+
else:
|
|
465
|
+
valid_scores = cls_scores[valid_score_mask]
|
|
466
|
+
valid_boxes = boxes[valid_score_mask]
|
|
467
|
+
|
|
468
|
+
if nms_type == "numpy":
|
|
469
|
+
keep = numpy_nms(
|
|
470
|
+
boxes=valid_boxes,
|
|
471
|
+
scores=valid_scores,
|
|
472
|
+
iou_threshold=iou_threshold,
|
|
473
|
+
max_detections=max_detections
|
|
474
|
+
)
|
|
475
|
+
elif nms_type == "tensorflow":
|
|
476
|
+
keep = tensorflow_nms(
|
|
477
|
+
boxes=valid_boxes,
|
|
478
|
+
scores=valid_scores,
|
|
479
|
+
iou_threshold=iou_threshold,
|
|
480
|
+
score_threshold=score_threshold,
|
|
481
|
+
max_detections=max_detections
|
|
482
|
+
)
|
|
483
|
+
elif nms_type == "torch":
|
|
484
|
+
keep = torch_nms(
|
|
485
|
+
boxes=valid_boxes,
|
|
486
|
+
scores=valid_scores,
|
|
487
|
+
iou_threshold=iou_threshold,
|
|
488
|
+
max_detections=max_detections
|
|
489
|
+
)
|
|
490
|
+
else:
|
|
491
|
+
raise TypeError(
|
|
492
|
+
"Unrecognized NMS type '{}' provided.".format(nms_type))
|
|
493
|
+
|
|
494
|
+
if len(keep) > 0:
|
|
495
|
+
cls_inds = np.ones((len(keep), 1)) * cls_ind
|
|
496
|
+
dets = np.concatenate(
|
|
497
|
+
[valid_boxes[keep], valid_scores[keep, None], cls_inds], 1
|
|
498
|
+
)
|
|
499
|
+
final_dets.append(dets)
|
|
500
|
+
if len(final_dets) == 0:
|
|
501
|
+
return None
|
|
502
|
+
return np.concatenate(final_dets, 0)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def multiclass_nms_class_agnostic(
|
|
506
|
+
boxes: np.ndarray,
|
|
507
|
+
scores: np.ndarray,
|
|
508
|
+
iou_threshold: float = 0.45,
|
|
509
|
+
score_threshold: float = 0.10,
|
|
510
|
+
max_detections: int = 300,
|
|
511
|
+
nms_type: str = "numpy"
|
|
512
|
+
) -> np.ndarray:
|
|
513
|
+
"""
|
|
514
|
+
This is the YOLOx Multiclass NMS implemented in NumpPy. Class-agnostic version.
|
|
515
|
+
Source:: https://github.com/Megvii-BaseDetection/YOLOX/blob/main/yolox/utils/demo_utils.py#L120.
|
|
516
|
+
|
|
517
|
+
Parameters
|
|
518
|
+
----------
|
|
519
|
+
boxes: np.ndarray
|
|
520
|
+
Normalized input boxes to the NMS with shape (n, 4)
|
|
521
|
+
in [xmin, ymin, xmax, ymax] format.
|
|
522
|
+
scores: np.ndarray
|
|
523
|
+
Input scores to the NMS (n, ).
|
|
524
|
+
iou_threshold: float
|
|
525
|
+
This is the IoU threshold for the NMS. Higher values
|
|
526
|
+
are less strict in filtering overlapping detections.
|
|
527
|
+
score_threshold: float
|
|
528
|
+
The confidence score threshold for the NMS. Filters to accept
|
|
529
|
+
more confident detections based on this threshold.
|
|
530
|
+
max_detections: int
|
|
531
|
+
The maximum number of boxes to be selected by NMS per class.
|
|
532
|
+
nms_type: str
|
|
533
|
+
Specify the type of NMS algorithm. By default, using a simple case
|
|
534
|
+
using NumPy. Otherwise, other options include 'tensorflow' and 'torch'.
|
|
535
|
+
|
|
536
|
+
Returns
|
|
537
|
+
-------
|
|
538
|
+
np.ndarray
|
|
539
|
+
Post-NMS detections (number of detections, 6) which contains
|
|
540
|
+
(xyxy, score, class) a total of 6 columns.
|
|
541
|
+
"""
|
|
542
|
+
cls_inds = scores.argmax(1)
|
|
543
|
+
cls_scores = scores[np.arange(len(cls_inds)), cls_inds]
|
|
544
|
+
|
|
545
|
+
valid_score_mask = cls_scores > score_threshold
|
|
546
|
+
if valid_score_mask.sum() == 0:
|
|
547
|
+
return None
|
|
548
|
+
valid_scores = cls_scores[valid_score_mask]
|
|
549
|
+
valid_boxes = boxes[valid_score_mask]
|
|
550
|
+
valid_cls_inds = cls_inds[valid_score_mask]
|
|
551
|
+
|
|
552
|
+
if nms_type == "numpy":
|
|
553
|
+
keep = numpy_nms(
|
|
554
|
+
boxes=valid_boxes,
|
|
555
|
+
scores=valid_scores,
|
|
556
|
+
iou_threshold=iou_threshold,
|
|
557
|
+
max_detections=max_detections
|
|
558
|
+
)
|
|
559
|
+
elif nms_type == "tensorflow":
|
|
560
|
+
keep = tensorflow_nms(
|
|
561
|
+
boxes=valid_boxes,
|
|
562
|
+
scores=valid_scores,
|
|
563
|
+
iou_threshold=iou_threshold,
|
|
564
|
+
score_threshold=score_threshold,
|
|
565
|
+
max_detections=max_detections
|
|
566
|
+
)
|
|
567
|
+
elif nms_type == "torch":
|
|
568
|
+
keep = torch_nms(
|
|
569
|
+
boxes=valid_boxes,
|
|
570
|
+
scores=valid_scores,
|
|
571
|
+
iou_threshold=iou_threshold,
|
|
572
|
+
max_detections=max_detections
|
|
573
|
+
)
|
|
574
|
+
else:
|
|
575
|
+
raise TypeError(
|
|
576
|
+
"Unrecognized NMS type '{}' provided.".format(nms_type))
|
|
577
|
+
|
|
578
|
+
if len(keep) > 0:
|
|
579
|
+
dets = np.concatenate(
|
|
580
|
+
[valid_boxes[keep], valid_scores[keep, None],
|
|
581
|
+
valid_cls_inds[keep, None]], 1
|
|
582
|
+
)
|
|
583
|
+
return dets
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def multiclass_nms(
|
|
587
|
+
boxes: np.ndarray,
|
|
588
|
+
scores: np.ndarray,
|
|
589
|
+
iou_threshold: float = 0.45,
|
|
590
|
+
score_threshold: float = 0.10,
|
|
591
|
+
max_detections: int = 300,
|
|
592
|
+
class_agnostic: bool = True,
|
|
593
|
+
nms_type: str = "numpy"
|
|
594
|
+
) -> np.ndarray:
|
|
595
|
+
"""
|
|
596
|
+
This is the YOLOx Multiclass NMS implemented in NumPy.
|
|
597
|
+
Source:: https://github.com/Megvii-BaseDetection/YOLOX/blob/main/yolox/utils/demo_utils.py#L87
|
|
598
|
+
|
|
599
|
+
Parameters
|
|
600
|
+
----------
|
|
601
|
+
boxes: np.ndarray
|
|
602
|
+
Normalized input boxes to the NMS with shape (n, 4)
|
|
603
|
+
in [xmin, ymin, xmax, ymax] format.
|
|
604
|
+
scores: np.ndarray
|
|
605
|
+
Input scores to the NMS (n, ).
|
|
606
|
+
iou_threshold: float
|
|
607
|
+
This is the IoU threshold for the NMS. Higher values
|
|
608
|
+
are less strict in filtering overlapping detections.
|
|
609
|
+
score_threshold: float
|
|
610
|
+
The confidence score threshold for the NMS. Filters to accept
|
|
611
|
+
more confident detections based on this threshold.
|
|
612
|
+
max_detections: int
|
|
613
|
+
The maximum number of boxes to be selected by NMS per class.
|
|
614
|
+
class_agnostic: bool
|
|
615
|
+
Run class-agnostic NMS. Default includes class.
|
|
616
|
+
nms_type: str
|
|
617
|
+
Specify the type of NMS algorithm. By default, using a simple case
|
|
618
|
+
using NumPy. Otherwise, other options include 'tensorflow' and 'torch'.
|
|
619
|
+
|
|
620
|
+
Returns
|
|
621
|
+
-------
|
|
622
|
+
np.ndarray
|
|
623
|
+
Post-NMS detections (number of detections, 6) which contains
|
|
624
|
+
(xyxy, score, class) a total of 6 columns.
|
|
625
|
+
"""
|
|
626
|
+
if class_agnostic:
|
|
627
|
+
nms_method = multiclass_nms_class_agnostic
|
|
628
|
+
else:
|
|
629
|
+
nms_method = multiclass_nms_class_aware
|
|
630
|
+
return nms_method(
|
|
631
|
+
boxes=boxes,
|
|
632
|
+
scores=scores,
|
|
633
|
+
iou_threshold=iou_threshold,
|
|
634
|
+
score_threshold=score_threshold,
|
|
635
|
+
max_detections=max_detections,
|
|
636
|
+
nms_type=nms_type
|
|
637
|
+
)
|