Adamfan/objectdetection
0
1# YOLOR general utils2 3import glob4import logging5import math6import os7import platform8import random9import re10import subprocess11import time12from pathlib import Path13 14import cv215import numpy as np16import pandas as pd17import torch18import torchvision19import yaml20 21from utils.google_utils import gsutil_getsize22from utils.metrics import fitness23from utils.torch_utils import init_torch_seeds24 25# Settings26torch.set_printoptions(linewidth=320, precision=5, profile='long')27np.set_printoptions(linewidth=320, formatter={'float_kind': '{:11.5g}'.format}) # format short g, %precision=528pd.options.display.max_columns = 1029cv2.setNumThreads(0) # prevent OpenCV from multithreading (incompatible with PyTorch DataLoader)30os.environ['NUMEXPR_MAX_THREADS'] = str(min(os.cpu_count(), 8)) # NumExpr max threads31 32 33def set_logging(rank=-1):34 logging.basicConfig(35 format="%(message)s",36 level=logging.INFO if rank in [-1, 0] else logging.WARN)37 38 39def init_seeds(seed=0):40 # Initialize random number generator (RNG) seeds41 random.seed(seed)42 np.random.seed(seed)43 init_torch_seeds(seed)44 45 46def get_latest_run(search_dir='.'):47 # Return path to most recent 'last.pt' in /runs (i.e. to --resume from)48 last_list = glob.glob(f'{search_dir}/**/last*.pt', recursive=True)49 return max(last_list, key=os.path.getctime) if last_list else ''50 51 52def isdocker():53 # Is environment a Docker container54 return Path('/workspace').exists() # or Path('/.dockerenv').exists()55 56 57def emojis(str=''):58 # Return platform-dependent emoji-safe version of string59 return str.encode().decode('ascii', 'ignore') if platform.system() == 'Windows' else str60 61 62def check_online():63 # Check internet connectivity64 import socket65 try:66 socket.create_connection(("1.1.1.1", 443), 5) # check host accesability67 return True68 except OSError:69 return False70 71 72def check_git_status():73 # Recommend 'git pull' if code is out of date74 print(colorstr('github: '), end='')75 try:76 assert Path('.git').exists(), 'skipping check (not a git repository)'77 assert not isdocker(), 'skipping check (Docker image)'78 assert check_online(), 'skipping check (offline)'79 80 cmd = 'git fetch && git config --get remote.origin.url'81 url = subprocess.check_output(cmd, shell=True).decode().strip().rstrip('.git') # github repo url82 branch = subprocess.check_output('git rev-parse --abbrev-ref HEAD', shell=True).decode().strip() # checked out83 n = int(subprocess.check_output(f'git rev-list {branch}..origin/master --count', shell=True)) # commits behind84 if n > 0:85 s = f"⚠️ WARNING: code is out of date by {n} commit{'s' * (n > 1)}. " \86 f"Use 'git pull' to update or 'git clone {url}' to download latest."87 else:88 s = f'up to date with {url} ✅'89 print(emojis(s)) # emoji-safe90 except Exception as e:91 print(e)92 93 94def check_requirements(requirements='requirements.txt', exclude=()):95 # Check installed dependencies meet requirements (pass *.txt file or list of packages)96 import pkg_resources as pkg97 prefix = colorstr('red', 'bold', 'requirements:')98 if isinstance(requirements, (str, Path)): # requirements.txt file99 file = Path(requirements)100 if not file.exists():101 print(f"{prefix} {file.resolve()} not found, check failed.")102 return103 requirements = [f'{x.name}{x.specifier}' for x in pkg.parse_requirements(file.open()) if x.name not in exclude]104 else: # list or tuple of packages105 requirements = [x for x in requirements if x not in exclude]106 107 n = 0 # number of packages updates108 for r in requirements:109 try:110 pkg.require(r)111 except Exception as e: # DistributionNotFound or VersionConflict if requirements not met112 n += 1113 print(f"{prefix} {e.req} not found and is required by YOLOR, attempting auto-update...")114 print(subprocess.check_output(f"pip install '{e.req}'", shell=True).decode())115 116 if n: # if packages updated117 source = file.resolve() if 'file' in locals() else requirements118 s = f"{prefix} {n} package{'s' * (n > 1)} updated per {source}\n" \119 f"{prefix} ⚠️ {colorstr('bold', 'Restart runtime or rerun command for updates to take effect')}\n"120 print(emojis(s)) # emoji-safe121 122 123def check_img_size(img_size, s=32):124 # Verify img_size is a multiple of stride s125 new_size = make_divisible(img_size, int(s)) # ceil gs-multiple126 if new_size != img_size:127 print('WARNING: --img-size %g must be multiple of max stride %g, updating to %g' % (img_size, s, new_size))128 return new_size129 130 131def check_imshow():132 # Check if environment supports image displays133 try:134 assert not isdocker(), 'cv2.imshow() is disabled in Docker environments'135 cv2.imshow('test', np.zeros((1, 1, 3)))136 cv2.waitKey(1)137 cv2.destroyAllWindows()138 cv2.waitKey(1)139 return True140 except Exception as e:141 print(f'WARNING: Environment does not support cv2.imshow() or PIL Image.show() image displays\n{e}')142 return False143 144 145def check_file(file):146 # Search for file if not found147 if Path(file).is_file() or file == '':148 return file149 else:150 files = glob.glob('./**/' + file, recursive=True) # find file151 assert len(files), f'File Not Found: {file}' # assert file was found152 assert len(files) == 1, f"Multiple files match '{file}', specify exact path: {files}" # assert unique153 return files[0] # return file154 155 156def check_dataset(dict):157 # Download dataset if not found locally158 val, s = dict.get('val'), dict.get('download')159 if val and len(val):160 val = [Path(x).resolve() for x in (val if isinstance(val, list) else [val])] # val path161 if not all(x.exists() for x in val):162 print('\nWARNING: Dataset not found, nonexistent paths: %s' % [str(x) for x in val if not x.exists()])163 if s and len(s): # download script164 print('Downloading %s ...' % s)165 if s.startswith('http') and s.endswith('.zip'): # URL166 f = Path(s).name # filename167 torch.hub.download_url_to_file(s, f)168 r = os.system('unzip -q %s -d ../ && rm %s' % (f, f)) # unzip169 else: # bash script170 r = os.system(s)171 print('Dataset autodownload %s\n' % ('success' if r == 0 else 'failure')) # analyze return value172 else:173 raise Exception('Dataset not found.')174 175 176def make_divisible(x, divisor):177 # Returns x evenly divisible by divisor178 return math.ceil(x / divisor) * divisor179 180 181def clean_str(s):182 # Cleans a string by replacing special characters with underscore _183 return re.sub(pattern="[|@#!¡·$€%&()=?¿^*;:,¨´><+]", repl="_", string=s)184 185 186def one_cycle(y1=0.0, y2=1.0, steps=100):187 # lambda function for sinusoidal ramp from y1 to y2188 return lambda x: ((1 - math.cos(x * math.pi / steps)) / 2) * (y2 - y1) + y1189 190 191def colorstr(*input):192 # Colors a string https://en.wikipedia.org/wiki/ANSI_escape_code, i.e. colorstr('blue', 'hello world')193 *args, string = input if len(input) > 1 else ('blue', 'bold', input[0]) # color arguments, string194 colors = {'black': '\033[30m', # basic colors195 'red': '\033[31m',196 'green': '\033[32m',197 'yellow': '\033[33m',198 'blue': '\033[34m',199 'magenta': '\033[35m',200 'cyan': '\033[36m',201 'white': '\033[37m',202 'bright_black': '\033[90m', # bright colors203 'bright_red': '\033[91m',204 'bright_green': '\033[92m',205 'bright_yellow': '\033[93m',206 'bright_blue': '\033[94m',207 'bright_magenta': '\033[95m',208 'bright_cyan': '\033[96m',209 'bright_white': '\033[97m',210 'end': '\033[0m', # misc211 'bold': '\033[1m',212 'underline': '\033[4m'}213 return ''.join(colors[x] for x in args) + f'{string}' + colors['end']214 215 216def labels_to_class_weights(labels, nc=80):217 # Get class weights (inverse frequency) from training labels218 if labels[0] is None: # no labels loaded219 return torch.Tensor()220 221 labels = np.concatenate(labels, 0) # labels.shape = (866643, 5) for COCO222 classes = labels[:, 0].astype(np.int32) # labels = [class xywh]223 weights = np.bincount(classes, minlength=nc) # occurrences per class224 225 # Prepend gridpoint count (for uCE training)226 # gpi = ((320 / 32 * np.array([1, 2, 4])) ** 2 * 3).sum() # gridpoints per image227 # weights = np.hstack([gpi * len(labels) - weights.sum() * 9, weights * 9]) ** 0.5 # prepend gridpoints to start228 229 weights[weights == 0] = 1 # replace empty bins with 1230 weights = 1 / weights # number of targets per class231 weights /= weights.sum() # normalize232 return torch.from_numpy(weights)233 234 235def labels_to_image_weights(labels, nc=80, class_weights=np.ones(80)):236 # Produces image weights based on class_weights and image contents237 class_counts = np.array([np.bincount(x[:, 0].astype(np.int32), minlength=nc) for x in labels])238 image_weights = (class_weights.reshape(1, nc) * class_counts).sum(1)239 # index = random.choices(range(n), weights=image_weights, k=1) # weight image sample240 return image_weights241 242 243def coco80_to_coco91_class(): # converts 80-index (val2014) to 91-index (paper)244 # https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/245 # a = np.loadtxt('data/coco.names', dtype='str', delimiter='\n')246 # b = np.loadtxt('data/coco_paper.names', dtype='str', delimiter='\n')247 # x1 = [list(a[i] == b).index(True) + 1 for i in range(80)] # darknet to coco248 # x2 = [list(b[i] == a).index(True) if any(b[i] == a) else None for i in range(91)] # coco to darknet249 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,250 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,251 64, 65, 67, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90]252 return x253 254 255def xyxy2xywh(x):256 # Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] where xy1=top-left, xy2=bottom-right257 y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)258 y[:, 0] = (x[:, 0] + x[:, 2]) / 2 # x center259 y[:, 1] = (x[:, 1] + x[:, 3]) / 2 # y center260 y[:, 2] = x[:, 2] - x[:, 0] # width261 y[:, 3] = x[:, 3] - x[:, 1] # height262 return y263 264 265def xywh2xyxy(x):266 # Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right267 y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)268 y[:, 0] = x[:, 0] - x[:, 2] / 2 # top left x269 y[:, 1] = x[:, 1] - x[:, 3] / 2 # top left y270 y[:, 2] = x[:, 0] + x[:, 2] / 2 # bottom right x271 y[:, 3] = x[:, 1] + x[:, 3] / 2 # bottom right y272 return y273 274 275def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0):276 # Convert nx4 boxes from [x, y, w, h] normalized to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right277 y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)278 y[:, 0] = w * (x[:, 0] - x[:, 2] / 2) + padw # top left x279 y[:, 1] = h * (x[:, 1] - x[:, 3] / 2) + padh # top left y280 y[:, 2] = w * (x[:, 0] + x[:, 2] / 2) + padw # bottom right x281 y[:, 3] = h * (x[:, 1] + x[:, 3] / 2) + padh # bottom right y282 return y283 284 285def xyn2xy(x, w=640, h=640, padw=0, padh=0):286 # Convert normalized segments into pixel segments, shape (n,2)287 y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)288 y[:, 0] = w * x[:, 0] + padw # top left x289 y[:, 1] = h * x[:, 1] + padh # top left y290 return y291 292 293def segment2box(segment, width=640, height=640):294 # Convert 1 segment label to 1 box label, applying inside-image constraint, i.e. (xy1, xy2, ...) to (xyxy)295 x, y = segment.T # segment xy296 inside = (x >= 0) & (y >= 0) & (x <= width) & (y <= height)297 x, y, = x[inside], y[inside]298 return np.array([x.min(), y.min(), x.max(), y.max()]) if any(x) else np.zeros((1, 4)) # xyxy299 300 301def segments2boxes(segments):302 # Convert segment labels to box labels, i.e. (cls, xy1, xy2, ...) to (cls, xywh)303 boxes = []304 for s in segments:305 x, y = s.T # segment xy306 boxes.append([x.min(), y.min(), x.max(), y.max()]) # cls, xyxy307 return xyxy2xywh(np.array(boxes)) # cls, xywh308 309 310def resample_segments(segments, n=1000):311 # Up-sample an (n,2) segment312 for i, s in enumerate(segments):313 s = np.concatenate((s, s[0:1, :]), axis=0)314 x = np.linspace(0, len(s) - 1, n)315 xp = np.arange(len(s))316 segments[i] = np.concatenate([np.interp(x, xp, s[:, i]) for i in range(2)]).reshape(2, -1).T # segment xy317 return segments318 319 320def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):321 # Rescale coords (xyxy) from img1_shape to img0_shape322 if ratio_pad is None: # calculate from img0_shape323 gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1]) # gain = old / new324 pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding325 else:326 gain = ratio_pad[0][0]327 pad = ratio_pad[1]328 329 coords[:, [0, 2]] -= pad[0] # x padding330 coords[:, [1, 3]] -= pad[1] # y padding331 coords[:, :4] /= gain332 clip_coords(coords, img0_shape)333 return coords334 335 336def clip_coords(boxes, img_shape):337 # Clip bounding xyxy bounding boxes to image shape (height, width)338 boxes[:, 0].clamp_(0, img_shape[1]) # x1339 boxes[:, 1].clamp_(0, img_shape[0]) # y1340 boxes[:, 2].clamp_(0, img_shape[1]) # x2341 boxes[:, 3].clamp_(0, img_shape[0]) # y2342 343 344def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7):345 # Returns the IoU of box1 to box2. box1 is 4, box2 is nx4346 box2 = box2.T347 348 # Get the coordinates of bounding boxes349 if x1y1x2y2: # x1, y1, x2, y2 = box1350 b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]351 b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]352 else: # transform from xywh to xyxy353 b1_x1, b1_x2 = box1[0] - box1[2] / 2, box1[0] + box1[2] / 2354 b1_y1, b1_y2 = box1[1] - box1[3] / 2, box1[1] + box1[3] / 2355 b2_x1, b2_x2 = box2[0] - box2[2] / 2, box2[0] + box2[2] / 2356 b2_y1, b2_y2 = box2[1] - box2[3] / 2, box2[1] + box2[3] / 2357 358 # Intersection area359 inter = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \360 (torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0)361 362 # Union Area363 w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps364 w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps365 union = w1 * h1 + w2 * h2 - inter + eps366 367 iou = inter / union368 369 if GIoU or DIoU or CIoU:370 cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1) # convex (smallest enclosing box) width371 ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1) # convex height372 if CIoU or DIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1373 c2 = cw ** 2 + ch ** 2 + eps # convex diagonal squared374 rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 +375 (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4 # center distance squared376 if DIoU:377 return iou - rho2 / c2 # DIoU378 elif CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47379 v = (4 / math.pi ** 2) * torch.pow(torch.atan(w2 / (h2 + eps)) - torch.atan(w1 / (h1 + eps)), 2)380 with torch.no_grad():381 alpha = v / (v - iou + (1 + eps))382 return iou - (rho2 / c2 + v * alpha) # CIoU383 else: # GIoU https://arxiv.org/pdf/1902.09630.pdf384 c_area = cw * ch + eps # convex area385 return iou - (c_area - union) / c_area # GIoU386 else:387 return iou # IoU388 389 390 391 392def bbox_alpha_iou(box1, box2, x1y1x2y2=False, GIoU=False, DIoU=False, CIoU=False, alpha=2, eps=1e-9):393 # Returns tsqrt_he IoU of box1 to box2. box1 is 4, box2 is nx4394 box2 = box2.T395 396 # Get the coordinates of bounding boxes397 if x1y1x2y2: # x1, y1, x2, y2 = box1398 b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]399 b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]400 else: # transform from xywh to xyxy401 b1_x1, b1_x2 = box1[0] - box1[2] / 2, box1[0] + box1[2] / 2402 b1_y1, b1_y2 = box1[1] - box1[3] / 2, box1[1] + box1[3] / 2403 b2_x1, b2_x2 = box2[0] - box2[2] / 2, box2[0] + box2[2] / 2404 b2_y1, b2_y2 = box2[1] - box2[3] / 2, box2[1] + box2[3] / 2405 406 # Intersection area407 inter = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \408 (torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0)409 410 # Union Area411 w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps412 w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps413 union = w1 * h1 + w2 * h2 - inter + eps414 415 # change iou into pow(iou+eps)416 # iou = inter / union417 iou = torch.pow(inter/union + eps, alpha)418 # beta = 2 * alpha419 if GIoU or DIoU or CIoU:420 cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1) # convex (smallest enclosing box) width421 ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1) # convex height422 if CIoU or DIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1423 c2 = (cw ** 2 + ch ** 2) ** alpha + eps # convex diagonal424 rho_x = torch.abs(b2_x1 + b2_x2 - b1_x1 - b1_x2)425 rho_y = torch.abs(b2_y1 + b2_y2 - b1_y1 - b1_y2)426 rho2 = ((rho_x ** 2 + rho_y ** 2) / 4) ** alpha # center distance427 if DIoU:428 return iou - rho2 / c2 # DIoU429 elif CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47430 v = (4 / math.pi ** 2) * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2)431 with torch.no_grad():432 alpha_ciou = v / ((1 + eps) - inter / union + v)433 # return iou - (rho2 / c2 + v * alpha_ciou) # CIoU434 return iou - (rho2 / c2 + torch.pow(v * alpha_ciou + eps, alpha)) # CIoU435 else: # GIoU https://arxiv.org/pdf/1902.09630.pdf436 # c_area = cw * ch + eps # convex area437 # return iou - (c_area - union) / c_area # GIoU438 c_area = torch.max(cw * ch + eps, union) # convex area439 return iou - torch.pow((c_area - union) / c_area + eps, alpha) # GIoU440 else:441 return iou # torch.log(iou+eps) or iou442 443 444def box_iou(box1, box2):445 # https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py446 """447 Return intersection-over-union (Jaccard index) of boxes.448 Both sets of boxes are expected to be in (x1, y1, x2, y2) format.449 Arguments:450 box1 (Tensor[N, 4])451 box2 (Tensor[M, 4])452 Returns:453 iou (Tensor[N, M]): the NxM matrix containing the pairwise454 IoU values for every element in boxes1 and boxes2455 """456 457 def box_area(box):458 # box = 4xn459 return (box[2] - box[0]) * (box[3] - box[1])460 461 area1 = box_area(box1.T)462 area2 = box_area(box2.T)463 464 # inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2)465 inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)466 return inter / (area1[:, None] + area2 - inter) # iou = inter / (area1 + area2 - inter)467 468 469def wh_iou(wh1, wh2):470 # Returns the nxm IoU matrix. wh1 is nx2, wh2 is mx2471 wh1 = wh1[:, None] # [N,1,2]472 wh2 = wh2[None] # [1,M,2]473 inter = torch.min(wh1, wh2).prod(2) # [N,M]474 return inter / (wh1.prod(2) + wh2.prod(2) - inter) # iou = inter / (area1 + area2 - inter)475 476 477def box_giou(box1, box2):478 """479 Return generalized intersection-over-union (Jaccard index) between two sets of boxes.480 Both sets of boxes are expected to be in ``(x1, y1, x2, y2)`` format with481 ``0 <= x1 < x2`` and ``0 <= y1 < y2``.482 Args:483 boxes1 (Tensor[N, 4]): first set of boxes484 boxes2 (Tensor[M, 4]): second set of boxes485 Returns:486 Tensor[N, M]: the NxM matrix containing the pairwise generalized IoU values487 for every element in boxes1 and boxes2488 """489 490 def box_area(box):491 # box = 4xn492 return (box[2] - box[0]) * (box[3] - box[1])493 494 area1 = box_area(box1.T)495 area2 = box_area(box2.T)496 497 inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)498 union = (area1[:, None] + area2 - inter)499 500 iou = inter / union501 502 lti = torch.min(box1[:, None, :2], box2[:, :2])503 rbi = torch.max(box1[:, None, 2:], box2[:, 2:])504 505 whi = (rbi - lti).clamp(min=0) # [N,M,2]506 areai = whi[:, :, 0] * whi[:, :, 1]507 508 return iou - (areai - union) / areai509 510 511def box_ciou(box1, box2, eps: float = 1e-7):512 """513 Return complete intersection-over-union (Jaccard index) between two sets of boxes.514 Both sets of boxes are expected to be in ``(x1, y1, x2, y2)`` format with515 ``0 <= x1 < x2`` and ``0 <= y1 < y2``.516 Args:517 boxes1 (Tensor[N, 4]): first set of boxes518 boxes2 (Tensor[M, 4]): second set of boxes519 eps (float, optional): small number to prevent division by zero. Default: 1e-7520 Returns:521 Tensor[N, M]: the NxM matrix containing the pairwise complete IoU values522 for every element in boxes1 and boxes2523 """524 525 def box_area(box):526 # box = 4xn527 return (box[2] - box[0]) * (box[3] - box[1])528 529 area1 = box_area(box1.T)530 area2 = box_area(box2.T)531 532 inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)533 union = (area1[:, None] + area2 - inter)534 535 iou = inter / union536 537 lti = torch.min(box1[:, None, :2], box2[:, :2])538 rbi = torch.max(box1[:, None, 2:], box2[:, 2:])539 540 whi = (rbi - lti).clamp(min=0) # [N,M,2]541 diagonal_distance_squared = (whi[:, :, 0] ** 2) + (whi[:, :, 1] ** 2) + eps542 543 # centers of boxes544 x_p = (box1[:, None, 0] + box1[:, None, 2]) / 2545 y_p = (box1[:, None, 1] + box1[:, None, 3]) / 2546 x_g = (box2[:, 0] + box2[:, 2]) / 2547 y_g = (box2[:, 1] + box2[:, 3]) / 2548 # The distance between boxes' centers squared.549 centers_distance_squared = (x_p - x_g) ** 2 + (y_p - y_g) ** 2550 551 w_pred = box1[:, None, 2] - box1[:, None, 0]552 h_pred = box1[:, None, 3] - box1[:, None, 1]553 554 w_gt = box2[:, 2] - box2[:, 0]555 h_gt = box2[:, 3] - box2[:, 1]556 557 v = (4 / (torch.pi ** 2)) * torch.pow((torch.atan(w_gt / h_gt) - torch.atan(w_pred / h_pred)), 2)558 with torch.no_grad():559 alpha = v / (1 - iou + v + eps)560 return iou - (centers_distance_squared / diagonal_distance_squared) - alpha * v561 562 563def box_diou(box1, box2, eps: float = 1e-7):564 """565 Return distance intersection-over-union (Jaccard index) between two sets of boxes.566 Both sets of boxes are expected to be in ``(x1, y1, x2, y2)`` format with567 ``0 <= x1 < x2`` and ``0 <= y1 < y2``.568 Args:569 boxes1 (Tensor[N, 4]): first set of boxes570 boxes2 (Tensor[M, 4]): second set of boxes571 eps (float, optional): small number to prevent division by zero. Default: 1e-7572 Returns:573 Tensor[N, M]: the NxM matrix containing the pairwise distance IoU values574 for every element in boxes1 and boxes2575 """576 577 def box_area(box):578 # box = 4xn579 return (box[2] - box[0]) * (box[3] - box[1])580 581 area1 = box_area(box1.T)582 area2 = box_area(box2.T)583 584 inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)585 union = (area1[:, None] + area2 - inter)586 587 iou = inter / union588 589 lti = torch.min(box1[:, None, :2], box2[:, :2])590 rbi = torch.max(box1[:, None, 2:], box2[:, 2:])591 592 whi = (rbi - lti).clamp(min=0) # [N,M,2]593 diagonal_distance_squared = (whi[:, :, 0] ** 2) + (whi[:, :, 1] ** 2) + eps594 595 # centers of boxes596 x_p = (box1[:, None, 0] + box1[:, None, 2]) / 2597 y_p = (box1[:, None, 1] + box1[:, None, 3]) / 2598 x_g = (box2[:, 0] + box2[:, 2]) / 2599 y_g = (box2[:, 1] + box2[:, 3]) / 2600 # The distance between boxes' centers squared.601 centers_distance_squared = (x_p - x_g) ** 2 + (y_p - y_g) ** 2602 603 # The distance IoU is the IoU penalized by a normalized604 # distance between boxes' centers squared.605 return iou - (centers_distance_squared / diagonal_distance_squared)606 607 608def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False,609 labels=()):610 """Runs Non-Maximum Suppression (NMS) on inference results611 612 Returns:613 list of detections, on (n,6) tensor per image [xyxy, conf, cls]614 """615 616 nc = prediction.shape[2] - 5 # number of classes617 xc = prediction[..., 4] > conf_thres # candidates618 619 # Settings620 min_wh, max_wh = 2, 4096 # (pixels) minimum and maximum box width and height621 max_det = 300 # maximum number of detections per image622 max_nms = 30000 # maximum number of boxes into torchvision.ops.nms()623 time_limit = 10.0 # seconds to quit after624 redundant = True # require redundant detections625 multi_label &= nc > 1 # multiple labels per box (adds 0.5ms/img)626 merge = False # use merge-NMS627 628 t = time.time()629 output = [torch.zeros((0, 6), device=prediction.device)] * prediction.shape[0]630 for xi, x in enumerate(prediction): # image index, image inference631 # Apply constraints632 # x[((x[..., 2:4] < min_wh) | (x[..., 2:4] > max_wh)).any(1), 4] = 0 # width-height633 x = x[xc[xi]] # confidence634 635 # Cat apriori labels if autolabelling636 if labels and len(labels[xi]):637 l = labels[xi]638 v = torch.zeros((len(l), nc + 5), device=x.device)639 v[:, :4] = l[:, 1:5] # box640 v[:, 4] = 1.0 # conf641 v[range(len(l)), l[:, 0].long() + 5] = 1.0 # cls642 x = torch.cat((x, v), 0)643 644 # If none remain process next image645 if not x.shape[0]:646 continue647 648 # Compute conf649 if nc == 1:650 x[:, 5:] = x[:, 4:5] # for models with one class, cls_loss is 0 and cls_conf is always 0.5,651 # so there is no need to multiplicate.652 else:653 x[:, 5:] *= x[:, 4:5] # conf = obj_conf * cls_conf654 655 # Box (center x, center y, width, height) to (x1, y1, x2, y2)656 box = xywh2xyxy(x[:, :4])657 658 # Detections matrix nx6 (xyxy, conf, cls)659 if multi_label:660 i, j = (x[:, 5:] > conf_thres).nonzero(as_tuple=False).T661 x = torch.cat((box[i], x[i, j + 5, None], j[:, None].float()), 1)662 else: # best class only663 conf, j = x[:, 5:].max(1, keepdim=True)664 x = torch.cat((box, conf, j.float()), 1)[conf.view(-1) > conf_thres]665 666 # Filter by class667 if classes is not None:668 x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]669 670 # Apply finite constraint671 # if not torch.isfinite(x).all():672 # x = x[torch.isfinite(x).all(1)]673 674 # Check shape675 n = x.shape[0] # number of boxes676 if not n: # no boxes677 continue678 elif n > max_nms: # excess boxes679 x = x[x[:, 4].argsort(descending=True)[:max_nms]] # sort by confidence680 681 # Batched NMS682 c = x[:, 5:6] * (0 if agnostic else max_wh) # classes683 boxes, scores = x[:, :4] + c, x[:, 4] # boxes (offset by class), scores684 i = torchvision.ops.nms(boxes, scores, iou_thres) # NMS685 if i.shape[0] > max_det: # limit detections686 i = i[:max_det]687 if merge and (1 < n < 3E3): # Merge NMS (boxes merged using weighted mean)688 # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)689 iou = box_iou(boxes[i], boxes) > iou_thres # iou matrix690 weights = iou * scores[None] # box weights691 x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True) # merged boxes692 if redundant:693 i = i[iou.sum(1) > 1] # require redundancy694 695 output[xi] = x[i]696 if (time.time() - t) > time_limit:697 print(f'WARNING: NMS time limit {time_limit}s exceeded')698 break # time limit exceeded699 700 return output701 702 703def non_max_suppression_kpt(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False,704 labels=(), kpt_label=False, nc=None, nkpt=None):705 """Runs Non-Maximum Suppression (NMS) on inference results706 707 Returns:708 list of detections, on (n,6) tensor per image [xyxy, conf, cls]709 """710 if nc is None:711 nc = prediction.shape[2] - 5 if not kpt_label else prediction.shape[2] - 56 # number of classes712 xc = prediction[..., 4] > conf_thres # candidates713 714 # Settings715 min_wh, max_wh = 2, 4096 # (pixels) minimum and maximum box width and height716 max_det = 300 # maximum number of detections per image717 max_nms = 30000 # maximum number of boxes into torchvision.ops.nms()718 time_limit = 10.0 # seconds to quit after719 redundant = True # require redundant detections720 multi_label &= nc > 1 # multiple labels per box (adds 0.5ms/img)721 merge = False # use merge-NMS722 723 t = time.time()724 output = [torch.zeros((0,6), device=prediction.device)] * prediction.shape[0]725 for xi, x in enumerate(prediction): # image index, image inference726 # Apply constraints727 # x[((x[..., 2:4] < min_wh) | (x[..., 2:4] > max_wh)).any(1), 4] = 0 # width-height728 x = x[xc[xi]] # confidence729 730 # Cat apriori labels if autolabelling731 if labels and len(labels[xi]):732 l = labels[xi]733 v = torch.zeros((len(l), nc + 5), device=x.device)734 v[:, :4] = l[:, 1:5] # box735 v[:, 4] = 1.0 # conf736 v[range(len(l)), l[:, 0].long() + 5] = 1.0 # cls737 x = torch.cat((x, v), 0)738 739 # If none remain process next image740 if not x.shape[0]:741 continue742 743 # Compute conf744 x[:, 5:5+nc] *= x[:, 4:5] # conf = obj_conf * cls_conf745 746 # Box (center x, center y, width, height) to (x1, y1, x2, y2)747 box = xywh2xyxy(x[:, :4])748 749 # Detections matrix nx6 (xyxy, conf, cls)750 if multi_label:751 i, j = (x[:, 5:] > conf_thres).nonzero(as_tuple=False).T752 x = torch.cat((box[i], x[i, j + 5, None], j[:, None].float()), 1)753 else: # best class only754 if not kpt_label:755 conf, j = x[:, 5:].max(1, keepdim=True)756 x = torch.cat((box, conf, j.float()), 1)[conf.view(-1) > conf_thres]757 else:758 kpts = x[:, 6:]759 conf, j = x[:, 5:6].max(1, keepdim=True)760 x = torch.cat((box, conf, j.float(), kpts), 1)[conf.view(-1) > conf_thres]761 762 763 # Filter by class764 if classes is not None:765 x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]766 767 # Apply finite constraint768 # if not torch.isfinite(x).all():769 # x = x[torch.isfinite(x).all(1)]770 771 # Check shape772 n = x.shape[0] # number of boxes773 if not n: # no boxes774 continue775 elif n > max_nms: # excess boxes776 x = x[x[:, 4].argsort(descending=True)[:max_nms]] # sort by confidence777 778 # Batched NMS779 c = x[:, 5:6] * (0 if agnostic else max_wh) # classes780 boxes, scores = x[:, :4] + c, x[:, 4] # boxes (offset by class), scores781 i = torchvision.ops.nms(boxes, scores, iou_thres) # NMS782 if i.shape[0] > max_det: # limit detections783 i = i[:max_det]784 if merge and (1 < n < 3E3): # Merge NMS (boxes merged using weighted mean)785 # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)786 iou = box_iou(boxes[i], boxes) > iou_thres # iou matrix787 weights = iou * scores[None] # box weights788 x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True) # merged boxes789 if redundant:790 i = i[iou.sum(1) > 1] # require redundancy791 792 output[xi] = x[i]793 if (time.time() - t) > time_limit:794 print(f'WARNING: NMS time limit {time_limit}s exceeded')795 break # time limit exceeded796 797 return output798 799 800def strip_optimizer(f='best.pt', s=''): # from utils.general import *; strip_optimizer()801 # Strip optimizer from 'f' to finalize training, optionally save as 's'802 x = torch.load(f, map_location=torch.device('cpu'), weights_only=False)803 if x.get('ema'):804 x['model'] = x['ema'] # replace model with ema805 for k in 'optimizer', 'training_results', 'wandb_id', 'ema', 'updates': # keys806 x[k] = None807 x['epoch'] = -1808 x['model'].half() # to FP16809 for p in x['model'].parameters():810 p.requires_grad = False811 torch.save(x, s or f)812 mb = os.path.getsize(s or f) / 1E6 # filesize813 print(f"Optimizer stripped from {f},{(' saved as %s,' % s) if s else ''} {mb:.1f}MB")814 815 816def print_mutation(hyp, results, yaml_file='hyp_evolved.yaml', bucket=''):817 # Print mutation results to evolve.txt (for use with train.py --evolve)818 a = '%10s' * len(hyp) % tuple(hyp.keys()) # hyperparam keys819 b = '%10.3g' * len(hyp) % tuple(hyp.values()) # hyperparam values820 c = '%10.4g' * len(results) % results # results (P, R, mAP@0.5, mAP@0.5:0.95, val_losses x 3)821 print('\n%s\n%s\nEvolved fitness: %s\n' % (a, b, c))822 823 if bucket:824 url = 'gs://%s/evolve.txt' % bucket825 if gsutil_getsize(url) > (os.path.getsize('evolve.txt') if os.path.exists('evolve.txt') else 0):826 os.system('gsutil cp %s .' % url) # download evolve.txt if larger than local827 828 with open('evolve.txt', 'a') as f: # append result829 f.write(c + b + '\n')830 x = np.unique(np.loadtxt('evolve.txt', ndmin=2), axis=0) # load unique rows831 x = x[np.argsort(-fitness(x))] # sort832 np.savetxt('evolve.txt', x, '%10.3g') # save sort by fitness833 834 # Save yaml835 for i, k in enumerate(hyp.keys()):836 hyp[k] = float(x[0, i + 7])837 with open(yaml_file, 'w') as f:838 results = tuple(x[0, :7])839 c = '%10.4g' * len(results) % results # results (P, R, mAP@0.5, mAP@0.5:0.95, val_losses x 3)840 f.write('# Hyperparameter Evolution Results\n# Generations: %g\n# Metrics: ' % len(x) + c + '\n\n')841 yaml.dump(hyp, f, sort_keys=False)842 843 if bucket:844 os.system('gsutil cp evolve.txt %s gs://%s' % (yaml_file, bucket)) # upload845 846 847def apply_classifier(x, model, img, im0):848 # applies a second stage classifier to yolo outputs849 im0 = [im0] if isinstance(im0, np.ndarray) else im0850 for i, d in enumerate(x): # per image851 if d is not None and len(d):852 d = d.clone()853 854 # Reshape and pad cutouts855 b = xyxy2xywh(d[:, :4]) # boxes856 b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1) # rectangle to square857 b[:, 2:] = b[:, 2:] * 1.3 + 30 # pad858 d[:, :4] = xywh2xyxy(b).long()859 860 # Rescale boxes from img_size to im0 size861 scale_coords(img.shape[2:], d[:, :4], im0[i].shape)862 863 # Classes864 pred_cls1 = d[:, 5].long()865 ims = []866 for j, a in enumerate(d): # per item867 cutout = im0[i][int(a[1]):int(a[3]), int(a[0]):int(a[2])]868 im = cv2.resize(cutout, (224, 224)) # BGR869 # cv2.imwrite('test%i.jpg' % j, cutout)870 871 im = im[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, to 3x416x416872 im = np.ascontiguousarray(im, dtype=np.float32) # uint8 to float32873 im /= 255.0 # 0 - 255 to 0.0 - 1.0874 ims.append(im)875 876 pred_cls2 = model(torch.Tensor(ims).to(d.device)).argmax(1) # classifier prediction877 x[i] = x[i][pred_cls1 == pred_cls2] # retain matching class detections878 879 return x880 881 882def increment_path(path, exist_ok=True, sep=''):883 # Increment path, i.e. runs/exp --> runs/exp{sep}0, runs/exp{sep}1 etc.884 path = Path(path) # os-agnostic885 if (path.exists() and exist_ok) or (not path.exists()):886 return str(path)887 else:888 dirs = glob.glob(f"{path}{sep}*") # similar paths889 matches = [re.search(rf"%s{sep}(\d+)" % path.stem, d) for d in dirs]890 i = [int(m.groups()[0]) for m in matches if m] # indices891 n = max(i) + 1 if i else 2 # increment number892 return f"{path}{sep}{n}" # update path893 