ultralytics 8.0.72__tar.gz → 8.0.74__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.

Potentially problematic release.


This version of ultralytics might be problematic. Click here for more details.

Files changed (130) hide show
  1. {ultralytics-8.0.72/ultralytics.egg-info → ultralytics-8.0.74}/PKG-INFO +1 -1
  2. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/__init__.py +1 -1
  3. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/hub/session.py +15 -13
  4. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/nn/modules.py +1 -1
  5. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/nn/tasks.py +1 -1
  6. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/data/augment.py +10 -10
  7. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/data/base.py +4 -5
  8. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/data/utils.py +3 -1
  9. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/engine/model.py +3 -2
  10. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/engine/predictor.py +29 -2
  11. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/engine/results.py +78 -1
  12. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/engine/trainer.py +1 -1
  13. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/__init__.py +3 -2
  14. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/callbacks/hub.py +1 -1
  15. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/ops.py +4 -2
  16. ultralytics-8.0.74/ultralytics/yolo/v8/classify/predict.py +42 -0
  17. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/detect/predict.py +0 -43
  18. ultralytics-8.0.74/ultralytics/yolo/v8/pose/predict.py +52 -0
  19. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/segment/predict.py +0 -50
  20. {ultralytics-8.0.72 → ultralytics-8.0.74/ultralytics.egg-info}/PKG-INFO +1 -1
  21. ultralytics-8.0.72/ultralytics/yolo/v8/classify/predict.py +0 -83
  22. ultralytics-8.0.72/ultralytics/yolo/v8/pose/predict.py +0 -97
  23. {ultralytics-8.0.72 → ultralytics-8.0.74}/CONTRIBUTING.md +0 -0
  24. {ultralytics-8.0.72 → ultralytics-8.0.74}/LICENSE +0 -0
  25. {ultralytics-8.0.72 → ultralytics-8.0.74}/MANIFEST.in +0 -0
  26. {ultralytics-8.0.72 → ultralytics-8.0.74}/README.md +0 -0
  27. {ultralytics-8.0.72 → ultralytics-8.0.74}/README.zh-CN.md +0 -0
  28. {ultralytics-8.0.72 → ultralytics-8.0.74}/requirements.txt +0 -0
  29. {ultralytics-8.0.72 → ultralytics-8.0.74}/setup.cfg +0 -0
  30. {ultralytics-8.0.72 → ultralytics-8.0.74}/setup.py +0 -0
  31. {ultralytics-8.0.72 → ultralytics-8.0.74}/tests/test_cli.py +0 -0
  32. {ultralytics-8.0.72 → ultralytics-8.0.74}/tests/test_engine.py +0 -0
  33. {ultralytics-8.0.72 → ultralytics-8.0.74}/tests/test_python.py +0 -0
  34. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/assets/bus.jpg +0 -0
  35. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/assets/zidane.jpg +0 -0
  36. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/Argoverse.yaml +0 -0
  37. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/GlobalWheat2020.yaml +0 -0
  38. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/ImageNet.yaml +0 -0
  39. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/Objects365.yaml +0 -0
  40. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/SKU-110K.yaml +0 -0
  41. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/VOC.yaml +0 -0
  42. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/VisDrone.yaml +0 -0
  43. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/coco-pose.yaml +0 -0
  44. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/coco.yaml +0 -0
  45. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/coco128-seg.yaml +0 -0
  46. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/coco128.yaml +0 -0
  47. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/coco8-pose.yaml +0 -0
  48. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/coco8-seg.yaml +0 -0
  49. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/coco8.yaml +0 -0
  50. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/datasets/xView.yaml +0 -0
  51. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/hub/__init__.py +0 -0
  52. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/hub/auth.py +0 -0
  53. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/hub/utils.py +0 -0
  54. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/models/v3/yolov3-spp.yaml +0 -0
  55. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/models/v3/yolov3-tiny.yaml +0 -0
  56. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/models/v3/yolov3.yaml +0 -0
  57. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/models/v5/yolov5-p6.yaml +0 -0
  58. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/models/v5/yolov5.yaml +0 -0
  59. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/models/v8/yolov8-cls.yaml +0 -0
  60. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/models/v8/yolov8-p2.yaml +0 -0
  61. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/models/v8/yolov8-p6.yaml +0 -0
  62. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/models/v8/yolov8-pose-p6.yaml +0 -0
  63. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/models/v8/yolov8-pose.yaml +0 -0
  64. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/models/v8/yolov8-seg.yaml +0 -0
  65. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/models/v8/yolov8.yaml +0 -0
  66. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/nn/__init__.py +0 -0
  67. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/nn/autobackend.py +0 -0
  68. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/nn/autoshape.py +0 -0
  69. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/tracker/__init__.py +0 -0
  70. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/tracker/cfg/botsort.yaml +0 -0
  71. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/tracker/cfg/bytetrack.yaml +0 -0
  72. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/tracker/track.py +0 -0
  73. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/tracker/trackers/__init__.py +0 -0
  74. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/tracker/trackers/basetrack.py +0 -0
  75. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/tracker/trackers/bot_sort.py +0 -0
  76. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/tracker/trackers/byte_tracker.py +0 -0
  77. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/tracker/utils/__init__.py +0 -0
  78. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/tracker/utils/gmc.py +0 -0
  79. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/tracker/utils/kalman_filter.py +0 -0
  80. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/tracker/utils/matching.py +0 -0
  81. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/__init__.py +0 -0
  82. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/cfg/__init__.py +0 -0
  83. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/cfg/default.yaml +0 -0
  84. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/data/__init__.py +0 -0
  85. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/data/build.py +0 -0
  86. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/data/dataloaders/__init__.py +0 -0
  87. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/data/dataloaders/stream_loaders.py +0 -0
  88. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/data/dataloaders/v5augmentations.py +0 -0
  89. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/data/dataloaders/v5loader.py +0 -0
  90. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/data/dataset.py +0 -0
  91. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/data/dataset_wrappers.py +0 -0
  92. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/engine/__init__.py +0 -0
  93. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/engine/exporter.py +0 -0
  94. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/engine/validator.py +0 -0
  95. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/autobatch.py +0 -0
  96. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/benchmarks.py +0 -0
  97. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/callbacks/__init__.py +0 -0
  98. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/callbacks/base.py +0 -0
  99. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/callbacks/clearml.py +0 -0
  100. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/callbacks/comet.py +0 -0
  101. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/callbacks/mlflow.py +0 -0
  102. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/callbacks/tensorboard.py +0 -0
  103. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/checks.py +0 -0
  104. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/dist.py +0 -0
  105. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/downloads.py +0 -0
  106. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/files.py +0 -0
  107. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/instance.py +0 -0
  108. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/loss.py +0 -0
  109. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/metrics.py +0 -0
  110. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/plotting.py +0 -0
  111. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/tal.py +0 -0
  112. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/utils/torch_utils.py +0 -0
  113. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/__init__.py +0 -0
  114. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/classify/__init__.py +0 -0
  115. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/classify/train.py +0 -0
  116. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/classify/val.py +0 -0
  117. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/detect/__init__.py +0 -0
  118. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/detect/train.py +0 -0
  119. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/detect/val.py +0 -0
  120. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/pose/__init__.py +0 -0
  121. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/pose/train.py +0 -0
  122. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/pose/val.py +0 -0
  123. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/segment/__init__.py +0 -0
  124. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/segment/train.py +0 -0
  125. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics/yolo/v8/segment/val.py +0 -0
  126. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics.egg-info/SOURCES.txt +0 -0
  127. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics.egg-info/dependency_links.txt +0 -0
  128. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics.egg-info/entry_points.txt +0 -0
  129. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics.egg-info/requires.txt +0 -0
  130. {ultralytics-8.0.72 → ultralytics-8.0.74}/ultralytics.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ultralytics
