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.
- bplusplus/__init__.py +4 -2
- bplusplus/collect.py +72 -3
- bplusplus/hierarchical/test.py +670 -0
- bplusplus/hierarchical/train.py +676 -0
- bplusplus/prepare.py +236 -71
- bplusplus/resnet/test.py +473 -0
- bplusplus/resnet/train.py +329 -0
- bplusplus-1.2.1.dist-info/METADATA +252 -0
- bplusplus-1.2.1.dist-info/RECORD +12 -0
- bplusplus/yolov5detect/__init__.py +0 -1
- bplusplus/yolov5detect/detect.py +0 -444
- bplusplus/yolov5detect/export.py +0 -1530
- bplusplus/yolov5detect/insect.yaml +0 -8
- bplusplus/yolov5detect/models/__init__.py +0 -0
- bplusplus/yolov5detect/models/common.py +0 -1109
- bplusplus/yolov5detect/models/experimental.py +0 -130
- bplusplus/yolov5detect/models/hub/anchors.yaml +0 -56
- bplusplus/yolov5detect/models/hub/yolov3-spp.yaml +0 -52
- bplusplus/yolov5detect/models/hub/yolov3-tiny.yaml +0 -42
- bplusplus/yolov5detect/models/hub/yolov3.yaml +0 -52
- bplusplus/yolov5detect/models/hub/yolov5-bifpn.yaml +0 -49
- bplusplus/yolov5detect/models/hub/yolov5-fpn.yaml +0 -43
- bplusplus/yolov5detect/models/hub/yolov5-p2.yaml +0 -55
- bplusplus/yolov5detect/models/hub/yolov5-p34.yaml +0 -42
- bplusplus/yolov5detect/models/hub/yolov5-p6.yaml +0 -57
- bplusplus/yolov5detect/models/hub/yolov5-p7.yaml +0 -68
- bplusplus/yolov5detect/models/hub/yolov5-panet.yaml +0 -49
- bplusplus/yolov5detect/models/hub/yolov5l6.yaml +0 -61
- bplusplus/yolov5detect/models/hub/yolov5m6.yaml +0 -61
- bplusplus/yolov5detect/models/hub/yolov5n6.yaml +0 -61
- bplusplus/yolov5detect/models/hub/yolov5s-LeakyReLU.yaml +0 -50
- bplusplus/yolov5detect/models/hub/yolov5s-ghost.yaml +0 -49
- bplusplus/yolov5detect/models/hub/yolov5s-transformer.yaml +0 -49
- bplusplus/yolov5detect/models/hub/yolov5s6.yaml +0 -61
- bplusplus/yolov5detect/models/hub/yolov5x6.yaml +0 -61
- bplusplus/yolov5detect/models/segment/yolov5l-seg.yaml +0 -49
- bplusplus/yolov5detect/models/segment/yolov5m-seg.yaml +0 -49
- bplusplus/yolov5detect/models/segment/yolov5n-seg.yaml +0 -49
- bplusplus/yolov5detect/models/segment/yolov5s-seg.yaml +0 -49
- bplusplus/yolov5detect/models/segment/yolov5x-seg.yaml +0 -49
- bplusplus/yolov5detect/models/tf.py +0 -797
- bplusplus/yolov5detect/models/yolo.py +0 -495
- bplusplus/yolov5detect/models/yolov5l.yaml +0 -49
- bplusplus/yolov5detect/models/yolov5m.yaml +0 -49
- bplusplus/yolov5detect/models/yolov5n.yaml +0 -49
- bplusplus/yolov5detect/models/yolov5s.yaml +0 -49
- bplusplus/yolov5detect/models/yolov5x.yaml +0 -49
- bplusplus/yolov5detect/utils/__init__.py +0 -97
- bplusplus/yolov5detect/utils/activations.py +0 -134
- bplusplus/yolov5detect/utils/augmentations.py +0 -448
- bplusplus/yolov5detect/utils/autoanchor.py +0 -175
- bplusplus/yolov5detect/utils/autobatch.py +0 -70
- bplusplus/yolov5detect/utils/aws/__init__.py +0 -0
- bplusplus/yolov5detect/utils/aws/mime.sh +0 -26
- bplusplus/yolov5detect/utils/aws/resume.py +0 -41
- bplusplus/yolov5detect/utils/aws/userdata.sh +0 -27
- bplusplus/yolov5detect/utils/callbacks.py +0 -72
- bplusplus/yolov5detect/utils/dataloaders.py +0 -1385
- bplusplus/yolov5detect/utils/docker/Dockerfile +0 -73
- bplusplus/yolov5detect/utils/docker/Dockerfile-arm64 +0 -40
- bplusplus/yolov5detect/utils/docker/Dockerfile-cpu +0 -42
- bplusplus/yolov5detect/utils/downloads.py +0 -136
- bplusplus/yolov5detect/utils/flask_rest_api/README.md +0 -70
- bplusplus/yolov5detect/utils/flask_rest_api/example_request.py +0 -17
- bplusplus/yolov5detect/utils/flask_rest_api/restapi.py +0 -49
- bplusplus/yolov5detect/utils/general.py +0 -1294
- bplusplus/yolov5detect/utils/google_app_engine/Dockerfile +0 -25
- bplusplus/yolov5detect/utils/google_app_engine/additional_requirements.txt +0 -6
- bplusplus/yolov5detect/utils/google_app_engine/app.yaml +0 -16
- bplusplus/yolov5detect/utils/loggers/__init__.py +0 -476
- bplusplus/yolov5detect/utils/loggers/clearml/README.md +0 -222
- bplusplus/yolov5detect/utils/loggers/clearml/__init__.py +0 -0
- bplusplus/yolov5detect/utils/loggers/clearml/clearml_utils.py +0 -230
- bplusplus/yolov5detect/utils/loggers/clearml/hpo.py +0 -90
- bplusplus/yolov5detect/utils/loggers/comet/README.md +0 -250
- bplusplus/yolov5detect/utils/loggers/comet/__init__.py +0 -551
- bplusplus/yolov5detect/utils/loggers/comet/comet_utils.py +0 -151
- bplusplus/yolov5detect/utils/loggers/comet/hpo.py +0 -126
- bplusplus/yolov5detect/utils/loggers/comet/optimizer_config.json +0 -135
- bplusplus/yolov5detect/utils/loggers/wandb/__init__.py +0 -0
- bplusplus/yolov5detect/utils/loggers/wandb/wandb_utils.py +0 -210
- bplusplus/yolov5detect/utils/loss.py +0 -259
- bplusplus/yolov5detect/utils/metrics.py +0 -381
- bplusplus/yolov5detect/utils/plots.py +0 -517
- bplusplus/yolov5detect/utils/segment/__init__.py +0 -0
- bplusplus/yolov5detect/utils/segment/augmentations.py +0 -100
- bplusplus/yolov5detect/utils/segment/dataloaders.py +0 -366
- bplusplus/yolov5detect/utils/segment/general.py +0 -160
- bplusplus/yolov5detect/utils/segment/loss.py +0 -198
- bplusplus/yolov5detect/utils/segment/metrics.py +0 -225
- bplusplus/yolov5detect/utils/segment/plots.py +0 -152
- bplusplus/yolov5detect/utils/torch_utils.py +0 -482
- bplusplus/yolov5detect/utils/triton.py +0 -90
- bplusplus-1.1.0.dist-info/METADATA +0 -179
- bplusplus-1.1.0.dist-info/RECORD +0 -92
- {bplusplus-1.1.0.dist-info → bplusplus-1.2.1.dist-info}/LICENSE +0 -0
- {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)
|