bplusplus 1.1.0__py3-none-any.whl → 1.2.1__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 bplusplus might be problematic. Click here for more details.

Files changed (97) hide show
  1. bplusplus/__init__.py +4 -2
  2. bplusplus/collect.py +72 -3
  3. bplusplus/hierarchical/test.py +670 -0
  4. bplusplus/hierarchical/train.py +676 -0
  5. bplusplus/prepare.py +236 -71
  6. bplusplus/resnet/test.py +473 -0
  7. bplusplus/resnet/train.py +329 -0
  8. bplusplus-1.2.1.dist-info/METADATA +252 -0
  9. bplusplus-1.2.1.dist-info/RECORD +12 -0
  10. bplusplus/yolov5detect/__init__.py +0 -1
  11. bplusplus/yolov5detect/detect.py +0 -444
  12. bplusplus/yolov5detect/export.py +0 -1530
  13. bplusplus/yolov5detect/insect.yaml +0 -8
  14. bplusplus/yolov5detect/models/__init__.py +0 -0
  15. bplusplus/yolov5detect/models/common.py +0 -1109
  16. bplusplus/yolov5detect/models/experimental.py +0 -130
  17. bplusplus/yolov5detect/models/hub/anchors.yaml +0 -56
  18. bplusplus/yolov5detect/models/hub/yolov3-spp.yaml +0 -52
  19. bplusplus/yolov5detect/models/hub/yolov3-tiny.yaml +0 -42
  20. bplusplus/yolov5detect/models/hub/yolov3.yaml +0 -52
  21. bplusplus/yolov5detect/models/hub/yolov5-bifpn.yaml +0 -49
  22. bplusplus/yolov5detect/models/hub/yolov5-fpn.yaml +0 -43
  23. bplusplus/yolov5detect/models/hub/yolov5-p2.yaml +0 -55
  24. bplusplus/yolov5detect/models/hub/yolov5-p34.yaml +0 -42
  25. bplusplus/yolov5detect/models/hub/yolov5-p6.yaml +0 -57
  26. bplusplus/yolov5detect/models/hub/yolov5-p7.yaml +0 -68
  27. bplusplus/yolov5detect/models/hub/yolov5-panet.yaml +0 -49
  28. bplusplus/yolov5detect/models/hub/yolov5l6.yaml +0 -61
  29. bplusplus/yolov5detect/models/hub/yolov5m6.yaml +0 -61
  30. bplusplus/yolov5detect/models/hub/yolov5n6.yaml +0 -61
  31. bplusplus/yolov5detect/models/hub/yolov5s-LeakyReLU.yaml +0 -50
  32. bplusplus/yolov5detect/models/hub/yolov5s-ghost.yaml +0 -49
  33. bplusplus/yolov5detect/models/hub/yolov5s-transformer.yaml +0 -49
  34. bplusplus/yolov5detect/models/hub/yolov5s6.yaml +0 -61
  35. bplusplus/yolov5detect/models/hub/yolov5x6.yaml +0 -61
  36. bplusplus/yolov5detect/models/segment/yolov5l-seg.yaml +0 -49
  37. bplusplus/yolov5detect/models/segment/yolov5m-seg.yaml +0 -49
  38. bplusplus/yolov5detect/models/segment/yolov5n-seg.yaml +0 -49
  39. bplusplus/yolov5detect/models/segment/yolov5s-seg.yaml +0 -49
  40. bplusplus/yolov5detect/models/segment/yolov5x-seg.yaml +0 -49
  41. bplusplus/yolov5detect/models/tf.py +0 -797
  42. bplusplus/yolov5detect/models/yolo.py +0 -495
  43. bplusplus/yolov5detect/models/yolov5l.yaml +0 -49
  44. bplusplus/yolov5detect/models/yolov5m.yaml +0 -49
  45. bplusplus/yolov5detect/models/yolov5n.yaml +0 -49
  46. bplusplus/yolov5detect/models/yolov5s.yaml +0 -49
  47. bplusplus/yolov5detect/models/yolov5x.yaml +0 -49
  48. bplusplus/yolov5detect/utils/__init__.py +0 -97
  49. bplusplus/yolov5detect/utils/activations.py +0 -134
  50. bplusplus/yolov5detect/utils/augmentations.py +0 -448
  51. bplusplus/yolov5detect/utils/autoanchor.py +0 -175
  52. bplusplus/yolov5detect/utils/autobatch.py +0 -70
  53. bplusplus/yolov5detect/utils/aws/__init__.py +0 -0
  54. bplusplus/yolov5detect/utils/aws/mime.sh +0 -26
  55. bplusplus/yolov5detect/utils/aws/resume.py +0 -41
  56. bplusplus/yolov5detect/utils/aws/userdata.sh +0 -27
  57. bplusplus/yolov5detect/utils/callbacks.py +0 -72
  58. bplusplus/yolov5detect/utils/dataloaders.py +0 -1385
  59. bplusplus/yolov5detect/utils/docker/Dockerfile +0 -73
  60. bplusplus/yolov5detect/utils/docker/Dockerfile-arm64 +0 -40
  61. bplusplus/yolov5detect/utils/docker/Dockerfile-cpu +0 -42
  62. bplusplus/yolov5detect/utils/downloads.py +0 -136
  63. bplusplus/yolov5detect/utils/flask_rest_api/README.md +0 -70
  64. bplusplus/yolov5detect/utils/flask_rest_api/example_request.py +0 -17
  65. bplusplus/yolov5detect/utils/flask_rest_api/restapi.py +0 -49
  66. bplusplus/yolov5detect/utils/general.py +0 -1294
  67. bplusplus/yolov5detect/utils/google_app_engine/Dockerfile +0 -25
  68. bplusplus/yolov5detect/utils/google_app_engine/additional_requirements.txt +0 -6
  69. bplusplus/yolov5detect/utils/google_app_engine/app.yaml +0 -16
  70. bplusplus/yolov5detect/utils/loggers/__init__.py +0 -476
  71. bplusplus/yolov5detect/utils/loggers/clearml/README.md +0 -222
  72. bplusplus/yolov5detect/utils/loggers/clearml/__init__.py +0 -0
  73. bplusplus/yolov5detect/utils/loggers/clearml/clearml_utils.py +0 -230
  74. bplusplus/yolov5detect/utils/loggers/clearml/hpo.py +0 -90
  75. bplusplus/yolov5detect/utils/loggers/comet/README.md +0 -250
  76. bplusplus/yolov5detect/utils/loggers/comet/__init__.py +0 -551
  77. bplusplus/yolov5detect/utils/loggers/comet/comet_utils.py +0 -151
  78. bplusplus/yolov5detect/utils/loggers/comet/hpo.py +0 -126
  79. bplusplus/yolov5detect/utils/loggers/comet/optimizer_config.json +0 -135
  80. bplusplus/yolov5detect/utils/loggers/wandb/__init__.py +0 -0
  81. bplusplus/yolov5detect/utils/loggers/wandb/wandb_utils.py +0 -210
  82. bplusplus/yolov5detect/utils/loss.py +0 -259
  83. bplusplus/yolov5detect/utils/metrics.py +0 -381
  84. bplusplus/yolov5detect/utils/plots.py +0 -517
  85. bplusplus/yolov5detect/utils/segment/__init__.py +0 -0
  86. bplusplus/yolov5detect/utils/segment/augmentations.py +0 -100
  87. bplusplus/yolov5detect/utils/segment/dataloaders.py +0 -366
  88. bplusplus/yolov5detect/utils/segment/general.py +0 -160
  89. bplusplus/yolov5detect/utils/segment/loss.py +0 -198
  90. bplusplus/yolov5detect/utils/segment/metrics.py +0 -225
  91. bplusplus/yolov5detect/utils/segment/plots.py +0 -152
  92. bplusplus/yolov5detect/utils/torch_utils.py +0 -482
  93. bplusplus/yolov5detect/utils/triton.py +0 -90
  94. bplusplus-1.1.0.dist-info/METADATA +0 -179
  95. bplusplus-1.1.0.dist-info/RECORD +0 -92
  96. {bplusplus-1.1.0.dist-info → bplusplus-1.2.1.dist-info}/LICENSE +0 -0
  97. {bplusplus-1.1.0.dist-info → bplusplus-1.2.1.dist-info}/WHEEL +0 -0
