import glob
import logging
import math
import os
import platform
import random
import re
import subprocess
import time
from pathlib import Path
import cv2
import matplotlib
import numpy as np
import torch
import yaml
from utils.google_utils import gsutil_getsize
from utils.metrics import fitness, fitness_p, fitness_r, fitness_ap50, fitness_ap, fitness_f
from utils.torch_utils import init_torch_seeds
torch.set_printoptions(linewidth=320, precision=5, profile='long')
np.set_printoptions(linewidth=320, formatter={'float_kind': '{:11.5g}'.format})
matplotlib.rc('font', **{'size': 11})
cv2.setNumThreads(0)
def set_logging(rank=-1):
logging.basicConfig(
format="%(message)s",
level=logging.INFO if rank in [-1, 0] else logging.WARN)
def init_seeds(seed=0):
random.seed(seed)
np.random.seed(seed)
init_torch_seeds(seed)
def get_latest_run(search_dir='.'):
last_list = glob.glob(f'{search_dir}/**/last*.pt', recursive=True)
return max(last_list, key=os.path.getctime) if last_list else ''
def check_git_status():
if platform.system() in ['Linux', 'Darwin'] and not os.path.isfile('/.dockerenv'):
s = subprocess.check_output('if [ -d .git ]; then git fetch && git status -uno; fi', shell=True).decode('utf-8')
if 'Your branch is behind' in s:
print(s[s.find('Your branch is behind'):s.find('\n\n')] + '\n')
def check_img_size(img_size, s=32):
new_size = make_divisible(img_size, int(s))
if new_size != img_size:
print('WARNING: --img-size %g must be multiple of max stride %g, updating to %g' % (img_size, s, new_size))
return new_size
def check_file(file):
if os.path.isfile(file) or file == '':
return file
else:
files = glob.glob('./**/' + file, recursive=True)
assert len(files), 'File Not Found: %s' % file
assert len(files) == 1, "Multiple files match '%s', specify exact path: %s" % (file, files)
return files[0]
def check_dataset(dict):
val, s = dict.get('val'), dict.get('download')
if val and len(val):
val = [Path(x).resolve() for x in (val if isinstance(val, list) else [val])]
if not all(x.exists() for x in val):
print('\nWARNING: Dataset not found, nonexistent paths: %s' % [str(x) for x in val if not x.exists()])
if s and len(s):
print('Downloading %s ...' % s)
if s.startswith('http') and s.endswith('.zip'):
f = Path(s).name
torch.hub.download_url_to_file(s, f)
r = os.system('unzip -q %s -d ../ && rm %s' % (f, f))
else:
r = os.system(s)
print('Dataset autodownload %s\n' % ('success' if r == 0 else 'failure'))
else:
raise Exception('Dataset not found.')
def make_divisible(x, divisor):
return math.ceil(x / divisor) * divisor
def labels_to_class_weights(labels, nc=80):
if labels[0] is None:
return torch.Tensor()
labels = np.concatenate(labels, 0)
classes = labels[:, 0].astype(np.int)
weights = np.bincount(classes, minlength=nc)
weights[weights == 0] = 1
weights = 1 / weights
weights /= weights.sum()
return torch.from_numpy(weights)
def labels_to_image_weights(labels, nc=80, class_weights=np.ones(80)):
n = len(labels)
class_counts = np.array([np.bincount(labels[i][:, 0].astype(np.int), minlength=nc) for i in range(n)])
image_weights = (class_weights.reshape(1, nc) * class_counts).sum(1)
return image_weights
def coco80_to_coco91_class():
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27, 28, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
64, 65, 67, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90]
return x
def xyxy2xywh(x):
y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
y[:, 0] = (x[:, 0] + x[:, 2]) / 2
y[:, 1] = (x[:, 1] + x[:, 3]) / 2
y[:, 2] = x[:, 2] - x[:, 0]
y[:, 3] = x[:, 3] - x[:, 1]
return y
def xywh2xyxy(x):
y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
y[:, 0] = x[:, 0] - x[:, 2] / 2
y[:, 1] = x[:, 1] - x[:, 3] / 2
y[:, 2] = x[:, 0] + x[:, 2] / 2
y[:, 3] = x[:, 1] + x[:, 3] / 2
return y
def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):
if ratio_pad is None:
gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])
pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2
else:
gain = ratio_pad[0][0]
pad = ratio_pad[1]
coords[:, [0, 2]] -= pad[0]
coords[:, [1, 3]] -= pad[1]
coords[:, :4] /= gain
clip_coords(coords, img0_shape)
return coords
def clip_coords(boxes, img_shape):
boxes[:, 0].clamp_(0, img_shape[1])
boxes[:, 1].clamp_(0, img_shape[0])
boxes[:, 2].clamp_(0, img_shape[1])
boxes[:, 3].clamp_(0, img_shape[0])
def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False, EIoU=False, ECIoU=False, eps=1e-9):
if x1y1x2y2:
b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]
b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]
else:
b1_x1, b1_x2 = box1[0] - box1[2] / 2, box1[0] + box1[2] / 2
b1_y1, b1_y2 = box1[1] - box1[3] / 2, box1[1] + box1[3] / 2
b2_x1, b2_x2 = box2[0] - box2[2] / 2, box2[0] + box2[2] / 2
b2_y1, b2_y2 = box2[1] - box2[3] / 2, box2[1] + box2[3] / 2
inter = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \
(torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0)
w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps
w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps
union = w1 * h1 + w2 * h2 - inter + eps
iou = inter / union
if GIoU or DIoU or CIoU or EIoU or ECIoU:
cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1)
ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1)
if CIoU or DIoU or EIoU or ECIoU:
c2 = cw ** 2 + ch ** 2 + eps
rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 +
(b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4
if DIoU:
return iou - rho2 / c2
elif CIoU:
v = (4 / math.pi ** 2) * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2)
with torch.no_grad():
alpha = v / ((1 + eps) - iou + v)
return iou - (rho2 / c2 + v * alpha)
elif EIoU:
rho3 = (w1-w2) **2
c3 = cw ** 2 + eps
rho4 = (h1-h2) **2
c4 = ch ** 2 + eps
return iou - rho2 / c2 - rho3 / c3 - rho4 / c4
elif ECIoU:
v = (4 / math.pi ** 2) * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2)
with torch.no_grad():
alpha = v / ((1 + eps) - iou + v)
rho3 = (w1-w2) **2
c3 = cw ** 2 + eps
rho4 = (h1-h2) **2
c4 = ch ** 2 + eps
return iou - v * alpha - rho2 / c2 - rho3 / c3 - rho4 / c4
else:
c_area = cw * ch + eps
return iou - (c_area - union) / c_area
else:
return iou
def box_iou(box1, box2):
"""
Return intersection-over-union (Jaccard index) of boxes.
Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
Arguments:
box1 (Tensor[N, 4])
box2 (Tensor[M, 4])
Returns:
iou (Tensor[N, M]): the NxM matrix containing the pairwise
IoU values for every element in boxes1 and boxes2
"""
def box_area(box):
return (box[2] - box[0]) * (box[3] - box[1])
area1 = box_area(box1.T)
area2 = box_area(box2.T)
inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)
return inter / (area1[:, None] + area2 - inter)
def wh_iou(wh1, wh2):
wh1 = wh1[:, None]
wh2 = wh2[None]
inter = torch.min(wh1, wh2).prod(2)
return inter / (wh1.prod(2) + wh2.prod(2) - inter)
def non_max_suppression(prediction, conf_thres=0.1, iou_thres=0.6, merge=False, classes=None, agnostic=False):
"""Performs Non-Maximum Suppression (NMS) on inference results
Returns:
detections with shape: nx6 (x1, y1, x2, y2, conf, cls)
"""
nc = prediction[0].shape[1] - 5
xc = prediction[..., 4] > conf_thres
min_wh, max_wh = 2, 4096
max_det = 300
time_limit = 10.0
redundant = True
multi_label = nc > 1
t = time.time()
output = [None] * prediction.shape[0]
for xi, x in enumerate(prediction):
x = x.cpu()
x = x[xc[xi]]
if not x.shape[0]:
continue
x[:, 5:] *= x[:, 4:5]
box = xywh2xyxy(x[:, :4])
if multi_label:
i, j = (x[:, 5:] > conf_thres).nonzero(as_tuple=False).T
x = torch.cat((box[i], x[i, j + 5, None], j[:, None].float()), 1)
else:
conf, j = x[:, 5:].max(1, keepdim=True)
x = torch.cat((box, conf, j.float()), 1)[conf.view(-1) > conf_thres]
if classes:
x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]
n = x.shape[0]
if not n:
continue
c = x[:, 5:6] * (0 if agnostic else max_wh)
boxes, scores = x[:, :4] + c, x[:, 4]
start_t = time.time()
if scores.device.type != 'cuda':
i = nms(boxes, scores, iou_thres)
else:
import torchvision
i = torchvision.ops.boxes.nms(boxes, scores, iou_thres)
if i.shape[0] > max_det:
i = i[:max_det]
if merge and (1 < n < 3E3):
try:
iou = box_iou(boxes[i], boxes) > iou_thres
weights = iou * scores[None]
x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True)
if redundant:
i = i[iou.sum(1) > 1]
except:
print(x, i, x.shape, i.shape)
pass
output[xi] = x[i]
return output
def nms(bboxes, scores, threshold=0.5):
x1 = bboxes[:,0]
y1 = bboxes[:,1]
x2 = bboxes[:,2]
y2 = bboxes[:,3]
areas = (x2-x1)*(y2-y1)
_, order = scores.sort(0, descending=True)
keep = []
while order.numel() > 0:
if order.numel() == 1:
i = order.item()
keep.append(i)
break
else:
i = order[0].item()
keep.append(i)
xx1 = x1[order[1:]].clamp(min=x1[i])
yy1 = y1[order[1:]].clamp(min=y1[i])
xx2 = x2[order[1:]].clamp(max=x2[i])
yy2 = y2[order[1:]].clamp(max=y2[i])
inter = (xx2-xx1).clamp(min=0) * (yy2-yy1).clamp(min=0)
iou = inter / (areas[i]+areas[order[1:]]-inter)
idx = (iou <= threshold).nonzero().squeeze()
if idx.numel() == 0:
break
order = order[idx+1]
return torch.LongTensor(keep)
def strip_optimizer(f='weights/best.pt', s=''):
x = torch.load(f, map_location=torch.device('cpu'))
x['optimizer'] = None
x['training_results'] = None
x['epoch'] = -1
torch.save(x, s or f)
mb = os.path.getsize(s or f) / 1E6
print('Optimizer stripped from %s,%s %.1fMB' % (f, (' saved as %s,' % s) if s else '', mb))
def print_mutation(hyp, results, yaml_file='hyp_evolved.yaml', bucket=''):
a = '%10s' * len(hyp) % tuple(hyp.keys())
b = '%10.3g' * len(hyp) % tuple(hyp.values())
c = '%10.4g' * len(results) % results
print('\n%s\n%s\nEvolved fitness: %s\n' % (a, b, c))
if bucket:
url = 'gs://%s/evolve.txt' % bucket
if gsutil_getsize(url) > (os.path.getsize('evolve.txt') if os.path.exists('evolve.txt') else 0):
os.system('gsutil cp %s .' % url)
with open('evolve.txt', 'a') as f:
f.write(c + b + '\n')
x = np.unique(np.loadtxt('evolve.txt', ndmin=2), axis=0)
x = x[np.argsort(-fitness(x))]
np.savetxt('evolve.txt', x, '%10.3g')
for i, k in enumerate(hyp.keys()):
hyp[k] = float(x[0, i + 7])
with open(yaml_file, 'w') as f:
results = tuple(x[0, :7])
c = '%10.4g' * len(results) % results
f.write('# Hyperparameter Evolution Results\n# Generations: %g\n# Metrics: ' % len(x) + c + '\n\n')
yaml.dump(hyp, f, sort_keys=False)
if bucket:
os.system('gsutil cp evolve.txt %s gs://%s' % (yaml_file, bucket))
def apply_classifier(x, model, img, im0):
im0 = [im0] if isinstance(im0, np.ndarray) else im0
for i, d in enumerate(x):
if d is not None and len(d):
d = d.clone()
b = xyxy2xywh(d[:, :4])
b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1)
b[:, 2:] = b[:, 2:] * 1.3 + 30
d[:, :4] = xywh2xyxy(b).long()
scale_coords(img.shape[2:], d[:, :4], im0[i].shape)
pred_cls1 = d[:, 5].long()
ims = []
for j, a in enumerate(d):
cutout = im0[i][int(a[1]):int(a[3]), int(a[0]):int(a[2])]
im = cv2.resize(cutout, (224, 224))
im = im[:, :, ::-1].transpose(2, 0, 1)
im = np.ascontiguousarray(im, dtype=np.float32)
im /= 255.0
ims.append(im)
pred_cls2 = model(torch.Tensor(ims).to(d.device)).argmax(1)
x[i] = x[i][pred_cls1 == pred_cls2]
return x
def increment_path(path, exist_ok=True, sep=''):
path = Path(path)
if (path.exists() and exist_ok) or (not path.exists()):
return str(path)
else:
dirs = glob.glob(f"{path}{sep}*")
matches = [re.search(rf"%s{sep}(\d+)" % path.stem, d) for d in dirs]
i = [int(m.groups()[0]) for m in matches if m]
n = max(i) + 1 if i else 2
return f"{path}{sep}{n}"