glenn-jocher commited on
Commit
fe341fa
1 Parent(s): 379396e

Utils reorganization (#1392)

Browse files

* Utils reorganization

* Add new utils files

* cleanup

* simplify

* reduce datasets.py

* remove evolve.sh

* loadWebcam cleanup

detect.py CHANGED
@@ -10,14 +10,15 @@ from numpy import random
10
  from models.experimental import attempt_load
11
  from utils.datasets import LoadStreams, LoadImages
12
  from utils.general import check_img_size, non_max_suppression, apply_classifier, scale_coords, xyxy2xywh, \
13
- plot_one_box, strip_optimizer, set_logging, increment_path
 
14
  from utils.torch_utils import select_device, load_classifier, time_synchronized
15
 
16
 
17
  def detect(save_img=False):
18
  source, weights, view_img, save_txt, imgsz = opt.source, opt.weights, opt.view_img, opt.save_txt, opt.img_size
19
- webcam = source.isnumeric() or source.endswith('.txt') or \
20
- source.lower().startswith(('rtsp://', 'rtmp://', 'http://'))
21
 
22
  # Directories
23
  save_dir = Path(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok)) # increment run
@@ -38,8 +39,7 @@ def detect(save_img=False):
38
  classify = False
39
  if classify:
40
  modelc = load_classifier(name='resnet101', n=2) # initialize
41
- modelc.load_state_dict(torch.load('weights/resnet101.pt', map_location=device)['model']) # load weights
42
- modelc.to(device).eval()
43
 
44
  # Set Dataloader
45
  vid_path, vid_writer = None, None
@@ -53,7 +53,7 @@ def detect(save_img=False):
53
 
54
  # Get names and colors
55
  names = model.module.names if hasattr(model, 'module') else model.names
56
- colors = [[random.randint(0, 255) for _ in range(3)] for _ in range(len(names))]
57
 
58
  # Run inference
59
  t0 = time.time()
 
10
  from models.experimental import attempt_load
11
  from utils.datasets import LoadStreams, LoadImages
12
  from utils.general import check_img_size, non_max_suppression, apply_classifier, scale_coords, xyxy2xywh, \
13
+ strip_optimizer, set_logging, increment_path
14
+ from utils.plots import plot_one_box
15
  from utils.torch_utils import select_device, load_classifier, time_synchronized
16
 
17
 
18
  def detect(save_img=False):
19
  source, weights, view_img, save_txt, imgsz = opt.source, opt.weights, opt.view_img, opt.save_txt, opt.img_size
20
+ webcam = source.isnumeric() or source.endswith('.txt') or source.lower().startswith(
21
+ ('rtsp://', 'rtmp://', 'http://'))
22
 
23
  # Directories
24
  save_dir = Path(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok)) # increment run
 
39
  classify = False
40
  if classify:
41
  modelc = load_classifier(name='resnet101', n=2) # initialize
42
+ modelc.load_state_dict(torch.load('weights/resnet101.pt', map_location=device)['model']).to(device).eval()
 
43
 
44
  # Set Dataloader
45
  vid_path, vid_writer = None, None
 
53
 
54
  # Get names and colors
55
  names = model.module.names if hasattr(model, 'module') else model.names
56
+ colors = [[random.randint(0, 255) for _ in range(3)] for _ in names]
57
 
58
  # Run inference
59
  t0 = time.time()
models/yolo.py CHANGED
@@ -13,10 +13,16 @@ import torch.nn as nn
13
 
14
  from models.common import Conv, Bottleneck, SPP, DWConv, Focus, BottleneckCSP, Concat, NMS, autoShape
15
  from models.experimental import MixConv2d, CrossConv, C3
16
- from utils.general import check_anchor_order, make_divisible, check_file, set_logging
 
17
  from utils.torch_utils import time_synchronized, fuse_conv_and_bn, model_info, scale_img, initialize_weights, \
18
  select_device, copy_attr
19
 
 
 
 
 
 
20
 
21
  class Detect(nn.Module):
22
  stride = None # strides computed during build
@@ -121,11 +127,7 @@ class Model(nn.Module):
121
  x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f] # from earlier layers
122
 
123
  if profile:
124
- try:
125
- import thop
126
- o = thop.profile(m, inputs=(x,), verbose=False)[0] / 1E9 * 2 # FLOPS
127
- except:
128
- o = 0
129
  t = time_synchronized()
130
  for _ in range(10):
131
  _ = m(x)
 
13
 
14
  from models.common import Conv, Bottleneck, SPP, DWConv, Focus, BottleneckCSP, Concat, NMS, autoShape
15
  from models.experimental import MixConv2d, CrossConv, C3
16
+ from utils.autoanchor import check_anchor_order
17
+ from utils.general import make_divisible, check_file, set_logging
18
  from utils.torch_utils import time_synchronized, fuse_conv_and_bn, model_info, scale_img, initialize_weights, \
19
  select_device, copy_attr
20
 
21
+ try:
22
+ import thop # for FLOPS computation
23
+ except ImportError:
24
+ thop = None
25
+
26
 
27
  class Detect(nn.Module):
28
  stride = None # strides computed during build
 
127
  x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f] # from earlier layers
128
 
129
  if profile:
130
+ o = thop.profile(m, inputs=(x,), verbose=False)[0] / 1E9 * 2 if thop else 0 # FLOPS
 
 
 
 
131
  t = time_synchronized()
132
  for _ in range(10):
133
  _ = m(x)
test.py CHANGED
@@ -11,9 +11,11 @@ from tqdm import tqdm
11
 
12
  from models.experimental import attempt_load
13
  from utils.datasets import create_dataloader
14
- from utils.general import coco80_to_coco91_class, check_dataset, check_file, check_img_size, compute_loss, \
15
- non_max_suppression, scale_coords, xyxy2xywh, clip_coords, plot_images, xywh2xyxy, box_iou, output_to_target, \
16
- ap_per_class, set_logging, increment_path
 
 
17
  from utils.torch_utils import select_device, time_synchronized
18
 
19
 
 
11
 
12
  from models.experimental import attempt_load
13
  from utils.datasets import create_dataloader
14
+ from utils.general import coco80_to_coco91_class, check_dataset, check_file, check_img_size, box_iou, \
15
+ non_max_suppression, scale_coords, xyxy2xywh, xywh2xyxy, clip_coords, set_logging, increment_path
16
+ from utils.loss import compute_loss
17
+ from utils.metrics import ap_per_class
18
+ from utils.plots import plot_images, output_to_target
19
  from utils.torch_utils import select_device, time_synchronized
20
 
21
 
train.py CHANGED
@@ -3,7 +3,6 @@ import logging
3
  import math
4
  import os
5
  import random
6
- import shutil
7
  import time
8
  from pathlib import Path
9
  from warnings import warn
@@ -23,13 +22,15 @@ from tqdm import tqdm
23
 
24
  import test # import test.py to get mAP after each epoch
25
  from models.yolo import Model
 
26
  from utils.datasets import create_dataloader
27
- from utils.general import (
28
- torch_distributed_zero_first, labels_to_class_weights, plot_labels, check_anchors, labels_to_image_weights,
29
- compute_loss, plot_images, fitness, strip_optimizer, plot_results, get_latest_run, check_dataset, check_file,
30
- check_git_status, check_img_size, increment_path, print_mutation, plot_evolution, set_logging, init_seeds)
31
  from utils.google_utils import attempt_download
32
- from utils.torch_utils import ModelEMA, select_device, intersect_dicts
 
 
33
 
34
  logger = logging.getLogger(__name__)
35
 
@@ -209,7 +210,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None):
209
 
210
  # Start training
211
  t0 = time.time()
212
- nw = max(round(hyp['warmup_epochs'] * nb), 1e3) # number of warmup iterations, max(3 epochs, 1k iterations)
213
  # nw = min(nw, (epochs - start_epoch) / 2 * nb) # limit warmup to < 1/2 of training
214
  maps = np.zeros(nc) # mAP per class
215
  results = (0, 0, 0, 0, 0, 0, 0) # P, R, [email protected], [email protected], val_loss(box, obj, cls)
@@ -334,9 +335,9 @@ def train(hyp, opt, device, tb_writer=None, wandb=None):
334
  os.system('gsutil cp %s gs://%s/results/results%s.txt' % (results_file, opt.bucket, opt.name))
335
 
336
  # Log
337
- tags = ['train/giou_loss', 'train/obj_loss', 'train/cls_loss', # train loss
338
  'metrics/precision', 'metrics/recall', 'metrics/mAP_0.5', 'metrics/mAP_0.5:0.95',
339
- 'val/giou_loss', 'val/obj_loss', 'val/cls_loss', # val loss
340
  'x/lr0', 'x/lr1', 'x/lr2'] # params
341
  for x, tag in zip(list(mloss[:-1]) + list(results) + lr, tags):
342
  if tb_writer:
 
3
  import math
4
  import os
5
  import random
 
6
  import time
7
  from pathlib import Path
8
  from warnings import warn
 
22
 
23
  import test # import test.py to get mAP after each epoch
24
  from models.yolo import Model
25
+ from utils.autoanchor import check_anchors
26
  from utils.datasets import create_dataloader
27
+ from utils.general import labels_to_class_weights, increment_path, labels_to_image_weights, init_seeds, \
28
+ fitness, strip_optimizer, get_latest_run, check_dataset, check_file, check_git_status, check_img_size, \
29
+ print_mutation, set_logging
 
30
  from utils.google_utils import attempt_download
31
+ from utils.loss import compute_loss
32
+ from utils.plots import plot_images, plot_labels, plot_results, plot_evolution
33
+ from utils.torch_utils import ModelEMA, select_device, intersect_dicts, torch_distributed_zero_first
34
 
35
  logger = logging.getLogger(__name__)
36
 
 
210
 
211
  # Start training
212
  t0 = time.time()
213
+ nw = max(round(hyp['warmup_epochs'] * nb), 1000) # number of warmup iterations, max(3 epochs, 1k iterations)
214
  # nw = min(nw, (epochs - start_epoch) / 2 * nb) # limit warmup to < 1/2 of training
215
  maps = np.zeros(nc) # mAP per class
216
  results = (0, 0, 0, 0, 0, 0, 0) # P, R, [email protected], [email protected], val_loss(box, obj, cls)
 
335
  os.system('gsutil cp %s gs://%s/results/results%s.txt' % (results_file, opt.bucket, opt.name))
336
 
337
  # Log
338
+ tags = ['train/box_loss', 'train/obj_loss', 'train/cls_loss', # train loss
339
  'metrics/precision', 'metrics/recall', 'metrics/mAP_0.5', 'metrics/mAP_0.5:0.95',
340
+ 'val/box_loss', 'val/obj_loss', 'val/cls_loss', # val loss
341
  'x/lr0', 'x/lr1', 'x/lr2'] # params
342
  for x, tag in zip(list(mloss[:-1]) + list(results) + lr, tags):
343
  if tb_writer:
utils/activations.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import torch
2
  import torch.nn as nn
3
  import torch.nn.functional as F
 
1
+ # Activation functions
2
+
3
  import torch
4
  import torch.nn as nn
5
  import torch.nn.functional as F
