general.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. import glob
  2. import logging
  3. import math
  4. import os
  5. import random
  6. import re
  7. import subprocess
  8. import time
  9. from pathlib import Path
  10. import cv2
  11. import numpy as np
  12. import torch
  13. import torchvision
  14. import yaml
  15. from utils.google_utils import gsutil_getsize
  16. from utils.metrics import fitness
  17. from utils.torch_utils import init_torch_seeds
  18. # Settings
  19. torch.set_printoptions(linewidth=320, precision=5, profile='long')
  20. np.set_printoptions(linewidth=320, formatter={'float_kind': '{:11.5g}'.format}) # format short g, %precision=5
  21. cv2.setNumThreads(0) # prevent OpenCV from multithreading (incompatible with PyTorch DataLoader)
  22. os.environ['NUMEXPR_MAX_THREADS'] = str(min(os.cpu_count(), 8)) # NumExpr max threads
  23. def set_logging(rank=-1):
  24. logging.basicConfig(
  25. format="%(message)s",
  26. level=logging.INFO if rank in [-1, 0] else logging.WARN)
  27. def init_seeds(seed=0):
  28. # Initialize random number generator (RNG) seeds
  29. random.seed(seed)
  30. np.random.seed(seed)
  31. init_torch_seeds(seed)
  32. def get_latest_run(search_dir='.'):
  33. # Return path to most recent 'last.pt' in /runs (i.e. to --resume from)
  34. last_list = glob.glob(f'{search_dir}/**/last*.pt', recursive=True)
  35. return max(last_list, key=os.path.getctime) if last_list else ''
  36. def check_online():
  37. # Check internet connectivity
  38. import socket
  39. try:
  40. socket.create_connection(("1.1.1.1", 53)) # check host accesability
  41. return True
  42. except OSError:
  43. return False
  44. def check_git_status():
  45. # Recommend 'git pull' if code is out of date
  46. print(colorstr('github: '), end='')
  47. try:
  48. assert Path('.git').exists(), 'skipping check (not a git repository)'
  49. assert not Path('/workspace').exists(), 'skipping check (Docker image)' # not Path('/.dockerenv').exists()
  50. assert check_online(), 'skipping check (offline)'
  51. cmd = 'git fetch && git config --get remote.origin.url' # github repo url
  52. url = subprocess.check_output(cmd, shell=True).decode()[:-1]
  53. cmd = 'git rev-list $(git rev-parse --abbrev-ref HEAD)..origin/master --count' # commits behind
  54. n = int(subprocess.check_output(cmd, shell=True))
  55. if n > 0:
  56. print(f"⚠️ WARNING: code is out of date by {n} {'commits' if n > 1 else 'commmit'}. "
  57. f"Use 'git pull' to update or 'git clone {url}' to download latest.")
  58. else:
  59. print(f'up to date with {url} ✅')
  60. except Exception as e:
  61. print(e)
  62. def check_requirements(file='requirements.txt'):
  63. # Check installed dependencies meet requirements
  64. import pkg_resources
  65. requirements = pkg_resources.parse_requirements(Path(file).open())
  66. requirements = [x.name + ''.join(*x.specs) if len(x.specs) else x.name for x in requirements]
  67. pkg_resources.require(requirements) # DistributionNotFound or VersionConflict exception if requirements not met
  68. def check_img_size(img_size, s=32):
  69. # Verify img_size is a multiple of stride s
  70. new_size = make_divisible(img_size, int(s)) # ceil gs-multiple
  71. if new_size != img_size:
  72. print('WARNING: --img-size %g must be multiple of max stride %g, updating to %g' % (img_size, s, new_size))
  73. return new_size
  74. def check_file(file):
  75. # Search for file if not found
  76. if os.path.isfile(file) or file == '':
  77. return file
  78. else:
  79. files = glob.glob('./**/' + file, recursive=True) # find file
  80. assert len(files), 'File Not Found: %s' % file # assert file was found
  81. assert len(files) == 1, "Multiple files match '%s', specify exact path: %s" % (file, files) # assert unique
  82. return files[0] # return file
  83. def check_dataset(dict):
  84. # Download dataset if not found locally
  85. val, s = dict.get('val'), dict.get('download')
  86. if val and len(val):
  87. val = [Path(x).resolve() for x in (val if isinstance(val, list) else [val])] # val path
  88. if not all(x.exists() for x in val):
  89. print('\nWARNING: Dataset not found, nonexistent paths: %s' % [str(x) for x in val if not x.exists()])
  90. if s and len(s): # download script
  91. print('Downloading %s ...' % s)
  92. if s.startswith('http') and s.endswith('.zip'): # URL
  93. f = Path(s).name # filename
  94. torch.hub.download_url_to_file(s, f)
  95. r = os.system('unzip -q %s -d ../ && rm %s' % (f, f)) # unzip
  96. else: # bash script
  97. r = os.system(s)
  98. print('Dataset autodownload %s\n' % ('success' if r == 0 else 'failure')) # analyze return value
  99. else:
  100. raise Exception('Dataset not found.')
  101. def make_divisible(x, divisor):
  102. # Returns x evenly divisible by divisor
  103. return math.ceil(x / divisor) * divisor
  104. def clean_str(s):
  105. # Cleans a string by replacing special characters with underscore _
  106. return re.sub(pattern="[|@#!¡·$€%&()=?¿^*;:,¨´><+]", repl="_", string=s)
  107. def one_cycle(y1=0.0, y2=1.0, steps=100):
  108. # lambda function for sinusoidal ramp from y1 to y2
  109. return lambda x: ((1 - math.cos(x * math.pi / steps)) / 2) * (y2 - y1) + y1
  110. def colorstr(*input):
  111. # Colors a string https://en.wikipedia.org/wiki/ANSI_escape_code, i.e. colorstr('blue', 'hello world')
  112. *args, string = input if len(input) > 1 else ('blue', 'bold', input[0]) # color arguments, string
  113. colors = {'black': '\033[30m', # basic colors
  114. 'red': '\033[31m',
  115. 'green': '\033[32m',
  116. 'yellow': '\033[33m',
  117. 'blue': '\033[34m',
  118. 'magenta': '\033[35m',
  119. 'cyan': '\033[36m',
  120. 'white': '\033[37m',
  121. 'bright_black': '\033[90m', # bright colors
  122. 'bright_red': '\033[91m',
  123. 'bright_green': '\033[92m',
  124. 'bright_yellow': '\033[93m',
  125. 'bright_blue': '\033[94m',
  126. 'bright_magenta': '\033[95m',
  127. 'bright_cyan': '\033[96m',
  128. 'bright_white': '\033[97m',
  129. 'end': '\033[0m', # misc
  130. 'bold': '\033[1m',
  131. 'underline': '\033[4m'}
  132. return ''.join(colors[x] for x in args) + f'{string}' + colors['end']
  133. def labels_to_class_weights(labels, nc=80):
  134. # Get class weights (inverse frequency) from training labels
  135. if labels[0] is None: # no labels loaded
  136. return torch.Tensor()
  137. labels = np.concatenate(labels, 0) # labels.shape = (866643, 5) for COCO
  138. classes = labels[:, 0].astype(np.int_) # labels = [class xywh]
  139. weights = np.bincount(classes, minlength=nc) # occurrences per class
  140. # Prepend gridpoint count (for uCE training)
  141. # gpi = ((320 / 32 * np.array([1, 2, 4])) ** 2 * 3).sum() # gridpoints per image
  142. # weights = np.hstack([gpi * len(labels) - weights.sum() * 9, weights * 9]) ** 0.5 # prepend gridpoints to start
  143. weights[weights == 0] = 1 # replace empty bins with 1
  144. weights = 1 / weights # number of targets per class
  145. weights /= weights.sum() # normalize
  146. return torch.from_numpy(weights)
  147. def labels_to_image_weights(labels, nc=80, class_weights=np.ones(80)):
  148. # Produces image weights based on class_weights and image contents
  149. class_counts = np.array([np.bincount(x[:, 0].astype(np.int_), minlength=nc) for x in labels])
  150. image_weights = (class_weights.reshape(1, nc) * class_counts).sum(1)
  151. # index = random.choices(range(n), weights=image_weights, k=1) # weight image sample
  152. return image_weights
  153. def coco80_to_coco91_class(): # converts 80-index (val2014) to 91-index (paper)
  154. # https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/
  155. # a = np.loadtxt('data/coco.names', dtype='str', delimiter='\n')
  156. # b = np.loadtxt('data/coco_paper.names', dtype='str', delimiter='\n')
  157. # x1 = [list(a[i] == b).index(True) + 1 for i in range(80)] # darknet to coco
  158. # x2 = [list(b[i] == a).index(True) if any(b[i] == a) else None for i in range(91)] # coco to darknet
  159. 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,
  160. 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,
  161. 64, 65, 67, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90]
  162. return x
  163. def xyxy2xywh(x):
  164. # Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] where xy1=top-left, xy2=bottom-right
  165. y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
  166. y[:, 0] = (x[:, 0] + x[:, 2]) / 2 # x center
  167. y[:, 1] = (x[:, 1] + x[:, 3]) / 2 # y center
  168. y[:, 2] = x[:, 2] - x[:, 0] # width
  169. y[:, 3] = x[:, 3] - x[:, 1] # height
  170. return y
  171. def xywh2xyxy(x):
  172. # Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
  173. y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
  174. y[:, 0] = x[:, 0] - x[:, 2] / 2 # top left x
  175. y[:, 1] = x[:, 1] - x[:, 3] / 2 # top left y
  176. y[:, 2] = x[:, 0] + x[:, 2] / 2 # bottom right x
  177. y[:, 3] = x[:, 1] + x[:, 3] / 2 # bottom right y
  178. return y
  179. def xywhn2xyxy(x, w=640, h=640, padw=32, padh=32):
  180. # Convert nx4 boxes from [x, y, w, h] normalized to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
  181. y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
  182. y[:, 0] = w * (x[:, 0] - x[:, 2] / 2) + padw # top left x
  183. y[:, 1] = h * (x[:, 1] - x[:, 3] / 2) + padh # top left y
  184. y[:, 2] = w * (x[:, 0] + x[:, 2] / 2) + padw # bottom right x
  185. y[:, 3] = h * (x[:, 1] + x[:, 3] / 2) + padh # bottom right y
  186. return y
  187. def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):
  188. # Rescale coords (xyxy) from img1_shape to img0_shape
  189. if ratio_pad is None: # calculate from img0_shape
  190. gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1]) # gain = old / new
  191. pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding
  192. else:
  193. gain = ratio_pad[0][0]
  194. pad = ratio_pad[1]
  195. coords[:, [0, 2]] -= pad[0] # x padding
  196. coords[:, [1, 3]] -= pad[1] # y padding
  197. coords[:, :4] /= gain
  198. clip_coords(coords, img0_shape)
  199. return coords
  200. def clip_coords(boxes, img_shape):
  201. # Clip bounding xyxy bounding boxes to image shape (height, width)
  202. boxes[:, 0].clamp_(0, img_shape[1]) # x1
  203. boxes[:, 1].clamp_(0, img_shape[0]) # y1
  204. boxes[:, 2].clamp_(0, img_shape[1]) # x2
  205. boxes[:, 3].clamp_(0, img_shape[0]) # y2
  206. def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-9):
  207. # Returns the IoU of box1 to box2. box1 is 4, box2 is nx4
  208. box2 = box2.T
  209. # Get the coordinates of bounding boxes
  210. if x1y1x2y2: # x1, y1, x2, y2 = box1
  211. b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]
  212. b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]
  213. else: # transform from xywh to xyxy
  214. b1_x1, b1_x2 = box1[0] - box1[2] / 2, box1[0] + box1[2] / 2
  215. b1_y1, b1_y2 = box1[1] - box1[3] / 2, box1[1] + box1[3] / 2
  216. b2_x1, b2_x2 = box2[0] - box2[2] / 2, box2[0] + box2[2] / 2
  217. b2_y1, b2_y2 = box2[1] - box2[3] / 2, box2[1] + box2[3] / 2
  218. # Intersection area
  219. inter = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \
  220. (torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0)
  221. # Union Area
  222. w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps
  223. w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps
  224. union = w1 * h1 + w2 * h2 - inter + eps
  225. iou = inter / union
  226. if GIoU or DIoU or CIoU:
  227. # convex (smallest enclosing box) width
  228. cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1)
  229. ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1) # convex height
  230. if CIoU or DIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
  231. c2 = cw ** 2 + ch ** 2 + eps # convex diagonal squared
  232. rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 +
  233. (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4 # center distance squared
  234. if DIoU:
  235. return iou - rho2 / c2 # DIoU
  236. elif CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47
  237. v = (4 / math.pi ** 2) * \
  238. torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2)
  239. with torch.no_grad():
  240. alpha = v / ((1 + eps) - iou + v)
  241. return iou - (rho2 / c2 + v * alpha) # CIoU
  242. else: # GIoU https://arxiv.org/pdf/1902.09630.pdf
  243. c_area = cw * ch + eps # convex area
  244. return iou - (c_area - union) / c_area # GIoU
  245. else:
  246. return iou # IoU
  247. def box_iou(box1, box2):
  248. # https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py
  249. """
  250. Return intersection-over-union (Jaccard index) of boxes.
  251. Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
  252. Arguments:
  253. box1 (Tensor[N, 4])
  254. box2 (Tensor[M, 4])
  255. Returns:
  256. iou (Tensor[N, M]): the NxM matrix containing the pairwise
  257. IoU values for every element in boxes1 and boxes2
  258. """
  259. def box_area(box):
  260. # box = 4xn
  261. return (box[2] - box[0]) * (box[3] - box[1])
  262. area1 = box_area(box1.T)
  263. area2 = box_area(box2.T)
  264. # inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2)
  265. inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) -
  266. torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)
  267. # iou = inter / (area1 + area2 - inter)
  268. return inter / (area1[:, None] + area2 - inter)
  269. def wh_iou(wh1, wh2):
  270. # Returns the nxm IoU matrix. wh1 is nx2, wh2 is mx2
  271. wh1 = wh1[:, None] # [N,1,2]
  272. wh2 = wh2[None] # [1,M,2]
  273. inter = torch.min(wh1, wh2).prod(2) # [N,M]
  274. # iou = inter / (area1 + area2 - inter)
  275. return inter / (wh1.prod(2) + wh2.prod(2) - inter)
  276. def jaccard_diou(box_a, box_b, iscrowd:bool=False):
  277. use_batch = True
  278. if box_a.dim() == 2:
  279. use_batch = False
  280. box_a = box_a[None, ...]
  281. box_b = box_b[None, ...]
  282. inter = intersect(box_a, box_b)
  283. area_a = ((box_a[:, :, 2]-box_a[:, :, 0]) *
  284. (box_a[:, :, 3]-box_a[:, :, 1])).unsqueeze(2).expand_as(inter) # [A,B]
  285. area_b = ((box_b[:, :, 2]-box_b[:, :, 0]) *
  286. (box_b[:, :, 3]-box_b[:, :, 1])).unsqueeze(1).expand_as(inter) # [A,B]
  287. union = area_a + area_b - inter
  288. x1 = ((box_a[:, :, 2]+box_a[:, :, 0]) / 2).unsqueeze(2).expand_as(inter)
  289. y1 = ((box_a[:, :, 3]+box_a[:, :, 1]) / 2).unsqueeze(2).expand_as(inter)
  290. x2 = ((box_b[:, :, 2]+box_b[:, :, 0]) / 2).unsqueeze(1).expand_as(inter)
  291. y2 = ((box_b[:, :, 3]+box_b[:, :, 1]) / 2).unsqueeze(1).expand_as(inter)
  292. t1 = box_a[:, :, 1].unsqueeze(2).expand_as(inter)
  293. b1 = box_a[:, :, 3].unsqueeze(2).expand_as(inter)
  294. l1 = box_a[:, :, 0].unsqueeze(2).expand_as(inter)
  295. r1 = box_a[:, :, 2].unsqueeze(2).expand_as(inter)
  296. t2 = box_b[:, :, 1].unsqueeze(1).expand_as(inter)
  297. b2 = box_b[:, :, 3].unsqueeze(1).expand_as(inter)
  298. l2 = box_b[:, :, 0].unsqueeze(1).expand_as(inter)
  299. r2 = box_b[:, :, 2].unsqueeze(1).expand_as(inter)
  300. cr = torch.max(r1, r2)
  301. cl = torch.min(l1, l2)
  302. ct = torch.min(t1, t2)
  303. cb = torch.max(b1, b2)
  304. D = (((x2 - x1)**2 + (y2 - y1)**2) / ((cr-cl)**2 + (cb-ct)**2 + 1e-7))
  305. out = inter / area_a if iscrowd else inter / (union + 1e-7) - D ** 0.7
  306. return out if use_batch else out.squeeze(0)
  307. def non_max_suppression_face(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, labels=()):
  308. """Performs Non-Maximum Suppression (NMS) on inference results
  309. Returns:
  310. detections with shape: nx6 (x1, y1, x2, y2, conf, cls)
  311. """
  312. nc = prediction.shape[2] - 13 # number of classes
  313. xc = prediction[..., 4] > conf_thres # candidates
  314. # Settings
  315. min_wh, max_wh = 2, 4096 # (pixels) minimum and maximum box width and height
  316. time_limit = 10.0 # seconds to quit after
  317. redundant = True # require redundant detections
  318. multi_label = nc > 1 # multiple labels per box (adds 0.5ms/img)
  319. multi_label=False
  320. merge = False # use merge-NMS
  321. t = time.time()
  322. output = [torch.zeros((0, 14), device=prediction.device)] * prediction.shape[0]
  323. for xi, x in enumerate(prediction): # image index, image inference
  324. # Apply constraints
  325. # x[((x[..., 2:4] < min_wh) | (x[..., 2:4] > max_wh)).any(1), 4] = 0 # width-height
  326. x = x[xc[xi]] # confidence
  327. # Cat apriori labels if autolabelling
  328. if labels and len(labels[xi]):
  329. l = labels[xi]
  330. v = torch.zeros((len(l), nc + 13), device=x.device)
  331. v[:, :4] = l[:, 1:5] # box
  332. v[:, 4] = 1.0 # conf
  333. v[range(len(l)), l[:, 0].long() + 13] = 1.0 # cls
  334. x = torch.cat((x, v), 0)
  335. # If none remain process next image
  336. if not x.shape[0]:
  337. continue
  338. # Compute conf
  339. x[:, 13:] *= x[:, 4:5] # conf = obj_conf * cls_conf
  340. # Box (center x, center y, width, height) to (x1, y1, x2, y2)
  341. box = xywh2xyxy(x[:, :4])
  342. # Detections matrix nx6 (xyxy, conf, landmarks, cls)
  343. if multi_label:
  344. i, j = (x[:, 13:] > conf_thres).nonzero(as_tuple=False).T
  345. x = torch.cat((box[i], x[i, j + 13, None], x[i, 5:13] ,j[:, None].float()), 1)
  346. else: # best class only
  347. conf, j = x[:, 13:].max(1, keepdim=True)
  348. x = torch.cat((box, conf, x[:, 5:13], j.float()), 1)[conf.view(-1) > conf_thres]
  349. # Filter by class
  350. if classes is not None:
  351. x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]
  352. # If none remain process next image
  353. n = x.shape[0] # number of boxes
  354. if not n:
  355. continue
  356. # Batched NMS
  357. c = x[:, 13:14] * (0 if agnostic else max_wh) # classes
  358. boxes, scores = x[:, :4] + c, x[:, 4] # boxes (offset by class), scores
  359. i = torchvision.ops.nms(boxes, scores, iou_thres) # NMS
  360. #if i.shape[0] > max_det: # limit detections
  361. # i = i[:max_det]
  362. if merge and (1 < n < 3E3): # Merge NMS (boxes merged using weighted mean)
  363. # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)
  364. iou = box_iou(boxes[i], boxes) > iou_thres # iou matrix
  365. weights = iou * scores[None] # box weights
  366. x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True) # merged boxes
  367. if redundant:
  368. i = i[iou.sum(1) > 1] # require redundancy
  369. output[xi] = x[i]
  370. if (time.time() - t) > time_limit:
  371. break # time limit exceeded
  372. return output
  373. def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, labels=()):
  374. """Performs Non-Maximum Suppression (NMS) on inference results
  375. Returns:
  376. detections with shape: nx6 (x1, y1, x2, y2, conf, cls)
  377. """
  378. nc = prediction.shape[2] - 5 # number of classes
  379. xc = prediction[..., 4] > conf_thres # candidates
  380. # Settings
  381. # (pixels) minimum and maximum box width and height
  382. min_wh, max_wh = 2, 4096
  383. #max_det = 300 # maximum number of detections per image
  384. #max_nms = 30000 # maximum number of boxes into torchvision.ops.nms()
  385. time_limit = 10.0 # seconds to quit after
  386. redundant = True # require redundant detections
  387. multi_label = nc > 1 # multiple labels per box (adds 0.5ms/img)
  388. merge = False # use merge-NMS
  389. t = time.time()
  390. output = [torch.zeros((0, 6), device=prediction.device)] * prediction.shape[0]
  391. for xi, x in enumerate(prediction): # image index, image inference
  392. # Apply constraints
  393. # x[((x[..., 2:4] < min_wh) | (x[..., 2:4] > max_wh)).any(1), 4] = 0 # width-height
  394. x = x[xc[xi]] # confidence
  395. # Cat apriori labels if autolabelling
  396. if labels and len(labels[xi]):
  397. l = labels[xi]
  398. v = torch.zeros((len(l), nc + 5), device=x.device)
  399. v[:, :4] = l[:, 1:5] # box
  400. v[:, 4] = 1.0 # conf
  401. v[range(len(l)), l[:, 0].long() + 5] = 1.0 # cls
  402. x = torch.cat((x, v), 0)
  403. # If none remain process next image
  404. if not x.shape[0]:
  405. continue
  406. # Compute conf
  407. x[:, 5:] *= x[:, 4:5] # conf = obj_conf * cls_conf
  408. # Box (center x, center y, width, height) to (x1, y1, x2, y2)
  409. box = xywh2xyxy(x[:, :4])
  410. # Detections matrix nx6 (xyxy, conf, cls)
  411. if multi_label:
  412. i, j = (x[:, 5:] > conf_thres).nonzero(as_tuple=False).T
  413. x = torch.cat((box[i], x[i, j + 5, None], j[:, None].float()), 1)
  414. else: # best class only
  415. conf, j = x[:, 5:].max(1, keepdim=True)
  416. x = torch.cat((box, conf, j.float()), 1)[
  417. conf.view(-1) > conf_thres]
  418. # Filter by class
  419. if classes is not None:
  420. x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]
  421. # Apply finite constraint
  422. # if not torch.isfinite(x).all():
  423. # x = x[torch.isfinite(x).all(1)]
  424. # Check shape
  425. n = x.shape[0] # number of boxes
  426. if not n: # no boxes
  427. continue
  428. #elif n > max_nms: # excess boxes
  429. # x = x[x[:, 4].argsort(descending=True)[:max_nms]] # sort by confidence
  430. x = x[x[:, 4].argsort(descending=True)] # sort by confidence
  431. # Batched NMS
  432. c = x[:, 5:6] * (0 if agnostic else max_wh) # classes
  433. boxes, scores = x[:, :4] + c, x[:, 4] # boxes (offset by class), scores
  434. i = torchvision.ops.nms(boxes, scores, iou_thres) # NMS
  435. #if i.shape[0] > max_det: # limit detections
  436. # i = i[:max_det]
  437. if merge and (1 < n < 3E3): # Merge NMS (boxes merged using weighted mean)
  438. # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)
  439. iou = box_iou(boxes[i], boxes) > iou_thres # iou matrix
  440. weights = iou * scores[None] # box weights
  441. x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True) # merged boxes
  442. if redundant:
  443. i = i[iou.sum(1) > 1] # require redundancy
  444. output[xi] = x[i]
  445. if (time.time() - t) > time_limit:
  446. print(f'WARNING: NMS time limit {time_limit}s exceeded')
  447. break # time limit exceeded
  448. return output
  449. def strip_optimizer(f='weights/best.pt', s=''): # from utils.general import *; strip_optimizer()
  450. # Strip optimizer from 'f' to finalize training, optionally save as 's'
  451. x = torch.load(f, map_location=torch.device('cpu'))
  452. for key in 'optimizer', 'training_results', 'wandb_id':
  453. x[key] = None
  454. x['epoch'] = -1
  455. x['model'].half() # to FP16
  456. for p in x['model'].parameters():
  457. p.requires_grad = False
  458. torch.save(x, s or f)
  459. mb = os.path.getsize(s or f) / 1E6 # filesize
  460. print('Optimizer stripped from %s,%s %.1fMB' % (f, (' saved as %s,' % s) if s else '', mb))
  461. def print_mutation(hyp, results, yaml_file='hyp_evolved.yaml', bucket=''):
  462. # Print mutation results to evolve.txt (for use with train.py --evolve)
  463. a = '%10s' * len(hyp) % tuple(hyp.keys()) # hyperparam keys
  464. b = '%10.3g' * len(hyp) % tuple(hyp.values()) # hyperparam values
  465. c = '%10.4g' * len(results) % results # results (P, R, mAP@0.5, mAP@0.5:0.95, val_losses x 3)
  466. print('\n%s\n%s\nEvolved fitness: %s\n' % (a, b, c))
  467. if bucket:
  468. url = 'gs://%s/evolve.txt' % bucket
  469. if gsutil_getsize(url) > (os.path.getsize('evolve.txt') if os.path.exists('evolve.txt') else 0):
  470. os.system('gsutil cp %s .' % url) # download evolve.txt if larger than local
  471. with open('evolve.txt', 'a') as f: # append result
  472. f.write(c + b + '\n')
  473. x = np.unique(np.loadtxt('evolve.txt', ndmin=2), axis=0) # load unique rows
  474. x = x[np.argsort(-fitness(x))] # sort
  475. np.savetxt('evolve.txt', x, '%10.3g') # save sort by fitness
  476. # Save yaml
  477. for i, k in enumerate(hyp.keys()):
  478. hyp[k] = float(x[0, i + 7])
  479. with open(yaml_file, 'w') as f:
  480. results = tuple(x[0, :7])
  481. c = '%10.4g' * len(results) % results # results (P, R, mAP@0.5, mAP@0.5:0.95, val_losses x 3)
  482. f.write('# Hyperparameter Evolution Results\n# Generations: %g\n# Metrics: ' % len(x) + c + '\n\n')
  483. yaml.dump(hyp, f, sort_keys=False)
  484. if bucket:
  485. os.system('gsutil cp evolve.txt %s gs://%s' % (yaml_file, bucket)) # upload
  486. def apply_classifier(x, model, img, im0):
  487. # applies a second stage classifier to yolo outputs
  488. im0 = [im0] if isinstance(im0, np.ndarray) else im0
  489. for i, d in enumerate(x): # per image
  490. if d is not None and len(d):
  491. d = d.clone()
  492. # Reshape and pad cutouts
  493. b = xyxy2xywh(d[:, :4]) # boxes
  494. b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1) # rectangle to square
  495. b[:, 2:] = b[:, 2:] * 1.3 + 30 # pad
  496. d[:, :4] = xywh2xyxy(b).long()
  497. # Rescale boxes from img_size to im0 size
  498. scale_coords(img.shape[2:], d[:, :4], im0[i].shape)
  499. # Classes
  500. pred_cls1 = d[:, 5].long()
  501. ims = []
  502. for j, a in enumerate(d): # per item
  503. cutout = im0[i][int(a[1]):int(a[3]), int(a[0]):int(a[2])]
  504. im = cv2.resize(cutout, (224, 224)) # BGR
  505. # cv2.imwrite('test%i.jpg' % j, cutout)
  506. # BGR to RGB, to 3x416x416
  507. im = im[:, :, ::-1].transpose(2, 0, 1)
  508. im = np.ascontiguousarray(
  509. im, dtype=np.float32) # uint8 to float32
  510. im /= 255.0 # 0 - 255 to 0.0 - 1.0
  511. ims.append(im)
  512. pred_cls2 = model(torch.Tensor(ims).to(d.device)
  513. ).argmax(1) # classifier prediction
  514. # retain matching class detections
  515. x[i] = x[i][pred_cls1 == pred_cls2]
  516. return x
  517. def increment_path(path, exist_ok=True, sep=''):
  518. # Increment path, i.e. runs/exp --> runs/exp{sep}0, runs/exp{sep}1 etc.
  519. path = Path(path) # os-agnostic
  520. if (path.exists() and exist_ok) or (not path.exists()):
  521. return str(path)
  522. else:
  523. dirs = glob.glob(f"{path}{sep}*") # similar paths
  524. matches = [re.search(rf"%s{sep}(\d+)" % path.stem, d) for d in dirs]
  525. i = [int(m.groups()[0]) for m in matches if m] # indices
  526. n = max(i) + 1 if i else 2 # increment number
  527. return f"{path}{sep}{n}" # update path