easydetect 0.2.2__tar.gz → 0.3.0__tar.gz
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.
- {easydetect-0.2.2 → easydetect-0.3.0}/PKG-INFO +1 -1
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/__init__.py +1 -1
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/cli.py +2 -2
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/model.py +18 -9
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/predictor.py +64 -4
- {easydetect-0.2.2 → easydetect-0.3.0}/pyproject.toml +1 -1
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/test_predictor.py +66 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tools/hub_README.md +1 -1
- {easydetect-0.2.2 → easydetect-0.3.0}/.gitignore +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/LICENSE +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/NOTICE +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/README.md +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/data/__init__.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/data/augment.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/data/dataset.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/data/labels.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/downloads.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/errors.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/exporter.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/metrics.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/nn/__init__.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/nn/decoder.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/nn/denoising.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/nn/dfine_net.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/nn/encoder.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/nn/hgnetv2.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/nn/ops.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/plotting.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/results.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/sources.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/tracker.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/trainer.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/utils/__init__.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/utils/loss.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/utils/ops.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/easydetect/validator.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/__init__.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/conftest.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/test_augment.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/test_cli.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/test_coco2yolo.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/test_dataset.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/test_downloads.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/test_lab_api.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/test_model.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/test_nn.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/test_onnxruntime.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/test_results.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/test_sources.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/test_tracker.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tests/test_train_export.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tools/bench.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tools/build_mirror.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tools/coco2yolo.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tools/compare_yolo.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tools/convert_dfine.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tools/make_demo.py +0 -0
- {easydetect-0.2.2 → easydetect-0.3.0}/tools/make_toyset.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: easydetect
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Easy real-time object detection you can ship: D-FINE with PyTorch training, OpenVINO inference on CPU/GPU/NPU, and a browser app to label and train. Apache-2.0 throughout.
|
|
5
5
|
Project-URL: Homepage, https://github.com/themakerrobot/easydetect
|
|
6
6
|
Project-URL: Source, https://github.com/themakerrobot/easydetect
|
|
@@ -24,7 +24,7 @@ needs numpy/opencv/openvino/pyyaml; training adds
|
|
|
24
24
|
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
|
|
27
|
-
__version__ = "0.
|
|
27
|
+
__version__ = "0.3.0"
|
|
28
28
|
|
|
29
29
|
from .errors import DownloadError, EasyDetectError, ModelNotFoundError
|
|
30
30
|
from .metrics import BoxMetrics, DetMetrics
|
|
@@ -28,8 +28,8 @@ Modes:
|
|
|
28
28
|
|
|
29
29
|
Common keys:
|
|
30
30
|
model=dfine-n|s|m|l|x|best.pt|model.xml source=bus.jpg|dir|video.mp4|0|url
|
|
31
|
-
data=data.yaml epochs=100 imgsz=640 batch=8 conf=0.
|
|
32
|
-
project=runs name=predict save=true show=false format=openvino half=true
|
|
31
|
+
data=data.yaml epochs=100 imgsz=640 batch=8 conf=0.5 iou=0.7|none device=0|cpu|AUTO
|
|
32
|
+
contain=0.8 project=runs name=predict save=true show=false format=openvino half=true
|
|
33
33
|
|
|
34
34
|
Examples:
|
|
35
35
|
easydetect predict model=dfine-s source=bus.jpg conf=0.5
|
|
@@ -114,12 +114,13 @@ class Detector:
|
|
|
114
114
|
def predict(
|
|
115
115
|
self,
|
|
116
116
|
source: Any = None,
|
|
117
|
-
conf: float = 0.
|
|
117
|
+
conf: float = 0.5,
|
|
118
118
|
imgsz: int | None = None,
|
|
119
119
|
device: str | None = None,
|
|
120
120
|
max_det: int = 300,
|
|
121
121
|
classes: list[int] | None = None,
|
|
122
122
|
iou: float | None = 0.7,
|
|
123
|
+
contain: float | None = None,
|
|
123
124
|
stream: bool = False,
|
|
124
125
|
save: bool = False,
|
|
125
126
|
show: bool = False,
|
|
@@ -133,9 +134,14 @@ class Detector:
|
|
|
133
134
|
"""Run detection on any supported source.
|
|
134
135
|
|
|
135
136
|
Returns a ``list[Results]``, or a generator when ``stream=True`` (the
|
|
136
|
-
only sane option for a long video or a live camera). ``
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
only sane option for a long video or a live camera). ``conf=0.5``
|
|
138
|
+
is where D-FINE's boxes are mostly right (70% on COCO, against 32% at
|
|
139
|
+
YOLO's usual 0.25). ``iou`` drops a box covering a higher-scoring one
|
|
140
|
+
by more than that IoU — the same object found twice, sometimes under
|
|
141
|
+
two classes; ``None`` keeps all. ``contain=0.8`` also merges a box
|
|
142
|
+
with 80% of its area inside another of its class — the visible pieces
|
|
143
|
+
of a half hidden object beside the whole of it — into one box; off by
|
|
144
|
+
default, as on COCO it removes more real objects than it fixes.
|
|
139
145
|
"""
|
|
140
146
|
if source is None:
|
|
141
147
|
raise ValueError("predict() needs a source (image, folder, video, url, camera index)")
|
|
@@ -152,6 +158,7 @@ class Detector:
|
|
|
152
158
|
max_det=max_det,
|
|
153
159
|
classes=classes,
|
|
154
160
|
iou=iou,
|
|
161
|
+
contain=contain,
|
|
155
162
|
save=save,
|
|
156
163
|
show=show,
|
|
157
164
|
project=project,
|
|
@@ -166,8 +173,9 @@ class Detector:
|
|
|
166
173
|
def track(
|
|
167
174
|
self,
|
|
168
175
|
source: Any = None,
|
|
169
|
-
conf: float = 0.
|
|
176
|
+
conf: float = 0.5,
|
|
170
177
|
iou: float | None = 0.7,
|
|
178
|
+
contain: float | None = None,
|
|
171
179
|
match_iou: float = 0.3,
|
|
172
180
|
max_age: int = 30,
|
|
173
181
|
persist: bool = False,
|
|
@@ -176,7 +184,7 @@ class Detector:
|
|
|
176
184
|
) -> Any:
|
|
177
185
|
"""Predict, and keep an id on each box across frames (``boxes.id``).
|
|
178
186
|
|
|
179
|
-
``iou``
|
|
187
|
+
``iou`` and ``contain`` filter duplicates, as in :meth:`predict`. ``match_iou``
|
|
180
188
|
is how much a box must overlap a track in the previous frame to carry
|
|
181
189
|
its id on; ``max_age`` is how many frames a track waits for its object.
|
|
182
190
|
"""
|
|
@@ -185,7 +193,7 @@ class Detector:
|
|
|
185
193
|
if self.tracker is None or not persist:
|
|
186
194
|
self.tracker = IoUTracker(iou=match_iou, max_age=max_age)
|
|
187
195
|
kwargs.setdefault("name", "track")
|
|
188
|
-
gen = self._run(source, conf=conf, iou=iou, tracker=self.tracker,
|
|
196
|
+
gen = self._run(source, conf=conf, iou=iou, contain=contain, tracker=self.tracker,
|
|
189
197
|
**self._track_kwargs(kwargs))
|
|
190
198
|
return _Stream(gen, "track") if stream else list(gen)
|
|
191
199
|
|
|
@@ -203,12 +211,13 @@ class Detector:
|
|
|
203
211
|
def _run(
|
|
204
212
|
self,
|
|
205
213
|
source: Any,
|
|
206
|
-
conf: float = 0.
|
|
214
|
+
conf: float = 0.5,
|
|
207
215
|
imgsz: int | None = None,
|
|
208
216
|
device: str | None = None,
|
|
209
217
|
max_det: int = 300,
|
|
210
218
|
classes: list[int] | None = None,
|
|
211
219
|
iou: float | None = 0.7,
|
|
220
|
+
contain: float | None = None,
|
|
212
221
|
save: bool = False,
|
|
213
222
|
show: bool = False,
|
|
214
223
|
project: str = "runs",
|
|
@@ -230,7 +239,7 @@ class Detector:
|
|
|
230
239
|
try:
|
|
231
240
|
for frame in loader:
|
|
232
241
|
det, speed = predictor(frame.img, conf=conf, max_det=max_det, classes=classes,
|
|
233
|
-
iou=iou)
|
|
242
|
+
iou=iou, contain=contain)
|
|
234
243
|
if tracker is not None:
|
|
235
244
|
det = tracker.update(det)
|
|
236
245
|
result = Results(
|
|
@@ -128,6 +128,12 @@ def read_names(model_path: Path) -> dict[int, str]:
|
|
|
128
128
|
#: Default for ``iou``: above this IoU, two boxes are one object.
|
|
129
129
|
IOU = 0.7
|
|
130
130
|
|
|
131
|
+
#: Default for ``conf``. D-FINE gives an unsure box 0.3-0.5 where YOLO would
|
|
132
|
+
#: give it under 0.25, so YOLO's 0.25 shows mostly mistakes here: dfine-m on
|
|
133
|
+
#: COCO val2017 (NMS 0.7) shows boxes 32% right at 0.25, 70% at 0.5, while
|
|
134
|
+
#: still finding 67% of the objects. See docs/performance.md.
|
|
135
|
+
CONF = 0.5
|
|
136
|
+
|
|
131
137
|
|
|
132
138
|
def drop_duplicates(xyxy: np.ndarray, iou: float = IOU) -> np.ndarray:
|
|
133
139
|
"""Indices of the boxes to keep, from boxes sorted best first.
|
|
@@ -158,6 +164,52 @@ def drop_duplicates(xyxy: np.ndarray, iou: float = IOU) -> np.ndarray:
|
|
|
158
164
|
return np.nonzero(alive)[0]
|
|
159
165
|
|
|
160
166
|
|
|
167
|
+
#: How much less sure an enclosing box may be than the box inside it and still
|
|
168
|
+
#: count as the whole object rather than a loose box around several.
|
|
169
|
+
CONTAIN_MARGIN = 0.1
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def drop_contained(xyxy: np.ndarray, scores: np.ndarray, cls: np.ndarray,
|
|
173
|
+
contain: float) -> np.ndarray:
|
|
174
|
+
"""Indices of the boxes to keep when a box lies inside another of its class.
|
|
175
|
+
|
|
176
|
+
IoU cannot see a part inside a whole: a chair half hidden behind a person
|
|
177
|
+
can come back as the whole chair (0.64) plus its two visible pieces (0.67,
|
|
178
|
+
0.61), and each piece overlaps the whole by an IoU of only its share of the
|
|
179
|
+
area. Measured as the overlap over the *smaller* box instead, a piece sits
|
|
180
|
+
near 1.0, so a pair of one class sharing at least ``contain`` of the
|
|
181
|
+
smaller box is one object, and one box goes:
|
|
182
|
+
|
|
183
|
+
* the inner one, when the enclosing box is about as sure (within
|
|
184
|
+
``CONTAIN_MARGIN``) — the whole object and a piece of it;
|
|
185
|
+
* the enclosing one, when it is much less sure than the box inside — a
|
|
186
|
+
loose box around a group, which must not erase the members.
|
|
187
|
+
|
|
188
|
+
NMS's "keep the best" would not do here: the best box is a piece, and once
|
|
189
|
+
it has removed the whole, the other piece no longer overlaps anything and
|
|
190
|
+
stays — two half chairs. Same class only: a cup on a table is not part of
|
|
191
|
+
the table. The cost is a real object inside another of its class, a child
|
|
192
|
+
held by an adult, so this is off unless asked for.
|
|
193
|
+
"""
|
|
194
|
+
x1, y1, x2, y2 = xyxy.T
|
|
195
|
+
area = np.clip(x2 - x1, 0, None) * np.clip(y2 - y1, 0, None)
|
|
196
|
+
gone = np.zeros(len(xyxy), bool)
|
|
197
|
+
for i in range(len(xyxy)):
|
|
198
|
+
for j in range(i + 1, len(xyxy)):
|
|
199
|
+
if cls[i] != cls[j]:
|
|
200
|
+
continue
|
|
201
|
+
iw = max(0.0, min(x2[i], x2[j]) - max(x1[i], x1[j]))
|
|
202
|
+
ih = max(0.0, min(y2[i], y2[j]) - max(y1[i], y1[j]))
|
|
203
|
+
if iw * ih < contain * max(min(area[i], area[j]), 1e-9):
|
|
204
|
+
continue
|
|
205
|
+
big, small = (i, j) if area[i] >= area[j] else (j, i)
|
|
206
|
+
if scores[big] >= scores[small] - CONTAIN_MARGIN:
|
|
207
|
+
gone[small] = True
|
|
208
|
+
else:
|
|
209
|
+
gone[big] = True
|
|
210
|
+
return np.nonzero(~gone)[0]
|
|
211
|
+
|
|
212
|
+
|
|
161
213
|
def _split_outputs(arrays: list[np.ndarray]) -> tuple[np.ndarray, np.ndarray]:
|
|
162
214
|
"""``(boxes, scores)`` for the one image in a batch, whatever order they come in."""
|
|
163
215
|
boxes = next((a for a in arrays if a.shape[-1] == 4), None)
|
|
@@ -231,16 +283,19 @@ class Predictor:
|
|
|
231
283
|
boxes: np.ndarray,
|
|
232
284
|
scores: np.ndarray,
|
|
233
285
|
orig_shape: tuple[int, int],
|
|
234
|
-
conf: float =
|
|
286
|
+
conf: float = CONF,
|
|
235
287
|
max_det: int = 300,
|
|
236
288
|
classes: list[int] | None = None,
|
|
237
289
|
iou: float | None = IOU,
|
|
290
|
+
contain: float | None = None,
|
|
238
291
|
) -> np.ndarray:
|
|
239
292
|
"""-> ``(N, 6)`` array of ``x1 y1 x2 y2 conf cls`` in pixels.
|
|
240
293
|
|
|
241
294
|
``iou`` drops a box that covers a higher-scoring one by more than that
|
|
242
295
|
IoU, whatever the two classes (``None`` keeps them all): see
|
|
243
|
-
:func:`drop_duplicates`.
|
|
296
|
+
:func:`drop_duplicates`. ``contain`` (off by default) also drops a box
|
|
297
|
+
lying mostly inside another of its class: see
|
|
298
|
+
:func:`drop_contained`.
|
|
244
299
|
"""
|
|
245
300
|
if _looks_like_logits(scores):
|
|
246
301
|
scores = 1.0 / (1.0 + np.exp(-scores))
|
|
@@ -263,6 +318,9 @@ class Predictor:
|
|
|
263
318
|
if iou is not None and len(xyxy) > 1:
|
|
264
319
|
kept = drop_duplicates(xyxy, iou)
|
|
265
320
|
xyxy, best, cls = xyxy[kept], best[kept], cls[kept]
|
|
321
|
+
if contain is not None and len(xyxy) > 1:
|
|
322
|
+
kept = drop_contained(xyxy, best, cls, contain)
|
|
323
|
+
xyxy, best, cls = xyxy[kept], best[kept], cls[kept]
|
|
266
324
|
return np.concatenate(
|
|
267
325
|
[xyxy, best.astype(np.float32)[:, None], cls.astype(np.float32)[:, None]], axis=1
|
|
268
326
|
)
|
|
@@ -270,10 +328,11 @@ class Predictor:
|
|
|
270
328
|
def __call__(
|
|
271
329
|
self,
|
|
272
330
|
img: np.ndarray,
|
|
273
|
-
conf: float =
|
|
331
|
+
conf: float = CONF,
|
|
274
332
|
max_det: int = 300,
|
|
275
333
|
classes: list[int] | None = None,
|
|
276
334
|
iou: float | None = IOU,
|
|
335
|
+
contain: float | None = None,
|
|
277
336
|
) -> tuple[np.ndarray, dict[str, float]]:
|
|
278
337
|
"""Run one image; returns ``(detections (N,6), speed dict in ms)``."""
|
|
279
338
|
import time
|
|
@@ -283,7 +342,8 @@ class Predictor:
|
|
|
283
342
|
t1 = time.perf_counter()
|
|
284
343
|
boxes, scores = self.infer(tensor)
|
|
285
344
|
t2 = time.perf_counter()
|
|
286
|
-
det = self.postprocess(boxes, scores, img.shape[:2], conf, max_det, classes, iou
|
|
345
|
+
det = self.postprocess(boxes, scores, img.shape[:2], conf, max_det, classes, iou,
|
|
346
|
+
contain)
|
|
287
347
|
t3 = time.perf_counter()
|
|
288
348
|
speed = {
|
|
289
349
|
"preprocess": (t1 - t0) * 1e3,
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "easydetect"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Easy real-time object detection you can ship: D-FINE with PyTorch training, OpenVINO inference on CPU/GPU/NPU, and a browser app to label and train. Apache-2.0 throughout."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -147,3 +147,69 @@ def test_one_predictor_can_be_shared_between_threads(tiny_ir):
|
|
|
147
147
|
for thread in threads:
|
|
148
148
|
thread.join()
|
|
149
149
|
assert failures == []
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_a_half_hidden_object_and_its_pieces_become_one_box():
|
|
153
|
+
from easydetect.predictor import drop_contained, drop_duplicates
|
|
154
|
+
|
|
155
|
+
# best first: a piece (0.67), the whole chair (0.64), another piece (0.61),
|
|
156
|
+
# and a second chair beside it that only touches the first
|
|
157
|
+
xyxy = np.array([[320, 520, 372, 640], # lower right piece
|
|
158
|
+
[285, 455, 373, 640], # the whole chair
|
|
159
|
+
[285, 470, 330, 640], # left piece
|
|
160
|
+
[380, 455, 470, 640]], # another chair
|
|
161
|
+
np.float32)
|
|
162
|
+
scores = np.array([0.67, 0.64, 0.61, 0.60])
|
|
163
|
+
cls = np.array([56, 56, 56, 56])
|
|
164
|
+
assert list(drop_duplicates(xyxy, 0.7)) == [0, 1, 2, 3] # IoU sees four chairs
|
|
165
|
+
# the whole chair stays, its pieces go; the chair beside it is left alone
|
|
166
|
+
assert list(drop_contained(xyxy, scores, cls, 0.8)) == [1, 3]
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_a_loose_box_around_a_group_does_not_erase_its_members():
|
|
170
|
+
from easydetect.predictor import drop_contained
|
|
171
|
+
|
|
172
|
+
xyxy = np.array([[0, 0, 50, 100], [50, 0, 100, 100], [0, 0, 100, 100]], np.float32)
|
|
173
|
+
scores = np.array([0.9, 0.85, 0.3]) # two people, and a box around both
|
|
174
|
+
assert list(drop_contained(xyxy, scores, np.zeros(3), 0.8)) == [0, 1]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_containment_never_crosses_classes():
|
|
178
|
+
from easydetect.predictor import drop_contained
|
|
179
|
+
|
|
180
|
+
xyxy = np.array([[0, 0, 100, 100], [10, 10, 40, 40]], np.float32) # a cup on a table
|
|
181
|
+
scores = np.array([0.8, 0.7])
|
|
182
|
+
assert list(drop_contained(xyxy, scores, np.array([60, 41]), 0.8)) == [0, 1]
|
|
183
|
+
assert list(drop_contained(xyxy, scores, np.array([60, 60]), 0.8)) == [0]
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_the_whole_path_merges_pieces_only_when_asked():
|
|
187
|
+
"""postprocess as predict() runs it: model outputs in, boxes out, at 640 x 720."""
|
|
188
|
+
from easydetect.predictor import Predictor
|
|
189
|
+
|
|
190
|
+
pieces = [(320, 520, 372, 640, 0.67), (285, 455, 373, 640, 0.64), (285, 470, 330, 640, 0.61),
|
|
191
|
+
(40, 100, 340, 640, 0.97)] # three chair boxes and a person
|
|
192
|
+
boxes = np.array([[(x1 + x2) / 2 / 640, (y1 + y2) / 2 / 720, (x2 - x1) / 640,
|
|
193
|
+
(y2 - y1) / 720] for x1, y1, x2, y2, _ in pieces], np.float32)
|
|
194
|
+
scores = np.zeros((4, 80), np.float32)
|
|
195
|
+
for i, (*_, s) in enumerate(pieces):
|
|
196
|
+
scores[i, 0 if i == 3 else 56] = s
|
|
197
|
+
post = Predictor.postprocess.__get__(object.__new__(Predictor))
|
|
198
|
+
default = post(boxes, scores, (720, 640), conf=0.5)
|
|
199
|
+
merged = post(boxes, scores, (720, 640), conf=0.5, contain=0.8)
|
|
200
|
+
assert len(default) == 4 # NMS at 0.7 keeps every piece
|
|
201
|
+
assert sorted(merged[:, 5].tolist()) == [0, 56] # one person, one chair
|
|
202
|
+
chair = merged[merged[:, 5] == 56][0]
|
|
203
|
+
assert chair[:4].round().tolist() == [285, 455, 373, 640] # the whole of it
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def test_the_defaults_are_the_measured_ones():
|
|
207
|
+
"""conf 0.5, NMS 0.7, contain off: docs/performance.md, 'Confidence and overlap defaults'."""
|
|
208
|
+
import inspect
|
|
209
|
+
|
|
210
|
+
from easydetect import Detector
|
|
211
|
+
|
|
212
|
+
for method in (Detector.predict, Detector.track):
|
|
213
|
+
params = inspect.signature(method).parameters
|
|
214
|
+
assert params["conf"].default == 0.5 and params["iou"].default == 0.7
|
|
215
|
+
assert params["contain"].default is None
|
|
@@ -29,7 +29,7 @@ pip install easydetect # OpenVINO and ONNX Runtime, no PyTorch
|
|
|
29
29
|
from easydetect import Detector
|
|
30
30
|
|
|
31
31
|
model = Detector("dfine-s") # downloads dfine-s/ from this repo once
|
|
32
|
-
results = model("photo.jpg", conf=0.
|
|
32
|
+
results = model("photo.jpg", conf=0.5)
|
|
33
33
|
results[0].save("result.jpg")
|
|
34
34
|
|
|
35
35
|
for r in model.predict(0, stream=True, show=True): # webcam; q or Esc quits
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|