utils/autoanchor.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Auto-anchor utils
2
+
3
+ import numpy as np
4
+ import torch
5
+ import yaml
6
+ from scipy.cluster.vq import kmeans
7
+ from tqdm import tqdm
8
+
9
+
10
+ def check_anchor_order(m):
11
+ # Check anchor order against stride order for YOLOv5 Detect() module m, and correct if necessary
12
+ a = m.anchor_grid.prod(-1).view(-1) # anchor area
13
+ da = a[-1] - a[0] # delta a
14
+ ds = m.stride[-1] - m.stride[0] # delta s
15
+ if da.sign() != ds.sign(): # same order
16
+ print('Reversing anchor order')
17
+ m.anchors[:] = m.anchors.flip(0)
18
+ m.anchor_grid[:] = m.anchor_grid.flip(0)
19
+
20
+
21
+ def check_anchors(dataset, model, thr=4.0, imgsz=640):
22
+ # Check anchor fit to data, recompute if necessary
23
+ print('\nAnalyzing anchors... ', end='')
24
+ m = model.module.model[-1] if hasattr(model, 'module') else model.model[-1] # Detect()
25
+ shapes = imgsz * dataset.shapes / dataset.shapes.max(1, keepdims=True)
26
+ scale = np.random.uniform(0.9, 1.1, size=(shapes.shape[0], 1)) # augment scale
27
+ wh = torch.tensor(np.concatenate([l[:, 3:5] * s for s, l in zip(shapes * scale, dataset.labels)])).float() # wh
28
+
29
+ def metric(k): # compute metric
30
+ r = wh[:, None] / k[None]
31
+ x = torch.min(r, 1. / r).min(2)[0] # ratio metric
32
+ best = x.max(1)[0] # best_x
33
+ aat = (x > 1. / thr).float().sum(1).mean() # anchors above threshold
34
+ bpr = (best > 1. / thr).float().mean() # best possible recall
35
+ return bpr, aat
36
+
37
+ bpr, aat = metric(m.anchor_grid.clone().cpu().view(-1, 2))
38
+ print('anchors/target = %.2f, Best Possible Recall (BPR) = %.4f' % (aat, bpr), end='')
39
+ if bpr < 0.98: # threshold to recompute
40
+ print('. Attempting to improve anchors, please wait...')
41
+ na = m.anchor_grid.numel() // 2 # number of anchors
42
+ new_anchors = kmean_anchors(dataset, n=na, img_size=imgsz, thr=thr, gen=1000, verbose=False)
43
+ new_bpr = metric(new_anchors.reshape(-1, 2))[0]
44
+ if new_bpr > bpr: # replace anchors
45
+ new_anchors = torch.tensor(new_anchors, device=m.anchors.device).type_as(m.anchors)
46
+ m.anchor_grid[:] = new_anchors.clone().view_as(m.anchor_grid) # for inference
47
+ m.anchors[:] = new_anchors.clone().view_as(m.anchors) / m.stride.to(m.anchors.device).view(-1, 1, 1) # loss
48
+ check_anchor_order(m)
49
+ print('New anchors saved to model. Update model *.yaml to use these anchors in the future.')
50
+ else:
51
+ print('Original anchors better than new anchors. Proceeding with original anchors.')
52
+ print('') # newline
53
+
54
+
55
+ def kmean_anchors(path='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen=1000, verbose=True):
56
+ """ Creates kmeans-evolved anchors from training dataset
57
+
58
+ Arguments:
59
+ path: path to dataset *.yaml, or a loaded dataset
60
+ n: number of anchors
61
+ img_size: image size used for training
62
+ thr: anchor-label wh ratio threshold hyperparameter hyp['anchor_t'] used for training, default=4.0
63
+ gen: generations to evolve anchors using genetic algorithm
64
+ verbose: print all results
65
+
66
+ Return:
67
+ k: kmeans evolved anchors
68
+
69
+ Usage:
70
+ from utils.general import *; _ = kmean_anchors()
71
+ """
72
+ thr = 1. / thr
73
+
74
+ def metric(k, wh): # compute metrics
75
+ r = wh[:, None] / k[None]
76
+ x = torch.min(r, 1. / r).min(2)[0] # ratio metric
77
+ # x = wh_iou(wh, torch.tensor(k)) # iou metric
78
+ return x, x.max(1)[0] # x, best_x
79
+
80
+ def anchor_fitness(k): # mutation fitness
81
+ _, best = metric(torch.tensor(k, dtype=torch.float32), wh)
82
+ return (best * (best > thr).float()).mean() # fitness
83
+
84
+ def print_results(k):
85
+ k = k[np.argsort(k.prod(1))] # sort small to large
86
+ x, best = metric(k, wh0)
87
+ bpr, aat = (best > thr).float().mean(), (x > thr).float().mean() * n # best possible recall, anch > thr
88
+ print('thr=%.2f: %.4f best possible recall, %.2f anchors past thr' % (thr, bpr, aat))
89
+ print('n=%g, img_size=%s, metric_all=%.3f/%.3f-mean/best, past_thr=%.3f-mean: ' %
90
+ (n, img_size, x.mean(), best.mean(), x[x > thr].mean()), end='')
91
+ for i, x in enumerate(k):
92
+ print('%i,%i' % (round(x[0]), round(x[1])), end=', ' if i < len(k) - 1 else '\n') # use in *.cfg
93
+ return k
94
+
95
+ if isinstance(path, str): # *.yaml file
96
+ with open(path) as f:
97
+ data_dict = yaml.load(f, Loader=yaml.FullLoader) # model dict
98
+ from utils.datasets import LoadImagesAndLabels
99
+ dataset = LoadImagesAndLabels(data_dict['train'], augment=True, rect=True)
100
+ else:
101
+ dataset = path # dataset
102
+
103
+ # Get label wh
104
+ shapes = img_size * dataset.shapes / dataset.shapes.max(1, keepdims=True)
105
+ wh0 = np.concatenate([l[:, 3:5] * s for s, l in zip(shapes, dataset.labels)]) # wh
106
+
107
+ # Filter
108
+ i = (wh0 < 3.0).any(1).sum()
109
+ if i:
110
+ print('WARNING: Extremely small objects found. '
111
+ '%g of %g labels are < 3 pixels in width or height.' % (i, len(wh0)))
112
+ wh = wh0[(wh0 >= 2.0).any(1)] # filter > 2 pixels
113
+
114
+ # Kmeans calculation
115
+ print('Running kmeans for %g anchors on %g points...' % (n, len(wh)))
116
+ s = wh.std(0) # sigmas for whitening
117
+ k, dist = kmeans(wh / s, n, iter=30) # points, mean distance
118
+ k *= s
119
+ wh = torch.tensor(wh, dtype=torch.float32) # filtered
120
+ wh0 = torch.tensor(wh0, dtype=torch.float32) # unfiltered
121
+ k = print_results(k)
122
+
123
+ # Plot
124
+ # k, d = [None] * 20, [None] * 20
125
+ # for i in tqdm(range(1, 21)):
126
+ # k[i-1], d[i-1] = kmeans(wh / s, i) # points, mean distance
127
+ # fig, ax = plt.subplots(1, 2, figsize=(14, 7))
128
+ # ax = ax.ravel()
129
+ # ax[0].plot(np.arange(1, 21), np.array(d) ** 2, marker='.')
130
+ # fig, ax = plt.subplots(1, 2, figsize=(14, 7)) # plot wh
131
+ # ax[0].hist(wh[wh[:, 0]<100, 0],400)
132
+ # ax[1].hist(wh[wh[:, 1]<100, 1],400)
133
+ # fig.tight_layout()
134
+ # fig.savefig('wh.png', dpi=200)
135
+
136
+ # Evolve
137
+ npr = np.random
138
+ f, sh, mp, s = anchor_fitness(k), k.shape, 0.9, 0.1 # fitness, generations, mutation prob, sigma
139
+ pbar = tqdm(range(gen), desc='Evolving anchors with Genetic Algorithm') # progress bar
140
+ for _ in pbar:
141
+ v = np.ones(sh)
142
+ while (v == 1).all(): # mutate until a change occurs (prevent duplicates)
143
+ v = ((npr.random(sh) < mp) * npr.random() * npr.randn(*sh) * s + 1).clip(0.3, 3.0)
144
+ kg = (k.copy() * v).clip(min=2.0)
145
+ fg = anchor_fitness(kg)
146
+ if fg > f:
147
+ f, k = fg, kg.copy()
148
+ pbar.desc = 'Evolving anchors with Genetic Algorithm: fitness = %.4f' % f
149
+ if verbose:
150
+ print_results(k)
151
+
152
+ return print_results(k)
utils/datasets.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import glob
2
  import math
3
  import os
@@ -16,8 +18,10 @@ from PIL import Image, ExifTags
16
  from torch.utils.data import Dataset
17
  from tqdm import tqdm
18
 
19
- from utils.general import xyxy2xywh, xywh2xyxy, torch_distributed_zero_first
 
20
 
 
21
  help_url = 'https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data'
22
  img_formats = ['.bmp', '.jpg', '.jpeg', '.png', '.tif', '.tiff', '.dng']
23
  vid_formats = ['.mov', '.avi', '.mp4', '.mpg', '.mpeg', '.m4v', '.wmv', '.mkv']
@@ -50,7 +54,7 @@ def exif_size(img):
50
 
51
  def create_dataloader(path, imgsz, batch_size, stride, opt, hyp=None, augment=False, cache=False, pad=0.0, rect=False,
52
  rank=-1, world_size=1, workers=8):
53
- # Make sure only the first process in DDP process the dataset first, and the following others can use the cache.
54
  with torch_distributed_zero_first(rank):
55
  dataset = LoadImagesAndLabels(path, imgsz, batch_size,
56
  augment=augment, # augment images
@@ -75,9 +79,9 @@ def create_dataloader(path, imgsz, batch_size, stride, opt, hyp=None, augment=Fa
75
 
76
 
77
  class InfiniteDataLoader(torch.utils.data.dataloader.DataLoader):
78
- """ Dataloader that reuses workers.
79
 
80
- Uses same syntax as vanilla DataLoader.
81
  """
82
 
83
  def __init__(self, *args, **kwargs):
@@ -94,7 +98,7 @@ class InfiniteDataLoader(torch.utils.data.dataloader.DataLoader):
94
 
95
 
96
  class _RepeatSampler(object):
97
- """ Sampler that repeats forever.
98
 
99
  Args:
100
  sampler (Sampler)
@@ -177,7 +181,6 @@ class LoadImages: # for inference
177
  img = img[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, to 3x416x416
178
  img = np.ascontiguousarray(img)
179
 
180
- # cv2.imwrite(path + '.letterbox.jpg', 255 * img.transpose((1, 2, 0))[:, :, ::-1]) # save letterbox image
181
  return path, img, img0, self.cap
182
 
183
  def new_video(self, path):
@@ -190,23 +193,15 @@ class LoadImages: # for inference
190
 
191
 
192
  class LoadWebcam: # for inference
193
- def __init__(self, pipe=0, img_size=640):
194
  self.img_size = img_size
195
 
196
- if pipe == '0':
197
- pipe = 0 # local camera
198
  # pipe = 'rtsp://192.168.1.64/1' # IP camera
199
  # pipe = 'rtsp://username:[email protected]/1' # IP camera with login
200
- # pipe = 'rtsp://170.93.143.139/rtplive/470011e600ef003a004ee33696235daa' # IP traffic camera
201
  # pipe = 'http://wmccpinetop.axiscam.net/mjpg/video.mjpg' # IP golf camera
202
 
203
- # https://answers.opencv.org/question/215996/changing-gstreamer-pipeline-to-opencv-in-pythonsolved/
204
- # pipe = '"rtspsrc location="rtsp://username:[email protected]/1" latency=10 ! appsink' # GStreamer
205
-
206
- # https://answers.opencv.org/question/200787/video-acceleration-gstremer-pipeline-in-videocapture/
207
- # https://stackoverflow.com/questions/54095699/install-gstreamer-support-for-opencv-python-package # install help
208
- # pipe = "rtspsrc location=rtsp://root:[email protected]:554/axis-media/media.amp?videocodec=h264&resolution=3840x2160 protocols=GST_RTSP_LOWER_TRANS_TCP ! rtph264depay ! queue ! vaapih264dec ! videoconvert ! appsink" # GStreamer
209
-
210
  self.pipe = pipe
211
  self.cap = cv2.VideoCapture(pipe) # video capture object
212
  self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 3) # set buffer size
@@ -895,52 +890,6 @@ def cutout(image, labels):
895
  return labels
896
 
897
 
898
- def reduce_img_size(path='path/images', img_size=1024): # from utils.datasets import *; reduce_img_size()
899
- # creates a new ./images_reduced folder with reduced size images of maximum size img_size
900
- path_new = path + '_reduced' # reduced images path
901
- create_folder(path_new)
902
- for f in tqdm(glob.glob('%s/*.*' % path)):
903
- try:
904
- img = cv2.imread(f)
905
- h, w = img.shape[:2]
906
- r = img_size / max(h, w) # size ratio
907
- if r < 1.0:
908
- img = cv2.resize(img, (int(w * r), int(h * r)), interpolation=cv2.INTER_AREA) # _LINEAR fastest
909
- fnew = f.replace(path, path_new) # .replace(Path(f).suffix, '.jpg')
910
- cv2.imwrite(fnew, img)
911
- except:
912
- print('WARNING: image failure %s' % f)
913
-
914
-
915
- def recursive_dataset2bmp(dataset='path/dataset_bmp'): # from utils.datasets import *; recursive_dataset2bmp()
916
- # Converts dataset to bmp (for faster training)
917
- formats = [x.lower() for x in img_formats] + [x.upper() for x in img_formats]
918
- for a, b, files in os.walk(dataset):
919
- for file in tqdm(files, desc=a):
920
- p = a + '/' + file
921
- s = Path(file).suffix
922
- if s == '.txt': # replace text
923
- with open(p, 'r') as f:
924
- lines = f.read()
925
- for f in formats:
926
- lines = lines.replace(f, '.bmp')
927
- with open(p, 'w') as f:
928
- f.write(lines)
929
- elif s in formats: # replace image
930
- cv2.imwrite(p.replace(s, '.bmp'), cv2.imread(p))
931
- if s != '.bmp':
932
- os.system("rm '%s'" % p)
933
-
934
-
935
- def imagelist2folder(path='path/images.txt'): # from utils.datasets import *; imagelist2folder()
936
- # Copies all the images in a text file (list of images) into a folder
937
- create_folder(path[:-4])
938
- with open(path, 'r') as f:
939
- for line in f.read().splitlines():
940
- os.system('cp "%s" %s' % (line, path[:-4]))
941
- print(line)
942
-
943
-
944
  def create_folder(path='./new'):
945
  # Create folder
946
  if os.path.exists(path):
 
1
+ # Dataset utils and dataloaders
2
+
3
  import glob
4
  import math
5
  import os
 
18
  from torch.utils.data import Dataset
19
  from tqdm import tqdm
20
 
21
+ from utils.general import xyxy2xywh, xywh2xyxy
22
+ from utils.torch_utils import torch_distributed_zero_first
23
 
24
+ # Parameters
25
  help_url = 'https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data'
26
  img_formats = ['.bmp', '.jpg', '.jpeg', '.png', '.tif', '.tiff', '.dng']
27
  vid_formats = ['.mov', '.avi', '.mp4', '.mpg', '.mpeg', '.m4v', '.wmv', '.mkv']
 
54
 
55
  def create_dataloader(path, imgsz, batch_size, stride, opt, hyp=None, augment=False, cache=False, pad=0.0, rect=False,
56
  rank=-1, world_size=1, workers=8):
57
+ # Make sure only the first process in DDP process the dataset first, and the following others can use the cache
58
  with torch_distributed_zero_first(rank):
59
  dataset = LoadImagesAndLabels(path, imgsz, batch_size,
60
  augment=augment, # augment images
 
79
 
80
 
81
  class InfiniteDataLoader(torch.utils.data.dataloader.DataLoader):
82
+ """ Dataloader that reuses workers
83
 
84
+ Uses same syntax as vanilla DataLoader
85
  """
86
 
87
  def __init__(self, *args, **kwargs):
 
98
 
99
 
100
  class _RepeatSampler(object):
101
+ """ Sampler that repeats forever
102
 
103
  Args:
104
  sampler (Sampler)
 
181
  img = img[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, to 3x416x416
182
  img = np.ascontiguousarray(img)
183
 
 
184
  return path, img, img0, self.cap
185
 
186
  def new_video(self, path):
 
193
 
194
 
195
  class LoadWebcam: # for inference
196
+ def __init__(self, pipe='0', img_size=640):
197
  self.img_size = img_size
198
 
199
+ if pipe.isnumeric():
200
+ pipe = eval(pipe) # local camera
201
  # pipe = 'rtsp://192.168.1.64/1' # IP camera
202
  # pipe = 'rtsp://username:[email protected]/1' # IP camera with login
 
203
  # pipe = 'http://wmccpinetop.axiscam.net/mjpg/video.mjpg' # IP golf camera
204
 
 
 
 
 
 
 
 
205
  self.pipe = pipe
206
  self.cap = cv2.VideoCapture(pipe) # video capture object
207
  self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 3) # set buffer size
 
890
  return labels
891
 
892
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
893
  def create_folder(path='./new'):
894
  # Create folder
895
  if os.path.exists(path):
utils/evolve.sh DELETED
@@ -1,15 +0,0 @@
1
- #!/bin/bash
2
- # Hyperparameter evolution commands (avoids CUDA memory leakage issues)
3
- # Replaces train.py python generations 'for' loop with a bash 'for' loop
4
-
5
- # Start on 4-GPU machine
6
- #for i in 0 1 2 3; do
7
- # t=ultralytics/yolov5:evolve && sudo docker pull $t && sudo docker run -d --ipc=host --gpus all -v "$(pwd)"/VOC:/usr/src/VOC $t bash utils/evolve.sh $i
8
- # sleep 60 # avoid simultaneous evolve.txt read/write
9
- #done
10
-
11
- # Hyperparameter evolution commands
12
- while true; do
13
- # python train.py --batch 64 --weights yolov5m.pt --data voc.yaml --img 512 --epochs 50 --evolve --bucket ult/evolve/voc --device $1
14
- python train.py --batch 40 --weights yolov5m.pt --data coco.yaml --img 640 --epochs 30 --evolve --bucket ult/evolve/coco --device $1
15
- done
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/general.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import glob
2
  import logging
3
  import math
@@ -5,27 +7,19 @@ import os
5
  import platform
6
  import random
7
  import re
8
- import shutil
9
  import subprocess
10
  import time
11
- from contextlib import contextmanager
12
- from copy import copy
13
  from pathlib import Path
14
 
15
  import cv2
16
  import matplotlib
17
- import matplotlib.pyplot as plt
18
  import numpy as np
19
  import torch
20
- import torch.nn as nn
21
  import yaml
22
- from PIL import Image
23
- from scipy.cluster.vq import kmeans
24
- from scipy.signal import butter, filtfilt
25
- from tqdm import tqdm
26
 
27
  from utils.google_utils import gsutil_getsize
28
- from utils.torch_utils import is_parallel, init_torch_seeds
 
29
 
30
  # Set printoptions
31
  torch.set_printoptions(linewidth=320, precision=5, profile='long')
@@ -36,18 +30,6 @@ matplotlib.rc('font', **{'size': 11})
36
  cv2.setNumThreads(0)
37
 
38
 
39
- @contextmanager
40
- def torch_distributed_zero_first(local_rank: int):
41
- """
42
- Decorator to make all processes in distributed training wait for each local_master to do something.
43
- """
44
- if local_rank not in [-1, 0]:
45
- torch.distributed.barrier()
46
- yield
47
- if local_rank == 0:
48
- torch.distributed.barrier()
49
-
50
-
51
  def set_logging(rank=-1):
52
  logging.basicConfig(
53
  format="%(message)s",
@@ -82,51 +64,6 @@ def check_img_size(img_size, s=32):
82
  return new_size
83
 
84
 
85
- def check_anchors(dataset, model, thr=4.0, imgsz=640):
86
- # Check anchor fit to data, recompute if necessary
87
- print('\nAnalyzing anchors... ', end='')
88
- m = model.module.model[-1] if hasattr(model, 'module') else model.model[-1] # Detect()
89
- shapes = imgsz * dataset.shapes / dataset.shapes.max(1, keepdims=True)
90
- scale = np.random.uniform(0.9, 1.1, size=(shapes.shape[0], 1)) # augment scale
91
- wh = torch.tensor(np.concatenate([l[:, 3:5] * s for s, l in zip(shapes * scale, dataset.labels)])).float() # wh
92
-
93
- def metric(k): # compute metric
94
- r = wh[:, None] / k[None]
95
- x = torch.min(r, 1. / r).min(2)[0] # ratio metric
96
- best = x.max(1)[0] # best_x
97
- aat = (x > 1. / thr).float().sum(1).mean() # anchors above threshold
98
- bpr = (best > 1. / thr).float().mean() # best possible recall
99
- return bpr, aat
100
-
101
- bpr, aat = metric(m.anchor_grid.clone().cpu().view(-1, 2))
102
- print('anchors/target = %.2f, Best Possible Recall (BPR) = %.4f' % (aat, bpr), end='')
103
- if bpr < 0.98: # threshold to recompute
104
- print('. Attempting to generate improved anchors, please wait...' % bpr)
105
- na = m.anchor_grid.numel() // 2 # number of anchors
106
- new_anchors = kmean_anchors(dataset, n=na, img_size=imgsz, thr=thr, gen=1000, verbose=False)
107
- new_bpr = metric(new_anchors.reshape(-1, 2))[0]
108
- if new_bpr > bpr: # replace anchors
109
- new_anchors = torch.tensor(new_anchors, device=m.anchors.device).type_as(m.anchors)
110
- m.anchor_grid[:] = new_anchors.clone().view_as(m.anchor_grid) # for inference
111
- m.anchors[:] = new_anchors.clone().view_as(m.anchors) / m.stride.to(m.anchors.device).view(-1, 1, 1) # loss
112
- check_anchor_order(m)
113
- print('New anchors saved to model. Update model *.yaml to use these anchors in the future.')
114
- else:
115
- print('Original anchors better than new anchors. Proceeding with original anchors.')
116
- print('') # newline
117
-
118
-
119
- def check_anchor_order(m):
120
- # Check anchor order against stride order for YOLOv5 Detect() module m, and correct if necessary
121
- a = m.anchor_grid.prod(-1).view(-1) # anchor area
122
- da = a[-1] - a[0] # delta a
123
- ds = m.stride[-1] - m.stride[0] # delta s
124
- if da.sign() != ds.sign(): # same order
125
- print('Reversing anchor order')
126
- m.anchors[:] = m.anchors.flip(0)
127
- m.anchor_grid[:] = m.anchor_grid.flip(0)
128
-
129
-
130
  def check_file(file):
131
  # Search for file if not found
132
  if os.path.isfile(file) or file == '':
@@ -139,7 +76,7 @@ def check_file(file):
139
 
140
 
141
  def check_dataset(dict):
142
- # Download dataset if not found
143
  val, s = dict.get('val'), dict.get('download')
144
  if val and len(val):
145
  val = [os.path.abspath(x) for x in (val if isinstance(val, list) else [val])] # val path
@@ -247,106 +184,6 @@ def clip_coords(boxes, img_shape):
247
  boxes[:, 3].clamp_(0, img_shape[0]) # y2
248
 
249
 
250
- def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, fname='precision-recall_curve.png'):
251
- """ Compute the average precision, given the recall and precision curves.
252
- Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
253
- # Arguments
254
- tp: True positives (nparray, nx1 or nx10).
255
- conf: Objectness value from 0-1 (nparray).
256
- pred_cls: Predicted object classes (nparray).
257
- target_cls: True object classes (nparray).
258
- plot: Plot precision-recall curve at [email protected]
259
- fname: Plot filename
260
- # Returns
261
- The average precision as computed in py-faster-rcnn.
262
- """
263
-
264
- # Sort by objectness
265
- i = np.argsort(-conf)
266
- tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]
267
-
268
- # Find unique classes
269
- unique_classes = np.unique(target_cls)
270
-
271
- # Create Precision-Recall curve and compute AP for each class
272
- px, py = np.linspace(0, 1, 1000), [] # for plotting
273
- pr_score = 0.1 # score to evaluate P and R https://github.com/ultralytics/yolov3/issues/898
274
- s = [unique_classes.shape[0], tp.shape[1]] # number class, number iou thresholds (i.e. 10 for mAP0.5...0.95)
275
- ap, p, r = np.zeros(s), np.zeros(s), np.zeros(s)
276
- for ci, c in enumerate(unique_classes):
277
- i = pred_cls == c
278
- n_l = (target_cls == c).sum() # number of labels
279
- n_p = i.sum() # number of predictions
280
-
281
- if n_p == 0 or n_l == 0:
282
- continue
283
- else:
284
- # Accumulate FPs and TPs
285
- fpc = (1 - tp[i]).cumsum(0)
286
- tpc = tp[i].cumsum(0)
287
-
288
- # Recall
289
- recall = tpc / (n_l + 1e-16) # recall curve
290
- r[ci] = np.interp(-pr_score, -conf[i], recall[:, 0]) # r at pr_score, negative x, xp because xp decreases
291
-
292
- # Precision
293
- precision = tpc / (tpc + fpc) # precision curve
294
- p[ci] = np.interp(-pr_score, -conf[i], precision[:, 0]) # p at pr_score
295
-
296
- # AP from recall-precision curve
297
- for j in range(tp.shape[1]):
298
- ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j])
299
- if j == 0:
300
- py.append(np.interp(px, mrec, mpre)) # precision at [email protected]
301
-
302
- # Compute F1 score (harmonic mean of precision and recall)
303
- f1 = 2 * p * r / (p + r + 1e-16)
304
-
305
- if plot:
306
- py = np.stack(py, axis=1)
307
- fig, ax = plt.subplots(1, 1, figsize=(5, 5))
308
- ax.plot(px, py, linewidth=0.5, color='grey') # plot(recall, precision)
309
- ax.plot(px, py.mean(1), linewidth=2, color='blue', label='all classes %.3f [email protected]' % ap[:, 0].mean())
310
- ax.set_xlabel('Recall')
311
- ax.set_ylabel('Precision')
312
- ax.set_xlim(0, 1)
313
- ax.set_ylim(0, 1)
314
- plt.legend()
315
- fig.tight_layout()
316
- fig.savefig(fname, dpi=200)
317
-
318
- return p, r, ap, f1, unique_classes.astype('int32')
319
-
320
-
321
- def compute_ap(recall, precision):
322
- """ Compute the average precision, given the recall and precision curves.
323
- Source: https://github.com/rbgirshick/py-faster-rcnn.
324
- # Arguments
325
- recall: The recall curve (list).
326
- precision: The precision curve (list).
327
- # Returns
328
- The average precision as computed in py-faster-rcnn.
329
- """
330
-
331
- # Append sentinel values to beginning and end
332
- mrec = recall # np.concatenate(([0.], recall, [recall[-1] + 1E-3]))
333
- mpre = precision # np.concatenate(([0.], precision, [0.]))
334
-
335
- # Compute the precision envelope
336
- mpre = np.flip(np.maximum.accumulate(np.flip(mpre)))
337
-
338
- # Integrate area under curve
339
- method = 'interp' # methods: 'continuous', 'interp'
340
- if method == 'interp':
341
- x = np.linspace(0, 1, 101) # 101-point interp (COCO)
342
- ap = np.trapz(np.interp(x, mrec, mpre), x) # integrate
343
- else: # 'continuous'
344
- i = np.where(mrec[1:] != mrec[:-1])[0] # points where x axis (recall) changes
345
- ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) # area under curve
346
-
347
- return ap, mpre, mrec
348
-
349
-
350
  def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-9):
