No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

1119 líneas
45KB

  1. import glob
  2. import math
  3. import os
  4. import random
  5. import shutil
  6. import subprocess
  7. import time
  8. from copy import copy
  9. from pathlib import Path
  10. from sys import platform
  11. import cv2
  12. import matplotlib
  13. import matplotlib.pyplot as plt
  14. import numpy as np
  15. import torch
  16. import torch.nn as nn
  17. import torchvision
  18. from scipy.signal import butter, filtfilt
  19. from tqdm import tqdm
  20. from . import torch_utils, google_utils #  torch_utils, google_utils
  21. # Set printoptions
  22. torch.set_printoptions(linewidth=320, precision=5, profile='long')
  23. np.set_printoptions(linewidth=320, formatter={'float_kind': '{:11.5g}'.format}) # format short g, %precision=5
  24. matplotlib.rc('font', **{'size': 11})
  25. # Prevent OpenCV from multithreading (to use PyTorch DataLoader)
  26. cv2.setNumThreads(0)
  27. def init_seeds(seed=0):
  28. random.seed(seed)
  29. np.random.seed(seed)
  30. torch_utils.init_seeds(seed=seed)
  31. def check_git_status():
  32. if platform in ['linux', 'darwin']:
  33. # Suggest 'git pull' if repo is out of date
  34. s = subprocess.check_output('if [ -d .git ]; then git fetch && git status -uno; fi', shell=True).decode('utf-8')
  35. if 'Your branch is behind' in s:
  36. print(s[s.find('Your branch is behind'):s.find('\n\n')] + '\n')
  37. def make_divisible(x, divisor):
  38. # Returns x evenly divisble by divisor
  39. return math.ceil(x / divisor) * divisor
  40. def labels_to_class_weights(labels, nc=80):
  41. # Get class weights (inverse frequency) from training labels
  42. if labels[0] is None: # no labels loaded
  43. return torch.Tensor()
  44. labels = np.concatenate(labels, 0) # labels.shape = (866643, 5) for COCO
  45. classes = labels[:, 0].astype(np.int) # labels = [class xywh]
  46. weights = np.bincount(classes, minlength=nc) # occurences per class
  47. # Prepend gridpoint count (for uCE trianing)
  48. # gpi = ((320 / 32 * np.array([1, 2, 4])) ** 2 * 3).sum() # gridpoints per image
  49. # weights = np.hstack([gpi * len(labels) - weights.sum() * 9, weights * 9]) ** 0.5 # prepend gridpoints to start
  50. weights[weights == 0] = 1 # replace empty bins with 1
  51. weights = 1 / weights # number of targets per class
  52. weights /= weights.sum() # normalize
  53. return torch.from_numpy(weights)
  54. def labels_to_image_weights(labels, nc=80, class_weights=np.ones(80)):
  55. # Produces image weights based on class mAPs
  56. n = len(labels)
  57. class_counts = np.array([np.bincount(labels[i][:, 0].astype(np.int), minlength=nc) for i in range(n)])
  58. image_weights = (class_weights.reshape(1, nc) * class_counts).sum(1)
  59. # index = random.choices(range(n), weights=image_weights, k=1) # weight image sample
  60. return image_weights
  61. def coco80_to_coco91_class(): # converts 80-index (val2014) to 91-index (paper)
  62. # https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/
  63. # a = np.loadtxt('data/coco.names', dtype='str', delimiter='\n')
  64. # b = np.loadtxt('data/coco_paper.names', dtype='str', delimiter='\n')
  65. # x1 = [list(a[i] == b).index(True) + 1 for i in range(80)] # darknet to coco
  66. # x2 = [list(b[i] == a).index(True) if any(b[i] == a) else None for i in range(91)] # coco to darknet
  67. x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27, 28, 31, 32, 33, 34,
  68. 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
  69. 64, 65, 67, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90]
  70. return x
  71. def xyxy2xywh(x):
  72. # Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] where xy1=top-left, xy2=bottom-right
  73. y = torch.zeros_like(x) if isinstance(x, torch.Tensor) else np.zeros_like(x)
  74. y[:, 0] = (x[:, 0] + x[:, 2]) / 2 # x center
  75. y[:, 1] = (x[:, 1] + x[:, 3]) / 2 # y center
  76. y[:, 2] = x[:, 2] - x[:, 0] # width
  77. y[:, 3] = x[:, 3] - x[:, 1] # height
  78. return y
  79. def xywh2xyxy(x):
  80. # Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
  81. y = torch.zeros_like(x) if isinstance(x, torch.Tensor) else np.zeros_like(x)
  82. y[:, 0] = x[:, 0] - x[:, 2] / 2 # top left x
  83. y[:, 1] = x[:, 1] - x[:, 3] / 2 # top left y
  84. y[:, 2] = x[:, 0] + x[:, 2] / 2 # bottom right x
  85. y[:, 3] = x[:, 1] + x[:, 3] / 2 # bottom right y
  86. return y
  87. def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):
  88. # Rescale coords (xyxy) from img1_shape to img0_shape
  89. if ratio_pad is None: # calculate from img0_shape
  90. gain = max(img1_shape) / max(img0_shape) # gain = old / new
  91. pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding
  92. else:
  93. gain = ratio_pad[0][0]
  94. pad = ratio_pad[1]
  95. coords[:, [0, 2]] -= pad[0] # x padding
  96. coords[:, [1, 3]] -= pad[1] # y padding
  97. coords[:, :4] /= gain
  98. clip_coords(coords, img0_shape)
  99. return coords
  100. def clip_coords(boxes, img_shape):
  101. # Clip bounding xyxy bounding boxes to image shape (height, width)
  102. boxes[:, 0].clamp_(0, img_shape[1]) # x1
  103. boxes[:, 1].clamp_(0, img_shape[0]) # y1
  104. boxes[:, 2].clamp_(0, img_shape[1]) # x2
  105. boxes[:, 3].clamp_(0, img_shape[0]) # y2
  106. def ap_per_class(tp, conf, pred_cls, target_cls):
  107. """ Compute the average precision, given the recall and precision curves.
  108. Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
  109. # Arguments
  110. tp: True positives (nparray, nx1 or nx10).
  111. conf: Objectness value from 0-1 (nparray).
  112. pred_cls: Predicted object classes (nparray).
  113. target_cls: True object classes (nparray).
  114. # Returns
  115. The average precision as computed in py-faster-rcnn.
  116. """
  117. # Sort by objectness
  118. i = np.argsort(-conf)
  119. tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]
  120. # Find unique classes
  121. unique_classes = np.unique(target_cls)
  122. # Create Precision-Recall curve and compute AP for each class
  123. pr_score = 0.1 # score to evaluate P and R https://github.com/ultralytics/yolov3/issues/898
  124. s = [unique_classes.shape[0], tp.shape[1]] # number class, number iou thresholds (i.e. 10 for mAP0.5...0.95)
  125. ap, p, r = np.zeros(s), np.zeros(s), np.zeros(s)
  126. for ci, c in enumerate(unique_classes):
  127. i = pred_cls == c
  128. n_gt = (target_cls == c).sum() # Number of ground truth objects
  129. n_p = i.sum() # Number of predicted objects
  130. if n_p == 0 or n_gt == 0:
  131. continue
  132. else:
  133. # Accumulate FPs and TPs
  134. fpc = (1 - tp[i]).cumsum(0)
  135. tpc = tp[i].cumsum(0)
  136. # Recall
  137. recall = tpc / (n_gt + 1e-16) # recall curve
  138. r[ci] = np.interp(-pr_score, -conf[i], recall[:, 0]) # r at pr_score, negative x, xp because xp decreases
  139. # Precision
  140. precision = tpc / (tpc + fpc) # precision curve
  141. p[ci] = np.interp(-pr_score, -conf[i], precision[:, 0]) # p at pr_score
  142. # AP from recall-precision curve
  143. for j in range(tp.shape[1]):
  144. ap[ci, j] = compute_ap(recall[:, j], precision[:, j])
  145. # Plot
  146. # fig, ax = plt.subplots(1, 1, figsize=(5, 5))
  147. # ax.plot(recall, precision)
  148. # ax.set_xlabel('Recall')
  149. # ax.set_ylabel('Precision')
  150. # ax.set_xlim(0, 1.01)
  151. # ax.set_ylim(0, 1.01)
  152. # fig.tight_layout()
  153. # fig.savefig('PR_curve.png', dpi=300)
  154. # Compute F1 score (harmonic mean of precision and recall)
  155. f1 = 2 * p * r / (p + r + 1e-16)
  156. return p, r, ap, f1, unique_classes.astype('int32')
  157. def compute_ap(recall, precision):
  158. """ Compute the average precision, given the recall and precision curves.
  159. Source: https://github.com/rbgirshick/py-faster-rcnn.
  160. # Arguments
  161. recall: The recall curve (list).
  162. precision: The precision curve (list).
  163. # Returns
  164. The average precision as computed in py-faster-rcnn.
  165. """
  166. # Append sentinel values to beginning and end
  167. mrec = np.concatenate(([0.], recall, [min(recall[-1] + 1E-3, 1.)]))
  168. mpre = np.concatenate(([0.], precision, [0.]))
  169. # Compute the precision envelope
  170. mpre = np.flip(np.maximum.accumulate(np.flip(mpre)))
  171. # Integrate area under curve
  172. method = 'interp' # methods: 'continuous', 'interp'
  173. if method == 'interp':
  174. x = np.linspace(0, 1, 101) # 101-point interp (COCO)
  175. ap = np.trapz(np.interp(x, mrec, mpre), x) # integrate
  176. else: # 'continuous'
  177. i = np.where(mrec[1:] != mrec[:-1])[0] # points where x axis (recall) changes
  178. ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) # area under curve
  179. return ap
  180. def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False):
  181. # Returns the IoU of box1 to box2. box1 is 4, box2 is nx4
  182. box2 = box2.t()
  183. # Get the coordinates of bounding boxes
  184. if x1y1x2y2: # x1, y1, x2, y2 = box1
  185. b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]
  186. b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]
  187. else: # transform from xywh to xyxy
  188. b1_x1, b1_x2 = box1[0] - box1[2] / 2, box1[0] + box1[2] / 2
  189. b1_y1, b1_y2 = box1[1] - box1[3] / 2, box1[1] + box1[3] / 2
  190. b2_x1, b2_x2 = box2[0] - box2[2] / 2, box2[0] + box2[2] / 2
  191. b2_y1, b2_y2 = box2[1] - box2[3] / 2, box2[1] + box2[3] / 2
  192. # Intersection area
  193. inter = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \
  194. (torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0)
  195. # Union Area
  196. w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1
  197. w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1
  198. union = (w1 * h1 + 1e-16) + w2 * h2 - inter
  199. iou = inter / union # iou
  200. if GIoU or DIoU or CIoU:
  201. cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1) # convex (smallest enclosing box) width
  202. ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1) # convex height
  203. if GIoU: # Generalized IoU https://arxiv.org/pdf/1902.09630.pdf
  204. c_area = cw * ch + 1e-16 # convex area
  205. return iou - (c_area - union) / c_area # GIoU
  206. if DIoU or CIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
  207. # convex diagonal squared
  208. c2 = cw ** 2 + ch ** 2 + 1e-16
  209. # centerpoint distance squared
  210. rho2 = ((b2_x1 + b2_x2) - (b1_x1 + b1_x2)) ** 2 / 4 + ((b2_y1 + b2_y2) - (b1_y1 + b1_y2)) ** 2 / 4
  211. if DIoU:
  212. return iou - rho2 / c2 # DIoU
  213. elif CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47
  214. v = (4 / math.pi ** 2) * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2)
  215. with torch.no_grad():
  216. alpha = v / (1 - iou + v)
  217. return iou - (rho2 / c2 + v * alpha) # CIoU
  218. return iou
  219. def box_iou(box1, box2):
  220. # https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py
  221. """
  222. Return intersection-over-union (Jaccard index) of boxes.
  223. Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
  224. Arguments:
  225. box1 (Tensor[N, 4])
  226. box2 (Tensor[M, 4])
  227. Returns:
  228. iou (Tensor[N, M]): the NxM matrix containing the pairwise
  229. IoU values for every element in boxes1 and boxes2
  230. """
  231. def box_area(box):
  232. # box = 4xn
  233. return (box[2] - box[0]) * (box[3] - box[1])
  234. area1 = box_area(box1.t())
  235. area2 = box_area(box2.t())
  236. # inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2)
  237. inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)
  238. return inter / (area1[:, None] + area2 - inter) # iou = inter / (area1 + area2 - inter)
  239. def wh_iou(wh1, wh2):
  240. # Returns the nxm IoU matrix. wh1 is nx2, wh2 is mx2
  241. wh1 = wh1[:, None] # [N,1,2]
  242. wh2 = wh2[None] # [1,M,2]
  243. inter = torch.min(wh1, wh2).prod(2) # [N,M]
  244. return inter / (wh1.prod(2) + wh2.prod(2) - inter) # iou = inter / (area1 + area2 - inter)
  245. class FocalLoss(nn.Module):
  246. # Wraps focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5)
  247. def __init__(self, loss_fcn, gamma=1.5, alpha=0.25):
  248. super(FocalLoss, self).__init__()
  249. self.loss_fcn = loss_fcn # must be nn.BCEWithLogitsLoss()
  250. self.gamma = gamma
  251. self.alpha = alpha
  252. self.reduction = loss_fcn.reduction
  253. self.loss_fcn.reduction = 'none' # required to apply FL to each element
  254. def forward(self, pred, true):
  255. loss = self.loss_fcn(pred, true)
  256. # p_t = torch.exp(-loss)
  257. # loss *= self.alpha * (1.000001 - p_t) ** self.gamma # non-zero power for gradient stability
  258. # TF implementation https://github.com/tensorflow/addons/blob/v0.7.1/tensorflow_addons/losses/focal_loss.py
  259. pred_prob = torch.sigmoid(pred) # prob from logits
  260. p_t = true * pred_prob + (1 - true) * (1 - pred_prob)
  261. alpha_factor = true * self.alpha + (1 - true) * (1 - self.alpha)
  262. modulating_factor = (1.0 - p_t) ** self.gamma
  263. loss *= alpha_factor * modulating_factor
  264. if self.reduction == 'mean':
  265. return loss.mean()
  266. elif self.reduction == 'sum':
  267. return loss.sum()
  268. else: # 'none'
  269. return loss
  270. def smooth_BCE(eps=0.1): # https://github.com/ultralytics/yolov3/issues/238#issuecomment-598028441
  271. # return positive, negative label smoothing BCE targets
  272. return 1.0 - 0.5 * eps, 0.5 * eps
  273. def compute_loss(p, targets, model): # predictions, targets, model
  274. ft = torch.cuda.FloatTensor if p[0].is_cuda else torch.Tensor
  275. lcls, lbox, lobj = ft([0]), ft([0]), ft([0])
  276. tcls, tbox, indices, anchors = build_targets(p, targets, model) # targets
  277. h = model.hyp # hyperparameters
  278. red = 'mean' # Loss reduction (sum or mean)
  279. # Define criteria
  280. BCEcls = nn.BCEWithLogitsLoss(pos_weight=ft([h['cls_pw']]), reduction=red)
  281. BCEobj = nn.BCEWithLogitsLoss(pos_weight=ft([h['obj_pw']]), reduction=red)
  282. # class label smoothing https://arxiv.org/pdf/1902.04103.pdf eqn 3
  283. cp, cn = smooth_BCE(eps=0.0)
  284. # focal loss
  285. g = h['fl_gamma'] # focal loss gamma
  286. if g > 0:
  287. BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g)
  288. # per output
  289. nt = 0 # targets
  290. for i, pi in enumerate(p): # layer index, layer predictions
  291. b, a, gj, gi = indices[i] # image, anchor, gridy, gridx
  292. tobj = torch.zeros_like(pi[..., 0]) # target obj
  293. nb = b.shape[0] # number of targets
  294. if nb:
  295. nt += nb # cumulative targets
  296. ps = pi[b, a, gj, gi] # prediction subset corresponding to targets
  297. # GIoU
  298. pxy = ps[:, :2].sigmoid() * 2. - 0.5
  299. pwh = (ps[:, 2:4].sigmoid() * 2) ** 2 * anchors[i]
  300. pbox = torch.cat((pxy, pwh), 1) # predicted box
  301. giou = bbox_iou(pbox.t(), tbox[i], x1y1x2y2=False, GIoU=True) # giou(prediction, target)
  302. lbox += (1.0 - giou).sum() if red == 'sum' else (1.0 - giou).mean() # giou loss
  303. # Obj
  304. tobj[b, a, gj, gi] = (1.0 - model.gr) + model.gr * giou.detach().clamp(0).type(tobj.dtype) # giou ratio
  305. # Class
  306. if model.nc > 1: # cls loss (only if multiple classes)
  307. t = torch.full_like(ps[:, 5:], cn) # targets
  308. t[range(nb), tcls[i]] = cp
  309. lcls += BCEcls(ps[:, 5:], t) # BCE
  310. # Append targets to text file
  311. # with open('targets.txt', 'a') as file:
  312. # [file.write('%11.5g ' * 4 % tuple(x) + '\n') for x in torch.cat((txy[i], twh[i]), 1)]
  313. lobj += BCEobj(pi[..., 4], tobj) # obj loss
  314. lbox *= h['giou']
  315. lobj *= h['obj']
  316. lcls *= h['cls']
  317. bs = tobj.shape[0] # batch size
  318. if red == 'sum':
  319. g = 3.0 # loss gain
  320. lobj *= g / bs
  321. if nt:
  322. lcls *= g / nt / model.nc
  323. lbox *= g / nt
  324. loss = lbox + lobj + lcls
  325. return loss * bs, torch.cat((lbox, lobj, lcls, loss)).detach()
  326. def build_targets(p, targets, model):
  327. # Build targets for compute_loss(), input targets(image,class,x,y,w,h)
  328. det = model.module.model[-1] if type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel) \
  329. else model.model[-1] # Detect() module
  330. na, nt = det.na, targets.shape[0] # number of anchors, targets
  331. tcls, tbox, indices, anch = [], [], [], []
  332. gain = torch.ones(6, device=targets.device) # normalized to gridspace gain
  333. off = torch.tensor([[1, 0], [0, 1], [-1, 0], [0, -1]], device=targets.device).float() # overlap offsets
  334. at = torch.arange(na).view(na, 1).repeat(1, nt) # anchor tensor, same as .repeat_interleave(nt)
  335. style = 'rect4'
  336. for i in range(det.nl):
  337. anchors = det.anchors[i]
  338. gain[2:] = torch.tensor(p[i].shape)[[3, 2, 3, 2]] # xyxy gain
  339. # Match targets to anchors
  340. a, t, offsets = [], targets * gain, 0
  341. if nt:
  342. r = t[None, :, 4:6] / anchors[:, None] # wh ratio
  343. j = torch.max(r, 1. / r).max(2)[0] < model.hyp['anchor_t'] # compare
  344. # j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t'] # iou(3,n) = wh_iou(anchors(3,2), gwh(n,2))
  345. a, t = at[j], t.repeat(na, 1, 1)[j] # filter
  346. # overlaps
  347. gxy = t[:, 2:4] # grid xy
  348. z = torch.zeros_like(gxy)
  349. if style == 'rect2':
  350. g = 0.2 # offset
  351. j, k = ((gxy % 1. < g) & (gxy > 1.)).T
  352. a, t = torch.cat((a, a[j], a[k]), 0), torch.cat((t, t[j], t[k]), 0)
  353. offsets = torch.cat((z, z[j] + off[0], z[k] + off[1]), 0) * g
  354. elif style == 'rect4':
  355. g = 0.5 # offset
  356. j, k = ((gxy % 1. < g) & (gxy > 1.)).T
  357. l, m = ((gxy % 1. > (1 - g)) & (gxy < (gain[[2, 3]] - 1.))).T
  358. a, t = torch.cat((a, a[j], a[k], a[l], a[m]), 0), torch.cat((t, t[j], t[k], t[l], t[m]), 0)
  359. offsets = torch.cat((z, z[j] + off[0], z[k] + off[1], z[l] + off[2], z[m] + off[3]), 0) * g
  360. # Define
  361. b, c = t[:, :2].long().T # image, class
  362. gxy = t[:, 2:4] # grid xy
  363. gwh = t[:, 4:6] # grid wh
  364. gij = (gxy - offsets).long()
  365. gi, gj = gij.T # grid xy indices
  366. # Append
  367. indices.append((b, a, gj, gi)) # image, anchor, grid indices
  368. tbox.append(torch.cat((gxy - gij, gwh), 1)) # box
  369. anch.append(anchors[a]) # anchors
  370. tcls.append(c) # class
  371. return tcls, tbox, indices, anch
  372. def non_max_suppression(prediction, conf_thres=0.1, iou_thres=0.6, fast=False, classes=None, agnostic=False):
  373. """
  374. Performs Non-Maximum Suppression on inference results
  375. Returns detections with shape:
  376. nx6 (x1, y1, x2, y2, conf, cls)
  377. """
  378. nc = prediction[0].shape[1] - 5 # number of classes
  379. xc = prediction[..., 4] > conf_thres # candidates
  380. # Settings
  381. min_wh, max_wh = 2, 4096 # (pixels) minimum and maximum box width and height
  382. max_det = 300 # maximum number of detections per image
  383. time_limit = 10.0 # seconds to quit after
  384. redundant = True # require redundant detections
  385. fast |= conf_thres > 0.001 # fast mode
  386. if fast:
  387. merge = False
  388. multi_label = False
  389. else:
  390. merge = True # merge for best mAP (adds 0.5ms/img)
  391. multi_label = nc > 1 # multiple labels per box (adds 0.5ms/img)
  392. t = time.time()
  393. output = [None] * prediction.shape[0]
  394. for xi, x in enumerate(prediction): # image index, image inference
  395. # Apply constraints
  396. # x[((x[..., 2:4] < min_wh) | (x[..., 2:4] > max_wh)).any(1), 4] = 0 # width-height
  397. x = x[xc[xi]] # confidence
  398. # If none remain process next image
  399. if not x.shape[0]:
  400. continue
  401. # Compute conf
  402. x[:, 5:] *= x[:, 4:5] # conf = obj_conf * cls_conf
  403. # Box (center x, center y, width, height) to (x1, y1, x2, y2)
  404. box = xywh2xyxy(x[:, :4])
  405. # Detections matrix nx6 (xyxy, conf, cls)
  406. if multi_label:
  407. i, j = (x[:, 5:] > conf_thres).nonzero().t()
  408. x = torch.cat((box[i], x[i, j + 5, None], j[:, None].float()), 1)
  409. else: # best class only
  410. conf, j = x[:, 5:].max(1, keepdim=True)
  411. x = torch.cat((box, conf, j.float()), 1)[conf.view(-1) > conf_thres]
  412. # Filter by class
  413. if classes:
  414. x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]
  415. # Apply finite constraint
  416. # if not torch.isfinite(x).all():
  417. # x = x[torch.isfinite(x).all(1)]
  418. # If none remain process next image
  419. n = x.shape[0] # number of boxes
  420. if not n:
  421. continue
  422. # Sort by confidence
  423. # x = x[x[:, 4].argsort(descending=True)]
  424. # Batched NMS
  425. c = x[:, 5:6] * (0 if agnostic else max_wh) # classes
  426. boxes, scores = x[:, :4] + c, x[:, 4] # boxes (offset by class), scores
  427. i = torchvision.ops.boxes.nms(boxes, scores, iou_thres)
  428. if i.shape[0] > max_det: # limit detections
  429. i = i[:max_det]
  430. if merge and (1 < n < 3E3): # Merge NMS (boxes merged using weighted mean)
  431. try: # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)
  432. iou = box_iou(boxes[i], boxes) > iou_thres # iou matrix
  433. weights = iou * scores[None] # box weights
  434. x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True) # merged boxes
  435. if redundant:
  436. i = i[iou.sum(1) > 1] # require redundancy
  437. except: # possible CUDA error https://github.com/ultralytics/yolov3/issues/1139
  438. print(x, i, x.shape, i.shape)
  439. pass
  440. output[xi] = x[i]
  441. if (time.time() - t) > time_limit:
  442. break # time limit exceeded
  443. return output
  444. def strip_optimizer(f='weights/best.pt'): # from utils.utils import *; strip_optimizer()
  445. # Strip optimizer from *.pt files for lighter files (reduced by 1/2 size)
  446. x = torch.load(f, map_location=torch.device('cpu'))
  447. x['optimizer'] = None
  448. torch.save(x, f)
  449. print('Optimizer stripped from %s' % f)
  450. def create_backbone(f='weights/best.pt', s='weights/backbone.pt'): # from utils.utils import *; create_backbone()
  451. # create backbone 's' from 'f'
  452. device = torch.device('cpu')
  453. x = torch.load(f, map_location=device)
  454. torch.save(x, s) # update model if SourceChangeWarning
  455. x = torch.load(s, map_location=device)
  456. x['optimizer'] = None
  457. x['training_results'] = None
  458. x['epoch'] = -1
  459. for p in x['model'].parameters():
  460. p.requires_grad = True
  461. torch.save(x, s)
  462. print('%s modified for backbone use and saved as %s' % (f, s))
  463. def coco_class_count(path='../coco/labels/train2014/'):
  464. # Histogram of occurrences per class
  465. nc = 80 # number classes
  466. x = np.zeros(nc, dtype='int32')
  467. files = sorted(glob.glob('%s/*.*' % path))
  468. for i, file in enumerate(files):
  469. labels = np.loadtxt(file, dtype=np.float32).reshape(-1, 5)
  470. x += np.bincount(labels[:, 0].astype('int32'), minlength=nc)
  471. print(i, len(files))
  472. def coco_only_people(path='../coco/labels/train2017/'): # from utils.utils import *; coco_only_people()
  473. # Find images with only people
  474. files = sorted(glob.glob('%s/*.*' % path))
  475. for i, file in enumerate(files):
  476. labels = np.loadtxt(file, dtype=np.float32).reshape(-1, 5)
  477. if all(labels[:, 0] == 0):
  478. print(labels.shape[0], file)
  479. def crop_images_random(path='../images/', scale=0.50): # from utils.utils import *; crop_images_random()
  480. # crops images into random squares up to scale fraction
  481. # WARNING: overwrites images!
  482. for file in tqdm(sorted(glob.glob('%s/*.*' % path))):
  483. img = cv2.imread(file) # BGR
  484. if img is not None:
  485. h, w = img.shape[:2]
  486. # create random mask
  487. a = 30 # minimum size (pixels)
  488. mask_h = random.randint(a, int(max(a, h * scale))) # mask height
  489. mask_w = mask_h # mask width
  490. # box
  491. xmin = max(0, random.randint(0, w) - mask_w // 2)
  492. ymin = max(0, random.randint(0, h) - mask_h // 2)
  493. xmax = min(w, xmin + mask_w)
  494. ymax = min(h, ymin + mask_h)
  495. # apply random color mask
  496. cv2.imwrite(file, img[ymin:ymax, xmin:xmax])
  497. def coco_single_class_labels(path='../coco/labels/train2014/', label_class=43):
  498. # Makes single-class coco datasets. from utils.utils import *; coco_single_class_labels()
  499. if os.path.exists('new/'):
  500. shutil.rmtree('new/') # delete output folder
  501. os.makedirs('new/') # make new output folder
  502. os.makedirs('new/labels/')
  503. os.makedirs('new/images/')
  504. for file in tqdm(sorted(glob.glob('%s/*.*' % path))):
  505. with open(file, 'r') as f:
  506. labels = np.array([x.split() for x in f.read().splitlines()], dtype=np.float32)
  507. i = labels[:, 0] == label_class
  508. if any(i):
  509. img_file = file.replace('labels', 'images').replace('txt', 'jpg')
  510. labels[:, 0] = 0 # reset class to 0
  511. with open('new/images.txt', 'a') as f: # add image to dataset list
  512. f.write(img_file + '\n')
  513. with open('new/labels/' + Path(file).name, 'a') as f: # write label
  514. for l in labels[i]:
  515. f.write('%g %.6f %.6f %.6f %.6f\n' % tuple(l))
  516. shutil.copyfile(src=img_file, dst='new/images/' + Path(file).name.replace('txt', 'jpg')) # copy images
  517. def kmean_anchors(path='./data/coco128.txt', n=9, img_size=(640, 640), thr=0.20, gen=1000):
  518. # Creates kmeans anchors for use in *.cfg files: from utils.utils import *; _ = kmean_anchors()
  519. # n: number of anchors
  520. # img_size: (min, max) image size used for multi-scale training (can be same values)
  521. # thr: IoU threshold hyperparameter used for training (0.0 - 1.0)
  522. # gen: generations to evolve anchors using genetic algorithm
  523. from utils.datasets import LoadImagesAndLabels
  524. def print_results(k):
  525. k = k[np.argsort(k.prod(1))] # sort small to large
  526. iou = wh_iou(wh, torch.Tensor(k))
  527. max_iou = iou.max(1)[0]
  528. bpr, aat = (max_iou > thr).float().mean(), (iou > thr).float().mean() * n # best possible recall, anch > thr
  529. # thr = 5.0
  530. # r = wh[:, None] / k[None]
  531. # ar = torch.max(r, 1. / r).max(2)[0]
  532. # max_ar = ar.min(1)[0]
  533. # bpr, aat = (max_ar < thr).float().mean(), (ar < thr).float().mean() * n # best possible recall, anch > thr
  534. print('%.2f iou_thr: %.3f best possible recall, %.2f anchors > thr' % (thr, bpr, aat))
  535. print('n=%g, img_size=%s, IoU_all=%.3f/%.3f-mean/best, IoU>thr=%.3f-mean: ' %
  536. (n, img_size, iou.mean(), max_iou.mean(), iou[iou > thr].mean()), end='')
  537. for i, x in enumerate(k):
  538. print('%i,%i' % (round(x[0]), round(x[1])), end=', ' if i < len(k) - 1 else '\n') # use in *.cfg
  539. return k
  540. def fitness(k): # mutation fitness
  541. iou = wh_iou(wh, torch.Tensor(k)) # iou
  542. max_iou = iou.max(1)[0]
  543. return (max_iou * (max_iou > thr).float()).mean() # product
  544. # def fitness_ratio(k): # mutation fitness
  545. # # wh(5316,2), k(9,2)
  546. # r = wh[:, None] / k[None]
  547. # x = torch.max(r, 1. / r).max(2)[0]
  548. # m = x.min(1)[0]
  549. # return 1. / (m * (m < 5).float()).mean() # product
  550. # Get label wh
  551. wh = []
  552. dataset = LoadImagesAndLabels(path, augment=True, rect=True)
  553. nr = 1 if img_size[0] == img_size[1] else 3 # number augmentation repetitions
  554. for s, l in zip(dataset.shapes, dataset.labels):
  555. # wh.append(l[:, 3:5] * (s / s.max())) # image normalized to letterbox normalized wh
  556. wh.append(l[:, 3:5] * s) # image normalized to pixels
  557. wh = np.concatenate(wh, 0).repeat(nr, axis=0) # augment 3x
  558. # wh *= np.random.uniform(img_size[0], img_size[1], size=(wh.shape[0], 1)) # normalized to pixels (multi-scale)
  559. wh = wh[(wh > 2.0).all(1)] # remove below threshold boxes (< 2 pixels wh)
  560. # Kmeans calculation
  561. from scipy.cluster.vq import kmeans
  562. print('Running kmeans for %g anchors on %g points...' % (n, len(wh)))
  563. s = wh.std(0) # sigmas for whitening
  564. k, dist = kmeans(wh / s, n, iter=30) # points, mean distance
  565. k *= s
  566. wh = torch.Tensor(wh)
  567. k = print_results(k)
  568. # # Plot
  569. # k, d = [None] * 20, [None] * 20
  570. # for i in tqdm(range(1, 21)):
  571. # k[i-1], d[i-1] = kmeans(wh / s, i) # points, mean distance
  572. # fig, ax = plt.subplots(1, 2, figsize=(14, 7))
  573. # ax = ax.ravel()
  574. # ax[0].plot(np.arange(1, 21), np.array(d) ** 2, marker='.')
  575. # fig, ax = plt.subplots(1, 2, figsize=(14, 7)) # plot wh
  576. # ax[0].hist(wh[wh[:, 0]<100, 0],400)
  577. # ax[1].hist(wh[wh[:, 1]<100, 1],400)
  578. # fig.tight_layout()
  579. # fig.savefig('wh.png', dpi=200)
  580. # Evolve
  581. npr = np.random
  582. f, sh, mp, s = fitness(k), k.shape, 0.9, 0.1 # fitness, generations, mutation prob, sigma
  583. for _ in tqdm(range(gen), desc='Evolving anchors'):
  584. v = np.ones(sh)
  585. while (v == 1).all(): # mutate until a change occurs (prevent duplicates)
  586. v = ((npr.random(sh) < mp) * npr.random() * npr.randn(*sh) * s + 1).clip(0.3, 3.0)
  587. kg = (k.copy() * v).clip(min=2.0)
  588. fg = fitness(kg)
  589. if fg > f:
  590. f, k = fg, kg.copy()
  591. print_results(k)
  592. k = print_results(k)
  593. return k
  594. def print_mutation(hyp, results, bucket=''):
  595. # Print mutation results to evolve.txt (for use with train.py --evolve)
  596. a = '%10s' * len(hyp) % tuple(hyp.keys()) # hyperparam keys
  597. b = '%10.3g' * len(hyp) % tuple(hyp.values()) # hyperparam values
  598. c = '%10.4g' * len(results) % results # results (P, R, mAP, F1, test_loss)
  599. print('\n%s\n%s\nEvolved fitness: %s\n' % (a, b, c))
  600. if bucket:
  601. os.system('gsutil cp gs://%s/evolve.txt .' % bucket) # download evolve.txt
  602. with open('evolve.txt', 'a') as f: # append result
  603. f.write(c + b + '\n')
  604. x = np.unique(np.loadtxt('evolve.txt', ndmin=2), axis=0) # load unique rows
  605. np.savetxt('evolve.txt', x[np.argsort(-fitness(x))], '%10.3g') # save sort by fitness
  606. if bucket:
  607. os.system('gsutil cp evolve.txt gs://%s' % bucket) # upload evolve.txt
  608. def apply_classifier(x, model, img, im0):
  609. # applies a second stage classifier to yolo outputs
  610. im0 = [im0] if isinstance(im0, np.ndarray) else im0
  611. for i, d in enumerate(x): # per image
  612. if d is not None and len(d):
  613. d = d.clone()
  614. # Reshape and pad cutouts
  615. b = xyxy2xywh(d[:, :4]) # boxes
  616. b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1) # rectangle to square
  617. b[:, 2:] = b[:, 2:] * 1.3 + 30 # pad
  618. d[:, :4] = xywh2xyxy(b).long()
  619. # Rescale boxes from img_size to im0 size
  620. scale_coords(img.shape[2:], d[:, :4], im0[i].shape)
  621. # Classes
  622. pred_cls1 = d[:, 5].long()
  623. ims = []
  624. for j, a in enumerate(d): # per item
  625. cutout = im0[i][int(a[1]):int(a[3]), int(a[0]):int(a[2])]
  626. im = cv2.resize(cutout, (224, 224)) # BGR
  627. # cv2.imwrite('test%i.jpg' % j, cutout)
  628. im = im[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, to 3x416x416
  629. im = np.ascontiguousarray(im, dtype=np.float32) # uint8 to float32
  630. im /= 255.0 # 0 - 255 to 0.0 - 1.0
  631. ims.append(im)
  632. pred_cls2 = model(torch.Tensor(ims).to(d.device)).argmax(1) # classifier prediction
  633. x[i] = x[i][pred_cls1 == pred_cls2] # retain matching class detections
  634. return x
  635. def fitness(x):
  636. # Returns fitness (for use with results.txt or evolve.txt)
  637. w = [0.0, 0.0, 0.1, 0.9] # weights for [P, R, mAP@0.5, mAP@0.5:0.95]
  638. return (x[:, :4] * w).sum(1)
  639. def output_to_target(output, width, height):
  640. """
  641. Convert a YOLO model output to target format
  642. [batch_id, class_id, x, y, w, h, conf]
  643. """
  644. if isinstance(output, torch.Tensor):
  645. output = output.cpu().numpy()
  646. targets = []
  647. for i, o in enumerate(output):
  648. if o is not None:
  649. for pred in o:
  650. box = pred[:4]
  651. w = (box[2] - box[0]) / width
  652. h = (box[3] - box[1]) / height
  653. x = box[0] / width + w / 2
  654. y = box[1] / height + h / 2
  655. conf = pred[4]
  656. cls = int(pred[5])
  657. targets.append([i, cls, x, y, w, h, conf])
  658. return np.array(targets)
  659. # Plotting functions ---------------------------------------------------------------------------------------------------
  660. def butter_lowpass_filtfilt(data, cutoff=1500, fs=50000, order=5):
  661. # https://stackoverflow.com/questions/28536191/how-to-filter-smooth-with-scipy-numpy
  662. def butter_lowpass(cutoff, fs, order):
  663. nyq = 0.5 * fs
  664. normal_cutoff = cutoff / nyq
  665. b, a = butter(order, normal_cutoff, btype='low', analog=False)
  666. return b, a
  667. b, a = butter_lowpass(cutoff, fs, order=order)
  668. return filtfilt(b, a, data) # forward-backward filter
  669. def plot_one_box(x, img, color=None, label=None, line_thickness=None):
  670. # Plots one bounding box on image img
  671. tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1 # line/font thickness
  672. color = color or [random.randint(0, 255) for _ in range(3)]
  673. c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
  674. cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
  675. if label:
  676. tf = max(tl - 1, 1) # font thickness
  677. t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
  678. c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
  679. cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA) # filled
  680. cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
  681. def plot_wh_methods(): # from utils.utils import *; plot_wh_methods()
  682. # Compares the two methods for width-height anchor multiplication
  683. # https://github.com/ultralytics/yolov3/issues/168
  684. x = np.arange(-4.0, 4.0, .1)
  685. ya = np.exp(x)
  686. yb = torch.sigmoid(torch.from_numpy(x)).numpy() * 2
  687. fig = plt.figure(figsize=(6, 3), dpi=150)
  688. plt.plot(x, ya, '.-', label='yolo method')
  689. plt.plot(x, yb ** 2, '.-', label='^2 power method')
  690. plt.plot(x, yb ** 2.5, '.-', label='^2.5 power method')
  691. plt.xlim(left=-4, right=4)
  692. plt.ylim(bottom=0, top=6)
  693. plt.xlabel('input')
  694. plt.ylabel('output')
  695. plt.legend()
  696. fig.tight_layout()
  697. fig.savefig('comparison.png', dpi=200)
  698. def plot_images(images, targets, paths=None, fname='images.jpg', names=None, max_size=640, max_subplots=16):
  699. tl = 3 # line thickness
  700. tf = max(tl - 1, 1) # font thickness
  701. if os.path.isfile(fname): # do not overwrite
  702. return None
  703. if isinstance(images, torch.Tensor):
  704. images = images.cpu().numpy()
  705. if isinstance(targets, torch.Tensor):
  706. targets = targets.cpu().numpy()
  707. # un-normalise
  708. if np.max(images[0]) <= 1:
  709. images *= 255
  710. bs, _, h, w = images.shape # batch size, _, height, width
  711. bs = min(bs, max_subplots) # limit plot images
  712. ns = np.ceil(bs ** 0.5) # number of subplots (square)
  713. # Check if we should resize
  714. scale_factor = max_size / max(h, w)
  715. if scale_factor < 1:
  716. h = math.ceil(scale_factor * h)
  717. w = math.ceil(scale_factor * w)
  718. # Empty array for output
  719. mosaic = np.full((int(ns * h), int(ns * w), 3), 255, dtype=np.uint8)
  720. # Fix class - colour map
  721. prop_cycle = plt.rcParams['axes.prop_cycle']
  722. # https://stackoverflow.com/questions/51350872/python-from-color-name-to-rgb
  723. hex2rgb = lambda h: tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4))
  724. color_lut = [hex2rgb(h) for h in prop_cycle.by_key()['color']]
  725. for i, img in enumerate(images):
  726. if i == max_subplots: # if last batch has fewer images than we expect
  727. break
  728. block_x = int(w * (i // ns))
  729. block_y = int(h * (i % ns))
  730. img = img.transpose(1, 2, 0)
  731. if scale_factor < 1:
  732. img = cv2.resize(img, (w, h))
  733. mosaic[block_y:block_y + h, block_x:block_x + w, :] = img
  734. if len(targets) > 0:
  735. image_targets = targets[targets[:, 0] == i]
  736. boxes = xywh2xyxy(image_targets[:, 2:6]).T
  737. classes = image_targets[:, 1].astype('int')
  738. gt = image_targets.shape[1] == 6 # ground truth if no conf column
  739. conf = None if gt else image_targets[:, 6] # check for confidence presence (gt vs pred)
  740. boxes[[0, 2]] *= w
  741. boxes[[0, 2]] += block_x
  742. boxes[[1, 3]] *= h
  743. boxes[[1, 3]] += block_y
  744. for j, box in enumerate(boxes.T):
  745. cls = int(classes[j])
  746. color = color_lut[cls % len(color_lut)]
  747. cls = names[cls] if names else cls
  748. if gt or conf[j] > 0.3: # 0.3 conf thresh
  749. label = '%s' % cls if gt else '%s %.1f' % (cls, conf[j])
  750. plot_one_box(box, mosaic, label=label, color=color, line_thickness=tl)
  751. # Draw image filename labels
  752. if paths is not None:
  753. label = os.path.basename(paths[i])[:40] # trim to 40 char
  754. t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
  755. cv2.putText(mosaic, label, (block_x + 5, block_y + t_size[1] + 5), 0, tl / 3, [220, 220, 220], thickness=tf,
  756. lineType=cv2.LINE_AA)
  757. # Image border
  758. cv2.rectangle(mosaic, (block_x, block_y), (block_x + w, block_y + h), (255, 255, 255), thickness=3)
  759. if fname is not None:
  760. mosaic = cv2.resize(mosaic, (int(ns * w * 0.5), int(ns * h * 0.5)), interpolation=cv2.INTER_AREA)
  761. cv2.imwrite(fname, cv2.cvtColor(mosaic, cv2.COLOR_BGR2RGB))
  762. return mosaic
  763. def plot_lr_scheduler(optimizer, scheduler, epochs=300):
  764. # Plot LR simulating training for full epochs
  765. optimizer, scheduler = copy(optimizer), copy(scheduler) # do not modify originals
  766. y = []
  767. for _ in range(epochs):
  768. scheduler.step()
  769. y.append(optimizer.param_groups[0]['lr'])
  770. plt.plot(y, '.-', label='LR')
  771. plt.xlabel('epoch')
  772. plt.ylabel('LR')
  773. plt.grid()
  774. plt.xlim(0, epochs)
  775. plt.ylim(0)
  776. plt.tight_layout()
  777. plt.savefig('LR.png', dpi=200)
  778. def plot_test_txt(): # from utils.utils import *; plot_test()
  779. # Plot test.txt histograms
  780. x = np.loadtxt('test.txt', dtype=np.float32)
  781. box = xyxy2xywh(x[:, :4])
  782. cx, cy = box[:, 0], box[:, 1]
  783. fig, ax = plt.subplots(1, 1, figsize=(6, 6), tight_layout=True)
  784. ax.hist2d(cx, cy, bins=600, cmax=10, cmin=0)
  785. ax.set_aspect('equal')
  786. plt.savefig('hist2d.png', dpi=300)
  787. fig, ax = plt.subplots(1, 2, figsize=(12, 6), tight_layout=True)
  788. ax[0].hist(cx, bins=600)
  789. ax[1].hist(cy, bins=600)
  790. plt.savefig('hist1d.png', dpi=200)
  791. def plot_targets_txt(): # from utils.utils import *; plot_targets_txt()
  792. # Plot targets.txt histograms
  793. x = np.loadtxt('targets.txt', dtype=np.float32).T
  794. s = ['x targets', 'y targets', 'width targets', 'height targets']
  795. fig, ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True)
  796. ax = ax.ravel()
  797. for i in range(4):
  798. ax[i].hist(x[i], bins=100, label='%.3g +/- %.3g' % (x[i].mean(), x[i].std()))
  799. ax[i].legend()
  800. ax[i].set_title(s[i])
  801. plt.savefig('targets.jpg', dpi=200)
  802. def plot_study_txt(f='study.txt', x=None): # from utils.utils import *; plot_study_txt()
  803. # Plot study.txt generated by test.py
  804. fig, ax = plt.subplots(2, 4, figsize=(10, 6), tight_layout=True)
  805. ax = ax.ravel()
  806. fig2, ax2 = plt.subplots(1, 1, figsize=(8, 4), tight_layout=True)
  807. ax2.plot(1E3 / np.array([209, 140, 97, 58, 35, 18][:-1]), [33.5, 39.1, 42.5, 45.9, 49., 50.5][:-1],
  808. '.-', linewidth=2, markersize=8, alpha=0.3, label='EfficientDet')
  809. for f in sorted(glob.glob('study*.txt')):
  810. y = np.loadtxt(f, dtype=np.float32, usecols=[0, 1, 2, 3, 7, 8, 9], ndmin=2).T
  811. x = np.arange(y.shape[1]) if x is None else np.array(x)
  812. s = ['P', 'R', 'mAP@.5', 'mAP@.5:.95', 't_inference (ms/img)', 't_NMS (ms/img)', 't_total (ms/img)']
  813. for i in range(7):
  814. ax[i].plot(x, y[i], '.-', linewidth=2, markersize=8)
  815. ax[i].set_title(s[i])
  816. j = y[3].argmax() + 1
  817. ax2.plot(y[6, :j], y[3, :j] * 1E2, '.-', linewidth=2, markersize=8,
  818. label=Path(f).stem.replace('study_coco_', '').replace('yolo', 'YOLO'))
  819. ax2.set_xlim(0)
  820. ax2.set_ylim(23, 50)
  821. ax2.set_xlabel('GPU Latency (ms)')
  822. ax2.set_ylabel('COCO AP val')
  823. ax2.legend(loc='lower right')
  824. ax2.grid()
  825. plt.savefig('study_mAP_latency.png', dpi=300)
  826. plt.savefig(f.replace('.txt', '.png'), dpi=200)
  827. def plot_labels(labels):
  828. # plot dataset labels
  829. c, b = labels[:, 0], labels[:, 1:].transpose() # classees, boxes
  830. def hist2d(x, y, n=100):
  831. xedges, yedges = np.linspace(x.min(), x.max(), n), np.linspace(y.min(), y.max(), n)
  832. hist, xedges, yedges = np.histogram2d(x, y, (xedges, yedges))
  833. xidx = np.clip(np.digitize(x, xedges) - 1, 0, hist.shape[0] - 1)
  834. yidx = np.clip(np.digitize(y, yedges) - 1, 0, hist.shape[1] - 1)
  835. return np.log(hist[xidx, yidx])
  836. fig, ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True)
  837. ax = ax.ravel()
  838. ax[0].hist(c, bins=int(c.max() + 1))
  839. ax[0].set_xlabel('classes')
  840. ax[1].scatter(b[0], b[1], c=hist2d(b[0], b[1], 90), cmap='jet')
  841. ax[1].set_xlabel('x')
  842. ax[1].set_ylabel('y')
  843. ax[2].scatter(b[2], b[3], c=hist2d(b[2], b[3], 90), cmap='jet')
  844. ax[2].set_xlabel('width')
  845. ax[2].set_ylabel('height')
  846. plt.savefig('labels.png', dpi=200)
  847. def plot_evolution_results(hyp): # from utils.utils import *; plot_evolution_results(hyp)
  848. # Plot hyperparameter evolution results in evolve.txt
  849. x = np.loadtxt('evolve.txt', ndmin=2)
  850. f = fitness(x)
  851. # weights = (f - f.min()) ** 2 # for weighted results
  852. plt.figure(figsize=(12, 10), tight_layout=True)
  853. matplotlib.rc('font', **{'size': 8})
  854. for i, (k, v) in enumerate(hyp.items()):
  855. y = x[:, i + 7]
  856. # mu = (y * weights).sum() / weights.sum() # best weighted result
  857. mu = y[f.argmax()] # best single result
  858. plt.subplot(4, 5, i + 1)
  859. plt.plot(mu, f.max(), 'o', markersize=10)
  860. plt.plot(y, f, '.')
  861. plt.title('%s = %.3g' % (k, mu), fontdict={'size': 9}) # limit to 40 characters
  862. print('%15s: %.3g' % (k, mu))
  863. plt.savefig('evolve.png', dpi=200)
  864. def plot_results_overlay(start=0, stop=0): # from utils.utils import *; plot_results_overlay()
  865. # Plot training 'results*.txt', overlaying train and val losses
  866. s = ['train', 'train', 'train', 'Precision', 'mAP@0.5', 'val', 'val', 'val', 'Recall', 'mAP@0.5:0.95'] # legends
  867. t = ['GIoU', 'Objectness', 'Classification', 'P-R', 'mAP-F1'] # titles
  868. for f in sorted(glob.glob('results*.txt') + glob.glob('../../Downloads/results*.txt')):
  869. results = np.loadtxt(f, usecols=[2, 3, 4, 8, 9, 12, 13, 14, 10, 11], ndmin=2).T
  870. n = results.shape[1] # number of rows
  871. x = range(start, min(stop, n) if stop else n)
  872. fig, ax = plt.subplots(1, 5, figsize=(14, 3.5), tight_layout=True)
  873. ax = ax.ravel()
  874. for i in range(5):
  875. for j in [i, i + 5]:
  876. y = results[j, x]
  877. ax[i].plot(x, y, marker='.', label=s[j])
  878. # y_smooth = butter_lowpass_filtfilt(y)
  879. # ax[i].plot(x, np.gradient(y_smooth), marker='.', label=s[j])
  880. ax[i].set_title(t[i])
  881. ax[i].legend()
  882. ax[i].set_ylabel(f) if i == 0 else None # add filename
  883. fig.savefig(f.replace('.txt', '.png'), dpi=200)
  884. def plot_results(start=0, stop=0, bucket='', id=()): # from utils.utils import *; plot_results()
  885. # Plot training 'results*.txt' as seen in https://github.com/ultralytics/yolov3#training
  886. fig, ax = plt.subplots(2, 5, figsize=(12, 6))
  887. ax = ax.ravel()
  888. s = ['GIoU', 'Objectness', 'Classification', 'Precision', 'Recall',
  889. 'val GIoU', 'val Objectness', 'val Classification', 'mAP@0.5', 'mAP@0.5:0.95']
  890. if bucket:
  891. os.system('rm -rf storage.googleapis.com')
  892. files = ['https://storage.googleapis.com/%s/results%g.txt' % (bucket, x) for x in id]
  893. else:
  894. files = glob.glob('results*.txt') + glob.glob('../../Downloads/results*.txt')
  895. for f in sorted(files):
  896. try:
  897. results = np.loadtxt(f, usecols=[2, 3, 4, 8, 9, 12, 13, 14, 10, 11], ndmin=2).T
  898. n = results.shape[1] # number of rows
  899. x = range(start, min(stop, n) if stop else n)
  900. for i in range(10):
  901. y = results[i, x]
  902. if i in [0, 1, 2, 5, 6, 7]:
  903. y[y == 0] = np.nan # dont show zero loss values
  904. # y /= y[0] # normalize
  905. ax[i].plot(x, y, marker='.', label=Path(f).stem, linewidth=2, markersize=8)
  906. ax[i].set_title(s[i])
  907. # if i in [5, 6, 7]: # share train and val loss y axes
  908. # ax[i].get_shared_y_axes().join(ax[i], ax[i - 5])
  909. except:
  910. print('Warning: Plotting error for %s, skipping file' % f)
  911. fig.tight_layout()
  912. ax[1].legend()
  913. fig.savefig('results.png', dpi=200)