ultralytics 8.0.64__py3-none-any.whl → 8.0.66__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.
Potentially problematic release.
This version of ultralytics might be problematic. Click here for more details.
- ultralytics/__init__.py +1 -1
- ultralytics/datasets/coco-pose.yaml +38 -0
- ultralytics/datasets/coco8-pose.yaml +25 -0
- ultralytics/models/v8/yolov8-pose-p6.yaml +57 -0
- ultralytics/models/v8/yolov8-pose.yaml +47 -0
- ultralytics/nn/autobackend.py +7 -2
- ultralytics/nn/modules.py +33 -2
- ultralytics/nn/tasks.py +24 -7
- ultralytics/tracker/track.py +2 -3
- ultralytics/yolo/cfg/__init__.py +4 -4
- ultralytics/yolo/cfg/default.yaml +2 -0
- ultralytics/yolo/data/augment.py +24 -19
- ultralytics/yolo/data/build.py +4 -4
- ultralytics/yolo/data/dataset.py +9 -3
- ultralytics/yolo/data/utils.py +110 -34
- ultralytics/yolo/engine/exporter.py +9 -7
- ultralytics/yolo/engine/model.py +5 -4
- ultralytics/yolo/engine/predictor.py +1 -0
- ultralytics/yolo/engine/results.py +70 -56
- ultralytics/yolo/utils/benchmarks.py +4 -2
- ultralytics/yolo/utils/downloads.py +3 -3
- ultralytics/yolo/utils/instance.py +1 -1
- ultralytics/yolo/utils/loss.py +14 -0
- ultralytics/yolo/utils/metrics.py +111 -13
- ultralytics/yolo/utils/ops.py +30 -50
- ultralytics/yolo/utils/plotting.py +79 -4
- ultralytics/yolo/utils/torch_utils.py +11 -9
- ultralytics/yolo/v8/__init__.py +2 -2
- ultralytics/yolo/v8/detect/train.py +1 -1
- ultralytics/yolo/v8/detect/val.py +2 -2
- ultralytics/yolo/v8/pose/__init__.py +7 -0
- ultralytics/yolo/v8/pose/predict.py +103 -0
- ultralytics/yolo/v8/pose/train.py +170 -0
- ultralytics/yolo/v8/pose/val.py +213 -0
- ultralytics/yolo/v8/segment/val.py +3 -4
- {ultralytics-8.0.64.dist-info → ultralytics-8.0.66.dist-info}/METADATA +27 -2
- {ultralytics-8.0.64.dist-info → ultralytics-8.0.66.dist-info}/RECORD +41 -33
- {ultralytics-8.0.64.dist-info → ultralytics-8.0.66.dist-info}/LICENSE +0 -0
- {ultralytics-8.0.64.dist-info → ultralytics-8.0.66.dist-info}/WHEEL +0 -0
- {ultralytics-8.0.64.dist-info → ultralytics-8.0.66.dist-info}/entry_points.txt +0 -0
- {ultralytics-8.0.64.dist-info → ultralytics-8.0.66.dist-info}/top_level.txt +0 -0
ultralytics/yolo/data/utils.py
CHANGED
|
@@ -6,10 +6,10 @@ import json
|
|
|
6
6
|
import os
|
|
7
7
|
import subprocess
|
|
8
8
|
import time
|
|
9
|
+
import zipfile
|
|
9
10
|
from multiprocessing.pool import ThreadPool
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from tarfile import is_tarfile
|
|
12
|
-
from zipfile import is_zipfile
|
|
13
13
|
|
|
14
14
|
import cv2
|
|
15
15
|
import numpy as np
|
|
@@ -61,7 +61,7 @@ def exif_size(img):
|
|
|
61
61
|
|
|
62
62
|
def verify_image_label(args):
|
|
63
63
|
# Verify one image-label pair
|
|
64
|
-
im_file, lb_file, prefix, keypoint, num_cls = args
|
|
64
|
+
im_file, lb_file, prefix, keypoint, num_cls, nkpt, ndim = args
|
|
65
65
|
# number (missing, found, empty, corrupt), message, segments, keypoints
|
|
66
66
|
nm, nf, ne, nc, msg, segments, keypoints = 0, 0, 0, 0, '', [], None
|
|
67
67
|
try:
|
|
@@ -92,25 +92,19 @@ def verify_image_label(args):
|
|
|
92
92
|
nl = len(lb)
|
|
93
93
|
if nl:
|
|
94
94
|
if keypoint:
|
|
95
|
-
assert lb.shape[1] ==
|
|
96
|
-
assert (lb[:, 5::
|
|
97
|
-
assert (lb[:, 6::
|
|
98
|
-
kpts = np.zeros((lb.shape[0], 39))
|
|
99
|
-
for i in range(len(lb)):
|
|
100
|
-
kpt = np.delete(lb[i, 5:], np.arange(2, lb.shape[1] - 5, 3)) # remove occlusion param from GT
|
|
101
|
-
kpts[i] = np.hstack((lb[i, :5], kpt))
|
|
102
|
-
lb = kpts
|
|
103
|
-
assert lb.shape[1] == 39, 'labels require 39 columns each after removing occlusion parameter'
|
|
95
|
+
assert lb.shape[1] == (5 + nkpt * ndim), f'labels require {(5 + nkpt * ndim)} columns each'
|
|
96
|
+
assert (lb[:, 5::ndim] <= 1).all(), 'non-normalized or out of bounds coordinate labels'
|
|
97
|
+
assert (lb[:, 6::ndim] <= 1).all(), 'non-normalized or out of bounds coordinate labels'
|
|
104
98
|
else:
|
|
105
99
|
assert lb.shape[1] == 5, f'labels require 5 columns, {lb.shape[1]} columns detected'
|
|
106
100
|
assert (lb[:, 1:] <= 1).all(), \
|
|
107
101
|
f'non-normalized or out of bounds coordinates {lb[:, 1:][lb[:, 1:] > 1]}'
|
|
102
|
+
assert (lb >= 0).all(), f'negative label values {lb[lb < 0]}'
|
|
108
103
|
# All labels
|
|
109
104
|
max_cls = int(lb[:, 0].max()) # max label count
|
|
110
105
|
assert max_cls <= num_cls, \
|
|
111
106
|
f'Label class {max_cls} exceeds dataset class count {num_cls}. ' \
|
|
112
107
|
f'Possible class labels are 0-{num_cls - 1}'
|
|
113
|
-
assert (lb >= 0).all(), f'negative label values {lb[lb < 0]}'
|
|
114
108
|
_, i = np.unique(lb, axis=0, return_index=True)
|
|
115
109
|
if len(i) < nl: # duplicate row check
|
|
116
110
|
lb = lb[i] # remove duplicates
|
|
@@ -119,12 +113,18 @@ def verify_image_label(args):
|
|
|
119
113
|
msg = f'{prefix}WARNING ⚠️ {im_file}: {nl - len(i)} duplicate labels removed'
|
|
120
114
|
else:
|
|
121
115
|
ne = 1 # label empty
|
|
122
|
-
lb = np.zeros((0,
|
|
116
|
+
lb = np.zeros((0, (5 + nkpt * ndim)), dtype=np.float32) if keypoint else np.zeros(
|
|
117
|
+
(0, 5), dtype=np.float32)
|
|
123
118
|
else:
|
|
124
119
|
nm = 1 # label missing
|
|
125
|
-
lb = np.zeros((0,
|
|
120
|
+
lb = np.zeros((0, (5 + nkpt * ndim)), dtype=np.float32) if keypoint else np.zeros((0, 5), dtype=np.float32)
|
|
126
121
|
if keypoint:
|
|
127
|
-
keypoints = lb[:, 5:].reshape(-1,
|
|
122
|
+
keypoints = lb[:, 5:].reshape(-1, nkpt, ndim)
|
|
123
|
+
if ndim == 2:
|
|
124
|
+
kpt_mask = np.ones(keypoints.shape[:2], dtype=np.float32)
|
|
125
|
+
kpt_mask = np.where(keypoints[..., 0] < 0, 0.0, kpt_mask)
|
|
126
|
+
kpt_mask = np.where(keypoints[..., 1] < 0, 0.0, kpt_mask)
|
|
127
|
+
keypoints = np.concatenate([keypoints, kpt_mask[..., None]], axis=-1) # (nl, nkpt, 3)
|
|
128
128
|
lb = lb[:, :5]
|
|
129
129
|
return im_file, lb, shape, segments, keypoints, nm, nf, ne, nc, msg
|
|
130
130
|
except Exception as e:
|
|
@@ -195,7 +195,7 @@ def check_det_dataset(dataset, autodownload=True):
|
|
|
195
195
|
|
|
196
196
|
# Download (optional)
|
|
197
197
|
extract_dir = ''
|
|
198
|
-
if isinstance(data, (str, Path)) and (is_zipfile(data) or is_tarfile(data)):
|
|
198
|
+
if isinstance(data, (str, Path)) and (zipfile.is_zipfile(data) or is_tarfile(data)):
|
|
199
199
|
new_dir = safe_download(data, dir=DATASETS_DIR, unzip=True, delete=False, curl=False)
|
|
200
200
|
data = next((DATASETS_DIR / new_dir).rglob('*.yaml'))
|
|
201
201
|
extract_dir, autodownload = data.parent, False
|
|
@@ -241,7 +241,8 @@ def check_det_dataset(dataset, autodownload=True):
|
|
|
241
241
|
if val:
|
|
242
242
|
val = [Path(x).resolve() for x in (val if isinstance(val, list) else [val])] # val path
|
|
243
243
|
if not all(x.exists() for x in val):
|
|
244
|
-
|
|
244
|
+
name = str(dataset).split('?')[0] # dataset name with URL auth stripped
|
|
245
|
+
m = f"\nDataset '{name}' images not found ⚠️, missing paths %s" % [str(x) for x in val if not x.exists()]
|
|
245
246
|
if s and autodownload:
|
|
246
247
|
LOGGER.warning(m)
|
|
247
248
|
else:
|
|
@@ -355,23 +356,8 @@ class HUBDatasetStats():
|
|
|
355
356
|
assert dir.is_dir(), f'Error unzipping {path}, {dir} not found. path/to/abc.zip MUST unzip to path/to/abc/'
|
|
356
357
|
return True, str(dir), self._find_yaml(dir) # zipped, data_dir, yaml_path
|
|
357
358
|
|
|
358
|
-
def _hub_ops(self, f
|
|
359
|
-
|
|
360
|
-
f_new = self.im_dir / Path(f).name # dataset-hub image filename
|
|
361
|
-
try: # use PIL
|
|
362
|
-
im = Image.open(f)
|
|
363
|
-
r = max_dim / max(im.height, im.width) # ratio
|
|
364
|
-
if r < 1.0: # image too large
|
|
365
|
-
im = im.resize((int(im.width * r), int(im.height * r)))
|
|
366
|
-
im.save(f_new, 'JPEG', quality=50, optimize=True) # save
|
|
367
|
-
except Exception as e: # use OpenCV
|
|
368
|
-
LOGGER.info(f'WARNING ⚠️ HUB ops PIL failure {f}: {e}')
|
|
369
|
-
im = cv2.imread(f)
|
|
370
|
-
im_height, im_width = im.shape[:2]
|
|
371
|
-
r = max_dim / max(im_height, im_width) # ratio
|
|
372
|
-
if r < 1.0: # image too large
|
|
373
|
-
im = cv2.resize(im, (int(im_width * r), int(im_height * r)), interpolation=cv2.INTER_AREA)
|
|
374
|
-
cv2.imwrite(str(f_new), im)
|
|
359
|
+
def _hub_ops(self, f):
|
|
360
|
+
compress_one_image(f, self.im_dir / Path(f).name) # save to dataset-hub
|
|
375
361
|
|
|
376
362
|
def get_json(self, save=False, verbose=False):
|
|
377
363
|
# Return dataset JSON for Ultralytics HUB
|
|
@@ -425,3 +411,93 @@ class HUBDatasetStats():
|
|
|
425
411
|
pass
|
|
426
412
|
LOGGER.info(f'Done. All images saved to {self.im_dir}')
|
|
427
413
|
return self.im_dir
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def compress_one_image(f, f_new=None, max_dim=1920, quality=50):
|
|
417
|
+
"""
|
|
418
|
+
Compresses a single image file to reduced size while preserving its aspect ratio and quality using either the
|
|
419
|
+
Python Imaging Library (PIL) or OpenCV library. If the input image is smaller than the maximum dimension, it will
|
|
420
|
+
not be resized.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
f (str): The path to the input image file.
|
|
424
|
+
f_new (str, optional): The path to the output image file. If not specified, the input file will be overwritten.
|
|
425
|
+
max_dim (int, optional): The maximum dimension (width or height) of the output image. Default is 1920 pixels.
|
|
426
|
+
quality (int, optional): The image compression quality as a percentage. Default is 50%.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
None
|
|
430
|
+
|
|
431
|
+
Usage:
|
|
432
|
+
from pathlib import Path
|
|
433
|
+
from ultralytics.yolo.data.utils import compress_one_image
|
|
434
|
+
for f in Path('/Users/glennjocher/Downloads/dataset').rglob('*.jpg'):
|
|
435
|
+
compress_one_image(f)
|
|
436
|
+
"""
|
|
437
|
+
try: # use PIL
|
|
438
|
+
im = Image.open(f)
|
|
439
|
+
r = max_dim / max(im.height, im.width) # ratio
|
|
440
|
+
if r < 1.0: # image too large
|
|
441
|
+
im = im.resize((int(im.width * r), int(im.height * r)))
|
|
442
|
+
im.save(f_new or f, 'JPEG', quality=quality, optimize=True) # save
|
|
443
|
+
except Exception as e: # use OpenCV
|
|
444
|
+
LOGGER.info(f'WARNING ⚠️ HUB ops PIL failure {f}: {e}')
|
|
445
|
+
im = cv2.imread(f)
|
|
446
|
+
im_height, im_width = im.shape[:2]
|
|
447
|
+
r = max_dim / max(im_height, im_width) # ratio
|
|
448
|
+
if r < 1.0: # image too large
|
|
449
|
+
im = cv2.resize(im, (int(im_width * r), int(im_height * r)), interpolation=cv2.INTER_AREA)
|
|
450
|
+
cv2.imwrite(str(f_new or f), im)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def delete_dsstore(path):
|
|
454
|
+
"""
|
|
455
|
+
Deletes all ".DS_store" files under a specified directory.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
path (str, optional): The directory path where the ".DS_store" files should be deleted.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
None
|
|
462
|
+
|
|
463
|
+
Usage:
|
|
464
|
+
from ultralytics.yolo.data.utils import delete_dsstore
|
|
465
|
+
delete_dsstore('/Users/glennjocher/Downloads/dataset')
|
|
466
|
+
|
|
467
|
+
Note:
|
|
468
|
+
".DS_store" files are created by the Apple operating system and contain metadata about folders and files. They
|
|
469
|
+
are hidden system files and can cause issues when transferring files between different operating systems.
|
|
470
|
+
"""
|
|
471
|
+
# Delete Apple .DS_store files
|
|
472
|
+
files = list(Path(path).rglob('.DS_store'))
|
|
473
|
+
LOGGER.info(f'Deleting *.DS_store files: {files}')
|
|
474
|
+
for f in files:
|
|
475
|
+
f.unlink()
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def zip_directory(dir, use_zipfile_library=True):
|
|
479
|
+
"""Zips a directory and saves the archive to the specified output path.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
dir (str): The path to the directory to be zipped.
|
|
483
|
+
use_zipfile_library (bool): Whether to use zipfile library or shutil for zipping.
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
None
|
|
487
|
+
|
|
488
|
+
Usage:
|
|
489
|
+
from ultralytics.yolo.data.utils import zip_directory
|
|
490
|
+
zip_directory('/Users/glennjocher/Downloads/playground')
|
|
491
|
+
|
|
492
|
+
zip -r coco8-pose.zip coco8-pose
|
|
493
|
+
"""
|
|
494
|
+
delete_dsstore(dir)
|
|
495
|
+
if use_zipfile_library:
|
|
496
|
+
dir = Path(dir)
|
|
497
|
+
with zipfile.ZipFile(dir.with_suffix('.zip'), 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
|
498
|
+
for file_path in dir.glob('**/*'):
|
|
499
|
+
if file_path.is_file():
|
|
500
|
+
zip_file.write(file_path, file_path.relative_to(dir))
|
|
501
|
+
else:
|
|
502
|
+
import shutil
|
|
503
|
+
shutil.make_archive(dir, 'zip', dir)
|
|
@@ -209,8 +209,8 @@ class Exporter:
|
|
|
209
209
|
self.file = file
|
|
210
210
|
self.output_shape = tuple(y.shape) if isinstance(y, torch.Tensor) else tuple(tuple(x.shape) for x in y)
|
|
211
211
|
self.pretty_name = Path(self.model.yaml.get('yaml_file', self.file)).stem.replace('yolo', 'YOLO')
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
trained_on = f'trained on {Path(self.args.data).name}' if self.args.data else '(untrained)'
|
|
213
|
+
description = f'Ultralytics {self.pretty_name} model {trained_on}'
|
|
214
214
|
self.metadata = {
|
|
215
215
|
'description': description,
|
|
216
216
|
'author': 'Ultralytics',
|
|
@@ -221,6 +221,8 @@ class Exporter:
|
|
|
221
221
|
'batch': self.args.batch,
|
|
222
222
|
'imgsz': self.imgsz,
|
|
223
223
|
'names': model.names} # model metadata
|
|
224
|
+
if model.task == 'pose':
|
|
225
|
+
self.metadata['kpt_shape'] = model.kpt_shape
|
|
224
226
|
|
|
225
227
|
LOGGER.info(f"\n{colorstr('PyTorch:')} starting from {file} with input shape {tuple(im.shape)} BCHW and "
|
|
226
228
|
f'output shape(s) {self.output_shape} ({file_size(file):.1f} MB)')
|
|
@@ -295,7 +297,8 @@ class Exporter:
|
|
|
295
297
|
check_requirements(requirements)
|
|
296
298
|
import onnx # noqa
|
|
297
299
|
|
|
298
|
-
|
|
300
|
+
opset_version = self.args.opset or get_latest_opset()
|
|
301
|
+
LOGGER.info(f'\n{prefix} starting export with onnx {onnx.__version__} opset {opset_version}...')
|
|
299
302
|
f = str(self.file.with_suffix('.onnx'))
|
|
300
303
|
|
|
301
304
|
output_names = ['output0', 'output1'] if isinstance(self.model, SegmentationModel) else ['output0']
|
|
@@ -313,7 +316,7 @@ class Exporter:
|
|
|
313
316
|
self.im.cpu() if dynamic else self.im,
|
|
314
317
|
f,
|
|
315
318
|
verbose=False,
|
|
316
|
-
opset_version=
|
|
319
|
+
opset_version=opset_version,
|
|
317
320
|
do_constant_folding=True, # WARNING: DNN inference with torch>=1.12 may require do_constant_folding=False
|
|
318
321
|
input_names=['images'],
|
|
319
322
|
output_names=output_names,
|
|
@@ -377,7 +380,6 @@ class Exporter:
|
|
|
377
380
|
yaml_save(Path(f) / 'metadata.yaml', self.metadata) # add metadata.yaml
|
|
378
381
|
return f, None
|
|
379
382
|
|
|
380
|
-
@try_export
|
|
381
383
|
def _export_coreml(self, prefix=colorstr('CoreML:')):
|
|
382
384
|
# YOLOv8 CoreML export
|
|
383
385
|
check_requirements('coremltools>=6.0')
|
|
@@ -410,8 +412,8 @@ class Exporter:
|
|
|
410
412
|
model = self.model
|
|
411
413
|
elif self.model.task == 'detect':
|
|
412
414
|
model = iOSDetectModel(self.model, self.im) if self.args.nms else self.model
|
|
413
|
-
|
|
414
|
-
# TODO CoreML
|
|
415
|
+
else:
|
|
416
|
+
# TODO CoreML Segment and Pose model pipelining
|
|
415
417
|
model = self.model
|
|
416
418
|
|
|
417
419
|
ts = torch.jit.trace(model.eval(), self.im, strict=False) # TorchScript model
|
ultralytics/yolo/engine/model.py
CHANGED
|
@@ -5,8 +5,8 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Union
|
|
6
6
|
|
|
7
7
|
from ultralytics import yolo # noqa
|
|
8
|
-
from ultralytics.nn.tasks import (ClassificationModel, DetectionModel,
|
|
9
|
-
guess_model_task, nn, yaml_model_load)
|
|
8
|
+
from ultralytics.nn.tasks import (ClassificationModel, DetectionModel, PoseModel, SegmentationModel,
|
|
9
|
+
attempt_load_one_weight, guess_model_task, nn, yaml_model_load)
|
|
10
10
|
from ultralytics.yolo.cfg import get_cfg
|
|
11
11
|
from ultralytics.yolo.engine.exporter import Exporter
|
|
12
12
|
from ultralytics.yolo.utils import (DEFAULT_CFG, DEFAULT_CFG_DICT, DEFAULT_CFG_KEYS, LOGGER, RANK, ROOT, callbacks,
|
|
@@ -25,7 +25,8 @@ TASK_MAP = {
|
|
|
25
25
|
yolo.v8.detect.DetectionPredictor],
|
|
26
26
|
'segment': [
|
|
27
27
|
SegmentationModel, yolo.v8.segment.SegmentationTrainer, yolo.v8.segment.SegmentationValidator,
|
|
28
|
-
yolo.v8.segment.SegmentationPredictor]
|
|
28
|
+
yolo.v8.segment.SegmentationPredictor],
|
|
29
|
+
'pose': [PoseModel, yolo.v8.pose.PoseTrainer, yolo.v8.pose.PoseValidator, yolo.v8.pose.PosePredictor]}
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
class YOLO:
|
|
@@ -195,7 +196,7 @@ class YOLO:
|
|
|
195
196
|
self.model.load(weights)
|
|
196
197
|
return self
|
|
197
198
|
|
|
198
|
-
def info(self, verbose=
|
|
199
|
+
def info(self, verbose=True):
|
|
199
200
|
"""
|
|
200
201
|
Logs model info.
|
|
201
202
|
|
|
@@ -17,6 +17,53 @@ from ultralytics.yolo.utils.plotting import Annotator, colors
|
|
|
17
17
|
from ultralytics.yolo.utils.torch_utils import TORCHVISION_0_10
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
class BaseTensor(SimpleClass):
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
tensor (torch.Tensor): A tensor.
|
|
25
|
+
orig_shape (tuple): Original image size, in the format (height, width).
|
|
26
|
+
|
|
27
|
+
Methods:
|
|
28
|
+
cpu(): Returns a copy of the tensor on CPU memory.
|
|
29
|
+
numpy(): Returns a copy of the tensor as a numpy array.
|
|
30
|
+
cuda(): Returns a copy of the tensor on GPU memory.
|
|
31
|
+
to(): Returns a copy of the tensor with the specified device and dtype.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, tensor, orig_shape) -> None:
|
|
35
|
+
super().__init__()
|
|
36
|
+
assert isinstance(tensor, torch.Tensor)
|
|
37
|
+
self.tensor = tensor
|
|
38
|
+
self.orig_shape = orig_shape
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def shape(self):
|
|
42
|
+
return self.data.shape
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def data(self):
|
|
46
|
+
return self.tensor
|
|
47
|
+
|
|
48
|
+
def cpu(self):
|
|
49
|
+
return self.__class__(self.data.cpu(), self.orig_shape)
|
|
50
|
+
|
|
51
|
+
def numpy(self):
|
|
52
|
+
return self.__class__(self.data.numpy(), self.orig_shape)
|
|
53
|
+
|
|
54
|
+
def cuda(self):
|
|
55
|
+
return self.__class__(self.data.cuda(), self.orig_shape)
|
|
56
|
+
|
|
57
|
+
def to(self, *args, **kwargs):
|
|
58
|
+
return self.__class__(self.data.to(*args, **kwargs), self.orig_shape)
|
|
59
|
+
|
|
60
|
+
def __len__(self): # override len(results)
|
|
61
|
+
return len(self.data)
|
|
62
|
+
|
|
63
|
+
def __getitem__(self, idx):
|
|
64
|
+
return self.__class__(self.data[idx], self.orig_shape)
|
|
65
|
+
|
|
66
|
+
|
|
20
67
|
class Results(SimpleClass):
|
|
21
68
|
"""
|
|
22
69
|
A class for storing and manipulating inference results.
|
|
@@ -40,22 +87,23 @@ class Results(SimpleClass):
|
|
|
40
87
|
_keys (tuple): A tuple of attribute names for non-empty attributes.
|
|
41
88
|
"""
|
|
42
89
|
|
|
43
|
-
def __init__(self, orig_img, path, names, boxes=None, masks=None, probs=None) -> None:
|
|
90
|
+
def __init__(self, orig_img, path, names, boxes=None, masks=None, probs=None, keypoints=None) -> None:
|
|
44
91
|
self.orig_img = orig_img
|
|
45
92
|
self.orig_shape = orig_img.shape[:2]
|
|
46
93
|
self.boxes = Boxes(boxes, self.orig_shape) if boxes is not None else None # native size boxes
|
|
47
94
|
self.masks = Masks(masks, self.orig_shape) if masks is not None else None # native size or imgsz masks
|
|
48
95
|
self.probs = probs if probs is not None else None
|
|
96
|
+
self.keypoints = keypoints if keypoints is not None else None
|
|
49
97
|
self.names = names
|
|
50
98
|
self.path = path
|
|
51
|
-
self._keys = ('boxes', 'masks', 'probs')
|
|
99
|
+
self._keys = ('boxes', 'masks', 'probs', 'keypoints')
|
|
52
100
|
|
|
53
101
|
def pandas(self):
|
|
54
102
|
pass
|
|
55
103
|
# TODO masks.pandas + boxes.pandas + cls.pandas
|
|
56
104
|
|
|
57
105
|
def __getitem__(self, idx):
|
|
58
|
-
r =
|
|
106
|
+
r = self.new()
|
|
59
107
|
for k in self.keys:
|
|
60
108
|
setattr(r, k, getattr(self, k)[idx])
|
|
61
109
|
return r
|
|
@@ -69,25 +117,25 @@ class Results(SimpleClass):
|
|
|
69
117
|
self.probs = probs
|
|
70
118
|
|
|
71
119
|
def cpu(self):
|
|
72
|
-
r =
|
|
120
|
+
r = self.new()
|
|
73
121
|
for k in self.keys:
|
|
74
122
|
setattr(r, k, getattr(self, k).cpu())
|
|
75
123
|
return r
|
|
76
124
|
|
|
77
125
|
def numpy(self):
|
|
78
|
-
r =
|
|
126
|
+
r = self.new()
|
|
79
127
|
for k in self.keys:
|
|
80
128
|
setattr(r, k, getattr(self, k).numpy())
|
|
81
129
|
return r
|
|
82
130
|
|
|
83
131
|
def cuda(self):
|
|
84
|
-
r =
|
|
132
|
+
r = self.new()
|
|
85
133
|
for k in self.keys:
|
|
86
134
|
setattr(r, k, getattr(self, k).cuda())
|
|
87
135
|
return r
|
|
88
136
|
|
|
89
137
|
def to(self, *args, **kwargs):
|
|
90
|
-
r =
|
|
138
|
+
r = self.new()
|
|
91
139
|
for k in self.keys:
|
|
92
140
|
setattr(r, k, getattr(self, k).to(*args, **kwargs))
|
|
93
141
|
return r
|
|
@@ -96,6 +144,9 @@ class Results(SimpleClass):
|
|
|
96
144
|
for k in self.keys:
|
|
97
145
|
return len(getattr(self, k))
|
|
98
146
|
|
|
147
|
+
def new(self):
|
|
148
|
+
return Results(orig_img=self.orig_img, path=self.path, names=self.names)
|
|
149
|
+
|
|
99
150
|
@property
|
|
100
151
|
def keys(self):
|
|
101
152
|
return [k for k in self._keys if getattr(self, k) is not None]
|
|
@@ -109,6 +160,7 @@ class Results(SimpleClass):
|
|
|
109
160
|
pil=False,
|
|
110
161
|
example='abc',
|
|
111
162
|
img=None,
|
|
163
|
+
kpt_line=True,
|
|
112
164
|
labels=True,
|
|
113
165
|
boxes=True,
|
|
114
166
|
masks=True,
|
|
@@ -126,6 +178,7 @@ class Results(SimpleClass):
|
|
|
126
178
|
pil (bool): Whether to return the image as a PIL Image.
|
|
127
179
|
example (str): An example string to display. Useful for indicating the expected format of the output.
|
|
128
180
|
img (numpy.ndarray): Plot to another image. if not, plot to original image.
|
|
181
|
+
kpt_line (bool): Whether to draw lines connecting keypoints.
|
|
129
182
|
labels (bool): Whether to plot the label of bounding boxes.
|
|
130
183
|
boxes (bool): Whether to plot the bounding boxes.
|
|
131
184
|
masks (bool): Whether to plot the masks.
|
|
@@ -146,11 +199,12 @@ class Results(SimpleClass):
|
|
|
146
199
|
pred_masks, show_masks = self.masks, masks
|
|
147
200
|
pred_probs, show_probs = self.probs, probs
|
|
148
201
|
names = self.names
|
|
202
|
+
keypoints = self.keypoints
|
|
149
203
|
if pred_boxes and show_boxes:
|
|
150
204
|
for d in reversed(pred_boxes):
|
|
151
205
|
c, conf, id = int(d.cls), float(d.conf) if conf else None, None if d.id is None else int(d.id.item())
|
|
152
206
|
name = ('' if id is None else f'id:{id} ') + names[c]
|
|
153
|
-
label = (
|
|
207
|
+
label = (f'{name} {conf:.2f}' if conf else name) if labels else None
|
|
154
208
|
annotator.box_label(d.xyxy.squeeze(), label, color=colors(c, True))
|
|
155
209
|
|
|
156
210
|
if pred_masks and show_masks:
|
|
@@ -168,10 +222,14 @@ class Results(SimpleClass):
|
|
|
168
222
|
text = f"{', '.join(f'{names[j] if names else j} {pred_probs[j]:.2f}' for j in top5i)}, "
|
|
169
223
|
annotator.text((32, 32), text, txt_color=(255, 255, 255)) # TODO: allow setting colors
|
|
170
224
|
|
|
225
|
+
if keypoints is not None:
|
|
226
|
+
for k in reversed(keypoints):
|
|
227
|
+
annotator.kpts(k, self.orig_shape, kpt_line=kpt_line)
|
|
228
|
+
|
|
171
229
|
return np.asarray(annotator.im) if annotator.pil else annotator.im
|
|
172
230
|
|
|
173
231
|
|
|
174
|
-
class Boxes(
|
|
232
|
+
class Boxes(BaseTensor):
|
|
175
233
|
"""
|
|
176
234
|
A class for storing and manipulating detection boxes.
|
|
177
235
|
|
|
@@ -246,37 +304,15 @@ class Boxes(SimpleClass):
|
|
|
246
304
|
def xywhn(self):
|
|
247
305
|
return self.xywh / self.orig_shape[[1, 0, 1, 0]]
|
|
248
306
|
|
|
249
|
-
def cpu(self):
|
|
250
|
-
return Boxes(self.boxes.cpu(), self.orig_shape)
|
|
251
|
-
|
|
252
|
-
def numpy(self):
|
|
253
|
-
return Boxes(self.boxes.numpy(), self.orig_shape)
|
|
254
|
-
|
|
255
|
-
def cuda(self):
|
|
256
|
-
return Boxes(self.boxes.cuda(), self.orig_shape)
|
|
257
|
-
|
|
258
|
-
def to(self, *args, **kwargs):
|
|
259
|
-
return Boxes(self.boxes.to(*args, **kwargs), self.orig_shape)
|
|
260
|
-
|
|
261
307
|
def pandas(self):
|
|
262
308
|
LOGGER.info('results.pandas() method not yet implemented')
|
|
263
309
|
|
|
264
|
-
@property
|
|
265
|
-
def shape(self):
|
|
266
|
-
return self.boxes.shape
|
|
267
|
-
|
|
268
310
|
@property
|
|
269
311
|
def data(self):
|
|
270
312
|
return self.boxes
|
|
271
313
|
|
|
272
|
-
def __len__(self): # override len(results)
|
|
273
|
-
return len(self.boxes)
|
|
274
|
-
|
|
275
|
-
def __getitem__(self, idx):
|
|
276
|
-
return Boxes(self.boxes[idx], self.orig_shape)
|
|
277
|
-
|
|
278
314
|
|
|
279
|
-
class Masks(
|
|
315
|
+
class Masks(BaseTensor):
|
|
280
316
|
"""
|
|
281
317
|
A class for storing and manipulating detection masks.
|
|
282
318
|
|
|
@@ -316,7 +352,7 @@ class Masks(SimpleClass):
|
|
|
316
352
|
def xyn(self):
|
|
317
353
|
# Segments (normalized)
|
|
318
354
|
return [
|
|
319
|
-
ops.
|
|
355
|
+
ops.scale_coords(self.masks.shape[1:], x, self.orig_shape, normalize=True)
|
|
320
356
|
for x in ops.masks2segments(self.masks)]
|
|
321
357
|
|
|
322
358
|
@property
|
|
@@ -324,31 +360,9 @@ class Masks(SimpleClass):
|
|
|
324
360
|
def xy(self):
|
|
325
361
|
# Segments (pixels)
|
|
326
362
|
return [
|
|
327
|
-
ops.
|
|
363
|
+
ops.scale_coords(self.masks.shape[1:], x, self.orig_shape, normalize=False)
|
|
328
364
|
for x in ops.masks2segments(self.masks)]
|
|
329
365
|
|
|
330
|
-
@property
|
|
331
|
-
def shape(self):
|
|
332
|
-
return self.masks.shape
|
|
333
|
-
|
|
334
366
|
@property
|
|
335
367
|
def data(self):
|
|
336
368
|
return self.masks
|
|
337
|
-
|
|
338
|
-
def cpu(self):
|
|
339
|
-
return Masks(self.masks.cpu(), self.orig_shape)
|
|
340
|
-
|
|
341
|
-
def numpy(self):
|
|
342
|
-
return Masks(self.masks.numpy(), self.orig_shape)
|
|
343
|
-
|
|
344
|
-
def cuda(self):
|
|
345
|
-
return Masks(self.masks.cuda(), self.orig_shape)
|
|
346
|
-
|
|
347
|
-
def to(self, *args, **kwargs):
|
|
348
|
-
return Masks(self.masks.to(*args, **kwargs), self.orig_shape)
|
|
349
|
-
|
|
350
|
-
def __len__(self): # override len(results)
|
|
351
|
-
return len(self.masks)
|
|
352
|
-
|
|
353
|
-
def __getitem__(self, idx):
|
|
354
|
-
return Masks(self.masks[idx], self.orig_shape)
|
|
@@ -75,11 +75,13 @@ def benchmark(model=Path(SETTINGS['weights_dir']) / 'yolov8n.pt', imgsz=160, hal
|
|
|
75
75
|
|
|
76
76
|
# Validate
|
|
77
77
|
if model.task == 'detect':
|
|
78
|
-
data, key = '
|
|
78
|
+
data, key = 'coco8.yaml', 'metrics/mAP50-95(B)'
|
|
79
79
|
elif model.task == 'segment':
|
|
80
|
-
data, key = '
|
|
80
|
+
data, key = 'coco8-seg.yaml', 'metrics/mAP50-95(M)'
|
|
81
81
|
elif model.task == 'classify':
|
|
82
82
|
data, key = 'imagenet100', 'metrics/accuracy_top5'
|
|
83
|
+
elif model.task == 'pose':
|
|
84
|
+
data, key = 'coco8-pose.yaml', 'metrics/mAP50-95(P)'
|
|
83
85
|
|
|
84
86
|
results = export.val(data=data, batch=1, imgsz=imgsz, plots=False, device=device, half=half, verbose=False)
|
|
85
87
|
metric, speed = results.results_dict[key], results.speed['inference']
|
|
@@ -14,9 +14,9 @@ from tqdm import tqdm
|
|
|
14
14
|
|
|
15
15
|
from ultralytics.yolo.utils import LOGGER, checks, emojis, is_online
|
|
16
16
|
|
|
17
|
-
GITHUB_ASSET_NAMES = [f'yolov8{
|
|
18
|
-
[f'yolov5{
|
|
19
|
-
[f'yolov3{
|
|
17
|
+
GITHUB_ASSET_NAMES = [f'yolov8{k}{suffix}.pt' for k in 'nsmlx' for suffix in ('', '6', '-cls', '-seg', '-pose')] + \
|
|
18
|
+
[f'yolov5{k}u.pt' for k in 'nsmlx'] + \
|
|
19
|
+
[f'yolov3{k}u.pt' for k in ('', '-spp', '-tiny')]
|
|
20
20
|
GITHUB_ASSET_STEMS = [Path(k).stem for k in GITHUB_ASSET_NAMES]
|
|
21
21
|
|
|
22
22
|
|
|
@@ -168,7 +168,7 @@ class Instances:
|
|
|
168
168
|
Args:
|
|
169
169
|
bboxes (ndarray): bboxes with shape [N, 4].
|
|
170
170
|
segments (list | ndarray): segments.
|
|
171
|
-
keypoints (ndarray): keypoints with shape [N, 17,
|
|
171
|
+
keypoints (ndarray): keypoints(x, y, visible) with shape [N, 17, 3].
|
|
172
172
|
"""
|
|
173
173
|
if segments is None:
|
|
174
174
|
segments = []
|
ultralytics/yolo/utils/loss.py
CHANGED
|
@@ -54,3 +54,17 @@ class BboxLoss(nn.Module):
|
|
|
54
54
|
wr = 1 - wl # weight right
|
|
55
55
|
return (F.cross_entropy(pred_dist, tl.view(-1), reduction='none').view(tl.shape) * wl +
|
|
56
56
|
F.cross_entropy(pred_dist, tr.view(-1), reduction='none').view(tl.shape) * wr).mean(-1, keepdim=True)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class KeypointLoss(nn.Module):
|
|
60
|
+
|
|
61
|
+
def __init__(self, sigmas) -> None:
|
|
62
|
+
super().__init__()
|
|
63
|
+
self.sigmas = sigmas
|
|
64
|
+
|
|
65
|
+
def forward(self, pred_kpts, gt_kpts, kpt_mask, area):
|
|
66
|
+
d = (pred_kpts[..., 0] - gt_kpts[..., 0]) ** 2 + (pred_kpts[..., 1] - gt_kpts[..., 1]) ** 2
|
|
67
|
+
kpt_loss_factor = (torch.sum(kpt_mask != 0) + torch.sum(kpt_mask == 0)) / (torch.sum(kpt_mask != 0) + 1e-9)
|
|
68
|
+
# e = d / (2 * (area * self.sigmas) ** 2 + 1e-9) # from formula
|
|
69
|
+
e = d / (2 * self.sigmas) ** 2 / (area + 1e-9) / 2 # from cocoeval
|
|
70
|
+
return kpt_loss_factor * ((1 - torch.exp(-e)) * kpt_mask).mean()
|