351
  # Returns the IoU of box1 to box2. box1 is 4, box2 is nx4
352
  box2 = box2.T
@@ -425,178 +262,6 @@ def wh_iou(wh1, wh2):
425
  return inter / (wh1.prod(2) + wh2.prod(2) - inter) # iou = inter / (area1 + area2 - inter)
426
 
427
 
428
- class FocalLoss(nn.Module):
429
- # Wraps focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5)
430
- def __init__(self, loss_fcn, gamma=1.5, alpha=0.25):
431
- super(FocalLoss, self).__init__()
432
- self.loss_fcn = loss_fcn # must be nn.BCEWithLogitsLoss()
433
- self.gamma = gamma
434
- self.alpha = alpha
435
- self.reduction = loss_fcn.reduction
436
- self.loss_fcn.reduction = 'none' # required to apply FL to each element
437
-
438
- def forward(self, pred, true):
439
- loss = self.loss_fcn(pred, true)
440
- # p_t = torch.exp(-loss)
441
- # loss *= self.alpha * (1.000001 - p_t) ** self.gamma # non-zero power for gradient stability
442
-
443
- # TF implementation https://github.com/tensorflow/addons/blob/v0.7.1/tensorflow_addons/losses/focal_loss.py
444
- pred_prob = torch.sigmoid(pred) # prob from logits
445
- p_t = true * pred_prob + (1 - true) * (1 - pred_prob)
446
- alpha_factor = true * self.alpha + (1 - true) * (1 - self.alpha)
447
- modulating_factor = (1.0 - p_t) ** self.gamma
448
- loss *= alpha_factor * modulating_factor
449
-
450
- if self.reduction == 'mean':
451
- return loss.mean()
452
- elif self.reduction == 'sum':
453
- return loss.sum()
454
- else: # 'none'
455
- return loss
456
-
457
-
458
- def smooth_BCE(eps=0.1): # https://github.com/ultralytics/yolov3/issues/238#issuecomment-598028441
459
- # return positive, negative label smoothing BCE targets
460
- return 1.0 - 0.5 * eps, 0.5 * eps
461
-
462
-
463
- class BCEBlurWithLogitsLoss(nn.Module):
464
- # BCEwithLogitLoss() with reduced missing label effects.
465
- def __init__(self, alpha=0.05):
466
- super(BCEBlurWithLogitsLoss, self).__init__()
467
- self.loss_fcn = nn.BCEWithLogitsLoss(reduction='none') # must be nn.BCEWithLogitsLoss()
468
- self.alpha = alpha
469
-
470
- def forward(self, pred, true):
471
- loss = self.loss_fcn(pred, true)
472
- pred = torch.sigmoid(pred) # prob from logits
473
- dx = pred - true # reduce only missing label effects
474
- # dx = (pred - true).abs() # reduce missing label and false label effects
475
- alpha_factor = 1 - torch.exp((dx - 1) / (self.alpha + 1e-4))
476
- loss *= alpha_factor
477
- return loss.mean()
478
-
479
-
480
- def compute_loss(p, targets, model): # predictions, targets, model
481
- device = targets.device
482
- lcls, lbox, lobj = torch.zeros(1, device=device), torch.zeros(1, device=device), torch.zeros(1, device=device)
483
- tcls, tbox, indices, anchors = build_targets(p, targets, model) # targets
484
- h = model.hyp # hyperparameters
485
-
486
- # Define criteria
487
- BCEcls = nn.BCEWithLogitsLoss(pos_weight=torch.Tensor([h['cls_pw']])).to(device)
488
- BCEobj = nn.BCEWithLogitsLoss(pos_weight=torch.Tensor([h['obj_pw']])).to(device)
489
-
490
- # Class label smoothing https://arxiv.org/pdf/1902.04103.pdf eqn 3
491
- cp, cn = smooth_BCE(eps=0.0)
492
-
493
- # Focal loss
494
- g = h['fl_gamma'] # focal loss gamma
495
- if g > 0:
496
- BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g)
497
-
498
- # Losses
499
- nt = 0 # number of targets
500
- np = len(p) # number of outputs
501
- balance = [4.0, 1.0, 0.4] if np == 3 else [4.0, 1.0, 0.4, 0.1] # P3-5 or P3-6
502
- for i, pi in enumerate(p): # layer index, layer predictions
503
- b, a, gj, gi = indices[i] # image, anchor, gridy, gridx
504
- tobj = torch.zeros_like(pi[..., 0], device=device) # target obj
505
-
506
- n = b.shape[0] # number of targets
507
- if n:
508
- nt += n # cumulative targets
509
- ps = pi[b, a, gj, gi] # prediction subset corresponding to targets
510
-
511
- # Regression
512
- pxy = ps[:, :2].sigmoid() * 2. - 0.5
513
- pwh = (ps[:, 2:4].sigmoid() * 2) ** 2 * anchors[i]
514
- pbox = torch.cat((pxy, pwh), 1).to(device) # predicted box
515
- iou = bbox_iou(pbox.T, tbox[i], x1y1x2y2=False, CIoU=True) # iou(prediction, target)
516
- lbox += (1.0 - iou).mean() # iou loss
517
-
518
- # Objectness
519
- tobj[b, a, gj, gi] = (1.0 - model.gr) + model.gr * iou.detach().clamp(0).type(tobj.dtype) # iou ratio
520
-
521
- # Classification
522
- if model.nc > 1: # cls loss (only if multiple classes)
523
- t = torch.full_like(ps[:, 5:], cn, device=device) # targets
524
- t[range(n), tcls[i]] = cp
525
- lcls += BCEcls(ps[:, 5:], t) # BCE
526
-
527
- # Append targets to text file
528
- # with open('targets.txt', 'a') as file:
529
- # [file.write('%11.5g ' * 4 % tuple(x) + '\n') for x in torch.cat((txy[i], twh[i]), 1)]
530
-
531
- lobj += BCEobj(pi[..., 4], tobj) * balance[i] # obj loss
532
-
533
- s = 3 / np # output count scaling
534
- lbox *= h['box'] * s
535
- lobj *= h['obj'] * s * (1.4 if np == 4 else 1.)
536
- lcls *= h['cls'] * s
537
- bs = tobj.shape[0] # batch size
538
-
539
- loss = lbox + lobj + lcls
540
- return loss * bs, torch.cat((lbox, lobj, lcls, loss)).detach()
541
-
542
-
543
- def build_targets(p, targets, model):
544
- # Build targets for compute_loss(), input targets(image,class,x,y,w,h)
545
- det = model.module.model[-1] if is_parallel(model) else model.model[-1] # Detect() module
546
- na, nt = det.na, targets.shape[0] # number of anchors, targets
547
- tcls, tbox, indices, anch = [], [], [], []
548
- gain = torch.ones(7, device=targets.device) # normalized to gridspace gain
549
- ai = torch.arange(na, device=targets.device).float().view(na, 1).repeat(1, nt) # same as .repeat_interleave(nt)
550
- targets = torch.cat((targets.repeat(na, 1, 1), ai[:, :, None]), 2) # append anchor indices
551
-
552
- g = 0.5 # bias
553
- off = torch.tensor([[0, 0],
554
- [1, 0], [0, 1], [-1, 0], [0, -1], # j,k,l,m
555
- # [1, 1], [1, -1], [-1, 1], [-1, -1], # jk,jm,lk,lm
556
- ], device=targets.device).float() * g # offsets
557
-
558
- for i in range(det.nl):
559
- anchors = det.anchors[i]
560
- gain[2:6] = torch.tensor(p[i].shape)[[3, 2, 3, 2]] # xyxy gain
561
-
562
- # Match targets to anchors
563
- t = targets * gain
564
- if nt:
565
- # Matches
566
- r = t[:, :, 4:6] / anchors[:, None] # wh ratio
567
- j = torch.max(r, 1. / r).max(2)[0] < model.hyp['anchor_t'] # compare
568
- # j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t'] # iou(3,n)=wh_iou(anchors(3,2), gwh(n,2))
569
- t = t[j] # filter
570
-
571
- # Offsets
572
- gxy = t[:, 2:4] # grid xy
573
- gxi = gain[[2, 3]] - gxy # inverse
574
- j, k = ((gxy % 1. < g) & (gxy > 1.)).T
575
- l, m = ((gxi % 1. < g) & (gxi > 1.)).T
576
- j = torch.stack((torch.ones_like(j), j, k, l, m))
577
- t = t.repeat((5, 1, 1))[j]
578
- offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j]
579
- else:
580
- t = targets[0]
581
- offsets = 0
582
-
583
- # Define
584
- b, c = t[:, :2].long().T # image, class
585
- gxy = t[:, 2:4] # grid xy
586
- gwh = t[:, 4:6] # grid wh
587
- gij = (gxy - offsets).long()
588
- gi, gj = gij.T # grid xy indices
589
-
590
- # Append
591
- a = t[:, 6].long() # anchor indices
592
- indices.append((b, a, gj.clamp_(0, gain[3] - 1), gi.clamp_(0, gain[2] - 1))) # image, anchor, grid indices
593
- tbox.append(torch.cat((gxy - gij, gwh), 1)) # box
594
- anch.append(anchors[a]) # anchors
595
- tcls.append(c) # class
596
-
597
- return tcls, tbox, indices, anch
598
-
599
-
600
  def non_max_suppression(prediction, conf_thres=0.1, iou_thres=0.6, merge=False, classes=None, agnostic=False):