3
- Version: 8.0.72
3
+ Version: 8.0.74
4
4
  Summary: Ultralytics YOLOv8
5
5
  Home-page: https://github.com/ultralytics/ultralytics
6
6
  Author: Ultralytics
@@ -1,6 +1,6 @@
1
1
  # Ultralytics YOLO 🚀, GPL-3.0 license
2
2
 
3
- __version__ = '8.0.72'
3
+ __version__ = '8.0.74'
4
4
 
5
5
  from ultralytics.hub import start
6
6
  from ultralytics.yolo.engine.model import YOLO
@@ -112,19 +112,21 @@ class HUBTrainingSession:
112
112
  raise ValueError('Dataset may still be processing. Please wait a minute and try again.') # RF fix
113
113
  self.model_id = data['id']
114
114
 
115
- # TODO: restore when server keys when dataset URL and GPU train is working
116
-
117
- self.train_args = {
118
- 'batch': data['batch_size'],
119
- 'epochs': data['epochs'],
120
- 'imgsz': data['imgsz'],
121
- 'patience': data['patience'],
122
- 'device': data['device'],
123
- 'cache': data['cache'],
124
- 'data': data['data']}
125
-
126
- self.model_file = data.get('cfg', data['weights'])
127
- self.model_file = checks.check_yolov5u_filename(self.model_file, verbose=False) # YOLOv5->YOLOv5u
115
+ if data['status'] == 'new': # new model to start training
116
+ self.train_args = {
117
+ # TODO: deprecate 'batch_size' key for 'batch' in 3Q23
118
+ 'batch': data['batch' if ('batch' in data) else 'batch_size'],
119
+ 'epochs': data['epochs'],
120
+ 'imgsz': data['imgsz'],
121
+ 'patience': data['patience'],
122
+ 'device': data['device'],
123
+ 'cache': data['cache'],
124
+ 'data': data['data']}
125
+ self.model_file = data.get('cfg', data['weights'])
126
+ self.model_file = checks.check_yolov5u_filename(self.model_file, verbose=False) # YOLOv5->YOLOv5u
127
+ elif data['status'] == 'training': # existing model to resume training
128
+ self.train_args = {'data': data['data'], 'resume': True}
129
+ self.model_file = data['resume']
128
130
 
129
131
  return data
130
132
  except requests.exceptions.ConnectionError as e:
@@ -374,7 +374,7 @@ class Ensemble(nn.ModuleList):
374
374
  y = [module(x, augment, profile, visualize)[0] for module in self]
