ultralytics 8.1.28__py3-none-any.whl → 8.3.62__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.
- tests/__init__.py +22 -0
- tests/conftest.py +83 -0
- tests/test_cli.py +122 -0
- tests/test_cuda.py +155 -0
- tests/test_engine.py +131 -0
- tests/test_exports.py +216 -0
- tests/test_integrations.py +150 -0
- tests/test_python.py +615 -0
- tests/test_solutions.py +94 -0
- ultralytics/__init__.py +11 -8
- ultralytics/cfg/__init__.py +569 -131
- ultralytics/cfg/datasets/Argoverse.yaml +2 -1
- ultralytics/cfg/datasets/DOTAv1.5.yaml +3 -2
- ultralytics/cfg/datasets/DOTAv1.yaml +3 -2
- ultralytics/cfg/datasets/GlobalWheat2020.yaml +3 -2
- ultralytics/cfg/datasets/ImageNet.yaml +2 -1
- ultralytics/cfg/datasets/Objects365.yaml +5 -4
- ultralytics/cfg/datasets/SKU-110K.yaml +2 -1
- ultralytics/cfg/datasets/VOC.yaml +3 -2
- ultralytics/cfg/datasets/VisDrone.yaml +6 -5
- ultralytics/cfg/datasets/african-wildlife.yaml +25 -0
- ultralytics/cfg/datasets/brain-tumor.yaml +23 -0
- ultralytics/cfg/datasets/carparts-seg.yaml +3 -2
- ultralytics/cfg/datasets/coco-pose.yaml +7 -6
- ultralytics/cfg/datasets/coco.yaml +3 -2
- ultralytics/cfg/datasets/coco128-seg.yaml +4 -3
- ultralytics/cfg/datasets/coco128.yaml +4 -3
- ultralytics/cfg/datasets/coco8-pose.yaml +3 -2
- ultralytics/cfg/datasets/coco8-seg.yaml +3 -2
- ultralytics/cfg/datasets/coco8.yaml +3 -2
- ultralytics/cfg/datasets/crack-seg.yaml +3 -2
- ultralytics/cfg/datasets/dog-pose.yaml +24 -0
- ultralytics/cfg/datasets/dota8.yaml +3 -2
- ultralytics/cfg/datasets/hand-keypoints.yaml +26 -0
- ultralytics/cfg/datasets/lvis.yaml +1236 -0
- ultralytics/cfg/datasets/medical-pills.yaml +22 -0
- ultralytics/cfg/datasets/open-images-v7.yaml +2 -1
- ultralytics/cfg/datasets/package-seg.yaml +5 -4
- ultralytics/cfg/datasets/signature.yaml +21 -0
- ultralytics/cfg/datasets/tiger-pose.yaml +3 -2
- ultralytics/cfg/datasets/xView.yaml +2 -1
- ultralytics/cfg/default.yaml +14 -11
- ultralytics/cfg/models/11/yolo11-cls-resnet18.yaml +24 -0
- ultralytics/cfg/models/11/yolo11-cls.yaml +33 -0
- ultralytics/cfg/models/11/yolo11-obb.yaml +50 -0
- ultralytics/cfg/models/11/yolo11-pose.yaml +51 -0
- ultralytics/cfg/models/11/yolo11-seg.yaml +50 -0
- ultralytics/cfg/models/11/yolo11.yaml +50 -0
- ultralytics/cfg/models/rt-detr/rtdetr-l.yaml +5 -2
- ultralytics/cfg/models/rt-detr/rtdetr-resnet101.yaml +5 -2
- ultralytics/cfg/models/rt-detr/rtdetr-resnet50.yaml +5 -2
- ultralytics/cfg/models/rt-detr/rtdetr-x.yaml +5 -2
- ultralytics/cfg/models/v10/yolov10b.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10l.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10m.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10n.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10s.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10x.yaml +45 -0
- ultralytics/cfg/models/v3/yolov3-spp.yaml +5 -2
- ultralytics/cfg/models/v3/yolov3-tiny.yaml +5 -2
- ultralytics/cfg/models/v3/yolov3.yaml +5 -2
- ultralytics/cfg/models/v5/yolov5-p6.yaml +5 -2
- ultralytics/cfg/models/v5/yolov5.yaml +5 -2
- ultralytics/cfg/models/v6/yolov6.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-cls-resnet101.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-cls-resnet50.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-cls.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-ghost-p2.yaml +6 -2
- ultralytics/cfg/models/v8/yolov8-ghost-p6.yaml +6 -2
- ultralytics/cfg/models/v8/yolov8-ghost.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-obb.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-p2.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-p6.yaml +10 -7
- ultralytics/cfg/models/v8/yolov8-pose-p6.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-pose.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-rtdetr.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-seg-p6.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-seg.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-world.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-worldv2.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8.yaml +5 -2
- ultralytics/cfg/models/v9/yolov9c-seg.yaml +41 -0
- ultralytics/cfg/models/v9/yolov9c.yaml +30 -25
- ultralytics/cfg/models/v9/yolov9e-seg.yaml +64 -0
- ultralytics/cfg/models/v9/yolov9e.yaml +46 -42
- ultralytics/cfg/models/v9/yolov9m.yaml +41 -0
- ultralytics/cfg/models/v9/yolov9s.yaml +41 -0
- ultralytics/cfg/models/v9/yolov9t.yaml +41 -0
- ultralytics/cfg/solutions/default.yaml +24 -0
- ultralytics/cfg/trackers/botsort.yaml +8 -5
- ultralytics/cfg/trackers/bytetrack.yaml +8 -5
- ultralytics/data/__init__.py +14 -3
- ultralytics/data/annotator.py +37 -15
- ultralytics/data/augment.py +1783 -289
- ultralytics/data/base.py +62 -27
- ultralytics/data/build.py +36 -8
- ultralytics/data/converter.py +196 -36
- ultralytics/data/dataset.py +233 -94
- ultralytics/data/loaders.py +199 -96
- ultralytics/data/split_dota.py +39 -29
- ultralytics/data/utils.py +110 -40
- ultralytics/engine/__init__.py +1 -1
- ultralytics/engine/exporter.py +569 -242
- ultralytics/engine/model.py +604 -252
- ultralytics/engine/predictor.py +22 -11
- ultralytics/engine/results.py +1228 -218
- ultralytics/engine/trainer.py +190 -129
- ultralytics/engine/tuner.py +18 -18
- ultralytics/engine/validator.py +18 -15
- ultralytics/hub/__init__.py +31 -13
- ultralytics/hub/auth.py +11 -7
- ultralytics/hub/google/__init__.py +159 -0
- ultralytics/hub/session.py +128 -94
- ultralytics/hub/utils.py +20 -21
- ultralytics/models/__init__.py +4 -2
- ultralytics/models/fastsam/__init__.py +2 -3
- ultralytics/models/fastsam/model.py +26 -4
- ultralytics/models/fastsam/predict.py +127 -63
- ultralytics/models/fastsam/utils.py +1 -44
- ultralytics/models/fastsam/val.py +1 -1
- ultralytics/models/nas/__init__.py +1 -1
- ultralytics/models/nas/model.py +21 -10
- ultralytics/models/nas/predict.py +3 -6
- ultralytics/models/nas/val.py +4 -4
- ultralytics/models/rtdetr/__init__.py +1 -1
- ultralytics/models/rtdetr/model.py +1 -1
- ultralytics/models/rtdetr/predict.py +6 -8
- ultralytics/models/rtdetr/train.py +6 -2
- ultralytics/models/rtdetr/val.py +3 -3
- ultralytics/models/sam/__init__.py +3 -3
- ultralytics/models/sam/amg.py +29 -23
- ultralytics/models/sam/build.py +211 -13
- ultralytics/models/sam/model.py +91 -30
- ultralytics/models/sam/modules/__init__.py +1 -1
- ultralytics/models/sam/modules/blocks.py +1129 -0
- ultralytics/models/sam/modules/decoders.py +381 -53
- ultralytics/models/sam/modules/encoders.py +515 -324
- ultralytics/models/sam/modules/memory_attention.py +237 -0
- ultralytics/models/sam/modules/sam.py +969 -21
- ultralytics/models/sam/modules/tiny_encoder.py +425 -154
- ultralytics/models/sam/modules/transformer.py +159 -60
- ultralytics/models/sam/modules/utils.py +293 -0
- ultralytics/models/sam/predict.py +1263 -132
- ultralytics/models/utils/__init__.py +1 -1
- ultralytics/models/utils/loss.py +36 -24
- ultralytics/models/utils/ops.py +3 -7
- ultralytics/models/yolo/__init__.py +3 -3
- ultralytics/models/yolo/classify/__init__.py +1 -1
- ultralytics/models/yolo/classify/predict.py +7 -8
- ultralytics/models/yolo/classify/train.py +17 -22
- ultralytics/models/yolo/classify/val.py +8 -4
- ultralytics/models/yolo/detect/__init__.py +1 -1
- ultralytics/models/yolo/detect/predict.py +3 -5
- ultralytics/models/yolo/detect/train.py +11 -4
- ultralytics/models/yolo/detect/val.py +90 -52
- ultralytics/models/yolo/model.py +14 -9
- ultralytics/models/yolo/obb/__init__.py +1 -1
- ultralytics/models/yolo/obb/predict.py +2 -2
- ultralytics/models/yolo/obb/train.py +5 -3
- ultralytics/models/yolo/obb/val.py +41 -23
- ultralytics/models/yolo/pose/__init__.py +1 -1
- ultralytics/models/yolo/pose/predict.py +3 -5
- ultralytics/models/yolo/pose/train.py +2 -2
- ultralytics/models/yolo/pose/val.py +51 -17
- ultralytics/models/yolo/segment/__init__.py +1 -1
- ultralytics/models/yolo/segment/predict.py +3 -5
- ultralytics/models/yolo/segment/train.py +2 -2
- ultralytics/models/yolo/segment/val.py +60 -19
- ultralytics/models/yolo/world/__init__.py +5 -0
- ultralytics/models/yolo/world/train.py +92 -0
- ultralytics/models/yolo/world/train_world.py +109 -0
- ultralytics/nn/__init__.py +1 -1
- ultralytics/nn/autobackend.py +228 -93
- ultralytics/nn/modules/__init__.py +39 -14
- ultralytics/nn/modules/activation.py +21 -0
- ultralytics/nn/modules/block.py +527 -67
- ultralytics/nn/modules/conv.py +24 -7
- ultralytics/nn/modules/head.py +177 -34
- ultralytics/nn/modules/transformer.py +6 -5
- ultralytics/nn/modules/utils.py +1 -2
- ultralytics/nn/tasks.py +225 -77
- ultralytics/solutions/__init__.py +30 -1
- ultralytics/solutions/ai_gym.py +96 -143
- ultralytics/solutions/analytics.py +247 -0
- ultralytics/solutions/distance_calculation.py +78 -135
- ultralytics/solutions/heatmap.py +93 -247
- ultralytics/solutions/object_counter.py +184 -259
- ultralytics/solutions/parking_management.py +246 -0
- ultralytics/solutions/queue_management.py +112 -0
- ultralytics/solutions/region_counter.py +116 -0
- ultralytics/solutions/security_alarm.py +144 -0
- ultralytics/solutions/solutions.py +178 -0
- ultralytics/solutions/speed_estimation.py +86 -174
- ultralytics/solutions/streamlit_inference.py +190 -0
- ultralytics/solutions/trackzone.py +68 -0
- ultralytics/trackers/__init__.py +1 -1
- ultralytics/trackers/basetrack.py +32 -13
- ultralytics/trackers/bot_sort.py +61 -28
- ultralytics/trackers/byte_tracker.py +83 -51
- ultralytics/trackers/track.py +21 -6
- ultralytics/trackers/utils/__init__.py +1 -1
- ultralytics/trackers/utils/gmc.py +62 -48
- ultralytics/trackers/utils/kalman_filter.py +166 -35
- ultralytics/trackers/utils/matching.py +40 -21
- ultralytics/utils/__init__.py +511 -239
- ultralytics/utils/autobatch.py +40 -22
- ultralytics/utils/benchmarks.py +266 -85
- ultralytics/utils/callbacks/__init__.py +1 -1
- ultralytics/utils/callbacks/base.py +1 -3
- ultralytics/utils/callbacks/clearml.py +7 -6
- ultralytics/utils/callbacks/comet.py +39 -17
- ultralytics/utils/callbacks/dvc.py +1 -1
- ultralytics/utils/callbacks/hub.py +16 -16
- ultralytics/utils/callbacks/mlflow.py +28 -24
- ultralytics/utils/callbacks/neptune.py +6 -2
- ultralytics/utils/callbacks/raytune.py +3 -4
- ultralytics/utils/callbacks/tensorboard.py +18 -18
- ultralytics/utils/callbacks/wb.py +27 -20
- ultralytics/utils/checks.py +160 -100
- ultralytics/utils/dist.py +2 -1
- ultralytics/utils/downloads.py +44 -37
- ultralytics/utils/errors.py +1 -1
- ultralytics/utils/files.py +72 -38
- ultralytics/utils/instance.py +41 -19
- ultralytics/utils/loss.py +84 -56
- ultralytics/utils/metrics.py +61 -56
- ultralytics/utils/ops.py +94 -89
- ultralytics/utils/patches.py +30 -14
- ultralytics/utils/plotting.py +600 -269
- ultralytics/utils/tal.py +67 -26
- ultralytics/utils/torch_utils.py +302 -102
- ultralytics/utils/triton.py +2 -1
- ultralytics/utils/tuner.py +21 -12
- ultralytics-8.3.62.dist-info/METADATA +370 -0
- ultralytics-8.3.62.dist-info/RECORD +241 -0
- {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/WHEEL +1 -1
- ultralytics/data/explorer/__init__.py +0 -5
- ultralytics/data/explorer/explorer.py +0 -472
- ultralytics/data/explorer/gui/__init__.py +0 -1
- ultralytics/data/explorer/gui/dash.py +0 -268
- ultralytics/data/explorer/utils.py +0 -166
- ultralytics/models/fastsam/prompt.py +0 -357
- ultralytics-8.1.28.dist-info/METADATA +0 -373
- ultralytics-8.1.28.dist-info/RECORD +0 -197
- {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/LICENSE +0 -0
- {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/entry_points.txt +0 -0
- {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/top_level.txt +0 -0
ultralytics/utils/ops.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Ultralytics
|
1
|
+
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
2
2
|
|
3
3
|
import contextlib
|
4
4
|
import math
|
@@ -9,7 +9,6 @@ import cv2
|
|
9
9
|
import numpy as np
|
10
10
|
import torch
|
11
11
|
import torch.nn.functional as F
|
12
|
-
import torchvision
|
13
12
|
|
14
13
|
from ultralytics.utils import LOGGER
|
15
14
|
from ultralytics.utils.metrics import batch_probiou
|
@@ -76,6 +75,10 @@ def segment2box(segment, width=640, height=640):
|
|
76
75
|
(np.ndarray): the minimum and maximum x and y values of the segment.
|
77
76
|
"""
|
78
77
|
x, y = segment.T # segment xy
|
78
|
+
# any 3 out of 4 sides are outside the image, clip coordinates first, https://github.com/ultralytics/ultralytics/pull/18294
|
79
|
+
if np.array([x.min() < 0, y.min() < 0, x.max() > width, y.max() > height]).sum() >= 3:
|
80
|
+
x = x.clip(0, width)
|
81
|
+
y = y.clip(0, height)
|
79
82
|
inside = (x >= 0) & (y >= 0) & (x <= width) & (y <= height)
|
80
83
|
x = x[inside]
|
81
84
|
y = y[inside]
|
@@ -142,14 +145,15 @@ def make_divisible(x, divisor):
|
|
142
145
|
|
143
146
|
def nms_rotated(boxes, scores, threshold=0.45):
|
144
147
|
"""
|
145
|
-
NMS for
|
148
|
+
NMS for oriented bounding boxes using probiou and fast-nms.
|
146
149
|
|
147
150
|
Args:
|
148
|
-
boxes (torch.Tensor): (N, 5), xywhr.
|
149
|
-
scores (torch.Tensor): (N,
|
150
|
-
threshold (float): IoU threshold.
|
151
|
+
boxes (torch.Tensor): Rotated bounding boxes, shape (N, 5), format xywhr.
|
152
|
+
scores (torch.Tensor): Confidence scores, shape (N,).
|
153
|
+
threshold (float, optional): IoU threshold. Defaults to 0.45.
|
151
154
|
|
152
155
|
Returns:
|
156
|
+
(torch.Tensor): Indices of boxes to keep after NMS.
|
153
157
|
"""
|
154
158
|
if len(boxes) == 0:
|
155
159
|
return np.empty((0,), dtype=np.int8)
|
@@ -200,22 +204,32 @@ def non_max_suppression(
|
|
200
204
|
max_nms (int): The maximum number of boxes into torchvision.ops.nms().
|
201
205
|
max_wh (int): The maximum box width and height in pixels.
|
202
206
|
in_place (bool): If True, the input prediction tensor will be modified in place.
|
207
|
+
rotated (bool): If Oriented Bounding Boxes (OBB) are being passed for NMS.
|
203
208
|
|
204
209
|
Returns:
|
205
210
|
(List[torch.Tensor]): A list of length batch_size, where each element is a tensor of
|
206
211
|
shape (num_boxes, 6 + num_masks) containing the kept boxes, with columns
|
207
212
|
(x1, y1, x2, y2, confidence, class, mask1, mask2, ...).
|
208
213
|
"""
|
214
|
+
import torchvision # scope for faster 'import ultralytics'
|
209
215
|
|
210
216
|
# Checks
|
211
217
|
assert 0 <= conf_thres <= 1, f"Invalid Confidence threshold {conf_thres}, valid values are between 0.0 and 1.0"
|
212
218
|
assert 0 <= iou_thres <= 1, f"Invalid IoU {iou_thres}, valid values are between 0.0 and 1.0"
|
213
219
|
if isinstance(prediction, (list, tuple)): # YOLOv8 model in validation model, output = (inference_out, loss_out)
|
214
220
|
prediction = prediction[0] # select only inference output
|
221
|
+
if classes is not None:
|
222
|
+
classes = torch.tensor(classes, device=prediction.device)
|
215
223
|
|
216
|
-
|
224
|
+
if prediction.shape[-1] == 6: # end-to-end model (BNC, i.e. 1,300,6)
|
225
|
+
output = [pred[pred[:, 4] > conf_thres][:max_det] for pred in prediction]
|
226
|
+
if classes is not None:
|
227
|
+
output = [pred[(pred[:, 5:6] == classes).any(1)] for pred in output]
|
228
|
+
return output
|
229
|
+
|
230
|
+
bs = prediction.shape[0] # batch size (BCN, i.e. 1,84,6300)
|
217
231
|
nc = nc or (prediction.shape[1] - 4) # number of classes
|
218
|
-
nm = prediction.shape[1] - nc - 4
|
232
|
+
nm = prediction.shape[1] - nc - 4 # number of masks
|
219
233
|
mi = 4 + nc # mask start index
|
220
234
|
xc = prediction[:, 4:mi].amax(1) > conf_thres # candidates
|
221
235
|
|
@@ -262,7 +276,7 @@ def non_max_suppression(
|
|
262
276
|
|
263
277
|
# Filter by class
|
264
278
|
if classes is not None:
|
265
|
-
x = x[(x[:, 5:6] ==
|
279
|
+
x = x[(x[:, 5:6] == classes).any(1)]
|
266
280
|
|
267
281
|
# Check shape
|
268
282
|
n = x.shape[0] # number of boxes
|
@@ -307,11 +321,11 @@ def clip_boxes(boxes, shape):
|
|
307
321
|
Takes a list of bounding boxes and a shape (height, width) and clips the bounding boxes to the shape.
|
308
322
|
|
309
323
|
Args:
|
310
|
-
boxes (torch.Tensor):
|
311
|
-
shape (tuple):
|
324
|
+
boxes (torch.Tensor): The bounding boxes to clip.
|
325
|
+
shape (tuple): The shape of the image.
|
312
326
|
|
313
327
|
Returns:
|
314
|
-
(torch.Tensor | numpy.ndarray):
|
328
|
+
(torch.Tensor | numpy.ndarray): The clipped boxes.
|
315
329
|
"""
|
316
330
|
if isinstance(boxes, torch.Tensor): # faster individually (WARNING: inplace .clamp_() Apple MPS bug)
|
317
331
|
boxes[..., 0] = boxes[..., 0].clamp(0, shape[1]) # x1
|
@@ -349,12 +363,12 @@ def scale_image(masks, im0_shape, ratio_pad=None):
|
|
349
363
|
Takes a mask, and resizes it to the original image size.
|
350
364
|
|
351
365
|
Args:
|
352
|
-
masks (np.ndarray):
|
353
|
-
im0_shape (tuple):
|
354
|
-
ratio_pad (tuple):
|
366
|
+
masks (np.ndarray): Resized and padded masks/images, [h, w, num]/[h, w, 3].
|
367
|
+
im0_shape (tuple): The original image shape.
|
368
|
+
ratio_pad (tuple): The ratio of the padding to the original image.
|
355
369
|
|
356
370
|
Returns:
|
357
|
-
masks (
|
371
|
+
masks (np.ndarray): The masks that are being returned with shape [h, w, num].
|
358
372
|
"""
|
359
373
|
# Rescale coordinates (xyxy) from im1_shape to im0_shape
|
360
374
|
im1_shape = masks.shape
|
@@ -391,7 +405,7 @@ def xyxy2xywh(x):
|
|
391
405
|
y (np.ndarray | torch.Tensor): The bounding box coordinates in (x, y, width, height) format.
|
392
406
|
"""
|
393
407
|
assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
|
394
|
-
y =
|
408
|
+
y = empty_like(x) # faster than clone/copy
|
395
409
|
y[..., 0] = (x[..., 0] + x[..., 2]) / 2 # x center
|
396
410
|
y[..., 1] = (x[..., 1] + x[..., 3]) / 2 # y center
|
397
411
|
y[..., 2] = x[..., 2] - x[..., 0] # width
|
@@ -402,7 +416,7 @@ def xyxy2xywh(x):
|
|
402
416
|
def xywh2xyxy(x):
|
403
417
|
"""
|
404
418
|
Convert bounding box coordinates from (x, y, width, height) format to (x1, y1, x2, y2) format where (x1, y1) is the
|
405
|
-
top-left corner and (x2, y2) is the bottom-right corner.
|
419
|
+
top-left corner and (x2, y2) is the bottom-right corner. Note: ops per 2 channels faster than per channel.
|
406
420
|
|
407
421
|
Args:
|
408
422
|
x (np.ndarray | torch.Tensor): The input bounding box coordinates in (x, y, width, height) format.
|
@@ -411,13 +425,11 @@ def xywh2xyxy(x):
|
|
411
425
|
y (np.ndarray | torch.Tensor): The bounding box coordinates in (x1, y1, x2, y2) format.
|
412
426
|
"""
|
413
427
|
assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
|
414
|
-
y =
|
415
|
-
|
416
|
-
|
417
|
-
y[...,
|
418
|
-
y[...,
|
419
|
-
y[..., 2] = x[..., 0] + dw # bottom right x
|
420
|
-
y[..., 3] = x[..., 1] + dh # bottom right y
|
428
|
+
y = empty_like(x) # faster than clone/copy
|
429
|
+
xy = x[..., :2] # centers
|
430
|
+
wh = x[..., 2:] / 2 # half width-height
|
431
|
+
y[..., :2] = xy - wh # top left xy
|
432
|
+
y[..., 2:] = xy + wh # bottom right xy
|
421
433
|
return y
|
422
434
|
|
423
435
|
|
@@ -436,7 +448,7 @@ def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0):
|
|
436
448
|
x1,y1 is the top-left corner, x2,y2 is the bottom-right corner of the bounding box.
|
437
449
|
"""
|
438
450
|
assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
|
439
|
-
y =
|
451
|
+
y = empty_like(x) # faster than clone/copy
|
440
452
|
y[..., 0] = w * (x[..., 0] - x[..., 2] / 2) + padw # top left x
|
441
453
|
y[..., 1] = h * (x[..., 1] - x[..., 3] / 2) + padh # top left y
|
442
454
|
y[..., 2] = w * (x[..., 0] + x[..., 2] / 2) + padw # bottom right x
|
@@ -462,7 +474,7 @@ def xyxy2xywhn(x, w=640, h=640, clip=False, eps=0.0):
|
|
462
474
|
if clip:
|
463
475
|
x = clip_boxes(x, (h - eps, w - eps))
|
464
476
|
assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
|
465
|
-
y =
|
477
|
+
y = empty_like(x) # faster than clone/copy
|
466
478
|
y[..., 0] = ((x[..., 0] + x[..., 2]) / 2) / w # x center
|
467
479
|
y[..., 1] = ((x[..., 1] + x[..., 3]) / 2) / h # y center
|
468
480
|
y[..., 2] = (x[..., 2] - x[..., 0]) / w # width
|
@@ -518,59 +530,58 @@ def ltwh2xywh(x):
|
|
518
530
|
return y
|
519
531
|
|
520
532
|
|
521
|
-
def xyxyxyxy2xywhr(
|
533
|
+
def xyxyxyxy2xywhr(x):
|
522
534
|
"""
|
523
535
|
Convert batched Oriented Bounding Boxes (OBB) from [xy1, xy2, xy3, xy4] to [xywh, rotation]. Rotation values are
|
524
|
-
|
536
|
+
returned in radians from 0 to pi/2.
|
525
537
|
|
526
538
|
Args:
|
527
|
-
|
539
|
+
x (numpy.ndarray | torch.Tensor): Input box corners [xy1, xy2, xy3, xy4] of shape (n, 8).
|
528
540
|
|
529
541
|
Returns:
|
530
542
|
(numpy.ndarray | torch.Tensor): Converted data in [cx, cy, w, h, rotation] format of shape (n, 5).
|
531
543
|
"""
|
532
|
-
is_torch = isinstance(
|
533
|
-
points =
|
534
|
-
points = points.reshape(len(
|
544
|
+
is_torch = isinstance(x, torch.Tensor)
|
545
|
+
points = x.cpu().numpy() if is_torch else x
|
546
|
+
points = points.reshape(len(x), -1, 2)
|
535
547
|
rboxes = []
|
536
548
|
for pts in points:
|
537
549
|
# NOTE: Use cv2.minAreaRect to get accurate xywhr,
|
538
550
|
# especially some objects are cut off by augmentations in dataloader.
|
539
|
-
(
|
540
|
-
rboxes.append([
|
541
|
-
return (
|
542
|
-
torch.tensor(rboxes, device=corners.device, dtype=corners.dtype)
|
543
|
-
if is_torch
|
544
|
-
else np.asarray(rboxes, dtype=points.dtype)
|
545
|
-
) # rboxes
|
551
|
+
(cx, cy), (w, h), angle = cv2.minAreaRect(pts)
|
552
|
+
rboxes.append([cx, cy, w, h, angle / 180 * np.pi])
|
553
|
+
return torch.tensor(rboxes, device=x.device, dtype=x.dtype) if is_torch else np.asarray(rboxes)
|
546
554
|
|
547
555
|
|
548
|
-
def xywhr2xyxyxyxy(
|
556
|
+
def xywhr2xyxyxyxy(x):
|
549
557
|
"""
|
550
558
|
Convert batched Oriented Bounding Boxes (OBB) from [xywh, rotation] to [xy1, xy2, xy3, xy4]. Rotation values should
|
551
|
-
be in
|
559
|
+
be in radians from 0 to pi/2.
|
552
560
|
|
553
561
|
Args:
|
554
|
-
|
562
|
+
x (numpy.ndarray | torch.Tensor): Boxes in [cx, cy, w, h, rotation] format of shape (n, 5) or (b, n, 5).
|
555
563
|
|
556
564
|
Returns:
|
557
565
|
(numpy.ndarray | torch.Tensor): Converted corner points of shape (n, 4, 2) or (b, n, 4, 2).
|
558
566
|
"""
|
559
|
-
|
560
|
-
|
567
|
+
cos, sin, cat, stack = (
|
568
|
+
(torch.cos, torch.sin, torch.cat, torch.stack)
|
569
|
+
if isinstance(x, torch.Tensor)
|
570
|
+
else (np.cos, np.sin, np.concatenate, np.stack)
|
571
|
+
)
|
561
572
|
|
562
|
-
ctr =
|
563
|
-
w, h, angle = (
|
573
|
+
ctr = x[..., :2]
|
574
|
+
w, h, angle = (x[..., i : i + 1] for i in range(2, 5))
|
564
575
|
cos_value, sin_value = cos(angle), sin(angle)
|
565
576
|
vec1 = [w / 2 * cos_value, w / 2 * sin_value]
|
566
577
|
vec2 = [-h / 2 * sin_value, h / 2 * cos_value]
|
567
|
-
vec1 =
|
568
|
-
vec2 =
|
578
|
+
vec1 = cat(vec1, -1)
|
579
|
+
vec2 = cat(vec2, -1)
|
569
580
|
pt1 = ctr + vec1 + vec2
|
570
581
|
pt2 = ctr + vec1 - vec2
|
571
582
|
pt3 = ctr - vec1 - vec2
|
572
583
|
pt4 = ctr - vec1 + vec2
|
573
|
-
return
|
584
|
+
return stack([pt1, pt2, pt3, pt4], -2)
|
574
585
|
|
575
586
|
|
576
587
|
def ltwh2xyxy(x):
|
@@ -591,7 +602,7 @@ def ltwh2xyxy(x):
|
|
591
602
|
|
592
603
|
def segments2boxes(segments):
|
593
604
|
"""
|
594
|
-
It converts segment labels to box labels, i.e. (cls, xy1, xy2, ...) to (cls, xywh)
|
605
|
+
It converts segment labels to box labels, i.e. (cls, xy1, xy2, ...) to (cls, xywh).
|
595
606
|
|
596
607
|
Args:
|
597
608
|
segments (list): list of segments, each segment is a list of points, each point is a list of x, y coordinates
|
@@ -618,9 +629,12 @@ def resample_segments(segments, n=1000):
|
|
618
629
|
segments (list): the resampled segments.
|
619
630
|
"""
|
620
631
|
for i, s in enumerate(segments):
|
632
|
+
if len(s) == n:
|
633
|
+
continue
|
621
634
|
s = np.concatenate((s, s[0:1, :]), axis=0)
|
622
|
-
x = np.linspace(0, len(s) - 1, n)
|
635
|
+
x = np.linspace(0, len(s) - 1, n - len(s) if len(s) < n else n)
|
623
636
|
xp = np.arange(len(s))
|
637
|
+
x = np.insert(x, np.searchsorted(x, xp), xp) if len(s) < n else x
|
624
638
|
segments[i] = (
|
625
639
|
np.concatenate([np.interp(x, xp, s[:, i]) for i in range(2)], dtype=np.float32).reshape(2, -1).T
|
626
640
|
) # segment xy
|
@@ -646,27 +660,6 @@ def crop_mask(masks, boxes):
|
|
646
660
|
return masks * ((r >= x1) * (r < x2) * (c >= y1) * (c < y2))
|
647
661
|
|
648
662
|
|
649
|
-
def process_mask_upsample(protos, masks_in, bboxes, shape):
|
650
|
-
"""
|
651
|
-
Takes the output of the mask head, and applies the mask to the bounding boxes. This produces masks of higher quality
|
652
|
-
but is slower.
|
653
|
-
|
654
|
-
Args:
|
655
|
-
protos (torch.Tensor): [mask_dim, mask_h, mask_w]
|
656
|
-
masks_in (torch.Tensor): [n, mask_dim], n is number of masks after nms
|
657
|
-
bboxes (torch.Tensor): [n, 4], n is number of masks after nms
|
658
|
-
shape (tuple): the size of the input image (h,w)
|
659
|
-
|
660
|
-
Returns:
|
661
|
-
(torch.Tensor): The upsampled masks.
|
662
|
-
"""
|
663
|
-
c, mh, mw = protos.shape # CHW
|
664
|
-
masks = (masks_in @ protos.float().view(c, -1)).sigmoid().view(-1, mh, mw)
|
665
|
-
masks = F.interpolate(masks[None], shape, mode="bilinear", align_corners=False)[0] # CHW
|
666
|
-
masks = crop_mask(masks, bboxes) # CHW
|
667
|
-
return masks.gt_(0.5)
|
668
|
-
|
669
|
-
|
670
663
|
def process_mask(protos, masks_in, bboxes, shape, upsample=False):
|
671
664
|
"""
|
672
665
|
Apply masks to bounding boxes using the output of the mask head.
|
@@ -682,10 +675,9 @@ def process_mask(protos, masks_in, bboxes, shape, upsample=False):
|
|
682
675
|
(torch.Tensor): A binary mask tensor of shape [n, h, w], where n is the number of masks after NMS, and h and w
|
683
676
|
are the height and width of the input image. The mask is applied to the bounding boxes.
|
684
677
|
"""
|
685
|
-
|
686
678
|
c, mh, mw = protos.shape # CHW
|
687
679
|
ih, iw = shape
|
688
|
-
masks = (masks_in @ protos.float().view(c, -1)).
|
680
|
+
masks = (masks_in @ protos.float().view(c, -1)).view(-1, mh, mw) # CHW
|
689
681
|
width_ratio = mw / iw
|
690
682
|
height_ratio = mh / ih
|
691
683
|
|
@@ -698,7 +690,7 @@ def process_mask(protos, masks_in, bboxes, shape, upsample=False):
|
|
698
690
|
masks = crop_mask(masks, downsampled_bboxes) # CHW
|
699
691
|
if upsample:
|
700
692
|
masks = F.interpolate(masks[None], shape, mode="bilinear", align_corners=False)[0] # CHW
|
701
|
-
return masks.gt_(0.
|
693
|
+
return masks.gt_(0.0)
|
702
694
|
|
703
695
|
|
704
696
|
def process_mask_native(protos, masks_in, bboxes, shape):
|
@@ -707,18 +699,18 @@ def process_mask_native(protos, masks_in, bboxes, shape):
|
|
707
699
|
|
708
700
|
Args:
|
709
701
|
protos (torch.Tensor): [mask_dim, mask_h, mask_w]
|
710
|
-
masks_in (torch.Tensor): [n, mask_dim], n is number of masks after nms
|
711
|
-
bboxes (torch.Tensor): [n, 4], n is number of masks after nms
|
712
|
-
shape (tuple):
|
702
|
+
masks_in (torch.Tensor): [n, mask_dim], n is number of masks after nms.
|
703
|
+
bboxes (torch.Tensor): [n, 4], n is number of masks after nms.
|
704
|
+
shape (tuple): The size of the input image (h,w).
|
713
705
|
|
714
706
|
Returns:
|
715
|
-
masks (torch.Tensor): The returned masks with dimensions [h, w, n]
|
707
|
+
masks (torch.Tensor): The returned masks with dimensions [h, w, n].
|
716
708
|
"""
|
717
709
|
c, mh, mw = protos.shape # CHW
|
718
|
-
masks = (masks_in @ protos.float().view(c, -1)).
|
710
|
+
masks = (masks_in @ protos.float().view(c, -1)).view(-1, mh, mw)
|
719
711
|
masks = scale_masks(masks[None], shape)[0] # CHW
|
720
712
|
masks = crop_mask(masks, bboxes) # CHW
|
721
|
-
return masks.gt_(0.
|
713
|
+
return masks.gt_(0.0)
|
722
714
|
|
723
715
|
|
724
716
|
def scale_masks(masks, shape, padding=True):
|
@@ -785,7 +777,7 @@ def regularize_rboxes(rboxes):
|
|
785
777
|
Regularize rotated boxes in range [0, pi/2].
|
786
778
|
|
787
779
|
Args:
|
788
|
-
rboxes (torch.Tensor): (N, 5)
|
780
|
+
rboxes (torch.Tensor): Input boxes of shape(N, 5) in xywhr format.
|
789
781
|
|
790
782
|
Returns:
|
791
783
|
(torch.Tensor): The regularized boxes.
|
@@ -798,23 +790,29 @@ def regularize_rboxes(rboxes):
|
|
798
790
|
return torch.stack([x, y, w_, h_, t], dim=-1) # regularized boxes
|
799
791
|
|
800
792
|
|
801
|
-
def masks2segments(masks, strategy="
|
793
|
+
def masks2segments(masks, strategy="all"):
|
802
794
|
"""
|
803
|
-
It takes a list of masks(n,h,w) and returns a list of segments(n,xy)
|
795
|
+
It takes a list of masks(n,h,w) and returns a list of segments(n,xy).
|
804
796
|
|
805
797
|
Args:
|
806
798
|
masks (torch.Tensor): the output of the model, which is a tensor of shape (batch_size, 160, 160)
|
807
|
-
strategy (str): '
|
799
|
+
strategy (str): 'all' or 'largest'. Defaults to all
|
808
800
|
|
809
801
|
Returns:
|
810
802
|
segments (List): list of segment masks
|
811
803
|
"""
|
804
|
+
from ultralytics.data.converter import merge_multi_segment
|
805
|
+
|
812
806
|
segments = []
|
813
807
|
for x in masks.int().cpu().numpy().astype("uint8"):
|
814
808
|
c = cv2.findContours(x, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
|
815
809
|
if c:
|
816
|
-
if strategy == "
|
817
|
-
c =
|
810
|
+
if strategy == "all": # merge and concatenate all segments
|
811
|
+
c = (
|
812
|
+
np.concatenate(merge_multi_segment([x.reshape(-1, 2) for x in c]))
|
813
|
+
if len(c) > 1
|
814
|
+
else c[0].reshape(-1, 2)
|
815
|
+
)
|
818
816
|
elif strategy == "largest": # select largest segment
|
819
817
|
c = np.array(c[np.array([len(x) for x in c]).argmax()]).reshape(-1, 2)
|
820
818
|
else:
|
@@ -838,7 +836,7 @@ def convert_torch2numpy_batch(batch: torch.Tensor) -> np.ndarray:
|
|
838
836
|
|
839
837
|
def clean_str(s):
|
840
838
|
"""
|
841
|
-
Cleans a string by replacing special characters with
|
839
|
+
Cleans a string by replacing special characters with '_' character.
|
842
840
|
|
843
841
|
Args:
|
844
842
|
s (str): a string needing special characters replaced
|
@@ -847,3 +845,10 @@ def clean_str(s):
|
|
847
845
|
(str): a string with special characters replaced by an underscore _
|
848
846
|
"""
|
849
847
|
return re.sub(pattern="[|@#!¡·$€%&()=?¿^*;:,¨´><+]", repl="_", string=s)
|
848
|
+
|
849
|
+
|
850
|
+
def empty_like(x):
|
851
|
+
"""Creates empty torch.Tensor or np.ndarray with same shape as input and float32 dtype."""
|
852
|
+
return (
|
853
|
+
torch.empty_like(x, dtype=torch.float32) if isinstance(x, torch.Tensor) else np.empty_like(x, dtype=np.float32)
|
854
|
+
)
|
ultralytics/utils/patches.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Ultralytics
|
1
|
+
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
2
2
|
"""Monkey patches to update/extend functionality of existing functions."""
|
3
3
|
|
4
4
|
import time
|
@@ -57,28 +57,44 @@ def imshow(winname: str, mat: np.ndarray):
|
|
57
57
|
|
58
58
|
|
59
59
|
# PyTorch functions ----------------------------------------------------------------------------------------------------
|
60
|
-
|
60
|
+
_torch_load = torch.load # copy to avoid recursion errors
|
61
|
+
_torch_save = torch.save
|
61
62
|
|
62
63
|
|
63
|
-
def
|
64
|
+
def torch_load(*args, **kwargs):
|
65
|
+
"""
|
66
|
+
Load a PyTorch model with updated arguments to avoid warnings.
|
67
|
+
|
68
|
+
This function wraps torch.load and adds the 'weights_only' argument for PyTorch 1.13.0+ to prevent warnings.
|
69
|
+
|
70
|
+
Args:
|
71
|
+
*args (Any): Variable length argument list to pass to torch.load.
|
72
|
+
**kwargs (Any): Arbitrary keyword arguments to pass to torch.load.
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
(Any): The loaded PyTorch object.
|
76
|
+
|
77
|
+
Note:
|
78
|
+
For PyTorch versions 2.0 and above, this function automatically sets 'weights_only=False'
|
79
|
+
if the argument is not provided, to avoid deprecation warnings.
|
80
|
+
"""
|
81
|
+
from ultralytics.utils.torch_utils import TORCH_1_13
|
82
|
+
|
83
|
+
if TORCH_1_13 and "weights_only" not in kwargs:
|
84
|
+
kwargs["weights_only"] = False
|
85
|
+
|
86
|
+
return _torch_load(*args, **kwargs)
|
87
|
+
|
88
|
+
|
89
|
+
def torch_save(*args, **kwargs):
|
64
90
|
"""
|
65
91
|
Optionally use dill to serialize lambda functions where pickle does not, adding robustness with 3 retries and
|
66
92
|
exponential standoff in case of save failure.
|
67
93
|
|
68
94
|
Args:
|
69
95
|
*args (tuple): Positional arguments to pass to torch.save.
|
70
|
-
|
71
|
-
**kwargs (any): Keyword arguments to pass to torch.save.
|
96
|
+
**kwargs (Any): Keyword arguments to pass to torch.save.
|
72
97
|
"""
|
73
|
-
try:
|
74
|
-
assert use_dill
|
75
|
-
import dill as pickle
|
76
|
-
except (AssertionError, ImportError):
|
77
|
-
import pickle
|
78
|
-
|
79
|
-
if "pickle_module" not in kwargs:
|
80
|
-
kwargs["pickle_module"] = pickle
|
81
|
-
|
82
98
|
for i in range(4): # 3 retries
|
83
99
|
try:
|
84
100
|
return _torch_save(*args, **kwargs)
|