601
  """Performs Non-Maximum Suppression (NMS) on inference results
602
 
@@ -662,15 +327,12 @@ def non_max_suppression(prediction, conf_thres=0.1, iou_thres=0.6, merge=False,
662
  if i.shape[0] > max_det: # limit detections
663
  i = i[:max_det]
664
  if merge and (1 < n < 3E3): # Merge NMS (boxes merged using weighted mean)
665
- try: # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)
666
- iou = box_iou(boxes[i], boxes) > iou_thres # iou matrix
667
- weights = iou * scores[None] # box weights
668
- x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True) # merged boxes
669
- if redundant:
670
- i = i[iou.sum(1) > 1] # require redundancy
671
- except: # possible CUDA error https://github.com/ultralytics/yolov3/issues/1139
672
- print(x, i, x.shape, i.shape)
673
- pass
674
 
675
  output[xi] = x[i]
676
  if (time.time() - t) > time_limit:
@@ -693,170 +355,6 @@ def strip_optimizer(f='weights/best.pt', s=''): # from utils.general import *;
693
  print('Optimizer stripped from %s,%s %.1fMB' % (f, (' saved as %s,' % s) if s else '', mb))
694
 
695
 
696
- def coco_class_count(path='../coco/labels/train2014/'):
697
- # Histogram of occurrences per class
698
- nc = 80 # number classes
699
- x = np.zeros(nc, dtype='int32')
700
- files = sorted(glob.glob('%s/*.*' % path))
701
- for i, file in enumerate(files):
702
- labels = np.loadtxt(file, dtype=np.float32).reshape(-1, 5)
703
- x += np.bincount(labels[:, 0].astype('int32'), minlength=nc)
704
- print(i, len(files))
705
-
706
-
707
- def coco_only_people(path='../coco/labels/train2017/'): # from utils.general import *; coco_only_people()
708
- # Find images with only people
709
- files = sorted(glob.glob('%s/*.*' % path))
710
- for i, file in enumerate(files):
711
- labels = np.loadtxt(file, dtype=np.float32).reshape(-1, 5)
712
- if all(labels[:, 0] == 0):
713
- print(labels.shape[0], file)
714
-
715
-
716
- def crop_images_random(path='../images/', scale=0.50): # from utils.general import *; crop_images_random()
717
- # crops images into random squares up to scale fraction
718
- # WARNING: overwrites images!
719
- for file in tqdm(sorted(glob.glob('%s/*.*' % path))):
720
- img = cv2.imread(file) # BGR
721
- if img is not None:
722
- h, w = img.shape[:2]
723
-
724
- # create random mask
725
- a = 30 # minimum size (pixels)
726
- mask_h = random.randint(a, int(max(a, h * scale))) # mask height
727
- mask_w = mask_h # mask width
728
-
729
- # box
730
- xmin = max(0, random.randint(0, w) - mask_w // 2)
731
- ymin = max(0, random.randint(0, h) - mask_h // 2)
732
- xmax = min(w, xmin + mask_w)
733
- ymax = min(h, ymin + mask_h)
734
-
735
- # apply random color mask
736
- cv2.imwrite(file, img[ymin:ymax, xmin:xmax])
737
-
738
-
739
- def coco_single_class_labels(path='../coco/labels/train2014/', label_class=43):
740
- # Makes single-class coco datasets. from utils.general import *; coco_single_class_labels()
741
- if os.path.exists('new/'):
742
- shutil.rmtree('new/') # delete output folder
743
- os.makedirs('new/') # make new output folder
744
- os.makedirs('new/labels/')
745
- os.makedirs('new/images/')
746
- for file in tqdm(sorted(glob.glob('%s/*.*' % path))):
747
- with open(file, 'r') as f:
748
- labels = np.array([x.split() for x in f.read().splitlines()], dtype=np.float32)
749
- i = labels[:, 0] == label_class
750
- if any(i):
751
- img_file = file.replace('labels', 'images').replace('txt', 'jpg')
752
- labels[:, 0] = 0 # reset class to 0
753
- with open('new/images.txt', 'a') as f: # add image to dataset list
754
- f.write(img_file + '\n')
755
- with open('new/labels/' + Path(file).name, 'a') as f: # write label
756
- for l in labels[i]:
757
- f.write('%g %.6f %.6f %.6f %.6f\n' % tuple(l))
758
- shutil.copyfile(src=img_file, dst='new/images/' + Path(file).name.replace('txt', 'jpg')) # copy images
759
-
760
-
761
- def kmean_anchors(path='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen=1000, verbose=True):
762
- """ Creates kmeans-evolved anchors from training dataset
763
-
764
- Arguments:
765
- path: path to dataset *.yaml, or a loaded dataset
766
- n: number of anchors
767
- img_size: image size used for training
768
- thr: anchor-label wh ratio threshold hyperparameter hyp['anchor_t'] used for training, default=4.0
769
- gen: generations to evolve anchors using genetic algorithm
770
-
771
- Return:
772
- k: kmeans evolved anchors
773
-
774
- Usage:
775
- from utils.general import *; _ = kmean_anchors()
776
- """
777
- thr = 1. / thr
778
-
779
- def metric(k, wh): # compute metrics
780
- r = wh[:, None] / k[None]
781
- x = torch.min(r, 1. / r).min(2)[0] # ratio metric
782
- # x = wh_iou(wh, torch.tensor(k)) # iou metric
783
- return x, x.max(1)[0] # x, best_x
784
-
785
- def fitness(k): # mutation fitness
786
- _, best = metric(torch.tensor(k, dtype=torch.float32), wh)
787
- return (best * (best > thr).float()).mean() # fitness
788
-
789
- def print_results(k):
790
- k = k[np.argsort(k.prod(1))] # sort small to large
791
- x, best = metric(k, wh0)
792
- bpr, aat = (best > thr).float().mean(), (x > thr).float().mean() * n # best possible recall, anch > thr
793
- print('thr=%.2f: %.4f best possible recall, %.2f anchors past thr' % (thr, bpr, aat))
794
- print('n=%g, img_size=%s, metric_all=%.3f/%.3f-mean/best, past_thr=%.3f-mean: ' %
795
- (n, img_size, x.mean(), best.mean(), x[x > thr].mean()), end='')
796
- for i, x in enumerate(k):
797
- print('%i,%i' % (round(x[0]), round(x[1])), end=', ' if i < len(k) - 1 else '\n') # use in *.cfg
798
- return k
799
-
800
- if isinstance(path, str): # *.yaml file
801
- with open(path) as f:
802
- data_dict = yaml.load(f, Loader=yaml.FullLoader) # model dict
803
- from utils.datasets import LoadImagesAndLabels
804
- dataset = LoadImagesAndLabels(data_dict['train'], augment=True, rect=True)
805
- else:
806
- dataset = path # dataset
807
-
808
- # Get label wh
809
- shapes = img_size * dataset.shapes / dataset.shapes.max(1, keepdims=True)
810
- wh0 = np.concatenate([l[:, 3:5] * s for s, l in zip(shapes, dataset.labels)]) # wh
811
-
812
- # Filter
813
- i = (wh0 < 3.0).any(1).sum()
814
- if i:
815
- print('WARNING: Extremely small objects found. '
816
- '%g of %g labels are < 3 pixels in width or height.' % (i, len(wh0)))
817
- wh = wh0[(wh0 >= 2.0).any(1)] # filter > 2 pixels
818
-
819
- # Kmeans calculation
820
- print('Running kmeans for %g anchors on %g points...' % (n, len(wh)))
821
- s = wh.std(0) # sigmas for whitening
822
- k, dist = kmeans(wh / s, n, iter=30) # points, mean distance
823
- k *= s
824
- wh = torch.tensor(wh, dtype=torch.float32) # filtered
825
- wh0 = torch.tensor(wh0, dtype=torch.float32) # unfiltered
826
- k = print_results(k)
827
-
828
- # Plot
829
- # k, d = [None] * 20, [None] * 20
830
- # for i in tqdm(range(1, 21)):
831
- # k[i-1], d[i-1] = kmeans(wh / s, i) # points, mean distance
832
- # fig, ax = plt.subplots(1, 2, figsize=(14, 7))
833
- # ax = ax.ravel()
834
- # ax[0].plot(np.arange(1, 21), np.array(d) ** 2, marker='.')
835
- # fig, ax = plt.subplots(1, 2, figsize=(14, 7)) # plot wh
836
- # ax[0].hist(wh[wh[:, 0]<100, 0],400)
837
- # ax[1].hist(wh[wh[:, 1]<100, 1],400)
838
- # fig.tight_layout()
839
- # fig.savefig('wh.png', dpi=200)
840
-
841
- # Evolve
842
- npr = np.random
843
- f, sh, mp, s = fitness(k), k.shape, 0.9, 0.1 # fitness, generations, mutation prob, sigma
844
- pbar = tqdm(range(gen), desc='Evolving anchors with Genetic Algorithm') # progress bar
845
- for _ in pbar:
846
- v = np.ones(sh)
847
- while (v == 1).all(): # mutate until a change occurs (prevent duplicates)
848
- v = ((npr.random(sh) < mp) * npr.random() * npr.randn(*sh) * s + 1).clip(0.3, 3.0)
849
- kg = (k.copy() * v).clip(min=2.0)
850
- fg = fitness(kg)
851
- if fg > f:
852
- f, k = fg, kg.copy()
853
- pbar.desc = 'Evolving anchors with Genetic Algorithm: fitness = %.4f' % f
854
- if verbose:
855
- print_results(k)
856
-
857
- return print_results(k)
858
-
859
-
860
  def print_mutation(hyp, results, yaml_file='hyp_evolved.yaml', bucket=''):
861
  # Print mutation results to evolve.txt (for use with train.py --evolve)
862
  a = '%10s' * len(hyp) % tuple(hyp.keys()) # hyperparam keys
@@ -923,34 +421,6 @@ def apply_classifier(x, model, img, im0):
923
  return x
924
 
925
 
926
- def fitness(x):
927
- # Returns fitness (for use with results.txt or evolve.txt)
928
- w = [0.0, 0.0, 0.1, 0.9] # weights for [P, R, [email protected], [email protected]:0.95]
929
- return (x[:, :4] * w).sum(1)
930
-
931
-
932
- def output_to_target(output, width, height):
933
- # Convert model output to target format [batch_id, class_id, x, y, w, h, conf]
934
- if isinstance(output, torch.Tensor):
935
- output = output.cpu().numpy()
936
-
937
- targets = []
938
- for i, o in enumerate(output):
939
- if o is not None:
940
- for pred in o:
941
- box = pred[:4]
942
- w = (box[2] - box[0]) / width
943
- h = (box[3] - box[1]) / height
944
- x = box[0] / width + w / 2
945
- y = box[1] / height + h / 2
946
- conf = pred[4]
947
- cls = int(pred[5])
948
-
949
- targets.append([i, cls, x, y, w, h, conf])
950
-
951
- return np.array(targets)
952
-
953
-
954
  def increment_path(path, exist_ok=True, sep=''):
955
  # Increment path, i.e. runs/exp --> runs/exp{sep}0, runs/exp{sep}1 etc.
956
  path = Path(path) # os-agnostic
@@ -962,339 +432,3 @@ def increment_path(path, exist_ok=True, sep=''):
962
  i = [int(m.groups()[0]) for m in matches if m] # indices
963
  n = max(i) + 1 if i else 2 # increment number
964
  return f"{path}{sep}{n}" # update path
965
-
966
-
967
- # Plotting functions ---------------------------------------------------------------------------------------------------
968
- def hist2d(x, y, n=100):
969
- # 2d histogram used in labels.png and evolve.png
970
- xedges, yedges = np.linspace(x.min(), x.max(), n), np.linspace(y.min(), y.max(), n)
971
- hist, xedges, yedges = np.histogram2d(x, y, (xedges, yedges))
972
- xidx = np.clip(np.digitize(x, xedges) - 1, 0, hist.shape[0] - 1)
973
- yidx = np.clip(np.digitize(y, yedges) - 1, 0, hist.shape[1] - 1)
974
- return np.log(hist[xidx, yidx])
975
-
976
-
977
- def butter_lowpass_filtfilt(data, cutoff=1500, fs=50000, order=5):
978
- # https://stackoverflow.com/questions/28536191/how-to-filter-smooth-with-scipy-numpy
979
- def butter_lowpass(cutoff, fs, order):
980
- nyq = 0.5 * fs
981
- normal_cutoff = cutoff / nyq
982
- b, a = butter(order, normal_cutoff, btype='low', analog=False)
983
- return b, a
984
-
985
- b, a = butter_lowpass(cutoff, fs, order=order)
986
- return filtfilt(b, a, data) # forward-backward filter
987
-
988
-
989
- def plot_one_box(x, img, color=None, label=None, line_thickness=None):
990
- # Plots one bounding box on image img
991
- tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1 # line/font thickness
992
- color = color or [random.randint(0, 255) for _ in range(3)]
993
- c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
994
- cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
995
- if label:
996
- tf = max(tl - 1, 1) # font thickness
997
- t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
998
- c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
999
- cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA) # filled
1000
- cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
1001
-
1002
-
1003
- def plot_wh_methods(): # from utils.general import *; plot_wh_methods()
1004
- # Compares the two methods for width-height anchor multiplication
1005
- # https://github.com/ultralytics/yolov3/issues/168
1006
- x = np.arange(-4.0, 4.0, .1)
1007
- ya = np.exp(x)
1008
- yb = torch.sigmoid(torch.from_numpy(x)).numpy() * 2
1009
-
1010
- fig = plt.figure(figsize=(6, 3), dpi=150)
1011
- plt.plot(x, ya, '.-', label='YOLOv3')
1012
- plt.plot(x, yb ** 2, '.-', label='YOLOv5 ^2')
1013
- plt.plot(x, yb ** 1.6, '.-', label='YOLOv5 ^1.6')
1014
- plt.xlim(left=-4, right=4)
1015
- plt.ylim(bottom=0, top=6)
1016
- plt.xlabel('input')
1017
- plt.ylabel('output')
1018
- plt.grid()
1019
- plt.legend()
1020
- fig.tight_layout()
1021
- fig.savefig('comparison.png', dpi=200)
1022
-
1023
-
1024
- def plot_images(images, targets, paths=None, fname='images.jpg', names=None, max_size=640, max_subplots=16):
1025
- tl = 3 # line thickness
1026
- tf = max(tl - 1, 1) # font thickness
1027
-
1028
- if isinstance(images, torch.Tensor):
1029
- images = images.cpu().float().numpy()
1030
-
1031
- if isinstance(targets, torch.Tensor):
1032
- targets = targets.cpu().numpy()
1033
-
1034
- # un-normalise
1035
- if np.max(images[0]) <= 1:
1036
- images *= 255
1037
-
1038
- bs, _, h, w = images.shape # batch size, _, height, width
1039
- bs = min(bs, max_subplots) # limit plot images
1040
- ns = np.ceil(bs ** 0.5) # number of subplots (square)
1041
-
1042
- # Check if we should resize
1043
- scale_factor = max_size / max(h, w)
1044
- if scale_factor < 1:
1045
- h = math.ceil(scale_factor * h)
1046
- w = math.ceil(scale_factor * w)
1047
-
1048
- # Empty array for output
1049
- mosaic = np.full((int(ns * h), int(ns * w), 3), 255, dtype=np.uint8)
1050
-
1051
- # Fix class - colour map
1052
- prop_cycle = plt.rcParams['axes.prop_cycle']
1053
- # https://stackoverflow.com/questions/51350872/python-from-color-name-to-rgb
1054
- hex2rgb = lambda h: tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4))
1055
- color_lut = [hex2rgb(h) for h in prop_cycle.by_key()['color']]
1056
-
1057
- for i, img in enumerate(images):
1058
- if i == max_subplots: # if last batch has fewer images than we expect
1059
- break
1060
-
1061
- block_x = int(w * (i // ns))
1062
- block_y = int(h * (i % ns))
1063
-
1064
- img = img.transpose(1, 2, 0)
1065
- if scale_factor < 1:
1066
- img = cv2.resize(img, (w, h))
1067
-
1068
- mosaic[block_y:block_y + h, block_x:block_x + w, :] = img
1069
- if len(targets) > 0:
1070
- image_targets = targets[targets[:, 0] == i]
1071
- boxes = xywh2xyxy(image_targets[:, 2:6]).T
1072
- classes = image_targets[:, 1].astype('int')
1073
- labels = image_targets.shape[1] == 6 # labels if no conf column
1074
- conf = None if labels else image_targets[:, 6] # check for confidence presence (label vs pred)
1075
-
1076
- boxes[[0, 2]] *= w
1077
- boxes[[0, 2]] += block_x
1078
- boxes[[1, 3]] *= h
1079
- boxes[[1, 3]] += block_y
1080
- for j, box in enumerate(boxes.T):
1081
- cls = int(classes[j])
1082
- color = color_lut[cls % len(color_lut)]
1083
- cls = names[cls] if names else cls
1084
- if labels or conf[j] > 0.3: # 0.3 conf thresh
1085
- label = '%s' % cls if labels else '%s %.1f' % (cls, conf[j])
1086
- plot_one_box(box, mosaic, label=label, color=color, line_thickness=tl)
1087
-
1088
- # Draw image filename labels
1089
- if paths is not None:
1090
- label = os.path.basename(paths[i])[:40] # trim to 40 char
1091
- t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
1092
- cv2.putText(mosaic, label, (block_x + 5, block_y + t_size[1] + 5), 0, tl / 3, [220, 220, 220], thickness=tf,
1093
- lineType=cv2.LINE_AA)
1094
-
1095
- # Image border
1096
- cv2.rectangle(mosaic, (block_x, block_y), (block_x + w, block_y + h), (255, 255, 255), thickness=3)
1097
-
1098
- if fname is not None:
1099
- r = min(1280. / max(h, w) / ns, 1.0) # ratio to limit image size
1100
- mosaic = cv2.resize(mosaic, (int(ns * w * r), int(ns * h * r)), interpolation=cv2.INTER_AREA)
1101
- # cv2.imwrite(fname, cv2.cvtColor(mosaic, cv2.COLOR_BGR2RGB)) # cv2 save
1102
- Image.fromarray(mosaic).save(fname) # PIL save
1103
- return mosaic
1104
-
1105
-
1106
- def plot_lr_scheduler(optimizer, scheduler, epochs=300, save_dir=''):
1107
- # Plot LR simulating training for full epochs
1108
- optimizer, scheduler = copy(optimizer), copy(scheduler) # do not modify originals
1109
- y = []
1110
- for _ in range(epochs):
1111
- scheduler.step()
1112
- y.append(optimizer.param_groups[0]['lr'])
1113
- plt.plot(y, '.-', label='LR')
1114
- plt.xlabel('epoch')
1115
- plt.ylabel('LR')
1116
- plt.grid()
1117
- plt.xlim(0, epochs)
1118
- plt.ylim(0)
1119
- plt.tight_layout()
1120
- plt.savefig(Path(save_dir) / 'LR.png', dpi=200)
1121
-
1122
-
1123
- def plot_test_txt(): # from utils.general import *; plot_test()
1124
- # Plot test.txt histograms
1125
- x = np.loadtxt('test.txt', dtype=np.float32)
1126
- box = xyxy2xywh(x[:, :4])
1127
- cx, cy = box[:, 0], box[:, 1]
1128
-
1129
- fig, ax = plt.subplots(1, 1, figsize=(6, 6), tight_layout=True)
1130
- ax.hist2d(cx, cy, bins=600, cmax=10, cmin=0)
1131
- ax.set_aspect('equal')
1132
- plt.savefig('hist2d.png', dpi=300)
1133
-
1134
- fig, ax = plt.subplots(1, 2, figsize=(12, 6), tight_layout=True)
1135
- ax[0].hist(cx, bins=600)
1136
- ax[1].hist(cy, bins=600)
1137
- plt.savefig('hist1d.png', dpi=200)
1138
-
1139
-
1140
- def plot_targets_txt(): # from utils.general import *; plot_targets_txt()
1141
- # Plot targets.txt histograms
1142
- x = np.loadtxt('targets.txt', dtype=np.float32).T
1143
- s = ['x targets', 'y targets', 'width targets', 'height targets']
1144
- fig, ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True)
1145
- ax = ax.ravel()
1146
- for i in range(4):
1147
- ax[i].hist(x[i], bins=100, label='%.3g +/- %.3g' % (x[i].mean(), x[i].std()))
1148
- ax[i].legend()
1149
- ax[i].set_title(s[i])
1150
- plt.savefig('targets.jpg', dpi=200)
1151
-
1152
-
1153
- def plot_study_txt(f='study.txt', x=None): # from utils.general import *; plot_study_txt()
1154
- # Plot study.txt generated by test.py
1155
- fig, ax = plt.subplots(2, 4, figsize=(10, 6), tight_layout=True)
1156
- ax = ax.ravel()
1157
-
1158
- fig2, ax2 = plt.subplots(1, 1, figsize=(8, 4), tight_layout=True)
1159
- for f in ['study/study_coco_yolov5%s.txt' % x for x in ['s', 'm', 'l', 'x']]:
1160
- y = np.loadtxt(f, dtype=np.float32, usecols=[0, 1, 2, 3, 7, 8, 9], ndmin=2).T
1161
- x = np.arange(y.shape[1]) if x is None else np.array(x)
1162
- s = ['P', 'R', '[email protected]', '[email protected]:.95', 't_inference (ms/img)', 't_NMS (ms/img)', 't_total (ms/img)']
1163
- for i in range(7):
1164
- ax[i].plot(x, y[i], '.-', linewidth=2, markersize=8)
1165
- ax[i].set_title(s[i])
1166
-
1167
- j = y[3].argmax() + 1
1168
- ax2.plot(y[6, :j], y[3, :j] * 1E2, '.-', linewidth=2, markersize=8,
1169
- label=Path(f).stem.replace('study_coco_', '').replace('yolo', 'YOLO'))
1170
-
1171
- ax2.plot(1E3 / np.array([209, 140, 97, 58, 35, 18]), [34.6, 40.5, 43.0, 47.5, 49.7, 51.5],
1172
- 'k.-', linewidth=2, markersize=8, alpha=.25, label='EfficientDet')
1173
-
1174
- ax2.grid()
1175
- ax2.set_xlim(0, 30)
1176
- ax2.set_ylim(28, 50)
1177
- ax2.set_yticks(np.arange(30, 55, 5))
1178
- ax2.set_xlabel('GPU Speed (ms/img)')
1179
- ax2.set_ylabel('COCO AP val')
1180
- ax2.legend(loc='lower right')
1181
- plt.savefig('study_mAP_latency.png', dpi=300)
1182
- plt.savefig(f.replace('.txt', '.png'), dpi=300)
1183
-
1184
-
1185
- def plot_labels(labels, save_dir=''):
1186
- # plot dataset labels
1187
- c, b = labels[:, 0], labels[:, 1:].transpose() # classes, boxes
1188
- nc = int(c.max() + 1) # number of classes
1189
-
1190
- fig, ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True)
1191
- ax = ax.ravel()
1192
- ax[0].hist(c, bins=np.linspace(0, nc, nc + 1) - 0.5, rwidth=0.8)
1193
- ax[0].set_xlabel('classes')
1194
- ax[1].scatter(b[0], b[1], c=hist2d(b[0], b[1], 90), cmap='jet')
1195
- ax[1].set_xlabel('x')
1196
- ax[1].set_ylabel('y')
1197
- ax[2].scatter(b[2], b[3], c=hist2d(b[2], b[3], 90), cmap='jet')
1198
- ax[2].set_xlabel('width')
1199
- ax[2].set_ylabel('height')
1200
- plt.savefig(Path(save_dir) / 'labels.png', dpi=200)
1201
- plt.close()
1202
-
1203
- # seaborn correlogram
1204
- try:
1205
- import seaborn as sns
1206
- import pandas as pd
1207
- x = pd.DataFrame(b.transpose(), columns=['x', 'y', 'width', 'height'])
1208
- sns.pairplot(x, corner=True, diag_kind='hist', kind='scatter', markers='o',
1209
- plot_kws=dict(s=3, edgecolor=None, linewidth=1, alpha=0.02),
1210
- diag_kws=dict(bins=50))
1211
- plt.savefig(Path(save_dir) / 'labels_correlogram.png', dpi=200)
1212
- plt.close()
1213
- except Exception as e:
1214
- pass
1215
-
1216
-
1217
- def plot_evolution(yaml_file='data/hyp.finetune.yaml'): # from utils.general import *; plot_evolution()
1218
- # Plot hyperparameter evolution results in evolve.txt
1219
- with open(yaml_file) as f:
1220
- hyp = yaml.load(f, Loader=yaml.FullLoader)
1221
- x = np.loadtxt('evolve.txt', ndmin=2)
1222
- f = fitness(x)
1223
- # weights = (f - f.min()) ** 2 # for weighted results
1224
- plt.figure(figsize=(10, 12), tight_layout=True)
1225
- matplotlib.rc('font', **{'size': 8})
1226
- for i, (k, v) in enumerate(hyp.items()):
1227
- y = x[:, i + 7]
1228
- # mu = (y * weights).sum() / weights.sum() # best weighted result
1229
- mu = y[f.argmax()] # best single result
1230
- plt.subplot(6, 5, i + 1)
1231
- plt.scatter(y, f, c=hist2d(y, f, 20), cmap='viridis', alpha=.8, edgecolors='none')
1232
- plt.plot(mu, f.max(), 'k+', markersize=15)
1233
- plt.title('%s = %.3g' % (k, mu), fontdict={'size': 9}) # limit to 40 characters
1234
- if i % 5 != 0:
1235
- plt.yticks([])
1236
- print('%15s: %.3g' % (k, mu))
1237
- plt.savefig('evolve.png', dpi=200)
1238
- print('\nPlot saved as evolve.png')
1239
-
1240
-
1241
- def plot_results_overlay(start=0, stop=0): # from utils.general import *; plot_results_overlay()
1242
- # Plot training 'results*.txt', overlaying train and val losses
1243
- s = ['train', 'train', 'train', 'Precision', '[email protected]', 'val', 'val', 'val', 'Recall', '[email protected]:0.95'] # legends
1244
- t = ['Box', 'Objectness', 'Classification', 'P-R', 'mAP-F1'] # titles
1245
- for f in sorted(glob.glob('results*.txt') + glob.glob('../../Downloads/results*.txt')):
1246
- results = np.loadtxt(f, usecols=[2, 3, 4, 8, 9, 12, 13, 14, 10, 11], ndmin=2).T
1247
- n = results.shape[1] # number of rows
1248
- x = range(start, min(stop, n) if stop else n)
1249
- fig, ax = plt.subplots(1, 5, figsize=(14, 3.5), tight_layout=True)
1250
- ax = ax.ravel()
1251
- for i in range(5):
1252
- for j in [i, i + 5]:
1253
- y = results[j, x]
1254
- ax[i].plot(x, y, marker='.', label=s[j])
1255
- # y_smooth = butter_lowpass_filtfilt(y)
1256
- # ax[i].plot(x, np.gradient(y_smooth), marker='.', label=s[j])
1257
-
1258
- ax[i].set_title(t[i])
1259
- ax[i].legend()
1260
- ax[i].set_ylabel(f) if i == 0 else None # add filename
1261
- fig.savefig(f.replace('.txt', '.png'), dpi=200)
1262
-
1263
-
1264
- def plot_results(start=0, stop=0, bucket='', id=(), labels=(), save_dir=''):
1265
- # from utils.general import *; plot_results(save_dir='runs/train/exp0')
1266
- # Plot training 'results*.txt' as seen in https://github.com/ultralytics/yolov5#reproduce-our-training
1267
- fig, ax = plt.subplots(2, 5, figsize=(12, 6))
1268
- ax = ax.ravel()
1269
- s = ['Box', 'Objectness', 'Classification', 'Precision', 'Recall',
1270
- 'val Box', 'val Objectness', 'val Classification', '[email protected]', '[email protected]:0.95']
1271
- if bucket:
1272
- # os.system('rm -rf storage.googleapis.com')
1273
- # files = ['https://storage.googleapis.com/%s/results%g.txt' % (bucket, x) for x in id]
1274
- files = ['results%g.txt' % x for x in id]
1275
- c = ('gsutil cp ' + '%s ' * len(files) + '.') % tuple('gs://%s/results%g.txt' % (bucket, x) for x in id)
1276
- os.system(c)
1277
- else:
1278
- files = glob.glob(str(Path(save_dir) / 'results*.txt')) + glob.glob('../../Downloads/results*.txt')
1279
- assert len(files), 'No results.txt files found in %s, nothing to plot.' % os.path.abspath(save_dir)
1280
- for fi, f in enumerate(files):
1281
- try:
1282
- results = np.loadtxt(f, usecols=[2, 3, 4, 8, 9, 12, 13, 14, 10, 11], ndmin=2).T
1283
- n = results.shape[1] # number of rows
1284
- x = range(start, min(stop, n) if stop else n)
1285
- for i in range(10):
1286
- y = results[i, x]
1287
- if i in [0, 1, 2, 5, 6, 7]:
1288
- y[y == 0] = np.nan # don't show zero loss values
1289
- # y /= y[0] # normalize
1290
- label = labels[fi] if len(labels) else Path(f).stem
1291
- ax[i].plot(x, y, marker='.', label=label, linewidth=1, markersize=6)
1292
- ax[i].set_title(s[i])
1293
- # if i in [5, 6, 7]: # share train and val loss y axes
1294
- # ax[i].get_shared_y_axes().join(ax[i], ax[i - 5])
1295
- except Exception as e:
1296
- print('Warning: Plotting error for %s; %s' % (f, e))
1297
-
1298
- fig.tight_layout()
1299
- ax[1].legend()
1300
- fig.savefig(Path(save_dir) / 'results.png', dpi=200)
 
1
+ # General utils
2
+
3
  import glob
4
  import logging
5
  import math
 
7
  import platform
8
  import random
9
  import re
 
10
  import subprocess
11
  import time
 
 
12
  from pathlib import Path
13
 
14
  import cv2
15
  import matplotlib
 
16
  import numpy as np
17
  import torch
 
18
  import yaml
 
 
 
 
19
 
20
  from utils.google_utils import gsutil_getsize
21
+ from utils.metrics import fitness
22
+ from utils.torch_utils import init_torch_seeds
23
 
24
  # Set printoptions
25
  torch.set_printoptions(linewidth=320, precision=5, profile='long')
 
30
  cv2.setNumThreads(0)
31
 
32
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  def set_logging(rank=-1):
34
  logging.basicConfig(
35
  format="%(message)s",
 
64
  return new_size
65
 
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  def check_file(file):
68
  # Search for file if not found
69
  if os.path.isfile(file) or file == '':
 
76
 
77
 
78
  def check_dataset(dict):
79
+ # Download dataset if not found locally
80
  val, s = dict.get('val'), dict.get('download')
81
  if val and len(val):
82
  val = [os.path.abspath(x) for x in (val if isinstance(val, list) else [val])] # val path
 
184
  boxes[:, 3].clamp_(0, img_shape[0]) # y2
185
 
186
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-9):
188
  # Returns the IoU of box1 to box2. box1 is 4, box2 is nx4
189
  box2 = box2.T
 
262
  return inter / (wh1.prod(2) + wh2.prod(2) - inter) # iou = inter / (area1 + area2 - inter)
263
 
264
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  def non_max_suppression(prediction, conf_thres=0.1, iou_thres=0.6, merge=False, classes=None, agnostic=False):
266
  """Performs Non-Maximum Suppression (NMS) on inference results
