easydetect 0.7.2__tar.gz → 0.7.4__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.7.2 → easydetect-0.7.4}/PKG-INFO +1 -1
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/__init__.py +1 -1
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/benchmark.py +3 -1
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/data/dataset.py +3 -1
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/data/keypoints.py +4 -2
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/exporter.py +4 -4
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/model.py +21 -2
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/results.py +3 -2
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/seg_trainer.py +3 -2
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/sources.py +4 -2
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/trainer.py +4 -0
- easydetect-0.7.4/easydetect/utils/imageio.py +64 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/pyproject.toml +1 -1
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_robustness.py +34 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_sources_threads.py +4 -2
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_train_export.py +17 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/bench_finetune.py +9 -3
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/profile_train.py +9 -1
- {easydetect-0.7.2 → easydetect-0.7.4}/.gitignore +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/LICENSE +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/NOTICE +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/README.md +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/cli.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/data/__init__.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/data/augment.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/data/labels.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/downloads.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/errors.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/metrics.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/nn/__init__.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/nn/decoder.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/nn/denoising.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/nn/dfine_net.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/nn/encoder.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/nn/hgnetv2.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/nn/ops.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/nn/posenet.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/nn/sam_decoder.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/plotting.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/pose.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/pose_trainer.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/predictor.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/segment.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/serve.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/tracker.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/utils/__init__.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/utils/checkpoint.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/utils/loss.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/utils/ops.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/easydetect/validator.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/__init__.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/conftest.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/pose_toy.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_augment.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_benchmark.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_cli.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_coco2yolo.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_custom_pose.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_custom_seg.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_dataset.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_downloads.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_lab_api.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_model.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_nn.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_onnxruntime.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_plotting.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_pose.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_predictor.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_results.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_results_summary.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_segment.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_segment_lowres.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_serve.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_sources.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tests/test_tracker.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/bench.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/build_mirror.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/coco2yolo.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/compare_yolo.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/convert_dfine.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/convert_sam.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/eval_exports.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/hub_README.md +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/make_demo.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/make_toyset.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/pi_kit.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/train_pose.py +0 -0
- {easydetect-0.7.2 → easydetect-0.7.4}/tools/voc2yolo.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: easydetect
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.4
|
|
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.7.
|
|
27
|
+
__version__ = "0.7.4"
|
|
28
28
|
|
|
29
29
|
from .errors import DownloadError, EasyDetectError, ModelNotFoundError
|
|
30
30
|
from .metrics import BoxMetrics, DetMetrics
|
|
@@ -23,6 +23,8 @@ from pathlib import Path
|
|
|
23
23
|
|
|
24
24
|
import numpy as np
|
|
25
25
|
|
|
26
|
+
from easydetect.utils.imageio import imread # any path, Hangul included
|
|
27
|
+
|
|
26
28
|
|
|
27
29
|
def _model_files(spec) -> list[Path]:
|
|
28
30
|
items = spec if isinstance(spec, (list, tuple)) else [spec]
|
|
@@ -75,7 +77,7 @@ def _picture(source) -> np.ndarray:
|
|
|
75
77
|
return cv2.GaussianBlur(noise, (0, 0), 3)
|
|
76
78
|
import cv2
|
|
77
79
|
|
|
78
|
-
img =
|
|
80
|
+
img = imread(str(source))
|
|
79
81
|
if img is None:
|
|
80
82
|
raise FileNotFoundError(f"cannot read {source}")
|
|
81
83
|
return img
|
|
@@ -19,6 +19,8 @@ import torch
|
|
|
19
19
|
import yaml
|
|
20
20
|
from torch.utils.data import Dataset
|
|
21
21
|
|
|
22
|
+
from easydetect.utils.imageio import imread # any path, Hangul included
|
|
23
|
+
|
|
22
24
|
from . import augment as aug
|
|
23
25
|
from .labels import label_path, label_row_to_box
|
|
24
26
|
|
|
@@ -201,7 +203,7 @@ class DetDataset(Dataset):
|
|
|
201
203
|
|
|
202
204
|
def _raw(self, i):
|
|
203
205
|
f = self.files[i]
|
|
204
|
-
img =
|
|
206
|
+
img = imread(str(f))
|
|
205
207
|
if img is None:
|
|
206
208
|
raise FileNotFoundError(f)
|
|
207
209
|
return img, self._load_labels(f) # cls, cx, cy, w, h (normalized)
|
|
@@ -22,6 +22,8 @@ from pathlib import Path
|
|
|
22
22
|
import cv2
|
|
23
23
|
import numpy as np
|
|
24
24
|
|
|
25
|
+
from easydetect.utils.imageio import imread # any path, Hangul included
|
|
26
|
+
|
|
25
27
|
from ..pose import COCO, INPUT, KeypointSpec, box_to_crop, crop, crop_matrix
|
|
26
28
|
from .augment import photometric
|
|
27
29
|
|
|
@@ -68,7 +70,7 @@ def _image_size(path: Path) -> tuple[int, int]:
|
|
|
68
70
|
with Image.open(path) as im:
|
|
69
71
|
return im.size
|
|
70
72
|
except Exception: # noqa: BLE001 — any reader trouble: decode it
|
|
71
|
-
img =
|
|
73
|
+
img = imread(str(path))
|
|
72
74
|
if img is None:
|
|
73
75
|
raise FileNotFoundError(path) from None
|
|
74
76
|
return img.shape[1], img.shape[0]
|
|
@@ -178,7 +180,7 @@ class KeypointDataset:
|
|
|
178
180
|
|
|
179
181
|
def __getitem__(self, i: int):
|
|
180
182
|
image_id, ann = self.items[i]
|
|
181
|
-
img =
|
|
183
|
+
img = imread(str(self.images[image_id]))
|
|
182
184
|
if img is None:
|
|
183
185
|
raise FileNotFoundError(self.images[image_id])
|
|
184
186
|
kpts = np.asarray(ann["keypoints"], np.float64).reshape(-1, 3)
|
|
@@ -9,6 +9,8 @@ import threading
|
|
|
9
9
|
import warnings
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
+
from easydetect.utils.imageio import imread # any path, Hangul included
|
|
13
|
+
|
|
12
14
|
#: torch.onnx keeps global state while exporting ("in_onnx_export"), so two
|
|
13
15
|
#: exports at once fail an assertion deep inside it. Serialise them — the
|
|
14
16
|
#: whole export, names and conversion included, so that two threads exporting
|
|
@@ -168,7 +170,6 @@ def _int8_agreement(fp32, int8, images, imgsz) -> tuple[int, int]:
|
|
|
168
170
|
"""How many of the FP32 model's boxes (conf 0.5) the INT8 one finds again
|
|
169
171
|
(same class, IoU 0.5) on some of the calibration pictures: NNCF quantizes
|
|
170
172
|
without checking, and a model that loses objects should say so."""
|
|
171
|
-
import cv2
|
|
172
173
|
import numpy as np
|
|
173
174
|
import openvino as ov
|
|
174
175
|
|
|
@@ -181,7 +182,7 @@ def _int8_agreement(fp32, int8, images, imgsz) -> tuple[int, int]:
|
|
|
181
182
|
step = max(len(images) // AGREEMENT_PICTURES, 1)
|
|
182
183
|
found = total = 0
|
|
183
184
|
for path in images[::step][:AGREEMENT_PICTURES]:
|
|
184
|
-
img =
|
|
185
|
+
img = imread(str(path))
|
|
185
186
|
if img is None:
|
|
186
187
|
continue
|
|
187
188
|
x = preprocess_image(img, imgsz)
|
|
@@ -202,7 +203,6 @@ def _int8_agreement(fp32, int8, images, imgsz) -> tuple[int, int]:
|
|
|
202
203
|
|
|
203
204
|
def _quantize(model, images, imgsz):
|
|
204
205
|
"""8-bit weights and activations, calibrated on ``images`` (paths)."""
|
|
205
|
-
import cv2
|
|
206
206
|
import nncf
|
|
207
207
|
|
|
208
208
|
from .predictor import preprocess_image
|
|
@@ -212,7 +212,7 @@ def _quantize(model, images, imgsz):
|
|
|
212
212
|
raise ValueError("int8 needs pictures to calibrate on: give data=")
|
|
213
213
|
|
|
214
214
|
def transform(path):
|
|
215
|
-
img =
|
|
215
|
+
img = imread(path)
|
|
216
216
|
if img is None:
|
|
217
217
|
raise ValueError(f"cannot read {path}")
|
|
218
218
|
return preprocess_image(img, imgsz)
|
|
@@ -26,6 +26,8 @@ from typing import Any
|
|
|
26
26
|
|
|
27
27
|
import numpy as np
|
|
28
28
|
|
|
29
|
+
from easydetect.utils.imageio import cv2_path, imwrite # any path, Hangul included
|
|
30
|
+
|
|
29
31
|
from . import downloads
|
|
30
32
|
from .errors import ModelNotFoundError
|
|
31
33
|
from .metrics import DetMetrics
|
|
@@ -457,6 +459,11 @@ class Detector:
|
|
|
457
459
|
self.names = {int(k): str(v) for k, v in ckpt.get("names", {}).items()}
|
|
458
460
|
self.net = DFINENet(ckpt["variant"], ckpt["num_classes"], pretrained_backbone=False)
|
|
459
461
|
self.net.load_state_dict(ckpt["model"])
|
|
462
|
+
# trained with fewer queries: runs (and fine-tunes again) with as many
|
|
463
|
+
if ckpt.get("queries") and ckpt["queries"] != self.net.decoder.num_queries:
|
|
464
|
+
self.net.trim(queries=int(ckpt["queries"]))
|
|
465
|
+
if ckpt.get("denoising") is not None:
|
|
466
|
+
self.net.decoder.num_denoising = int(ckpt["denoising"])
|
|
460
467
|
self.net.eval()
|
|
461
468
|
self.ir_path = None
|
|
462
469
|
self.predictor = None
|
|
@@ -505,6 +512,8 @@ class Detector:
|
|
|
505
512
|
seg_epochs: int = 20,
|
|
506
513
|
seg_batch: int = 16,
|
|
507
514
|
seg_lr: float = 1e-4,
|
|
515
|
+
queries: int | None = None,
|
|
516
|
+
denoising: int | None = None,
|
|
508
517
|
**kwargs: Any,
|
|
509
518
|
) -> Path:
|
|
510
519
|
"""Train on a data.yaml dataset. Returns the path of ``best.pt``.
|
|
@@ -518,6 +527,12 @@ class Detector:
|
|
|
518
527
|
``on_epoch_end`` is called with a dict of the epoch's numbers — the
|
|
519
528
|
same row that lands in ``results.csv``.
|
|
520
529
|
|
|
530
|
+
``queries`` (of 300) and ``denoising`` (training-only queries, 100)
|
|
531
|
+
make every step cheaper; on a CPU most of a step is spent on them, not
|
|
532
|
+
on the picture. ``queries=100, denoising=30`` trains dfine-n about
|
|
533
|
+
twice as fast on a laptop and suits scenes with a handful of objects;
|
|
534
|
+
the model keeps ``queries`` (it runs faster too).
|
|
535
|
+
|
|
521
536
|
A keypoint dataset (``kpt_shape`` in its data.yaml, Ultralytics' pose
|
|
522
537
|
format) also trains a keypoint network after the detector: on crops of
|
|
523
538
|
the labelled boxes, from the detector's backbone, for ``pose_epochs``
|
|
@@ -561,6 +576,10 @@ class Detector:
|
|
|
561
576
|
elif self.net.num_classes != cfg["nc"]:
|
|
562
577
|
self.net = self._reheaded_net(cfg["nc"])
|
|
563
578
|
|
|
579
|
+
if queries is not None:
|
|
580
|
+
self.net.trim(queries=int(queries))
|
|
581
|
+
if denoising is not None:
|
|
582
|
+
self.net.decoder.num_denoising = int(denoising)
|
|
564
583
|
device = _torch_device(device)
|
|
565
584
|
trainer = Trainer(
|
|
566
585
|
self.net,
|
|
@@ -898,7 +917,7 @@ class _OutputWriter:
|
|
|
898
917
|
out = self.save_dir / f"{stem}.jpg"
|
|
899
918
|
if out.exists():
|
|
900
919
|
out = self.save_dir / f"{stem}_{frame.index}.jpg"
|
|
901
|
-
|
|
920
|
+
imwrite(str(out), img)
|
|
902
921
|
return
|
|
903
922
|
writer = self.videos.get(frame.path)
|
|
904
923
|
if writer is None:
|
|
@@ -906,7 +925,7 @@ class _OutputWriter:
|
|
|
906
925
|
h, w = img.shape[:2]
|
|
907
926
|
# H.264 plays in browsers; OpenCV's pip wheels often lack it, so fall back
|
|
908
927
|
for codec in ("avc1", "mp4v"):
|
|
909
|
-
writer = cv2.VideoWriter(
|
|
928
|
+
writer = cv2.VideoWriter(cv2_path(self.save_dir / f"{stem}.mp4"),
|
|
910
929
|
cv2.VideoWriter_fourcc(*codec),
|
|
911
930
|
frame.fps or 25.0, # keep the source's timing
|
|
912
931
|
(w, h))
|
|
@@ -20,6 +20,8 @@ from typing import Any
|
|
|
20
20
|
|
|
21
21
|
import numpy as np
|
|
22
22
|
|
|
23
|
+
from easydetect.utils.imageio import imwrite # any path, Hangul included
|
|
24
|
+
|
|
23
25
|
|
|
24
26
|
class Boxes:
|
|
25
27
|
"""Detected boxes for one image.
|
|
@@ -223,14 +225,13 @@ class Results:
|
|
|
223
225
|
|
|
224
226
|
def save(self, filename: str | Path | None = None) -> Path:
|
|
225
227
|
"""Write the annotated image; returns the path written."""
|
|
226
|
-
import cv2
|
|
227
228
|
|
|
228
229
|
if filename is None:
|
|
229
230
|
stem = Path(self.path).stem or "image"
|
|
230
231
|
filename = Path(f"{stem}.jpg")
|
|
231
232
|
filename = Path(filename)
|
|
232
233
|
filename.parent.mkdir(parents=True, exist_ok=True)
|
|
233
|
-
|
|
234
|
+
imwrite(str(filename), self.plot())
|
|
234
235
|
return filename
|
|
235
236
|
|
|
236
237
|
def show(self, title: str | None = None) -> None:
|
|
@@ -25,6 +25,8 @@ from pathlib import Path
|
|
|
25
25
|
|
|
26
26
|
import numpy as np
|
|
27
27
|
|
|
28
|
+
from easydetect.utils.imageio import imread # any path, Hangul included
|
|
29
|
+
|
|
28
30
|
from .nn.sam_decoder import GRID, IMG_SIZE, LOW_RES, MASK_CHOICES
|
|
29
31
|
|
|
30
32
|
BOX_JITTER = 0.05 # Gaussian sigma of each edge's shift, as a share of the box's side
|
|
@@ -103,13 +105,12 @@ class EmbeddingCache:
|
|
|
103
105
|
def file(self, path: Path) -> tuple[Path, tuple[int, int]]:
|
|
104
106
|
"""``(the .npy file of the picture's embedding, (height, width))``,
|
|
105
107
|
encoding the picture first if it has not been."""
|
|
106
|
-
import cv2
|
|
107
108
|
|
|
108
109
|
file = self._key(path)
|
|
109
110
|
meta = file.with_suffix(".json")
|
|
110
111
|
if file.exists() and meta.exists():
|
|
111
112
|
return file, tuple(json.loads(meta.read_text()))
|
|
112
|
-
img =
|
|
113
|
+
img = imread(str(path))
|
|
113
114
|
if img is None:
|
|
114
115
|
raise FileNotFoundError(path)
|
|
115
116
|
tensor, _ = self.segmenter.preprocess(img)
|
|
@@ -33,6 +33,8 @@ from typing import Any
|
|
|
33
33
|
|
|
34
34
|
import numpy as np
|
|
35
35
|
|
|
36
|
+
from easydetect.utils.imageio import cv2_path, imread # any path, Hangul included
|
|
37
|
+
|
|
36
38
|
IMG_FORMATS = {".jpg", ".jpeg", ".png", ".bmp", ".webp", ".tif", ".tiff", ".dng", ".pfm"}
|
|
37
39
|
VID_FORMATS = {".mp4", ".avi", ".mov", ".mkv", ".mpg", ".mpeg", ".m4v", ".wmv", ".webm", ".gif"}
|
|
38
40
|
_URL_RE = re.compile(r"^(https?|rtsp|rtmp|tcp|udp)://", re.IGNORECASE)
|
|
@@ -252,12 +254,12 @@ class SourceLoader:
|
|
|
252
254
|
elif kind == "url-image":
|
|
253
255
|
yield Frame(_read_url_image(payload), payload, i, self.total, "image")
|
|
254
256
|
elif kind == "image":
|
|
255
|
-
img =
|
|
257
|
+
img = imread(str(payload))
|
|
256
258
|
if img is None:
|
|
257
259
|
raise ValueError(f"could not read image: {payload}")
|
|
258
260
|
yield Frame(img, payload, i, self.total, "image")
|
|
259
261
|
else: # video file, webcam index, or network stream
|
|
260
|
-
cap = cv2.VideoCapture(payload if isinstance(payload, int) else
|
|
262
|
+
cap = cv2.VideoCapture(payload if isinstance(payload, int) else cv2_path(payload))
|
|
261
263
|
if not cap.isOpened():
|
|
262
264
|
raise ValueError(f"could not open {payload!r}")
|
|
263
265
|
frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) if kind == "video" else 0
|
|
@@ -436,6 +436,8 @@ class Trainer:
|
|
|
436
436
|
# a resumed run started where its first attempt did
|
|
437
437
|
"start": old.get("start") or self.origin,
|
|
438
438
|
"imgsz": self.imgsz,
|
|
439
|
+
"queries": self.net.decoder.num_queries,
|
|
440
|
+
"denoising": self.net.decoder.num_denoising,
|
|
439
441
|
"batch": self.batch,
|
|
440
442
|
"epochs": self.epochs,
|
|
441
443
|
"patience": self.patience,
|
|
@@ -736,6 +738,8 @@ class Trainer:
|
|
|
736
738
|
"scaler": self.scaler.state_dict() if self.amp else None,
|
|
737
739
|
"variant": net.variant,
|
|
738
740
|
"num_classes": net.num_classes,
|
|
741
|
+
"queries": net.decoder.num_queries,
|
|
742
|
+
"denoising": net.decoder.num_denoising,
|
|
739
743
|
"names": self.cfg["names"],
|
|
740
744
|
"epoch": epoch,
|
|
741
745
|
"best": self.best,
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Apache-2.0
|
|
2
|
+
"""Pictures in and out by any path, Hangul included.
|
|
3
|
+
|
|
4
|
+
OpenCV's ``imread``/``imwrite`` take the path as bytes in the system code
|
|
5
|
+
page; on Windows a path with characters outside it (a folder called
|
|
6
|
+
``바탕 화면``, a OneDrive in Korean) reads as nothing. Reading the file's
|
|
7
|
+
bytes with Python and decoding them in memory works for every path.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import cv2
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def imread(path, flags: int = cv2.IMREAD_COLOR):
|
|
19
|
+
"""``cv2.imread`` for any path: the picture, or ``None`` when it cannot be read."""
|
|
20
|
+
try:
|
|
21
|
+
data = np.fromfile(str(path), dtype=np.uint8)
|
|
22
|
+
except OSError:
|
|
23
|
+
return None
|
|
24
|
+
if data.size == 0:
|
|
25
|
+
return None
|
|
26
|
+
return cv2.imdecode(data, flags)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def imwrite(path, img, params=None) -> bool:
|
|
30
|
+
"""``cv2.imwrite`` for any path; the format follows the file's extension."""
|
|
31
|
+
ext = Path(str(path)).suffix or ".jpg"
|
|
32
|
+
ok, buf = cv2.imencode(ext, img, params or [])
|
|
33
|
+
if not ok:
|
|
34
|
+
return False
|
|
35
|
+
buf.tofile(str(path))
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def cv2_path(path) -> str:
|
|
40
|
+
"""A path OpenCV's video reader and writer can open. On Windows a path
|
|
41
|
+
outside the code page becomes its short (8.3) form, which is plain ASCII;
|
|
42
|
+
elsewhere, or when it is ASCII already, the path as it is. A file that
|
|
43
|
+
does not exist yet (a video about to be written) keeps its name and gets
|
|
44
|
+
its folder's short form."""
|
|
45
|
+
text = str(path)
|
|
46
|
+
import os
|
|
47
|
+
import sys
|
|
48
|
+
|
|
49
|
+
if sys.platform != "win32" or text.isascii():
|
|
50
|
+
return text
|
|
51
|
+
import ctypes
|
|
52
|
+
|
|
53
|
+
def short(p: str) -> str:
|
|
54
|
+
size = ctypes.windll.kernel32.GetShortPathNameW(p, None, 0)
|
|
55
|
+
if not size:
|
|
56
|
+
return p
|
|
57
|
+
buf = ctypes.create_unicode_buffer(size)
|
|
58
|
+
ctypes.windll.kernel32.GetShortPathNameW(p, buf, size)
|
|
59
|
+
return buf.value
|
|
60
|
+
|
|
61
|
+
if os.path.exists(text):
|
|
62
|
+
return short(text)
|
|
63
|
+
folder, name = os.path.split(text)
|
|
64
|
+
return os.path.join(short(folder), name)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "easydetect"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.4"
|
|
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"
|
|
@@ -236,3 +236,37 @@ def test_matching_all_layers_at_once_gives_the_same_loss():
|
|
|
236
236
|
apart, _ = crit(out, targets)
|
|
237
237
|
assert calls and calls[0] > 2 # the last layer, the others, the encoder
|
|
238
238
|
assert torch.allclose(together, apart)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def test_pictures_in_a_hangul_folder_read_and_write(tmp_path):
|
|
242
|
+
"""Windows' OpenCV cannot open such a path itself (바탕 화면, a Korean OneDrive)."""
|
|
243
|
+
import numpy as np
|
|
244
|
+
|
|
245
|
+
from easydetect.utils.imageio import cv2_path, imread, imwrite
|
|
246
|
+
|
|
247
|
+
folder = tmp_path / "바탕 화면" / "사진"
|
|
248
|
+
folder.mkdir(parents=True)
|
|
249
|
+
img = np.full((20, 30, 3), 120, np.uint8)
|
|
250
|
+
assert imwrite(folder / "고양이.png", img)
|
|
251
|
+
back = imread(folder / "고양이.png")
|
|
252
|
+
assert back is not None and back.shape == img.shape and (back == img).all()
|
|
253
|
+
assert imread(folder / "없음.jpg") is None
|
|
254
|
+
assert imread(folder / "고양이.png").dtype == np.uint8
|
|
255
|
+
assert cv2_path(folder / "a.mp4").endswith("a.mp4")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@needs_torch
|
|
259
|
+
def test_a_dataset_in_a_hangul_folder_trains(dataset, tmp_path):
|
|
260
|
+
import shutil
|
|
261
|
+
|
|
262
|
+
import yaml
|
|
263
|
+
|
|
264
|
+
from easydetect.data.dataset import DetDataset
|
|
265
|
+
|
|
266
|
+
root = tmp_path / "바탕 화면" / "데이터"
|
|
267
|
+
shutil.copytree(dataset.parent, root)
|
|
268
|
+
cfg = yaml.safe_load(dataset.read_text())
|
|
269
|
+
(root / "data.yaml").write_text(yaml.safe_dump(dict(cfg, path=str(root))), encoding="utf-8")
|
|
270
|
+
ds = DetDataset(root / "data.yaml", "train", 64, augment=True)
|
|
271
|
+
img, labels = ds[0]
|
|
272
|
+
assert img.shape[-2:] == (64, 64)
|
|
@@ -114,9 +114,11 @@ def pictures(tmp_path):
|
|
|
114
114
|
|
|
115
115
|
|
|
116
116
|
def test_pictures_are_read_ahead_of_the_model(pictures, monkeypatch):
|
|
117
|
+
from easydetect import sources
|
|
118
|
+
|
|
117
119
|
read = []
|
|
118
|
-
real =
|
|
119
|
-
monkeypatch.setattr(
|
|
120
|
+
real = sources.imread
|
|
121
|
+
monkeypatch.setattr(sources, "imread", lambda p, *a: read.append(p) or real(p, *a))
|
|
120
122
|
frames = iter(SourceLoader(pictures))
|
|
121
123
|
first = next(frames)
|
|
122
124
|
deadline = time.time() + 3
|
|
@@ -477,3 +477,20 @@ def test_multiscale_mosaic_and_mixup_train_and_are_recorded(dataset, tmp_path, m
|
|
|
477
477
|
assert any("mixup" in line for line in setup["augment"])
|
|
478
478
|
assert any("random size, 32–64" in line for line in setup["augment"])
|
|
479
479
|
assert (32, 32) in sizes # the batches went through at the drawn size
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def test_fewer_queries_train_and_stay_with_the_model(dataset, tmp_path, monkeypatch):
|
|
483
|
+
monkeypatch.setattr("easydetect.nn.hgnetv2.HGNetv2.load_imagenet", lambda self, name, url: None)
|
|
484
|
+
model = Detector("dfine-n", verbose=False, pretrained=False)
|
|
485
|
+
best = model.train(data=str(dataset), project=str(tmp_path), epochs=1, imgsz=64, batch=2,
|
|
486
|
+
workers=0, device="cpu", val=False, amp=False, queries=40, denoising=10)
|
|
487
|
+
again = Detector(str(best), verbose=False)
|
|
488
|
+
again._ensure_net()
|
|
489
|
+
assert again.net.decoder.num_queries == 40 and again.net.decoder.num_denoising == 10
|
|
490
|
+
# at 160 px the encoder has more than 300 candidates: only the trim limits it
|
|
491
|
+
onnx = again.export(format="onnx", imgsz=160, out_dir=tmp_path / "x", verbose=False)
|
|
492
|
+
import onnxruntime as ort
|
|
493
|
+
|
|
494
|
+
out = ort.InferenceSession(str(onnx)).run(None, {"images": np.zeros((1, 3, 160, 160),
|
|
495
|
+
np.float32)})
|
|
496
|
+
assert any(o.shape[1] == 40 for o in out if o.ndim == 3)
|
|
@@ -141,14 +141,17 @@ def train_dfine(data: Path, out: Path, args) -> tuple[Path, float]:
|
|
|
141
141
|
# in a process of its own, as the yolo command is: a crash in what training
|
|
142
142
|
# leaves behind (loader workers, CUDA) then takes only that process down
|
|
143
143
|
started = time.time()
|
|
144
|
-
|
|
144
|
+
extra = {k: v for k, v in (("queries", args.queries), ("denoising", args.denoising),
|
|
145
|
+
("freeze", args.freeze)) if v is not None}
|
|
146
|
+
code = ("import json, sys; from easydetect import Detector; "
|
|
145
147
|
"print('BEST', Detector(sys.argv[1], verbose=False).train(data=sys.argv[2], "
|
|
146
148
|
"epochs=int(sys.argv[3]), imgsz=int(sys.argv[4]), batch=int(sys.argv[5]), "
|
|
147
149
|
"patience=int(sys.argv[3]), project=sys.argv[6], name=sys.argv[1], "
|
|
148
|
-
"workers=int(sys.argv[7])))")
|
|
150
|
+
"workers=int(sys.argv[7]), **json.loads(sys.argv[8])))")
|
|
149
151
|
best = None
|
|
150
152
|
with subprocess.Popen([sys.executable, "-c", code, args.dfine, str(data), str(args.epochs),
|
|
151
|
-
str(args.imgsz), str(args.batch), str(out), str(args.workers)
|
|
153
|
+
str(args.imgsz), str(args.batch), str(out), str(args.workers),
|
|
154
|
+
json.dumps(extra)],
|
|
152
155
|
env=UNBUFFERED, stdout=subprocess.PIPE, text=True) as proc:
|
|
153
156
|
for line in proc.stdout: # passed on as it comes
|
|
154
157
|
print(line, end="", flush=True)
|
|
@@ -276,6 +279,9 @@ def main():
|
|
|
276
279
|
parser.add_argument("--yolo", default="yolo26s.pt")
|
|
277
280
|
parser.add_argument("--dfine", default="dfine-s")
|
|
278
281
|
parser.add_argument("--device", default="CPU", help="OpenVINO device for scoring")
|
|
282
|
+
parser.add_argument("--queries", type=int, help="D-FINE decoder queries (of 300)")
|
|
283
|
+
parser.add_argument("--denoising", type=int, help="D-FINE denoising queries (100)")
|
|
284
|
+
parser.add_argument("--freeze", help="D-FINE parts not trained (backbone)")
|
|
279
285
|
parser.add_argument("--rescore", action="store_true",
|
|
280
286
|
help="score finished datasets again (their trained models are reused)")
|
|
281
287
|
args = parser.parse_args()
|
|
@@ -35,6 +35,9 @@ def main():
|
|
|
35
35
|
parser.add_argument("--steps", type=int, default=40)
|
|
36
36
|
parser.add_argument("--warmup", type=int, default=10)
|
|
37
37
|
parser.add_argument("--no-amp", action="store_true")
|
|
38
|
+
parser.add_argument("--freeze", default=None, help="backbone, encoder, …: parts not trained")
|
|
39
|
+
parser.add_argument("--queries", type=int, help="decoder queries while training (of 300)")
|
|
40
|
+
parser.add_argument("--denoising", type=int, help="denoising queries while training (100)")
|
|
38
41
|
parser.add_argument("--trace", action="store_true", help="also PyTorch's operator table")
|
|
39
42
|
parser.add_argument("--syncs", action="store_true",
|
|
40
43
|
help="list the lines of easydetect where a step waits for the GPU")
|
|
@@ -44,9 +47,14 @@ def main():
|
|
|
44
47
|
|
|
45
48
|
nc = load_data_yaml(args.data)["nc"]
|
|
46
49
|
net = DFINENet(args.model, nc)
|
|
50
|
+
if args.queries:
|
|
51
|
+
net.trim(queries=args.queries)
|
|
52
|
+
if args.denoising is not None:
|
|
53
|
+
net.decoder.num_denoising = args.denoising
|
|
47
54
|
with tempfile.TemporaryDirectory() as tmp:
|
|
48
55
|
tr = Trainer(net, args.data, epochs=1, imgsz=args.imgsz, batch=args.batch,
|
|
49
|
-
workers=args.workers, project=tmp, amp=not args.no_amp
|
|
56
|
+
workers=args.workers, project=tmp, amp=not args.no_amp,
|
|
57
|
+
freeze=args.freeze)
|
|
50
58
|
ds = DetDataset(args.data, "train", args.imgsz, augment=True)
|
|
51
59
|
dl = tr._loader(ds)
|
|
52
60
|
net = tr.net.to(tr.device).train()
|
|
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
|
|
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
|