From 86897e366312c01c2e32a004564480e4793febea Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 6 Feb 2021 10:29:32 -0800 Subject: [PATCH 01/88] Update train.py test batch_size (#2148) * Update train.py * Update loss.py --- train.py | 4 ++-- utils/loss.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/train.py b/train.py index 4ec97ae71e16..4cbd022bd231 100644 --- a/train.py +++ b/train.py @@ -190,7 +190,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): # Process 0 if rank in [-1, 0]: ema.updates = start_epoch * nb // accumulate # set EMA updates - testloader = create_dataloader(test_path, imgsz_test, total_batch_size, gs, opt, # testloader + testloader = create_dataloader(test_path, imgsz_test, batch_size * 2, gs, opt, # testloader hyp=hyp, cache=opt.cache_images and not opt.notest, rect=True, rank=-1, world_size=opt.world_size, workers=opt.workers, pad=0.5, prefix=colorstr('val: '))[0] @@ -338,7 +338,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): final_epoch = epoch + 1 == epochs if not opt.notest or final_epoch: # Calculate mAP results, maps, times = test.test(opt.data, - batch_size=total_batch_size, + batch_size=batch_size * 2, imgsz=imgsz_test, model=ema.ema, single_cls=opt.single_cls, diff --git a/utils/loss.py b/utils/loss.py index 889ddf7295da..2490d4bb7cfc 100644 --- a/utils/loss.py +++ b/utils/loss.py @@ -105,8 +105,7 @@ def __init__(self, model, autobalance=False): BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g) det = model.module.model[-1] if is_parallel(model) else model.model[-1] # Detect() module - self.balance = {3: [3.67, 1.0, 0.43], 4: [3.78, 1.0, 0.39, 0.22], 5: [3.88, 1.0, 0.37, 0.17, 0.10]}[det.nl] - # self.balance = [1.0] * det.nl + self.balance = {3: [3.67, 1.0, 0.43], 4: [4.0, 1.0, 0.25, 0.06], 5: [4.0, 1.0, 0.25, 0.06, .02]}[det.nl] self.ssi = (det.stride == 16).nonzero(as_tuple=False).item() # stride 16 index self.BCEcls, self.BCEobj, self.gr, self.hyp, self.autobalance = BCEcls, BCEobj, model.gr, h, autobalance for k in 'na', 'nc', 'nl', 'anchors': From ad839eda388dcd12b0154b6bc3eef1555a7ff8f7 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 6 Feb 2021 11:21:04 -0800 Subject: [PATCH 02/88] Update train.py (#2149) --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index 4cbd022bd231..ba48896f1e94 100644 --- a/train.py +++ b/train.py @@ -414,7 +414,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): if opt.data.endswith('coco.yaml') and nc == 80: # if COCO for conf, iou, save_json in ([0.25, 0.45, False], [0.001, 0.65, True]): # speed, mAP tests results, _, _ = test.test(opt.data, - batch_size=total_batch_size, + batch_size=batch_size * 2, imgsz=imgsz_test, conf_thres=conf, iou_thres=iou, From 6b634c6b8749335d0c25009f0b6fec4dd619d084 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 6 Feb 2021 11:26:54 -0800 Subject: [PATCH 03/88] Linear LR scheduler option (#2150) * Linear LR scheduler option * Update train.py --- train.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/train.py b/train.py index ba48896f1e94..4065e1f149ef 100644 --- a/train.py +++ b/train.py @@ -120,7 +120,10 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): # Scheduler https://arxiv.org/pdf/1812.01187.pdf # https://pytorch.org/docs/stable/_modules/torch/optim/lr_scheduler.html#OneCycleLR - lf = one_cycle(1, hyp['lrf'], epochs) # cosine 1->hyp['lrf'] + if opt.linear_lr: + lf = lambda x: (1 - x / (epochs - 1)) * (1.0 - hyp['lrf']) + hyp['lrf'] # linear + else: + lf = one_cycle(1, hyp['lrf'], epochs) # cosine 1->hyp['lrf'] scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) # plot_lr_scheduler(optimizer, scheduler, epochs) @@ -464,6 +467,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') parser.add_argument('--quad', action='store_true', help='quad dataloader') + parser.add_argument('--linear-lr', action='store_true', help='linear LR') opt = parser.parse_args() # Set DDP variables From a5359f6c3288c0549193681632c8e9b324e81017 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 6 Feb 2021 22:13:39 -0800 Subject: [PATCH 04/88] Update data-autodownload background tasks (#2154) * Update get_coco.sh * Update get_voc.sh --- data/scripts/get_coco.sh | 9 +++++---- data/scripts/get_voc.sh | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/data/scripts/get_coco.sh b/data/scripts/get_coco.sh index b0df905c8525..02634c000dfe 100755 --- a/data/scripts/get_coco.sh +++ b/data/scripts/get_coco.sh @@ -10,8 +10,9 @@ # Download/unzip labels d='../' # unzip directory url=https://github.com/ultralytics/yolov5/releases/download/v1.0/ -f='coco2017labels.zip' # 68 MB -echo 'Downloading' $url$f ' ...' && curl -L $url$f -o $f && unzip -q $f -d $d && rm $f # download, unzip, remove +f='coco2017labels.zip' # 68 MB +echo 'Downloading' $url$f ' ...' +curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & # download, unzip, remove in background # Download/unzip images d='../coco/images' # unzip directory @@ -20,7 +21,7 @@ f1='train2017.zip' # 19G, 118k images f2='val2017.zip' # 1G, 5k images f3='test2017.zip' # 7G, 41k images (optional) for f in $f1 $f2; do - echo 'Downloading' $url$f '...' && curl -L $url$f -o $f # download, (unzip, remove in background) - unzip -q $f -d $d && rm $f & + echo 'Downloading' $url$f '...' + curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & # download, unzip, remove in background done wait # finish background tasks diff --git a/data/scripts/get_voc.sh b/data/scripts/get_voc.sh index 06414b085095..13b83c28d706 100644 --- a/data/scripts/get_voc.sh +++ b/data/scripts/get_voc.sh @@ -18,8 +18,8 @@ f1=VOCtrainval_06-Nov-2007.zip # 446MB, 5012 images f2=VOCtest_06-Nov-2007.zip # 438MB, 4953 images f3=VOCtrainval_11-May-2012.zip # 1.95GB, 17126 images for f in $f3 $f2 $f1; do - echo 'Downloading' $url$f '...' && curl -L $url$f -o $f # download, (unzip, remove in background) - unzip -q $f -d $d && rm $f & + echo 'Downloading' $url$f '...' + curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & # download, unzip, remove in background done wait # finish background tasks From c32b0aff76521ddf1d230921c5069b929c9dc161 Mon Sep 17 00:00:00 2001 From: ab-101 <56578530+ab-101@users.noreply.github.com> Date: Tue, 9 Feb 2021 12:13:40 +0500 Subject: [PATCH 05/88] Update detect.py (#2167) Without this cv2.imshow opens a window but nothing is visible --- detect.py | 1 + 1 file changed, 1 insertion(+) diff --git a/detect.py b/detect.py index f9085e670916..3f1d6c521b67 100644 --- a/detect.py +++ b/detect.py @@ -118,6 +118,7 @@ def detect(save_img=False): # Stream results if view_img: cv2.imshow(str(p), im0) + cv2.waitKey(1) # 1 millisecond # Save results (image with detections) if save_img: From ace3e02e406307ed0cebc25aebe15835767107b2 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Feb 2021 22:03:29 -0800 Subject: [PATCH 06/88] Update requirements.txt (#2173) --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index d22b42f5d786..cb50cf8f32e1 100755 --- a/requirements.txt +++ b/requirements.txt @@ -21,8 +21,8 @@ seaborn>=0.11.0 pandas # export -------------------------------------- -# coremltools==4.0 -# onnx>=1.8.0 +# coremltools>=4.1 +# onnx>=1.8.1 # scikit-learn==0.19.2 # for coreml quantization # extras -------------------------------------- From c9bda112aebaa0be846864f9d224191d0e19d419 Mon Sep 17 00:00:00 2001 From: Transigent Date: Wed, 10 Feb 2021 18:16:38 +1000 Subject: [PATCH 07/88] Update utils/datasets.py to support .webp files (#2174) Simply added 'webp' as an image format to the img_formats array so that webp image files can be used as training data. --- utils/datasets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/datasets.py b/utils/datasets.py index 7a8f073608cb..05c8fdbf4c4f 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -25,7 +25,7 @@ # Parameters help_url = 'https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data' -img_formats = ['bmp', 'jpg', 'jpeg', 'png', 'tif', 'tiff', 'dng'] # acceptable image suffixes +img_formats = ['bmp', 'jpg', 'jpeg', 'png', 'tif', 'tiff', 'dng', 'webp'] # acceptable image suffixes vid_formats = ['mov', 'avi', 'mp4', 'mpg', 'mpeg', 'm4v', 'wmv', 'mkv'] # acceptable video suffixes logger = logging.getLogger(__name__) From a5d5f9262d21556b0f91a6facaef77708ca4cbc4 Mon Sep 17 00:00:00 2001 From: NanoCode012 Date: Thu, 11 Feb 2021 04:01:48 +0700 Subject: [PATCH 08/88] Changed socket port and added timeout (#2176) --- utils/general.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/general.py b/utils/general.py index bbc0f32b8425..f979a05c6e49 100755 --- a/utils/general.py +++ b/utils/general.py @@ -51,7 +51,7 @@ def check_online(): # Check internet connectivity import socket try: - socket.create_connection(("1.1.1.1", 53)) # check host accesability + socket.create_connection(("1.1.1.1", 443), 5) # check host accesability return True except OSError: return False From 404749a33cc29d119f54b2ce35bf3b33a847a487 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 10 Feb 2021 16:10:43 -0800 Subject: [PATCH 09/88] PyTorch Hub results.save('path/to/dir') (#2179) --- models/common.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/models/common.py b/models/common.py index e8adb66293d5..7cfea01f223e 100644 --- a/models/common.py +++ b/models/common.py @@ -1,6 +1,7 @@ # This file contains modules common to various models import math +from pathlib import Path import numpy as np import requests @@ -241,7 +242,7 @@ def __init__(self, imgs, pred, names=None): self.xywhn = [x / g for x, g in zip(self.xywh, gn)] # xywh normalized self.n = len(self.pred) - def display(self, pprint=False, show=False, save=False, render=False): + def display(self, pprint=False, show=False, save=False, render=False, save_dir=''): colors = color_list() for i, (img, pred) in enumerate(zip(self.imgs, self.pred)): str = f'image {i + 1}/{len(self.pred)}: {img.shape[0]}x{img.shape[1]} ' @@ -259,7 +260,7 @@ def display(self, pprint=False, show=False, save=False, render=False): if show: img.show(f'image {i}') # show if save: - f = f'results{i}.jpg' + f = Path(save_dir) / f'results{i}.jpg' img.save(f) # save print(f"{'Saving' * (i == 0)} {f},", end='' if i < self.n - 1 else ' done.\n') if render: @@ -271,8 +272,8 @@ def print(self): def show(self): self.display(show=True) # show results - def save(self): - self.display(save=True) # save results + def save(self, save_dir=''): + self.display(save=True, save_dir=save_dir) # save results def render(self): self.display(render=True) # render results From bdd88e1ed7c3d3c703f477e574a0db376104e0b6 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 11 Feb 2021 21:22:45 -0800 Subject: [PATCH 10/88] YOLOv5 Segmentation Dataloader Updates (#2188) * Update C3 module * Update C3 module * Update C3 module * Update C3 module * update * update * update * update * update * update * update * update * update * updates * updates * updates * updates * updates * updates * updates * updates * updates * updates * update * update * update * update * updates * updates * updates * updates * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update datasets * update * update * update * update attempt_downlaod() * merge * merge * update * update * update * update * update * update * update * update * update * update * parameterize eps * comments * gs-multiple * update * max_nms implemented * Create one_cycle() function * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * GitHub API rate limit fix * update * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * astuple * epochs * update * update * ComputeLoss() * update * update * update * update * update * update * update * update * update * update * update * merge * merge * merge * merge * update * update * update * update * commit=tag == tags[-1] * Update cudnn.benchmark * update * update * update * updates * updates * updates * updates * updates * updates * updates * update * update * update * update * update * mosaic9 * update * update * update * update * update * update * institute cache versioning * only display on existing cache * reverse cache exists booleans --- data/scripts/get_coco.sh | 2 +- utils/datasets.py | 134 ++++++++++++++++++++++----------------- utils/general.py | 36 ++++++++++- utils/loss.py | 2 +- 4 files changed, 113 insertions(+), 61 deletions(-) diff --git a/data/scripts/get_coco.sh b/data/scripts/get_coco.sh index 02634c000dfe..bbb1e9291d5b 100755 --- a/data/scripts/get_coco.sh +++ b/data/scripts/get_coco.sh @@ -10,7 +10,7 @@ # Download/unzip labels d='../' # unzip directory url=https://github.com/ultralytics/yolov5/releases/download/v1.0/ -f='coco2017labels.zip' # 68 MB +f='coco2017labels.zip' # or 'coco2017labels-segments.zip', 68 MB echo 'Downloading' $url$f ' ...' curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & # download, unzip, remove in background diff --git a/utils/datasets.py b/utils/datasets.py index 05c8fdbf4c4f..29a8812a20a2 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -20,7 +20,8 @@ from torch.utils.data import Dataset from tqdm import tqdm -from utils.general import xyxy2xywh, xywh2xyxy, xywhn2xyxy, clean_str +from utils.general import xyxy2xywh, xywh2xyxy, xywhn2xyxy, xyn2xy, segment2box, segments2boxes, resample_segments, \ + clean_str from utils.torch_utils import torch_distributed_zero_first # Parameters @@ -374,21 +375,23 @@ def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, r self.label_files = img2label_paths(self.img_files) # labels cache_path = (p if p.is_file() else Path(self.label_files[0]).parent).with_suffix('.cache') # cached labels if cache_path.is_file(): - cache = torch.load(cache_path) # load - if cache['hash'] != get_hash(self.label_files + self.img_files) or 'results' not in cache: # changed - cache = self.cache_labels(cache_path, prefix) # re-cache + cache, exists = torch.load(cache_path), True # load + if cache['hash'] != get_hash(self.label_files + self.img_files) or 'version' not in cache: # changed + cache, exists = self.cache_labels(cache_path, prefix), False # re-cache else: - cache = self.cache_labels(cache_path, prefix) # cache + cache, exists = self.cache_labels(cache_path, prefix), False # cache # Display cache - [nf, nm, ne, nc, n] = cache.pop('results') # found, missing, empty, corrupted, total - desc = f"Scanning '{cache_path}' for images and labels... {nf} found, {nm} missing, {ne} empty, {nc} corrupted" - tqdm(None, desc=prefix + desc, total=n, initial=n) + nf, nm, ne, nc, n = cache.pop('results') # found, missing, empty, corrupted, total + if exists: + d = f"Scanning '{cache_path}' for images and labels... {nf} found, {nm} missing, {ne} empty, {nc} corrupted" + tqdm(None, desc=prefix + d, total=n, initial=n) # display cache results assert nf > 0 or not augment, f'{prefix}No labels in {cache_path}. Can not train without labels. See {help_url}' # Read cache cache.pop('hash') # remove hash - labels, shapes = zip(*cache.values()) + cache.pop('version') # remove version + labels, shapes, self.segments = zip(*cache.values()) self.labels = list(labels) self.shapes = np.array(shapes, dtype=np.float64) self.img_files = list(cache.keys()) # update @@ -451,6 +454,7 @@ def cache_labels(self, path=Path('./labels.cache'), prefix=''): im = Image.open(im_file) im.verify() # PIL verify shape = exif_size(im) # image size + segments = [] # instance segments assert (shape[0] > 9) & (shape[1] > 9), f'image size {shape} <10 pixels' assert im.format.lower() in img_formats, f'invalid image format {im.format}' @@ -458,7 +462,12 @@ def cache_labels(self, path=Path('./labels.cache'), prefix=''): if os.path.isfile(lb_file): nf += 1 # label found with open(lb_file, 'r') as f: - l = np.array([x.split() for x in f.read().strip().splitlines()], dtype=np.float32) # labels + l = [x.split() for x in f.read().strip().splitlines()] + if any([len(x) > 8 for x in l]): # is segment + classes = np.array([x[0] for x in l], dtype=np.float32) + segments = [np.array(x[1:], dtype=np.float32).reshape(-1, 2) for x in l] # (cls, xy1...) + l = np.concatenate((classes.reshape(-1, 1), segments2boxes(segments)), 1) # (cls, xywh) + l = np.array(l, dtype=np.float32) if len(l): assert l.shape[1] == 5, 'labels require 5 columns each' assert (l >= 0).all(), 'negative labels' @@ -470,7 +479,7 @@ def cache_labels(self, path=Path('./labels.cache'), prefix=''): else: nm += 1 # label missing l = np.zeros((0, 5), dtype=np.float32) - x[im_file] = [l, shape] + x[im_file] = [l, shape, segments] except Exception as e: nc += 1 print(f'{prefix}WARNING: Ignoring corrupted image and/or label {im_file}: {e}') @@ -482,7 +491,8 @@ def cache_labels(self, path=Path('./labels.cache'), prefix=''): print(f'{prefix}WARNING: No labels found in {path}. See {help_url}') x['hash'] = get_hash(self.label_files + self.img_files) - x['results'] = [nf, nm, ne, nc, i + 1] + x['results'] = nf, nm, ne, nc, i + 1 + x['version'] = 0.1 # cache version torch.save(x, path) # save for next time logging.info(f'{prefix}New cache created: {path}') return x @@ -652,7 +662,7 @@ def hist_equalize(img, clahe=True, bgr=False): def load_mosaic(self, index): # loads images in a 4-mosaic - labels4 = [] + labels4, segments4 = [], [] s = self.img_size yc, xc = [int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border] # mosaic center x, y indices = [index] + [self.indices[random.randint(0, self.n - 1)] for _ in range(3)] # 3 additional image indices @@ -680,19 +690,21 @@ def load_mosaic(self, index): padh = y1a - y1b # Labels - labels = self.labels[index].copy() + labels, segments = self.labels[index].copy(), self.segments[index].copy() if labels.size: labels[:, 1:] = xywhn2xyxy(labels[:, 1:], w, h, padw, padh) # normalized xywh to pixel xyxy format + segments = [xyn2xy(x, w, h, padw, padh) for x in segments] labels4.append(labels) + segments4.extend(segments) # Concat/clip labels - if len(labels4): - labels4 = np.concatenate(labels4, 0) - np.clip(labels4[:, 1:], 0, 2 * s, out=labels4[:, 1:]) # use with random_perspective - # img4, labels4 = replicate(img4, labels4) # replicate + labels4 = np.concatenate(labels4, 0) + for x in (labels4[:, 1:], *segments4): + np.clip(x, 0, 2 * s, out=x) # clip when using random_perspective() + # img4, labels4 = replicate(img4, labels4) # replicate # Augment - img4, labels4 = random_perspective(img4, labels4, + img4, labels4 = random_perspective(img4, labels4, segments4, degrees=self.hyp['degrees'], translate=self.hyp['translate'], scale=self.hyp['scale'], @@ -706,7 +718,7 @@ def load_mosaic(self, index): def load_mosaic9(self, index): # loads images in a 9-mosaic - labels9 = [] + labels9, segments9 = [], [] s = self.img_size indices = [index] + [self.indices[random.randint(0, self.n - 1)] for _ in range(8)] # 8 additional image indices for i, index in enumerate(indices): @@ -739,30 +751,34 @@ def load_mosaic9(self, index): x1, y1, x2, y2 = [max(x, 0) for x in c] # allocate coords # Labels - labels = self.labels[index].copy() + labels, segments = self.labels[index].copy(), self.segments[index].copy() if labels.size: labels[:, 1:] = xywhn2xyxy(labels[:, 1:], w, h, padx, pady) # normalized xywh to pixel xyxy format + segments = [xyn2xy(x, w, h, padx, pady) for x in segments] labels9.append(labels) + segments9.extend(segments) # Image img9[y1:y2, x1:x2] = img[y1 - pady:, x1 - padx:] # img9[ymin:ymax, xmin:xmax] hp, wp = h, w # height, width previous # Offset - yc, xc = [int(random.uniform(0, s)) for x in self.mosaic_border] # mosaic center x, y + yc, xc = [int(random.uniform(0, s)) for _ in self.mosaic_border] # mosaic center x, y img9 = img9[yc:yc + 2 * s, xc:xc + 2 * s] # Concat/clip labels - if len(labels9): - labels9 = np.concatenate(labels9, 0) - labels9[:, [1, 3]] -= xc - labels9[:, [2, 4]] -= yc + labels9 = np.concatenate(labels9, 0) + labels9[:, [1, 3]] -= xc + labels9[:, [2, 4]] -= yc + c = np.array([xc, yc]) # centers + segments9 = [x - c for x in segments9] - np.clip(labels9[:, 1:], 0, 2 * s, out=labels9[:, 1:]) # use with random_perspective - # img9, labels9 = replicate(img9, labels9) # replicate + for x in (labels9[:, 1:], *segments9): + np.clip(x, 0, 2 * s, out=x) # clip when using random_perspective() + # img9, labels9 = replicate(img9, labels9) # replicate # Augment - img9, labels9 = random_perspective(img9, labels9, + img9, labels9 = random_perspective(img9, labels9, segments9, degrees=self.hyp['degrees'], translate=self.hyp['translate'], scale=self.hyp['scale'], @@ -823,7 +839,8 @@ def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scale return img, ratio, (dw, dh) -def random_perspective(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10, perspective=0.0, border=(0, 0)): +def random_perspective(img, targets=(), segments=(), degrees=10, translate=.1, scale=.1, shear=10, perspective=0.0, + border=(0, 0)): # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10)) # targets = [cls, xyxy] @@ -875,37 +892,38 @@ def random_perspective(img, targets=(), degrees=10, translate=.1, scale=.1, shea # Transform label coordinates n = len(targets) if n: - # warp points - xy = np.ones((n * 4, 3)) - xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2) # x1y1, x2y2, x1y2, x2y1 - xy = xy @ M.T # transform - if perspective: - xy = (xy[:, :2] / xy[:, 2:3]).reshape(n, 8) # rescale - else: # affine - xy = xy[:, :2].reshape(n, 8) - - # create new boxes - x = xy[:, [0, 2, 4, 6]] - y = xy[:, [1, 3, 5, 7]] - xy = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T - - # # apply angle-based reduction of bounding boxes - # radians = a * math.pi / 180 - # reduction = max(abs(math.sin(radians)), abs(math.cos(radians))) ** 0.5 - # x = (xy[:, 2] + xy[:, 0]) / 2 - # y = (xy[:, 3] + xy[:, 1]) / 2 - # w = (xy[:, 2] - xy[:, 0]) * reduction - # h = (xy[:, 3] - xy[:, 1]) * reduction - # xy = np.concatenate((x - w / 2, y - h / 2, x + w / 2, y + h / 2)).reshape(4, n).T - - # clip boxes - xy[:, [0, 2]] = xy[:, [0, 2]].clip(0, width) - xy[:, [1, 3]] = xy[:, [1, 3]].clip(0, height) + use_segments = any(x.any() for x in segments) + new = np.zeros((n, 4)) + if use_segments: # warp segments + segments = resample_segments(segments) # upsample + for i, segment in enumerate(segments): + xy = np.ones((len(segment), 3)) + xy[:, :2] = segment + xy = xy @ M.T # transform + xy = xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2] # perspective rescale or affine + + # clip + new[i] = segment2box(xy, width, height) + + else: # warp boxes + xy = np.ones((n * 4, 3)) + xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2) # x1y1, x2y2, x1y2, x2y1 + xy = xy @ M.T # transform + xy = (xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2]).reshape(n, 8) # perspective rescale or affine + + # create new boxes + x = xy[:, [0, 2, 4, 6]] + y = xy[:, [1, 3, 5, 7]] + new = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T + + # clip + new[:, [0, 2]] = new[:, [0, 2]].clip(0, width) + new[:, [1, 3]] = new[:, [1, 3]].clip(0, height) # filter candidates - i = box_candidates(box1=targets[:, 1:5].T * s, box2=xy.T) + i = box_candidates(box1=targets[:, 1:5].T * s, box2=new.T, area_thr=0.01 if use_segments else 0.10) targets = targets[i] - targets[:, 1:5] = xy[i] + targets[:, 1:5] = new[i] return img, targets diff --git a/utils/general.py b/utils/general.py index f979a05c6e49..24807483f5f4 100755 --- a/utils/general.py +++ b/utils/general.py @@ -225,7 +225,7 @@ def xywh2xyxy(x): return y -def xywhn2xyxy(x, w=640, h=640, padw=32, padh=32): +def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0): # Convert nx4 boxes from [x, y, w, h] normalized to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) y[:, 0] = w * (x[:, 0] - x[:, 2] / 2) + padw # top left x @@ -235,6 +235,40 @@ def xywhn2xyxy(x, w=640, h=640, padw=32, padh=32): return y +def xyn2xy(x, w=640, h=640, padw=0, padh=0): + # Convert normalized segments into pixel segments, shape (n,2) + y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) + y[:, 0] = w * x[:, 0] + padw # top left x + y[:, 1] = h * x[:, 1] + padh # top left y + return y + + +def segment2box(segment, width=640, height=640): + # Convert 1 segment label to 1 box label, applying inside-image constraint, i.e. (xy1, xy2, ...) to (xyxy) + x, y = segment.T # segment xy + inside = (x >= 0) & (y >= 0) & (x <= width) & (y <= height) + x, y, = x[inside], y[inside] + return np.array([x.min(), y.min(), x.max(), y.max()]) if any(x) else np.zeros((1, 4)) # cls, xyxy + + +def segments2boxes(segments): + # Convert segment labels to box labels, i.e. (cls, xy1, xy2, ...) to (cls, xywh) + boxes = [] + for s in segments: + x, y = s.T # segment xy + boxes.append([x.min(), y.min(), x.max(), y.max()]) # cls, xyxy + return xyxy2xywh(np.array(boxes)) # cls, xywh + + +def resample_segments(segments, n=1000): + # Up-sample an (n,2) segment + for i, s in enumerate(segments): + x = np.linspace(0, len(s) - 1, n) + xp = np.arange(len(s)) + segments[i] = np.concatenate([np.interp(x, xp, s[:, i]) for i in range(2)]).reshape(2, -1).T # segment xy + return segments + + def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None): # Rescale coords (xyxy) from img1_shape to img0_shape if ratio_pad is None: # calculate from img0_shape diff --git a/utils/loss.py b/utils/loss.py index 2490d4bb7cfc..481d25e207f2 100644 --- a/utils/loss.py +++ b/utils/loss.py @@ -105,7 +105,7 @@ def __init__(self, model, autobalance=False): BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g) det = model.module.model[-1] if is_parallel(model) else model.model[-1] # Detect() module - self.balance = {3: [3.67, 1.0, 0.43], 4: [4.0, 1.0, 0.25, 0.06], 5: [4.0, 1.0, 0.25, 0.06, .02]}[det.nl] + self.balance = {3: [4.0, 1.0, 0.4], 4: [4.0, 1.0, 0.25, 0.06], 5: [4.0, 1.0, 0.25, 0.06, .02]}[det.nl] self.ssi = (det.stride == 16).nonzero(as_tuple=False).item() # stride 16 index self.BCEcls, self.BCEobj, self.gr, self.hyp, self.autobalance = BCEcls, BCEobj, model.gr, h, autobalance for k in 'na', 'nc', 'nl', 'anchors': From 17ac94b7968703e708bfeb7274de755c4b2f1f43 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 11 Feb 2021 22:39:37 -0800 Subject: [PATCH 11/88] Created using Colaboratory --- tutorial.ipynb | 182 ++++++++++++++++++++++++------------------------- 1 file changed, 90 insertions(+), 92 deletions(-) diff --git a/tutorial.ipynb b/tutorial.ipynb index 3f7133f4f7d7..7587d9f536fe 100644 --- a/tutorial.ipynb +++ b/tutorial.ipynb @@ -16,7 +16,7 @@ "accelerator": "GPU", "widgets": { "application/vnd.jupyter.widget-state+json": { - "811fd52fef65422c8267bafcde8a2c3d": { + "1f8e9b8ebded4175b2eaa9f75c3ceb00": { "model_module": "@jupyter-widgets/controls", "model_name": "HBoxModel", "state": { @@ -28,15 +28,15 @@ "_view_count": null, "_view_module_version": "1.5.0", "box_style": "", - "layout": "IPY_MODEL_8f41b90117224eef9133a9c3a103dbba", + "layout": "IPY_MODEL_0a1246a73077468ab80e979cc0576cd2", "_model_module": "@jupyter-widgets/controls", "children": [ - "IPY_MODEL_ca2fb37af6ed43d4a74cdc9f2ac5c4a5", - "IPY_MODEL_29419ae5ebb9403ea73f7e5a68037bdd" + "IPY_MODEL_d327cde5a85a4a51bb8b1b3e9cf06c97", + "IPY_MODEL_d5ef1cb2cbed4b87b3c5d292ff2b0da6" ] } }, - "8f41b90117224eef9133a9c3a103dbba": { + "0a1246a73077468ab80e979cc0576cd2": { "model_module": "@jupyter-widgets/base", "model_name": "LayoutModel", "state": { @@ -87,12 +87,12 @@ "left": null } }, - "ca2fb37af6ed43d4a74cdc9f2ac5c4a5": { + "d327cde5a85a4a51bb8b1b3e9cf06c97": { "model_module": "@jupyter-widgets/controls", "model_name": "FloatProgressModel", "state": { "_view_name": "ProgressView", - "style": "IPY_MODEL_6511b4dfb10b48d1bc98bcfb3987bfa0", + "style": "IPY_MODEL_8d5dff8bca14435a88fa1814533acd85", "_dom_classes": [], "description": "100%", "_model_name": "FloatProgressModel", @@ -107,30 +107,30 @@ "min": 0, "description_tooltip": null, "_model_module": "@jupyter-widgets/controls", - "layout": "IPY_MODEL_64f0badf1a8f489885aa984dd62d37dc" + "layout": "IPY_MODEL_3d5136c19e7645ca9bc8f51ceffb2be1" } }, - "29419ae5ebb9403ea73f7e5a68037bdd": { + "d5ef1cb2cbed4b87b3c5d292ff2b0da6": { "model_module": "@jupyter-widgets/controls", "model_name": "HTMLModel", "state": { "_view_name": "HTMLView", - "style": "IPY_MODEL_f569911c5cfc4d81bb1bdfa83447afc8", + "style": "IPY_MODEL_2919396dbd4b4c8e821d12bd28665d8a", "_dom_classes": [], "description": "", "_model_name": "HTMLModel", "placeholder": "​", "_view_module": "@jupyter-widgets/controls", "_model_module_version": "1.5.0", - "value": " 781M/781M [00:23<00:00, 34.2MB/s]", + "value": " 781M/781M [00:12<00:00, 65.5MB/s]", "_view_count": null, "_view_module_version": "1.5.0", "description_tooltip": null, "_model_module": "@jupyter-widgets/controls", - "layout": "IPY_MODEL_84943ade566440aaa2dcf3b3b27e7074" + "layout": "IPY_MODEL_6feb16f2b2fa4021b1a271e1dd442d04" } }, - "6511b4dfb10b48d1bc98bcfb3987bfa0": { + "8d5dff8bca14435a88fa1814533acd85": { "model_module": "@jupyter-widgets/controls", "model_name": "ProgressStyleModel", "state": { @@ -145,7 +145,7 @@ "_model_module": "@jupyter-widgets/controls" } }, - "64f0badf1a8f489885aa984dd62d37dc": { + "3d5136c19e7645ca9bc8f51ceffb2be1": { "model_module": "@jupyter-widgets/base", "model_name": "LayoutModel", "state": { @@ -196,7 +196,7 @@ "left": null } }, - "f569911c5cfc4d81bb1bdfa83447afc8": { + "2919396dbd4b4c8e821d12bd28665d8a": { "model_module": "@jupyter-widgets/controls", "model_name": "DescriptionStyleModel", "state": { @@ -210,7 +210,7 @@ "_model_module": "@jupyter-widgets/controls" } }, - "84943ade566440aaa2dcf3b3b27e7074": { + "6feb16f2b2fa4021b1a271e1dd442d04": { "model_module": "@jupyter-widgets/base", "model_name": "LayoutModel", "state": { @@ -261,7 +261,7 @@ "left": null } }, - "8501ed1563e4452eac9df6b7a66e8f8c": { + "e6459e0bcee449b090fc9807672725bc": { "model_module": "@jupyter-widgets/controls", "model_name": "HBoxModel", "state": { @@ -273,15 +273,15 @@ "_view_count": null, "_view_module_version": "1.5.0", "box_style": "", - "layout": "IPY_MODEL_d2bb96801e1f46f4a58e02534f7026ff", + "layout": "IPY_MODEL_c341e1d3bf3b40d1821ce392eb966c68", "_model_module": "@jupyter-widgets/controls", "children": [ - "IPY_MODEL_468a796ef06b4a24bcba6fbd4a0a8db5", - "IPY_MODEL_42ad5c1ea7be4835bffebf90642178f1" + "IPY_MODEL_660afee173694231a6dce3cd94df6cae", + "IPY_MODEL_261218485cef48df961519dde5edfcbe" ] } }, - "d2bb96801e1f46f4a58e02534f7026ff": { + "c341e1d3bf3b40d1821ce392eb966c68": { "model_module": "@jupyter-widgets/base", "model_name": "LayoutModel", "state": { @@ -332,12 +332,12 @@ "left": null } }, - "468a796ef06b4a24bcba6fbd4a0a8db5": { + "660afee173694231a6dce3cd94df6cae": { "model_module": "@jupyter-widgets/controls", "model_name": "FloatProgressModel", "state": { "_view_name": "ProgressView", - "style": "IPY_MODEL_c58b5536d98f4814831934e9c30c4d78", + "style": "IPY_MODEL_32736d503c06497abfae8c0421918255", "_dom_classes": [], "description": "100%", "_model_name": "FloatProgressModel", @@ -352,30 +352,30 @@ "min": 0, "description_tooltip": null, "_model_module": "@jupyter-widgets/controls", - "layout": "IPY_MODEL_505597101151486ea29e9ab754544d27" + "layout": "IPY_MODEL_e257738711f54d5280c8393d9d3dce1c" } }, - "42ad5c1ea7be4835bffebf90642178f1": { + "261218485cef48df961519dde5edfcbe": { "model_module": "@jupyter-widgets/controls", "model_name": "HTMLModel", "state": { "_view_name": "HTMLView", - "style": "IPY_MODEL_de6e7b4b4a1c408c9f89d89b07a13bcd", + "style": "IPY_MODEL_beb7a6fe34b840899bb79c062681696f", "_dom_classes": [], "description": "", "_model_name": "HTMLModel", "placeholder": "​", "_view_module": "@jupyter-widgets/controls", "_model_module_version": "1.5.0", - "value": " 21.1M/21.1M [00:01<00:00, 18.2MB/s]", + "value": " 21.1M/21.1M [00:00<00:00, 33.5MB/s]", "_view_count": null, "_view_module_version": "1.5.0", "description_tooltip": null, "_model_module": "@jupyter-widgets/controls", - "layout": "IPY_MODEL_f5cc9c7d4c274b2d81327ba3163c43fd" + "layout": "IPY_MODEL_e639132395d64d70b99d8b72c32f8fbb" } }, - "c58b5536d98f4814831934e9c30c4d78": { + "32736d503c06497abfae8c0421918255": { "model_module": "@jupyter-widgets/controls", "model_name": "ProgressStyleModel", "state": { @@ -390,7 +390,7 @@ "_model_module": "@jupyter-widgets/controls" } }, - "505597101151486ea29e9ab754544d27": { + "e257738711f54d5280c8393d9d3dce1c": { "model_module": "@jupyter-widgets/base", "model_name": "LayoutModel", "state": { @@ -441,7 +441,7 @@ "left": null } }, - "de6e7b4b4a1c408c9f89d89b07a13bcd": { + "beb7a6fe34b840899bb79c062681696f": { "model_module": "@jupyter-widgets/controls", "model_name": "DescriptionStyleModel", "state": { @@ -455,7 +455,7 @@ "_model_module": "@jupyter-widgets/controls" } }, - "f5cc9c7d4c274b2d81327ba3163c43fd": { + "e639132395d64d70b99d8b72c32f8fbb": { "model_module": "@jupyter-widgets/base", "model_name": "LayoutModel", "state": { @@ -550,7 +550,7 @@ "colab": { "base_uri": "https://localhost:8080/" }, - "outputId": "c6ad57c2-40b7-4764-b07d-19ee2ceaabaf" + "outputId": "ae8805a9-ce15-4e1c-f6b4-baa1c1033f56" }, "source": [ "!git clone https://github.com/ultralytics/yolov5 # clone repo\n", @@ -563,12 +563,12 @@ "clear_output()\n", "print('Setup complete. Using torch %s %s' % (torch.__version__, torch.cuda.get_device_properties(0) if torch.cuda.is_available() else 'CPU'))" ], - "execution_count": null, + "execution_count": 1, "outputs": [ { "output_type": "stream", "text": [ - "Setup complete. Using torch 1.7.0+cu101 _CudaDeviceProperties(name='Tesla V100-SXM2-16GB', major=7, minor=0, total_memory=16130MB, multi_processor_count=80)\n" + "Setup complete. Using torch 1.7.0+cu101 _CudaDeviceProperties(name='Tesla V100-SXM2-16GB', major=7, minor=0, total_memory=16160MB, multi_processor_count=80)\n" ], "name": "stdout" } @@ -672,30 +672,30 @@ "base_uri": "https://localhost:8080/", "height": 65, "referenced_widgets": [ - "811fd52fef65422c8267bafcde8a2c3d", - "8f41b90117224eef9133a9c3a103dbba", - "ca2fb37af6ed43d4a74cdc9f2ac5c4a5", - "29419ae5ebb9403ea73f7e5a68037bdd", - "6511b4dfb10b48d1bc98bcfb3987bfa0", - "64f0badf1a8f489885aa984dd62d37dc", - "f569911c5cfc4d81bb1bdfa83447afc8", - "84943ade566440aaa2dcf3b3b27e7074" + "1f8e9b8ebded4175b2eaa9f75c3ceb00", + "0a1246a73077468ab80e979cc0576cd2", + "d327cde5a85a4a51bb8b1b3e9cf06c97", + "d5ef1cb2cbed4b87b3c5d292ff2b0da6", + "8d5dff8bca14435a88fa1814533acd85", + "3d5136c19e7645ca9bc8f51ceffb2be1", + "2919396dbd4b4c8e821d12bd28665d8a", + "6feb16f2b2fa4021b1a271e1dd442d04" ] }, - "outputId": "59a7a546-8492-492e-861d-70a2c85a6794" + "outputId": "d6ace7c6-1be5-41ff-d607-1c716b88d298" }, "source": [ "# Download COCO val2017\n", "torch.hub.download_url_to_file('https://github.com/ultralytics/yolov5/releases/download/v1.0/coco2017val.zip', 'tmp.zip')\n", "!unzip -q tmp.zip -d ../ && rm tmp.zip" ], - "execution_count": null, + "execution_count": 2, "outputs": [ { "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "811fd52fef65422c8267bafcde8a2c3d", + "model_id": "1f8e9b8ebded4175b2eaa9f75c3ceb00", "version_minor": 0, "version_major": 2 }, @@ -723,46 +723,45 @@ "colab": { "base_uri": "https://localhost:8080/" }, - "outputId": "427c211e-e283-4e87-f7b3-7b8dfb11a4a5" + "outputId": "cc25f70c-0a11-44f6-cc44-e92c5083488c" }, "source": [ "# Run YOLOv5x on COCO val2017\n", "!python test.py --weights yolov5x.pt --data coco.yaml --img 640 --iou 0.65" ], - "execution_count": null, + "execution_count": 3, "outputs": [ { "output_type": "stream", "text": [ "Namespace(augment=False, batch_size=32, conf_thres=0.001, data='./data/coco.yaml', device='', exist_ok=False, img_size=640, iou_thres=0.65, name='exp', project='runs/test', save_conf=False, save_hybrid=False, save_json=True, save_txt=False, single_cls=False, task='val', verbose=False, weights=['yolov5x.pt'])\n", - "YOLOv5 v4.0-21-gb26a2f6 torch 1.7.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16130.5MB)\n", + "YOLOv5 v4.0-75-gbdd88e1 torch 1.7.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n", "\n", "Downloading https://github.com/ultralytics/yolov5/releases/download/v4.0/yolov5x.pt to yolov5x.pt...\n", - "100% 168M/168M [00:05<00:00, 31.9MB/s]\n", + "100% 168M/168M [00:04<00:00, 39.7MB/s]\n", "\n", "Fusing layers... \n", "Model Summary: 476 layers, 87730285 parameters, 0 gradients, 218.8 GFLOPS\n", - "\u001b[34m\u001b[1mval: \u001b[0mScanning '../coco/labels/val2017' for images and labels... 4952 found, 48 missing, 0 empty, 0 corrupted: 100% 5000/5000 [00:01<00:00, 2791.81it/s]\n", - "\u001b[34m\u001b[1mval: \u001b[0mNew cache created: ../coco/labels/val2017.cache\n", - "\u001b[34m\u001b[1mval: \u001b[0mScanning '../coco/labels/val2017.cache' for images and labels... 4952 found, 48 missing, 0 empty, 0 corrupted: 100% 5000/5000 [00:00<00:00, 13332180.55it/s]\n", - " Class Images Targets P R mAP@.5 mAP@.5:.95: 100% 157/157 [01:30<00:00, 1.73it/s]\n", - " all 5e+03 3.63e+04 0.419 0.765 0.68 0.486\n", - "Speed: 5.2/2.0/7.2 ms inference/NMS/total per 640x640 image at batch-size 32\n", + "\u001b[34m\u001b[1mval: \u001b[0mScanning '../coco/val2017' for images and labels... 4952 found, 48 missing, 0 empty, 0 corrupted: 100% 5000/5000 [00:01<00:00, 2824.78it/s]\n", + "\u001b[34m\u001b[1mval: \u001b[0mNew cache created: ../coco/val2017.cache\n", + " Class Images Targets P R mAP@.5 mAP@.5:.95: 100% 157/157 [01:33<00:00, 1.68it/s]\n", + " all 5e+03 3.63e+04 0.749 0.619 0.68 0.486\n", + "Speed: 5.2/2.0/7.3 ms inference/NMS/total per 640x640 image at batch-size 32\n", "\n", "Evaluating pycocotools mAP... saving runs/test/exp/yolov5x_predictions.json...\n", "loading annotations into memory...\n", - "Done (t=0.41s)\n", + "Done (t=0.44s)\n", "creating index...\n", "index created!\n", "Loading and preparing results...\n", - "DONE (t=5.26s)\n", + "DONE (t=4.47s)\n", "creating index...\n", "index created!\n", "Running per image evaluation...\n", "Evaluate annotation type *bbox*\n", - "DONE (t=93.97s).\n", + "DONE (t=94.87s).\n", "Accumulating evaluation results...\n", - "DONE (t=15.06s).\n", + "DONE (t=15.96s).\n", " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.501\n", " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.687\n", " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.544\n", @@ -837,30 +836,30 @@ "base_uri": "https://localhost:8080/", "height": 65, "referenced_widgets": [ - "8501ed1563e4452eac9df6b7a66e8f8c", - "d2bb96801e1f46f4a58e02534f7026ff", - "468a796ef06b4a24bcba6fbd4a0a8db5", - "42ad5c1ea7be4835bffebf90642178f1", - "c58b5536d98f4814831934e9c30c4d78", - "505597101151486ea29e9ab754544d27", - "de6e7b4b4a1c408c9f89d89b07a13bcd", - "f5cc9c7d4c274b2d81327ba3163c43fd" + "e6459e0bcee449b090fc9807672725bc", + "c341e1d3bf3b40d1821ce392eb966c68", + "660afee173694231a6dce3cd94df6cae", + "261218485cef48df961519dde5edfcbe", + "32736d503c06497abfae8c0421918255", + "e257738711f54d5280c8393d9d3dce1c", + "beb7a6fe34b840899bb79c062681696f", + "e639132395d64d70b99d8b72c32f8fbb" ] }, - "outputId": "c68a3db4-1314-46b4-9e52-83532eb65749" + "outputId": "e8b7d5b3-a71e-4446-eec2-ad13419cf700" }, "source": [ "# Download COCO128\n", "torch.hub.download_url_to_file('https://github.com/ultralytics/yolov5/releases/download/v1.0/coco128.zip', 'tmp.zip')\n", "!unzip -q tmp.zip -d ../ && rm tmp.zip" ], - "execution_count": null, + "execution_count": 4, "outputs": [ { "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "8501ed1563e4452eac9df6b7a66e8f8c", + "model_id": "e6459e0bcee449b090fc9807672725bc", "version_minor": 0, "version_major": 2 }, @@ -925,27 +924,27 @@ "colab": { "base_uri": "https://localhost:8080/" }, - "outputId": "6af7116a-01ab-4b94-e5d7-b37c17dc95de" + "outputId": "38e51b29-2df4-4f00-cde8-5f6e4a34da9e" }, "source": [ "# Train YOLOv5s on COCO128 for 3 epochs\n", "!python train.py --img 640 --batch 16 --epochs 3 --data coco128.yaml --weights yolov5s.pt --nosave --cache" ], - "execution_count": null, + "execution_count": 5, "outputs": [ { "output_type": "stream", "text": [ "\u001b[34m\u001b[1mgithub: \u001b[0mup to date with https://github.com/ultralytics/yolov5 ✅\n", - "YOLOv5 v4.0-21-gb26a2f6 torch 1.7.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16130.5MB)\n", + "YOLOv5 v4.0-75-gbdd88e1 torch 1.7.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n", "\n", - "Namespace(adam=False, batch_size=16, bucket='', cache_images=True, cfg='', data='./data/coco128.yaml', device='', epochs=3, evolve=False, exist_ok=False, global_rank=-1, hyp='data/hyp.scratch.yaml', image_weights=False, img_size=[640, 640], local_rank=-1, log_artifacts=False, log_imgs=16, multi_scale=False, name='exp', noautoanchor=False, nosave=True, notest=False, project='runs/train', quad=False, rect=False, resume=False, save_dir='runs/train/exp', single_cls=False, sync_bn=False, total_batch_size=16, weights='yolov5s.pt', workers=8, world_size=1)\n", + "Namespace(adam=False, batch_size=16, bucket='', cache_images=True, cfg='', data='./data/coco128.yaml', device='', epochs=3, evolve=False, exist_ok=False, global_rank=-1, hyp='data/hyp.scratch.yaml', image_weights=False, img_size=[640, 640], linear_lr=False, local_rank=-1, log_artifacts=False, log_imgs=16, multi_scale=False, name='exp', noautoanchor=False, nosave=True, notest=False, project='runs/train', quad=False, rect=False, resume=False, save_dir='runs/train/exp', single_cls=False, sync_bn=False, total_batch_size=16, weights='yolov5s.pt', workers=8, world_size=1)\n", "\u001b[34m\u001b[1mwandb: \u001b[0mInstall Weights & Biases for YOLOv5 logging with 'pip install wandb' (recommended)\n", "Start Tensorboard with \"tensorboard --logdir runs/train\", view at http://localhost:6006/\n", - "2021-01-17 19:56:03.945851: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudart.so.10.1\n", + "2021-02-12 06:38:28.027271: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudart.so.10.1\n", "\u001b[34m\u001b[1mhyperparameters: \u001b[0mlr0=0.01, lrf=0.2, momentum=0.937, weight_decay=0.0005, warmup_epochs=3.0, warmup_momentum=0.8, warmup_bias_lr=0.1, box=0.05, cls=0.5, cls_pw=1.0, obj=1.0, obj_pw=1.0, iou_t=0.2, anchor_t=4.0, fl_gamma=0.0, hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, degrees=0.0, translate=0.1, scale=0.5, shear=0.0, perspective=0.0, flipud=0.0, fliplr=0.5, mosaic=1.0, mixup=0.0\n", "Downloading https://github.com/ultralytics/yolov5/releases/download/v4.0/yolov5s.pt to yolov5s.pt...\n", - "100% 14.1M/14.1M [00:00<00:00, 15.8MB/s]\n", + "100% 14.1M/14.1M [00:01<00:00, 13.2MB/s]\n", "\n", "\n", " from n params module arguments \n", @@ -979,12 +978,11 @@ "Transferred 362/362 items from yolov5s.pt\n", "Scaled weight_decay = 0.0005\n", "Optimizer groups: 62 .bias, 62 conv.weight, 59 other\n", - "\u001b[34m\u001b[1mtrain: \u001b[0mScanning '../coco128/labels/train2017' for images and labels... 128 found, 0 missing, 2 empty, 0 corrupted: 100% 128/128 [00:00<00:00, 2647.74it/s]\n", + "\u001b[34m\u001b[1mtrain: \u001b[0mScanning '../coco128/labels/train2017' for images and labels... 128 found, 0 missing, 2 empty, 0 corrupted: 100% 128/128 [00:00<00:00, 2566.00it/s]\n", "\u001b[34m\u001b[1mtrain: \u001b[0mNew cache created: ../coco128/labels/train2017.cache\n", - "\u001b[34m\u001b[1mtrain: \u001b[0mScanning '../coco128/labels/train2017.cache' for images and labels... 128 found, 0 missing, 2 empty, 0 corrupted: 100% 128/128 [00:00<00:00, 1503840.09it/s]\n", - "\u001b[34m\u001b[1mtrain: \u001b[0mCaching images (0.1GB): 100% 128/128 [00:00<00:00, 176.03it/s]\n", - "\u001b[34m\u001b[1mval: \u001b[0mScanning '../coco128/labels/train2017.cache' for images and labels... 128 found, 0 missing, 2 empty, 0 corrupted: 100% 128/128 [00:00<00:00, 24200.82it/s]\n", - "\u001b[34m\u001b[1mval: \u001b[0mCaching images (0.1GB): 100% 128/128 [00:01<00:00, 123.25it/s]\n", + "\u001b[34m\u001b[1mtrain: \u001b[0mCaching images (0.1GB): 100% 128/128 [00:00<00:00, 175.07it/s]\n", + "\u001b[34m\u001b[1mval: \u001b[0mScanning '../coco128/labels/train2017.cache' for images and labels... 128 found, 0 missing, 2 empty, 0 corrupted: 100% 128/128 [00:00<00:00, 764773.38it/s]\n", + "\u001b[34m\u001b[1mval: \u001b[0mCaching images (0.1GB): 100% 128/128 [00:00<00:00, 128.17it/s]\n", "Plotting labels... \n", "\n", "\u001b[34m\u001b[1mautoanchor: \u001b[0mAnalyzing anchors... anchors/target = 4.26, Best Possible Recall (BPR) = 0.9946\n", @@ -994,19 +992,19 @@ "Starting training for 3 epochs...\n", "\n", " Epoch gpu_mem box obj cls total targets img_size\n", - " 0/2 3.27G 0.04357 0.06779 0.01869 0.1301 207 640: 100% 8/8 [00:04<00:00, 1.95it/s]\n", - " Class Images Targets P R mAP@.5 mAP@.5:.95: 100% 8/8 [00:05<00:00, 1.36it/s]\n", - " all 128 929 0.392 0.732 0.657 0.428\n", + " 0/2 3.27G 0.04357 0.06781 0.01869 0.1301 207 640: 100% 8/8 [00:03<00:00, 2.03it/s]\n", + " Class Images Targets P R mAP@.5 mAP@.5:.95: 100% 4/4 [00:04<00:00, 1.14s/it]\n", + " all 128 929 0.646 0.627 0.659 0.431\n", "\n", " Epoch gpu_mem box obj cls total targets img_size\n", - " 1/2 7.47G 0.04308 0.06636 0.02083 0.1303 227 640: 100% 8/8 [00:02<00:00, 3.88it/s]\n", - " Class Images Targets P R mAP@.5 mAP@.5:.95: 100% 8/8 [00:01<00:00, 5.07it/s]\n", - " all 128 929 0.387 0.737 0.657 0.432\n", + " 1/2 7.75G 0.04308 0.06654 0.02083 0.1304 227 640: 100% 8/8 [00:01<00:00, 4.11it/s]\n", + " Class Images Targets P R mAP@.5 mAP@.5:.95: 100% 4/4 [00:01<00:00, 2.94it/s]\n", + " all 128 929 0.681 0.607 0.663 0.434\n", "\n", " Epoch gpu_mem box obj cls total targets img_size\n", - " 2/2 7.48G 0.04461 0.06864 0.01866 0.1319 191 640: 100% 8/8 [00:02<00:00, 3.57it/s]\n", - " Class Images Targets P R mAP@.5 mAP@.5:.95: 100% 8/8 [00:02<00:00, 2.82it/s]\n", - " all 128 929 0.385 0.742 0.658 0.431\n", + " 2/2 7.75G 0.04461 0.06896 0.01866 0.1322 191 640: 100% 8/8 [00:02<00:00, 3.94it/s]\n", + " Class Images Targets P R mAP@.5 mAP@.5:.95: 100% 4/4 [00:03<00:00, 1.22it/s]\n", + " all 128 929 0.642 0.632 0.662 0.432\n", "Optimizer stripped from runs/train/exp/weights/last.pt, 14.8MB\n", "3 epochs completed in 0.007 hours.\n", "\n" @@ -1238,4 +1236,4 @@ "outputs": [] } ] -} +} \ No newline at end of file From 3e560e2faeeaba00adbb2c8e72716c0a133dd917 Mon Sep 17 00:00:00 2001 From: Daniel Khromov Date: Sat, 13 Feb 2021 02:37:51 +0300 Subject: [PATCH 12/88] YOLOv5 PyTorch Hub results.save() method retains filenames (#2194) * save results with name * debug * save original imgs names * Update common.py Co-authored-by: Glenn Jocher --- models/common.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/models/common.py b/models/common.py index 7cfea01f223e..4f4f331da583 100644 --- a/models/common.py +++ b/models/common.py @@ -196,10 +196,11 @@ def forward(self, imgs, size=640, augment=False, profile=False): # Pre-process n, imgs = (len(imgs), imgs) if isinstance(imgs, list) else (1, [imgs]) # number of images, list of images - shape0, shape1 = [], [] # image and inference shapes + shape0, shape1, files = [], [], [] # image and inference shapes, filenames for i, im in enumerate(imgs): if isinstance(im, str): # filename or uri im = Image.open(requests.get(im, stream=True).raw if im.startswith('http') else im) # open + files.append(Path(im.filename).with_suffix('.jpg').name if isinstance(im, Image.Image) else f'image{i}.jpg') im = np.array(im) # to numpy if im.shape[0] < 5: # image in CHW im = im.transpose((1, 2, 0)) # reverse dataloader .transpose(2, 0, 1) @@ -224,18 +225,19 @@ def forward(self, imgs, size=640, augment=False, profile=False): for i in range(n): scale_coords(shape1, y[i][:, :4], shape0[i]) - return Detections(imgs, y, self.names) + return Detections(imgs, y, files, self.names) class Detections: # detections class for YOLOv5 inference results - def __init__(self, imgs, pred, names=None): + def __init__(self, imgs, pred, files, names=None): super(Detections, self).__init__() d = pred[0].device # device gn = [torch.tensor([*[im.shape[i] for i in [1, 0, 1, 0]], 1., 1.], device=d) for im in imgs] # normalizations self.imgs = imgs # list of images as numpy arrays self.pred = pred # list of tensors pred[0] = (xyxy, conf, cls) self.names = names # class names + self.files = files # image filenames self.xyxy = pred # xyxy pixels self.xywh = [xyxy2xywh(x) for x in pred] # xywh pixels self.xyxyn = [x / g for x, g in zip(self.xyxy, gn)] # xyxy normalized @@ -258,9 +260,9 @@ def display(self, pprint=False, show=False, save=False, render=False, save_dir=' if pprint: print(str.rstrip(', ')) if show: - img.show(f'image {i}') # show + img.show(self.files[i]) # show if save: - f = Path(save_dir) / f'results{i}.jpg' + f = Path(save_dir) / self.files[i] img.save(f) # save print(f"{'Saving' * (i == 0)} {f},", end='' if i < self.n - 1 else ' done.\n') if render: @@ -272,7 +274,8 @@ def print(self): def show(self): self.display(show=True) # show results - def save(self, save_dir=''): + def save(self, save_dir='results/'): + Path(save_dir).mkdir(exist_ok=True) self.display(save=True, save_dir=save_dir) # save results def render(self): From 3ff783c18f32ec790bba5d7ca2b8d067ecd2160b Mon Sep 17 00:00:00 2001 From: VdLMV Date: Mon, 15 Feb 2021 19:49:22 +0100 Subject: [PATCH 13/88] TTA augument boxes one pixel shifted in de-flip ud and lr (#2219) * TTA augument boxes one pixel shifted in de-flip ud and lr * PEP8 reformat Co-authored-by: Jaap van de Loosdrecht Co-authored-by: Glenn Jocher --- models/yolo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/yolo.py b/models/yolo.py index 11e6a65921a4..704d0e6d260d 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -110,9 +110,9 @@ def forward(self, x, augment=False, profile=False): # cv2.imwrite(f'img_{si}.jpg', 255 * xi[0].cpu().numpy().transpose((1, 2, 0))[:, :, ::-1]) # save yi[..., :4] /= si # de-scale if fi == 2: - yi[..., 1] = img_size[0] - yi[..., 1] # de-flip ud + yi[..., 1] = img_size[0] - 1 - yi[..., 1] # de-flip ud elif fi == 3: - yi[..., 0] = img_size[1] - yi[..., 0] # de-flip lr + yi[..., 0] = img_size[1] - 1 - yi[..., 0] # de-flip lr y.append(yi) return torch.cat(y, 1), None # augmented inference, train else: From 7b833e37bf074758c94d66b3bf439582d0a08dfe Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 15 Feb 2021 11:02:20 -0800 Subject: [PATCH 14/88] LoadStreams() frame loss bug fix (#2222) --- utils/datasets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/datasets.py b/utils/datasets.py index 29a8812a20a2..4f2939d4bef2 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -300,7 +300,8 @@ def update(self, index, cap): # _, self.imgs[index] = cap.read() cap.grab() if n == 4: # read every 4th frame - _, self.imgs[index] = cap.retrieve() + success, im = cap.retrieve() + self.imgs[index] = im if success else self.imgs[index] * 0 n = 0 time.sleep(0.01) # wait time From f8464b4f66e627ed2778c9a27dbe4a8642482baf Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 15 Feb 2021 21:21:53 -0800 Subject: [PATCH 15/88] Update yolo.py channel array (#2223) --- models/yolo.py | 35 ++++++++++------------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/models/yolo.py b/models/yolo.py index 704d0e6d260d..41817098ccbc 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -2,7 +2,6 @@ import logging import sys from copy import deepcopy -from pathlib import Path sys.path.append('./') # to run '$ python *.py' files in subdirectories logger = logging.getLogger(__name__) @@ -213,43 +212,27 @@ def parse_model(d, ch): # model_dict, input_channels(3) if m in [Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, DWConv, MixConv2d, Focus, CrossConv, BottleneckCSP, C3]: c1, c2 = ch[f], args[0] - - # Normal - # if i > 0 and args[0] != no: # channel expansion factor - # ex = 1.75 # exponential (default 2.0) - # e = math.log(c2 / ch[1]) / math.log(2) - # c2 = int(ch[1] * ex ** e) - # if m != Focus: - - c2 = make_divisible(c2 * gw, 8) if c2 != no else c2 - - # Experimental - # if i > 0 and args[0] != no: # channel expansion factor - # ex = 1 + gw # exponential (default 2.0) - # ch1 = 32 # ch[1] - # e = math.log(c2 / ch1) / math.log(2) # level 1-n - # c2 = int(ch1 * ex ** e) - # if m != Focus: - # c2 = make_divisible(c2, 8) if c2 != no else c2 + if c2 != no: # if not output + c2 = make_divisible(c2 * gw, 8) args = [c1, c2, *args[1:]] if m in [BottleneckCSP, C3]: - args.insert(2, n) + args.insert(2, n) # number of repeats n = 1 elif m is nn.BatchNorm2d: args = [ch[f]] elif m is Concat: - c2 = sum([ch[x if x < 0 else x + 1] for x in f]) + c2 = sum([ch[x] for x in f]) elif m is Detect: - args.append([ch[x + 1] for x in f]) + args.append([ch[x] for x in f]) if isinstance(args[1], int): # number of anchors args[1] = [list(range(args[1] * 2))] * len(f) elif m is Contract: - c2 = ch[f if f < 0 else f + 1] * args[0] ** 2 + c2 = ch[f] * args[0] ** 2 elif m is Expand: - c2 = ch[f if f < 0 else f + 1] // args[0] ** 2 + c2 = ch[f] // args[0] ** 2 else: - c2 = ch[f if f < 0 else f + 1] + c2 = ch[f] m_ = nn.Sequential(*[m(*args) for _ in range(n)]) if n > 1 else m(*args) # module t = str(m)[8:-2].replace('__main__.', '') # module type @@ -258,6 +241,8 @@ def parse_model(d, ch): # model_dict, input_channels(3) logger.info('%3s%18s%3s%10.0f %-40s%-30s' % (i, f, n, np, t, args)) # print save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist layers.append(m_) + if i == 0: + ch = [] ch.append(c2) return nn.Sequential(*layers), sorted(save) From 26c2e54c8f97e66b646f92932eb521901d69f889 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 16 Feb 2021 13:56:47 -0800 Subject: [PATCH 16/88] Add check_imshow() (#2231) * Add check_imshow() * Update general.py * Update general.py --- detect.py | 8 ++++---- utils/general.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/detect.py b/detect.py index 3f1d6c521b67..22bf21b4c825 100644 --- a/detect.py +++ b/detect.py @@ -9,8 +9,8 @@ from models.experimental import attempt_load from utils.datasets import LoadStreams, LoadImages -from utils.general import check_img_size, check_requirements, non_max_suppression, apply_classifier, scale_coords, \ - xyxy2xywh, strip_optimizer, set_logging, increment_path +from utils.general import check_img_size, check_requirements, check_imshow, non_max_suppression, apply_classifier, \ + scale_coords, xyxy2xywh, strip_optimizer, set_logging, increment_path from utils.plots import plot_one_box from utils.torch_utils import select_device, load_classifier, time_synchronized @@ -45,7 +45,7 @@ def detect(save_img=False): # Set Dataloader vid_path, vid_writer = None, None if webcam: - view_img = True + view_img = check_imshow() cudnn.benchmark = True # set True to speed up constant image size inference dataset = LoadStreams(source, img_size=imgsz, stride=stride) else: @@ -118,7 +118,7 @@ def detect(save_img=False): # Stream results if view_img: cv2.imshow(str(p), im0) - cv2.waitKey(1) # 1 millisecond + cv2.waitKey(1) # 1 millisecond # Save results (image with detections) if save_img: diff --git a/utils/general.py b/utils/general.py index 24807483f5f4..2d3e83ede35e 100755 --- a/utils/general.py +++ b/utils/general.py @@ -95,6 +95,19 @@ def check_img_size(img_size, s=32): return new_size +def check_imshow(): + # Check if environment supports image displays + try: + cv2.imshow('test', np.zeros((1, 1, 3))) + cv2.waitKey(1) + cv2.destroyAllWindows() + cv2.waitKey(1) + return True + except Exception as e: + print(f'WARNING: Environment does not support cv2.imshow() or PIL Image.show() image previews\n{e}') + return False + + def check_file(file): # Search for file if not found if os.path.isfile(file) or file == '': From 5a40ce65ce215a79949b96f4ac2e6f4da90256ad Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 16 Feb 2021 15:27:24 -0800 Subject: [PATCH 17/88] Update CI badge (#2230) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c14071698c5..233fc17f1c35 100755 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@   -![CI CPU testing](https://github.com/ultralytics/yolov5/workflows/CI%20CPU%20testing/badge.svg) +CI CPU testing This repository represents Ultralytics open-source research into future object detection methods, and incorporates lessons learned and best practices evolved over thousands of hours of training and evolution on anonymized client datasets. **All code and models are under active development, and are subject to modification or deletion without notice.** Use at your own risk. From d2e754b67bc08d3634df05932cc94d8c9314a7b1 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 16 Feb 2021 15:58:07 -0800 Subject: [PATCH 18/88] Add isdocker() (#2232) * Add isdocker() * Update general.py * Update general.py --- utils/general.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/utils/general.py b/utils/general.py index 2d3e83ede35e..64b360fbe7df 100755 --- a/utils/general.py +++ b/utils/general.py @@ -47,6 +47,11 @@ def get_latest_run(search_dir='.'): return max(last_list, key=os.path.getctime) if last_list else '' +def isdocker(): + # Is environment a Docker container + return Path('/workspace').exists() # or Path('/.dockerenv').exists() + + def check_online(): # Check internet connectivity import socket @@ -62,7 +67,7 @@ def check_git_status(): print(colorstr('github: '), end='') try: assert Path('.git').exists(), 'skipping check (not a git repository)' - assert not Path('/workspace').exists(), 'skipping check (Docker image)' # not Path('/.dockerenv').exists() + assert not isdocker(), 'skipping check (Docker image)' assert check_online(), 'skipping check (offline)' cmd = 'git fetch && git config --get remote.origin.url' @@ -98,13 +103,14 @@ def check_img_size(img_size, s=32): def check_imshow(): # Check if environment supports image displays try: + assert not isdocker(), 'cv2.imshow() is disabled in Docker environments' cv2.imshow('test', np.zeros((1, 1, 3))) cv2.waitKey(1) cv2.destroyAllWindows() cv2.waitKey(1) return True except Exception as e: - print(f'WARNING: Environment does not support cv2.imshow() or PIL Image.show() image previews\n{e}') + print(f'WARNING: Environment does not support cv2.imshow() or PIL Image.show() image displays\n{e}') return False From 9d873077841434d1c6cbd1c4248ca2252820d3ba Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 19 Feb 2021 11:22:42 -0800 Subject: [PATCH 19/88] YOLOv5 Hub URL inference bug fix (#2250) * Update common.py * Update common.py * Update common.py --- models/common.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/models/common.py b/models/common.py index 4f4f331da583..f24ea7885668 100644 --- a/models/common.py +++ b/models/common.py @@ -199,7 +199,8 @@ def forward(self, imgs, size=640, augment=False, profile=False): shape0, shape1, files = [], [], [] # image and inference shapes, filenames for i, im in enumerate(imgs): if isinstance(im, str): # filename or uri - im = Image.open(requests.get(im, stream=True).raw if im.startswith('http') else im) # open + im, f = Image.open(requests.get(im, stream=True).raw if im.startswith('http') else im), im # open + im.filename = f # for uri files.append(Path(im.filename).with_suffix('.jpg').name if isinstance(im, Image.Image) else f'image{i}.jpg') im = np.array(im) # to numpy if im.shape[0] < 5: # image in CHW @@ -253,7 +254,7 @@ def display(self, pprint=False, show=False, save=False, render=False, save_dir=' n = (pred[:, -1] == c).sum() # detections per class str += f"{n} {self.names[int(c)]}{'s' * (n > 1)}, " # add to string if show or save or render: - img = Image.fromarray(img.astype(np.uint8)) if isinstance(img, np.ndarray) else img # from np + img = Image.fromarray(img) if isinstance(img, np.ndarray) else img # from np for *box, conf, cls in pred: # xyxy, confidence, class # str += '%s %.2f, ' % (names[int(cls)], conf) # label ImageDraw.Draw(img).rectangle(box, width=4, outline=colors[int(cls) % 10]) # plot From db28ce61acbeec9eaeb1577ccd417796ca138ee8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 19 Feb 2021 12:35:38 -0800 Subject: [PATCH 20/88] Improved hubconf.py CI tests (#2251) --- hubconf.py | 9 +++++++-- models/common.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/hubconf.py b/hubconf.py index 2a34813310e8..47eee4477725 100644 --- a/hubconf.py +++ b/hubconf.py @@ -133,9 +133,14 @@ def custom(path_or_model='path/to/model.pt', autoshape=True): # model = custom(path_or_model='path/to/model.pt') # custom example # Verify inference + import numpy as np from PIL import Image - imgs = [Image.open(x) for x in Path('data/images').glob('*.jpg')] - results = model(imgs) + imgs = [Image.open('data/images/bus.jpg'), # PIL + 'data/images/zidane.jpg', # filename + 'https://github.com/ultralytics/yolov5/raw/master/data/images/bus.jpg', # URI + np.zeros((640, 480, 3))] # numpy + + results = model(imgs) # batched inference results.print() results.save() diff --git a/models/common.py b/models/common.py index f24ea7885668..e8e5ff1eb2c1 100644 --- a/models/common.py +++ b/models/common.py @@ -254,7 +254,7 @@ def display(self, pprint=False, show=False, save=False, render=False, save_dir=' n = (pred[:, -1] == c).sum() # detections per class str += f"{n} {self.names[int(c)]}{'s' * (n > 1)}, " # add to string if show or save or render: - img = Image.fromarray(img) if isinstance(img, np.ndarray) else img # from np + img = Image.fromarray(img.astype(np.uint8)) if isinstance(img, np.ndarray) else img # from np for *box, conf, cls in pred: # xyxy, confidence, class # str += '%s %.2f, ' % (names[int(cls)], conf) # label ImageDraw.Draw(img).rectangle(box, width=4, outline=colors[int(cls) % 10]) # plot From 5f42643a53125ccc450add998401e3529d9d59d1 Mon Sep 17 00:00:00 2001 From: Yann Defretin Date: Fri, 19 Feb 2021 21:38:05 +0100 Subject: [PATCH 21/88] Unified hub and detect.py box and labels plotting (#2243) --- models/common.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/models/common.py b/models/common.py index e8e5ff1eb2c1..efcc6071af63 100644 --- a/models/common.py +++ b/models/common.py @@ -11,7 +11,7 @@ from utils.datasets import letterbox from utils.general import non_max_suppression, make_divisible, scale_coords, xyxy2xywh -from utils.plots import color_list +from utils.plots import color_list, plot_one_box def autopad(k, p=None): # kernel, padding @@ -254,10 +254,10 @@ def display(self, pprint=False, show=False, save=False, render=False, save_dir=' n = (pred[:, -1] == c).sum() # detections per class str += f"{n} {self.names[int(c)]}{'s' * (n > 1)}, " # add to string if show or save or render: - img = Image.fromarray(img.astype(np.uint8)) if isinstance(img, np.ndarray) else img # from np for *box, conf, cls in pred: # xyxy, confidence, class - # str += '%s %.2f, ' % (names[int(cls)], conf) # label - ImageDraw.Draw(img).rectangle(box, width=4, outline=colors[int(cls) % 10]) # plot + label = f'{self.names[int(cls)]} {conf:.2f}' + plot_one_box(box, img, label=label, color=colors[int(cls) % 10]) + img = Image.fromarray(img.astype(np.uint8)) if isinstance(img, np.ndarray) else img # from np if pprint: print(str.rstrip(', ')) if show: From 47faf95079d004b6114058fc9fa802190cbb95c5 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 19 Feb 2021 15:20:41 -0800 Subject: [PATCH 22/88] reset head --- utils/plots.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/utils/plots.py b/utils/plots.py index 3ec793528fe5..94f46a9a4026 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -15,7 +15,7 @@ import seaborn as sns import torch import yaml -from PIL import Image, ImageDraw +from PIL import Image, ImageDraw, ImageFont from scipy.signal import butter, filtfilt from utils.general import xywh2xyxy, xyxy2xywh @@ -68,6 +68,20 @@ def plot_one_box(x, img, color=None, label=None, line_thickness=None): cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA) +def plot_one_box_PIL(box, img, color=None, label=None, line_thickness=None): + img = Image.fromarray(img) + draw = ImageDraw.Draw(img) + line_thickness = line_thickness or max(int(min(img.size) / 200), 2) + draw.rectangle(box, width=line_thickness, outline=tuple(color)) # plot + if label: + fontsize = max(round(max(img.size) / 40), 12) + font = ImageFont.truetype("Arial.ttf", fontsize) + txt_width, txt_height = font.getsize(label) + draw.rectangle([box[0], box[1] - txt_height + 4, box[0] + txt_width, box[1]], fill=tuple(color)) + draw.text((box[0], box[1] - txt_height + 1), label, fill=(255, 255, 255), font=font) + return np.asarray(img) + + def plot_wh_methods(): # from utils.plots import *; plot_wh_methods() # Compares the two methods for width-height anchor multiplication # https://github.com/ultralytics/yolov3/issues/168 From c09964c27cc275c8e32630715cca5be77078dae2 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 19 Feb 2021 15:39:09 -0800 Subject: [PATCH 23/88] Update inference default to multi_label=False (#2252) * Update inference default to multi_label=False * bug fix * Update plots.py * Update plots.py --- models/common.py | 2 +- test.py | 8 ++++---- utils/general.py | 9 +++++---- utils/plots.py | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/models/common.py b/models/common.py index efcc6071af63..ad35f908d865 100644 --- a/models/common.py +++ b/models/common.py @@ -7,7 +7,7 @@ import requests import torch import torch.nn as nn -from PIL import Image, ImageDraw +from PIL import Image from utils.datasets import letterbox from utils.general import non_max_suppression, make_divisible, scale_coords, xyxy2xywh diff --git a/test.py b/test.py index 738764f15601..c30148dfb2f1 100644 --- a/test.py +++ b/test.py @@ -106,7 +106,7 @@ def test(data, with torch.no_grad(): # Run model t = time_synchronized() - inf_out, train_out = model(img, augment=augment) # inference and training outputs + out, train_out = model(img, augment=augment) # inference and training outputs t0 += time_synchronized() - t # Compute loss @@ -117,11 +117,11 @@ def test(data, targets[:, 2:] *= torch.Tensor([width, height, width, height]).to(device) # to pixels lb = [targets[targets[:, 0] == i, 1:] for i in range(nb)] if save_hybrid else [] # for autolabelling t = time_synchronized() - output = non_max_suppression(inf_out, conf_thres=conf_thres, iou_thres=iou_thres, labels=lb) + out = non_max_suppression(out, conf_thres=conf_thres, iou_thres=iou_thres, labels=lb, multi_label=True) t1 += time_synchronized() - t # Statistics per image - for si, pred in enumerate(output): + for si, pred in enumerate(out): labels = targets[targets[:, 0] == si, 1:] nl = len(labels) tcls = labels[:, 0].tolist() if nl else [] # target class @@ -209,7 +209,7 @@ def test(data, f = save_dir / f'test_batch{batch_i}_labels.jpg' # labels Thread(target=plot_images, args=(img, targets, paths, f, names), daemon=True).start() f = save_dir / f'test_batch{batch_i}_pred.jpg' # predictions - Thread(target=plot_images, args=(img, output_to_target(output), paths, f, names), daemon=True).start() + Thread(target=plot_images, args=(img, output_to_target(out), paths, f, names), daemon=True).start() # Compute statistics stats = [np.concatenate(x, 0) for x in zip(*stats)] # to numpy diff --git a/utils/general.py b/utils/general.py index 64b360fbe7df..3b5f4629b00a 100755 --- a/utils/general.py +++ b/utils/general.py @@ -390,11 +390,12 @@ def wh_iou(wh1, wh2): return inter / (wh1.prod(2) + wh2.prod(2) - inter) # iou = inter / (area1 + area2 - inter) -def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, labels=()): - """Performs Non-Maximum Suppression (NMS) on inference results +def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False, + labels=()): + """Runs Non-Maximum Suppression (NMS) on inference results Returns: - detections with shape: nx6 (x1, y1, x2, y2, conf, cls) + list of detections, on (n,6) tensor per image [xyxy, conf, cls] """ nc = prediction.shape[2] - 5 # number of classes @@ -406,7 +407,7 @@ def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=Non max_nms = 30000 # maximum number of boxes into torchvision.ops.nms() time_limit = 10.0 # seconds to quit after redundant = True # require redundant detections - multi_label = nc > 1 # multiple labels per box (adds 0.5ms/img) + multi_label &= nc > 1 # multiple labels per box (adds 0.5ms/img) merge = False # use merge-NMS t = time.time() diff --git a/utils/plots.py b/utils/plots.py index 94f46a9a4026..aa9a1cab81f0 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -54,7 +54,7 @@ def butter_lowpass(cutoff, fs, order): return filtfilt(b, a, data) # forward-backward filter -def plot_one_box(x, img, color=None, label=None, line_thickness=None): +def plot_one_box(x, img, color=None, label=None, line_thickness=3): # Plots one bounding box on image img tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1 # line/font thickness color = color or [random.randint(0, 255) for _ in range(3)] From 6f5d6fcdaa8c1c5b24a06fdf9fd4e12c781fb4f7 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 20 Feb 2021 11:19:01 -0800 Subject: [PATCH 24/88] Robust objectness loss balancing (#2256) --- utils/loss.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/loss.py b/utils/loss.py index 481d25e207f2..2302d18de87d 100644 --- a/utils/loss.py +++ b/utils/loss.py @@ -105,8 +105,8 @@ def __init__(self, model, autobalance=False): BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g) det = model.module.model[-1] if is_parallel(model) else model.model[-1] # Detect() module - self.balance = {3: [4.0, 1.0, 0.4], 4: [4.0, 1.0, 0.25, 0.06], 5: [4.0, 1.0, 0.25, 0.06, .02]}[det.nl] - self.ssi = (det.stride == 16).nonzero(as_tuple=False).item() # stride 16 index + self.balance = {3: [4.0, 1.0, 0.4]}.get(det.nl, [4.0, 1.0, 0.25, 0.06, .02]) # P3-P7 + self.ssi = list(det.stride).index(16) if autobalance else 0 # stride 16 index self.BCEcls, self.BCEobj, self.gr, self.hyp, self.autobalance = BCEcls, BCEobj, model.gr, h, autobalance for k in 'na', 'nc', 'nl', 'anchors': setattr(self, k, getattr(det, k)) From 095d2c11d89892cd9c0c4d034cd1c768a0dba11c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 20 Feb 2021 13:21:58 -0800 Subject: [PATCH 25/88] Created using Colaboratory --- tutorial.ipynb | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/tutorial.ipynb b/tutorial.ipynb index 7587d9f536fe..7fce40c3824e 100644 --- a/tutorial.ipynb +++ b/tutorial.ipynb @@ -563,7 +563,7 @@ "clear_output()\n", "print('Setup complete. Using torch %s %s' % (torch.__version__, torch.cuda.get_device_properties(0) if torch.cuda.is_available() else 'CPU'))" ], - "execution_count": 1, + "execution_count": null, "outputs": [ { "output_type": "stream", @@ -689,7 +689,7 @@ "torch.hub.download_url_to_file('https://github.com/ultralytics/yolov5/releases/download/v1.0/coco2017val.zip', 'tmp.zip')\n", "!unzip -q tmp.zip -d ../ && rm tmp.zip" ], - "execution_count": 2, + "execution_count": null, "outputs": [ { "output_type": "display_data", @@ -729,7 +729,7 @@ "# Run YOLOv5x on COCO val2017\n", "!python test.py --weights yolov5x.pt --data coco.yaml --img 640 --iou 0.65" ], - "execution_count": 3, + "execution_count": null, "outputs": [ { "output_type": "stream", @@ -853,7 +853,7 @@ "torch.hub.download_url_to_file('https://github.com/ultralytics/yolov5/releases/download/v1.0/coco128.zip', 'tmp.zip')\n", "!unzip -q tmp.zip -d ../ && rm tmp.zip" ], - "execution_count": 4, + "execution_count": null, "outputs": [ { "output_type": "display_data", @@ -930,7 +930,7 @@ "# Train YOLOv5s on COCO128 for 3 epochs\n", "!python train.py --img 640 --batch 16 --epochs 3 --data coco128.yaml --weights yolov5s.pt --nosave --cache" ], - "execution_count": 5, + "execution_count": null, "outputs": [ { "output_type": "stream", @@ -1222,6 +1222,19 @@ "execution_count": null, "outputs": [] }, + { + "cell_type": "code", + "metadata": { + "id": "RVRSOhEvUdb5" + }, + "source": [ + "# Evolve\n", + "!python train.py --img 640 --batch 64 --epochs 100 --data coco128.yaml --weights yolov5s.pt --cache --noautoanchor --evolve\n", + "!d=runs/train/evolve && cp evolve.* $d && zip -r evolve.zip $d && gsutil mv evolve.zip gs://bucket # upload results (optional)" + ], + "execution_count": null, + "outputs": [] + }, { "cell_type": "code", "metadata": { From e27ca0d8455ad91ec52e4dfd757825e653508bde Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 21 Feb 2021 21:46:42 -0800 Subject: [PATCH 26/88] Update minimum stride to 32 (#2266) --- test.py | 5 +++-- train.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test.py b/test.py index c30148dfb2f1..ecd45f5f4943 100644 --- a/test.py +++ b/test.py @@ -52,7 +52,8 @@ def test(data, # Load model model = attempt_load(weights, map_location=device) # load FP32 model - imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size + gs = max(int(model.stride.max()), 32) # grid size (max stride) + imgsz = check_img_size(imgsz, s=gs) # check img_size # Multi-GPU disabled, incompatible with .half() https://github.com/ultralytics/yolov5/issues/99 # if device.type != 'cpu' and torch.cuda.device_count() > 1: @@ -85,7 +86,7 @@ def test(data, if device.type != 'cpu': model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters()))) # run once path = data['test'] if opt.task == 'test' else data['val'] # path to val/test images - dataloader = create_dataloader(path, imgsz, batch_size, model.stride.max(), opt, pad=0.5, rect=True, + dataloader = create_dataloader(path, imgsz, batch_size, gs, opt, pad=0.5, rect=True, prefix=colorstr('test: ' if opt.task == 'test' else 'val: '))[0] seen = 0 diff --git a/train.py b/train.py index 4065e1f149ef..e19cfa81d8da 100644 --- a/train.py +++ b/train.py @@ -161,7 +161,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): del ckpt, state_dict # Image sizes - gs = int(model.stride.max()) # grid size (max stride) + gs = max(int(model.stride.max()), 32) # grid size (max stride) nl = model.model[-1].nl # number of detection layers (used for scaling hyp['obj']) imgsz, imgsz_test = [check_img_size(x, gs) for x in opt.img_size] # verify imgsz are gs-multiples From 95aefea49374a1fe867794971c76337526a4d6cb Mon Sep 17 00:00:00 2001 From: Aditya Lohia <64709773+aditya-dl@users.noreply.github.com> Date: Mon, 22 Feb 2021 11:20:44 +0530 Subject: [PATCH 27/88] Dynamic ONNX engine generation (#2208) * add: dynamic onnx export * delete: test onnx inference * fix dynamic output axis * Code reduction * fix: dynamic output axes, dynamic input naming * Remove fixed axes Co-authored-by: Shivam Swanrkar Co-authored-by: Glenn Jocher --- models/export.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/models/export.py b/models/export.py index 057658af53dc..cc817871f218 100644 --- a/models/export.py +++ b/models/export.py @@ -22,6 +22,7 @@ parser = argparse.ArgumentParser() parser.add_argument('--weights', type=str, default='./yolov5s.pt', help='weights path') # from yolov5/models/ parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='image size') # height, width + parser.add_argument('--dynamic', action='store_true', help='dynamic ONNX axes') parser.add_argument('--batch-size', type=int, default=1, help='batch size') opt = parser.parse_args() opt.img_size *= 2 if len(opt.img_size) == 1 else 1 # expand @@ -70,7 +71,9 @@ print('\nStarting ONNX export with onnx %s...' % onnx.__version__) f = opt.weights.replace('.pt', '.onnx') # filename torch.onnx.export(model, img, f, verbose=False, opset_version=12, input_names=['images'], - output_names=['classes', 'boxes'] if y is None else ['output']) + output_names=['classes', 'boxes'] if y is None else ['output'], + dynamic_axes={'images': {0: 'batch', 2: 'height', 3: 'width'}, # size(1,3,640,640) + 'output': {0: 'batch', 2: 'y', 3: 'x'}} if opt.dynamic else None) # Checks onnx_model = onnx.load(f) # load onnx model From 32dd1614f405d16678fea787137eb9662d7dc1e0 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 22 Feb 2021 18:34:07 -0800 Subject: [PATCH 28/88] Update greetings.yml for auto-rebase on PR (#2272) --- .github/workflows/greetings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml index d62cf5c1600d..ee472297107e 100644 --- a/.github/workflows/greetings.yml +++ b/.github/workflows/greetings.yml @@ -11,7 +11,7 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} pr-message: | 👋 Hello @${{ github.actor }}, thank you for submitting a 🚀 PR! To allow your work to be integrated as seamlessly as possible, we advise you to: - - ✅ Verify your PR is **up-to-date with origin/master.** If your PR is behind origin/master update by running the following, replacing 'feature' with the name of your local branch: + - ✅ Verify your PR is **up-to-date with origin/master.** If your PR is behind origin/master an automatic [GitHub actions](https://github.com/ultralytics/yolov5/blob/master/.github/workflows/rebase.yml) rebase may be attempted by including the /rebase command in a comment body, or by running the following code, replacing 'feature' with the name of your local branch: ```bash git remote add upstream https://github.com/ultralytics/yolov5.git git fetch upstream From cc79f3a9ea5d927475e7b896b18aa998c6e70795 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 22 Feb 2021 22:50:00 -0800 Subject: [PATCH 29/88] Update Dockerfile with apt install zip (#2274) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 98dfee204770..fe64d6da29f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM nvcr.io/nvidia/pytorch:20.12-py3 # Install linux packages -RUN apt update && apt install -y screen libgl1-mesa-glx +RUN apt update && apt install -y zip screen libgl1-mesa-glx # Install python dependencies RUN python -m pip install --upgrade pip From 83dc1b4484d8c5fe69c6a6ff50912ca90cace35a Mon Sep 17 00:00:00 2001 From: xiaowo1996 <429740343@qq.com> Date: Wed, 24 Feb 2021 01:38:56 +0800 Subject: [PATCH 30/88] FLOPS min stride 32 (#2276) Signed-off-by: xiaowo1996 <429740343@qq.com> --- utils/torch_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 2cb09e71ce71..1b1cc2038c55 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -205,7 +205,7 @@ def model_info(model, verbose=False, img_size=640): try: # FLOPS from thop import profile - stride = int(model.stride.max()) if hasattr(model, 'stride') else 32 + stride = max(int(model.stride.max()), 32) if hasattr(model, 'stride') else 32 img = torch.zeros((1, model.yaml.get('ch', 3), stride, stride), device=next(model.parameters()).device) # input flops = profile(deepcopy(model), inputs=(img,), verbose=False)[0] / 1E9 * 2 # stride GFLOPS img_size = img_size if isinstance(img_size, list) else [img_size, img_size] # expand if int/float From 7a6870b81f31db40b06d2e899801febbeed96696 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 23 Feb 2021 11:27:44 -0800 Subject: [PATCH 31/88] Update README.md --- README.md | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 233fc17f1c35..b7129e80adfe 100755 --- a/README.md +++ b/README.md @@ -89,17 +89,15 @@ To run inference on example images in `data/images`: ```bash $ python detect.py --source data/images --weights yolov5s.pt --conf 0.25 -Namespace(agnostic_nms=False, augment=False, classes=None, conf_thres=0.25, device='', img_size=640, iou_thres=0.45, save_conf=False, save_dir='runs/detect', save_txt=False, source='data/images/', update=False, view_img=False, weights=['yolov5s.pt']) -Using torch 1.7.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16130MB) - -Downloading https://github.com/ultralytics/yolov5/releases/download/v3.1/yolov5s.pt to yolov5s.pt... 100%|██████████████| 14.5M/14.5M [00:00<00:00, 21.3MB/s] +Namespace(agnostic_nms=False, augment=False, classes=None, conf_thres=0.25, device='', exist_ok=False, img_size=640, iou_thres=0.45, name='exp', project='runs/detect', save_conf=False, save_txt=False, source='data/images/', update=False, view_img=False, weights=['yolov5s.pt']) +YOLOv5 v4.0-96-g83dc1b4 torch 1.7.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB) Fusing layers... -Model Summary: 232 layers, 7459581 parameters, 0 gradients -image 1/2 data/images/bus.jpg: 640x480 4 persons, 1 buss, 1 skateboards, Done. (0.012s) -image 2/2 data/images/zidane.jpg: 384x640 2 persons, 2 ties, Done. (0.012s) -Results saved to runs/detect/exp -Done. (0.113s) +Model Summary: 224 layers, 7266973 parameters, 0 gradients, 17.0 GFLOPS +image 1/2 /content/yolov5/data/images/bus.jpg: 640x480 4 persons, 1 bus, Done. (0.010s) +image 2/2 /content/yolov5/data/images/zidane.jpg: 384x640 2 persons, 1 tie, Done. (0.011s) +Results saved to runs/detect/exp2 +Done. (0.103s) ``` @@ -108,18 +106,17 @@ Done. (0.113s) To run **batched inference** with YOLOv5 and [PyTorch Hub](https://github.com/ultralytics/yolov5/issues/36): ```python import torch -from PIL import Image # Model model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True) # Images -img1 = Image.open('zidane.jpg') -img2 = Image.open('bus.jpg') -imgs = [img1, img2] # batched list of images +dir = 'https://github.com/ultralytics/yolov5/raw/master/data/images/' +imgs = [dir + f for f in ('zidane.jpg', 'bus.jpg')] # batched list of images # Inference -result = model(imgs) +results = model(imgs) +results.print() # or .show(), .save() ``` From d5d275b6e97766835ebb04d02e5d1e3478d3eeee Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 23 Feb 2021 23:10:14 -0800 Subject: [PATCH 32/88] Amazon AWS EC2 startup and re-startup scripts (#2185) * Amazon AWS EC2 startup and re-startup scripts * Create resume.py * cleanup --- utils/aws/__init__.py | 0 utils/aws/mime.sh | 26 ++++++++++++++++++++++++++ utils/aws/resume.py | 34 ++++++++++++++++++++++++++++++++++ utils/aws/userdata.sh | 26 ++++++++++++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 utils/aws/__init__.py create mode 100644 utils/aws/mime.sh create mode 100644 utils/aws/resume.py create mode 100644 utils/aws/userdata.sh diff --git a/utils/aws/__init__.py b/utils/aws/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/utils/aws/mime.sh b/utils/aws/mime.sh new file mode 100644 index 000000000000..c319a83cfbdf --- /dev/null +++ b/utils/aws/mime.sh @@ -0,0 +1,26 @@ +# AWS EC2 instance startup 'MIME' script https://aws.amazon.com/premiumsupport/knowledge-center/execute-user-data-ec2/ +# This script will run on every instance restart, not only on first start +# --- DO NOT COPY ABOVE COMMENTS WHEN PASTING INTO USERDATA --- + +Content-Type: multipart/mixed; boundary="//" +MIME-Version: 1.0 + +--// +Content-Type: text/cloud-config; charset="us-ascii" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Content-Disposition: attachment; filename="cloud-config.txt" + +#cloud-config +cloud_final_modules: +- [scripts-user, always] + +--// +Content-Type: text/x-shellscript; charset="us-ascii" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Content-Disposition: attachment; filename="userdata.txt" + +#!/bin/bash +# --- paste contents of userdata.sh here --- +--// diff --git a/utils/aws/resume.py b/utils/aws/resume.py new file mode 100644 index 000000000000..338c8b10127b --- /dev/null +++ b/utils/aws/resume.py @@ -0,0 +1,34 @@ +# Resume all interrupted trainings in yolov5/ dir including DPP trainings +# Usage: $ python utils/aws/resume.py + +import os +from pathlib import Path + +import torch +import yaml + +port = 0 # --master_port +path = Path('').resolve() +for last in path.rglob('*/**/last.pt'): + ckpt = torch.load(last) + if ckpt['optimizer'] is None: + continue + + # Load opt.yaml + with open(last.parent.parent / 'opt.yaml') as f: + opt = yaml.load(f, Loader=yaml.SafeLoader) + + # Get device count + d = opt['device'].split(',') # devices + nd = len(d) # number of devices + ddp = nd > 1 or (nd == 0 and torch.cuda.device_count() > 1) # distributed data parallel + + if ddp: # multi-GPU + port += 1 + cmd = f'python -m torch.distributed.launch --nproc_per_node {nd} --master_port {port} train.py --resume {last}' + else: # single-GPU + cmd = f'python train.py --resume {last}' + + cmd += ' > /dev/null 2>&1 &' # redirect output to dev/null and run in daemon thread + print(cmd) + os.system(cmd) diff --git a/utils/aws/userdata.sh b/utils/aws/userdata.sh new file mode 100644 index 000000000000..36405d1a1565 --- /dev/null +++ b/utils/aws/userdata.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# AWS EC2 instance startup script https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html +# This script will run only once on first instance start (for a re-start script see mime.sh) +# /home/ubuntu (ubuntu) or /home/ec2-user (amazon-linux) is working dir +# Use >300 GB SSD + +cd home/ubuntu +if [ ! -d yolov5 ]; then + echo "Running first-time script." # install dependencies, download COCO, pull Docker + git clone https://github.com/ultralytics/yolov5 && sudo chmod -R 777 yolov5 + cd yolov5 + bash data/scripts/get_coco.sh && echo "Data done." & + sudo docker pull ultralytics/yolov5:latest && echo "Docker done." & + # python -m pip install --upgrade pip && pip install -r requirements.txt && python detect.py && echo "Requirements done." & +else + echo "Running re-start script." # resume interrupted runs + i=0 + list=$(docker ps -qa) # container list i.e. $'one\ntwo\nthree\nfour' + while IFS= read -r id; do + ((i++)) + echo "restarting container $i: $id" + docker start $id + # docker exec -it $id python train.py --resume # single-GPU + docker exec -d $id python utils/aws/resume.py + done <<<"$list" +fi From 0070995bd58629d4628d11b1c8de9788aa55379b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 24 Feb 2021 01:43:59 -0800 Subject: [PATCH 33/88] Amazon AWS EC2 startup and re-startup scripts (#2282) --- utils/aws/resume.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils/aws/resume.py b/utils/aws/resume.py index 338c8b10127b..563f22be20dc 100644 --- a/utils/aws/resume.py +++ b/utils/aws/resume.py @@ -2,11 +2,14 @@ # Usage: $ python utils/aws/resume.py import os +import sys from pathlib import Path import torch import yaml +sys.path.append('./') # to run '$ python *.py' files in subdirectories + port = 0 # --master_port path = Path('').resolve() for last in path.rglob('*/**/last.pt'): From ca5b10b759d2e41221e7ffddcefe1f8087791dec Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 24 Feb 2021 13:31:20 -0800 Subject: [PATCH 34/88] Update train.py (#2290) * Update train.py * Update train.py * Update train.py * Update train.py * Create train.py --- train.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/train.py b/train.py index e19cfa81d8da..8533667fe57f 100644 --- a/train.py +++ b/train.py @@ -146,8 +146,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): # Results if ckpt.get('training_results') is not None: - with open(results_file, 'w') as file: - file.write(ckpt['training_results']) # write results.txt + results_file.write_text(ckpt['training_results']) # write results.txt # Epochs start_epoch = ckpt['epoch'] + 1 @@ -354,7 +353,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): # Write with open(results_file, 'a') as f: - f.write(s + '%10.4g' * 7 % results + '\n') # P, R, mAP@.5, mAP@.5-.95, val_loss(box, obj, cls) + f.write(s + '%10.4g' * 7 % results + '\n') # append metrics, val_loss if len(opt.name) and opt.bucket: os.system('gsutil cp %s gs://%s/results/results%s.txt' % (results_file, opt.bucket, opt.name)) @@ -375,15 +374,13 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): best_fitness = fi # Save model - save = (not opt.nosave) or (final_epoch and not opt.evolve) - if save: - with open(results_file, 'r') as f: # create checkpoint - ckpt = {'epoch': epoch, - 'best_fitness': best_fitness, - 'training_results': f.read(), - 'model': ema.ema, - 'optimizer': None if final_epoch else optimizer.state_dict(), - 'wandb_id': wandb_run.id if wandb else None} + if (not opt.nosave) or (final_epoch and not opt.evolve): # if save + ckpt = {'epoch': epoch, + 'best_fitness': best_fitness, + 'training_results': results_file.read_text(), + 'model': ema.ema, + 'optimizer': None if final_epoch else optimizer.state_dict(), + 'wandb_id': wandb_run.id if wandb else None} # Save last, best and delete torch.save(ckpt, last) @@ -396,9 +393,9 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): if rank in [-1, 0]: # Strip optimizers final = best if best.exists() else last # final model - for f in [last, best]: + for f in last, best: if f.exists(): - strip_optimizer(f) # strip optimizers + strip_optimizer(f) if opt.bucket: os.system(f'gsutil cp {final} gs://{opt.bucket}/weights') # upload @@ -415,17 +412,17 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): # Test best.pt logger.info('%g epochs completed in %.3f hours.\n' % (epoch - start_epoch + 1, (time.time() - t0) / 3600)) if opt.data.endswith('coco.yaml') and nc == 80: # if COCO - for conf, iou, save_json in ([0.25, 0.45, False], [0.001, 0.65, True]): # speed, mAP tests + for m in (last, best) if best.exists() else (last): # speed, mAP tests results, _, _ = test.test(opt.data, batch_size=batch_size * 2, imgsz=imgsz_test, - conf_thres=conf, - iou_thres=iou, - model=attempt_load(final, device).half(), + conf_thres=0.001, + iou_thres=0.7, + model=attempt_load(m, device).half(), single_cls=opt.single_cls, dataloader=testloader, save_dir=save_dir, - save_json=save_json, + save_json=True, plots=False) else: From ec1d8496baa6bff7cb3ea223fd23f2d0cf0804ec Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 24 Feb 2021 18:26:46 -0800 Subject: [PATCH 35/88] Improved model+EMA checkpointing (#2292) * Enhanced model+EMA checkpointing * update * bug fix * bug fix 2 * always save optimizer * ema half * remove model.float() * model half * carry ema/model in fp32 * rm model.float() * both to float always * cleanup * cleanup --- test.py | 1 - train.py | 25 ++++++++++++++++--------- utils/general.py | 4 ++-- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/test.py b/test.py index ecd45f5f4943..9f484c809052 100644 --- a/test.py +++ b/test.py @@ -272,7 +272,6 @@ def test(data, if not training: s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else '' print(f"Results saved to {save_dir}{s}") - model.float() # for training maps = np.zeros(nc) + map for i, c in enumerate(ap_class): maps[c] = ap[i] diff --git a/train.py b/train.py index 8533667fe57f..7aa57fa99e24 100644 --- a/train.py +++ b/train.py @@ -31,7 +31,7 @@ from utils.google_utils import attempt_download from utils.loss import ComputeLoss from utils.plots import plot_images, plot_labels, plot_results, plot_evolution -from utils.torch_utils import ModelEMA, select_device, intersect_dicts, torch_distributed_zero_first +from utils.torch_utils import ModelEMA, select_device, intersect_dicts, torch_distributed_zero_first, is_parallel logger = logging.getLogger(__name__) @@ -136,6 +136,9 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): id=ckpt.get('wandb_id') if 'ckpt' in locals() else None) loggers = {'wandb': wandb} # loggers dict + # EMA + ema = ModelEMA(model) if rank in [-1, 0] else None + # Resume start_epoch, best_fitness = 0, 0.0 if pretrained: @@ -144,6 +147,11 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): optimizer.load_state_dict(ckpt['optimizer']) best_fitness = ckpt['best_fitness'] + # EMA + if ema and ckpt.get('ema'): + ema.ema.load_state_dict(ckpt['ema'][0].float().state_dict()) + ema.updates = ckpt['ema'][1] + # Results if ckpt.get('training_results') is not None: results_file.write_text(ckpt['training_results']) # write results.txt @@ -173,9 +181,6 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model).to(device) logger.info('Using SyncBatchNorm()') - # EMA - ema = ModelEMA(model) if rank in [-1, 0] else None - # DDP mode if cuda and rank != -1: model = DDP(model, device_ids=[opt.local_rank], output_device=opt.local_rank) @@ -191,7 +196,6 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): # Process 0 if rank in [-1, 0]: - ema.updates = start_epoch * nb // accumulate # set EMA updates testloader = create_dataloader(test_path, imgsz_test, batch_size * 2, gs, opt, # testloader hyp=hyp, cache=opt.cache_images and not opt.notest, rect=True, rank=-1, world_size=opt.world_size, workers=opt.workers, @@ -335,8 +339,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): # DDP process 0 or single-GPU if rank in [-1, 0]: # mAP - if ema: - ema.update_attr(model, include=['yaml', 'nc', 'hyp', 'gr', 'names', 'stride', 'class_weights']) + ema.update_attr(model, include=['yaml', 'nc', 'hyp', 'gr', 'names', 'stride', 'class_weights']) final_epoch = epoch + 1 == epochs if not opt.notest or final_epoch: # Calculate mAP results, maps, times = test.test(opt.data, @@ -378,8 +381,9 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): ckpt = {'epoch': epoch, 'best_fitness': best_fitness, 'training_results': results_file.read_text(), - 'model': ema.ema, - 'optimizer': None if final_epoch else optimizer.state_dict(), + 'model': (model.module if is_parallel(model) else model).half(), + 'ema': (ema.ema.half(), ema.updates), + 'optimizer': optimizer.state_dict(), 'wandb_id': wandb_run.id if wandb else None} # Save last, best and delete @@ -387,6 +391,9 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): if best_fitness == fi: torch.save(ckpt, best) del ckpt + + model.float(), ema.ema.float() + # end epoch ---------------------------------------------------------------------------------------------------- # end training diff --git a/utils/general.py b/utils/general.py index 3b5f4629b00a..e5bbc50c6177 100755 --- a/utils/general.py +++ b/utils/general.py @@ -484,8 +484,8 @@ def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=Non def strip_optimizer(f='weights/best.pt', s=''): # from utils.general import *; strip_optimizer() # Strip optimizer from 'f' to finalize training, optionally save as 's' x = torch.load(f, map_location=torch.device('cpu')) - for key in 'optimizer', 'training_results', 'wandb_id': - x[key] = None + for k in 'optimizer', 'training_results', 'wandb_id', 'ema': # keys + x[k] = None x['epoch'] = -1 x['model'].half() # to FP16 for p in x['model'].parameters(): From 71dd2768f28ed24e83087203a2dea565c99a1120 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 24 Feb 2021 21:03:21 -0800 Subject: [PATCH 36/88] Improved model+EMA checkpointing 2 (#2295) --- test.py | 1 + train.py | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test.py b/test.py index 9f484c809052..91176eca01db 100644 --- a/test.py +++ b/test.py @@ -269,6 +269,7 @@ def test(data, print(f'pycocotools unable to run: {e}') # Return results + model.float() # for training if not training: s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else '' print(f"Results saved to {save_dir}{s}") diff --git a/train.py b/train.py index 7aa57fa99e24..e37cf816bcb1 100644 --- a/train.py +++ b/train.py @@ -4,6 +4,7 @@ import os import random import time +from copy import deepcopy from pathlib import Path from threading import Thread @@ -381,8 +382,8 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): ckpt = {'epoch': epoch, 'best_fitness': best_fitness, 'training_results': results_file.read_text(), - 'model': (model.module if is_parallel(model) else model).half(), - 'ema': (ema.ema.half(), ema.updates), + 'model': deepcopy(model.module if is_parallel(model) else model).half(), + 'ema': (deepcopy(ema.ema).half(), ema.updates), 'optimizer': optimizer.state_dict(), 'wandb_id': wandb_run.id if wandb else None} @@ -392,8 +393,6 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): torch.save(ckpt, best) del ckpt - model.float(), ema.ema.float() - # end epoch ---------------------------------------------------------------------------------------------------- # end training From a82dce7faa5d13d6f9c342f04aaaa3b5de80d749 Mon Sep 17 00:00:00 2001 From: Iden Craven Date: Thu, 25 Feb 2021 19:05:38 -0700 Subject: [PATCH 37/88] Fix labels being missed when image extension appears twice in filename (#2300) --- utils/datasets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/datasets.py b/utils/datasets.py index 4f2939d4bef2..d6ab16518034 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -335,7 +335,7 @@ def __len__(self): def img2label_paths(img_paths): # Define label paths as a function of image paths sa, sb = os.sep + 'images' + os.sep, os.sep + 'labels' + os.sep # /images/, /labels/ substrings - return [x.replace(sa, sb, 1).replace('.' + x.split('.')[-1], '.txt') for x in img_paths] + return ['txt'.join(x.replace(sa, sb, 1).rsplit(x.split('.')[-1], 1)) for x in img_paths] class LoadImagesAndLabels(Dataset): # for training/testing From efa4946d158f4042890b243cf9314aa62dac83e4 Mon Sep 17 00:00:00 2001 From: Jan Hajek Date: Fri, 26 Feb 2021 04:18:19 +0100 Subject: [PATCH 38/88] W&B entity support (#2298) * W&B entity support * shorten wandb_entity to entity Co-authored-by: Jan Hajek Co-authored-by: Glenn Jocher --- train.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/train.py b/train.py index e37cf816bcb1..bbf879f3af5f 100644 --- a/train.py +++ b/train.py @@ -134,6 +134,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): wandb_run = wandb.init(config=opt, resume="allow", project='YOLOv5' if opt.project == 'runs/train' else Path(opt.project).stem, name=save_dir.stem, + entity=opt.entity, id=ckpt.get('wandb_id') if 'ckpt' in locals() else None) loggers = {'wandb': wandb} # loggers dict @@ -467,6 +468,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): parser.add_argument('--log-artifacts', action='store_true', help='log artifacts, i.e. final trained model') parser.add_argument('--workers', type=int, default=8, help='maximum number of dataloader workers') parser.add_argument('--project', default='runs/train', help='save to project/name') + parser.add_argument('--entity', default=None, help='W&B entity') parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') parser.add_argument('--quad', action='store_true', help='quad dataloader') From cbd55da5d24becbe3b94afaaa4cdd1187a512c3f Mon Sep 17 00:00:00 2001 From: oleg Date: Fri, 26 Feb 2021 15:07:40 -0800 Subject: [PATCH 39/88] Update yolo.py (#2120) * Avoid mutable state in Detect * LoadImages() pathlib update (#2140) * Unique *.cache filenames fix (#2134) * fix #2121 * Update test.py * Update train.py * Update autoanchor.py * Update datasets.py * Update log_dataset.py * Update datasets.py Co-authored-by: Glenn Jocher * Update train.py test batch_size (#2148) * Update train.py * Update loss.py * Update train.py (#2149) * Linear LR scheduler option (#2150) * Linear LR scheduler option * Update train.py * Update data-autodownload background tasks (#2154) * Update get_coco.sh * Update get_voc.sh * Update detect.py (#2167) Without this cv2.imshow opens a window but nothing is visible * Update requirements.txt (#2173) * Update utils/datasets.py to support .webp files (#2174) Simply added 'webp' as an image format to the img_formats array so that webp image files can be used as training data. * Changed socket port and added timeout (#2176) * PyTorch Hub results.save('path/to/dir') (#2179) * YOLOv5 Segmentation Dataloader Updates (#2188) * Update C3 module * Update C3 module * Update C3 module * Update C3 module * update * update * update * update * update * update * update * update * update * updates * updates * updates * updates * updates * updates * updates * updates * updates * updates * update * update * update * update * updates * updates * updates * updates * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update datasets * update * update * update * update attempt_downlaod() * merge * merge * update * update * update * update * update * update * update * update * update * update * parameterize eps * comments * gs-multiple * update * max_nms implemented * Create one_cycle() function * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * GitHub API rate limit fix * update * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * ComputeLoss * astuple * epochs * update * update * ComputeLoss() * update * update * update * update * update * update * update * update * update * update * update * merge * merge * merge * merge * update * update * update * update * commit=tag == tags[-1] * Update cudnn.benchmark * update * update * update * updates * updates * updates * updates * updates * updates * updates * update * update * update * update * update * mosaic9 * update * update * update * update * update * update * institute cache versioning * only display on existing cache * reverse cache exists booleans * Created using Colaboratory * YOLOv5 PyTorch Hub results.save() method retains filenames (#2194) * save results with name * debug * save original imgs names * Update common.py Co-authored-by: Glenn Jocher * TTA augument boxes one pixel shifted in de-flip ud and lr (#2219) * TTA augument boxes one pixel shifted in de-flip ud and lr * PEP8 reformat Co-authored-by: Jaap van de Loosdrecht Co-authored-by: Glenn Jocher * LoadStreams() frame loss bug fix (#2222) * Update yolo.py channel array (#2223) * Add check_imshow() (#2231) * Add check_imshow() * Update general.py * Update general.py * Update CI badge (#2230) * Add isdocker() (#2232) * Add isdocker() * Update general.py * Update general.py * YOLOv5 Hub URL inference bug fix (#2250) * Update common.py * Update common.py * Update common.py * Improved hubconf.py CI tests (#2251) * Unified hub and detect.py box and labels plotting (#2243) * reset head * Update inference default to multi_label=False (#2252) * Update inference default to multi_label=False * bug fix * Update plots.py * Update plots.py * Robust objectness loss balancing (#2256) * Created using Colaboratory * Update minimum stride to 32 (#2266) * Dynamic ONNX engine generation (#2208) * add: dynamic onnx export * delete: test onnx inference * fix dynamic output axis * Code reduction * fix: dynamic output axes, dynamic input naming * Remove fixed axes Co-authored-by: Shivam Swanrkar Co-authored-by: Glenn Jocher * Update greetings.yml for auto-rebase on PR (#2272) * Update Dockerfile with apt install zip (#2274) * FLOPS min stride 32 (#2276) Signed-off-by: xiaowo1996 <429740343@qq.com> * Update README.md * Amazon AWS EC2 startup and re-startup scripts (#2185) * Amazon AWS EC2 startup and re-startup scripts * Create resume.py * cleanup * Amazon AWS EC2 startup and re-startup scripts (#2282) * Update train.py (#2290) * Update train.py * Update train.py * Update train.py * Update train.py * Create train.py * Improved model+EMA checkpointing (#2292) * Enhanced model+EMA checkpointing * update * bug fix * bug fix 2 * always save optimizer * ema half * remove model.float() * model half * carry ema/model in fp32 * rm model.float() * both to float always * cleanup * cleanup * Improved model+EMA checkpointing 2 (#2295) * Fix labels being missed when image extension appears twice in filename (#2300) * W&B entity support (#2298) * W&B entity support * shorten wandb_entity to entity Co-authored-by: Jan Hajek Co-authored-by: Glenn Jocher * Avoid mutable state in Detect * Update yolo and remove .to(device) Co-authored-by: Oleg Boiko Co-authored-by: Glenn Jocher Co-authored-by: train255 Co-authored-by: ab-101 <56578530+ab-101@users.noreply.github.com> Co-authored-by: Transigent Co-authored-by: NanoCode012 Co-authored-by: Daniel Khromov Co-authored-by: VdLMV Co-authored-by: Jaap van de Loosdrecht Co-authored-by: Yann Defretin Co-authored-by: Aditya Lohia <64709773+aditya-dl@users.noreply.github.com> Co-authored-by: Shivam Swanrkar Co-authored-by: xiaowo1996 <429740343@qq.com> Co-authored-by: Iden Craven Co-authored-by: Jan Hajek Co-authored-by: Jan Hajek --- models/yolo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/yolo.py b/models/yolo.py index 41817098ccbc..85043f2b0205 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -49,7 +49,7 @@ def forward(self, x): self.grid[i] = self._make_grid(nx, ny).to(x[i].device) y = x[i].sigmoid() - y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i] # xy + y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i] # xy y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh z.append(y.view(bs, -1, self.no)) From dfeec198cbb0d19bf06a26e3712b7825f993fc47 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 27 Feb 2021 12:51:33 -0800 Subject: [PATCH 40/88] final_epoch EMA bug fix (#2317) --- train.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/train.py b/train.py index bbf879f3af5f..5c203f12651d 100644 --- a/train.py +++ b/train.py @@ -383,7 +383,8 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): ckpt = {'epoch': epoch, 'best_fitness': best_fitness, 'training_results': results_file.read_text(), - 'model': deepcopy(model.module if is_parallel(model) else model).half(), + 'model': ema.ema if final_epoch else deepcopy( + model.module if is_parallel(model) else model).half(), 'ema': (deepcopy(ema.ema).half(), ema.updates), 'optimizer': optimizer.state_dict(), 'wandb_id': wandb_run.id if wandb else None} From cd30d838eb098b1c96219a83521e71bdd9360f60 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 27 Feb 2021 15:28:23 -0800 Subject: [PATCH 41/88] Update test.py (#2319) --- test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test.py b/test.py index 91176eca01db..fd4d339ffea6 100644 --- a/test.py +++ b/test.py @@ -326,6 +326,7 @@ def test(data, test(opt.data, w, opt.batch_size, opt.img_size, 0.25, 0.45, save_json=False, plots=False) elif opt.task == 'study': # run over a range of settings and save/plot + # python test.py --task study --data coco.yaml --iou 0.7 --weights yolov5s.pt yolov5m.pt yolov5l.pt yolov5x.pt x = list(range(256, 1536 + 128, 128)) # x axis (image sizes) for w in opt.weights: f = f'study_{Path(opt.data).stem}_{Path(w).stem}.txt' # filename to save to From c2026a5f35fd632c71b10fdbaf9194e714906f02 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 27 Feb 2021 15:55:31 -0800 Subject: [PATCH 42/88] Update Dockerfile install htop (#2320) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fe64d6da29f9..a768774fa9c7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM nvcr.io/nvidia/pytorch:20.12-py3 # Install linux packages -RUN apt update && apt install -y zip screen libgl1-mesa-glx +RUN apt update && apt install -y zip htop screen libgl1-mesa-glx # Install python dependencies RUN python -m pip install --upgrade pip From fd96810518adcbb07ca0c5e1373c57e9025966c4 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 28 Feb 2021 21:14:08 -0800 Subject: [PATCH 43/88] remove TTA 1 pixel offset (#2325) --- models/yolo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/yolo.py b/models/yolo.py index 85043f2b0205..a9e1da43d913 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -109,9 +109,9 @@ def forward(self, x, augment=False, profile=False): # cv2.imwrite(f'img_{si}.jpg', 255 * xi[0].cpu().numpy().transpose((1, 2, 0))[:, :, ::-1]) # save yi[..., :4] /= si # de-scale if fi == 2: - yi[..., 1] = img_size[0] - 1 - yi[..., 1] # de-flip ud + yi[..., 1] = img_size[0] - yi[..., 1] # de-flip ud elif fi == 3: - yi[..., 0] = img_size[1] - 1 - yi[..., 0] # de-flip lr + yi[..., 0] = img_size[1] - yi[..., 0] # de-flip lr y.append(yi) return torch.cat(y, 1), None # augmented inference, train else: From fab5085674f7748dc16d7ca25afb225fa441bc9d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 1 Mar 2021 17:13:34 -0800 Subject: [PATCH 44/88] EMA bug fix 2 (#2330) * EMA bug fix 2 * update --- hubconf.py | 2 +- models/experimental.py | 3 ++- train.py | 10 +++++----- utils/general.py | 8 +++++--- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/hubconf.py b/hubconf.py index 47eee4477725..a8eb51681794 100644 --- a/hubconf.py +++ b/hubconf.py @@ -120,7 +120,7 @@ def custom(path_or_model='path/to/model.pt', autoshape=True): """ model = torch.load(path_or_model) if isinstance(path_or_model, str) else path_or_model # load checkpoint if isinstance(model, dict): - model = model['model'] # load model + model = model['ema' if model.get('ema') else 'model'] # load model hub_model = Model(model.yaml).to(next(model.parameters()).device) # create hub_model.load_state_dict(model.float().state_dict()) # load state_dict diff --git a/models/experimental.py b/models/experimental.py index 5fe56858c54a..d79052314f9b 100644 --- a/models/experimental.py +++ b/models/experimental.py @@ -115,7 +115,8 @@ def attempt_load(weights, map_location=None): model = Ensemble() for w in weights if isinstance(weights, list) else [weights]: attempt_download(w) - model.append(torch.load(w, map_location=map_location)['model'].float().fuse().eval()) # load FP32 model + ckpt = torch.load(w, map_location=map_location) # load + model.append(ckpt['ema' if ckpt.get('ema') else 'model'].float().fuse().eval()) # FP32 model # Compatibility updates for m in model.modules(): diff --git a/train.py b/train.py index 5c203f12651d..e2c82339f7fe 100644 --- a/train.py +++ b/train.py @@ -151,8 +151,8 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): # EMA if ema and ckpt.get('ema'): - ema.ema.load_state_dict(ckpt['ema'][0].float().state_dict()) - ema.updates = ckpt['ema'][1] + ema.ema.load_state_dict(ckpt['ema'].float().state_dict()) + ema.updates = ckpt['updates'] # Results if ckpt.get('training_results') is not None: @@ -383,9 +383,9 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): ckpt = {'epoch': epoch, 'best_fitness': best_fitness, 'training_results': results_file.read_text(), - 'model': ema.ema if final_epoch else deepcopy( - model.module if is_parallel(model) else model).half(), - 'ema': (deepcopy(ema.ema).half(), ema.updates), + 'model': deepcopy(model.module if is_parallel(model) else model).half(), + 'ema': deepcopy(ema.ema).half(), + 'updates': ema.updates, 'optimizer': optimizer.state_dict(), 'wandb_id': wandb_run.id if wandb else None} diff --git a/utils/general.py b/utils/general.py index e5bbc50c6177..df8cf7bab60d 100755 --- a/utils/general.py +++ b/utils/general.py @@ -481,10 +481,12 @@ def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=Non return output -def strip_optimizer(f='weights/best.pt', s=''): # from utils.general import *; strip_optimizer() +def strip_optimizer(f='best.pt', s=''): # from utils.general import *; strip_optimizer() # Strip optimizer from 'f' to finalize training, optionally save as 's' x = torch.load(f, map_location=torch.device('cpu')) - for k in 'optimizer', 'training_results', 'wandb_id', 'ema': # keys + if x.get('ema'): + x['model'] = x['ema'] # replace model with ema + for k in 'optimizer', 'training_results', 'wandb_id', 'ema', 'updates': # keys x[k] = None x['epoch'] = -1 x['model'].half() # to FP16 @@ -492,7 +494,7 @@ def strip_optimizer(f='weights/best.pt', s=''): # from utils.general import *; p.requires_grad = False torch.save(x, s or f) mb = os.path.getsize(s or f) / 1E6 # filesize - print('Optimizer stripped from %s,%s %.1fMB' % (f, (' saved as %s,' % s) if s else '', mb)) + print(f"Optimizer stripped from {f},{(' saved as %s,' % s) if s else ''} {mb:.1f}MB") def print_mutation(hyp, results, yaml_file='hyp_evolved.yaml', bucket=''): From ab86cec85443f979ee7f99bdb60223ad36b07198 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 2 Mar 2021 13:01:59 -0800 Subject: [PATCH 45/88] FROM nvcr.io/nvidia/pytorch:21.02-py3 (#2341) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a768774fa9c7..d42af2f78954 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Start FROM Nvidia PyTorch image https://ngc.nvidia.com/catalog/containers/nvidia:pytorch -FROM nvcr.io/nvidia/pytorch:20.12-py3 +FROM nvcr.io/nvidia/pytorch:21.02-py3 # Install linux packages RUN apt update && apt install -y zip htop screen libgl1-mesa-glx From 2c56ad5436cf0b84612c0c83842067d34df5c94b Mon Sep 17 00:00:00 2001 From: Ryan Avery Date: Tue, 2 Mar 2021 16:09:52 -0800 Subject: [PATCH 46/88] Confusion matrix background axis swap (#2114) --- utils/metrics.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utils/metrics.py b/utils/metrics.py index ba812ff13a58..666b8c7ec1c0 100644 --- a/utils/metrics.py +++ b/utils/metrics.py @@ -147,12 +147,12 @@ def process_batch(self, detections, labels): if n and sum(j) == 1: self.matrix[gc, detection_classes[m1[j]]] += 1 # correct else: - self.matrix[gc, self.nc] += 1 # background FP + self.matrix[self.nc, gc] += 1 # background FP if n: for i, dc in enumerate(detection_classes): if not any(m1 == i): - self.matrix[self.nc, dc] += 1 # background FN + self.matrix[dc, self.nc] += 1 # background FN def matrix(self): return self.matrix @@ -168,8 +168,8 @@ def plot(self, save_dir='', names=()): sn.set(font_scale=1.0 if self.nc < 50 else 0.8) # for label size labels = (0 < len(names) < 99) and len(names) == self.nc # apply names to ticklabels sn.heatmap(array, annot=self.nc < 30, annot_kws={"size": 8}, cmap='Blues', fmt='.2f', square=True, - xticklabels=names + ['background FN'] if labels else "auto", - yticklabels=names + ['background FP'] if labels else "auto").set_facecolor((1, 1, 1)) + xticklabels=names + ['background FP'] if labels else "auto", + yticklabels=names + ['background FN'] if labels else "auto").set_facecolor((1, 1, 1)) fig.axes[0].set_xlabel('True') fig.axes[0].set_ylabel('Predicted') fig.savefig(Path(save_dir) / 'confusion_matrix.png', dpi=250) From fe6ebb96bbe630cc45ed02ec0ea3fa0a3aa8c506 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 2 Mar 2021 19:20:51 -0800 Subject: [PATCH 47/88] Created using Colaboratory --- tutorial.ipynb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tutorial.ipynb b/tutorial.ipynb index 7fce40c3824e..f2b03dc57f0a 100644 --- a/tutorial.ipynb +++ b/tutorial.ipynb @@ -1169,11 +1169,9 @@ }, "source": [ "# Reproduce\n", - "%%shell\n", - "for x in yolov5s yolov5m yolov5l yolov5x; do\n", - " python test.py --weights $x.pt --data coco.yaml --img 640 --conf 0.25 --iou 0.45 # speed\n", - " python test.py --weights $x.pt --data coco.yaml --img 640 --conf 0.001 --iou 0.65 # mAP\n", - "done" + "for x in 'yolov5s', 'yolov5m', 'yolov5l', 'yolov5x':\n", + " !python test.py --weights {x}.pt --data coco.yaml --img 640 --conf 0.25 --iou 0.45 # speed\n", + " !python test.py --weights {x}.pt --data coco.yaml --img 640 --conf 0.001 --iou 0.65 # mAP" ], "execution_count": null, "outputs": [] From a3ecf0fd640465f9a7c009e81bcc5ecabf381004 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 2 Mar 2021 23:08:21 -0800 Subject: [PATCH 48/88] Anchor override (#2350) --- models/yolo.py | 7 +++++-- train.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/models/yolo.py b/models/yolo.py index a9e1da43d913..a047fef397ee 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -62,7 +62,7 @@ def _make_grid(nx=20, ny=20): class Model(nn.Module): - def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None): # model, input channels, number of classes + def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, anchors=None): # model, input channels, number of classes super(Model, self).__init__() if isinstance(cfg, dict): self.yaml = cfg # model dict @@ -75,8 +75,11 @@ def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None): # model, input channels, # Define model ch = self.yaml['ch'] = self.yaml.get('ch', ch) # input channels if nc and nc != self.yaml['nc']: - logger.info('Overriding model.yaml nc=%g with nc=%g' % (self.yaml['nc'], nc)) + logger.info(f"Overriding model.yaml nc={self.yaml['nc']} with nc={nc}") self.yaml['nc'] = nc # override yaml value + if anchors: + logger.info(f'Overriding model.yaml anchors with anchors={anchors}') + self.yaml['anchors'] = round(anchors) # override yaml value self.model, self.save = parse_model(deepcopy(self.yaml), ch=[ch]) # model, savelist self.names = [str(i) for i in range(self.yaml['nc'])] # default names # print([x.shape for x in self.forward(torch.zeros(1, ch, 64, 64))]) diff --git a/train.py b/train.py index e2c82339f7fe..1b8b315ce927 100644 --- a/train.py +++ b/train.py @@ -84,7 +84,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): model.load_state_dict(state_dict, strict=False) # load logger.info('Transferred %g/%g items from %s' % (len(state_dict), len(model.state_dict()), weights)) # report else: - model = Model(opt.cfg, ch=3, nc=nc).to(device) # create + model = Model(opt.cfg, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create # Freeze freeze = [] # parameter names to freeze (full or partial) From e931b9da33f45551928059b8d61bddd50e401e48 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 3 Mar 2021 21:06:36 -0800 Subject: [PATCH 49/88] Resume with custom anchors fix (#2361) * Resume with custom anchors fix * Update train.py --- train.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/train.py b/train.py index 1b8b315ce927..ecac59857ccc 100644 --- a/train.py +++ b/train.py @@ -75,10 +75,8 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): with torch_distributed_zero_first(rank): attempt_download(weights) # download if not found locally ckpt = torch.load(weights, map_location=device) # load checkpoint - if hyp.get('anchors'): - ckpt['model'].yaml['anchors'] = round(hyp['anchors']) # force autoanchor - model = Model(opt.cfg or ckpt['model'].yaml, ch=3, nc=nc).to(device) # create - exclude = ['anchor'] if opt.cfg or hyp.get('anchors') else [] # exclude keys + model = Model(opt.cfg or ckpt['model'].yaml, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create + exclude = ['anchor'] if (opt.cfg or hyp.get('anchors')) and not opt.resume else [] # exclude keys state_dict = ckpt['model'].float().state_dict() # to FP32 state_dict = intersect_dicts(state_dict, model.state_dict(), exclude=exclude) # intersect model.load_state_dict(state_dict, strict=False) # load @@ -216,6 +214,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): # Anchors if not opt.noautoanchor: check_anchors(dataset, model=model, thr=hyp['anchor_t'], imgsz=imgsz) + model.half().float() # pre-reduce anchor precision # Model parameters hyp['box'] *= 3. / nl # scale to layers From 300d518f73796cebb26f0a3233e180ef1665d6ee Mon Sep 17 00:00:00 2001 From: Yonghye Kwon Date: Sat, 6 Mar 2021 06:06:18 +0900 Subject: [PATCH 50/88] Faster random index generator for mosaic augmentation (#2345) * faster random index generator for mosaic augementation We don't need to access list to generate random index It makes augmentation slower. * Update datasets.py Co-authored-by: Glenn Jocher From 692e1f31dc1fecdd57bfada86380933953b6e899 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 5 Mar 2021 15:26:27 -0800 Subject: [PATCH 51/88] --no-cache notebook (#2381) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d42af2f78954..1a8fe2e72885 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ RUN apt update && apt install -y zip htop screen libgl1-mesa-glx # Install python dependencies RUN python -m pip install --upgrade pip COPY requirements.txt . -RUN pip install -r requirements.txt gsutil +RUN pip install --no-cache -r requirements.txt gsutil notebook # Create working directory RUN mkdir -p /usr/src/app From c64fe219b4333b98c88a2a706101597f4059bb71 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 5 Mar 2021 15:53:57 -0800 Subject: [PATCH 52/88] ENV HOME=/usr/src/app (#2382) Set HOME environment variable per Binder requirements. https://github.com/binder-examples/minimal-dockerfile --- Dockerfile | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1a8fe2e72885..e1b40c2d15c6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,8 +5,8 @@ FROM nvcr.io/nvidia/pytorch:21.02-py3 RUN apt update && apt install -y zip htop screen libgl1-mesa-glx # Install python dependencies -RUN python -m pip install --upgrade pip COPY requirements.txt . +RUN python -m pip install --upgrade pip RUN pip install --no-cache -r requirements.txt gsutil notebook # Create working directory @@ -16,11 +16,8 @@ WORKDIR /usr/src/app # Copy contents COPY . /usr/src/app -# Copy weights -#RUN python3 -c "from models import *; \ -#attempt_download('weights/yolov5s.pt'); \ -#attempt_download('weights/yolov5m.pt'); \ -#attempt_download('weights/yolov5l.pt')" +# Set environment variables +ENV HOME=/usr/src/app # --------------------------------------------------- Extras Below --------------------------------------------------- From cd8ed3521d98ea120d07f57ea5372c4b375241ca Mon Sep 17 00:00:00 2001 From: Yonghye Kwon Date: Sat, 6 Mar 2021 15:58:26 +0900 Subject: [PATCH 53/88] image weights compatible faster random index generator v2 for mosaic augmentation (#2383) image weights compatible faster random index generator v2 for mosaic augmentation --- utils/datasets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/datasets.py b/utils/datasets.py index d6ab16518034..ed18f449ddd3 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -666,7 +666,7 @@ def load_mosaic(self, index): labels4, segments4 = [], [] s = self.img_size yc, xc = [int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border] # mosaic center x, y - indices = [index] + [self.indices[random.randint(0, self.n - 1)] for _ in range(3)] # 3 additional image indices + indices = [index] + random.choices(self.indices, k=3) # 3 additional image indices for i, index in enumerate(indices): # Load image img, _, (h, w) = load_image(self, index) @@ -721,7 +721,7 @@ def load_mosaic9(self, index): labels9, segments9 = [], [] s = self.img_size - indices = [index] + [self.indices[random.randint(0, self.n - 1)] for _ in range(8)] # 8 additional image indices + indices = [index] + random.choices(self.indices, k=8) # 8 additional image indices for i, index in enumerate(indices): # Load image img, _, (h, w) = load_image(self, index) From 7a0a81fd1d770bbfbf94ced5e38cc0f0573b765e Mon Sep 17 00:00:00 2001 From: Jan Hajek Date: Sat, 6 Mar 2021 21:02:10 +0100 Subject: [PATCH 54/88] GPU export options (#2297) * option for skip last layer and cuda export support * added parameter device * fix import * cleanup 1 * cleanup 2 * opt-in grid --grid will export with grid computation, default export will skip grid (same as current) * default --device cpu GPU export causes ONNX and CoreML errors. Co-authored-by: Jan Hajek Co-authored-by: Glenn Jocher --- models/export.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/models/export.py b/models/export.py index cc817871f218..11e60c7a583d 100644 --- a/models/export.py +++ b/models/export.py @@ -17,13 +17,16 @@ from models.experimental import attempt_load from utils.activations import Hardswish, SiLU from utils.general import set_logging, check_img_size +from utils.torch_utils import select_device if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--weights', type=str, default='./yolov5s.pt', help='weights path') # from yolov5/models/ parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='image size') # height, width - parser.add_argument('--dynamic', action='store_true', help='dynamic ONNX axes') parser.add_argument('--batch-size', type=int, default=1, help='batch size') + parser.add_argument('--dynamic', action='store_true', help='dynamic ONNX axes') + parser.add_argument('--grid', action='store_true', help='export Detect() layer grid') + parser.add_argument('--device', default='cpu', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') opt = parser.parse_args() opt.img_size *= 2 if len(opt.img_size) == 1 else 1 # expand print(opt) @@ -31,7 +34,8 @@ t = time.time() # Load PyTorch model - model = attempt_load(opt.weights, map_location=torch.device('cpu')) # load FP32 model + device = select_device(opt.device) + model = attempt_load(opt.weights, map_location=device) # load FP32 model labels = model.names # Checks @@ -39,7 +43,7 @@ opt.img_size = [check_img_size(x, gs) for x in opt.img_size] # verify img_size are gs-multiples # Input - img = torch.zeros(opt.batch_size, 3, *opt.img_size) # image size(1,3,320,192) iDetection + img = torch.zeros(opt.batch_size, 3, *opt.img_size).to(device) # image size(1,3,320,192) iDetection # Update model for k, m in model.named_modules(): @@ -51,7 +55,7 @@ m.act = SiLU() # elif isinstance(m, models.yolo.Detect): # m.forward = m.forward_export # assign forward (optional) - model.model[-1].export = True # set Detect() layer export=True + model.model[-1].export = not opt.grid # set Detect() layer grid export y = model(img) # dry run # TorchScript export From ba18528b4737a4b08b55653c54f3d3e830f8e151 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 6 Mar 2021 13:07:34 -0800 Subject: [PATCH 55/88] bbox_iou() stability and speed improvements (#2385) --- utils/general.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/general.py b/utils/general.py index df8cf7bab60d..e1c14bdaa4b3 100755 --- a/utils/general.py +++ b/utils/general.py @@ -312,7 +312,7 @@ def clip_coords(boxes, img_shape): boxes[:, 3].clamp_(0, img_shape[0]) # y2 -def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-9): +def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7): # Returns the IoU of box1 to box2. box1 is 4, box2 is nx4 box2 = box2.T @@ -348,7 +348,7 @@ def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False, eps= elif CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47 v = (4 / math.pi ** 2) * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2) with torch.no_grad(): - alpha = v / ((1 + eps) - iou + v) + alpha = v / (v - iou + (1 + eps)) return iou - (rho2 / c2 + v * alpha) # CIoU else: # GIoU https://arxiv.org/pdf/1902.09630.pdf c_area = cw * ch + eps # convex area From 7c2c95732c3eaa10465080b693e14a9e12e08e8d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 7 Mar 2021 20:18:30 -0800 Subject: [PATCH 56/88] AWS wait && echo "All tasks done." (#2391) --- utils/aws/userdata.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/aws/userdata.sh b/utils/aws/userdata.sh index 36405d1a1565..a6d6e7976cf3 100644 --- a/utils/aws/userdata.sh +++ b/utils/aws/userdata.sh @@ -11,7 +11,8 @@ if [ ! -d yolov5 ]; then cd yolov5 bash data/scripts/get_coco.sh && echo "Data done." & sudo docker pull ultralytics/yolov5:latest && echo "Docker done." & - # python -m pip install --upgrade pip && pip install -r requirements.txt && python detect.py && echo "Requirements done." & + python -m pip install --upgrade pip && pip install -r requirements.txt && python detect.py && echo "Requirements done." & + wait && echo "All tasks done." # finish background tasks else echo "Running re-start script." # resume interrupted runs i=0 From e8a2b83268950e346899a84e8d29e84d178553b1 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 7 Mar 2021 20:21:49 -0800 Subject: [PATCH 57/88] GCP sudo docker userdata.sh (#2393) * GCP sudo docker * cleanup --- utils/aws/userdata.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utils/aws/userdata.sh b/utils/aws/userdata.sh index a6d6e7976cf3..890606b76a06 100644 --- a/utils/aws/userdata.sh +++ b/utils/aws/userdata.sh @@ -16,12 +16,12 @@ if [ ! -d yolov5 ]; then else echo "Running re-start script." # resume interrupted runs i=0 - list=$(docker ps -qa) # container list i.e. $'one\ntwo\nthree\nfour' + list=$(sudo docker ps -qa) # container list i.e. $'one\ntwo\nthree\nfour' while IFS= read -r id; do ((i++)) echo "restarting container $i: $id" - docker start $id - # docker exec -it $id python train.py --resume # single-GPU - docker exec -d $id python utils/aws/resume.py + sudo docker start $id + # sudo docker exec -it $id python train.py --resume # single-GPU + sudo docker exec -d $id python utils/aws/resume.py # multi-scenario done <<<"$list" fi From c51dfec8ea554db6811579f6d618dac45766e647 Mon Sep 17 00:00:00 2001 From: Kartikeya Sharma Date: Tue, 9 Mar 2021 21:26:49 -0600 Subject: [PATCH 58/88] CVPR 2021 Argoverse-HD dataset autodownload support (#2400) * added argoverse-download ability * bugfix * add support for Argoverse dataset * Refactored code * renamed to argoverse-HD * unzip -q and YOLOv5 small cleanup items * add image counts Co-authored-by: Kartikeya Sharma Co-authored-by: Kartikeya Sharma Co-authored-by: Glenn Jocher --- data/argoverse_hd.yaml | 21 +++++++++++ data/scripts/get_argoverse_hd.sh | 65 ++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 data/argoverse_hd.yaml create mode 100644 data/scripts/get_argoverse_hd.sh diff --git a/data/argoverse_hd.yaml b/data/argoverse_hd.yaml new file mode 100644 index 000000000000..df7a9361e769 --- /dev/null +++ b/data/argoverse_hd.yaml @@ -0,0 +1,21 @@ +# Argoverse-HD dataset (ring-front-center camera) http://www.cs.cmu.edu/~mengtial/proj/streaming/ +# Train command: python train.py --data argoverse_hd.yaml +# Default dataset location is next to /yolov5: +# /parent_folder +# /argoverse +# /yolov5 + + +# download command/URL (optional) +download: bash data/scripts/get_argoverse_hd.sh + +# train and val data as 1) directory: path/images/, 2) file: path/images.txt, or 3) list: [path1/images/, path2/images/] +train: ../argoverse/Argoverse-1.1/images/train/ # 39384 images +val: ../argoverse/Argoverse-1.1/images/val/ # 15062 iamges +test: ../argoverse/Argoverse-1.1/images/test/ # Submit to: https://eval.ai/web/challenges/challenge-page/800/overview + +# number of classes +nc: 8 + +# class names +names: [ 'person', 'bicycle', 'car', 'motorcycle', 'bus', 'truck', 'traffic_light', 'stop_sign' ] diff --git a/data/scripts/get_argoverse_hd.sh b/data/scripts/get_argoverse_hd.sh new file mode 100644 index 000000000000..884862db03f5 --- /dev/null +++ b/data/scripts/get_argoverse_hd.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Argoverse-HD dataset (ring-front-center camera) http://www.cs.cmu.edu/~mengtial/proj/streaming/ +# Download command: bash data/scripts/get_argoverse_hd.sh +# Train command: python train.py --data argoverse_hd.yaml +# Default dataset location is next to /yolov5: +# /parent_folder +# /argoverse +# /yolov5 + +# Download/unzip images +d='../argoverse/' # unzip directory +mkdir $d +url=https://argoverse-hd.s3.us-east-2.amazonaws.com/ +f=Argoverse-HD-Full.zip +wget $url$f -O $f && unzip -q $f -d $d && rm $f & # download, unzip, remove in background +wait # finish background tasks + +cd ../argoverse/Argoverse-1.1/ +ln -s tracking images + +cd ../Argoverse-HD/annotations/ + +python3 - "$@" < Date: Tue, 9 Mar 2021 21:07:27 -0800 Subject: [PATCH 59/88] CVPR 2021 Argoverse-HD autodownload fix (#2418) --- data/scripts/get_argoverse_hd.sh | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/data/scripts/get_argoverse_hd.sh b/data/scripts/get_argoverse_hd.sh index 884862db03f5..9e0db9fad91b 100644 --- a/data/scripts/get_argoverse_hd.sh +++ b/data/scripts/get_argoverse_hd.sh @@ -12,8 +12,8 @@ d='../argoverse/' # unzip directory mkdir $d url=https://argoverse-hd.s3.us-east-2.amazonaws.com/ f=Argoverse-HD-Full.zip -wget $url$f -O $f && unzip -q $f -d $d && rm $f & # download, unzip, remove in background -wait # finish background tasks +wget $url$f -O $f && unzip -q $f -d $d && rm $f &# download, unzip, remove in background +wait # finish background tasks cd ../argoverse/Argoverse-1.1/ ln -s tracking images @@ -23,6 +23,7 @@ cd ../Argoverse-HD/annotations/ python3 - "$@" < Date: Tue, 9 Mar 2021 23:43:46 -0800 Subject: [PATCH 60/88] DDP after autoanchor reorder (#2421) --- train.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/train.py b/train.py index ecac59857ccc..6bd65f063391 100644 --- a/train.py +++ b/train.py @@ -181,10 +181,6 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model).to(device) logger.info('Using SyncBatchNorm()') - # DDP mode - if cuda and rank != -1: - model = DDP(model, device_ids=[opt.local_rank], output_device=opt.local_rank) - # Trainloader dataloader, dataset = create_dataloader(train_path, imgsz, batch_size, gs, opt, hyp=hyp, augment=True, cache=opt.cache_images, rect=opt.rect, rank=rank, @@ -214,7 +210,11 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): # Anchors if not opt.noautoanchor: check_anchors(dataset, model=model, thr=hyp['anchor_t'], imgsz=imgsz) - model.half().float() # pre-reduce anchor precision + model.half().float() # pre-reduce anchor precision + + # DDP mode + if cuda and rank != -1: + model = DDP(model, device_ids=[opt.local_rank], output_device=opt.local_rank) # Model parameters hyp['box'] *= 3. / nl # scale to layers From f01f3223d564e40e7dfa99997c3c520ab128c925 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 12 Mar 2021 13:35:44 -0800 Subject: [PATCH 61/88] Integer printout (#2450) * Integer printout * test.py 'Labels' * Update train.py --- test.py | 4 ++-- train.py | 2 +- utils/torch_utils.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test.py b/test.py index fd4d339ffea6..46288019a8bd 100644 --- a/test.py +++ b/test.py @@ -93,7 +93,7 @@ def test(data, confusion_matrix = ConfusionMatrix(nc=nc) names = {k: v for k, v in enumerate(model.names if hasattr(model, 'names') else model.module.names)} coco91class = coco80_to_coco91_class() - s = ('%20s' + '%12s' * 6) % ('Class', 'Images', 'Targets', 'P', 'R', 'mAP@.5', 'mAP@.5:.95') + s = ('%20s' + '%12s' * 6) % ('Class', 'Images', 'Labels', 'P', 'R', 'mAP@.5', 'mAP@.5:.95') p, r, f1, mp, mr, map50, map, t0, t1 = 0., 0., 0., 0., 0., 0., 0., 0., 0. loss = torch.zeros(3, device=device) jdict, stats, ap, ap_class, wandb_images = [], [], [], [], [] @@ -223,7 +223,7 @@ def test(data, nt = torch.zeros(1) # Print results - pf = '%20s' + '%12.3g' * 6 # print format + pf = '%20s' + '%12i' * 2 + '%12.3g' * 4 # print format print(pf % ('all', seen, nt.sum(), mp, mr, map50, map)) # Print results per class diff --git a/train.py b/train.py index 6bd65f063391..dcb89a3c199b 100644 --- a/train.py +++ b/train.py @@ -264,7 +264,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): if rank != -1: dataloader.sampler.set_epoch(epoch) pbar = enumerate(dataloader) - logger.info(('\n' + '%10s' * 8) % ('Epoch', 'gpu_mem', 'box', 'obj', 'cls', 'total', 'targets', 'img_size')) + logger.info(('\n' + '%10s' * 8) % ('Epoch', 'gpu_mem', 'box', 'obj', 'cls', 'total', 'labels', 'img_size')) if rank in [-1, 0]: pbar = tqdm(pbar, total=nb) # progress bar optimizer.zero_grad() diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 1b1cc2038c55..806d29470e55 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -120,7 +120,7 @@ def profile(x, ops, n=100, device=None): s_in = tuple(x.shape) if isinstance(x, torch.Tensor) else 'list' s_out = tuple(y.shape) if isinstance(y, torch.Tensor) else 'list' p = sum(list(x.numel() for x in m.parameters())) if isinstance(m, nn.Module) else 0 # parameters - print(f'{p:12.4g}{flops:12.4g}{dtf:16.4g}{dtb:16.4g}{str(s_in):>24s}{str(s_out):>24s}') + print(f'{p:12}{flops:12.4g}{dtf:16.4g}{dtb:16.4g}{str(s_in):>24s}{str(s_out):>24s}') def is_parallel(model): From f4197214aa3776ea2dfab0f5fdf1f36537b0b125 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 12 Mar 2021 22:08:42 -0800 Subject: [PATCH 62/88] Update test.py --task train val study (#2453) * Update test.py --task train val study * update argparser --task --- test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test.py b/test.py index 46288019a8bd..39e0992264ec 100644 --- a/test.py +++ b/test.py @@ -85,9 +85,9 @@ def test(data, if not training: if device.type != 'cpu': model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters()))) # run once - path = data['test'] if opt.task == 'test' else data['val'] # path to val/test images - dataloader = create_dataloader(path, imgsz, batch_size, gs, opt, pad=0.5, rect=True, - prefix=colorstr('test: ' if opt.task == 'test' else 'val: '))[0] + task = opt.task if opt.task in ('train', 'val', 'test') else 'val' # path to train/val/test images + dataloader = create_dataloader(data[task], imgsz, batch_size, gs, opt, pad=0.5, rect=True, + prefix=colorstr(f'{task}: '))[0] seen = 0 confusion_matrix = ConfusionMatrix(nc=nc) @@ -287,7 +287,7 @@ def test(data, parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)') parser.add_argument('--conf-thres', type=float, default=0.001, help='object confidence threshold') parser.add_argument('--iou-thres', type=float, default=0.6, help='IOU threshold for NMS') - parser.add_argument('--task', default='val', help="'val', 'test', 'study'") + parser.add_argument('--task', default='val', help='train, val, test, speed or study') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--single-cls', action='store_true', help='treat as single-class dataset') parser.add_argument('--augment', action='store_true', help='augmented inference') @@ -305,7 +305,7 @@ def test(data, print(opt) check_requirements() - if opt.task in ['val', 'test']: # run normally + if opt.task in ('train', 'val', 'test'): # run normally test(opt.data, opt.weights, opt.batch_size, From 08d4918d7f49055158b1cceb27ea0d1990251afc Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 12 Mar 2021 22:15:41 -0800 Subject: [PATCH 63/88] labels.jpg class names (#2454) * labels.png class names * fontsize=10 --- train.py | 2 +- utils/plots.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/train.py b/train.py index dcb89a3c199b..005fdf60c021 100644 --- a/train.py +++ b/train.py @@ -203,7 +203,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): # cf = torch.bincount(c.long(), minlength=nc) + 1. # frequency # model._initialize_biases(cf.to(device)) if plots: - plot_labels(labels, save_dir, loggers) + plot_labels(labels, names, save_dir, loggers) if tb_writer: tb_writer.add_histogram('classes', c, 0) diff --git a/utils/plots.py b/utils/plots.py index aa9a1cab81f0..47e7b7b74f1c 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -269,7 +269,7 @@ def plot_study_txt(path='', x=None): # from utils.plots import *; plot_study_tx plt.savefig(str(Path(path).name) + '.png', dpi=300) -def plot_labels(labels, save_dir=Path(''), loggers=None): +def plot_labels(labels, names=(), save_dir=Path(''), loggers=None): # plot dataset labels print('Plotting labels... ') c, b = labels[:, 0], labels[:, 1:].transpose() # classes, boxes @@ -286,7 +286,12 @@ def plot_labels(labels, save_dir=Path(''), loggers=None): matplotlib.use('svg') # faster ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True)[1].ravel() ax[0].hist(c, bins=np.linspace(0, nc, nc + 1) - 0.5, rwidth=0.8) - ax[0].set_xlabel('classes') + ax[0].set_ylabel('instances') + if 0 < len(names) < 30: + ax[0].set_xticks(range(len(names))) + ax[0].set_xticklabels(names, rotation=90, fontsize=10) + else: + ax[0].set_xlabel('classes') sns.histplot(x, x='x', y='y', ax=ax[2], bins=50, pmax=0.9) sns.histplot(x, x='width', y='height', ax=ax[3], bins=50, pmax=0.9) From 747c2653eecfb870b1ed40b1e00e0ef209b036e9 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 12 Mar 2021 22:27:53 -0800 Subject: [PATCH 64/88] CVPR 2021 Argoverse-HD autodownload curl (#2455) curl preferred over wget for slightly better cross platform compatibility (i.e. out of the box macos compatible). --- data/scripts/get_argoverse_hd.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/scripts/get_argoverse_hd.sh b/data/scripts/get_argoverse_hd.sh index 9e0db9fad91b..caec61efed78 100644 --- a/data/scripts/get_argoverse_hd.sh +++ b/data/scripts/get_argoverse_hd.sh @@ -12,7 +12,7 @@ d='../argoverse/' # unzip directory mkdir $d url=https://argoverse-hd.s3.us-east-2.amazonaws.com/ f=Argoverse-HD-Full.zip -wget $url$f -O $f && unzip -q $f -d $d && rm $f &# download, unzip, remove in background +curl -L $url$f -o $f && unzip -q $f -d $d && rm $f &# download, unzip, remove in background wait # finish background tasks cd ../argoverse/Argoverse-1.1/ From 569757ecc09d115e275a6ec3662514d72dfe18c2 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 13 Mar 2021 19:50:34 -0800 Subject: [PATCH 65/88] Add autoShape() speed profiling (#2459) * Add autoShape() speed profiling * Update common.py * Create README.md * Update hubconf.py * cleanuip --- README.md | 4 ++-- hubconf.py | 8 ++++---- models/common.py | 14 +++++++++++--- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b7129e80adfe..097b2750bf49 100755 --- a/README.md +++ b/README.md @@ -108,11 +108,11 @@ To run **batched inference** with YOLOv5 and [PyTorch Hub](https://github.com/ul import torch # Model -model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True) +model = torch.hub.load('ultralytics/yolov5', 'yolov5s') # Images dir = 'https://github.com/ultralytics/yolov5/raw/master/data/images/' -imgs = [dir + f for f in ('zidane.jpg', 'bus.jpg')] # batched list of images +imgs = [dir + f for f in ('zidane.jpg', 'bus.jpg')] # batch of images # Inference results = model(imgs) diff --git a/hubconf.py b/hubconf.py index a8eb51681794..e51ac90da36c 100644 --- a/hubconf.py +++ b/hubconf.py @@ -51,7 +51,7 @@ def create(name, pretrained, channels, classes, autoshape): raise Exception(s) from e -def yolov5s(pretrained=False, channels=3, classes=80, autoshape=True): +def yolov5s(pretrained=True, channels=3, classes=80, autoshape=True): """YOLOv5-small model from https://github.com/ultralytics/yolov5 Arguments: @@ -65,7 +65,7 @@ def yolov5s(pretrained=False, channels=3, classes=80, autoshape=True): return create('yolov5s', pretrained, channels, classes, autoshape) -def yolov5m(pretrained=False, channels=3, classes=80, autoshape=True): +def yolov5m(pretrained=True, channels=3, classes=80, autoshape=True): """YOLOv5-medium model from https://github.com/ultralytics/yolov5 Arguments: @@ -79,7 +79,7 @@ def yolov5m(pretrained=False, channels=3, classes=80, autoshape=True): return create('yolov5m', pretrained, channels, classes, autoshape) -def yolov5l(pretrained=False, channels=3, classes=80, autoshape=True): +def yolov5l(pretrained=True, channels=3, classes=80, autoshape=True): """YOLOv5-large model from https://github.com/ultralytics/yolov5 Arguments: @@ -93,7 +93,7 @@ def yolov5l(pretrained=False, channels=3, classes=80, autoshape=True): return create('yolov5l', pretrained, channels, classes, autoshape) -def yolov5x(pretrained=False, channels=3, classes=80, autoshape=True): +def yolov5x(pretrained=True, channels=3, classes=80, autoshape=True): """YOLOv5-xlarge model from https://github.com/ultralytics/yolov5 Arguments: diff --git a/models/common.py b/models/common.py index ad35f908d865..7ef5762efbf3 100644 --- a/models/common.py +++ b/models/common.py @@ -12,6 +12,7 @@ from utils.datasets import letterbox from utils.general import non_max_suppression, make_divisible, scale_coords, xyxy2xywh from utils.plots import color_list, plot_one_box +from utils.torch_utils import time_synchronized def autopad(k, p=None): # kernel, padding @@ -190,6 +191,7 @@ def forward(self, imgs, size=640, augment=False, profile=False): # torch: = torch.zeros(16,3,720,1280) # BCHW # multiple: = [Image.open('image1.jpg'), Image.open('image2.jpg'), ...] # list of images + t = [time_synchronized()] p = next(self.model.parameters()) # for device and type if isinstance(imgs, torch.Tensor): # torch return self.model(imgs.to(p.device).type_as(p), augment, profile) # inference @@ -216,22 +218,25 @@ def forward(self, imgs, size=640, augment=False, profile=False): x = np.stack(x, 0) if n > 1 else x[0][None] # stack x = np.ascontiguousarray(x.transpose((0, 3, 1, 2))) # BHWC to BCHW x = torch.from_numpy(x).to(p.device).type_as(p) / 255. # uint8 to fp16/32 + t.append(time_synchronized()) # Inference with torch.no_grad(): y = self.model(x, augment, profile)[0] # forward - y = non_max_suppression(y, conf_thres=self.conf, iou_thres=self.iou, classes=self.classes) # NMS + t.append(time_synchronized()) # Post-process + y = non_max_suppression(y, conf_thres=self.conf, iou_thres=self.iou, classes=self.classes) # NMS for i in range(n): scale_coords(shape1, y[i][:, :4], shape0[i]) + t.append(time_synchronized()) - return Detections(imgs, y, files, self.names) + return Detections(imgs, y, files, t, self.names, x.shape) class Detections: # detections class for YOLOv5 inference results - def __init__(self, imgs, pred, files, names=None): + def __init__(self, imgs, pred, files, times, names=None, shape=None): super(Detections, self).__init__() d = pred[0].device # device gn = [torch.tensor([*[im.shape[i] for i in [1, 0, 1, 0]], 1., 1.], device=d) for im in imgs] # normalizations @@ -244,6 +249,8 @@ def __init__(self, imgs, pred, files, names=None): self.xyxyn = [x / g for x, g in zip(self.xyxy, gn)] # xyxy normalized self.xywhn = [x / g for x, g in zip(self.xywh, gn)] # xywh normalized self.n = len(self.pred) + self.t = ((times[i + 1] - times[i]) * 1000 / self.n for i in range(3)) # timestamps (ms) + self.s = shape # inference BCHW shape def display(self, pprint=False, show=False, save=False, render=False, save_dir=''): colors = color_list() @@ -271,6 +278,7 @@ def display(self, pprint=False, show=False, save=False, render=False, save_dir=' def print(self): self.display(pprint=True) # print results + print(f'Speed: %.1f/%.1f/%.1f ms pre-process/inference/NMS per image at shape {tuple(self.s)}' % tuple(self.t)) def show(self): self.display(show=True) # show results From f813f6dcc875901c6ba7a509c14227c2292efed4 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 13 Mar 2021 20:00:03 -0800 Subject: [PATCH 66/88] autoShape() speed profiling update (#2460) --- models/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/common.py b/models/common.py index 7ef5762efbf3..464d639a1f0b 100644 --- a/models/common.py +++ b/models/common.py @@ -168,7 +168,6 @@ def forward(self, x): class autoShape(nn.Module): # input-robust model wrapper for passing cv2/np/PIL/torch inputs. Includes preprocessing, inference and NMS - img_size = 640 # inference size (pixels) conf = 0.25 # NMS confidence threshold iou = 0.45 # NMS IoU threshold classes = None # (optional list) filter by class @@ -278,7 +277,8 @@ def display(self, pprint=False, show=False, save=False, render=False, save_dir=' def print(self): self.display(pprint=True) # print results - print(f'Speed: %.1f/%.1f/%.1f ms pre-process/inference/NMS per image at shape {tuple(self.s)}' % tuple(self.t)) + print(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {tuple(self.s)}' % + tuple(self.t)) def show(self): self.display(show=True) # show results From 20d879db36c4b5f72f4002127a9ebbdf30da11de Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 13 Mar 2021 20:05:21 -0800 Subject: [PATCH 67/88] Update tutorial.ipynb --- tutorial.ipynb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tutorial.ipynb b/tutorial.ipynb index f2b03dc57f0a..5eeb78d12faa 100644 --- a/tutorial.ipynb +++ b/tutorial.ipynb @@ -605,14 +605,14 @@ "output_type": "stream", "text": [ "Namespace(agnostic_nms=False, augment=False, classes=None, conf_thres=0.25, device='', exist_ok=False, img_size=640, iou_thres=0.45, name='exp', project='runs/detect', save_conf=False, save_txt=False, source='data/images/', update=False, view_img=False, weights=['yolov5s.pt'])\n", - "YOLOv5 v4.0-21-gb26a2f6 torch 1.7.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16130.5MB)\n", + "YOLOv5 v4.0-132-gf813f6d torch 1.8.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n", "\n", "Fusing layers... \n", "Model Summary: 224 layers, 7266973 parameters, 0 gradients, 17.0 GFLOPS\n", - "image 1/2 /content/yolov5/data/images/bus.jpg: 640x480 4 persons, 1 buss, 1 skateboards, Done. (0.011s)\n", - "image 2/2 /content/yolov5/data/images/zidane.jpg: 384x640 2 persons, 2 ties, Done. (0.011s)\n", + "image 1/2 /content/yolov5/data/images/bus.jpg: 640x480 4 persons, 1 bus, Done. (0.008s)\n", + "image 2/2 /content/yolov5/data/images/zidane.jpg: 384x640 2 persons, 1 tie, Done. (0.008s)\n", "Results saved to runs/detect/exp\n", - "Done. (0.110s)\n" + "Done. (0.087)\n" ], "name": "stdout" }, @@ -1247,4 +1247,4 @@ "outputs": [] } ] -} \ No newline at end of file +} From 6f718cee740e7cd423edd1136db78c5be49fa7c0 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 13 Mar 2021 20:20:09 -0800 Subject: [PATCH 68/88] Created using Colaboratory --- tutorial.ipynb | 185 +++++++++++++++++++++++++------------------------ 1 file changed, 93 insertions(+), 92 deletions(-) diff --git a/tutorial.ipynb b/tutorial.ipynb index 5eeb78d12faa..b678e4bec9c2 100644 --- a/tutorial.ipynb +++ b/tutorial.ipynb @@ -16,7 +16,7 @@ "accelerator": "GPU", "widgets": { "application/vnd.jupyter.widget-state+json": { - "1f8e9b8ebded4175b2eaa9f75c3ceb00": { + "b54ab52f1d4f4903897ab6cd49a3b9b2": { "model_module": "@jupyter-widgets/controls", "model_name": "HBoxModel", "state": { @@ -28,15 +28,15 @@ "_view_count": null, "_view_module_version": "1.5.0", "box_style": "", - "layout": "IPY_MODEL_0a1246a73077468ab80e979cc0576cd2", + "layout": "IPY_MODEL_1852f93fc2714d40adccb8aa161c42ff", "_model_module": "@jupyter-widgets/controls", "children": [ - "IPY_MODEL_d327cde5a85a4a51bb8b1b3e9cf06c97", - "IPY_MODEL_d5ef1cb2cbed4b87b3c5d292ff2b0da6" + "IPY_MODEL_3293cfe869bd4a1bbbe18b49b6815de1", + "IPY_MODEL_8d5ee8b8ab6d46b98818bd2c562ddd1c" ] } }, - "0a1246a73077468ab80e979cc0576cd2": { + "1852f93fc2714d40adccb8aa161c42ff": { "model_module": "@jupyter-widgets/base", "model_name": "LayoutModel", "state": { @@ -87,12 +87,12 @@ "left": null } }, - "d327cde5a85a4a51bb8b1b3e9cf06c97": { + "3293cfe869bd4a1bbbe18b49b6815de1": { "model_module": "@jupyter-widgets/controls", "model_name": "FloatProgressModel", "state": { "_view_name": "ProgressView", - "style": "IPY_MODEL_8d5dff8bca14435a88fa1814533acd85", + "style": "IPY_MODEL_49fcb2adb0354430b76f491af98abfe9", "_dom_classes": [], "description": "100%", "_model_name": "FloatProgressModel", @@ -107,30 +107,30 @@ "min": 0, "description_tooltip": null, "_model_module": "@jupyter-widgets/controls", - "layout": "IPY_MODEL_3d5136c19e7645ca9bc8f51ceffb2be1" + "layout": "IPY_MODEL_c7d76e0c53064363add56b8d05e561f5" } }, - "d5ef1cb2cbed4b87b3c5d292ff2b0da6": { + "8d5ee8b8ab6d46b98818bd2c562ddd1c": { "model_module": "@jupyter-widgets/controls", "model_name": "HTMLModel", "state": { "_view_name": "HTMLView", - "style": "IPY_MODEL_2919396dbd4b4c8e821d12bd28665d8a", + "style": "IPY_MODEL_48f321f789634aa584f8a29a3b925dd5", "_dom_classes": [], "description": "", "_model_name": "HTMLModel", "placeholder": "​", "_view_module": "@jupyter-widgets/controls", "_model_module_version": "1.5.0", - "value": " 781M/781M [00:12<00:00, 65.5MB/s]", + "value": " 781M/781M [00:13<00:00, 62.6MB/s]", "_view_count": null, "_view_module_version": "1.5.0", "description_tooltip": null, "_model_module": "@jupyter-widgets/controls", - "layout": "IPY_MODEL_6feb16f2b2fa4021b1a271e1dd442d04" + "layout": "IPY_MODEL_6610d6275f3e49d9937d50ed0a105947" } }, - "8d5dff8bca14435a88fa1814533acd85": { + "49fcb2adb0354430b76f491af98abfe9": { "model_module": "@jupyter-widgets/controls", "model_name": "ProgressStyleModel", "state": { @@ -145,7 +145,7 @@ "_model_module": "@jupyter-widgets/controls" } }, - "3d5136c19e7645ca9bc8f51ceffb2be1": { + "c7d76e0c53064363add56b8d05e561f5": { "model_module": "@jupyter-widgets/base", "model_name": "LayoutModel", "state": { @@ -196,7 +196,7 @@ "left": null } }, - "2919396dbd4b4c8e821d12bd28665d8a": { + "48f321f789634aa584f8a29a3b925dd5": { "model_module": "@jupyter-widgets/controls", "model_name": "DescriptionStyleModel", "state": { @@ -210,7 +210,7 @@ "_model_module": "@jupyter-widgets/controls" } }, - "6feb16f2b2fa4021b1a271e1dd442d04": { + "6610d6275f3e49d9937d50ed0a105947": { "model_module": "@jupyter-widgets/base", "model_name": "LayoutModel", "state": { @@ -261,7 +261,7 @@ "left": null } }, - "e6459e0bcee449b090fc9807672725bc": { + "0fffa335322b41658508e06aed0acbf0": { "model_module": "@jupyter-widgets/controls", "model_name": "HBoxModel", "state": { @@ -273,15 +273,15 @@ "_view_count": null, "_view_module_version": "1.5.0", "box_style": "", - "layout": "IPY_MODEL_c341e1d3bf3b40d1821ce392eb966c68", + "layout": "IPY_MODEL_a354c6f80ce347e5a3ef64af87c0eccb", "_model_module": "@jupyter-widgets/controls", "children": [ - "IPY_MODEL_660afee173694231a6dce3cd94df6cae", - "IPY_MODEL_261218485cef48df961519dde5edfcbe" + "IPY_MODEL_85823e71fea54c39bd11e2e972348836", + "IPY_MODEL_fb11acd663fa4e71b041d67310d045fd" ] } }, - "c341e1d3bf3b40d1821ce392eb966c68": { + "a354c6f80ce347e5a3ef64af87c0eccb": { "model_module": "@jupyter-widgets/base", "model_name": "LayoutModel", "state": { @@ -332,12 +332,12 @@ "left": null } }, - "660afee173694231a6dce3cd94df6cae": { + "85823e71fea54c39bd11e2e972348836": { "model_module": "@jupyter-widgets/controls", "model_name": "FloatProgressModel", "state": { "_view_name": "ProgressView", - "style": "IPY_MODEL_32736d503c06497abfae8c0421918255", + "style": "IPY_MODEL_8a919053b780449aae5523658ad611fa", "_dom_classes": [], "description": "100%", "_model_name": "FloatProgressModel", @@ -352,30 +352,30 @@ "min": 0, "description_tooltip": null, "_model_module": "@jupyter-widgets/controls", - "layout": "IPY_MODEL_e257738711f54d5280c8393d9d3dce1c" + "layout": "IPY_MODEL_5bae9393a58b44f7b69fb04816f94f6f" } }, - "261218485cef48df961519dde5edfcbe": { + "fb11acd663fa4e71b041d67310d045fd": { "model_module": "@jupyter-widgets/controls", "model_name": "HTMLModel", "state": { "_view_name": "HTMLView", - "style": "IPY_MODEL_beb7a6fe34b840899bb79c062681696f", + "style": "IPY_MODEL_d26c6d16c7f24030ab2da5285bf198ee", "_dom_classes": [], "description": "", "_model_name": "HTMLModel", "placeholder": "​", "_view_module": "@jupyter-widgets/controls", "_model_module_version": "1.5.0", - "value": " 21.1M/21.1M [00:00<00:00, 33.5MB/s]", + "value": " 21.1M/21.1M [00:02<00:00, 9.36MB/s]", "_view_count": null, "_view_module_version": "1.5.0", "description_tooltip": null, "_model_module": "@jupyter-widgets/controls", - "layout": "IPY_MODEL_e639132395d64d70b99d8b72c32f8fbb" + "layout": "IPY_MODEL_f7767886b2364c8d9efdc79e175ad8eb" } }, - "32736d503c06497abfae8c0421918255": { + "8a919053b780449aae5523658ad611fa": { "model_module": "@jupyter-widgets/controls", "model_name": "ProgressStyleModel", "state": { @@ -390,7 +390,7 @@ "_model_module": "@jupyter-widgets/controls" } }, - "e257738711f54d5280c8393d9d3dce1c": { + "5bae9393a58b44f7b69fb04816f94f6f": { "model_module": "@jupyter-widgets/base", "model_name": "LayoutModel", "state": { @@ -441,7 +441,7 @@ "left": null } }, - "beb7a6fe34b840899bb79c062681696f": { + "d26c6d16c7f24030ab2da5285bf198ee": { "model_module": "@jupyter-widgets/controls", "model_name": "DescriptionStyleModel", "state": { @@ -455,7 +455,7 @@ "_model_module": "@jupyter-widgets/controls" } }, - "e639132395d64d70b99d8b72c32f8fbb": { + "f7767886b2364c8d9efdc79e175ad8eb": { "model_module": "@jupyter-widgets/base", "model_name": "LayoutModel", "state": { @@ -550,7 +550,7 @@ "colab": { "base_uri": "https://localhost:8080/" }, - "outputId": "ae8805a9-ce15-4e1c-f6b4-baa1c1033f56" + "outputId": "20027455-bf84-41fd-c902-b7282d53c91d" }, "source": [ "!git clone https://github.com/ultralytics/yolov5 # clone repo\n", @@ -563,12 +563,12 @@ "clear_output()\n", "print('Setup complete. Using torch %s %s' % (torch.__version__, torch.cuda.get_device_properties(0) if torch.cuda.is_available() else 'CPU'))" ], - "execution_count": null, + "execution_count": 1, "outputs": [ { "output_type": "stream", "text": [ - "Setup complete. Using torch 1.7.0+cu101 _CudaDeviceProperties(name='Tesla V100-SXM2-16GB', major=7, minor=0, total_memory=16160MB, multi_processor_count=80)\n" + "Setup complete. Using torch 1.8.0+cu101 _CudaDeviceProperties(name='Tesla V100-SXM2-16GB', major=7, minor=0, total_memory=16160MB, multi_processor_count=80)\n" ], "name": "stdout" } @@ -672,30 +672,30 @@ "base_uri": "https://localhost:8080/", "height": 65, "referenced_widgets": [ - "1f8e9b8ebded4175b2eaa9f75c3ceb00", - "0a1246a73077468ab80e979cc0576cd2", - "d327cde5a85a4a51bb8b1b3e9cf06c97", - "d5ef1cb2cbed4b87b3c5d292ff2b0da6", - "8d5dff8bca14435a88fa1814533acd85", - "3d5136c19e7645ca9bc8f51ceffb2be1", - "2919396dbd4b4c8e821d12bd28665d8a", - "6feb16f2b2fa4021b1a271e1dd442d04" + "b54ab52f1d4f4903897ab6cd49a3b9b2", + "1852f93fc2714d40adccb8aa161c42ff", + "3293cfe869bd4a1bbbe18b49b6815de1", + "8d5ee8b8ab6d46b98818bd2c562ddd1c", + "49fcb2adb0354430b76f491af98abfe9", + "c7d76e0c53064363add56b8d05e561f5", + "48f321f789634aa584f8a29a3b925dd5", + "6610d6275f3e49d9937d50ed0a105947" ] }, - "outputId": "d6ace7c6-1be5-41ff-d607-1c716b88d298" + "outputId": "f0884441-78d9-443c-afa6-d00ec387908d" }, "source": [ "# Download COCO val2017\n", "torch.hub.download_url_to_file('https://github.com/ultralytics/yolov5/releases/download/v1.0/coco2017val.zip', 'tmp.zip')\n", "!unzip -q tmp.zip -d ../ && rm tmp.zip" ], - "execution_count": null, + "execution_count": 2, "outputs": [ { "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "1f8e9b8ebded4175b2eaa9f75c3ceb00", + "model_id": "b54ab52f1d4f4903897ab6cd49a3b9b2", "version_minor": 0, "version_major": 2 }, @@ -723,45 +723,45 @@ "colab": { "base_uri": "https://localhost:8080/" }, - "outputId": "cc25f70c-0a11-44f6-cc44-e92c5083488c" + "outputId": "5b54c11e-9f4b-4d9a-8e6e-6a2a4f0cc60d" }, "source": [ "# Run YOLOv5x on COCO val2017\n", "!python test.py --weights yolov5x.pt --data coco.yaml --img 640 --iou 0.65" ], - "execution_count": null, + "execution_count": 3, "outputs": [ { "output_type": "stream", "text": [ "Namespace(augment=False, batch_size=32, conf_thres=0.001, data='./data/coco.yaml', device='', exist_ok=False, img_size=640, iou_thres=0.65, name='exp', project='runs/test', save_conf=False, save_hybrid=False, save_json=True, save_txt=False, single_cls=False, task='val', verbose=False, weights=['yolov5x.pt'])\n", - "YOLOv5 v4.0-75-gbdd88e1 torch 1.7.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n", + "YOLOv5 v4.0-133-g20d879d torch 1.8.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n", "\n", "Downloading https://github.com/ultralytics/yolov5/releases/download/v4.0/yolov5x.pt to yolov5x.pt...\n", - "100% 168M/168M [00:04<00:00, 39.7MB/s]\n", + "100% 168M/168M [00:02<00:00, 59.1MB/s]\n", "\n", "Fusing layers... \n", "Model Summary: 476 layers, 87730285 parameters, 0 gradients, 218.8 GFLOPS\n", - "\u001b[34m\u001b[1mval: \u001b[0mScanning '../coco/val2017' for images and labels... 4952 found, 48 missing, 0 empty, 0 corrupted: 100% 5000/5000 [00:01<00:00, 2824.78it/s]\n", + "\u001b[34m\u001b[1mval: \u001b[0mScanning '../coco/val2017' for images and labels... 4952 found, 48 missing, 0 empty, 0 corrupted: 100% 5000/5000 [00:01<00:00, 3236.68it/s]\n", "\u001b[34m\u001b[1mval: \u001b[0mNew cache created: ../coco/val2017.cache\n", - " Class Images Targets P R mAP@.5 mAP@.5:.95: 100% 157/157 [01:33<00:00, 1.68it/s]\n", - " all 5e+03 3.63e+04 0.749 0.619 0.68 0.486\n", - "Speed: 5.2/2.0/7.3 ms inference/NMS/total per 640x640 image at batch-size 32\n", + " Class Images Labels P R mAP@.5 mAP@.5:.95: 100% 157/157 [01:20<00:00, 1.95it/s]\n", + " all 5000 36335 0.749 0.619 0.68 0.486\n", + "Speed: 5.3/1.7/6.9 ms inference/NMS/total per 640x640 image at batch-size 32\n", "\n", "Evaluating pycocotools mAP... saving runs/test/exp/yolov5x_predictions.json...\n", "loading annotations into memory...\n", - "Done (t=0.44s)\n", + "Done (t=0.43s)\n", "creating index...\n", "index created!\n", "Loading and preparing results...\n", - "DONE (t=4.47s)\n", + "DONE (t=5.10s)\n", "creating index...\n", "index created!\n", "Running per image evaluation...\n", "Evaluate annotation type *bbox*\n", - "DONE (t=94.87s).\n", + "DONE (t=88.52s).\n", "Accumulating evaluation results...\n", - "DONE (t=15.96s).\n", + "DONE (t=17.17s).\n", " Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.501\n", " Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.687\n", " Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.544\n", @@ -836,30 +836,30 @@ "base_uri": "https://localhost:8080/", "height": 65, "referenced_widgets": [ - "e6459e0bcee449b090fc9807672725bc", - "c341e1d3bf3b40d1821ce392eb966c68", - "660afee173694231a6dce3cd94df6cae", - "261218485cef48df961519dde5edfcbe", - "32736d503c06497abfae8c0421918255", - "e257738711f54d5280c8393d9d3dce1c", - "beb7a6fe34b840899bb79c062681696f", - "e639132395d64d70b99d8b72c32f8fbb" + "0fffa335322b41658508e06aed0acbf0", + "a354c6f80ce347e5a3ef64af87c0eccb", + "85823e71fea54c39bd11e2e972348836", + "fb11acd663fa4e71b041d67310d045fd", + "8a919053b780449aae5523658ad611fa", + "5bae9393a58b44f7b69fb04816f94f6f", + "d26c6d16c7f24030ab2da5285bf198ee", + "f7767886b2364c8d9efdc79e175ad8eb" ] }, - "outputId": "e8b7d5b3-a71e-4446-eec2-ad13419cf700" + "outputId": "b41ac253-9e1b-4c26-d78b-700ea0154f43" }, "source": [ "# Download COCO128\n", "torch.hub.download_url_to_file('https://github.com/ultralytics/yolov5/releases/download/v1.0/coco128.zip', 'tmp.zip')\n", "!unzip -q tmp.zip -d ../ && rm tmp.zip" ], - "execution_count": null, + "execution_count": 4, "outputs": [ { "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e6459e0bcee449b090fc9807672725bc", + "model_id": "0fffa335322b41658508e06aed0acbf0", "version_minor": 0, "version_major": 2 }, @@ -924,27 +924,27 @@ "colab": { "base_uri": "https://localhost:8080/" }, - "outputId": "38e51b29-2df4-4f00-cde8-5f6e4a34da9e" + "outputId": "cf494627-09b9-4399-ff0c-fdb62b32340a" }, "source": [ "# Train YOLOv5s on COCO128 for 3 epochs\n", "!python train.py --img 640 --batch 16 --epochs 3 --data coco128.yaml --weights yolov5s.pt --nosave --cache" ], - "execution_count": null, + "execution_count": 5, "outputs": [ { "output_type": "stream", "text": [ "\u001b[34m\u001b[1mgithub: \u001b[0mup to date with https://github.com/ultralytics/yolov5 ✅\n", - "YOLOv5 v4.0-75-gbdd88e1 torch 1.7.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n", + "YOLOv5 v4.0-133-g20d879d torch 1.8.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n", "\n", - "Namespace(adam=False, batch_size=16, bucket='', cache_images=True, cfg='', data='./data/coco128.yaml', device='', epochs=3, evolve=False, exist_ok=False, global_rank=-1, hyp='data/hyp.scratch.yaml', image_weights=False, img_size=[640, 640], linear_lr=False, local_rank=-1, log_artifacts=False, log_imgs=16, multi_scale=False, name='exp', noautoanchor=False, nosave=True, notest=False, project='runs/train', quad=False, rect=False, resume=False, save_dir='runs/train/exp', single_cls=False, sync_bn=False, total_batch_size=16, weights='yolov5s.pt', workers=8, world_size=1)\n", + "Namespace(adam=False, batch_size=16, bucket='', cache_images=True, cfg='', data='./data/coco128.yaml', device='', entity=None, epochs=3, evolve=False, exist_ok=False, global_rank=-1, hyp='data/hyp.scratch.yaml', image_weights=False, img_size=[640, 640], linear_lr=False, local_rank=-1, log_artifacts=False, log_imgs=16, multi_scale=False, name='exp', noautoanchor=False, nosave=True, notest=False, project='runs/train', quad=False, rect=False, resume=False, save_dir='runs/train/exp', single_cls=False, sync_bn=False, total_batch_size=16, weights='yolov5s.pt', workers=8, world_size=1)\n", "\u001b[34m\u001b[1mwandb: \u001b[0mInstall Weights & Biases for YOLOv5 logging with 'pip install wandb' (recommended)\n", "Start Tensorboard with \"tensorboard --logdir runs/train\", view at http://localhost:6006/\n", - "2021-02-12 06:38:28.027271: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudart.so.10.1\n", + "2021-03-14 04:18:58.124672: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudart.so.11.0\n", "\u001b[34m\u001b[1mhyperparameters: \u001b[0mlr0=0.01, lrf=0.2, momentum=0.937, weight_decay=0.0005, warmup_epochs=3.0, warmup_momentum=0.8, warmup_bias_lr=0.1, box=0.05, cls=0.5, cls_pw=1.0, obj=1.0, obj_pw=1.0, iou_t=0.2, anchor_t=4.0, fl_gamma=0.0, hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, degrees=0.0, translate=0.1, scale=0.5, shear=0.0, perspective=0.0, flipud=0.0, fliplr=0.5, mosaic=1.0, mixup=0.0\n", "Downloading https://github.com/ultralytics/yolov5/releases/download/v4.0/yolov5s.pt to yolov5s.pt...\n", - "100% 14.1M/14.1M [00:01<00:00, 13.2MB/s]\n", + "100% 14.1M/14.1M [00:00<00:00, 63.1MB/s]\n", "\n", "\n", " from n params module arguments \n", @@ -978,11 +978,11 @@ "Transferred 362/362 items from yolov5s.pt\n", "Scaled weight_decay = 0.0005\n", "Optimizer groups: 62 .bias, 62 conv.weight, 59 other\n", - "\u001b[34m\u001b[1mtrain: \u001b[0mScanning '../coco128/labels/train2017' for images and labels... 128 found, 0 missing, 2 empty, 0 corrupted: 100% 128/128 [00:00<00:00, 2566.00it/s]\n", + "\u001b[34m\u001b[1mtrain: \u001b[0mScanning '../coco128/labels/train2017' for images and labels... 128 found, 0 missing, 2 empty, 0 corrupted: 100% 128/128 [00:00<00:00, 2956.76it/s]\n", "\u001b[34m\u001b[1mtrain: \u001b[0mNew cache created: ../coco128/labels/train2017.cache\n", - "\u001b[34m\u001b[1mtrain: \u001b[0mCaching images (0.1GB): 100% 128/128 [00:00<00:00, 175.07it/s]\n", - "\u001b[34m\u001b[1mval: \u001b[0mScanning '../coco128/labels/train2017.cache' for images and labels... 128 found, 0 missing, 2 empty, 0 corrupted: 100% 128/128 [00:00<00:00, 764773.38it/s]\n", - "\u001b[34m\u001b[1mval: \u001b[0mCaching images (0.1GB): 100% 128/128 [00:00<00:00, 128.17it/s]\n", + "\u001b[34m\u001b[1mtrain: \u001b[0mCaching images (0.1GB): 100% 128/128 [00:00<00:00, 205.30it/s]\n", + "\u001b[34m\u001b[1mval: \u001b[0mScanning '../coco128/labels/train2017.cache' for images and labels... 128 found, 0 missing, 2 empty, 0 corrupted: 100% 128/128 [00:00<00:00, 604584.36it/s]\n", + "\u001b[34m\u001b[1mval: \u001b[0mCaching images (0.1GB): 100% 128/128 [00:00<00:00, 144.17it/s]\n", "Plotting labels... \n", "\n", "\u001b[34m\u001b[1mautoanchor: \u001b[0mAnalyzing anchors... anchors/target = 4.26, Best Possible Recall (BPR) = 0.9946\n", @@ -991,21 +991,22 @@ "Logging results to runs/train/exp\n", "Starting training for 3 epochs...\n", "\n", - " Epoch gpu_mem box obj cls total targets img_size\n", - " 0/2 3.27G 0.04357 0.06781 0.01869 0.1301 207 640: 100% 8/8 [00:03<00:00, 2.03it/s]\n", - " Class Images Targets P R mAP@.5 mAP@.5:.95: 100% 4/4 [00:04<00:00, 1.14s/it]\n", - " all 128 929 0.646 0.627 0.659 0.431\n", + " Epoch gpu_mem box obj cls total labels img_size\n", + " 0/2 3.29G 0.04237 0.06417 0.02121 0.1277 183 640: 100% 8/8 [00:03<00:00, 2.41it/s]\n", + " Class Images Labels P R mAP@.5 mAP@.5:.95: 100% 4/4 [00:04<00:00, 1.04s/it]\n", + " all 128 929 0.642 0.637 0.661 0.432\n", "\n", - " Epoch gpu_mem box obj cls total targets img_size\n", - " 1/2 7.75G 0.04308 0.06654 0.02083 0.1304 227 640: 100% 8/8 [00:01<00:00, 4.11it/s]\n", - " Class Images Targets P R mAP@.5 mAP@.5:.95: 100% 4/4 [00:01<00:00, 2.94it/s]\n", - " all 128 929 0.681 0.607 0.663 0.434\n", + " Epoch gpu_mem box obj cls total labels img_size\n", + " 1/2 6.65G 0.04431 0.06403 0.019 0.1273 166 640: 100% 8/8 [00:01<00:00, 5.73it/s]\n", + " Class Images Labels P R mAP@.5 mAP@.5:.95: 100% 4/4 [00:01<00:00, 3.21it/s]\n", + " all 128 929 0.662 0.626 0.658 0.433\n", "\n", - " Epoch gpu_mem box obj cls total targets img_size\n", - " 2/2 7.75G 0.04461 0.06896 0.01866 0.1322 191 640: 100% 8/8 [00:02<00:00, 3.94it/s]\n", - " Class Images Targets P R mAP@.5 mAP@.5:.95: 100% 4/4 [00:03<00:00, 1.22it/s]\n", - " all 128 929 0.642 0.632 0.662 0.432\n", + " Epoch gpu_mem box obj cls total labels img_size\n", + " 2/2 6.65G 0.04506 0.06836 0.01913 0.1325 182 640: 100% 8/8 [00:01<00:00, 5.51it/s]\n", + " Class Images Labels P R mAP@.5 mAP@.5:.95: 100% 4/4 [00:02<00:00, 1.35it/s]\n", + " all 128 929 0.658 0.625 0.661 0.433\n", "Optimizer stripped from runs/train/exp/weights/last.pt, 14.8MB\n", + "Optimizer stripped from runs/train/exp/weights/best.pt, 14.8MB\n", "3 epochs completed in 0.007 hours.\n", "\n" ], @@ -1247,4 +1248,4 @@ "outputs": [] } ] -} +} \ No newline at end of file From 38ff499b26b9f8bf183cd1c08746dd33d000eb59 Mon Sep 17 00:00:00 2001 From: Yann Defretin Date: Mon, 15 Mar 2021 01:11:27 +0100 Subject: [PATCH 69/88] Update autosplit() with annotated_only option (#2466) * Be able to create dataset from annotated images only Add the ability to create a dataset/splits only with images that have an annotation file, i.e a .txt file, associated to it. As we talked about this, the absence of a txt file could mean two things: * either the image wasn't yet labelled by someone, * either there is no object to detect. When it's easy to create small datasets, when you have to create datasets with thousands of images (and more coming), it's hard to track where you at and you don't want to wait to have all of them annotated before starting to train. Which means some images would lack txt files and annotations, resulting in label inconsistency as you say in #2313. By adding the annotated_only argument to the function, people could create, if they want to, datasets/splits only with images that were labelled, for sure. * Cleanup and update print() Co-authored-by: Glenn Jocher --- utils/datasets.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/utils/datasets.py b/utils/datasets.py index ed18f449ddd3..9a4b3f9fcc9f 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -1032,20 +1032,24 @@ def extract_boxes(path='../coco128/'): # from utils.datasets import *; extract_ b[[1, 3]] = np.clip(b[[1, 3]], 0, h) assert cv2.imwrite(str(f), im[b[1]:b[3], b[0]:b[2]]), f'box failure in {f}' - -def autosplit(path='../coco128', weights=(0.9, 0.1, 0.0)): # from utils.datasets import *; autosplit('../coco128') +def autosplit(path='../coco128', weights=(0.9, 0.1, 0.0), annotated_only=False): """ Autosplit a dataset into train/val/test splits and save path/autosplit_*.txt files - # Arguments - path: Path to images directory - weights: Train, val, test weights (list) + Usage: from utils.datasets import *; autosplit('../coco128') + Arguments + path: Path to images directory + weights: Train, val, test weights (list) + annotated_only: Only use images with an annotated txt file """ path = Path(path) # images dir - files = list(path.rglob('*.*')) + files = sum([list(path.rglob(f"*.{img_ext}")) for img_ext in img_formats], []) # image files only n = len(files) # number of files indices = random.choices([0, 1, 2], weights=weights, k=n) # assign each image to a split + txt = ['autosplit_train.txt', 'autosplit_val.txt', 'autosplit_test.txt'] # 3 txt files [(path / x).unlink() for x in txt if (path / x).exists()] # remove existing + + print(f'Autosplitting images from {path}' + ', using *.txt labeled images only' * annotated_only) for i, img in tqdm(zip(indices, files), total=n): - if img.suffix[1:] in img_formats: + if not annotated_only or Path(img2label_paths([str(img)])[0]).exists(): # check label with open(path / txt[i], 'a') as f: f.write(str(img) + '\n') # add image to txt file From 2d41e70e828c215a3c8486bb24ac2169084079f1 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 14 Mar 2021 21:58:12 -0700 Subject: [PATCH 70/88] Scipy kmeans-robust autoanchor update (#2470) Fix for https://github.com/ultralytics/yolov5/issues/2394 --- utils/autoanchor.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/utils/autoanchor.py b/utils/autoanchor.py index 5dba9f1ea22f..57777462e89f 100644 --- a/utils/autoanchor.py +++ b/utils/autoanchor.py @@ -37,17 +37,21 @@ def metric(k): # compute metric bpr = (best > 1. / thr).float().mean() # best possible recall return bpr, aat - bpr, aat = metric(m.anchor_grid.clone().cpu().view(-1, 2)) + anchors = m.anchor_grid.clone().cpu().view(-1, 2) # current anchors + bpr, aat = metric(anchors) print(f'anchors/target = {aat:.2f}, Best Possible Recall (BPR) = {bpr:.4f}', end='') if bpr < 0.98: # threshold to recompute print('. Attempting to improve anchors, please wait...') na = m.anchor_grid.numel() // 2 # number of anchors - new_anchors = kmean_anchors(dataset, n=na, img_size=imgsz, thr=thr, gen=1000, verbose=False) - new_bpr = metric(new_anchors.reshape(-1, 2))[0] + try: + anchors = kmean_anchors(dataset, n=na, img_size=imgsz, thr=thr, gen=1000, verbose=False) + except Exception as e: + print(f'{prefix}ERROR: {e}') + new_bpr = metric(anchors)[0] if new_bpr > bpr: # replace anchors - new_anchors = torch.tensor(new_anchors, device=m.anchors.device).type_as(m.anchors) - m.anchor_grid[:] = new_anchors.clone().view_as(m.anchor_grid) # for inference - m.anchors[:] = new_anchors.clone().view_as(m.anchors) / m.stride.to(m.anchors.device).view(-1, 1, 1) # loss + anchors = torch.tensor(anchors, device=m.anchors.device).type_as(m.anchors) + m.anchor_grid[:] = anchors.clone().view_as(m.anchor_grid) # for inference + m.anchors[:] = anchors.clone().view_as(m.anchors) / m.stride.to(m.anchors.device).view(-1, 1, 1) # loss check_anchor_order(m) print(f'{prefix}New anchors saved to model. Update model *.yaml to use these anchors in the future.') else: @@ -119,6 +123,7 @@ def print_results(k): print(f'{prefix}Running kmeans for {n} anchors on {len(wh)} points...') s = wh.std(0) # sigmas for whitening k, dist = kmeans(wh / s, n, iter=30) # points, mean distance + assert len(k) == n, print(f'{prefix}ERROR: scipy.cluster.vq.kmeans requested {n} points but returned only {len(k)}') k *= s wh = torch.tensor(wh, dtype=torch.float32) # filtered wh0 = torch.tensor(wh0, dtype=torch.float32) # unfiltered From 9b11f0c58b7c1f775ee32acb7dcc6a36407a779b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 14 Mar 2021 23:16:17 -0700 Subject: [PATCH 71/88] PyTorch Hub models default to CUDA:0 if available (#2472) * PyTorch Hub models default to CUDA:0 if available * device as string bug fix --- hubconf.py | 4 +++- utils/datasets.py | 4 ++-- utils/general.py | 2 +- utils/torch_utils.py | 6 +++--- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/hubconf.py b/hubconf.py index e51ac90da36c..b7b740d39c06 100644 --- a/hubconf.py +++ b/hubconf.py @@ -12,6 +12,7 @@ from models.yolo import Model from utils.general import set_logging from utils.google_utils import attempt_download +from utils.torch_utils import select_device dependencies = ['torch', 'yaml'] set_logging() @@ -43,7 +44,8 @@ def create(name, pretrained, channels, classes, autoshape): model.names = ckpt['model'].names # set class names attribute if autoshape: model = model.autoshape() # for file/URI/PIL/cv2/np inputs and NMS - return model + device = select_device('0' if torch.cuda.is_available() else 'cpu') # default to GPU if available + return model.to(device) except Exception as e: help_url = 'https://github.com/ultralytics/yolov5/issues/36' diff --git a/utils/datasets.py b/utils/datasets.py index 9a4b3f9fcc9f..86d7be39bec0 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -385,7 +385,7 @@ def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, r # Display cache nf, nm, ne, nc, n = cache.pop('results') # found, missing, empty, corrupted, total if exists: - d = f"Scanning '{cache_path}' for images and labels... {nf} found, {nm} missing, {ne} empty, {nc} corrupted" + d = f"Scanning '{cache_path}' images and labels... {nf} found, {nm} missing, {ne} empty, {nc} corrupted" tqdm(None, desc=prefix + d, total=n, initial=n) # display cache results assert nf > 0 or not augment, f'{prefix}No labels in {cache_path}. Can not train without labels. See {help_url}' @@ -485,7 +485,7 @@ def cache_labels(self, path=Path('./labels.cache'), prefix=''): nc += 1 print(f'{prefix}WARNING: Ignoring corrupted image and/or label {im_file}: {e}') - pbar.desc = f"{prefix}Scanning '{path.parent / path.stem}' for images and labels... " \ + pbar.desc = f"{prefix}Scanning '{path.parent / path.stem}' images and labels... " \ f"{nf} found, {nm} missing, {ne} empty, {nc} corrupted" if nf == 0: diff --git a/utils/general.py b/utils/general.py index e1c14bdaa4b3..621df64c6cf1 100755 --- a/utils/general.py +++ b/utils/general.py @@ -79,7 +79,7 @@ def check_git_status(): f"Use 'git pull' to update or 'git clone {url}' to download latest." else: s = f'up to date with {url} ✅' - print(s.encode().decode('ascii', 'ignore') if platform.system() == 'Windows' else s) + print(s.encode().decode('ascii', 'ignore') if platform.system() == 'Windows' else s) # emoji-safe except Exception as e: print(e) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 806d29470e55..8f3538ab152a 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -1,8 +1,8 @@ # PyTorch utils - import logging import math import os +import platform import subprocess import time from contextlib import contextmanager @@ -53,7 +53,7 @@ def git_describe(): def select_device(device='', batch_size=None): # device = 'cpu' or '0' or '0,1,2,3' - s = f'YOLOv5 {git_describe()} torch {torch.__version__} ' # string + s = f'YOLOv5 🚀 {git_describe()} torch {torch.__version__} ' # string cpu = device.lower() == 'cpu' if cpu: os.environ['CUDA_VISIBLE_DEVICES'] = '-1' # force torch.cuda.is_available() = False @@ -73,7 +73,7 @@ def select_device(device='', batch_size=None): else: s += 'CPU\n' - logger.info(s) # skip a line + logger.info(s.encode().decode('ascii', 'ignore') if platform.system() == 'Windows' else s) # emoji-safe return torch.device('cuda:0' if cuda else 'cpu') From ed2c74218d6d46605cc5fa68ce9bd6ece213abe4 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 14 Mar 2021 23:32:39 -0700 Subject: [PATCH 72/88] Created using Colaboratory --- tutorial.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tutorial.ipynb b/tutorial.ipynb index b678e4bec9c2..c710685b7e75 100644 --- a/tutorial.ipynb +++ b/tutorial.ipynb @@ -605,7 +605,7 @@ "output_type": "stream", "text": [ "Namespace(agnostic_nms=False, augment=False, classes=None, conf_thres=0.25, device='', exist_ok=False, img_size=640, iou_thres=0.45, name='exp', project='runs/detect', save_conf=False, save_txt=False, source='data/images/', update=False, view_img=False, weights=['yolov5s.pt'])\n", - "YOLOv5 v4.0-132-gf813f6d torch 1.8.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n", + "YOLOv5 🚀 v4.0-137-g9b11f0c torch 1.8.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n", "\n", "Fusing layers... \n", "Model Summary: 224 layers, 7266973 parameters, 0 gradients, 17.0 GFLOPS\n", @@ -735,7 +735,7 @@ "output_type": "stream", "text": [ "Namespace(augment=False, batch_size=32, conf_thres=0.001, data='./data/coco.yaml', device='', exist_ok=False, img_size=640, iou_thres=0.65, name='exp', project='runs/test', save_conf=False, save_hybrid=False, save_json=True, save_txt=False, single_cls=False, task='val', verbose=False, weights=['yolov5x.pt'])\n", - "YOLOv5 v4.0-133-g20d879d torch 1.8.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n", + "YOLOv5 🚀 v4.0-137-g9b11f0c torch 1.8.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n", "\n", "Downloading https://github.com/ultralytics/yolov5/releases/download/v4.0/yolov5x.pt to yolov5x.pt...\n", "100% 168M/168M [00:02<00:00, 59.1MB/s]\n", @@ -936,7 +936,7 @@ "output_type": "stream", "text": [ "\u001b[34m\u001b[1mgithub: \u001b[0mup to date with https://github.com/ultralytics/yolov5 ✅\n", - "YOLOv5 v4.0-133-g20d879d torch 1.8.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n", + "YOLOv5 🚀 v4.0-137-g9b11f0c torch 1.8.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n", "\n", "Namespace(adam=False, batch_size=16, bucket='', cache_images=True, cfg='', data='./data/coco128.yaml', device='', entity=None, epochs=3, evolve=False, exist_ok=False, global_rank=-1, hyp='data/hyp.scratch.yaml', image_weights=False, img_size=[640, 640], linear_lr=False, local_rank=-1, log_artifacts=False, log_imgs=16, multi_scale=False, name='exp', noautoanchor=False, nosave=True, notest=False, project='runs/train', quad=False, rect=False, resume=False, save_dir='runs/train/exp', single_cls=False, sync_bn=False, total_batch_size=16, weights='yolov5s.pt', workers=8, world_size=1)\n", "\u001b[34m\u001b[1mwandb: \u001b[0mInstall Weights & Biases for YOLOv5 logging with 'pip install wandb' (recommended)\n", From e8fc97aa3891f05812d7dfff90ca66d3481bda2c Mon Sep 17 00:00:00 2001 From: Ayush Chaurasia Date: Tue, 23 Mar 2021 05:14:50 +0530 Subject: [PATCH 73/88] Improved W&B integration (#2125) * Init Commit * new wandb integration * Update * Use data_dict in test * Updates * Update: scope of log_img * Update: scope of log_img * Update * Update: Fix logging conditions * Add tqdm bar, support for .txt dataset format * Improve Result table Logger * Init Commit * new wandb integration * Update * Use data_dict in test * Updates * Update: scope of log_img * Update: scope of log_img * Update * Update: Fix logging conditions * Add tqdm bar, support for .txt dataset format * Improve Result table Logger * Add dataset creation in training script * Change scope: self.wandb_run * Add wandb-artifact:// natively you can now use --resume with wandb run links * Add suuport for logging dataset while training * Cleanup * Fix: Merge conflict * Fix: CI tests * Automatically use wandb config * Fix: Resume * Fix: CI * Enhance: Using val_table * More resume enhancement * FIX : CI * Add alias * Get useful opt config data * train.py cleanup * Cleanup train.py * more cleanup * Cleanup| CI fix * Reformat using PEP8 * FIX:CI * rebase * remove uneccesary changes * remove uneccesary changes * remove uneccesary changes * remove unecessary chage from test.py * FIX: resume from local checkpoint * FIX:resume * FIX:resume * Reformat * Performance improvement * Fix local resume * Fix local resume * FIX:CI * Fix: CI * Imporve image logging * (:(:Redo CI tests:):) * Remember epochs when resuming * Remember epochs when resuming * Update DDP location Potential fix for #2405 * PEP8 reformat * 0.25 confidence threshold * reset train.py plots syntax to previous * reset epochs completed syntax to previous * reset space to previous * remove brackets * reset comment to previous * Update: is_coco check, remove unused code * Remove redundant print statement * Remove wandb imports * remove dsviz logger from test.py * Remove redundant change from test.py * remove redundant changes from train.py * reformat and improvements * Fix typo * Add tqdm tqdm progress when scanning files, naming improvements Co-authored-by: Glenn Jocher --- models/common.py | 2 +- test.py | 49 +++--- train.py | 116 +++++++------ utils/wandb_logging/log_dataset.py | 16 +- utils/wandb_logging/wandb_utils.py | 267 +++++++++++++++++++++-------- 5 files changed, 282 insertions(+), 168 deletions(-) diff --git a/models/common.py b/models/common.py index 464d639a1f0b..83cc8b5ce27b 100644 --- a/models/common.py +++ b/models/common.py @@ -278,7 +278,7 @@ def display(self, pprint=False, show=False, save=False, render=False, save_dir=' def print(self): self.display(pprint=True) # print results print(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {tuple(self.s)}' % - tuple(self.t)) + tuple(self.t)) def show(self): self.display(show=True) # show results diff --git a/test.py b/test.py index 39e0992264ec..61d6965f7414 100644 --- a/test.py +++ b/test.py @@ -35,8 +35,9 @@ def test(data, save_hybrid=False, # for hybrid auto-labelling save_conf=False, # save auto-label confidences plots=True, - log_imgs=0, # number of logged images - compute_loss=None): + wandb_logger=None, + compute_loss=None, + is_coco=False): # Initialize/load model and set device training = model is not None if training: # called by train.py @@ -66,21 +67,19 @@ def test(data, # Configure model.eval() - is_coco = data.endswith('coco.yaml') # is COCO dataset - with open(data) as f: - data = yaml.load(f, Loader=yaml.SafeLoader) # model dict + if isinstance(data, str): + is_coco = data.endswith('coco.yaml') + with open(data) as f: + data = yaml.load(f, Loader=yaml.SafeLoader) check_dataset(data) # check nc = 1 if single_cls else int(data['nc']) # number of classes iouv = torch.linspace(0.5, 0.95, 10).to(device) # iou vector for mAP@0.5:0.95 niou = iouv.numel() # Logging - log_imgs, wandb = min(log_imgs, 100), None # ceil - try: - import wandb # Weights & Biases - except ImportError: - log_imgs = 0 - + log_imgs = 0 + if wandb_logger and wandb_logger.wandb: + log_imgs = min(wandb_logger.log_imgs, 100) # Dataloader if not training: if device.type != 'cpu': @@ -147,15 +146,17 @@ def test(data, with open(save_dir / 'labels' / (path.stem + '.txt'), 'a') as f: f.write(('%g ' * len(line)).rstrip() % line + '\n') - # W&B logging - if plots and len(wandb_images) < log_imgs: - box_data = [{"position": {"minX": xyxy[0], "minY": xyxy[1], "maxX": xyxy[2], "maxY": xyxy[3]}, - "class_id": int(cls), - "box_caption": "%s %.3f" % (names[cls], conf), - "scores": {"class_score": conf}, - "domain": "pixel"} for *xyxy, conf, cls in pred.tolist()] - boxes = {"predictions": {"box_data": box_data, "class_labels": names}} # inference-space - wandb_images.append(wandb.Image(img[si], boxes=boxes, caption=path.name)) + # W&B logging - Media Panel Plots + if len(wandb_images) < log_imgs and wandb_logger.current_epoch > 0: # Check for test operation + if wandb_logger.current_epoch % wandb_logger.bbox_interval == 0: + box_data = [{"position": {"minX": xyxy[0], "minY": xyxy[1], "maxX": xyxy[2], "maxY": xyxy[3]}, + "class_id": int(cls), + "box_caption": "%s %.3f" % (names[cls], conf), + "scores": {"class_score": conf}, + "domain": "pixel"} for *xyxy, conf, cls in pred.tolist()] + boxes = {"predictions": {"box_data": box_data, "class_labels": names}} # inference-space + wandb_images.append(wandb_logger.wandb.Image(img[si], boxes=boxes, caption=path.name)) + wandb_logger.log_training_progress(predn, path, names) # logs dsviz tables # Append to pycocotools JSON dictionary if save_json: @@ -239,9 +240,11 @@ def test(data, # Plots if plots: confusion_matrix.plot(save_dir=save_dir, names=list(names.values())) - if wandb and wandb.run: - val_batches = [wandb.Image(str(f), caption=f.name) for f in sorted(save_dir.glob('test*.jpg'))] - wandb.log({"Images": wandb_images, "Validation": val_batches}, commit=False) + if wandb_logger and wandb_logger.wandb: + val_batches = [wandb_logger.wandb.Image(str(f), caption=f.name) for f in sorted(save_dir.glob('test*.jpg'))] + wandb_logger.log({"Validation": val_batches}) + if wandb_images: + wandb_logger.log({"Bounding Box Debugger/Images": wandb_images}) # Save JSON if save_json and len(jdict): diff --git a/train.py b/train.py index 005fdf60c021..62a72375c7a3 100644 --- a/train.py +++ b/train.py @@ -1,3 +1,4 @@ + import argparse import logging import math @@ -33,11 +34,12 @@ from utils.loss import ComputeLoss from utils.plots import plot_images, plot_labels, plot_results, plot_evolution from utils.torch_utils import ModelEMA, select_device, intersect_dicts, torch_distributed_zero_first, is_parallel +from utils.wandb_logging.wandb_utils import WandbLogger, resume_and_get_id, check_wandb_config_file logger = logging.getLogger(__name__) -def train(hyp, opt, device, tb_writer=None, wandb=None): +def train(hyp, opt, device, tb_writer=None): logger.info(colorstr('hyperparameters: ') + ', '.join(f'{k}={v}' for k, v in hyp.items())) save_dir, epochs, batch_size, total_batch_size, weights, rank = \ Path(opt.save_dir), opt.epochs, opt.batch_size, opt.total_batch_size, opt.weights, opt.global_rank @@ -61,10 +63,17 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): init_seeds(2 + rank) with open(opt.data) as f: data_dict = yaml.load(f, Loader=yaml.SafeLoader) # data dict - with torch_distributed_zero_first(rank): - check_dataset(data_dict) # check - train_path = data_dict['train'] - test_path = data_dict['val'] + is_coco = opt.data.endswith('coco.yaml') + + # Logging- Doing this before checking the dataset. Might update data_dict + if rank in [-1, 0]: + opt.hyp = hyp # add hyperparameters + run_id = torch.load(weights).get('wandb_id') if weights.endswith('.pt') and os.path.isfile(weights) else None + wandb_logger = WandbLogger(opt, Path(opt.save_dir).stem, run_id, data_dict) + data_dict = wandb_logger.data_dict + if wandb_logger.wandb: + weights, epochs, hyp = opt.weights, opt.epochs, opt.hyp # WandbLogger might update weights, epochs if resuming + loggers = {'wandb': wandb_logger.wandb} # loggers dict nc = 1 if opt.single_cls else int(data_dict['nc']) # number of classes names = ['item'] if opt.single_cls and len(data_dict['names']) != 1 else data_dict['names'] # class names assert len(names) == nc, '%g names found for nc=%g dataset in %s' % (len(names), nc, opt.data) # check @@ -83,6 +92,10 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): logger.info('Transferred %g/%g items from %s' % (len(state_dict), len(model.state_dict()), weights)) # report else: model = Model(opt.cfg, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create + with torch_distributed_zero_first(rank): + check_dataset(data_dict) # check + train_path = data_dict['train'] + test_path = data_dict['val'] # Freeze freeze = [] # parameter names to freeze (full or partial) @@ -126,16 +139,6 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) # plot_lr_scheduler(optimizer, scheduler, epochs) - # Logging - if rank in [-1, 0] and wandb and wandb.run is None: - opt.hyp = hyp # add hyperparameters - wandb_run = wandb.init(config=opt, resume="allow", - project='YOLOv5' if opt.project == 'runs/train' else Path(opt.project).stem, - name=save_dir.stem, - entity=opt.entity, - id=ckpt.get('wandb_id') if 'ckpt' in locals() else None) - loggers = {'wandb': wandb} # loggers dict - # EMA ema = ModelEMA(model) if rank in [-1, 0] else None @@ -326,9 +329,9 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): # if tb_writer: # tb_writer.add_image(f, result, dataformats='HWC', global_step=epoch) # tb_writer.add_graph(model, imgs) # add model to tensorboard - elif plots and ni == 10 and wandb: - wandb.log({"Mosaics": [wandb.Image(str(x), caption=x.name) for x in save_dir.glob('train*.jpg') - if x.exists()]}, commit=False) + elif plots and ni == 10 and wandb_logger.wandb: + wandb_logger.log({"Mosaics": [wandb_logger.wandb.Image(str(x), caption=x.name) for x in + save_dir.glob('train*.jpg') if x.exists()]}) # end batch ------------------------------------------------------------------------------------------------ # end epoch ---------------------------------------------------------------------------------------------------- @@ -343,8 +346,9 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): ema.update_attr(model, include=['yaml', 'nc', 'hyp', 'gr', 'names', 'stride', 'class_weights']) final_epoch = epoch + 1 == epochs if not opt.notest or final_epoch: # Calculate mAP - results, maps, times = test.test(opt.data, - batch_size=batch_size * 2, + wandb_logger.current_epoch = epoch + 1 + results, maps, times = test.test(data_dict, + batch_size=total_batch_size, imgsz=imgsz_test, model=ema.ema, single_cls=opt.single_cls, @@ -352,8 +356,9 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): save_dir=save_dir, verbose=nc < 50 and final_epoch, plots=plots and final_epoch, - log_imgs=opt.log_imgs if wandb else 0, - compute_loss=compute_loss) + wandb_logger=wandb_logger, + compute_loss=compute_loss, + is_coco=is_coco) # Write with open(results_file, 'a') as f: @@ -369,8 +374,8 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): for x, tag in zip(list(mloss[:-1]) + list(results) + lr, tags): if tb_writer: tb_writer.add_scalar(tag, x, epoch) # tensorboard - if wandb: - wandb.log({tag: x}, step=epoch, commit=tag == tags[-1]) # W&B + if wandb_logger.wandb: + wandb_logger.log({tag: x}) # W&B # Update best mAP fi = fitness(np.array(results).reshape(1, -1)) # weighted combination of [P, R, mAP@.5, mAP@.5-.95] @@ -386,36 +391,29 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): 'ema': deepcopy(ema.ema).half(), 'updates': ema.updates, 'optimizer': optimizer.state_dict(), - 'wandb_id': wandb_run.id if wandb else None} + 'wandb_id': wandb_logger.wandb_run.id if wandb_logger.wandb else None} # Save last, best and delete torch.save(ckpt, last) if best_fitness == fi: torch.save(ckpt, best) + if wandb_logger.wandb: + if ((epoch + 1) % opt.save_period == 0 and not final_epoch) and opt.save_period != -1: + wandb_logger.log_model( + last.parent, opt, epoch, fi, best_model=best_fitness == fi) del ckpt - + wandb_logger.end_epoch(best_result=best_fitness == fi) + # end epoch ---------------------------------------------------------------------------------------------------- # end training - if rank in [-1, 0]: - # Strip optimizers - final = best if best.exists() else last # final model - for f in last, best: - if f.exists(): - strip_optimizer(f) - if opt.bucket: - os.system(f'gsutil cp {final} gs://{opt.bucket}/weights') # upload - # Plots if plots: plot_results(save_dir=save_dir) # save as results.png - if wandb: + if wandb_logger.wandb: files = ['results.png', 'confusion_matrix.png', *[f'{x}_curve.png' for x in ('F1', 'PR', 'P', 'R')]] - wandb.log({"Results": [wandb.Image(str(save_dir / f), caption=f) for f in files - if (save_dir / f).exists()]}) - if opt.log_artifacts: - wandb.log_artifact(artifact_or_path=str(final), type='model', name=save_dir.stem) - + wandb_logger.log({"Results": [wandb_logger.wandb.Image(str(save_dir / f), caption=f) for f in files + if (save_dir / f).exists()]}) # Test best.pt logger.info('%g epochs completed in %.3f hours.\n' % (epoch - start_epoch + 1, (time.time() - t0) / 3600)) if opt.data.endswith('coco.yaml') and nc == 80: # if COCO @@ -430,13 +428,24 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): dataloader=testloader, save_dir=save_dir, save_json=True, - plots=False) + plots=False, + is_coco=is_coco) + # Strip optimizers + final = best if best.exists() else last # final model + for f in last, best: + if f.exists(): + strip_optimizer(f) # strip optimizers + if opt.bucket: + os.system(f'gsutil cp {final} gs://{opt.bucket}/weights') # upload + if wandb_logger.wandb: # Log the stripped model + wandb_logger.wandb.log_artifact(str(final), type='model', + name='run_' + wandb_logger.wandb_run.id + '_model', + aliases=['last', 'best', 'stripped']) else: dist.destroy_process_group() - - wandb.run.finish() if wandb and wandb.run else None torch.cuda.empty_cache() + wandb_logger.finish_run() return results @@ -464,8 +473,6 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer') parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode') parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') - parser.add_argument('--log-imgs', type=int, default=16, help='number of images for W&B logging, max 100') - parser.add_argument('--log-artifacts', action='store_true', help='log artifacts, i.e. final trained model') parser.add_argument('--workers', type=int, default=8, help='maximum number of dataloader workers') parser.add_argument('--project', default='runs/train', help='save to project/name') parser.add_argument('--entity', default=None, help='W&B entity') @@ -473,6 +480,10 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') parser.add_argument('--quad', action='store_true', help='quad dataloader') parser.add_argument('--linear-lr', action='store_true', help='linear LR') + parser.add_argument('--upload_dataset', action='store_true', help='Upload dataset as W&B artifact table') + parser.add_argument('--bbox_interval', type=int, default=-1, help='Set bounding-box image logging interval for W&B') + parser.add_argument('--save_period', type=int, default=-1, help='Log model after every "save_period" epoch') + parser.add_argument('--artifact_alias', type=str, default="latest", help='version of dataset artifact to be used') opt = parser.parse_args() # Set DDP variables @@ -484,7 +495,8 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): check_requirements() # Resume - if opt.resume: # resume an interrupted run + wandb_run = resume_and_get_id(opt) + if opt.resume and not wandb_run: # resume an interrupted run ckpt = opt.resume if isinstance(opt.resume, str) else get_latest_run() # specified or most recent path assert os.path.isfile(ckpt), 'ERROR: --resume checkpoint does not exist' apriori = opt.global_rank, opt.local_rank @@ -517,18 +529,12 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): # Train logger.info(opt) - try: - import wandb - except ImportError: - wandb = None - prefix = colorstr('wandb: ') - logger.info(f"{prefix}Install Weights & Biases for YOLOv5 logging with 'pip install wandb' (recommended)") if not opt.evolve: tb_writer = None # init loggers if opt.global_rank in [-1, 0]: logger.info(f'Start Tensorboard with "tensorboard --logdir {opt.project}", view at http://localhost:6006/') tb_writer = SummaryWriter(opt.save_dir) # Tensorboard - train(hyp, opt, device, tb_writer, wandb) + train(hyp, opt, device, tb_writer) # Evolve hyperparameters (optional) else: @@ -602,7 +608,7 @@ def train(hyp, opt, device, tb_writer=None, wandb=None): hyp[k] = round(hyp[k], 5) # significant digits # Train mutation - results = train(hyp.copy(), opt, device, wandb=wandb) + results = train(hyp.copy(), opt, device) # Write mutation results print_mutation(hyp.copy(), results, yaml_file, opt.bucket) diff --git a/utils/wandb_logging/log_dataset.py b/utils/wandb_logging/log_dataset.py index d790a9ce721e..97e68425cddd 100644 --- a/utils/wandb_logging/log_dataset.py +++ b/utils/wandb_logging/log_dataset.py @@ -12,20 +12,7 @@ def create_dataset_artifact(opt): with open(opt.data) as f: data = yaml.load(f, Loader=yaml.SafeLoader) # data dict - logger = WandbLogger(opt, '', None, data, job_type='create_dataset') - nc, names = (1, ['item']) if opt.single_cls else (int(data['nc']), data['names']) - names = {k: v for k, v in enumerate(names)} # to index dictionary - logger.log_dataset_artifact(LoadImagesAndLabels(data['train']), names, name='train') # trainset - logger.log_dataset_artifact(LoadImagesAndLabels(data['val']), names, name='val') # valset - - # Update data.yaml with artifact links - data['train'] = WANDB_ARTIFACT_PREFIX + str(Path(opt.project) / 'train') - data['val'] = WANDB_ARTIFACT_PREFIX + str(Path(opt.project) / 'val') - path = opt.data if opt.overwrite_config else opt.data.replace('.', '_wandb.') # updated data.yaml path - data.pop('download', None) # download via artifact instead of predefined field 'download:' - with open(path, 'w') as f: - yaml.dump(data, f) - print("New Config file => ", path) + logger = WandbLogger(opt, '', None, data, job_type='Dataset Creation') if __name__ == '__main__': @@ -33,7 +20,6 @@ def create_dataset_artifact(opt): parser.add_argument('--data', type=str, default='data/coco128.yaml', help='data.yaml path') parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset') parser.add_argument('--project', type=str, default='YOLOv5', help='name of W&B Project') - parser.add_argument('--overwrite_config', action='store_true', help='overwrite data.yaml') opt = parser.parse_args() create_dataset_artifact(opt) diff --git a/utils/wandb_logging/wandb_utils.py b/utils/wandb_logging/wandb_utils.py index 264cd4840e3c..c9a32f5b6026 100644 --- a/utils/wandb_logging/wandb_utils.py +++ b/utils/wandb_logging/wandb_utils.py @@ -1,13 +1,18 @@ +import argparse import json +import os import shutil import sys +import torch +import yaml from datetime import datetime from pathlib import Path - -import torch +from tqdm import tqdm sys.path.append(str(Path(__file__).parent.parent.parent)) # add utils/ to path -from utils.general import colorstr, xywh2xyxy +from utils.datasets import LoadImagesAndLabels +from utils.datasets import img2label_paths +from utils.general import colorstr, xywh2xyxy, check_dataset try: import wandb @@ -22,87 +27,183 @@ def remove_prefix(from_string, prefix): return from_string[len(prefix):] +def check_wandb_config_file(data_config_file): + wandb_config = '_wandb.'.join(data_config_file.rsplit('.', 1)) # updated data.yaml path + if Path(wandb_config).is_file(): + return wandb_config + return data_config_file + + +def resume_and_get_id(opt): + # It's more elegant to stick to 1 wandb.init call, but as useful config data is overwritten in the WandbLogger's wandb.init call + if isinstance(opt.resume, str): + if opt.resume.startswith(WANDB_ARTIFACT_PREFIX): + run_path = Path(remove_prefix(opt.resume, WANDB_ARTIFACT_PREFIX)) + run_id = run_path.stem + project = run_path.parent.stem + model_artifact_name = WANDB_ARTIFACT_PREFIX + 'run_' + run_id + '_model' + assert wandb, 'install wandb to resume wandb runs' + # Resume wandb-artifact:// runs here| workaround for not overwriting wandb.config + run = wandb.init(id=run_id, project=project, resume='allow') + opt.resume = model_artifact_name + return run + return None + + class WandbLogger(): def __init__(self, opt, name, run_id, data_dict, job_type='Training'): - self.wandb = wandb - self.wandb_run = wandb.init(config=opt, resume="allow", - project='YOLOv5' if opt.project == 'runs/train' else Path(opt.project).stem, - name=name, - job_type=job_type, - id=run_id) if self.wandb else None - - if job_type == 'Training': - self.setup_training(opt, data_dict) - if opt.bbox_interval == -1: - opt.bbox_interval = (opt.epochs // 10) if opt.epochs > 10 else opt.epochs - if opt.save_period == -1: - opt.save_period = (opt.epochs // 10) if opt.epochs > 10 else opt.epochs + # Pre-training routine -- + self.job_type = job_type + self.wandb, self.wandb_run, self.data_dict = wandb, None if not wandb else wandb.run, data_dict + if self.wandb: + self.wandb_run = wandb.init(config=opt, + resume="allow", + project='YOLOv5' if opt.project == 'runs/train' else Path(opt.project).stem, + name=name, + job_type=job_type, + id=run_id) if not wandb.run else wandb.run + if self.job_type == 'Training': + if not opt.resume: + wandb_data_dict = self.check_and_upload_dataset(opt) if opt.upload_dataset else data_dict + # Info useful for resuming from artifacts + self.wandb_run.config.opt = vars(opt) + self.wandb_run.config.data_dict = wandb_data_dict + self.data_dict = self.setup_training(opt, data_dict) + if self.job_type == 'Dataset Creation': + self.data_dict = self.check_and_upload_dataset(opt) + + def check_and_upload_dataset(self, opt): + assert wandb, 'Install wandb to upload dataset' + check_dataset(self.data_dict) + config_path = self.log_dataset_artifact(opt.data, + opt.single_cls, + 'YOLOv5' if opt.project == 'runs/train' else Path(opt.project).stem) + print("Created dataset config file ", config_path) + with open(config_path) as f: + wandb_data_dict = yaml.load(f, Loader=yaml.SafeLoader) + return wandb_data_dict def setup_training(self, opt, data_dict): - self.log_dict = {} - self.train_artifact_path, self.trainset_artifact = \ - self.download_dataset_artifact(data_dict['train'], opt.artifact_alias) - self.test_artifact_path, self.testset_artifact = \ - self.download_dataset_artifact(data_dict['val'], opt.artifact_alias) - self.result_artifact, self.result_table, self.weights = None, None, None - if self.train_artifact_path is not None: - train_path = Path(self.train_artifact_path) / 'data/images/' - data_dict['train'] = str(train_path) - if self.test_artifact_path is not None: - test_path = Path(self.test_artifact_path) / 'data/images/' - data_dict['val'] = str(test_path) + self.log_dict, self.current_epoch, self.log_imgs = {}, 0, 16 # Logging Constants + self.bbox_interval = opt.bbox_interval + if isinstance(opt.resume, str): + modeldir, _ = self.download_model_artifact(opt) + if modeldir: + self.weights = Path(modeldir) / "last.pt" + config = self.wandb_run.config + opt.weights, opt.save_period, opt.batch_size, opt.bbox_interval, opt.epochs, opt.hyp = str( + self.weights), config.save_period, config.total_batch_size, config.bbox_interval, config.epochs, \ + config.opt['hyp'] + data_dict = dict(self.wandb_run.config.data_dict) # eliminates the need for config file to resume + if 'val_artifact' not in self.__dict__: # If --upload_dataset is set, use the existing artifact, don't download + self.train_artifact_path, self.train_artifact = self.download_dataset_artifact(data_dict.get('train'), + opt.artifact_alias) + self.val_artifact_path, self.val_artifact = self.download_dataset_artifact(data_dict.get('val'), + opt.artifact_alias) + self.result_artifact, self.result_table, self.val_table, self.weights = None, None, None, None + if self.train_artifact_path is not None: + train_path = Path(self.train_artifact_path) / 'data/images/' + data_dict['train'] = str(train_path) + if self.val_artifact_path is not None: + val_path = Path(self.val_artifact_path) / 'data/images/' + data_dict['val'] = str(val_path) + self.val_table = self.val_artifact.get("val") + self.map_val_table_path() + if self.val_artifact is not None: self.result_artifact = wandb.Artifact("run_" + wandb.run.id + "_progress", "evaluation") self.result_table = wandb.Table(["epoch", "id", "prediction", "avg_confidence"]) - if opt.resume_from_artifact: - modeldir, _ = self.download_model_artifact(opt.resume_from_artifact) - if modeldir: - self.weights = Path(modeldir) / "best.pt" - opt.weights = self.weights + if opt.bbox_interval == -1: + self.bbox_interval = opt.bbox_interval = (opt.epochs // 10) if opt.epochs > 10 else 1 + return data_dict def download_dataset_artifact(self, path, alias): if path.startswith(WANDB_ARTIFACT_PREFIX): dataset_artifact = wandb.use_artifact(remove_prefix(path, WANDB_ARTIFACT_PREFIX) + ":" + alias) assert dataset_artifact is not None, "'Error: W&B dataset artifact doesn\'t exist'" datadir = dataset_artifact.download() - labels_zip = Path(datadir) / "data/labels.zip" - shutil.unpack_archive(labels_zip, Path(datadir) / 'data/labels', 'zip') - print("Downloaded dataset to : ", datadir) return datadir, dataset_artifact return None, None - def download_model_artifact(self, name): - model_artifact = wandb.use_artifact(name + ":latest") - assert model_artifact is not None, 'Error: W&B model artifact doesn\'t exist' - modeldir = model_artifact.download() - print("Downloaded model to : ", modeldir) - return modeldir, model_artifact + def download_model_artifact(self, opt): + if opt.resume.startswith(WANDB_ARTIFACT_PREFIX): + model_artifact = wandb.use_artifact(remove_prefix(opt.resume, WANDB_ARTIFACT_PREFIX) + ":latest") + assert model_artifact is not None, 'Error: W&B model artifact doesn\'t exist' + modeldir = model_artifact.download() + epochs_trained = model_artifact.metadata.get('epochs_trained') + total_epochs = model_artifact.metadata.get('total_epochs') + assert epochs_trained < total_epochs, 'training to %g epochs is finished, nothing to resume.' % ( + total_epochs) + return modeldir, model_artifact + return None, None - def log_model(self, path, opt, epoch): - datetime_suffix = datetime.today().strftime('%Y-%m-%d-%H-%M-%S') + def log_model(self, path, opt, epoch, fitness_score, best_model=False): model_artifact = wandb.Artifact('run_' + wandb.run.id + '_model', type='model', metadata={ 'original_url': str(path), - 'epoch': epoch + 1, + 'epochs_trained': epoch + 1, 'save period': opt.save_period, 'project': opt.project, - 'datetime': datetime_suffix + 'total_epochs': opt.epochs, + 'fitness_score': fitness_score }) model_artifact.add_file(str(path / 'last.pt'), name='last.pt') - model_artifact.add_file(str(path / 'best.pt'), name='best.pt') - wandb.log_artifact(model_artifact) + wandb.log_artifact(model_artifact, + aliases=['latest', 'epoch ' + str(self.current_epoch), 'best' if best_model else '']) print("Saving model artifact on epoch ", epoch + 1) - def log_dataset_artifact(self, dataset, class_to_id, name='dataset'): + def log_dataset_artifact(self, data_file, single_cls, project, overwrite_config=False): + with open(data_file) as f: + data = yaml.load(f, Loader=yaml.SafeLoader) # data dict + nc, names = (1, ['item']) if single_cls else (int(data['nc']), data['names']) + names = {k: v for k, v in enumerate(names)} # to index dictionary + self.train_artifact = self.create_dataset_table(LoadImagesAndLabels( + data['train']), names, name='train') if data.get('train') else None + self.val_artifact = self.create_dataset_table(LoadImagesAndLabels( + data['val']), names, name='val') if data.get('val') else None + if data.get('train'): + data['train'] = WANDB_ARTIFACT_PREFIX + str(Path(project) / 'train') + if data.get('val'): + data['val'] = WANDB_ARTIFACT_PREFIX + str(Path(project) / 'val') + path = data_file if overwrite_config else '_wandb.'.join(data_file.rsplit('.', 1)) # updated data.yaml path + data.pop('download', None) + with open(path, 'w') as f: + yaml.dump(data, f) + + if self.job_type == 'Training': # builds correct artifact pipeline graph + self.wandb_run.use_artifact(self.val_artifact) + self.wandb_run.use_artifact(self.train_artifact) + self.val_artifact.wait() + self.val_table = self.val_artifact.get('val') + self.map_val_table_path() + else: + self.wandb_run.log_artifact(self.train_artifact) + self.wandb_run.log_artifact(self.val_artifact) + return path + + def map_val_table_path(self): + self.val_table_map = {} + print("Mapping dataset") + for i, data in enumerate(tqdm(self.val_table.data)): + self.val_table_map[data[3]] = data[0] + + def create_dataset_table(self, dataset, class_to_id, name='dataset'): + # TODO: Explore multiprocessing to slpit this loop parallely| This is essential for speeding up the the logging artifact = wandb.Artifact(name=name, type="dataset") - image_path = dataset.path - artifact.add_dir(image_path, name='data/images') - table = wandb.Table(columns=["id", "train_image", "Classes"]) + for img_file in tqdm([dataset.path]) if Path(dataset.path).is_dir() else tqdm(dataset.img_files): + if Path(img_file).is_dir(): + artifact.add_dir(img_file, name='data/images') + labels_path = 'labels'.join(dataset.path.rsplit('images', 1)) + artifact.add_dir(labels_path, name='data/labels') + else: + artifact.add_file(img_file, name='data/images/' + Path(img_file).name) + label_file = Path(img2label_paths([img_file])[0]) + artifact.add_file(str(label_file), + name='data/labels/' + label_file.name) if label_file.exists() else None + table = wandb.Table(columns=["id", "train_image", "Classes", "name"]) class_set = wandb.Classes([{'id': id, 'name': name} for id, name in class_to_id.items()]) - for si, (img, labels, paths, shapes) in enumerate(dataset): + for si, (img, labels, paths, shapes) in enumerate(tqdm(dataset)): height, width = shapes[0] - labels[:, 2:] = (xywh2xyxy(labels[:, 2:].view(-1, 4))) - labels[:, 2:] *= torch.Tensor([width, height, width, height]) - box_data = [] - img_classes = {} + labels[:, 2:] = (xywh2xyxy(labels[:, 2:].view(-1, 4))) * torch.Tensor([width, height, width, height]) + box_data, img_classes = [], {} for cls, *xyxy in labels[:, 1:].tolist(): cls = int(cls) box_data.append({"position": {"minX": xyxy[0], "minY": xyxy[1], "maxX": xyxy[2], "maxY": xyxy[3]}, @@ -112,34 +213,52 @@ def log_dataset_artifact(self, dataset, class_to_id, name='dataset'): "domain": "pixel"}) img_classes[cls] = class_to_id[cls] boxes = {"ground_truth": {"box_data": box_data, "class_labels": class_to_id}} # inference-space - table.add_data(si, wandb.Image(paths, classes=class_set, boxes=boxes), json.dumps(img_classes)) + table.add_data(si, wandb.Image(paths, classes=class_set, boxes=boxes), json.dumps(img_classes), + Path(paths).name) artifact.add(table, name) - labels_path = 'labels'.join(image_path.rsplit('images', 1)) - zip_path = Path(labels_path).parent / (name + '_labels.zip') - if not zip_path.is_file(): # make_archive won't check if file exists - shutil.make_archive(zip_path.with_suffix(''), 'zip', labels_path) - artifact.add_file(str(zip_path), name='data/labels.zip') - wandb.log_artifact(artifact) - print("Saving data to W&B...") + return artifact + + def log_training_progress(self, predn, path, names): + if self.val_table and self.result_table: + class_set = wandb.Classes([{'id': id, 'name': name} for id, name in names.items()]) + box_data = [] + total_conf = 0 + for *xyxy, conf, cls in predn.tolist(): + if conf >= 0.25: + box_data.append( + {"position": {"minX": xyxy[0], "minY": xyxy[1], "maxX": xyxy[2], "maxY": xyxy[3]}, + "class_id": int(cls), + "box_caption": "%s %.3f" % (names[cls], conf), + "scores": {"class_score": conf}, + "domain": "pixel"}) + total_conf = total_conf + conf + boxes = {"predictions": {"box_data": box_data, "class_labels": names}} # inference-space + id = self.val_table_map[Path(path).name] + self.result_table.add_data(self.current_epoch, + id, + wandb.Image(self.val_table.data[id][1], boxes=boxes, classes=class_set), + total_conf / max(1, len(box_data)) + ) def log(self, log_dict): if self.wandb_run: for key, value in log_dict.items(): self.log_dict[key] = value - def end_epoch(self): - if self.wandb_run and self.log_dict: + def end_epoch(self, best_result=False): + if self.wandb_run: wandb.log(self.log_dict) - self.log_dict = {} + self.log_dict = {} + if self.result_artifact: + train_results = wandb.JoinedTable(self.val_table, self.result_table, "id") + self.result_artifact.add(train_results, 'result') + wandb.log_artifact(self.result_artifact, aliases=['latest', 'epoch ' + str(self.current_epoch), + ('best' if best_result else '')]) + self.result_table = wandb.Table(["epoch", "id", "prediction", "avg_confidence"]) + self.result_artifact = wandb.Artifact("run_" + wandb.run.id + "_progress", "evaluation") def finish_run(self): if self.wandb_run: - if self.result_artifact: - print("Add Training Progress Artifact") - self.result_artifact.add(self.result_table, 'result') - train_results = wandb.JoinedTable(self.testset_artifact.get("val"), self.result_table, "id") - self.result_artifact.add(train_results, 'joined_result') - wandb.log_artifact(self.result_artifact) if self.log_dict: wandb.log(self.log_dict) wandb.run.finish() From 1c132a1f9426d91c18ec7eff6ab95a727344c690 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 23 Mar 2021 14:10:47 +0100 Subject: [PATCH 74/88] Update Detections() times=None (#2570) Fix for results.tolist() method breaking after YOLOv5 Hub profiling PRshttps://github.com/ultralytics/yolov5/pull/2460 https://github.com/ultralytics/yolov5/pull/2459 and --- models/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/common.py b/models/common.py index 83cc8b5ce27b..721171393e04 100644 --- a/models/common.py +++ b/models/common.py @@ -235,7 +235,7 @@ def forward(self, imgs, size=640, augment=False, profile=False): class Detections: # detections class for YOLOv5 inference results - def __init__(self, imgs, pred, files, times, names=None, shape=None): + def __init__(self, imgs, pred, files, times=None, names=None, shape=None): super(Detections, self).__init__() d = pred[0].device # device gn = [torch.tensor([*[im.shape[i] for i in [1, 0, 1, 0]], 1., 1.], device=d) for im in imgs] # normalizations From 0d891c601e8178e4b9665da46d630456668b1996 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 23 Mar 2021 14:25:55 +0100 Subject: [PATCH 75/88] check_requirements() exclude pycocotools, thop (#2571) Exclude non-critical packages from dependency checks in detect.py. pycocotools and thop in particular are not required for inference. Issue first raised in https://github.com/ultralytics/yolov5/issues/1944 and also raised in https://github.com/ultralytics/yolov5/discussions/2556 --- detect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detect.py b/detect.py index 22bf21b4c825..c843447260ba 100644 --- a/detect.py +++ b/detect.py @@ -164,7 +164,7 @@ def detect(save_img=False): parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') opt = parser.parse_args() print(opt) - check_requirements() + check_requirements(exclude=('pycocotools', 'thop')) with torch.no_grad(): if opt.update: # update all models (to fix SourceChangeWarning) From 1bf936528018c36bcbd22e9b9f76a8c61e97d2a6 Mon Sep 17 00:00:00 2001 From: Ayush Chaurasia Date: Tue, 23 Mar 2021 21:24:34 +0530 Subject: [PATCH 76/88] W&B DDP fix (#2574) --- train.py | 8 +++++--- utils/wandb_logging/wandb_utils.py | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/train.py b/train.py index 62a72375c7a3..fd2d6745ab46 100644 --- a/train.py +++ b/train.py @@ -66,14 +66,16 @@ def train(hyp, opt, device, tb_writer=None): is_coco = opt.data.endswith('coco.yaml') # Logging- Doing this before checking the dataset. Might update data_dict + loggers = {'wandb': None} # loggers dict if rank in [-1, 0]: opt.hyp = hyp # add hyperparameters run_id = torch.load(weights).get('wandb_id') if weights.endswith('.pt') and os.path.isfile(weights) else None wandb_logger = WandbLogger(opt, Path(opt.save_dir).stem, run_id, data_dict) + loggers['wandb'] = wandb_logger.wandb data_dict = wandb_logger.data_dict if wandb_logger.wandb: weights, epochs, hyp = opt.weights, opt.epochs, opt.hyp # WandbLogger might update weights, epochs if resuming - loggers = {'wandb': wandb_logger.wandb} # loggers dict + nc = 1 if opt.single_cls else int(data_dict['nc']) # number of classes names = ['item'] if opt.single_cls and len(data_dict['names']) != 1 else data_dict['names'] # class names assert len(names) == nc, '%g names found for nc=%g dataset in %s' % (len(names), nc, opt.data) # check @@ -381,6 +383,7 @@ def train(hyp, opt, device, tb_writer=None): fi = fitness(np.array(results).reshape(1, -1)) # weighted combination of [P, R, mAP@.5, mAP@.5-.95] if fi > best_fitness: best_fitness = fi + wandb_logger.end_epoch(best_result=best_fitness == fi) # Save model if (not opt.nosave) or (final_epoch and not opt.evolve): # if save @@ -402,7 +405,6 @@ def train(hyp, opt, device, tb_writer=None): wandb_logger.log_model( last.parent, opt, epoch, fi, best_model=best_fitness == fi) del ckpt - wandb_logger.end_epoch(best_result=best_fitness == fi) # end epoch ---------------------------------------------------------------------------------------------------- # end training @@ -442,10 +444,10 @@ def train(hyp, opt, device, tb_writer=None): wandb_logger.wandb.log_artifact(str(final), type='model', name='run_' + wandb_logger.wandb_run.id + '_model', aliases=['last', 'best', 'stripped']) + wandb_logger.finish_run() else: dist.destroy_process_group() torch.cuda.empty_cache() - wandb_logger.finish_run() return results diff --git a/utils/wandb_logging/wandb_utils.py b/utils/wandb_logging/wandb_utils.py index c9a32f5b6026..d6dd256366e0 100644 --- a/utils/wandb_logging/wandb_utils.py +++ b/utils/wandb_logging/wandb_utils.py @@ -16,9 +16,9 @@ try: import wandb + from wandb import init, finish except ImportError: wandb = None - print(f"{colorstr('wandb: ')}Install Weights & Biases for YOLOv5 logging with 'pip install wandb' (recommended)") WANDB_ARTIFACT_PREFIX = 'wandb-artifact://' @@ -71,6 +71,9 @@ def __init__(self, opt, name, run_id, data_dict, job_type='Training'): self.data_dict = self.setup_training(opt, data_dict) if self.job_type == 'Dataset Creation': self.data_dict = self.check_and_upload_dataset(opt) + else: + print(f"{colorstr('wandb: ')}Install Weights & Biases for YOLOv5 logging with 'pip install wandb' (recommended)") + def check_and_upload_dataset(self, opt): assert wandb, 'Install wandb to upload dataset' From 2b329b0945a69431fd8bf36668307069e6e999b1 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 24 Mar 2021 01:05:59 +0100 Subject: [PATCH 77/88] Enhanced check_requirements() with auto-install (#2575) * Update check_requirements() with auto-install This PR builds on an idea I had to automatically install missing dependencies rather than simply report an error message. YOLOv5 should now 1) display all dependency issues and not simply display the first missing dependency, and 2) attempt to install/update each missing/VersionConflict package. * cleanup * cleanup 2 * Check requirements.txt file exists * cleanup 3 --- utils/general.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/utils/general.py b/utils/general.py index 621df64c6cf1..ef89ea3a0f03 100755 --- a/utils/general.py +++ b/utils/general.py @@ -1,4 +1,4 @@ -# General utils +# YOLOv5 general utils import glob import logging @@ -86,10 +86,20 @@ def check_git_status(): def check_requirements(file='requirements.txt', exclude=()): # Check installed dependencies meet requirements - import pkg_resources - requirements = [f'{x.name}{x.specifier}' for x in pkg_resources.parse_requirements(Path(file).open()) - if x.name not in exclude] - pkg_resources.require(requirements) # DistributionNotFound or VersionConflict exception if requirements not met + import pkg_resources as pkg + prefix = colorstr('red', 'bold', 'requirements:') + file = Path(file) + if not file.exists(): + print(f"{prefix} {file.resolve()} not found, check failed.") + return + + requirements = [f'{x.name}{x.specifier}' for x in pkg.parse_requirements(file.open()) if x.name not in exclude] + for r in requirements: + try: + pkg.require(r) + except Exception as e: # DistributionNotFound or VersionConflict if requirements not met + print(f"{prefix} {e.req} not found and is required by YOLOv5, attempting auto-install...") + print(subprocess.check_output(f"pip install '{e.req}'", shell=True).decode()) def check_img_size(img_size, s=32): From e5b0200cd250759c782207160761ca9756300065 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 24 Mar 2021 01:29:00 +0100 Subject: [PATCH 78/88] Update tensorboard>=2.4.1 (#2576) * Update tensorboard>=2.4.1 Update tensorboard version to attempt to address https://github.com/ultralytics/yolov5/issues/2573 (tensorboard logging fail in Docker image). * cleanup --- requirements.txt | 2 +- train.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index cb50cf8f32e1..51de7735d301 100755 --- a/requirements.txt +++ b/requirements.txt @@ -8,12 +8,12 @@ opencv-python>=4.1.2 Pillow PyYAML>=5.3.1 scipy>=1.4.1 -tensorboard>=2.2 torch>=1.7.0 torchvision>=0.8.1 tqdm>=4.41.0 # logging ------------------------------------- +tensorboard>=2.4.1 # wandb # plotting ------------------------------------ diff --git a/train.py b/train.py index fd2d6745ab46..b9e4eea613dc 100644 --- a/train.py +++ b/train.py @@ -1,4 +1,3 @@ - import argparse import logging import math @@ -34,7 +33,7 @@ from utils.loss import ComputeLoss from utils.plots import plot_images, plot_labels, plot_results, plot_evolution from utils.torch_utils import ModelEMA, select_device, intersect_dicts, torch_distributed_zero_first, is_parallel -from utils.wandb_logging.wandb_utils import WandbLogger, resume_and_get_id, check_wandb_config_file +from utils.wandb_logging.wandb_utils import WandbLogger, resume_and_get_id logger = logging.getLogger(__name__) @@ -75,7 +74,7 @@ def train(hyp, opt, device, tb_writer=None): data_dict = wandb_logger.data_dict if wandb_logger.wandb: weights, epochs, hyp = opt.weights, opt.epochs, opt.hyp # WandbLogger might update weights, epochs if resuming - + nc = 1 if opt.single_cls else int(data_dict['nc']) # number of classes names = ['item'] if opt.single_cls and len(data_dict['names']) != 1 else data_dict['names'] # class names assert len(names) == nc, '%g names found for nc=%g dataset in %s' % (len(names), nc, opt.data) # check @@ -405,7 +404,7 @@ def train(hyp, opt, device, tb_writer=None): wandb_logger.log_model( last.parent, opt, epoch, fi, best_model=best_fitness == fi) del ckpt - + # end epoch ---------------------------------------------------------------------------------------------------- # end training if rank in [-1, 0]: @@ -534,7 +533,8 @@ def train(hyp, opt, device, tb_writer=None): if not opt.evolve: tb_writer = None # init loggers if opt.global_rank in [-1, 0]: - logger.info(f'Start Tensorboard with "tensorboard --logdir {opt.project}", view at http://localhost:6006/') + prefix = colorstr('tensorboard: ') + logger.info(f"{prefix}Start with 'tensorboard --logdir {opt.project}', view at http://localhost:6006/") tb_writer = SummaryWriter(opt.save_dir) # Tensorboard train(hyp, opt, device, tb_writer) From 2bcc89d76225a704bb9a21c926bd28ef7847d81d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 24 Mar 2021 15:42:00 +0100 Subject: [PATCH 79/88] YOLOv5 PyTorch Hub models >> check_requirements() (#2577) * Update hubconf.py with check_requirements() Dependency checks have been missing from YOLOv5 PyTorch Hub model loading, causing errors in some cases when users are attempting to import hub models in unsupported environments. This should examine the YOLOv5 requirements.txt file and pip install any missing or version-conflict packages encountered. This is highly experimental (!), please let us know if this creates problems in your custom workflows. * Update hubconf.py --- hubconf.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hubconf.py b/hubconf.py index b7b740d39c06..4b4ae04cf332 100644 --- a/hubconf.py +++ b/hubconf.py @@ -1,8 +1,8 @@ -"""File for accessing YOLOv5 via PyTorch Hub https://pytorch.org/hub/ +"""File for accessing YOLOv5 models via PyTorch Hub https://pytorch.org/hub/ultralytics_yolov5/ Usage: import torch - model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True, channels=3, classes=80) + model = torch.hub.load('ultralytics/yolov5', 'yolov5s') """ from pathlib import Path @@ -10,11 +10,12 @@ import torch from models.yolo import Model -from utils.general import set_logging +from utils.general import check_requirements, set_logging from utils.google_utils import attempt_download from utils.torch_utils import select_device dependencies = ['torch', 'yaml'] +check_requirements(exclude=('pycocotools', 'thop')) set_logging() From 9f98201dd98651a768acefdd87856c86a031ff89 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 24 Mar 2021 15:43:32 +0100 Subject: [PATCH 80/88] W&B DDP fix 2 (#2587) Revert unintentional change to test batch sizes caused by PR https://github.com/ultralytics/yolov5/pull/2125 --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index b9e4eea613dc..25a9accd3be0 100644 --- a/train.py +++ b/train.py @@ -349,7 +349,7 @@ def train(hyp, opt, device, tb_writer=None): if not opt.notest or final_epoch: # Calculate mAP wandb_logger.current_epoch = epoch + 1 results, maps, times = test.test(data_dict, - batch_size=total_batch_size, + batch_size=batch_size * 2, imgsz=imgsz_test, model=ema.ema, single_cls=opt.single_cls, From 8ace1b1b992433f31721c0553287cd664f2efe6b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 24 Mar 2021 16:23:54 +0100 Subject: [PATCH 81/88] YOLOv5 PyTorch Hub models >> check_requirements() (#2588) * YOLOv5 PyTorch Hub models >> check_requirements() Update YOLOv5 PyTorch Hub requirements.txt path to cache path. * Update hubconf.py --- hubconf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hubconf.py b/hubconf.py index 4b4ae04cf332..710882cf158f 100644 --- a/hubconf.py +++ b/hubconf.py @@ -15,7 +15,7 @@ from utils.torch_utils import select_device dependencies = ['torch', 'yaml'] -check_requirements(exclude=('pycocotools', 'thop')) +check_requirements(Path(__file__).parent / 'requirements.txt', exclude=('pycocotools', 'thop')) set_logging() From 75feeb797c4a9553f4274860c6c278f1fc628f60 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 24 Mar 2021 16:42:54 +0100 Subject: [PATCH 82/88] YOLOv5 PyTorch Hub models >> check_requirements() (#2591) Prints 'Please restart runtime or rerun command for update to take effect.' following package auto-install to inform users to restart/rerun. --- utils/general.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/general.py b/utils/general.py index ef89ea3a0f03..50d60c519b04 100755 --- a/utils/general.py +++ b/utils/general.py @@ -100,6 +100,7 @@ def check_requirements(file='requirements.txt', exclude=()): except Exception as e: # DistributionNotFound or VersionConflict if requirements not met print(f"{prefix} {e.req} not found and is required by YOLOv5, attempting auto-install...") print(subprocess.check_output(f"pip install '{e.req}'", shell=True).decode()) + print(f'Please restart runtime or rerun command for update to take effect.') def check_img_size(img_size, s=32): From 333ccc5b0f66c7d0aba096e3e2d9d1912db1e610 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 24 Mar 2021 17:51:39 +0100 Subject: [PATCH 83/88] YOLOv5 PyTorch Hub models >> check_requirements() (#2592) Improved user-feedback following requirements auto-update. --- utils/general.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/utils/general.py b/utils/general.py index 50d60c519b04..284146c87e10 100755 --- a/utils/general.py +++ b/utils/general.py @@ -52,6 +52,11 @@ def isdocker(): return Path('/workspace').exists() # or Path('/.dockerenv').exists() +def emojis(str=''): + # Return platform-dependent emoji-safe version of string + return str.encode().decode('ascii', 'ignore') if platform.system() == 'Windows' else str + + def check_online(): # Check internet connectivity import socket @@ -79,7 +84,7 @@ def check_git_status(): f"Use 'git pull' to update or 'git clone {url}' to download latest." else: s = f'up to date with {url} ✅' - print(s.encode().decode('ascii', 'ignore') if platform.system() == 'Windows' else s) # emoji-safe + print(emojis(s)) # emoji-safe except Exception as e: print(e) @@ -93,14 +98,20 @@ def check_requirements(file='requirements.txt', exclude=()): print(f"{prefix} {file.resolve()} not found, check failed.") return + n = 0 # number of packages updates requirements = [f'{x.name}{x.specifier}' for x in pkg.parse_requirements(file.open()) if x.name not in exclude] for r in requirements: try: pkg.require(r) except Exception as e: # DistributionNotFound or VersionConflict if requirements not met - print(f"{prefix} {e.req} not found and is required by YOLOv5, attempting auto-install...") + n += 1 + print(f"{prefix} {e.req} not found and is required by YOLOv5, attempting auto-update...") print(subprocess.check_output(f"pip install '{e.req}'", shell=True).decode()) - print(f'Please restart runtime or rerun command for update to take effect.') + + if n: # if packages updated + s = f"{prefix} {n} package{'s' * (n > 1)} updated per {file.resolve()}\n" \ + f"{prefix} ⚠️ {colorstr('bold', 'Restart runtime or rerun command for updates to take effect')}\n" + print(emojis(s)) # emoji-safe def check_img_size(img_size, s=32): From 16206692f245e713a5beb380de6dc4bed944986c Mon Sep 17 00:00:00 2001 From: Max Kolomeychenko Date: Thu, 25 Mar 2021 02:57:34 +0300 Subject: [PATCH 84/88] Supervisely Ecosystem (#2519) guide describes YOLOv5 apps collection in Supervisely Ecosystem --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 097b2750bf49..1240f83be2a5 100755 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ $ pip install -r requirements.txt * [Train Custom Data](https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data)  🚀 RECOMMENDED * [Weights & Biases Logging](https://github.com/ultralytics/yolov5/issues/1289)  🌟 NEW +* [Supervisely Ecosystem](https://github.com/ultralytics/yolov5/issues/2518)  🌟 NEW * [Multi-GPU Training](https://github.com/ultralytics/yolov5/issues/475) * [PyTorch Hub](https://github.com/ultralytics/yolov5/issues/36)  ⭐ NEW * [ONNX and TorchScript Export](https://github.com/ultralytics/yolov5/issues/251) From ad05e37d99bd1b86f7223540ad93381b8269d75c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 25 Mar 2021 14:09:49 +0100 Subject: [PATCH 85/88] Save webcam results, add --nosave option (#2598) This updates the default detect.py behavior to automatically save all inference images/videos/webcams unless the new argument --nosave is used (python detect.py --nosave) or unless a list of streaming sources is passed (python detect.py --source streams.txt) --- detect.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/detect.py b/detect.py index c843447260ba..2a4d6f4550c8 100644 --- a/detect.py +++ b/detect.py @@ -17,6 +17,7 @@ def detect(save_img=False): source, weights, view_img, save_txt, imgsz = opt.source, opt.weights, opt.view_img, opt.save_txt, opt.img_size + save_img = not opt.nosave and not source.endswith('.txt') # save inference images webcam = source.isnumeric() or source.endswith('.txt') or source.lower().startswith( ('rtsp://', 'rtmp://', 'http://')) @@ -49,7 +50,6 @@ def detect(save_img=False): cudnn.benchmark = True # set True to speed up constant image size inference dataset = LoadStreams(source, img_size=imgsz, stride=stride) else: - save_img = True dataset = LoadImages(source, img_size=imgsz, stride=stride) # Get names and colors @@ -124,17 +124,19 @@ def detect(save_img=False): if save_img: if dataset.mode == 'image': cv2.imwrite(save_path, im0) - else: # 'video' + else: # 'video' or 'stream' if vid_path != save_path: # new video vid_path = save_path if isinstance(vid_writer, cv2.VideoWriter): vid_writer.release() # release previous video writer - - fourcc = 'mp4v' # output video codec - fps = vid_cap.get(cv2.CAP_PROP_FPS) - w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) - h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*fourcc), fps, (w, h)) + if vid_cap: # video + fps = vid_cap.get(cv2.CAP_PROP_FPS) + w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + else: # stream + fps, w, h = 30, im0.shape[1], im0.shape[0] + save_path += '.mp4' + vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h)) vid_writer.write(im0) if save_txt or save_img: @@ -155,6 +157,7 @@ def detect(save_img=False): parser.add_argument('--view-img', action='store_true', help='display results') parser.add_argument('--save-txt', action='store_true', help='save results to *.txt') parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels') + parser.add_argument('--nosave', action='store_true', help='do not save images/videos') parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3') parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS') parser.add_argument('--augment', action='store_true', help='augmented inference') From d4456e43b23be03dfd5098d2a1992cd338581801 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 25 Mar 2021 15:12:49 +0100 Subject: [PATCH 86/88] Update segment2box() comment (#2600) --- utils/general.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/general.py b/utils/general.py index 284146c87e10..9822582cdb86 100755 --- a/utils/general.py +++ b/utils/general.py @@ -289,7 +289,7 @@ def segment2box(segment, width=640, height=640): x, y = segment.T # segment xy inside = (x >= 0) & (y >= 0) & (x <= width) & (y <= height) x, y, = x[inside], y[inside] - return np.array([x.min(), y.min(), x.max(), y.max()]) if any(x) else np.zeros((1, 4)) # cls, xyxy + return np.array([x.min(), y.min(), x.max(), y.max()]) if any(x) else np.zeros((1, 4)) # xyxy def segments2boxes(segments): From 3bb414890a253bb1a269fb81cc275d11c8fffa72 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 25 Mar 2021 20:55:20 +0100 Subject: [PATCH 87/88] resume.py typo (#2603) --- utils/aws/resume.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/aws/resume.py b/utils/aws/resume.py index 563f22be20dc..faad8d247411 100644 --- a/utils/aws/resume.py +++ b/utils/aws/resume.py @@ -1,4 +1,4 @@ -# Resume all interrupted trainings in yolov5/ dir including DPP trainings +# Resume all interrupted trainings in yolov5/ dir including DDP trainings # Usage: $ python utils/aws/resume.py import os From fca16dc4b3b877391a9e2710b52ab78b3ee59130 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 25 Mar 2021 21:48:25 +0100 Subject: [PATCH 88/88] Remove Cython from requirements.txt (#2604) Cython should be a dependency of the remaining packages in requirements.txt, so should be installed anyway even if not a direct requirement. --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 51de7735d301..fd187eb56cfe 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ # pip install -r requirements.txt # base ---------------------------------------- -Cython matplotlib>=3.2.2 numpy>=1.18.5 opencv-python>=4.1.2