@@ -1,448 +0,0 @@
1
- # Ultralytics YOLOv5 🚀, AGPL-3.0 license
2
- """Image augmentation functions."""
3
-
4
- import math
5
- import random
6
-
7
- import cv2
8
- import numpy as np
9
- import torch
10
- import torchvision.transforms as T
11
- import torchvision.transforms.functional as TF
12
-
13
- from utils.general import LOGGER, check_version, colorstr, resample_segments, segment2box, xywhn2xyxy
14
- from utils.metrics import bbox_ioa
15
-
16
- IMAGENET_MEAN = 0.485, 0.456, 0.406 # RGB mean
17
- IMAGENET_STD = 0.229, 0.224, 0.225 # RGB standard deviation
18
-
19
-
20
- class Albumentations:
21
- """Provides optional data augmentation for YOLOv5 using Albumentations library if installed."""
22
-
23
- def __init__(self, size=640):
24
- """Initializes Albumentations class for optional data augmentation in YOLOv5 with specified input size."""
25
- self.transform = None
26
- prefix = colorstr("albumentations: ")
27
- try:
28
- import albumentations as A
29
-
30
- check_version(A.__version__, "1.0.3", hard=True) # version requirement
31
-
32
- T = [
33
- A.RandomResizedCrop(height=size, width=size, scale=(0.8, 1.0), ratio=(0.9, 1.11), p=0.0),
34
- A.Blur(p=0.01),
35
- A.MedianBlur(p=0.01),
36
- A.ToGray(p=0.01),
37
- A.CLAHE(p=0.01),
38
- A.RandomBrightnessContrast(p=0.0),
39
- A.RandomGamma(p=0.0),
40
- A.ImageCompression(quality_lower=75, p=0.0),
41
- ] # transforms
42
- self.transform = A.Compose(T, bbox_params=A.BboxParams(format="yolo", label_fields=["class_labels"]))
43
-
44
- LOGGER.info(prefix + ", ".join(f"{x}".replace("always_apply=False, ", "") for x in T if x.p))
45
- except ImportError: # package not installed, skip
46
- pass
47
- except Exception as e:
48
- LOGGER.info(f"{prefix}{e}")
49
-
50
- def __call__(self, im, labels, p=1.0):
51
- """Applies transformations to an image and labels with probability `p`, returning updated image and labels."""
52
- if self.transform and random.random() < p:
53
- new = self.transform(image=im, bboxes=labels[:, 1:], class_labels=labels[:, 0]) # transformed
54
- im, labels = new["image"], np.array([[c, *b] for c, b in zip(new["class_labels"], new["bboxes"])])
55
- return im, labels
56
-
57
-
58
- def normalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD, inplace=False):
59
- """
60
- Applies ImageNet normalization to RGB images in BCHW format, modifying them in-place if specified.
61
-
62
- Example: y = (x - mean) / std
63
- """
64
- return TF.normalize(x, mean, std, inplace=inplace)
65
-
66
-
67
- def denormalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD):
68
- """Reverses ImageNet normalization for BCHW format RGB images by applying `x = x * std + mean`."""
69
- for i in range(3):
70
- x[:, i] = x[:, i] * std[i] + mean[i]
71
- return x
72
-
73
-
74
- def augment_hsv(im, hgain=0.5, sgain=0.5, vgain=0.5):
75
- """Applies HSV color-space augmentation to an image with random gains for hue, saturation, and value."""
76
- if hgain or sgain or vgain:
77
- r = np.random.uniform(-1, 1, 3) * [hgain, sgain, vgain] + 1 # random gains
78
- hue, sat, val = cv2.split(cv2.cvtColor(im, cv2.COLOR_BGR2HSV))
79
- dtype = im.dtype # uint8
80
-
81
- x = np.arange(0, 256, dtype=r.dtype)
82
- lut_hue = ((x * r[0]) % 180).astype(dtype)
83
- lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)
84
- lut_val = np.clip(x * r[2], 0, 255).astype(dtype)
85
-
86
- im_hsv = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val)))
87
- cv2.cvtColor(im_hsv, cv2.COLOR_HSV2BGR, dst=im) # no return needed
88
-
89
-
90
- def hist_equalize(im, clahe=True, bgr=False):
91
- """Equalizes image histogram, with optional CLAHE, for BGR or RGB image with shape (n,m,3) and range 0-255."""
92
- yuv = cv2.cvtColor(im, cv2.COLOR_BGR2YUV if bgr else cv2.COLOR_RGB2YUV)
93
- if clahe:
94
- c = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
95
- yuv[:, :, 0] = c.apply(yuv[:, :, 0])
96
- else:
97
- yuv[:, :, 0] = cv2.equalizeHist(yuv[:, :, 0]) # equalize Y channel histogram
98
- return cv2.cvtColor(yuv, cv2.COLOR_YUV2BGR if bgr else cv2.COLOR_YUV2RGB) # convert YUV image to RGB
99
-
100
-
101
- def replicate(im, labels):
102
- """
103
- Replicates half of the smallest object labels in an image for data augmentation.
104
-
105
- Returns augmented image and labels.
106
- """
107
- h, w = im.shape[:2]
108
- boxes = labels[:, 1:].astype(int)
109
- x1, y1, x2, y2 = boxes.T
110
- s = ((x2 - x1) + (y2 - y1)) / 2 # side length (pixels)
111
- for i in s.argsort()[: round(s.size * 0.5)]: # smallest indices
112
- x1b, y1b, x2b, y2b = boxes[i]
113
- bh, bw = y2b - y1b, x2b - x1b
114
- yc, xc = int(random.uniform(0, h - bh)), int(random.uniform(0, w - bw)) # offset x, y
115
- x1a, y1a, x2a, y2a = [xc, yc, xc + bw, yc + bh]
116
- im[y1a:y2a, x1a:x2a] = im[y1b:y2b, x1b:x2b] # im4[ymin:ymax, xmin:xmax]
117
- labels = np.append(labels, [[labels[i, 0], x1a, y1a, x2a, y2a]], axis=0)
118
-
119
- return im, labels
120
-
121
-
122
- def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32):
123
- """Resizes and pads image to new_shape with stride-multiple constraints, returns resized image, ratio, padding."""
124
- shape = im.shape[:2] # current shape [height, width]
125
- if isinstance(new_shape, int):
126
- new_shape = (new_shape, new_shape)
127
-
128
- # Scale ratio (new / old)
129
- r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
130
- if not scaleup: # only scale down, do not scale up (for better val mAP)
131
- r = min(r, 1.0)
132
-
133
- # Compute padding
134
- ratio = r, r # width, height ratios
135
- new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
136
- dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
137
- if auto: # minimum rectangle
138
- dw, dh = np.mod(dw, stride), np.mod(dh, stride) # wh padding
139
- elif scaleFill: # stretch
140
- dw, dh = 0.0, 0.0
141
- new_unpad = (new_shape[1], new_shape[0])
142
- ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios
143
-
144
- dw /= 2 # divide padding into 2 sides
145
- dh /= 2
146
-
147
- if shape[::-1] != new_unpad: # resize
148
- im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
149
- top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
150
- left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
151
- im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
152
- return im, ratio, (dw, dh)
153
-
154
-
155
- def random_perspective(
156
- im, targets=(), segments=(), degrees=10, translate=0.1, scale=0.1, shear=10, perspective=0.0, border=(0, 0)
157
- ):
158
- # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(0.1, 0.1), scale=(0.9, 1.1), shear=(-10, 10))
159
- # targets = [cls, xyxy]
160
- """Applies random perspective transformation to an image, modifying the image and corresponding labels."""
161
- height = im.shape[0] + border[0] * 2 # shape(h,w,c)
162
- width = im.shape[1] + border[1] * 2
163
-
164
- # Center
165
- C = np.eye(3)
166
- C[0, 2] = -im.shape[1] / 2 # x translation (pixels)
167
- C[1, 2] = -im.shape[0] / 2 # y translation (pixels)
168
-
169
- # Perspective
170
- P = np.eye(3)
171
- P[2, 0] = random.uniform(-perspective, perspective) # x perspective (about y)
172
- P[2, 1] = random.uniform(-perspective, perspective) # y perspective (about x)
173
-
174
- # Rotation and Scale
175
- R = np.eye(3)
176
- a = random.uniform(-degrees, degrees)
177
- # a += random.choice([-180, -90, 0, 90]) # add 90deg rotations to small rotations
178
- s = random.uniform(1 - scale, 1 + scale)
179
- # s = 2 ** random.uniform(-scale, scale)
180
- R[:2] = cv2.getRotationMatrix2D(angle=a, center=(0, 0), scale=s)
181
-
182
- # Shear
183
- S = np.eye(3)
184
- S[0, 1] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # x shear (deg)
185
- S[1, 0] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # y shear (deg)
186
-
187
- # Translation
188
- T = np.eye(3)
189
- T[0, 2] = random.uniform(0.5 - translate, 0.5 + translate) * width # x translation (pixels)
190
- T[1, 2] = random.uniform(0.5 - translate, 0.5 + translate) * height # y translation (pixels)
191
-
192
- # Combined rotation matrix
193
- M = T @ S @ R @ P @ C # order of operations (right to left) is IMPORTANT
194
- if (border[0] != 0) or (border[1] != 0) or (M != np.eye(3)).any(): # image changed
195
- if perspective:
196
- im = cv2.warpPerspective(im, M, dsize=(width, height), borderValue=(114, 114, 114))
197
- else: # affine
198
- im = cv2.warpAffine(im, M[:2], dsize=(width, height), borderValue=(114, 114, 114))
199
-
200
- # Visualize
201
- # import matplotlib.pyplot as plt
202
- # ax = plt.subplots(1, 2, figsize=(12, 6))[1].ravel()
203
- # ax[0].imshow(im[:, :, ::-1]) # base
204
- # ax[1].imshow(im2[:, :, ::-1]) # warped
205
-
206
- # Transform label coordinates
207
- n = len(targets)
208
- if n:
209
- use_segments = any(x.any() for x in segments) and len(segments) == n
210
- new = np.zeros((n, 4))
211
- if use_segments: # warp segments
212
- segments = resample_segments(segments) # upsample
213
- for i, segment in enumerate(segments):
214
- xy = np.ones((len(segment), 3))
215
- xy[:, :2] = segment
216
- xy = xy @ M.T # transform
217
- xy = xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2] # perspective rescale or affine
218
-
219
- # clip
220
- new[i] = segment2box(xy, width, height)
221
-
222
- else: # warp boxes
223
- xy = np.ones((n * 4, 3))
224
- xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2) # x1y1, x2y2, x1y2, x2y1
225
- xy = xy @ M.T # transform
226
- xy = (xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2]).reshape(n, 8) # perspective rescale or affine
227
-
228
- # create new boxes
229
- x = xy[:, [0, 2, 4, 6]]
230
- y = xy[:, [1, 3, 5, 7]]
231
- new = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T
232
-
233
- # clip
234
- new[:, [0, 2]] = new[:, [0, 2]].clip(0, width)
235
- new[:, [1, 3]] = new[:, [1, 3]].clip(0, height)
236
-
237
- # filter candidates
238
- i = box_candidates(box1=targets[:, 1:5].T * s, box2=new.T, area_thr=0.01 if use_segments else 0.10)
239
- targets = targets[i]
240
- targets[:, 1:5] = new[i]
241
-
242
- return im, targets
243
-
244
-
245
- def copy_paste(im, labels, segments, p=0.5):
246
- """
247
- Applies Copy-Paste augmentation by flipping and merging segments and labels on an image.
248
-
249
- Details at https://arxiv.org/abs/2012.07177.
250
- """
251
- n = len(segments)
252
- if p and n:
253
- h, w, c = im.shape # height, width, channels
254
- im_new = np.zeros(im.shape, np.uint8)
255
- for j in random.sample(range(n), k=round(p * n)):
256
- l, s = labels[j], segments[j]
257
- box = w - l[3], l[2], w - l[1], l[4]
258
- ioa = bbox_ioa(box, labels[:, 1:5]) # intersection over area
259
- if (ioa < 0.30).all(): # allow 30% obscuration of existing labels
260
- labels = np.concatenate((labels, [[l[0], *box]]), 0)
261
- segments.append(np.concatenate((w - s[:, 0:1], s[:, 1:2]), 1))
262
- cv2.drawContours(im_new, [segments[j].astype(np.int32)], -1, (1, 1, 1), cv2.FILLED)
263
-
264
- result = cv2.flip(im, 1) # augment segments (flip left-right)
265
- i = cv2.flip(im_new, 1).astype(bool)
266
- im[i] = result[i] # cv2.imwrite('debug.jpg', im) # debug
267
-
268
- return im, labels, segments
269
-
270
-
271
- def cutout(im, labels, p=0.5):
272
- """
273
- Applies cutout augmentation to an image with optional label adjustment, using random masks of varying sizes.
274
-
275
- Details at https://arxiv.org/abs/1708.04552.
276
- """
277
- if random.random() < p:
278
- h, w = im.shape[:2]
279
- scales = [0.5] * 1 + [0.25] * 2 + [0.125] * 4 + [0.0625] * 8 + [0.03125] * 16 # image size fraction
280
- for s in scales:
281
- mask_h = random.randint(1, int(h * s)) # create random masks
282
- mask_w = random.randint(1, int(w * s))
283
-
284
- # box
285
- xmin = max(0, random.randint(0, w) - mask_w // 2)
286
- ymin = max(0, random.randint(0, h) - mask_h // 2)
287
- xmax = min(w, xmin + mask_w)
288
- ymax = min(h, ymin + mask_h)
289
-
290
- # apply random color mask
291
- im[ymin:ymax, xmin:xmax] = [random.randint(64, 191) for _ in range(3)]
292
-
293
- # return unobscured labels
294
- if len(labels) and s > 0.03:
295
- box = np.array([xmin, ymin, xmax, ymax], dtype=np.float32)
296
- ioa = bbox_ioa(box, xywhn2xyxy(labels[:, 1:5], w, h)) # intersection over area
297
- labels = labels[ioa < 0.60] # remove >60% obscured labels
298
-
299
- return labels
300
-
301
-
302
- def mixup(im, labels, im2, labels2):
303
- """
304
- Applies MixUp augmentation by blending images and labels.
305
-
306
- See https://arxiv.org/pdf/1710.09412.pdf for details.
307
- """
308
- r = np.random.beta(32.0, 32.0) # mixup ratio, alpha=beta=32.0
309
- im = (im * r + im2 * (1 - r)).astype(np.uint8)
310
- labels = np.concatenate((labels, labels2), 0)
311
- return im, labels
312
-
313
-
314
- def box_candidates(box1, box2, wh_thr=2, ar_thr=100, area_thr=0.1, eps=1e-16):
315
- """
316
- Filters bounding box candidates by minimum width-height threshold `wh_thr` (pixels), aspect ratio threshold
317
- `ar_thr`, and area ratio threshold `area_thr`.
318
-
319
- box1(4,n) is before augmentation, box2(4,n) is after augmentation.
320
- """
321
- w1, h1 = box1[2] - box1[0], box1[3] - box1[1]
322
- w2, h2 = box2[2] - box2[0], box2[3] - box2[1]
323
- ar = np.maximum(w2 / (h2 + eps), h2 / (w2 + eps)) # aspect ratio
324
- return (w2 > wh_thr) & (h2 > wh_thr) & (w2 * h2 / (w1 * h1 + eps) > area_thr) & (ar < ar_thr) # candidates
325
-
326
-
327
- def classify_albumentations(
328
- augment=True,
329
- size=224,
330
- scale=(0.08, 1.0),
331
- ratio=(0.75, 1.0 / 0.75), # 0.75, 1.33
332
- hflip=0.5,
333
- vflip=0.0,
334
- jitter=0.4,
335
- mean=IMAGENET_MEAN,
336
- std=IMAGENET_STD,
337
- auto_aug=False,
338
- ):
339
- # YOLOv5 classification Albumentations (optional, only used if package is installed)
340
- """Sets up and returns Albumentations transforms for YOLOv5 classification tasks depending on augmentation
341
- settings.
342
- """
343
- prefix = colorstr("albumentations: ")
344
- try:
345
- import albumentations as A
346
- from albumentations.pytorch import ToTensorV2
347
-
348
- check_version(A.__version__, "1.0.3", hard=True) # version requirement
349
- if augment: # Resize and crop
350
- T = [A.RandomResizedCrop(height=size, width=size, scale=scale, ratio=ratio)]
351
- if auto_aug:
352
- # TODO: implement AugMix, AutoAug & RandAug in albumentation
353
- LOGGER.info(f"{prefix}auto augmentations are currently not supported")
354
- else:
355
- if hflip > 0:
356
- T += [A.HorizontalFlip(p=hflip)]
357
- if vflip > 0:
358
- T += [A.VerticalFlip(p=vflip)]
359
- if jitter > 0:
360
- color_jitter = (float(jitter),) * 3 # repeat value for brightness, contrast, saturation, 0 hue
361
- T += [A.ColorJitter(*color_jitter, 0)]
362
- else: # Use fixed crop for eval set (reproducibility)
363
- T = [A.SmallestMaxSize(max_size=size), A.CenterCrop(height=size, width=size)]
364
- T += [A.Normalize(mean=mean, std=std), ToTensorV2()] # Normalize and convert to Tensor
365
- LOGGER.info(prefix + ", ".join(f"{x}".replace("always_apply=False, ", "") for x in T if x.p))
366
- return A.Compose(T)
367
-
368
- except ImportError: # package not installed, skip
369
- LOGGER.warning(f"{prefix}⚠️ not found, install with `pip install albumentations` (recommended)")
370
- except Exception as e:
371
- LOGGER.info(f"{prefix}{e}")
372
-
373
-
374
- def classify_transforms(size=224):
375
- """Applies a series of transformations including center crop, ToTensor, and normalization for classification."""
376
- assert isinstance(size, int), f"ERROR: classify_transforms size {size} must be integer, not (list, tuple)"
377
- # T.Compose([T.ToTensor(), T.Resize(size), T.CenterCrop(size), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)])
378
- return T.Compose([CenterCrop(size), ToTensor(), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)])
379
-
380
-
381
- class LetterBox:
382
- """Resizes and pads images to specified dimensions while maintaining aspect ratio for YOLOv5 preprocessing."""
383
-
384
- def __init__(self, size=(640, 640), auto=False, stride=32):
385
- """Initializes a LetterBox object for YOLOv5 image preprocessing with optional auto sizing and stride
386
- adjustment.
387
- """
388
- super().__init__()
389
- self.h, self.w = (size, size) if isinstance(size, int) else size
390
- self.auto = auto # pass max size integer, automatically solve for short side using stride
391
- self.stride = stride # used with auto
392
-
393
- def __call__(self, im):
394
- """
395
- Resizes and pads input image `im` (HWC format) to specified dimensions, maintaining aspect ratio.
396
-
397
- im = np.array HWC
398
- """
399
- imh, imw = im.shape[:2]
400
- r = min(self.h / imh, self.w / imw) # ratio of new/old
401
- h, w = round(imh * r), round(imw * r) # resized image
402
- hs, ws = (math.ceil(x / self.stride) * self.stride for x in (h, w)) if self.auto else self.h, self.w
403
- top, left = round((hs - h) / 2 - 0.1), round((ws - w) / 2 - 0.1)
404
- im_out = np.full((self.h, self.w, 3), 114, dtype=im.dtype)
405
- im_out[top : top + h, left : left + w] = cv2.resize(im, (w, h), interpolation=cv2.INTER_LINEAR)
406
- return im_out
407
-
408
-
409
- class CenterCrop:
410
- """Applies center crop to an image, resizing it to the specified size while maintaining aspect ratio."""
411
-
412
- def __init__(self, size=640):
413
- """Initializes CenterCrop for image preprocessing, accepting single int or tuple for size, defaults to 640."""
414
- super().__init__()
415
- self.h, self.w = (size, size) if isinstance(size, int) else size
416
-
417
- def __call__(self, im):
418
- """
419
- Applies center crop to the input image and resizes it to a specified size, maintaining aspect ratio.
420
-
421
- im = np.array HWC
422
- """
423
- imh, imw = im.shape[:2]
424
- m = min(imh, imw) # min dimension
425
- top, left = (imh - m) // 2, (imw - m) // 2
426
- return cv2.resize(im[top : top + m, left : left + m], (self.w, self.h), interpolation=cv2.INTER_LINEAR)
427
-
428
-
429
- class ToTensor:
430
- """Converts BGR np.array image from HWC to RGB CHW format, normalizes to [0, 1], and supports FP16 if half=True."""
431
-
432
- def __init__(self, half=False):
433
- """Initializes ToTensor for YOLOv5 image preprocessing, with optional half precision (half=True for FP16)."""
434
- super().__init__()
435
- self.half = half
436
-
437
- def __call__(self, im):
438
- """
439
- Converts BGR np.array image from HWC to RGB CHW format, and normalizes to [0, 1], with support for FP16 if
440
- `half=True`.
441
-
442
- im = np.array HWC in BGR order
443
- """
444
- im = np.ascontiguousarray(im.transpose((2, 0, 1))[::-1]) # HWC to CHW -> BGR to RGB -> contiguous
445
- im = torch.from_numpy(im) # to torch
446
- im = im.half() if self.half else im.float() # uint8 to fp16/32
447
- im /= 255.0 # 0-255 to 0.0-1.0
448
- return im
@@ -1,175 +0,0 @@
1
- # Ultralytics YOLOv5 🚀, AGPL-3.0 license
2
- """AutoAnchor utils."""
3
-
4
- import random
5
-
6
- import numpy as np
7
- import torch
8
- import yaml
9
- from tqdm import tqdm
10
-
11
- from utils import TryExcept
12
- from utils.general import LOGGER, TQDM_BAR_FORMAT, colorstr
13
-
14
- PREFIX = colorstr("AutoAnchor: ")
15
-
16
-
17
- def check_anchor_order(m):
18
- """Checks and corrects anchor order against stride in YOLOv5 Detect() module if necessary."""
19
- a = m.anchors.prod(-1).mean(-1).view(-1) # mean anchor area per output layer
20
- da = a[-1] - a[0] # delta a
21
- ds = m.stride[-1] - m.stride[0] # delta s
22
- if da and (da.sign() != ds.sign()): # same order
23
- LOGGER.info(f"{PREFIX}Reversing anchor order")
24
- m.anchors[:] = m.anchors.flip(0)
25
-
26
-
27
- @TryExcept(f"{PREFIX}ERROR")
28
- def check_anchors(dataset, model, thr=4.0, imgsz=640):
29
- """Evaluates anchor fit to dataset and adjusts if necessary, supporting customizable threshold and image size."""
30
- m = model.module.model[-1] if hasattr(model, "module") else model.model[-1] # Detect()
31
- shapes = imgsz * dataset.shapes / dataset.shapes.max(1, keepdims=True)
32
- scale = np.random.uniform(0.9, 1.1, size=(shapes.shape[0], 1)) # augment scale
33
- wh = torch.tensor(np.concatenate([l[:, 3:5] * s for s, l in zip(shapes * scale, dataset.labels)])).float() # wh
34
-
35
- def metric(k): # compute metric
36
- """Computes ratio metric, anchors above threshold, and best possible recall for YOLOv5 anchor evaluation."""
37
- r = wh[:, None] / k[None]
38
- x = torch.min(r, 1 / r).min(2)[0] # ratio metric
39
- best = x.max(1)[0] # best_x
40
- aat = (x > 1 / thr).float().sum(1).mean() # anchors above threshold
41
- bpr = (best > 1 / thr).float().mean() # best possible recall
42
- return bpr, aat
43
-
44
- stride = m.stride.to(m.anchors.device).view(-1, 1, 1) # model strides
45
- anchors = m.anchors.clone() * stride # current anchors
46
- bpr, aat = metric(anchors.cpu().view(-1, 2))
47
- s = f"\n{PREFIX}{aat:.2f} anchors/target, {bpr:.3f} Best Possible Recall (BPR). "
48
- if bpr > 0.98: # threshold to recompute
49
- LOGGER.info(f"{s}Current anchors are a good fit to dataset ✅")
50
- else:
51
- LOGGER.info(f"{s}Anchors are a poor fit to dataset ⚠️, attempting to improve...")
52
- na = m.anchors.numel() // 2 # number of anchors
53
- anchors = kmean_anchors(dataset, n=na, img_size=imgsz, thr=thr, gen=1000, verbose=False)
54
- new_bpr = metric(anchors)[0]
55
- if new_bpr > bpr: # replace anchors
56
- anchors = torch.tensor(anchors, device=m.anchors.device).type_as(m.anchors)
57
- m.anchors[:] = anchors.clone().view_as(m.anchors)
58
- check_anchor_order(m) # must be in pixel-space (not grid-space)
59
- m.anchors /= stride
60
- s = f"{PREFIX}Done ✅ (optional: update model *.yaml to use these anchors in the future)"
61
- else:
62
- s = f"{PREFIX}Done ⚠️ (original anchors better than new anchors, proceeding with original anchors)"
63
- LOGGER.info(s)
64
-
65
-
66
- def kmean_anchors(dataset="./data/coco128.yaml", n=9, img_size=640, thr=4.0, gen=1000, verbose=True):
67
- """
68
- Creates kmeans-evolved anchors from training dataset.
69
-
70
- Arguments:
71
- dataset: path to data.yaml, or a loaded dataset
72
- n: number of anchors
73
- img_size: image size used for training
74
- thr: anchor-label wh ratio threshold hyperparameter hyp['anchor_t'] used for training, default=4.0
75
- gen: generations to evolve anchors using genetic algorithm
76
- verbose: print all results
77
-
78
- Return:
79
- k: kmeans evolved anchors
80
-
81
- Usage:
82
- from utils.autoanchor import *; _ = kmean_anchors()
83
- """
84
- from scipy.cluster.vq import kmeans
85
-
86
- npr = np.random
87
- thr = 1 / thr
88
-
89
- def metric(k, wh): # compute metrics
90
- """Computes ratio metric, anchors above threshold, and best possible recall for YOLOv5 anchor evaluation."""
91
- r = wh[:, None] / k[None]
92
- x = torch.min(r, 1 / r).min(2)[0] # ratio metric
93
- # x = wh_iou(wh, torch.tensor(k)) # iou metric
94
- return x, x.max(1)[0] # x, best_x
95
-
96
- def anchor_fitness(k): # mutation fitness
97
- """Evaluates fitness of YOLOv5 anchors by computing recall and ratio metrics for an anchor evolution process."""
98
- _, best = metric(torch.tensor(k, dtype=torch.float32), wh)
99
- return (best * (best > thr).float()).mean() # fitness
100
-
101
- def print_results(k, verbose=True):
102
- """Sorts and logs kmeans-evolved anchor metrics and best possible recall values for YOLOv5 anchor evaluation."""
103
- k = k[np.argsort(k.prod(1))] # sort small to large
104
- x, best = metric(k, wh0)
105
- bpr, aat = (best > thr).float().mean(), (x > thr).float().mean() * n # best possible recall, anch > thr
106
- s = (
107
- f"{PREFIX}thr={thr:.2f}: {bpr:.4f} best possible recall, {aat:.2f} anchors past thr\n"
108
- f"{PREFIX}n={n}, img_size={img_size}, metric_all={x.mean():.3f}/{best.mean():.3f}-mean/best, "
109
- f"past_thr={x[x > thr].mean():.3f}-mean: "
110
- )
111
- for x in k:
112
- s += "%i,%i, " % (round(x[0]), round(x[1]))
113
- if verbose:
114
- LOGGER.info(s[:-2])
115
- return k
116
-
117
- if isinstance(dataset, str): # *.yaml file
118
- with open(dataset, errors="ignore") as f:
119
- data_dict = yaml.safe_load(f) # model dict
120
- from utils.dataloaders import LoadImagesAndLabels
121
-
122
- dataset = LoadImagesAndLabels(data_dict["train"], augment=True, rect=True)
123
-
124
- # Get label wh
125
- shapes = img_size * dataset.shapes / dataset.shapes.max(1, keepdims=True)
126
- wh0 = np.concatenate([l[:, 3:5] * s for s, l in zip(shapes, dataset.labels)]) # wh
127
-
128
- # Filter
129
- i = (wh0 < 3.0).any(1).sum()
130
- if i:
131
- LOGGER.info(f"{PREFIX}WARNING ⚠️ Extremely small objects found: {i} of {len(wh0)} labels are <3 pixels in size")
132
- wh = wh0[(wh0 >= 2.0).any(1)].astype(np.float32) # filter > 2 pixels
133
- # wh = wh * (npr.rand(wh.shape[0], 1) * 0.9 + 0.1) # multiply by random scale 0-1
134
-
135
- # Kmeans init
136
- try:
137
- LOGGER.info(f"{PREFIX}Running kmeans for {n} anchors on {len(wh)} points...")
138
- assert n <= len(wh) # apply overdetermined constraint
139
- s = wh.std(0) # sigmas for whitening
140
- k = kmeans(wh / s, n, iter=30)[0] * s # points
141
- assert n == len(k) # kmeans may return fewer points than requested if wh is insufficient or too similar
142
- except Exception:
143
- LOGGER.warning(f"{PREFIX}WARNING ⚠️ switching strategies from kmeans to random init")
144
- k = np.sort(npr.rand(n * 2)).reshape(n, 2) * img_size # random init
145
- wh, wh0 = (torch.tensor(x, dtype=torch.float32) for x in (wh, wh0))
146
- k = print_results(k, verbose=False)
147
-
148
- # Plot
149
- # k, d = [None] * 20, [None] * 20
150
- # for i in tqdm(range(1, 21)):
151
- # k[i-1], d[i-1] = kmeans(wh / s, i) # points, mean distance
152
- # fig, ax = plt.subplots(1, 2, figsize=(14, 7), tight_layout=True)
153
- # ax = ax.ravel()
154
- # ax[0].plot(np.arange(1, 21), np.array(d) ** 2, marker='.')
155
- # fig, ax = plt.subplots(1, 2, figsize=(14, 7)) # plot wh
156
- # ax[0].hist(wh[wh[:, 0]<100, 0],400)
157
- # ax[1].hist(wh[wh[:, 1]<100, 1],400)
158
- # fig.savefig('wh.png', dpi=200)
159
-
160
- # Evolve
161
- f, sh, mp, s = anchor_fitness(k), k.shape, 0.9, 0.1 # fitness, generations, mutation prob, sigma
162
- pbar = tqdm(range(gen), bar_format=TQDM_BAR_FORMAT) # progress bar
163
- for _ in pbar:
164
- v = np.ones(sh)
165
- while (v == 1).all(): # mutate until a change occurs (prevent duplicates)
166
- v = ((npr.random(sh) < mp) * random.random() * npr.randn(*sh) * s + 1).clip(0.3, 3.0)
167
- kg = (k.copy() * v).clip(min=2.0)
168
- fg = anchor_fitness(kg)
169
- if fg > f:
170
- f, k = fg, kg.copy()
171
- pbar.desc = f"{PREFIX}Evolving anchors with Genetic Algorithm: fitness = {f:.4f}"
172
- if verbose:
173
- print_results(k, verbose)
174
-
175
- return print_results(k).astype(np.float32)