375
375
  # y = torch.stack(y).max(0)[0] # max ensemble
376
376
  # y = torch.stack(y).mean(0) # mean ensemble
377
- y = torch.cat(y, 1) # nms ensemble
377
+ y = torch.cat(y, 2) # nms ensemble, y shape(B, HW, C)
378
378
  return y, None # inference, train output
379
379
 
380
380
 
@@ -539,7 +539,7 @@ def guess_model_task(model):
539
539
  model (nn.Module) or (dict): PyTorch model or model configuration in YAML format.
540
540
 
541
541
  Returns:
542
- str: Task of the model ('detect', 'segment', 'classify').
542
+ str: Task of the model ('detect', 'segment', 'classify', 'pose').
543
543
 
544
544
  Raises:
545
545
  SyntaxError: If the task of the model could not be determined.
@@ -127,7 +127,7 @@ class Mosaic(BaseMixTransform):
127
127
  s = self.imgsz
128
128
  yc, xc = (int(random.uniform(-x, 2 * s + x)) for x in self.border) # mosaic center x, y
129
129
  for i in range(4):
130
- labels_patch = (labels if i == 0 else labels['mix_labels'][i - 1]).copy()
130
+ labels_patch = labels if i == 0 else labels['mix_labels'][i - 1]
131
131
  # Load image
132
132
  img = labels_patch['img']
133
133
  h, w = labels_patch.pop('resized_shape')
@@ -223,18 +223,18 @@ class RandomPerspective:
223
223
 
224
224
  def affine_transform(self, img, border):
225
225
  # Center
226
- C = np.eye(3)
226
+ C = np.eye(3, dtype=np.float32)
227
227
 
228
228
  C[0, 2] = -img.shape[1] / 2 # x translation (pixels)
229
229
  C[1, 2] = -img.shape[0] / 2 # y translation (pixels)
230
230
 
231
231
  # Perspective
232
- P = np.eye(3)
232
+ P = np.eye(3, dtype=np.float32)
233
233
  P[2, 0] = random.uniform(-self.perspective, self.perspective) # x perspective (about y)
234
234
  P[2, 1] = random.uniform(-self.perspective, self.perspective) # y perspective (about x)
235
235
 
236
236
  # Rotation and Scale
237
- R = np.eye(3)
237
+ R = np.eye(3, dtype=np.float32)
238
238
  a = random.uniform(-self.degrees, self.degrees)
239
239
  # a += random.choice([-180, -90, 0, 90]) # add 90deg rotations to small rotations
240
240
  s = random.uniform(1 - self.scale, 1 + self.scale)
@@ -242,12 +242,12 @@ class RandomPerspective:
242
242
  R[:2] = cv2.getRotationMatrix2D(angle=a, center=(0, 0), scale=s)
243
243
 
244
244
  # Shear
245
- S = np.eye(3)
245
+ S = np.eye(3, dtype=np.float32)
246
246
  S[0, 1] = math.tan(random.uniform(-self.shear, self.shear) * math.pi / 180) # x shear (deg)
247
247
  S[1, 0] = math.tan(random.uniform(-self.shear, self.shear) * math.pi / 180) # y shear (deg)
248
248
 
249
249
  # Translation
250
- T = np.eye(3)
250
+ T = np.eye(3, dtype=np.float32)
251
251
  T[0, 2] = random.uniform(0.5 - self.translate, 0.5 + self.translate) * self.size[0] # x translation (pixels)
252
252
  T[1, 2] = random.uniform(0.5 - self.translate, 0.5 + self.translate) * self.size[1] # y translation (pixels)
253
253
 
@@ -274,7 +274,7 @@ class RandomPerspective:
274
274
  if n == 0:
275
275
  return bboxes
276
276
 
277
- xy = np.ones((n * 4, 3))
277
+ xy = np.ones((n * 4, 3), dtype=bboxes.dtype)
278
278
  xy[:, :2] = bboxes[:, [0, 1, 2, 3, 0, 3, 2, 1]].reshape(n * 4, 2) # x1y1, x2y2, x1y2, x2y1
279
279
  xy = xy @ M.T # transform
280
280
  xy = (xy[:, :2] / xy[:, 2:3] if self.perspective else xy[:, :2]).reshape(n, 8) # perspective rescale or affine
@@ -282,7 +282,7 @@ class RandomPerspective:
282
282
  # create new boxes
283
283
  x = xy[:, [0, 2, 4, 6]]
284
284
  y = xy[:, [1, 3, 5, 7]]
285
- return np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T
285
+ return np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1)), dtype=bboxes.dtype).reshape(4, n).T
286
286
 
287
287
  def apply_segments(self, segments, M):