267
 
 
327
  if i.shape[0] > max_det: # limit detections
328
  i = i[:max_det]
329
  if merge and (1 < n < 3E3): # Merge NMS (boxes merged using weighted mean)
330
+ # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)
331
+ iou = box_iou(boxes[i], boxes) > iou_thres # iou matrix
332
+ weights = iou * scores[None] # box weights
333
+ x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True) # merged boxes
334
+ if redundant:
335
+ i = i[iou.sum(1) > 1] # require redundancy
 
 
 
336
 
337
  output[xi] = x[i]
338
  if (time.time() - t) > time_limit:
 
355
  print('Optimizer stripped from %s,%s %.1fMB' % (f, (' saved as %s,' % s) if s else '', mb))
356
 
357
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  def print_mutation(hyp, results, yaml_file='hyp_evolved.yaml', bucket=''):
359
  # Print mutation results to evolve.txt (for use with train.py --evolve)
360
  a = '%10s' * len(hyp) % tuple(hyp.keys()) # hyperparam keys
 
421
  return x
422
 
423
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  def increment_path(path, exist_ok=True, sep=''):
425
  # Increment path, i.e. runs/exp --> runs/exp{sep}0, runs/exp{sep}1 etc.
426
  path = Path(path) # os-agnostic
 
432
  i = [int(m.groups()[0]) for m in matches if m] # indices
433
  n = max(i) + 1 if i else 2 # increment number
434
  return f"{path}{sep}{n}" # update path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/google_utils.py CHANGED
@@ -1,6 +1,4 @@
1
- # This file contains google utils: https://cloud.google.com/storage/docs/reference/libraries
2
- # pip install --upgrade google-cloud-storage
3
- # from google.cloud import storage
4
 
5
  import os
6
  import platform
 
1
+ # Google utils: https://cloud.google.com/storage/docs/reference/libraries
 
 
2
 
3
  import os
4
  import platform
utils/loss.py ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Loss functions
2
+
3
+ import torch
4
+ import torch.nn as nn
5
+
6
+ from utils.general import bbox_iou
7
+ from utils.torch_utils import is_parallel
8
+
9
+
10
+ def smooth_BCE(eps=0.1): # https://github.com/ultralytics/yolov3/issues/238#issuecomment-598028441
11
+ # return positive, negative label smoothing BCE targets
12
+ return 1.0 - 0.5 * eps, 0.5 * eps
13
+
14
+
15
+ class BCEBlurWithLogitsLoss(nn.Module):
16
+ # BCEwithLogitLoss() with reduced missing label effects.
17
+ def __init__(self, alpha=0.05):
18
+ super(BCEBlurWithLogitsLoss, self).__init__()
19
+ self.loss_fcn = nn.BCEWithLogitsLoss(reduction='none') # must be nn.BCEWithLogitsLoss()
20
+ self.alpha = alpha
21
+
22
+ def forward(self, pred, true):
23
+ loss = self.loss_fcn(pred, true)
24
+ pred = torch.sigmoid(pred) # prob from logits
25
+ dx = pred - true # reduce only missing label effects
26
+ # dx = (pred - true).abs() # reduce missing label and false label effects
27
+ alpha_factor = 1 - torch.exp((dx - 1) / (self.alpha + 1e-4))
28
+ loss *= alpha_factor
29
+ return loss.mean()
30
+
31
+
32
+ class FocalLoss(nn.Module):
33
+ # Wraps focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5)
34
+ def __init__(self, loss_fcn, gamma=1.5, alpha=0.25):
35
+ super(FocalLoss, self).__init__()
36
+ self.loss_fcn = loss_fcn # must be nn.BCEWithLogitsLoss()
37
+ self.gamma = gamma
38
+ self.alpha = alpha
39
+ self.reduction = loss_fcn.reduction
40
+ self.loss_fcn.reduction = 'none' # required to apply FL to each element
41
+
42
+ def forward(self, pred, true):
43
+ loss = self.loss_fcn(pred, true)
44
+ # p_t = torch.exp(-loss)
45
+ # loss *= self.alpha * (1.000001 - p_t) ** self.gamma # non-zero power for gradient stability
46
+
47
+ # TF implementation https://github.com/tensorflow/addons/blob/v0.7.1/tensorflow_addons/losses/focal_loss.py
48
+ pred_prob = torch.sigmoid(pred) # prob from logits
49
+ p_t = true * pred_prob + (1 - true) * (1 - pred_prob)
50
+ alpha_factor = true * self.alpha + (1 - true) * (1 - self.alpha)
51
+ modulating_factor = (1.0 - p_t) ** self.gamma
52
+ loss *= alpha_factor * modulating_factor
53
+
54
+ if self.reduction == 'mean':
55
+ return loss.mean()
56
+ elif self.reduction == 'sum':
57
+ return loss.sum()
58
+ else: # 'none'
59
+ return loss
60
+
61
+
62
+ def compute_loss(p, targets, model): # predictions, targets, model
63
+ device = targets.device
64
+ lcls, lbox, lobj = torch.zeros(1, device=device), torch.zeros(1, device=device), torch.zeros(1, device=device)
65
+ tcls, tbox, indices, anchors = build_targets(p, targets, model) # targets
66
+ h = model.hyp # hyperparameters
67
+
68
+ # Define criteria
69
+ BCEcls = nn.BCEWithLogitsLoss(pos_weight=torch.Tensor([h['cls_pw']])).to(device)
70
+ BCEobj = nn.BCEWithLogitsLoss(pos_weight=torch.Tensor([h['obj_pw']])).to(device)
71
+
72
+ # Class label smoothing https://arxiv.org/pdf/1902.04103.pdf eqn 3
73
+ cp, cn = smooth_BCE(eps=0.0)
74
+
75
+ # Focal loss
76
+ g = h['fl_gamma'] # focal loss gamma
77
+ if g > 0:
78
+ BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g)
79
+
80
+ # Losses
81
+ nt = 0 # number of targets
82
+ no = len(p) # number of outputs
83
+ balance = [4.0, 1.0, 0.4] if no == 3 else [4.0, 1.0, 0.4, 0.1] # P3-5 or P3-6
84
+ for i, pi in enumerate(p): # layer index, layer predictions
85
+ b, a, gj, gi = indices[i] # image, anchor, gridy, gridx
86
+ tobj = torch.zeros_like(pi[..., 0], device=device) # target obj
87
+
88
+ n = b.shape[0] # number of targets
89
+ if n:
90
+ nt += n # cumulative targets
91
+ ps = pi[b, a, gj, gi] # prediction subset corresponding to targets
92
+
93
+ # Regression
94
+ pxy = ps[:, :2].sigmoid() * 2. - 0.5
95
+ pwh = (ps[:, 2:4].sigmoid() * 2) ** 2 * anchors[i]
96
+ pbox = torch.cat((pxy, pwh), 1).to(device) # predicted box
97
+ iou = bbox_iou(pbox.T, tbox[i], x1y1x2y2=False, CIoU=True) # iou(prediction, target)
98
+ lbox += (1.0 - iou).mean() # iou loss
99
+
100
+ # Objectness
101
+ tobj[b, a, gj, gi] = (1.0 - model.gr) + model.gr * iou.detach().clamp(0).type(tobj.dtype) # iou ratio
102
+
103
+ # Classification
104
+ if model.nc > 1: # cls loss (only if multiple classes)
105
+ t = torch.full_like(ps[:, 5:], cn, device=device) # targets
106
+ t[range(n), tcls[i]] = cp
107
+ lcls += BCEcls(ps[:, 5:], t) # BCE
108
+
109
+ # Append targets to text file
110
+ # with open('targets.txt', 'a') as file:
111
+ # [file.write('%11.5g ' * 4 % tuple(x) + '\n') for x in torch.cat((txy[i], twh[i]), 1)]
112
+
113
+ lobj += BCEobj(pi[..., 4], tobj) * balance[i] # obj loss
114
+
115
+ s = 3 / no # output count scaling
116
+ lbox *= h['box'] * s
117
+ lobj *= h['obj'] * s * (1.4 if no == 4 else 1.)
118
+ lcls *= h['cls'] * s
119
+ bs = tobj.shape[0] # batch size
120
+
121
+ loss = lbox + lobj + lcls
122
+ return loss * bs, torch.cat((lbox, lobj, lcls, loss)).detach()
123
+
124
+
125
+ def build_targets(p, targets, model):
126
+ # Build targets for compute_loss(), input targets(image,class,x,y,w,h)
127
+ det = model.module.model[-1] if is_parallel(model) else model.model[-1] # Detect() module
128
+ na, nt = det.na, targets.shape[0] # number of anchors, targets
129
+ tcls, tbox, indices, anch = [], [], [], []
130
+ gain = torch.ones(7, device=targets.device) # normalized to gridspace gain
131
+ ai = torch.arange(na, device=targets.device).float().view(na, 1).repeat(1, nt) # same as .repeat_interleave(nt)
132
+ targets = torch.cat((targets.repeat(na, 1, 1), ai[:, :, None]), 2) # append anchor indices
133
+
134
+ g = 0.5 # bias
135
+ off = torch.tensor([[0, 0],
136
+ [1, 0], [0, 1], [-1, 0], [0, -1], # j,k,l,m
137
+ # [1, 1], [1, -1], [-1, 1], [-1, -1], # jk,jm,lk,lm
138
+ ], device=targets.device).float() * g # offsets
139
+
140
+ for i in range(det.nl):
141
+ anchors = det.anchors[i]
142
+ gain[2:6] = torch.tensor(p[i].shape)[[3, 2, 3, 2]] # xyxy gain
143
+
144
+ # Match targets to anchors
145
+ t = targets * gain
146
+ if nt:
147
+ # Matches
148
+ r = t[:, :, 4:6] / anchors[:, None] # wh ratio
149
+ j = torch.max(r, 1. / r).max(2)[0] < model.hyp['anchor_t'] # compare
150
+ # j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t'] # iou(3,n)=wh_iou(anchors(3,2), gwh(n,2))
151
+ t = t[j] # filter
152
+
153
+ # Offsets
154
+ gxy = t[:, 2:4] # grid xy
155
+ gxi = gain[[2, 3]] - gxy # inverse
156
+ j, k = ((gxy % 1. < g) & (gxy > 1.)).T
157
+ l, m = ((gxi % 1. < g) & (gxi > 1.)).T
158
+ j = torch.stack((torch.ones_like(j), j, k, l, m))
159
+ t = t.repeat((5, 1, 1))[j]
160
+ offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j]
161
+ else:
162
+ t = targets[0]
163
+ offsets = 0
164
+
165
+ # Define
166
+ b, c = t[:, :2].long().T # image, class
167
+ gxy = t[:, 2:4] # grid xy
168
+ gwh = t[:, 4:6] # grid wh
169
+ gij = (gxy - offsets).long()
170
+ gi, gj = gij.T # grid xy indices
171
+
172
+ # Append
173
+ a = t[:, 6].long() # anchor indices
174
+ indices.append((b, a, gj.clamp_(0, gain[3] - 1), gi.clamp_(0, gain[2] - 1))) # image, anchor, grid indices
175
+ tbox.append(torch.cat((gxy - gij, gwh), 1)) # box
176
+ anch.append(anchors[a]) # anchors
177
+ tcls.append(c) # class
178
+
179
+ return tcls, tbox, indices, anch
utils/metrics.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Model validation metrics
2
+
3
+ import matplotlib.pyplot as plt
4
+ import numpy as np
5
+
6
+
7
+ def fitness(x):
8
+ # Model fitness as a weighted combination of metrics
9
+ w = [0.0, 0.0, 0.1, 0.9] # weights for [P, R, [email protected], [email protected]:0.95]
10
+ return (x[:, :4] * w).sum(1)
11
+
12
+
13
+ def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, fname='precision-recall_curve.png'):
14
+ """ Compute the average precision, given the recall and precision curves.
15
+ Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
16
+ # Arguments
17
+ tp: True positives (nparray, nx1 or nx10).
18
+ conf: Objectness value from 0-1 (nparray).
19
+ pred_cls: Predicted object classes (nparray).
20
+ target_cls: True object classes (nparray).
21
+ plot: Plot precision-recall curve at [email protected]
22
+ fname: Plot filename
23
+ # Returns
24
+ The average precision as computed in py-faster-rcnn.
25
+ """
26
+
27
+ # Sort by objectness
28
+ i = np.argsort(-conf)
29
+ tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]
30
+
31
+ # Find unique classes
32
+ unique_classes = np.unique(target_cls)
33
+
34
+ # Create Precision-Recall curve and compute AP for each class
35
+ px, py = np.linspace(0, 1, 1000), [] # for plotting
36
+ pr_score = 0.1 # score to evaluate P and R https://github.com/ultralytics/yolov3/issues/898
37
+ s = [unique_classes.shape[0], tp.shape[1]] # number class, number iou thresholds (i.e. 10 for mAP0.5...0.95)
38
+ ap, p, r = np.zeros(s), np.zeros(s), np.zeros(s)
39
+ for ci, c in enumerate(unique_classes):
40
+ i = pred_cls == c
41
+ n_l = (target_cls == c).sum() # number of labels
42
+ n_p = i.sum() # number of predictions
43
+
44
+ if n_p == 0 or n_l == 0:
45
+ continue
46
+ else:
47
+ # Accumulate FPs and TPs
48
+ fpc = (1 - tp[i]).cumsum(0)
49
+ tpc = tp[i].cumsum(0)
50
+
51
+ # Recall
52
+ recall = tpc / (n_l + 1e-16) # recall curve
53
+ r[ci] = np.interp(-pr_score, -conf[i], recall[:, 0]) # r at pr_score, negative x, xp because xp decreases
54
+
55
+ # Precision
56
+ precision = tpc / (tpc + fpc) # precision curve
57
+ p[ci] = np.interp(-pr_score, -conf[i], precision[:, 0]) # p at pr_score
58
+
59
+ # AP from recall-precision curve
60
+ for j in range(tp.shape[1]):
61
+ ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j])
62
+ if j == 0:
63
+ py.append(np.interp(px, mrec, mpre)) # precision at [email protected]
64
+
65
+ # Compute F1 score (harmonic mean of precision and recall)
66
+ f1 = 2 * p * r / (p + r + 1e-16)
67
+
68
+ if plot:
69
+ py = np.stack(py, axis=1)
70
+ fig, ax = plt.subplots(1, 1, figsize=(5, 5))
71
+ ax.plot(px, py, linewidth=0.5, color='grey') # plot(recall, precision)
72
+ ax.plot(px, py.mean(1), linewidth=2, color='blue', label='all classes %.3f [email protected]' % ap[:, 0].mean())
73
+ ax.set_xlabel('Recall')
74
+ ax.set_ylabel('Precision')
75
+ ax.set_xlim(0, 1)
76
+ ax.set_ylim(0, 1)
77
+ plt.legend()
78
+ fig.tight_layout()
79
+ fig.savefig(fname, dpi=200)
80
+
81
+ return p, r, ap, f1, unique_classes.astype('int32')
82
+
83
+
84
+ def compute_ap(recall, precision):
85
+ """ Compute the average precision, given the recall and precision curves.
86
+ Source: https://github.com/rbgirshick/py-faster-rcnn.
87
+ # Arguments
88
+ recall: The recall curve (list).
89
+ precision: The precision curve (list).
90
+ # Returns
91
+ The average precision as computed in py-faster-rcnn.
92
+ """
93
+
94
+ # Append sentinel values to beginning and end
95
+ mrec = recall # np.concatenate(([0.], recall, [recall[-1] + 1E-3]))
96
+ mpre = precision # np.concatenate(([0.], precision, [0.]))
97
+
98
+ # Compute the precision envelope
99
+ mpre = np.flip(np.maximum.accumulate(np.flip(mpre)))
100
+
101
+ # Integrate area under curve
102
+ method = 'interp' # methods: 'continuous', 'interp'
103
+ if method == 'interp':
104
+ x = np.linspace(0, 1, 101) # 101-point interp (COCO)
105
+ ap = np.trapz(np.interp(x, mrec, mpre), x) # integrate
106
+ else: # 'continuous'
107
+ i = np.where(mrec[1:] != mrec[:-1])[0] # points where x axis (recall) changes
108
+ ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) # area under curve
109
+
110
+ return ap, mpre, mrec
utils/plots.py ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Plotting utils
2
+
3
+ import glob
4
+ import math
5
+ import os
6
+ import random
7
+ from copy import copy
8
+ from pathlib import Path
9
+
10
+ import cv2
11
+ import matplotlib
12
+ import matplotlib.pyplot as plt
13
+ import numpy as np
14
+ import torch
15
+ import yaml
16
+ from PIL import Image
17
+ from scipy.signal import butter, filtfilt
18
+
19
+ from utils.general import xywh2xyxy, xyxy2xywh
20
+ from utils.metrics import fitness
21
+
22
+
23
+ def color_list():
24
+ # Return first 10 plt colors as (r,g,b) https://stackoverflow.com/questions/51350872/python-from-color-name-to-rgb
25
+ def hex2rgb(h):
26
+ return tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4))
27
+
28
+ return [hex2rgb(h) for h in plt.rcParams['axes.prop_cycle'].by_key()['color']]
29
+
30
+
31
+ def hist2d(x, y, n=100):
32
+ # 2d histogram used in labels.png and evolve.png
33
+ xedges, yedges = np.linspace(x.min(), x.max(), n), np.linspace(y.min(), y.max(), n)
34
+ hist, xedges, yedges = np.histogram2d(x, y, (xedges, yedges))
35
+ xidx = np.clip(np.digitize(x, xedges) - 1, 0, hist.shape[0] - 1)
36
+ yidx = np.clip(np.digitize(y, yedges) - 1, 0, hist.shape[1] - 1)
37
+ return np.log(hist[xidx, yidx])
38
+
39
+
40
+ def butter_lowpass_filtfilt(data, cutoff=1500, fs=50000, order=5):
41
+ # https://stackoverflow.com/questions/28536191/how-to-filter-smooth-with-scipy-numpy
42
+ def butter_lowpass(cutoff, fs, order):
43
+ nyq = 0.5 * fs
44
+ normal_cutoff = cutoff / nyq
45
+ return butter(order, normal_cutoff, btype='low', analog=False)
46
+
47
+ b, a = butter_lowpass(cutoff, fs, order=order)
48
+ return filtfilt(b, a, data) # forward-backward filter
49
+
50
+
51
+ def plot_one_box(x, img, color=None, label=None, line_thickness=None):
52
+ # Plots one bounding box on image img
53
+ tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1 # line/font thickness
54
+ color = color or [random.randint(0, 255) for _ in range(3)]
55
+ c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
56
+ cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
57
+ if label:
58
+ tf = max(tl - 1, 1) # font thickness
59
+ t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
60
+ c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
61
+ cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA) # filled
62
+ cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
63
+
64
+
65
+ def plot_wh_methods(): # from utils.general import *; plot_wh_methods()
66
+ # Compares the two methods for width-height anchor multiplication
67
+ # https://github.com/ultralytics/yolov3/issues/168
68
+ x = np.arange(-4.0, 4.0, .1)
69
+ ya = np.exp(x)
70
+ yb = torch.sigmoid(torch.from_numpy(x)).numpy() * 2
71
+
72
+ fig = plt.figure(figsize=(6, 3), dpi=150)
73
+ plt.plot(x, ya, '.-', label='YOLOv3')
74
+ plt.plot(x, yb ** 2, '.-', label='YOLOv5 ^2')
75
+ plt.plot(x, yb ** 1.6, '.-', label='YOLOv5 ^1.6')
76
+ plt.xlim(left=-4, right=4)
77
+ plt.ylim(bottom=0, top=6)
78
+ plt.xlabel('input')
79
+ plt.ylabel('output')
80
+ plt.grid()
81
+ plt.legend()
82
+ fig.tight_layout()
83
+ fig.savefig('comparison.png', dpi=200)
84
+
85
+
86
+ def output_to_target(output, width, height):
87
+ # Convert model output to target format [batch_id, class_id, x, y, w, h, conf]
88
+ if isinstance(output, torch.Tensor):
89
+ output = output.cpu().numpy()
90
+
91
+ targets = []
92
+ for i, o in enumerate(output):
93
+ if o is not None:
94
+ for pred in o:
95
+ box = pred[:4]
96
+ w = (box[2] - box[0]) / width
97
+ h = (box[3] - box[1]) / height
98
+ x = box[0] / width + w / 2
99
+ y = box[1] / height + h / 2
100
+ conf = pred[4]
101
+ cls = int(pred[5])
102
+
103
+ targets.append([i, cls, x, y, w, h, conf])
104
+
105
+ return np.array(targets)
106
+
107
+
108
+ def plot_images(images, targets, paths=None, fname='images.jpg', names=None, max_size=640, max_subplots=16):
109
+ # Plot image grid with labels
110
+
111
+ if isinstance(images, torch.Tensor):
112
+ images = images.cpu().float().numpy()
113
+ if isinstance(targets, torch.Tensor):
114
+ targets = targets.cpu().numpy()
115
+
116
+ # un-normalise
117
+ if np.max(images[0]) <= 1:
118
+ images *= 255
119
+
120
+ tl = 3 # line thickness
121
+ tf = max(tl - 1, 1) # font thickness
122
+ bs, _, h, w = images.shape # batch size, _, height, width
123
+ bs = min(bs, max_subplots) # limit plot images
124
+ ns = np.ceil(bs ** 0.5) # number of subplots (square)
125
+
126
+ # Check if we should resize
127
+ scale_factor = max_size / max(h, w)
128
+ if scale_factor < 1:
129
+ h = math.ceil(scale_factor * h)
130
+ w = math.ceil(scale_factor * w)
131
+
132
+ colors = color_list() # list of colors
133
+ mosaic = np.full((int(ns * h), int(ns * w), 3), 255, dtype=np.uint8) # init
134
+ for i, img in enumerate(images):
135
+ if i == max_subplots: # if last batch has fewer images than we expect
136
+ break
137
+
138
+ block_x = int(w * (i // ns))
139
+ block_y = int(h * (i % ns))
140
+
141
+ img = img.transpose(1, 2, 0)
142
+ if scale_factor < 1:
143
+ img = cv2.resize(img, (w, h))
144
+
145
+ mosaic[block_y:block_y + h, block_x:block_x + w, :] = img
146
+ if len(targets) > 0:
147
+ image_targets = targets[targets[:, 0] == i]
148
+ boxes = xywh2xyxy(image_targets[:, 2:6]).T
149
+ classes = image_targets[:, 1].astype('int')
150
+ labels = image_targets.shape[1] == 6 # labels if no conf column
151
+ conf = None if labels else image_targets[:, 6] # check for confidence presence (label vs pred)
152
+
153
+ boxes[[0, 2]] *= w
154
+ boxes[[0, 2]] += block_x
155
+ boxes[[1, 3]] *= h
156
+ boxes[[1, 3]] += block_y
157
+ for j, box in enumerate(boxes.T):
158
+ cls = int(classes[j])
159
+ color = colors[cls % len(colors)]
160
+ cls = names[cls] if names else cls
161
+ if labels or conf[j] > 0.3: # 0.3 conf thresh
162
+ label = '%s' % cls if labels else '%s %.1f' % (cls, conf[j])
163
+ plot_one_box(box, mosaic, label=label, color=color, line_thickness=tl)
164
+
165
+ # Draw image filename labels
166
+ if paths is not None:
167
+ label = os.path.basename(paths[i])[:40] # trim to 40 char
168
+ t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
169
+ cv2.putText(mosaic, label, (block_x + 5, block_y + t_size[1] + 5), 0, tl / 3, [220, 220, 220], thickness=tf,
170
+ lineType=cv2.LINE_AA)
171
+
172
+ # Image border
173
+ cv2.rectangle(mosaic, (block_x, block_y), (block_x + w, block_y + h), (255, 255, 255), thickness=3)
174
+
175
+ if fname is not None:
176
+ r = min(1280. / max(h, w) / ns, 1.0) # ratio to limit image size
177
+ mosaic = cv2.resize(mosaic, (int(ns * w * r), int(ns * h * r)), interpolation=cv2.INTER_AREA)
178
+ # cv2.imwrite(fname, cv2.cvtColor(mosaic, cv2.COLOR_BGR2RGB)) # cv2 save
179
+ Image.fromarray(mosaic).save(fname) # PIL save
180
+ return mosaic
181
+
182
+
183
+ def plot_lr_scheduler(optimizer, scheduler, epochs=300, save_dir=''):
184
+ # Plot LR simulating training for full epochs
185
+ optimizer, scheduler = copy(optimizer), copy(scheduler) # do not modify originals
186
+ y = []
187
+ for _ in range(epochs):
188
+ scheduler.step()
189
+ y.append(optimizer.param_groups[0]['lr'])
190
+ plt.plot(y, '.-', label='LR')
191
+ plt.xlabel('epoch')
192
+ plt.ylabel('LR')
193
+ plt.grid()
194
+ plt.xlim(0, epochs)
195
+ plt.ylim(0)
196
+ plt.tight_layout()
197
+ plt.savefig(Path(save_dir) / 'LR.png', dpi=200)
198
+
199
+
200
+ def plot_test_txt(): # from utils.general import *; plot_test()
201
+ # Plot test.txt histograms
202
+ x = np.loadtxt('test.txt', dtype=np.float32)
203
+ box = xyxy2xywh(x[:, :4])
204
+ cx, cy = box[:, 0], box[:, 1]
205
+
206
+ fig, ax = plt.subplots(1, 1, figsize=(6, 6), tight_layout=True)
207
+ ax.hist2d(cx, cy, bins=600, cmax=10, cmin=0)
208
+ ax.set_aspect('equal')
209
+ plt.savefig('hist2d.png', dpi=300)
210
+
211
+ fig, ax = plt.subplots(1, 2, figsize=(12, 6), tight_layout=True)
212
+ ax[0].hist(cx, bins=600)
213
+ ax[1].hist(cy, bins=600)
214
+ plt.savefig('hist1d.png', dpi=200)
215
+
216
+
217
+ def plot_targets_txt(): # from utils.general import *; plot_targets_txt()
218
+ # Plot targets.txt histograms
219
+ x = np.loadtxt('targets.txt', dtype=np.float32).T
220
+ s = ['x targets', 'y targets', 'width targets', 'height targets']
221
+ fig, ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True)
222
+ ax = ax.ravel()
223
+ for i in range(4):
224
+ ax[i].hist(x[i], bins=100, label='%.3g +/- %.3g' % (x[i].mean(), x[i].std()))
225
+ ax[i].legend()
226
+ ax[i].set_title(s[i])
227
+ plt.savefig('targets.jpg', dpi=200)
228
+
229
+
230
+ def plot_study_txt(f='study.txt', x=None): # from utils.general import *; plot_study_txt()
231
+ # Plot study.txt generated by test.py
232
+ fig, ax = plt.subplots(2, 4, figsize=(10, 6), tight_layout=True)
233
+ ax = ax.ravel()
234
+
235
+ fig2, ax2 = plt.subplots(1, 1, figsize=(8, 4), tight_layout=True)
236
+ for f in ['study/study_coco_yolov5%s.txt' % x for x in ['s', 'm', 'l', 'x']]:
237
+ y = np.loadtxt(f, dtype=np.float32, usecols=[0, 1, 2, 3, 7, 8, 9], ndmin=2).T
238
+ x = np.arange(y.shape[1]) if x is None else np.array(x)
239
+ s = ['P', 'R', '[email protected]', '[email protected]:.95', 't_inference (ms/img)', 't_NMS (ms/img)', 't_total (ms/img)']
240
+ for i in range(7):
241
+ ax[i].plot(x, y[i], '.-', linewidth=2, markersize=8)
242
+ ax[i].set_title(s[i])
243
+
244
+ j = y[3].argmax() + 1
245
+ ax2.plot(y[6, :j], y[3, :j] * 1E2, '.-', linewidth=2, markersize=8,
246
+ label=Path(f).stem.replace('study_coco_', '').replace('yolo', 'YOLO'))
247
+
248
+ ax2.plot(1E3 / np.array([209, 140, 97, 58, 35, 18]), [34.6, 40.5, 43.0, 47.5, 49.7, 51.5],
249
+ 'k.-', linewidth=2, markersize=8, alpha=.25, label='EfficientDet')
250
+
251
+ ax2.grid()
252
+ ax2.set_xlim(0, 30)
253
+ ax2.set_ylim(28, 50)
254
+ ax2.set_yticks(np.arange(30, 55, 5))
255
+ ax2.set_xlabel('GPU Speed (ms/img)')
256
+ ax2.set_ylabel('COCO AP val')
257
+ ax2.legend(loc='lower right')
258
+ plt.savefig('study_mAP_latency.png', dpi=300)
259
+ plt.savefig(f.replace('.txt', '.png'), dpi=300)
260
+
261
+
262
+ def plot_labels(labels, save_dir=''):
263
+ # plot dataset labels
264
+ c, b = labels[:, 0], labels[:, 1:].transpose() # classes, boxes
265
+ nc = int(c.max() + 1) # number of classes
266
+
267
+ fig, ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True)
268
+ ax = ax.ravel()
269
+ ax[0].hist(c, bins=np.linspace(0, nc, nc + 1) - 0.5, rwidth=0.8)
270
+ ax[0].set_xlabel('classes')
271
+ ax[1].scatter(b[0], b[1], c=hist2d(b[0], b[1], 90), cmap='jet')
272
+ ax[1].set_xlabel('x')
273
+ ax[1].set_ylabel('y')
274
+ ax[2].scatter(b[2], b[3], c=hist2d(b[2], b[3], 90), cmap='jet')
275
+ ax[2].set_xlabel('width')
276
+ ax[2].set_ylabel('height')
277
+ plt.savefig(Path(save_dir) / 'labels.png', dpi=200)
278
+ plt.close()
279
+
280
+ # seaborn correlogram
281
+ try:
282
+ import seaborn as sns
283
+ import pandas as pd
284
+ x = pd.DataFrame(b.transpose(), columns=['x', 'y', 'width', 'height'])
285
+ sns.pairplot(x, corner=True, diag_kind='hist', kind='scatter', markers='o',
286
+ plot_kws=dict(s=3, edgecolor=None, linewidth=1, alpha=0.02),
287
+ diag_kws=dict(bins=50))
288
+ plt.savefig(Path(save_dir) / 'labels_correlogram.png', dpi=200)
289
+ plt.close()
290
+ except Exception as e:
291
+ pass
292
+
293
+
294
+ def plot_evolution(yaml_file='data/hyp.finetune.yaml'): # from utils.general import *; plot_evolution()
295
+ # Plot hyperparameter evolution results in evolve.txt
296
+ with open(yaml_file) as f:
297
+ hyp = yaml.load(f, Loader=yaml.FullLoader)
298
+ x = np.loadtxt('evolve.txt', ndmin=2)
299
+ f = fitness(x)
300
+ # weights = (f - f.min()) ** 2 # for weighted results
301
+ plt.figure(figsize=(10, 12), tight_layout=True)
302
+ matplotlib.rc('font', **{'size': 8})
303
+ for i, (k, v) in enumerate(hyp.items()):
304
+ y = x[:, i + 7]
305
+ # mu = (y * weights).sum() / weights.sum() # best weighted result
306
+ mu = y[f.argmax()] # best single result
307
+ plt.subplot(6, 5, i + 1)
308
+ plt.scatter(y, f, c=hist2d(y, f, 20), cmap='viridis', alpha=.8, edgecolors='none')
309
+ plt.plot(mu, f.max(), 'k+', markersize=15)
310
+ plt.title('%s = %.3g' % (k, mu), fontdict={'size': 9}) # limit to 40 characters
311
+ if i % 5 != 0:
312
+ plt.yticks([])
313
+ print('%15s: %.3g' % (k, mu))
314
+ plt.savefig('evolve.png', dpi=200)
315
+ print('\nPlot saved as evolve.png')
316
+
317
+
318
+ def plot_results_overlay(start=0, stop=0): # from utils.general import *; plot_results_overlay()
319
+ # Plot training 'results*.txt', overlaying train and val losses
320
+ s = ['train', 'train', 'train', 'Precision', '[email protected]', 'val', 'val', 'val', 'Recall', '[email protected]:0.95'] # legends
321
+ t = ['Box', 'Objectness', 'Classification', 'P-R', 'mAP-F1'] # titles
322
+ for f in sorted(glob.glob('results*.txt') + glob.glob('../../Downloads/results*.txt')):
323
+ results = np.loadtxt(f, usecols=[2, 3, 4, 8, 9, 12, 13, 14, 10, 11], ndmin=2).T
324
+ n = results.shape[1] # number of rows
325
+ x = range(start, min(stop, n) if stop else n)
326
+ fig, ax = plt.subplots(1, 5, figsize=(14, 3.5), tight_layout=True)
327
+ ax = ax.ravel()
328
+ for i in range(5):
329
+ for j in [i, i + 5]:
330
+ y = results[j, x]
331
+ ax[i].plot(x, y, marker='.', label=s[j])
332
+ # y_smooth = butter_lowpass_filtfilt(y)
333
+ # ax[i].plot(x, np.gradient(y_smooth), marker='.', label=s[j])
334
+
335
+ ax[i].set_title(t[i])
336
+ ax[i].legend()
337
+ ax[i].set_ylabel(f) if i == 0 else None # add filename
338
+ fig.savefig(f.replace('.txt', '.png'), dpi=200)
339
+
340
+
341
+ def plot_results(start=0, stop=0, bucket='', id=(), labels=(), save_dir=''):
342
+ # from utils.general import *; plot_results(save_dir='runs/train/exp0')
343
+ # Plot training 'results*.txt' as seen in https://github.com/ultralytics/yolov5#reproduce-our-training
344
+ fig, ax = plt.subplots(2, 5, figsize=(12, 6))
345
+ ax = ax.ravel()
346
+ s = ['Box', 'Objectness', 'Classification', 'Precision', 'Recall',
347
+ 'val Box', 'val Objectness', 'val Classification', '[email protected]', '[email protected]:0.95']
348
+ if bucket:
349
+ # os.system('rm -rf storage.googleapis.com')
350
+ # files = ['https://storage.googleapis.com/%s/results%g.txt' % (bucket, x) for x in id]
351
+ files = ['results%g.txt' % x for x in id]
352
+ c = ('gsutil cp ' + '%s ' * len(files) + '.') % tuple('gs://%s/results%g.txt' % (bucket, x) for x in id)
353
+ os.system(c)
354
+ else:
355
+ files = glob.glob(str(Path(save_dir) / 'results*.txt')) + glob.glob('../../Downloads/results*.txt')
356
+ assert len(files), 'No results.txt files found in %s, nothing to plot.' % os.path.abspath(save_dir)
357
+ for fi, f in enumerate(files):
358
+ try:
359
+ results = np.loadtxt(f, usecols=[2, 3, 4, 8, 9, 12, 13, 14, 10, 11], ndmin=2).T
360
+ n = results.shape[1] # number of rows
361
+ x = range(start, min(stop, n) if stop else n)
362
+ for i in range(10):
363
+ y = results[i, x]
364
+ if i in [0, 1, 2, 5, 6, 7]:
365
+ y[y == 0] = np.nan # don't show zero loss values
366
+ # y /= y[0] # normalize
367
+ label = labels[fi] if len(labels) else Path(f).stem
368
+ ax[i].plot(x, y, marker='.', label=label, linewidth=1, markersize=6)
369
+ ax[i].set_title(s[i])
370
+ # if i in [5, 6, 7]: # share train and val loss y axes
371
+ # ax[i].get_shared_y_axes().join(ax[i], ax[i - 5])
372
+ except Exception as e:
373
+ print('Warning: Plotting error for %s; %s' % (f, e))
374
+
375
+ fig.tight_layout()
376
+ ax[1].legend()
377
+ fig.savefig(Path(save_dir) / 'results.png', dpi=200)
utils/torch_utils.py CHANGED
@@ -1,7 +1,10 @@
 
 
1
  import logging