288
288
  """apply affine to segments and generate new bboxes from segments.
@@ -298,7 +298,7 @@ class RandomPerspective:
298
298
  if n == 0:
299
299
  return [], segments
300
300
 
301
- xy = np.ones((n * num, 3))
301
+ xy = np.ones((n * num, 3), dtype=segments.dtype)
302
302
  segments = segments.reshape(-1, 2)
303
303
  xy[:, :2] = segments
304
304
  xy = xy @ M.T # transform
@@ -319,7 +319,7 @@ class RandomPerspective:
319
319
  n, nkpt = keypoints.shape[:2]
320
320
  if n == 0:
321
321
  return keypoints
322
- xy = np.ones((n * nkpt, 3))
322
+ xy = np.ones((n * nkpt, 3), dtype=keypoints.dtype)
323
323
  visible = keypoints[..., 2].reshape(n * nkpt, 1)
324
324
  xy[:, :2] = keypoints[..., :2].reshape(n * nkpt, 2)
325
325
  xy = xy @ M.T # transform
@@ -3,6 +3,7 @@
3
3
  import glob
4
4
  import math
5
5
  import os
6
+ from copy import deepcopy
6
7
  from multiprocessing.pool import ThreadPool
7
8
  from pathlib import Path
8
9
  from typing import Optional
@@ -177,13 +178,11 @@ class BaseDataset(Dataset):
177
178
  return self.transforms(self.get_label_info(index))
178
179
 
179
180
  def get_label_info(self, index):
180
- label = self.labels[index].copy()
181
+ label = deepcopy(self.labels[index]) # requires deepcopy() https://github.com/ultralytics/ultralytics/pull/1948
181
182
  label.pop('shape', None) # shape is for rect, remove it
182
183
  label['img'], label['ori_shape'], label['resized_shape'] = self.load_image(index)
183
- label['ratio_pad'] = (
184
- label['resized_shape'][0] / label['ori_shape'][0],
185
- label['resized_shape'][1] / label['ori_shape'][1],
186
- ) # for evaluation
184
+ label['ratio_pad'] = (label['resized_shape'][0] / label['ori_shape'][0],
185
+ label['resized_shape'][1] / label['ori_shape'][1]) # for evaluation
187
186
  if self.rect:
188
187
  label['rect_shape'] = self.batch_shapes[self.batch[index]]
189
188
  label = self.update_labels_info(label)
@@ -17,7 +17,8 @@ from PIL import ExifTags, Image, ImageOps
17
17
  from tqdm import tqdm
18
18
 
19
19
  from ultralytics.nn.autobackend import check_class_names
20
- from ultralytics.yolo.utils import DATASETS_DIR, LOGGER, NUM_THREADS, ROOT, clean_url, colorstr, emojis, yaml_load
20
+ from ultralytics.yolo.utils import (DATASETS_DIR, LOGGER, NUM_THREADS, ROOT, SETTINGS_YAML, clean_url, colorstr, emojis,
21
+ yaml_load)
21
22
  from ultralytics.yolo.utils.checks import check_file, check_font, is_ascii
22
23
  from ultralytics.yolo.utils.downloads import download, safe_download, unzip_file
23
24
  from ultralytics.yolo.utils.ops import segments2boxes
@@ -246,6 +247,7 @@ def check_det_dataset(dataset, autodownload=True):
246
247
  if s and autodownload:
247
248
  LOGGER.warning(m)
248
249
  else:
250
+ m += f"\nNote dataset download directory is '{DATASETS_DIR}'. You can update this in '{SETTINGS_YAML}'"
249
251
  raise FileNotFoundError(m)
250
252
  t = time.time()
251
253
  if s.startswith('http') and s.endswith('.zip'): # URL
@@ -166,7 +166,9 @@ class YOLO:
166
166
  """
167
167
  Raises TypeError is model is not a PyTorch model
168
168
  """
169
- if not isinstance(self.model, nn.Module):
169
+ pt_str = isinstance(self.model, (str, Path)) and Path(self.model).suffix == '.pt'
170
+ pt_module = isinstance(self.model, nn.Module)
171
+ if not (pt_module or pt_str):
170
172
  raise TypeError(f"model='{self.model}' must be a *.pt PyTorch model, but is a different type. "
171
173
  f'PyTorch models can be used to train, val, predict and export, i.e. '
172
174
  f"'yolo export model=yolov8n.pt', but exported formats like ONNX, TensorRT etc. only "
@@ -356,7 +358,6 @@ class YOLO:
356
358
  raise AttributeError("Dataset required but missing, i.e. pass 'data=coco128.yaml'")
357
359
  if overrides.get('resume'):
358
360
  overrides['resume'] = self.ckpt_path
359
-
360
361
  self.task = overrides.get('task') or self.task
361
362
  self.trainer = TASK_MAP[self.task][1](overrides=overrides, _callbacks=self.callbacks)
362
363
  if not overrides.get('resume'): # manually set model only if not resuming
@@ -109,8 +109,35 @@ class BasePredictor:
109
109
  def preprocess(self, img):
110
110
  pass
111
111
 
112
- def write_results(self, results, batch, print_string):
113
- raise NotImplementedError('print_results function needs to be implemented')
112
+ def write_results(self, idx, results, batch):
113
+ p, im, _ = batch
114
+ log_string = ''
115
+ if len(im.shape) == 3:
116
+ im = im[None] # expand for batch dim
117
+ self.seen += 1
118
+ if self.source_type.webcam or self.source_type.from_img: # batch_size >= 1
119
+ log_string += f'{idx}: '
120
+ frame = self.dataset.count
121
+ else:
122
+ frame = getattr(self.dataset, 'frame', 0)
123
+ self.data_path = p
124
+ self.txt_path = str(self.save_dir / 'labels' / p.stem) + ('' if self.dataset.mode == 'image' else f'_{frame}')
125
+ log_string += '%gx%g ' % im.shape[2:] # print string
126
+ result = results[idx]
127
+ log_string += result.verbose()
128
+
129
+ if self.args.save or self.args.show: # Add bbox to image
130
+ plot_args = dict(line_width=self.args.line_thickness, boxes=self.args.boxes)
131
+ if not self.args.retina_masks:
132
+ plot_args['im_gpu'] = im[idx]
133
+ self.plotted_img = result.plot(**plot_args)
134
+ # write
135
+ if self.args.save_txt:
136
+ result.save_txt(f'{self.txt_path}.txt', save_conf=self.args.save_conf)
137
+ if self.args.save_crop:
138
+ result.save_crop(save_dir=self.save_dir / 'crops', file_name=self.data_path.stem)
139
+
140
+ return log_string
114
141
 
115
142
  def postprocess(self, preds, img, orig_img):
116
143
  return preds
@@ -7,13 +7,14 @@ Usage: See https://docs.ultralytics.com/modes/predict/
7
7
 
8
8
  from copy import deepcopy
9
9
  from functools import lru_cache
10
+ from pathlib import Path
10
11
 
11
12
  import numpy as np
12
13
  import torch
13
14
 
14
15
  from ultralytics.yolo.data.augment import LetterBox
15
16
  from ultralytics.yolo.utils import LOGGER, SimpleClass, deprecation_warn, ops
16
- from ultralytics.yolo.utils.plotting import Annotator, colors
17
+ from ultralytics.yolo.utils.plotting import Annotator, colors, save_one_box
17
18
 
18
19
 
19
20
  class BaseTensor(SimpleClass):
@@ -233,6 +234,80 @@ class Results(SimpleClass):
233
234
 
234
235
  return annotator.result()
235
236
 
237
+ def verbose(self):
238
+ """
239
+ Return log string for each tasks.
240
+ """
241
+ log_string = ''
242
+ probs = self.probs
243
+ boxes = self.boxes
244
+ if len(self) == 0:
245
+ return log_string if probs is not None else log_string + '(no detections), '
246
+ if probs is not None:
247
+ n5 = min(len(self.names), 5)
248
+ top5i = probs.argsort(0, descending=True)[:n5].tolist() # top 5 indices
249
+ log_string += f"{', '.join(f'{self.names[j]} {probs[j]:.2f}' for j in top5i)}, "
250
+ if boxes:
251
+ for c in boxes.cls.unique():
252
+ n = (boxes.cls == c).sum() # detections per class
253
+ log_string += f"{n} {self.names[int(c)]}{'s' * (n > 1)}, "
254
+ return log_string
255
+
256
+ def save_txt(self, txt_file, save_conf=False):
257
+ """Save predictions into txt file.
258
+
259
+ Args:
260
+ txt_file (str): txt file path.
261
+ save_conf (bool): save confidence score or not.
262
+ """
263
+ boxes = self.boxes
264
+ masks = self.masks
265
+ probs = self.probs
266
+ kpts = self.keypoints
267
+ texts = []
268
+ if probs is not None:
269
+ # classify
270
+ n5 = min(len(self.names), 5)
271
+ top5i = probs.argsort(0, descending=True)[:n5].tolist() # top 5 indices
272
+ [texts.append(f'{probs[j]:.2f} {self.names[j]}') for j in top5i]
273
+ elif boxes:
274
+ # detect/segment/pose
275
+ for j, d in enumerate(boxes):
276
+ c, conf, id = int(d.cls), float(d.conf), None if d.id is None else int(d.id.item())
277
+ line = (c, *d.xywhn.view(-1))
278
+ if masks:
279
+ seg = masks[j].xyn[0].copy().reshape(-1) # reversed mask.xyn, (n,2) to (n*2)
280
+ line = (c, *seg)
281
+ if kpts is not None:
282
+ kpt = (kpts[j][:, :2] / d.orig_shape[[1, 0]]).reshape(-1).tolist()
283
+ line += (*kpt, )
284
+ line += (conf, ) * save_conf + (() if id is None else (id, ))
285
+ texts.append(('%g ' * len(line)).rstrip() % line)
286
+
287
+ with open(txt_file, 'a') as f:
288
+ for text in texts:
289
+ f.write(text + '\n')
290
+
291
+ def save_crop(self, save_dir, file_name=Path('im.jpg')):
292
+ """Save cropped predictions to `save_dir/cls/file_name.jpg`.
293
+
294
+ Args:
295
+ save_dir (str | pathlib.Path): Save path.
296
+ file_name (str | pathlib.Path): File name.
297
+ """
298
+ if self.probs is not None:
299
+ LOGGER.warning('Warning: Classify task do not support `save_crop`.')
300
+ return
301
+ if isinstance(save_dir, str):
302
+ save_dir = Path(save_dir)
303
+ if isinstance(file_name, str):
304
+ file_name = Path(file_name)
305
+ for d in self.boxes:
306
+ save_one_box(d.xyxy,
307
+ self.orig_img.copy(),
308
+ file=save_dir / self.names[int(d.cls)] / f'{file_name.stem}.jpg',
309
+ BGR=True)
310
+
236
311
 
237
312
  class Boxes(BaseTensor):
238
313
  """