2
  import math
3
  import os
4
  import time
 
5
  from copy import deepcopy
6
 
7
  import torch
@@ -13,10 +16,21 @@ import torchvision
13
  logger = logging.getLogger(__name__)
14
 
15
 
16
- def init_torch_seeds(seed=0):
17
- torch.manual_seed(seed)
 
 
 
 
 
 
 
 
18
 
 
 
19
  # Speed-reproducibility tradeoff https://pytorch.org/docs/stable/notes/randomness.html
 
20
  if seed == 0: # slower, more reproducible
21
  cudnn.deterministic = True
22
  cudnn.benchmark = False
@@ -104,8 +118,6 @@ def prune(model, amount=0.3):
104
 
105
  def fuse_conv_and_bn(conv, bn):
106
  # Fuse convolution and batchnorm layers https://tehnokv.com/posts/fusing-batchnorm-and-conv/
107
-
108
- # init
109
  fusedconv = nn.Conv2d(conv.in_channels,
110
  conv.out_channels,
111
  kernel_size=conv.kernel_size,
@@ -145,8 +157,7 @@ def model_info(model, verbose=False):
145
  except ImportError:
146
  fs = ''
147
 
148
- logger.info(
149
- 'Model Summary: %g layers, %g parameters, %g gradients%s' % (len(list(model.parameters())), n_p, n_g, fs))
150
 
151
 
152
  def load_classifier(name='resnet101', n=2):
 
1
+ # PyTorch utils
2
+
3
  import logging
4
  import math
5
  import os
6
  import time
7
+ from contextlib import contextmanager
8
  from copy import deepcopy
9
 
10
  import torch
 
16
  logger = logging.getLogger(__name__)
17
 
18
 
19
+ @contextmanager
20
+ def torch_distributed_zero_first(local_rank: int):
21
+ """
22
+ Decorator to make all processes in distributed training wait for each local_master to do something.
23
+ """
24
+ if local_rank not in [-1, 0]:
25
+ torch.distributed.barrier()
26
+ yield
27
+ if local_rank == 0:
28
+ torch.distributed.barrier()
29
 
30
+
31
+ def init_torch_seeds(seed=0):
32
  # Speed-reproducibility tradeoff https://pytorch.org/docs/stable/notes/randomness.html
33
+ torch.manual_seed(seed)
34
  if seed == 0: # slower, more reproducible
35
  cudnn.deterministic = True
36
  cudnn.benchmark = False
 
118
 
119
  def fuse_conv_and_bn(conv, bn):
120
  # Fuse convolution and batchnorm layers https://tehnokv.com/posts/fusing-batchnorm-and-conv/
 
 
121
  fusedconv = nn.Conv2d(conv.in_channels,
122
  conv.out_channels,
123
  kernel_size=conv.kernel_size,
 
157
  except ImportError:
158
  fs = ''
159
 
160
+ logger.info(f"Model Summary: {len(list(model.modules()))} layers, {n_p} parameters, {n_g} gradients{fs}")
 
161
 
162
 
163
  def load_classifier(name='resnet101', n=2):