@@ -339,6 +414,8 @@ class Masks(BaseTensor):
339
414
  """
340
415
 
341
416
  def __init__(self, masks, orig_shape) -> None:
417
+ if masks.ndim == 2:
418
+ masks = masks[None, :]
342
419
  self.masks = masks # N, h, w
343
420
  self.orig_shape = orig_shape
344
421
 
@@ -552,7 +552,7 @@ class BaseTrainer:
552
552
  if self.resume:
553
553
  assert start_epoch > 0, \
554
554
  f'{self.args.model} training to {self.epochs} epochs is finished, nothing to resume.\n' \
555
- f"Start a new training without --resume, i.e. 'yolo task=... mode=train model={self.args.model}'"
555
+ f"Start a new training without resuming, i.e. 'yolo train model={self.args.model}'"
556
556
  LOGGER.info(
557
557
  f'Resuming training from {self.args.model} from epoch {start_epoch + 1} to {self.epochs} total epochs')
558
558
  if self.epochs < start_epoch:
@@ -490,6 +490,7 @@ def get_user_config_dir(sub_dir='Ultralytics'):
490
490
 
491
491
 
492
492
  USER_CONFIG_DIR = Path(os.getenv('YOLO_CONFIG_DIR', get_user_config_dir())) # Ultralytics settings dir
493
+ SETTINGS_YAML = USER_CONFIG_DIR / 'settings.yaml'
493
494
 
494
495
 
495
496
  def emojis(string=''):
@@ -591,7 +592,7 @@ def set_sentry():
591
592
  logging.getLogger(logger).setLevel(logging.CRITICAL)
592
593
 
593
594
 
594
- def get_settings(file=USER_CONFIG_DIR / 'settings.yaml', version='0.0.3'):
595
+ def get_settings(file=SETTINGS_YAML, version='0.0.3'):
595
596
  """
596
597
  Loads a global Ultralytics settings YAML file or creates one with default values if it does not exist.
597
598
 
@@ -640,7 +641,7 @@ def get_settings(file=USER_CONFIG_DIR / 'settings.yaml', version='0.0.3'):
640
641
  return settings
641
642
 
642
643
 
643
- def set_settings(kwargs, file=USER_CONFIG_DIR / 'settings.yaml'):
644
+ def set_settings(kwargs, file=SETTINGS_YAML):
644
645
  """
645
646
  Function that runs on a first-time ultralytics package installation to set up global settings and create necessary
646
647
  directories.
@@ -40,7 +40,7 @@ def on_model_save(trainer):
40
40
  # Upload checkpoints with rate limiting
41
41
  is_best = trainer.best_fitness == trainer.fitness
42
42
  if time() - session.timers['ckpt'] > session.rate_limits['ckpt']:
43
- LOGGER.info(f'{PREFIX}Uploading checkpoint {session.model_id}')
43
+ LOGGER.info(f'{PREFIX}Uploading checkpoint https://hub.ultralytics.com/models/{session.model_id}')
44
44
  session.upload_model(trainer.epoch, trainer.last, is_best)
45
45
  session.timers['ckpt'] = time() # reset timer
46
46
 
@@ -81,7 +81,8 @@ def segment2box(segment, width=640, height=640):
81
81
  x, y = segment.T # segment xy
82
82
  inside = (x >= 0) & (y >= 0) & (x <= width) & (y <= height)
83
83
  x, y, = x[inside], y[inside]
84
- return np.array([x.min(), y.min(), x.max(), y.max()]) if any(x) else np.zeros(4) # xyxy
84
+ return np.array([x.min(), y.min(), x.max(), y.max()], dtype=segment.dtype) if any(x) else np.zeros(
85
+ 4, dtype=segment.dtype) # xyxy
85
86
 
86
87
 
87
88
  def scale_boxes(img1_shape, boxes, img0_shape, ratio_pad=None):
@@ -529,7 +530,8 @@ def resample_segments(segments, n=1000):
529
530
  s = np.concatenate((s, s[0:1, :]), axis=0)
530
531
  x = np.linspace(0, len(s) - 1, n)
531
532
  xp = np.arange(len(s))
532
- segments[i] = np.concatenate([np.interp(x, xp, s[:, i]) for i in range(2)]).reshape(2, -1).T # segment xy
533
+ segments[i] = np.concatenate([np.interp(x, xp, s[:, i]) for i in range(2)],
534
+ dtype=np.float32).reshape(2, -1).T # segment xy
533
535
  return segments
534
536
 
535
537
 
@@ -0,0 +1,42 @@
1
+ # Ultralytics YOLO 🚀, GPL-3.0 license
2
+
3
+ import torch
4
+
5
+ from ultralytics.yolo.engine.predictor import BasePredictor
6
+ from ultralytics.yolo.engine.results import Results
7
+ from ultralytics.yolo.utils import DEFAULT_CFG, ROOT
8
+
9
+
10
+ class ClassificationPredictor(BasePredictor):
11
+
12
+ def preprocess(self, img):
13
+ img = (img if isinstance(img, torch.Tensor) else torch.from_numpy(img)).to(self.model.device)
14
+ return img.half() if self.model.fp16 else img.float() # uint8 to fp16/32
15
+
16
+ def postprocess(self, preds, img, orig_imgs):
17
+ results = []
18
+ for i, pred in enumerate(preds):
19
+ orig_img = orig_imgs[i] if isinstance(orig_imgs, list) else orig_imgs
20
+ path, _, _, _, _ = self.batch
21
+ img_path = path[i] if isinstance(path, list) else path
22
+ results.append(Results(orig_img=orig_img, path=img_path, names=self.model.names, probs=pred))
23
+
24
+ return results
25
+
26
+
27
+ def predict(cfg=DEFAULT_CFG, use_python=False):
28
+ model = cfg.model or 'yolov8n-cls.pt' # or "resnet18"
29
+ source = cfg.source if cfg.source is not None else ROOT / 'assets' if (ROOT / 'assets').exists() \
30
+ else 'https://ultralytics.com/images/bus.jpg'
31
+
32
+ args = dict(model=model, source=source)
33
+ if use_python:
34
+ from ultralytics import YOLO
35
+ YOLO(model)(**args)
36
+ else:
37
+ predictor = ClassificationPredictor(overrides=args)
38
+ predictor.predict_cli()
39
+
40
+
41
+ if __name__ == '__main__':
42
+ predict()
@@ -5,7 +5,6 @@ import torch
5
5
  from ultralytics.yolo.engine.predictor import BasePredictor
6
6
  from ultralytics.yolo.engine.results import Results
7
7
  from ultralytics.yolo.utils import DEFAULT_CFG, ROOT, ops
8
- from ultralytics.yolo.utils.plotting import save_one_box
9
8
 
10
9
 
11
10
  class DetectionPredictor(BasePredictor):
@@ -34,48 +33,6 @@ class DetectionPredictor(BasePredictor):
34
33
  results.append(Results(orig_img=orig_img, path=img_path, names=self.model.names, boxes=pred))
35
34
  return results
36
35
 
37
- def write_results(self, idx, results, batch):
38
- p, im, im0 = batch
39
- log_string = ''
40
- if len(im.shape) == 3:
41
- im = im[None] # expand for batch dim
42
- self.seen += 1
43
- imc = im0.copy() if self.args.save_crop else im0
44
- if self.source_type.webcam or self.source_type.from_img: # batch_size >= 1
45
- log_string += f'{idx}: '
46
- frame = self.dataset.count
47
- else:
48
- frame = getattr(self.dataset, 'frame', 0)
49
- self.data_path = p
50
- self.txt_path = str(self.save_dir / 'labels' / p.stem) + ('' if self.dataset.mode == 'image' else f'_{frame}')
51
- log_string += '%gx%g ' % im.shape[2:] # print string
52
-
53
- result = results[idx] # TODO: make boxes inherit from tensors
54
- if len(result) == 0:
55
- return f'{log_string}(no detections), '
56
- det = result.boxes
57
- for c in det.cls.unique():
58
- n = (det.cls == c).sum() # detections per class
59
- log_string += f"{n} {self.model.names[int(c)]}{'s' * (n > 1)}, "
60
-
61
- if self.args.save or self.args.show: # Add bbox to image
62
- self.plotted_img = result.plot(line_width=self.args.line_thickness)
63
-
64
- # write
65
- for d in reversed(det):
66
- c, conf, id = int(d.cls), float(d.conf), None if d.id is None else int(d.id.item())
67
- if self.args.save_txt: # Write to file
68
- line = (c, *d.xywhn.view(-1)) + (conf, ) * self.args.save_conf + (() if id is None else (id, ))
69
- with open(f'{self.txt_path}.txt', 'a') as f:
70
- f.write(('%g ' * len(line)).rstrip() % line + '\n')
71
- if self.args.save_crop:
72
- save_one_box(d.xyxy,
73
- imc,
74
- file=self.save_dir / 'crops' / self.model.names[c] / f'{self.data_path.stem}.jpg',
75
- BGR=True)
76
-
77
- return log_string
78
-
79
36
 
80
37
  def predict(cfg=DEFAULT_CFG, use_python=False):
81
38
  model = cfg.model or 'yolov8n.pt'
@@ -0,0 +1,52 @@
1
+ # Ultralytics YOLO 🚀, GPL-3.0 license
2
+
3
+ from ultralytics.yolo.engine.results import Results
4
+ from ultralytics.yolo.utils import DEFAULT_CFG, ROOT, ops
5
+ from ultralytics.yolo.v8.detect.predict import DetectionPredictor
6
+
7
+
8
+ class PosePredictor(DetectionPredictor):
9
+
10
+ def postprocess(self, preds, img, orig_img):
11
+ preds = ops.non_max_suppression(preds,
12
+ self.args.conf,
13
+ self.args.iou,
14
+ agnostic=self.args.agnostic_nms,
15
+ max_det=self.args.max_det,
16
+ classes=self.args.classes,
17
+ nc=len(self.model.names))
18
+
19
+ results = []
20
+ for i, pred in enumerate(preds):
21
+ orig_img = orig_img[i] if isinstance(orig_img, list) else orig_img
22
+ shape = orig_img.shape
23
+ pred[:, :4] = ops.scale_boxes(img.shape[2:], pred[:, :4], shape).round()
24
+ pred_kpts = pred[:, 6:].view(len(pred), *self.model.kpt_shape) if len(pred) else pred[:, 6:]
25
+ pred_kpts = ops.scale_coords(img.shape[2:], pred_kpts, shape)
26
+ path, _, _, _, _ = self.batch
27
+ img_path = path[i] if isinstance(path, list) else path
28
+ results.append(
29
+ Results(orig_img=orig_img,
30
+ path=img_path,
31
+ names=self.model.names,
32
+ boxes=pred[:, :6],
33
+ keypoints=pred_kpts))
34
+ return results
35
+
36
+
37
+ def predict(cfg=DEFAULT_CFG, use_python=False):
38
+ model = cfg.model or 'yolov8n-pose.pt'
39
+ source = cfg.source if cfg.source is not None else ROOT / 'assets' if (ROOT / 'assets').exists() \
40
+ else 'https://ultralytics.com/images/bus.jpg'
41
+
42
+ args = dict(model=model, source=source)
43
+ if use_python:
44
+ from ultralytics import YOLO
45
+ YOLO(model)(**args)
46
+ else:
47
+ predictor = PosePredictor(overrides=args)
48
+ predictor.predict_cli()
49
+
50
+
51
+ if __name__ == '__main__':
52
+ predict()
@@ -4,7 +4,6 @@ import torch
4
4
 
5
5
  from ultralytics.yolo.engine.results import Results
6
6
  from ultralytics.yolo.utils import DEFAULT_CFG, ROOT, ops
7
- from ultralytics.yolo.utils.plotting import save_one_box
8
7
  from ultralytics.yolo.v8.detect.predict import DetectionPredictor
9
8
 
10
9
 
@@ -40,55 +39,6 @@ class SegmentationPredictor(DetectionPredictor):
40
39
  Results(orig_img=orig_img, path=img_path, names=self.model.names, boxes=pred[:, :6], masks=masks))
41
40
  return results
42
41
 
43
- def write_results(self, idx, results, batch):
44
- p, im, im0 = batch
45
- log_string = ''
46
- if len(im.shape) == 3:
47
- im = im[None] # expand for batch dim
48
- self.seen += 1
49
- imc = im0.copy() if self.args.save_crop else im0
50
- if self.source_type.webcam or self.source_type.from_img: # batch_size >= 1
51
- log_string += f'{idx}: '
52
- frame = self.dataset.count
53
- else:
54
- frame = getattr(self.dataset, 'frame', 0)
55
-
56
- self.data_path = p
57
- self.txt_path = str(self.save_dir / 'labels' / p.stem) + ('' if self.dataset.mode == 'image' else f'_{frame}')
58
- log_string += '%gx%g ' % im.shape[2:] # print string
59
-
60
- result = results[idx]
61
- if len(result) == 0:
62
- return f'{log_string}(no detections), '
63
- det, mask = result.boxes, result.masks # getting tensors TODO: mask mask,box inherit for tensor
64
-
65
- # Print results
66
- for c in det.cls.unique():
67
- n = (det.cls == c).sum() # detections per class
68
- log_string += f"{n} {self.model.names[int(c)]}{'s' * (n > 1)}, "
69
-
70
- # Mask plotting
71
- if self.args.save or self.args.show:
72
- im_gpu = torch.as_tensor(im0, dtype=torch.float16, device=mask.masks.device).permute(
73
- 2, 0, 1).flip(0).contiguous() / 255 if self.args.retina_masks else im[idx]
74
- self.plotted_img = result.plot(line_width=self.args.line_thickness, im_gpu=im_gpu, boxes=self.args.boxes)
75
-
76
- # Write results
77
- for j, d in enumerate(reversed(det)):
78
- c, conf, id = int(d.cls), float(d.conf), None if d.id is None else int(d.id.item())
79
- if self.args.save_txt: # Write to file
80
- seg = mask.xyn[len(det) - j - 1].copy().reshape(-1) # reversed mask.xyn, (n,2) to (n*2)
81
- line = (c, *seg) + (conf, ) * self.args.save_conf + (() if id is None else (id, ))
82
- with open(f'{self.txt_path}.txt', 'a') as f:
83
- f.write(('%g ' * len(line)).rstrip() % line + '\n')
84
- if self.args.save_crop:
85
- save_one_box(d.xyxy,
86
- imc,
87
- file=self.save_dir / 'crops' / self.model.names[c] / f'{self.data_path.stem}.jpg',
88
- BGR=True)
89
-
90
- return log_string
91
-
92
42
 
93
43
  def predict(cfg=DEFAULT_CFG, use_python=False):
94
44
  model = cfg.model or 'yolov8n-seg.pt'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ultralytics
3
- Version: 8.0.72
3
+ Version: 8.0.74
4
4
  Summary: Ultralytics YOLOv8
5
5
  Home-page: https://github.com/ultralytics/ultralytics
6
6
  Author: Ultralytics