From bba5f85c06b98df352dd5d0a1f00d3d8e7ac044e Mon Sep 17 00:00:00 2001
From: fatih <34196005+fcakyon@users.noreply.github.com>
Date: Wed, 14 Dec 2022 00:01:58 +0300
Subject: [PATCH] update to ultralytics/yolov5 13.12.22 (#170)
* init update
* update val
* update export
* update benchmarks
* update detect
* update train
* update utils
* update segment utils
* update wandb utils
* update clearml utils
* update data
* update models
* update version
* update classify
* update segment
* fix val
* fix val
* fix val half
* fix val half
---
README.md | 2 +-
requirements.txt | 26 ++--
yolov5/__init__.py | 2 +-
yolov5/benchmarks.py | 2 +-
yolov5/classify/predict.py | 9 +-
yolov5/classify/train.py | 7 +-
yolov5/classify/val.py | 5 +-
yolov5/data/scripts/download_weights.sh | 7 +-
yolov5/data/scripts/get_coco.sh | 2 +-
yolov5/detect.py | 37 +++---
yolov5/export.py | 11 +-
yolov5/models/common.py | 4 +-
yolov5/models/tf.py | 5 +-
yolov5/segment/predict.py | 38 +++---
yolov5/segment/train.py | 26 ++--
yolov5/segment/val.py | 24 ++--
yolov5/train.py | 74 ++++++-----
yolov5/utils/__init__.py | 11 +-
yolov5/utils/augmentations.py | 8 +-
yolov5/utils/autoanchor.py | 4 +-
yolov5/utils/dataloaders.py | 80 ++++++++----
yolov5/utils/downloads.py | 8 +-
yolov5/utils/general.py | 116 ++++++++++++------
yolov5/utils/loggers/__init__.py | 22 ++--
yolov5/utils/loggers/clearml/README.md | 12 +-
yolov5/utils/loggers/clearml/clearml_utils.py | 11 +-
yolov5/utils/loggers/wandb/wandb_utils.py | 2 +-
yolov5/utils/metrics.py | 32 ++---
yolov5/utils/plots.py | 48 +++-----
yolov5/utils/segment/dataloaders.py | 3 +-
yolov5/utils/segment/general.py | 29 ++++-
yolov5/utils/torch_utils.py | 3 +-
yolov5/val.py | 18 +--
33 files changed, 409 insertions(+), 279 deletions(-)
diff --git a/README.md b/README.md
index 209233c..26dc4d8 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ You can finally install YOLOv5 o
-This yolov5 package contains everything from ultralytics/yolov5 at this commit plus:
+This yolov5 package contains everything from ultralytics/yolov5 at this commit plus:
1. Easy installation via pip: `pip install yolov5`
diff --git a/requirements.txt b/requirements.txt
index 2cd0209..a913365 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,29 +1,33 @@
-# YOLOv5 requirements
+# YOLOv5 π requirements
# Usage: pip install -r requirements.txt
-# Base ----------------------------------------
+# Base ------------------------------------------------------------------------
+gitpython
+ipython # interactive notebook
matplotlib>=3.2.2
numpy>=1.18.5
opencv-python>=4.1.1
Pillow>=7.1.2
+psutil # system resources
PyYAML>=5.3.1
requests>=2.23.0
scipy>=1.4.1
-torch>=1.7.0 # see https://pytorch.org/get-started/locally/ (recommended)
+thop>=0.1.1 # FLOPs computation
+torch>=1.7.0 # see https://pytorch.org/get-started/locally (recommended)
torchvision>=0.8.1
tqdm>=4.64.0
# protobuf<=3.20.1 # https://github.com/ultralytics/yolov5/issues/8012
-# Logging -------------------------------------
+# Logging ---------------------------------------------------------------------
tensorboard>=2.4.1
-# clearml
+# clearml>=1.2.0
# comet
-# Plotting ------------------------------------
+# Plotting --------------------------------------------------------------------
pandas>=1.1.4
seaborn>=0.11.0
-# Export --------------------------------------
+# Export ----------------------------------------------------------------------
# coremltools>=6.0 # CoreML export
# onnx>=1.9.0 # ONNX export
# onnx-simplifier>=0.4.1 # ONNX simplifier
@@ -34,17 +38,15 @@ seaborn>=0.11.0
# tensorflowjs>=3.9.0 # TF.js export
# openvino-dev # OpenVINO export
-# Deploy --------------------------------------
+# Deploy ----------------------------------------------------------------------
# tritonclient[all]~=2.24.0
-# Extras --------------------------------------
-ipython # interactive notebook
-psutil # system utilization
-thop>=0.1.1 # FLOPs computation
+# Extras ----------------------------------------------------------------------
# mss # screenshots
# albumentations>=1.0.3
# pycocotools>=2.0 # COCO mAP
# roboflow
+# ultralytics # HUB https://hub.ultralytics.com
# CLI
fire
diff --git a/yolov5/__init__.py b/yolov5/__init__.py
index dc40255..a170317 100644
--- a/yolov5/__init__.py
+++ b/yolov5/__init__.py
@@ -1,4 +1,4 @@
from yolov5.helpers import YOLOv5
from yolov5.helpers import load_model as load
-__version__ = "6.2.3"
+__version__ = "7.0.0"
diff --git a/yolov5/benchmarks.py b/yolov5/benchmarks.py
index b24fb47..10eab4c 100644
--- a/yolov5/benchmarks.py
+++ b/yolov5/benchmarks.py
@@ -163,7 +163,7 @@ def run_cli(**kwargs):
def parse_opt():
parser = argparse.ArgumentParser()
- parser.add_argument('--weights', type=str, default=ROOT / 'yolov5s.pt', help='weights path')
+ parser.add_argument('--weights', type=str, default='yolov5s.pt', help='weights path')
parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='inference size (pixels)')
parser.add_argument('--batch-size', type=int, default=1, help='batch size')
parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='dataset.yaml path')
diff --git a/yolov5/classify/predict.py b/yolov5/classify/predict.py
index e83352b..8feafe6 100644
--- a/yolov5/classify/predict.py
+++ b/yolov5/classify/predict.py
@@ -6,7 +6,10 @@
$ python classify/predict.py --weights yolov5s-cls.pt --source 0 # webcam
img.jpg # image
vid.mp4 # video
+ screen # screenshot
path/ # directory
+ list.txt # list of images
+ list.streams # list of streams
'path/*.jpg' # glob
'https://youtu.be/Zgi9g1ksQHc' # YouTube
'rtsp://example.com/media.mp4' # RTSP, RTMP, HTTP stream
@@ -63,7 +66,7 @@ def run(
augment=False, # augmented inference
visualize=False, # visualize features
update=False, # update all models
- project=ROOT / 'runs/predict-cls', # save results to project/name
+ project='runs/predict-cls', # save results to project/name
name='exp', # save results to project/name
exist_ok=False, # existing project/name ok, do not increment
half=False, # use FP16 half-precision inference
@@ -74,7 +77,7 @@ def run(
save_img = not nosave and not source.endswith('.txt') # save inference images
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
- webcam = source.isnumeric() or source.endswith('.txt') or (is_url and not is_file)
+ webcam = source.isnumeric() or source.endswith('.streams') or (is_url and not is_file)
screenshot = source.lower().startswith('screen')
if is_url and is_file:
source = check_file(source) # download
@@ -205,7 +208,7 @@ def parse_opt():
parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[224], help='inference size h,w')
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--view-img', action='store_true', help='show results')
- parser.add_argument('--save-txt', action='store_false', help='save results to *.txt')
+ parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
parser.add_argument('--augment', action='store_true', help='augmented inference')
parser.add_argument('--visualize', action='store_true', help='visualize features')
diff --git a/yolov5/classify/train.py b/yolov5/classify/train.py
index 552ff7d..0a272d4 100644
--- a/yolov5/classify/train.py
+++ b/yolov5/classify/train.py
@@ -40,8 +40,8 @@
from yolov5.models.experimental import attempt_load
from yolov5.models.yolo import ClassificationModel, DetectionModel
from yolov5.utils.dataloaders import create_classification_dataloader
-from yolov5.utils.general import (DATASETS_DIR, LOGGER, WorkingDirectory, check_git_status, check_requirements, colorstr,
- download, increment_path, init_seeds, print_args, yaml_save)
+from yolov5.utils.general import (DATASETS_DIR, LOGGER, TQDM_BAR_FORMAT, WorkingDirectory, check_git_info, check_git_status,
+ check_requirements, colorstr, download, increment_path, init_seeds, print_args, yaml_save)
from yolov5.utils.loggers import GenericLogger
from yolov5.utils.plots import imshow_cls
from yolov5.utils.torch_utils import (ModelEMA, model_info, reshape_classifier_output, select_device, smart_DDP,
@@ -50,6 +50,7 @@
LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html
RANK = int(os.getenv('RANK', -1))
WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1))
+#GIT_INFO = check_git_info()
def train(opt, device):
@@ -174,7 +175,7 @@ def train(opt, device):
trainloader.sampler.set_epoch(epoch)
pbar = enumerate(trainloader)
if RANK in {-1, 0}:
- pbar = tqdm(enumerate(trainloader), total=len(trainloader), bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}')
+ pbar = tqdm(enumerate(trainloader), total=len(trainloader), bar_format=TQDM_BAR_FORMAT)
for i, (images, labels) in pbar: # progress bar
images, labels = images.to(device, non_blocking=True), labels.to(device)
diff --git a/yolov5/classify/val.py b/yolov5/classify/val.py
index 7b0d745..b31309b 100644
--- a/yolov5/classify/val.py
+++ b/yolov5/classify/val.py
@@ -36,7 +36,8 @@
from yolov5.models.common import DetectMultiBackend
from yolov5.utils.dataloaders import create_classification_dataloader
-from yolov5.utils.general import LOGGER, Profile, check_img_size, check_requirements, colorstr, increment_path, print_args
+from yolov5.utils.general import (LOGGER, TQDM_BAR_FORMAT, Profile, check_img_size, check_requirements, colorstr,
+ increment_path, print_args)
from yolov5.utils.torch_utils import select_device, smart_inference_mode
@@ -112,7 +113,7 @@ def run(
n = len(dataloader) # number of batches
action = 'validating' if dataloader.dataset.root.stem == 'val' else 'testing'
desc = f"{pbar.desc[:-36]}{action:>36}" if pbar else f"{action}"
- bar = tqdm(dataloader, desc, n, not training, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0)
+ bar = tqdm(dataloader, desc, n, not training, bar_format=TQDM_BAR_FORMAT, position=0)
with torch.cuda.amp.autocast(enabled=device.type != 'cpu'):
for images, labels in bar:
with dt[0]:
diff --git a/yolov5/data/scripts/download_weights.sh b/yolov5/data/scripts/download_weights.sh
index fe3de26..31e0a15 100755
--- a/yolov5/data/scripts/download_weights.sh
+++ b/yolov5/data/scripts/download_weights.sh
@@ -9,13 +9,14 @@
# βββ ...
python - <=1.12 may require do_constant_folding=False
input_names=['images'],
output_names=output_names,
dynamic_axes=dynamic or None)
@@ -607,6 +607,7 @@ def run(
f = [str(x) for x in f if x] # filter out '' and None
if any(f):
cls, det, seg = (isinstance(model, x) for x in (ClassificationModel, DetectionModel, SegmentationModel)) # type
+ det &= not seg # segmentation models inherit from SegmentationModel(DetectionModel)
dir = Path('segment' if seg else 'classify' if cls else '')
h = '--half' if half else '' # --half FP16 inference arg
s = "# WARNING β οΈ ClassificationModel not yet supported for PyTorch Hub AutoShape inference" if cls else \
@@ -614,8 +615,8 @@ def run(
LOGGER.info(f'\nExport complete ({time.time() - t:.1f}s)'
f"\nResults saved to {colorstr('bold', file.parent.resolve())}"
f"\nDetect: yolov5 {'detect' if det else 'predict'} --weights {f[-1]} {h}"
- f"\nValidate: yolov5 val --weights {f[-1]} {h}"
- f"\nPython: model = yolov5.load('{f[-1]}') {s}"
+ f"\nValidate: yolov5 yolov5 val --weights {f[-1]} {h}"
+ f"\nnPython: model = yolov5.load('{f[-1]}') {s}"
f"\nVisualize: https://netron.app")
return f # return list of exported files/dirs
diff --git a/yolov5/models/common.py b/yolov5/models/common.py
index 101328e..2747ac4 100644
--- a/yolov5/models/common.py
+++ b/yolov5/models/common.py
@@ -692,9 +692,9 @@ def forward(self, ims, size=640, augment=False, profile=False):
s = im.shape[:2] # HWC
shape0.append(s) # image shape
g = max(size) / max(s) # gain
- shape1.append([y * g for y in s])
+ shape1.append([int(y * g) for y in s])
ims[i] = im if im.data.contiguous else np.ascontiguousarray(im) # update
- shape1 = [make_divisible(x, self.stride) for x in np.array(shape1).max(0)] if self.pt else size # inf shape
+ shape1 = [make_divisible(x, self.stride) for x in np.array(shape1).max(0)] # inf shape
x = [letterbox(im, shape1, auto=False)[0] for im in ims] # pad
x = np.ascontiguousarray(np.array(x).transpose((0, 3, 1, 2))) # stack and BHWC to BCHW
x = torch.from_numpy(x).to(p.device).type_as(p) / 255 # uint8 to fp16/32
diff --git a/yolov5/models/tf.py b/yolov5/models/tf.py
index 32fa23d..caf09ee 100644
--- a/yolov5/models/tf.py
+++ b/yolov5/models/tf.py
@@ -333,6 +333,7 @@ def __init__(self, nc=80, anchors=(), nm=32, npr=256, ch=(), imgsz=(640, 640), w
def call(self, x):
p = self.proto(x[0])
+ # p = TFUpsample(None, scale_factor=4, mode='nearest')(self.proto(x[0])) # (optional) full-size protos
p = tf.transpose(p, [0, 3, 1, 2]) # from shape(1,160,160,32) to shape(1,32,160,160)
x = self.detect(self, x)
return (x, p) if self.training else (x[0], p)
@@ -355,8 +356,8 @@ class TFUpsample(keras.layers.Layer):
# TF version of torch.nn.Upsample()
def __init__(self, size, scale_factor, mode, w=None): # warning: all arguments needed including 'w'
super().__init__()
- assert scale_factor == 2, "scale_factor must be 2"
- self.upsample = lambda x: tf.image.resize(x, (x.shape[1] * 2, x.shape[2] * 2), method=mode)
+ assert scale_factor % 2 == 0, "scale_factor must be multiple of 2"
+ self.upsample = lambda x: tf.image.resize(x, (x.shape[1] * scale_factor, x.shape[2] * scale_factor), mode)
# self.upsample = keras.layers.UpSampling2D(size=scale_factor, interpolation=mode)
# with default arguments: align_corners=False, half_pixel_centers=False
# self.upsample = lambda x: tf.raw_ops.ResizeNearestNeighbor(images=x,
diff --git a/yolov5/segment/predict.py b/yolov5/segment/predict.py
index a57f87d..92c2799 100644
--- a/yolov5/segment/predict.py
+++ b/yolov5/segment/predict.py
@@ -6,7 +6,10 @@
$ python segment/predict.py --weights yolov5s-seg.pt --source 0 # webcam
img.jpg # image
vid.mp4 # video
+ screen # screenshot
path/ # directory
+ list.txt # list of images
+ list.streams # list of streams
'path/*.jpg' # glob
'https://youtu.be/Zgi9g1ksQHc' # YouTube
'rtsp://example.com/media.mp4' # RTSP, RTMP, HTTP stream
@@ -15,7 +18,7 @@
$ python segment/predict.py --weights yolov5s-seg.pt # PyTorch
yolov5s-seg.torchscript # TorchScript
yolov5s-seg.onnx # ONNX Runtime or OpenCV DNN with --dnn
- yolov5s-seg.xml # OpenVINO
+ yolov5s-seg_openvino_model # OpenVINO
yolov5s-seg.engine # TensorRT
yolov5s-seg.mlmodel # CoreML (macOS-only)
yolov5s-seg_saved_model # TensorFlow SavedModel
@@ -43,9 +46,9 @@
from yolov5.utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadScreenshots, LoadStreams
from yolov5.utils.general import (LOGGER, Profile, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2,
increment_path, non_max_suppression, print_args, scale_boxes, scale_segments,
- strip_optimizer, xyxy2xywh)
+ strip_optimizer)
from yolov5.utils.plots import Annotator, colors, save_one_box
-from yolov5.utils.segment.general import masks2segments, process_mask
+from yolov5.utils.segment.general import masks2segments, process_mask, process_mask_native
from yolov5.utils.torch_utils import select_device, smart_inference_mode
@@ -85,9 +88,8 @@ def run(
save_img = not nosave and not source.endswith('.txt') # save inference images
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
- webcam = source.isnumeric() or source.endswith('.txt') or (is_url and not is_file)
+ webcam = source.isnumeric() or source.endswith('.streams') or (is_url and not is_file)
screenshot = source.lower().startswith('screen')
-
if is_url and is_file:
source = check_file(source) # download
@@ -160,13 +162,19 @@ def run(
imc = im0.copy() if save_crop else im0 # for save_crop
annotator = Annotator(im0, line_width=line_thickness, example=str(names))
if len(det):
- masks = process_mask(proto[i], det[:, 6:], det[:, :4], im.shape[2:], upsample=True) # HWC
- det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round() # rescale boxes to im0 size
+ if retina_masks:
+ # scale bbox first the crop masks
+ det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round() # rescale boxes to im0 size
+ masks = process_mask_native(proto[i], det[:, 6:], det[:, :4], im0.shape[:2]) # HWC
+ else:
+ masks = process_mask(proto[i], det[:, 6:], det[:, :4], im.shape[2:], upsample=True) # HWC
+ det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round() # rescale boxes to im0 size
# Segments
if save_txt:
- segments = reversed(masks2segments(masks))
- segments = [scale_segments(im.shape[2:], x, im0.shape).round() for x in segments]
+ segments = [
+ scale_segments(im0.shape if retina_masks else im.shape[2:], x, im0.shape, normalize=True)
+ for x in reversed(masks2segments(masks))]
# Print results
for c in det[:, 5].unique():
@@ -174,15 +182,17 @@ def run(
s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " # add to string
# Mask plotting
- annotator.masks(masks,
- colors=[colors(x, True) for x in det[:, 5]],
- im_gpu=None if retina_masks else im[i])
+ annotator.masks(
+ masks,
+ colors=[colors(x, True) for x in det[:, 5]],
+ im_gpu=torch.as_tensor(im0, dtype=torch.float16).to(device).permute(2, 0, 1).flip(0).contiguous() /
+ 255 if retina_masks else im[i])
# Write results
for j, (*xyxy, conf, cls) in enumerate(reversed(det[:, :6])):
if save_txt: # Write to file
- segj = segments[j].reshape(-1) # (n,2) to (n*2)
- line = (cls, *segj, conf) if save_conf else (cls, *segj) # label format
+ seg = segments[j].reshape(-1) # (n,2) to (n*2)
+ line = (cls, *seg, conf) if save_conf else (cls, *seg) # label format
with open(f'{txt_path}.txt', 'a') as f:
f.write(('%g ' * len(line)).rstrip() % line + '\n')
diff --git a/yolov5/segment/train.py b/yolov5/segment/train.py
index 2415651..f0a95c1 100644
--- a/yolov5/segment/train.py
+++ b/yolov5/segment/train.py
@@ -5,7 +5,7 @@
Usage - Single-GPU training:
$ yolov5 segment train --data coco128-seg.yaml --weights yolov5s-seg.pt --img 640 # from pretrained (recommended)
- $ yolov5 segment trainy --data coco128-seg.yaml --weights '' --cfg yolov5s-seg.yaml --img 640 # from scratch
+ $ yolov5 segment train --data coco128-seg.yaml --weights '' --cfg yolov5s-seg.yaml --img 640 # from scratch
Usage - Multi-GPU DDP training:
$ python -m torch.distributed.run --nproc_per_node 4 --master_port 1 segment/train.py --data coco128-seg.yaml --weights yolov5s-seg.pt --img 640 --device 0,1,2,3
@@ -46,10 +46,10 @@
from yolov5.utils.autobatch import check_train_batch_size
from yolov5.utils.callbacks import Callbacks
from yolov5.utils.downloads import attempt_download, is_url
-from yolov5.utils.general import (LOGGER, check_amp, check_dataset, check_file, check_git_status, check_img_size,
- check_requirements, check_suffix, check_yaml, colorstr, get_latest_run, increment_path,
- init_seeds, intersect_dicts, labels_to_class_weights, labels_to_image_weights, one_cycle,
- print_args, print_mutation, strip_optimizer, yaml_save)
+from yolov5.utils.general import (LOGGER, TQDM_BAR_FORMAT, check_amp, check_dataset, check_file, check_git_info,
+ check_git_status, check_img_size, check_requirements, check_suffix, check_yaml, colorstr,
+ get_latest_run, increment_path, init_seeds, intersect_dicts, labels_to_class_weights,
+ labels_to_image_weights, one_cycle, print_args, print_mutation, strip_optimizer, yaml_save)
from yolov5.utils.loggers import GenericLogger
from yolov5.utils.plots import plot_evolve, plot_labels
from yolov5.utils.segment.dataloaders import create_dataloader
@@ -62,6 +62,7 @@
LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html
RANK = int(os.getenv('RANK', -1))
WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1))
+#GIT_INFO = check_git_info()
def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictionary
@@ -279,7 +280,7 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
LOGGER.info(('\n' + '%11s' * 8) %
('Epoch', 'GPU_mem', 'box_loss', 'seg_loss', 'obj_loss', 'cls_loss', 'Instances', 'Size'))
if RANK in {-1, 0}:
- pbar = tqdm(pbar, total=nb, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}') # progress bar
+ pbar = tqdm(pbar, total=nb, bar_format=TQDM_BAR_FORMAT) # progress bar
optimizer.zero_grad()
for i, (imgs, targets, paths, _, masks) in pbar: # batch ------------------------------------------------------
# callbacks.run('on_train_batch_start')
@@ -461,11 +462,11 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
def parse_opt(known=False):
parser = argparse.ArgumentParser()
- parser.add_argument('--weights', type=str, default=ROOT / 'yolov5s-seg.pt', help='initial weights path')
+ parser.add_argument('--weights', type=str, default='yolov5s-seg.pt', help='initial weights path')
parser.add_argument('--cfg', type=str, default='', help='model.yaml path')
parser.add_argument('--data', type=str, default=ROOT / 'data/coco128-seg.yaml', help='dataset.yaml path')
parser.add_argument('--hyp', type=str, default=ROOT / 'data/hyps/hyp.scratch-low.yaml', help='hyperparameters path')
- parser.add_argument('--epochs', type=int, default=300, help='total training epochs')
+ parser.add_argument('--epochs', type=int, default=100, help='total training epochs')
parser.add_argument('--batch-size', type=int, default=16, help='total batch size for all GPUs, -1 for autobatch')
parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='train, val image size (pixels)')
parser.add_argument('--rect', action='store_true', help='rectangular training')
@@ -476,7 +477,7 @@ def parse_opt(known=False):
parser.add_argument('--noplots', action='store_true', help='save no plot files')
parser.add_argument('--evolve', type=int, nargs='?', const=300, help='evolve hyperparameters for x generations')
parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')
- parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"')
+ parser.add_argument('--cache', type=str, nargs='?', const='ram', help='image --cache ram/disk')
parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training')
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%')
@@ -504,13 +505,6 @@ def parse_opt(known=False):
parser.add_argument('--neptune_token', type=str, default=None, help='neptune.ai api token')
parser.add_argument('--neptune_project', type=str, default=None, help='https://docs.neptune.ai/api-reference/neptune')
-
- # Weights & Biases arguments
- # parser.add_argument('--entity', default=None, help='W&B: Entity')
- # parser.add_argument('--upload_dataset', nargs='?', const=True, default=False, help='W&B: Upload data, "val" option')
- # parser.add_argument('--bbox_interval', type=int, default=-1, help='W&B: Set bounding-box image logging interval')
- # parser.add_argument('--artifact_alias', type=str, default='latest', help='W&B: Version of dataset artifact to use')
-
return parser.parse_known_args()[0] if known else parser.parse_args()
diff --git a/yolov5/segment/val.py b/yolov5/segment/val.py
index a911795..12e7eb0 100644
--- a/yolov5/segment/val.py
+++ b/yolov5/segment/val.py
@@ -10,7 +10,7 @@
$ yolov5 segment val --weights yolov5s-seg.pt # PyTorch
yolov5s-seg.torchscript # TorchScript
yolov5s-seg.onnx # ONNX Runtime or OpenCV DNN with --dnn
- yolov5s-seg.xml # OpenVINO
+ yolov5s-seg_openvino_label # OpenVINO
yolov5s-seg.engine # TensorRT
yolov5s-seg.mlmodel # CoreML (macOS-only)
yolov5s-seg_saved_model # TensorFlow SavedModel
@@ -42,13 +42,13 @@
from yolov5.models.common import DetectMultiBackend
from yolov5.models.yolo import SegmentationModel
from yolov5.utils.callbacks import Callbacks
-from yolov5.utils.general import (LOGGER, NUM_THREADS, Profile, check_dataset, check_img_size, check_requirements, check_yaml,
- coco80_to_coco91_class, colorstr, increment_path, non_max_suppression, print_args,
- scale_boxes, xywh2xyxy, xyxy2xywh)
+from yolov5.utils.general import (LOGGER, NUM_THREADS, TQDM_BAR_FORMAT, Profile, check_dataset, check_img_size,
+ check_requirements, check_yaml, coco80_to_coco91_class, colorstr, increment_path,
+ non_max_suppression, print_args, scale_boxes, xywh2xyxy, xyxy2xywh)
from yolov5.utils.metrics import ConfusionMatrix, box_iou
from yolov5.utils.plots import output_to_target, plot_val_study
from yolov5.utils.segment.dataloaders import create_dataloader
-from yolov5.utils.segment.general import mask_iou, process_mask, process_mask_upsample, scale_image
+from yolov5.utils.segment.general import mask_iou, process_mask, process_mask_native, scale_image
from yolov5.utils.segment.metrics import Metrics, ap_per_class_box_and_mask
from yolov5.utils.segment.plots import plot_images_and_masks
from yolov5.utils.torch_utils import de_parallel, select_device, smart_inference_mode
@@ -162,7 +162,7 @@ def run(
):
if save_json:
check_requirements(['pycocotools'])
- process = process_mask_upsample # more accurate
+ process = process_mask_native # more accurate
else:
process = process_mask # faster
@@ -248,7 +248,7 @@ def run(
loss = torch.zeros(4, device=device)
jdict, stats = [], []
# callbacks.run('on_val_start')
- pbar = tqdm(dataloader, desc=s, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}') # progress bar
+ pbar = tqdm(dataloader, desc=s, bar_format=TQDM_BAR_FORMAT) # progress bar
for batch_i, (im, targets, paths, shapes, masks) in enumerate(pbar):
# callbacks.run('on_val_batch_start')
with dt[0]:
@@ -323,7 +323,7 @@ def run(
pred_masks = torch.as_tensor(pred_masks, dtype=torch.uint8)
if plots and batch_i < 3:
- plot_masks.append(pred_masks[:15].cpu()) # filter top 15 to plot
+ plot_masks.append(pred_masks[:15]) # filter top 15 to plot
# Save/log
if save_txt:
@@ -378,8 +378,8 @@ def run(
# Save JSON
if save_json and len(jdict):
w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else '' # weights
- anno_json = str(Path(data.get('path', '../coco')) / 'annotations/instances_val2017.json') # annotations json
- pred_json = str(save_dir / f"{w}_predictions.json") # predictions json
+ anno_json = str(Path('../datasets/coco/annotations/instances_val2017.json')) # annotations
+ pred_json = str(save_dir / f"{w}_predictions.json") # predictions
LOGGER.info(f'\nEvaluating pycocotools mAP... saving {pred_json}...')
with open(pred_json, 'w') as f:
json.dump(jdict, f)
@@ -455,7 +455,7 @@ def main(opt):
else:
weights = opt.weights if isinstance(opt.weights, list) else [opt.weights]
- opt.half = True # FP16 for fastest results
+ opt.half = torch.cuda.is_available() and opt.device != 'cpu' # FP16 for fastest results
if opt.task == 'speed': # speed benchmarks
# python val.py --task speed --data coco.yaml --batch 1 --weights yolov5n.pt yolov5s.pt...
opt.conf_thres, opt.iou_thres, opt.save_json = 0.25, 0.45, False
@@ -474,6 +474,8 @@ def main(opt):
np.savetxt(f, y, fmt='%10.4g') # save
os.system('zip -r study.zip study_*.txt')
plot_val_study(x=x) # plot
+ else:
+ raise NotImplementedError(f'--task {opt.task} not in ("train", "val", "test", "speed", "study")')
if __name__ == "__main__":
diff --git a/yolov5/train.py b/yolov5/train.py
index 6ada380..1a0f0fa 100644
--- a/yolov5/train.py
+++ b/yolov5/train.py
@@ -45,14 +45,14 @@
from yolov5.models.yolo import Model
from yolov5.utils.autoanchor import check_anchors
from yolov5.utils.autobatch import check_train_batch_size
-from yolov5.utils.aws import upload_file_to_s3, upload_folder_to_s3
from yolov5.utils.callbacks import Callbacks
from yolov5.utils.dataloaders import create_dataloader
from yolov5.utils.downloads import attempt_download, is_url
-from yolov5.utils.general import (LOGGER, check_amp, check_dataset, check_file, check_git_status, check_img_size,
- check_requirements, check_suffix, check_yaml, colorstr, get_latest_run, increment_path,
- init_seeds, intersect_dicts, labels_to_class_weights, labels_to_image_weights, methods,
- one_cycle, print_args, print_mutation, strip_optimizer, yaml_save)
+from yolov5.utils.general import (LOGGER, TQDM_BAR_FORMAT, check_amp, check_dataset, check_file, check_git_info,
+ check_git_status, check_img_size, check_requirements, check_suffix, check_yaml, colorstr,
+ get_latest_run, increment_path, init_seeds, intersect_dicts, labels_to_class_weights,
+ labels_to_image_weights, methods, one_cycle, print_args, print_mutation, strip_optimizer,
+ yaml_save)
from yolov5.utils.loggers import Loggers
from yolov5.utils.loggers.comet.comet_utils import check_comet_resume
from yolov5.utils.loss import ComputeLoss
@@ -64,17 +64,12 @@
LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html
RANK = int(os.getenv('RANK', -1))
WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1))
+#GIT_INFO = check_git_info()
# fix OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.
os.environ['KMP_DUPLICATE_LIB_OK']='True'
-def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictionary
- save_dir, epochs, batch_size, weights, single_cls, evolve, data, cfg, resume, noval, nosave, workers, freeze = \
- Path(opt.save_dir), opt.epochs, opt.batch_size, opt.weights, opt.single_cls, opt.evolve, opt.data, opt.cfg, \
- opt.resume, opt.noval, opt.nosave, opt.workers, opt.freeze
- callbacks.run('on_pretrain_routine_start')
-
- # coco to yolov5 conversion
+def convert_coco_dataset_to_yolo(opt, save_dir):
is_coco_data = False
has_yolo_s3_data_dir = False
with open(opt.data, errors='ignore') as f:
@@ -115,6 +110,32 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
if "val_json_path" in data_info and Path(data_info["val_json_path"]).is_file():
copyfile(data_info["val_json_path"], str(w / "val.json"))
+def upload_to_s3(opt, data, save_dir):
+ from yolov5.utils.aws import upload_file_to_s3, upload_folder_to_s3
+
+ with open(data, errors='ignore') as f:
+ data_info = yaml.safe_load(f) # load data dict
+ # upload yolo formatted data to s3
+ s3_folder = "s3://" + str(Path(opt.s3_upload_dir.replace("s3://","")) / save_dir.name / 'data').replace(os.sep, '/')
+ LOGGER.info(f"{colorstr('aws:')} Uploading yolo formatted dataset to {s3_folder}")
+ s3_file = s3_folder + "/data.yaml"
+ result = upload_file_to_s3(local_file=opt.data, s3_file=s3_file)
+ s3_folder_train = s3_folder + "/train/"
+ result = upload_folder_to_s3(local_folder=data_info["train"], s3_folder=s3_folder_train)
+ s3_folder_val = s3_folder + "/val/"
+ result = upload_folder_to_s3(local_folder=data_info["val"], s3_folder=s3_folder_val)
+ if result:
+ LOGGER.info(f"{colorstr('aws:')} Dataset has been successfully uploaded to {s3_folder}")
+
+
+def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictionary
+ save_dir, epochs, batch_size, weights, single_cls, evolve, data, cfg, resume, noval, nosave, workers, freeze = \
+ Path(opt.save_dir), opt.epochs, opt.batch_size, opt.weights, opt.single_cls, opt.evolve, opt.data, opt.cfg, \
+ opt.resume, opt.noval, opt.nosave, opt.workers, opt.freeze
+ callbacks.run('on_pretrain_routine_start')
+
+ convert_coco_dataset_to_yolo(opt, save_dir)
+
# Directories
w = save_dir / 'weights' # weights dir
(w.parent if evolve else w).mkdir(parents=True, exist_ok=True) # make dir
@@ -158,20 +179,7 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
# upload dataset to s3
if opt.upload_dataset and opt.s3_upload_dir:
- with open(data, errors='ignore') as f:
- data_info = yaml.safe_load(f) # load data dict
- # upload yolo formatted data to s3
- s3_folder = "s3://" + str(Path(opt.s3_upload_dir.replace("s3://","")) / save_dir.name / 'data').replace(os.sep, '/')
- LOGGER.info(f"{colorstr('aws:')} Uploading yolo formatted dataset to {s3_folder}")
- s3_file = s3_folder + "/data.yaml"
- result = upload_file_to_s3(local_file=opt.data, s3_file=s3_file)
- s3_folder_train = s3_folder + "/train/"
- result = upload_folder_to_s3(local_folder=data_info["train"], s3_folder=s3_folder_train)
- s3_folder_val = s3_folder + "/val/"
- result = upload_folder_to_s3(local_folder=data_info["val"], s3_folder=s3_folder_val)
- if result:
- LOGGER.info(f"{colorstr('aws:')} Dataset has been successfully uploaded to {s3_folder}")
-
+ upload_to_s3(opt, data, save_dir)
# Model
check_suffix(weights, '.pt') # check weights
pretrained = weights.endswith('.pt')
@@ -338,7 +346,7 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
pbar = enumerate(train_loader)
LOGGER.info(('\n' + '%11s' * 7) % ('Epoch', 'GPU_mem', 'box_loss', 'obj_loss', 'cls_loss', 'Instances', 'Size'))
if RANK in {-1, 0}:
- pbar = tqdm(pbar, total=nb, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}') # progress bar
+ pbar = tqdm(pbar, total=nb, bar_format=TQDM_BAR_FORMAT) # progress bar
optimizer.zero_grad()
for i, (imgs, targets, paths, _) in pbar: # batch -------------------------------------------------------------
callbacks.run('on_train_batch_start')
@@ -450,6 +458,8 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
# upload best model to aws s3
if opt.s3_upload_dir:
+ from yolov5.utils.aws import upload_file_to_s3
+
s3_file = "s3://" + str(Path(opt.s3_upload_dir.replace("s3://","")) / save_dir.name / "weights" / "best.pt").replace(os.sep, '/')
LOGGER.info(f"{colorstr('aws:')} Uploading best weight to AWS S3...")
result = upload_file_to_s3(local_file=str(best), s3_file=s3_file)
@@ -495,6 +505,8 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
# upload best model to aws s3
if opt.s3_upload_dir:
+ from yolov5.utils.aws import upload_file_to_s3
+
s3_file = "s3://" + str(Path(opt.s3_upload_dir.replace("s3://","")) / save_dir.name / "weights" / "best.pt").replace(os.sep, '/')
LOGGER.info(f"{colorstr('aws:')} Uploading best weight to AWS S3...")
result = upload_file_to_s3(local_file=str(best), s3_file=s3_file)
@@ -514,7 +526,7 @@ def parse_opt(known=False):
parser.add_argument('--cfg', type=str, default='', help='model.yaml path')
parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='dataset.yaml path')
parser.add_argument('--hyp', type=str, default=ROOT / 'data/hyps/hyp.scratch-low.yaml', help='hyperparameters path')
- parser.add_argument('--epochs', type=int, default=300, help='total training epochs')
+ parser.add_argument('--epochs', type=int, default=100, help='total training epochs')
parser.add_argument('--batch-size', type=int, default=16, help='total batch size for all GPUs, -1 for autobatch')
parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='train, val image size (pixels)')
parser.add_argument('--rect', action='store_true', help='rectangular training')
@@ -525,7 +537,7 @@ def parse_opt(known=False):
parser.add_argument('--noplots', action='store_true', help='save no plot files')
parser.add_argument('--evolve', type=int, nargs='?', const=300, help='evolve hyperparameters for x generations')
parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')
- parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"')
+ parser.add_argument('--cache', type=str, nargs='?', const='ram', help='image --cache ram/disk')
parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training')
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%')
@@ -545,7 +557,7 @@ def parse_opt(known=False):
parser.add_argument('--seed', type=int, default=0, help='Global training seed')
parser.add_argument('--local_rank', type=int, default=-1, help='Automatic DDP Multi-GPU argument, do not modify')
parser.add_argument('--mmdet_tags', action='store_true', help='Log train/val keys in MMDetection format')
-
+
# Weights & Biases arguments
parser.add_argument('--entity', default=None, help='Entity')
parser.add_argument('--bbox_interval', type=int, default=-1, help='Set bounding-box image logging interval')
@@ -567,7 +579,7 @@ def main(opt, callbacks=Callbacks()):
if RANK in {-1, 0}:
print_args(vars(opt))
#check_git_status()
- check_requirements()
+ #check_requirements()
# Resume (from specified or most recent last.pt)
if opt.resume and not check_comet_resume(opt) and not opt.evolve:
diff --git a/yolov5/utils/__init__.py b/yolov5/utils/__init__.py
index 55a4e89..ad37b27 100644
--- a/yolov5/utils/__init__.py
+++ b/yolov5/utils/__init__.py
@@ -37,6 +37,16 @@ def wrapper(*args, **kwargs):
return wrapper
+def join_threads(verbose=False):
+ # Join all daemon threads, i.e. atexit.register(lambda: join_threads())
+ main_thread = threading.current_thread()
+ for t in threading.enumerate():
+ if t is not main_thread:
+ if verbose:
+ print(f'Joining thread {t.name}')
+ t.join()
+
+
def notebook_init(verbose=True):
# Check system software and hardware
print('Checking setup...')
@@ -47,7 +57,6 @@ def notebook_init(verbose=True):
from yolov5.utils.general import check_font, check_requirements, is_colab
from yolov5.utils.torch_utils import select_device # imports
- check_requirements(('psutil', 'IPython'))
check_font()
import psutil
diff --git a/yolov5/utils/augmentations.py b/yolov5/utils/augmentations.py
index 4e9712c..7ce3f6a 100644
--- a/yolov5/utils/augmentations.py
+++ b/yolov5/utils/augmentations.py
@@ -250,12 +250,10 @@ def copy_paste(im, labels, segments, p=0.5):
if (ioa < 0.30).all(): # allow 30% obscuration of existing labels
labels = np.concatenate((labels, [[l[0], *box]]), 0)
segments.append(np.concatenate((w - s[:, 0:1], s[:, 1:2]), 1))
- cv2.drawContours(im_new, [segments[j].astype(np.int32)], -1, (255, 255, 255), cv2.FILLED)
+ cv2.drawContours(im_new, [segments[j].astype(np.int32)], -1, (1, 1, 1), cv2.FILLED)
- result = cv2.bitwise_and(src1=im, src2=im_new)
- result = cv2.flip(result, 1) # augment segments (flip left-right)
- i = result > 0 # pixels to replace
- # i[:, :] = result.max(2).reshape(h, w, 1) # act over ch
+ result = cv2.flip(im, 1) # augment segments (flip left-right)
+ i = cv2.flip(im_new, 1).astype(bool)
im[i] = result[i] # cv2.imwrite('debug.jpg', im) # debug
return im, labels, segments
diff --git a/yolov5/utils/autoanchor.py b/yolov5/utils/autoanchor.py
index c82cf32..f89fcc9 100644
--- a/yolov5/utils/autoanchor.py
+++ b/yolov5/utils/autoanchor.py
@@ -11,7 +11,7 @@
from tqdm import tqdm
from yolov5.utils import TryExcept
-from yolov5.utils.general import LOGGER, colorstr
+from yolov5.utils.general import LOGGER, TQDM_BAR_FORMAT, colorstr
PREFIX = colorstr('AutoAnchor: ')
@@ -153,7 +153,7 @@ def print_results(k, verbose=True):
# Evolve
f, sh, mp, s = anchor_fitness(k), k.shape, 0.9, 0.1 # fitness, generations, mutation prob, sigma
- pbar = tqdm(range(gen), bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}') # progress bar
+ pbar = tqdm(range(gen), bar_format=TQDM_BAR_FORMAT) # progress bar
for _ in pbar:
v = np.ones(sh)
while (v == 1).all(): # mutate until a change occurs (prevent duplicates)
diff --git a/yolov5/utils/dataloaders.py b/yolov5/utils/dataloaders.py
index 13c63df..c220974 100644
--- a/yolov5/utils/dataloaders.py
+++ b/yolov5/utils/dataloaders.py
@@ -19,6 +19,7 @@
from urllib.parse import urlparse
import numpy as np
+import psutil
import torch
import torch.nn.functional as F
import torchvision
@@ -28,17 +29,16 @@
from tqdm import tqdm
from yolov5.utils.augmentations import (Albumentations, augment_hsv, classify_albumentations, classify_transforms, copy_paste,
- cutout, letterbox, mixup, random_perspective)
-from yolov5.utils.general import (DATASETS_DIR, LOGGER, NUM_THREADS, check_dataset, check_requirements, check_yaml, clean_str,
- cv2, is_colab, is_kaggle, segments2boxes, unzip_file, xyn2xy, xywh2xyxy, xywhn2xyxy,
- xyxy2xywhn)
+ letterbox, mixup, random_perspective)
+from yolov5.utils.general import (DATASETS_DIR, LOGGER, NUM_THREADS, TQDM_BAR_FORMAT, check_dataset, check_requirements,
+ check_yaml, clean_str, cv2, is_colab, is_kaggle, segments2boxes, unzip_file, xyn2xy,
+ xywh2xyxy, xywhn2xyxy, xyxy2xywhn)
from yolov5.utils.torch_utils import torch_distributed_zero_first
# Parameters
HELP_URL = 'See https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data'
IMG_FORMATS = 'bmp', 'dng', 'jpeg', 'jpg', 'mpo', 'png', 'tif', 'tiff', 'webp', 'pfm' # include image suffixes
VID_FORMATS = 'asf', 'avi', 'gif', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'ts', 'wmv' # include video suffixes
-BAR_FORMAT = '{l_bar}{bar:10}{r_bar}{bar:-10b}' # tqdm bar format
LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html
RANK = int(os.getenv('RANK', -1))
PIN_MEMORY = str(os.getenv('PIN_MEMORY', True)).lower() == 'true' # global pin_memory for dataloaders
@@ -238,6 +238,8 @@ def __next__(self):
class LoadImages:
# YOLOv5 image/video dataloader, i.e. `python detect.py --source image.jpg/vid.mp4`
def __init__(self, path, img_size=640, stride=32, auto=True, transforms=None, vid_stride=1):
+ if isinstance(path, str) and Path(path).suffix == ".txt": # *.txt file with img/vid/dir on each line
+ path = Path(path).read_text().rsplit()
files = []
for p in sorted(path) if isinstance(path, (list, tuple)) else [path]:
p = str(Path(p).resolve())
@@ -338,7 +340,7 @@ def __len__(self):
class LoadStreams:
# YOLOv5 streamloader, i.e. `python detect.py --source 'rtsp://example.com/media.mp4' # RTSP, RTMP, HTTP streams`
- def __init__(self, sources='streams.txt', img_size=640, stride=32, auto=True, transforms=None, vid_stride=1):
+ def __init__(self, sources='file.streams', img_size=640, stride=32, auto=True, transforms=None, vid_stride=1):
torch.backends.cudnn.benchmark = True # faster for fixed-size inference
self.mode = 'stream'
self.img_size = img_size
@@ -352,6 +354,7 @@ def __init__(self, sources='streams.txt', img_size=640, stride=32, auto=True, tr
# Start thread to read frames from video stream
st = f'{i + 1}/{n}: {s}... '
if urlparse(s).hostname in ('www.youtube.com', 'youtube.com', 'youtu.be'): # if source is YouTube video
+ # YouTube format i.e. 'https://www.youtube.com/watch?v=Zgi9g1ksQHc' or 'https://youtu.be/Zgi9g1ksQHc'
check_requirements(('pafy', 'youtube_dl==2020.12.2'))
import pafy
s = pafy.new(s).getbest(preftype="mp4").url # YouTube URL
@@ -444,6 +447,7 @@ def __init__(self,
single_cls=False,
stride=32,
pad=0.0,
+ min_items=0,
prefix=''):
self.img_size = img_size
self.augment = augment
@@ -467,15 +471,15 @@ def __init__(self,
with open(p) as t:
t = t.read().strip().splitlines()
parent = str(p.parent) + os.sep
- f += [x.replace('./', parent) if x.startswith('./') else x for x in t] # local to global path
- # f += [p.parent / x.lstrip(os.sep) for x in t] # local to global path (pathlib)
+ f += [x.replace('./', parent, 1) if x.startswith('./') else x for x in t] # to global path
+ # f += [p.parent / x.lstrip(os.sep) for x in t] # to global path (pathlib)
else:
raise FileNotFoundError(f'{prefix}{p} does not exist')
self.im_files = sorted(x.replace('/', os.sep) for x in f if x.split('.')[-1].lower() in IMG_FORMATS)
# self.img_files = sorted([x for x in f if x.suffix[1:].lower() in IMG_FORMATS]) # pathlib
assert self.im_files, f'{prefix}No images found'
except Exception as e:
- raise Exception(f'{prefix}Error loading data from {path}: {e}\n{HELP_URL}')
+ raise Exception(f'{prefix}Error loading data from {path}: {e}\n{HELP_URL}') from e
# Check cache
self.label_files = img2label_paths(self.im_files) # labels
@@ -490,8 +494,8 @@ def __init__(self,
# Display cache
nf, nm, ne, nc, n = cache.pop('results') # found, missing, empty, corrupt, total
if exists and LOCAL_RANK in {-1, 0}:
- d = f"Scanning '{cache_path}' images and labels... {nf} found, {nm} missing, {ne} empty, {nc} corrupt"
- tqdm(None, desc=prefix + d, total=n, initial=n, bar_format=BAR_FORMAT) # display cache results
+ d = f"Scanning {cache_path}... {nf} images, {nm + ne} backgrounds, {nc} corrupt"
+ tqdm(None, desc=prefix + d, total=n, initial=n, bar_format=TQDM_BAR_FORMAT) # display cache results
if cache['msgs']:
LOGGER.info('\n'.join(cache['msgs'])) # display warnings
assert nf > 0 or not augment, f'{prefix}No labels found in {cache_path}, can not start training. {HELP_URL}'
@@ -505,7 +509,19 @@ def __init__(self,
self.shapes = np.array(shapes)
self.im_files = list(cache.keys()) # update
self.label_files = img2label_paths(cache.keys()) # update
- n = len(shapes) # number of images
+
+ # Filter images
+ if min_items:
+ include = np.array([len(x) >= min_items for x in self.labels]).nonzero()[0].astype(int)
+ LOGGER.info(f'{prefix}{n - len(include)}/{n} images filtered from dataset')
+ self.im_files = [self.im_files[i] for i in include]
+ self.label_files = [self.label_files[i] for i in include]
+ self.labels = [self.labels[i] for i in include]
+ self.segments = [self.segments[i] for i in include]
+ self.shapes = self.shapes[include] # wh
+
+ # Create indices
+ n = len(self.shapes) # number of images
bi = np.floor(np.arange(n) / batch_size).astype(int) # batch index
nb = bi[-1] + 1 # number of batches
self.batch = bi # batch index of image
@@ -523,8 +539,6 @@ def __init__(self,
self.segments[i] = segment[j]
if single_cls: # single-class training, merge all classes into 0
self.labels[i][:, 0] = 0
- if segment:
- self.segments[i][:, 0] = 0
# Rectangular Training
if self.rect:
@@ -551,34 +565,53 @@ def __init__(self,
self.batch_shapes = np.ceil(np.array(shapes) * img_size / stride + pad).astype(int) * stride
- # Cache images into RAM/disk for faster training (WARNING: large datasets may exceed system resources)
+ # Cache images into RAM/disk for faster training
+ if cache_images == 'ram' and not self.check_cache_ram(prefix=prefix):
+ cache_images = False
self.ims = [None] * n
self.npy_files = [Path(f).with_suffix('.npy') for f in self.im_files]
if cache_images:
- gb = 0 # Gigabytes of cached images
+ b, gb = 0, 1 << 30 # bytes of cached images, bytes per gigabytes
self.im_hw0, self.im_hw = [None] * n, [None] * n
fcn = self.cache_images_to_disk if cache_images == 'disk' else self.load_image
results = ThreadPool(NUM_THREADS).imap(fcn, range(n))
- pbar = tqdm(enumerate(results), total=n, bar_format=BAR_FORMAT, disable=LOCAL_RANK > 0)
+ pbar = tqdm(enumerate(results), total=n, bar_format=TQDM_BAR_FORMAT, disable=LOCAL_RANK > 0)
for i, x in pbar:
if cache_images == 'disk':
- gb += self.npy_files[i].stat().st_size
+ b += self.npy_files[i].stat().st_size
else: # 'ram'
self.ims[i], self.im_hw0[i], self.im_hw[i] = x # im, hw_orig, hw_resized = load_image(self, i)
- gb += self.ims[i].nbytes
- pbar.desc = f'{prefix}Caching images ({gb / 1E9:.1f}GB {cache_images})'
+ b += self.ims[i].nbytes
+ pbar.desc = f'{prefix}Caching images ({b / gb:.1f}GB {cache_images})'
pbar.close()
+ def check_cache_ram(self, safety_margin=0.1, prefix=''):
+ # Check image caching requirements vs available memory
+ b, gb = 0, 1 << 30 # bytes of cached images, bytes per gigabytes
+ n = min(self.n, 30) # extrapolate from 30 random images
+ for _ in range(n):
+ im = cv2.imread(random.choice(self.im_files)) # sample image
+ ratio = self.img_size / max(im.shape[0], im.shape[1]) # max(h, w) # ratio
+ b += im.nbytes * ratio ** 2
+ mem_required = b * self.n / n # GB required to cache dataset into RAM
+ mem = psutil.virtual_memory()
+ cache = mem_required * (1 + safety_margin) < mem.available # to cache or not to cache, that is the question
+ if not cache:
+ LOGGER.info(f"{prefix}{mem_required / gb:.1f}GB RAM required, "
+ f"{mem.available / gb:.1f}/{mem.total / gb:.1f}GB available, "
+ f"{'caching images β
' if cache else 'not caching images β οΈ'}")
+ return cache
+
def cache_labels(self, path=Path('./labels.cache'), prefix=''):
# Cache dataset labels, check images and read shapes
x = {} # dict
nm, nf, ne, nc, msgs = 0, 0, 0, 0, [] # number missing, found, empty, corrupt, messages
- desc = f"{prefix}Scanning '{path.parent / path.stem}' images and labels..."
+ desc = f"{prefix}Scanning {path.parent / path.stem}..."
with Pool(NUM_THREADS) as pool:
pbar = tqdm(pool.imap(verify_image_label, zip(self.im_files, self.label_files, repeat(prefix))),
desc=desc,
total=len(self.im_files),
- bar_format=BAR_FORMAT)
+ bar_format=TQDM_BAR_FORMAT)
for im_file, lb, shape, segments, nm_f, nf_f, ne_f, nc_f, msg in pbar:
nm += nm_f
nf += nf_f
@@ -588,7 +621,7 @@ def cache_labels(self, path=Path('./labels.cache'), prefix=''):
x[im_file] = [lb, shape, segments]
if msg:
msgs.append(msg)
- pbar.desc = f"{desc}{nf} found, {nm} missing, {ne} empty, {nc} corrupt"
+ pbar.desc = f"{desc} {nf} images, {nm + ne} backgrounds, {nc} corrupt"
pbar.close()
if msgs:
@@ -835,6 +868,7 @@ def load_mosaic9(self, index):
# img9, labels9 = replicate(img9, labels9) # replicate
# Augment
+ img9, labels9, segments9 = copy_paste(img9, labels9, segments9, p=self.hyp['copy_paste'])
img9, labels9 = random_perspective(img9,
labels9,
segments9,
diff --git a/yolov5/utils/downloads.py b/yolov5/utils/downloads.py
index bab6dcd..16745a7 100644
--- a/yolov5/utils/downloads.py
+++ b/yolov5/utils/downloads.py
@@ -59,14 +59,14 @@ def safe_download(file, url, url2=None, min_bytes=1E0, error_msg=''):
LOGGER.info('')
-def attempt_download(file, repo='ultralytics/yolov5', release='v6.2'):
- # Attempt file download from GitHub release assets if not found locally. release = 'latest', 'v6.2', etc.
+def attempt_download(file, repo='ultralytics/yolov5', release='v7.0'):
+ # Attempt file download from GitHub release assets if not found locally. release = 'latest', 'v7.0', etc.
from yolov5.utils.general import LOGGER
def github_assets(repository, version='latest'):
- # Return GitHub repo tag (i.e. 'v6.2') and assets (i.e. ['yolov5s.pt', 'yolov5m.pt', ...])
+ # Return GitHub repo tag (i.e. 'v7.0') and assets (i.e. ['yolov5s.pt', 'yolov5m.pt', ...])
if version != 'latest':
- version = f'tags/{version}' # i.e. tags/v6.2
+ version = f'tags/{version}' # i.e. tags/v7.0
response = requests.get(f'https://api.github.com/repos/{repository}/releases/{version}').json() # github api
return response['tag_name'], [x['name'] for x in response['assets']] # tag, assets
diff --git a/yolov5/utils/general.py b/yolov5/utils/general.py
index d0629e3..0695da5 100644
--- a/yolov5/utils/general.py
+++ b/yolov5/utils/general.py
@@ -7,12 +7,12 @@
import glob
import inspect
import logging
+import logging.config
import math
import os
import platform
import random
import re
-import shutil
import signal
import sys
import time
@@ -23,8 +23,9 @@
from multiprocessing.pool import ThreadPool
from pathlib import Path
from subprocess import check_output
+from tarfile import is_tarfile
from typing import Optional
-from zipfile import ZipFile
+from zipfile import ZipFile, is_zipfile
import cv2
import IPython
@@ -48,6 +49,7 @@
DATASETS_DIR = Path(os.getenv('YOLOv5_DATASETS_DIR', ROOT.parent / 'datasets')) # global datasets directory
AUTOINSTALL = str(os.getenv('YOLOv5_AUTOINSTALL', True)).lower() == 'true' # global auto-install mode
VERBOSE = str(os.getenv('YOLOv5_VERBOSE', True)).lower() == 'true' # global verbose mode
+TQDM_BAR_FORMAT = '{l_bar}{bar:10}{r_bar}' # tqdm bar format
FONT = 'Arial.ttf' # https://ultralytics.com/assets/Arial.ttf
torch.set_printoptions(linewidth=320, precision=5, profile='long')
@@ -71,7 +73,7 @@ def is_chinese(s='δΊΊε·₯ζΊθ½'):
def is_colab():
# Is environment a Google Colab instance?
- return 'COLAB_GPU' in os.environ
+ return 'google.colab' in sys.modules
def is_notebook():
@@ -110,23 +112,33 @@ def is_writeable(dir, test=False):
return False
-def set_logging(name=None, verbose=VERBOSE):
- # Sets level and returns logger
- if is_kaggle() or is_colab():
- for h in logging.root.handlers:
- logging.root.removeHandler(h) # remove all handlers associated with the root logger object
- rank = int(os.getenv('RANK', -1)) # rank in world for Multi-GPU trainings
- level = logging.INFO if verbose and rank in {-1, 0} else logging.ERROR
- log = logging.getLogger(name)
- log.setLevel(level)
- handler = logging.StreamHandler()
- handler.setFormatter(logging.Formatter("%(message)s"))
- handler.setLevel(level)
- log.addHandler(handler)
+LOGGING_NAME = "yolov5"
-set_logging() # run before defining LOGGER
-LOGGER = logging.getLogger("yolov5") # define globally (used in train.py, val.py, detect.py, etc.)
+def set_logging(name=LOGGING_NAME, verbose=True):
+ # sets up logging for the given name
+ rank = int(os.getenv('RANK', -1)) # rank in world for Multi-GPU trainings
+ level = logging.INFO if verbose and rank in {-1, 0} else logging.ERROR
+ logging.config.dictConfig({
+ "version": 1,
+ "disable_existing_loggers": False,
+ "formatters": {
+ name: {
+ "format": "%(message)s"}},
+ "handlers": {
+ name: {
+ "class": "logging.StreamHandler",
+ "formatter": name,
+ "level": level,}},
+ "loggers": {
+ name: {
+ "level": level,
+ "handlers": [name],
+ "propagate": False,}}})
+
+
+set_logging(LOGGING_NAME) # run before defining LOGGER
+LOGGER = logging.getLogger(LOGGING_NAME) # define globally (used in train.py, val.py, detect.py, etc.)
if platform.system() == 'Windows':
for fn in LOGGER.info, LOGGER.warning:
setattr(LOGGER, fn.__name__, lambda x: fn(emojis(x))) # emoji safe logging
@@ -282,11 +294,16 @@ def file_size(path):
def check_online():
# Check internet connectivity
import socket
- try:
- socket.create_connection(("1.1.1.1", 443), 5) # check host accessibility
- return True
- except OSError:
- return False
+
+ def run_once():
+ # Check once
+ try:
+ socket.create_connection(("1.1.1.1", 443), 5) # check host accessibility
+ return True
+ except OSError:
+ return False
+
+ return run_once() or run_once() # check twice to increase robustness to intermittent connectivity issues
def git_describe(path=ROOT): # path must be a directory
@@ -326,6 +343,24 @@ def check_git_status(repo='ultralytics/yolov5', branch='master'):
LOGGER.info(s)
+@WorkingDirectory(ROOT)
+def check_git_info(path='.'):
+ # YOLOv5 git info check, return {remote, branch, commit}
+ check_requirements('gitpython')
+ import git
+ try:
+ repo = git.Repo(path)
+ remote = repo.remotes.origin.url.replace('.git', '') # i.e. 'https://github.com/ultralytics/yolov5'
+ commit = repo.head.commit.hexsha # i.e. '3134699c73af83aac2a481435550b968d5792c0d'
+ try:
+ branch = repo.active_branch.name # i.e. 'main'
+ except TypeError: # not on any branch
+ branch = None # i.e. 'detached HEAD' state
+ return {'remote': remote, 'branch': branch, 'commit': commit}
+ except git.exc.InvalidGitRepositoryError: # path is not a git dir
+ return {'remote': None, 'branch': None, 'commit': None}
+
+
def check_python(minimum='3.7.0'):
# Check current python version vs. required python version
check_version(platform.python_version(), minimum, name='Python ', hard=True)
@@ -368,7 +403,7 @@ def check_requirements(requirements=ROOT / 'requirements.txt', exclude=(), insta
if s and install and AUTOINSTALL: # check environment variable
LOGGER.info(f"{prefix} YOLOv5 requirement{'s' * (n > 1)} {s}not found, attempting AutoUpdate...")
try:
- assert check_online(), "AutoUpdate skipped (offline)"
+ # assert check_online(), "AutoUpdate skipped (offline)"
LOGGER.info(check_output(f'pip install {s} {cmds}', shell=True).decode())
source = file if 'file' in locals() else requirements
s = f"{prefix} {n} package{'s' * (n > 1)} updated per {source}\n" \
@@ -465,7 +500,7 @@ def check_dataset(data, autodownload=True):
# Download (optional)
extract_dir = ''
- if isinstance(data, (str, Path)) and str(data).endswith('.zip'): # i.e. gs://bucket/dir/coco128.zip
+ if isinstance(data, (str, Path)) and (is_zipfile(data) or is_tarfile(data)):
download(data, dir=f'{DATASETS_DIR}/{Path(data).stem}', unzip=True, delete=False, curl=False, threads=1)
data = next((DATASETS_DIR / Path(data).stem).rglob('*.yaml'))
extract_dir, autodownload = data.parent, False
@@ -476,9 +511,10 @@ def check_dataset(data, autodownload=True):
# Checks
for k in 'train', 'val', 'names':
- assert k in data, f"data.yaml '{k}:' field missing β"
+ assert k in data, emojis(f"data.yaml '{k}:' field missing β")
if isinstance(data['names'], (list, tuple)): # old array format
data['names'] = dict(enumerate(data['names'])) # convert to dict
+ assert all(isinstance(k, int) for k in data['names'].keys()), 'data.yaml names keys must be integers, i.e. 2: car'
data['nc'] = len(data['names'])
# Resolve paths
@@ -607,11 +643,11 @@ def download_one(url, dir):
else:
LOGGER.warning(f'β Failed to download {url}...')
- if unzip and success and f.suffix in ('.zip', '.tar', '.gz'):
+ if unzip and success and (f.suffix == '.gz' or is_zipfile(f) or is_tarfile(f)):
LOGGER.info(f'Unzipping {f}...')
- if f.suffix == '.zip':
+ if is_zipfile(f):
unzip_file(f, dir) # unzip
- elif f.suffix == '.tar':
+ elif is_tarfile(f):
os.system(f'tar xf {f} --directory {f.parent}') # unzip
elif f.suffix == '.gz':
os.system(f'tar xfz {f} --directory {f.parent}') # unzip
@@ -804,7 +840,7 @@ def scale_boxes(img1_shape, boxes, img0_shape, ratio_pad=None):
return boxes
-def scale_segments(img1_shape, segments, img0_shape, ratio_pad=None):
+def scale_segments(img1_shape, segments, img0_shape, ratio_pad=None, normalize=False):
# Rescale coords (xyxy) from img1_shape to img0_shape
if ratio_pad is None: # calculate from img0_shape
gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1]) # gain = old / new
@@ -817,6 +853,9 @@ def scale_segments(img1_shape, segments, img0_shape, ratio_pad=None):
segments[:, 1] -= pad[1] # y padding
segments /= gain
clip_segments(segments, img0_shape)
+ if normalize:
+ segments[:, 0] /= img0_shape[1] # width
+ segments[:, 1] /= img0_shape[0] # height
return segments
@@ -832,14 +871,14 @@ def clip_boxes(boxes, shape):
boxes[:, [1, 3]] = boxes[:, [1, 3]].clip(0, shape[0]) # y1, y2
-def clip_segments(boxes, shape):
+def clip_segments(segments, shape):
# Clip segments (xy1,xy2,...) to image shape (height, width)
- if isinstance(boxes, torch.Tensor): # faster individually
- boxes[:, 0].clamp_(0, shape[1]) # x
- boxes[:, 1].clamp_(0, shape[0]) # y
+ if isinstance(segments, torch.Tensor): # faster individually
+ segments[:, 0].clamp_(0, shape[1]) # x
+ segments[:, 1].clamp_(0, shape[0]) # y
else: # np.array (faster grouped)
- boxes[:, 0] = boxes[:, 0].clip(0, shape[1]) # x
- boxes[:, 1] = boxes[:, 1].clip(0, shape[0]) # y
+ segments[:, 0] = segments[:, 0].clip(0, shape[1]) # x
+ segments[:, 1] = segments[:, 1].clip(0, shape[0]) # y
def non_max_suppression(
@@ -997,7 +1036,7 @@ def print_mutation(keys, results, hyp, save_dir, bucket, prefix=colorstr('evolve
# Save yaml
with open(evolve_yaml, 'w') as f:
- data = pd.read_csv(evolve_csv)
+ data = pd.read_csv(evolve_csv, skipinitialspace=True)
data = data.rename(columns=lambda x: x.strip()) # strip keys
i = np.argmax(fitness(data.values[:, :4])) #
generations = len(data)
@@ -1076,7 +1115,7 @@ def increment_path(path, exist_ok=False, sep='', mkdir=False):
return path
-# OpenCV Chinese-friendly functions ------------------------------------------------------------------------------------
+# OpenCV Multilanguage-friendly functions ------------------------------------------------------------------------------------
imshow_ = cv2.imshow # copy to avoid recursion errors
@@ -1099,4 +1138,3 @@ def imshow(path, im):
cv2.imread, cv2.imwrite, cv2.imshow = imread, imwrite, imshow # redefine
# Variables ------------------------------------------------------------------------------------------------------------
-NCOLS = 0 if is_docker() else shutil.get_terminal_size().columns # terminal window size for tqdm
diff --git a/yolov5/utils/loggers/__init__.py b/yolov5/utils/loggers/__init__.py
index 1626c4c..ed1354e 100644
--- a/yolov5/utils/loggers/__init__.py
+++ b/yolov5/utils/loggers/__init__.py
@@ -10,14 +10,15 @@
import pkg_resources as pkg
import torch
from torch.utils.tensorboard import SummaryWriter
-from yolov5.utils.general import colorstr, cv2
+
+from yolov5.utils.general import LOGGER, colorstr, cv2
from yolov5.utils.loggers.clearml.clearml_utils import ClearmlLogger
from yolov5.utils.loggers.neptune.neptune_utils import NeptuneLogger
from yolov5.utils.loggers.wandb.wandb_utils import WandbLogger
from yolov5.utils.plots import plot_images, plot_labels, plot_results
from yolov5.utils.torch_utils import de_parallel
-LOGGERS = ('csv', 'tb', 'wandb', 'neptune', 'clearml', 'comet') # *.csv, TensorBoard, Weights & Biases, ClearML
+LOGGERS = ('csv', 'tb', 'wandb', 'clearml', 'comet') # *.csv, TensorBoard, Weights & Biases, ClearML
RANK = int(os.getenv('RANK', -1))
try:
@@ -46,8 +47,6 @@
except (ImportError, AssertionError):
clearml = None
-
-
try:
if RANK not in [0, -1]:
comet_ml = None
@@ -130,10 +129,6 @@ def __init__(self, save_dir=None, weights=None, opt=None, hyp=None, logger=None,
run_id = torch.load(self.weights).get('wandb_id') if self.opt.resume and not wandb_artifact_resume else None
self.opt.hyp = self.hyp # add hyperparameters
self.wandb = WandbLogger(self.opt, run_id)
- # temp warn. because nested artifacts not supported after 0.12.10
- if pkg.parse_version(wandb.__version__) >= pkg.parse_version('0.12.11'):
- s = "YOLOv5 temporarily requires wandb version 0.12.10 or below. Some features may not work as expected."
- self.logger.warning(s)
else:
self.wandb = None
@@ -145,7 +140,14 @@ def __init__(self, save_dir=None, weights=None, opt=None, hyp=None, logger=None,
# ClearML
if clearml and 'clearml' in self.include:
- self.clearml = ClearmlLogger(self.opt, self.hyp)
+ try:
+ self.clearml = ClearmlLogger(self.opt, self.hyp)
+ except Exception:
+ self.clearml = None
+ prefix = colorstr('ClearML: ')
+ LOGGER.warning(f'{prefix}WARNING β οΈ ClearML is installed but not configured, skipping ClearML logging.'
+ f' See https://github.com/ultralytics/yolov5/tree/master/utils/loggers/clearml#readme')
+
else:
self.clearml = None
@@ -472,7 +474,7 @@ def log_tensorboard_graph(tb, model, imgsz=(640, 640)):
warnings.simplefilter('ignore') # suppress jit trace warning
tb.add_graph(torch.jit.trace(de_parallel(model), im, strict=False), [])
except Exception as e:
- print(f'WARNING: TensorBoard graph visualization failure {e}')
+ LOGGER.warning(f'WARNING β οΈ TensorBoard graph visualization failure {e}')
def web_project_name(project):
diff --git a/yolov5/utils/loggers/clearml/README.md b/yolov5/utils/loggers/clearml/README.md
index 64eef6b..3cf4c26 100644
--- a/yolov5/utils/loggers/clearml/README.md
+++ b/yolov5/utils/loggers/clearml/README.md
@@ -54,15 +54,23 @@ That's it! You're done π
To enable ClearML experiment tracking, simply install the ClearML pip package.
```bash
-pip install clearml
+pip install clearml>=1.2.0
```
-This will enable integration with the YOLOv5 training script. Every training run from now on, will be captured and stored by the ClearML experiment manager. If you want to change the `project_name` or `task_name`, head over to our custom logger, where you can change it: `utils/loggers/clearml/clearml_utils.py`
+This will enable integration with the YOLOv5 training script. Every training run from now on, will be captured and stored by the ClearML experiment manager.
+
+If you want to change the `project_name` or `task_name`, use the `--project` and `--name` arguments of the `train.py` script, by default the project will be called `YOLOv5` and the task `Training`.
+PLEASE NOTE: ClearML uses `/` as a delimter for subprojects, so be careful when using `/` in your project name!
```bash
python train.py --img 640 --batch 16 --epochs 3 --data coco128.yaml --weights yolov5s.pt --cache
```
+or with custom project and task name:
+```bash
+python train.py --project my_project --name my_training --img 640 --batch 16 --epochs 3 --data coco128.yaml --weights yolov5s.pt --cache
+```
+
This will capture:
- Source code + uncommitted changes
- Installed packages
diff --git a/yolov5/utils/loggers/clearml/clearml_utils.py b/yolov5/utils/loggers/clearml/clearml_utils.py
index 07928e1..8ff90be 100644
--- a/yolov5/utils/loggers/clearml/clearml_utils.py
+++ b/yolov5/utils/loggers/clearml/clearml_utils.py
@@ -85,10 +85,11 @@ def __init__(self, opt, hyp):
self.data_dict = None
if self.clearml:
self.task = Task.init(
- project_name='YOLOv5',
- task_name='training',
+ project_name=opt.project if opt.project != 'runs/train' else 'YOLOv5',
+ task_name=opt.name if opt.name != 'exp' else 'Training',
tags=['YOLOv5'],
output_uri=True,
+ reuse_last_task_id=opt.exist_ok,
auto_connect_frameworks={'pytorch': False}
# We disconnect pytorch auto-detection, because we added manual model save points in the code
)
@@ -96,6 +97,12 @@ def __init__(self, opt, hyp):
# Only the hyperparameters coming from the yaml config file
# will have to be added manually!
self.task.connect(hyp, name='Hyperparameters')
+ self.task.connect(opt, name='Args')
+
+ # Make sure the code is easily remotely runnable by setting the docker image to use by the remote agent
+ self.task.set_base_docker("ultralytics/yolov5:latest",
+ docker_arguments='--ipc=host -e="CLEARML_AGENT_SKIP_PYTHON_ENV_INSTALL=1"',
+ docker_setup_bash_script='pip install clearml')
# Get ClearML Dataset Version if requested
if opt.data.startswith('clearml://'):
diff --git a/yolov5/utils/loggers/wandb/wandb_utils.py b/yolov5/utils/loggers/wandb/wandb_utils.py
index 58f3f4a..35485f4 100644
--- a/yolov5/utils/loggers/wandb/wandb_utils.py
+++ b/yolov5/utils/loggers/wandb/wandb_utils.py
@@ -452,7 +452,7 @@ def create_dataset_table(self, dataset: LoadImagesAndLabels, class_to_id: Dict[i
def log_training_progress(self, predn, path, names):
"""
- Build evaluation Table. Uses reference from yolov5.validation dataset table.
+ Build evaluation Table. Uses reference from validation dataset table.
arguments:
predn (list): list of predictions in the native space in the format - [xmin, ymin, xmax, ymax, confidence, class]
diff --git a/yolov5/utils/metrics.py b/yolov5/utils/metrics.py
index b93c430..52c7919 100644
--- a/yolov5/utils/metrics.py
+++ b/yolov5/utils/metrics.py
@@ -177,9 +177,6 @@ def process_batch(self, detections, labels):
if not any(m1 == i):
self.matrix[dc, self.nc] += 1 # predicted background
- def matrix(self):
- return self.matrix
-
def tp_fp(self):
tp = self.matrix.diagonal() # true positives
fp = self.matrix.sum(1) - tp # false positives
@@ -227,19 +224,19 @@ def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7
# Get the coordinates of bounding boxes
if xywh: # transform from xywh to xyxy
- (x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, 1), box2.chunk(4, 1)
+ (x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1)
w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2
b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_
b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_
else: # x1, y1, x2, y2 = box1
- b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, 1)
- b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, 1)
- w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1
- w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1
+ b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1)
+ b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1)
+ w1, h1 = b1_x2 - b1_x1, (b1_y2 - b1_y1).clamp(eps)
+ w2, h2 = b2_x2 - b2_x1, (b2_y2 - b2_y1).clamp(eps)
# Intersection area
- inter = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \
- (torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0)
+ inter = (b1_x2.minimum(b2_x2) - b1_x1.maximum(b2_x1)).clamp(0) * \
+ (b1_y2.minimum(b2_y2) - b1_y1.maximum(b2_y1)).clamp(0)
# Union Area
union = w1 * h1 + w2 * h2 - inter + eps
@@ -247,13 +244,13 @@ def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7
# IoU
iou = inter / union
if CIoU or DIoU or GIoU:
- cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1) # convex (smallest enclosing box) width
- ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1) # convex height
+ cw = b1_x2.maximum(b2_x2) - b1_x1.minimum(b2_x1) # convex (smallest enclosing box) width
+ ch = b1_y2.maximum(b2_y2) - b1_y1.minimum(b2_y1) # convex height
if CIoU or DIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
c2 = cw ** 2 + ch ** 2 + eps # convex diagonal squared
rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4 # center dist ** 2
if 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 + eps)) - torch.atan(w1 / (h1 + eps)), 2)
+ v = (4 / math.pi ** 2) * (torch.atan(w2 / h2) - torch.atan(w1 / h1)).pow(2)
with torch.no_grad():
alpha = v / (v - iou + (1 + eps))
return iou - (rho2 / c2 + v * alpha) # CIoU
@@ -263,11 +260,6 @@ def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7
return iou # IoU
-def box_area(box):
- # box = xyxy(4,n)
- return (box[2] - box[0]) * (box[3] - box[1])
-
-
def box_iou(box1, box2, eps=1e-7):
# https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py
"""
@@ -282,11 +274,11 @@ def box_iou(box1, box2, eps=1e-7):
"""
# inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2)
- (a1, a2), (b1, b2) = box1[:, None].chunk(2, 2), box2.chunk(2, 1)
+ (a1, a2), (b1, b2) = box1.unsqueeze(1).chunk(2, 2), box2.unsqueeze(0).chunk(2, 2)
inter = (torch.min(a2, b2) - torch.max(a1, b1)).clamp(0).prod(2)
# IoU = inter / (area1 + area2 - inter)
- return inter / (box_area(box1.T)[:, None] + box_area(box2.T) - inter + eps)
+ return inter / ((a2 - a1).prod(2) + (b2 - b1).prod(2) - inter + eps)
def bbox_ioa(box1, box2, eps=1e-7):
diff --git a/yolov5/utils/plots.py b/yolov5/utils/plots.py
index 16cce4c..fffa5bc 100644
--- a/yolov5/utils/plots.py
+++ b/yolov5/utils/plots.py
@@ -114,7 +114,7 @@ def box_label(self, box, label='', color=(128, 128, 128), txt_color=(255, 255, 2
thickness=tf,
lineType=cv2.LINE_AA)
- def masks(self, masks, colors, im_gpu=None, alpha=0.5):
+ def masks(self, masks, colors, im_gpu, alpha=0.5, retina_masks=False):
"""Plot masks at once.
Args:
masks (tensor): predicted masks on cuda, shape: [n, h, w]
@@ -125,37 +125,21 @@ def masks(self, masks, colors, im_gpu=None, alpha=0.5):
if self.pil:
# convert to numpy first
self.im = np.asarray(self.im).copy()
- if im_gpu is None:
- # Add multiple masks of shape(h,w,n) with colors list([r,g,b], [r,g,b], ...)
- if len(masks) == 0:
- return
- if isinstance(masks, torch.Tensor):
- masks = torch.as_tensor(masks, dtype=torch.uint8)
- masks = masks.permute(1, 2, 0).contiguous()
- masks = masks.cpu().numpy()
- # masks = np.ascontiguousarray(masks.transpose(1, 2, 0))
- masks = scale_image(masks.shape[:2], masks, self.im.shape)
- masks = np.asarray(masks, dtype=np.float32)
- colors = np.asarray(colors, dtype=np.float32) # shape(n,3)
- s = masks.sum(2, keepdims=True).clip(0, 1) # add all masks together
- masks = (masks @ colors).clip(0, 255) # (h,w,n) @ (n,3) = (h,w,3)
- self.im[:] = masks * alpha + self.im * (1 - s * alpha)
- else:
- if len(masks) == 0:
- self.im[:] = im_gpu.permute(1, 2, 0).contiguous().cpu().numpy() * 255
- colors = torch.tensor(colors, device=im_gpu.device, dtype=torch.float32) / 255.0
- colors = colors[:, None, None] # shape(n,1,1,3)
- masks = masks.unsqueeze(3) # shape(n,h,w,1)
- masks_color = masks * (colors * alpha) # shape(n,h,w,3)
-
- inv_alph_masks = (1 - masks * alpha).cumprod(0) # shape(n,h,w,1)
- mcs = (masks_color * inv_alph_masks).sum(0) * 2 # mask color summand shape(n,h,w,3)
-
- im_gpu = im_gpu.flip(dims=[0]) # flip channel
- im_gpu = im_gpu.permute(1, 2, 0).contiguous() # shape(h,w,3)
- im_gpu = im_gpu * inv_alph_masks[-1] + mcs
- im_mask = (im_gpu * 255).byte().cpu().numpy()
- self.im[:] = scale_image(im_gpu.shape, im_mask, self.im.shape)
+ if len(masks) == 0:
+ self.im[:] = im_gpu.permute(1, 2, 0).contiguous().cpu().numpy() * 255
+ colors = torch.tensor(colors, device=im_gpu.device, dtype=torch.float32) / 255.0
+ colors = colors[:, None, None] # shape(n,1,1,3)
+ masks = masks.unsqueeze(3) # shape(n,h,w,1)
+ masks_color = masks * (colors * alpha) # shape(n,h,w,3)
+
+ inv_alph_masks = (1 - masks * alpha).cumprod(0) # shape(n,h,w,1)
+ mcs = (masks_color * inv_alph_masks).sum(0) * 2 # mask color summand shape(n,h,w,3)
+
+ im_gpu = im_gpu.flip(dims=[0]) # flip channel
+ im_gpu = im_gpu.permute(1, 2, 0).contiguous() # shape(h,w,3)
+ im_gpu = im_gpu * inv_alph_masks[-1] + mcs
+ im_mask = (im_gpu * 255).byte().cpu().numpy()
+ self.im[:] = im_mask if retina_masks else scale_image(im_gpu.shape, im_mask, self.im.shape)
if self.pil:
# convert im back to PIL and update draw
self.fromarray(self.im)
diff --git a/yolov5/utils/segment/dataloaders.py b/yolov5/utils/segment/dataloaders.py
index a63d6ec..9de6f0f 100644
--- a/yolov5/utils/segment/dataloaders.py
+++ b/yolov5/utils/segment/dataloaders.py
@@ -93,12 +93,13 @@ def __init__(
single_cls=False,
stride=32,
pad=0,
+ min_items=0,
prefix="",
downsample_ratio=1,
overlap=False,
):
super().__init__(path, img_size, batch_size, augment, hyp, rect, image_weights, cache_images, single_cls,
- stride, pad, prefix)
+ stride, pad, min_items, prefix)
self.downsample_ratio = downsample_ratio
self.overlap = overlap
diff --git a/yolov5/utils/segment/general.py b/yolov5/utils/segment/general.py
index b526333..9da8945 100644
--- a/yolov5/utils/segment/general.py
+++ b/yolov5/utils/segment/general.py
@@ -25,10 +25,10 @@ def crop_mask(masks, boxes):
def process_mask_upsample(protos, masks_in, bboxes, shape):
"""
Crop after upsample.
- proto_out: [mask_dim, mask_h, mask_w]
- out_masks: [n, mask_dim], n is number of masks after nms
+ protos: [mask_dim, mask_h, mask_w]
+ masks_in: [n, mask_dim], n is number of masks after nms
bboxes: [n, 4], n is number of masks after nms
- shape:input_image_size, (h, w)
+ shape: input_image_size, (h, w)
return: h, w, n
"""
@@ -67,6 +67,29 @@ def process_mask(protos, masks_in, bboxes, shape, upsample=False):
return masks.gt_(0.5)
+def process_mask_native(protos, masks_in, bboxes, shape):
+ """
+ Crop after upsample.
+ protos: [mask_dim, mask_h, mask_w]
+ masks_in: [n, mask_dim], n is number of masks after nms
+ bboxes: [n, 4], n is number of masks after nms
+ shape: input_image_size, (h, w)
+
+ return: h, w, n
+ """
+ c, mh, mw = protos.shape # CHW
+ masks = (masks_in @ protos.float().view(c, -1)).sigmoid().view(-1, mh, mw)
+ gain = min(mh / shape[0], mw / shape[1]) # gain = old / new
+ pad = (mw - shape[1] * gain) / 2, (mh - shape[0] * gain) / 2 # wh padding
+ top, left = int(pad[1]), int(pad[0]) # y, x
+ bottom, right = int(mh - pad[1]), int(mw - pad[0])
+ masks = masks[:, top:bottom, left:right]
+
+ masks = F.interpolate(masks[None], shape, mode='bilinear', align_corners=False)[0] # CHW
+ masks = crop_mask(masks, bboxes) # CHW
+ return masks.gt_(0.5)
+
+
def scale_image(im1_shape, masks, im0_shape, ratio_pad=None):
"""
img1_shape: model input shape, [h, w]
diff --git a/yolov5/utils/torch_utils.py b/yolov5/utils/torch_utils.py
index 516b6bf..41c2b1d 100644
--- a/yolov5/utils/torch_utils.py
+++ b/yolov5/utils/torch_utils.py
@@ -32,6 +32,7 @@
# Suppress PyTorch warnings
warnings.filterwarnings('ignore', message='User provided device_type of \'cuda\', but CUDA is not available. Disabling')
+warnings.filterwarnings('ignore', category=UserWarning)
def smart_inference_mode(torch_1_9=check_version(torch.__version__, '1.9.0')):
@@ -81,7 +82,7 @@ def reshape_classifier_output(model, n=1000):
elif nn.Conv2d in types:
i = types.index(nn.Conv2d) # nn.Conv2d index
if m[i].out_channels != n:
- m[i] = nn.Conv2d(m[i].in_channels, n, m[i].kernel_size, m[i].stride, bias=m[i].bias)
+ m[i] = nn.Conv2d(m[i].in_channels, n, m[i].kernel_size, m[i].stride, bias=m[i].bias is not None)
@contextmanager
diff --git a/yolov5/val.py b/yolov5/val.py
index aca6f78..e8c6dd7 100644
--- a/yolov5/val.py
+++ b/yolov5/val.py
@@ -39,9 +39,9 @@
from yolov5.models.common import DetectMultiBackend
from yolov5.utils.callbacks import Callbacks
from yolov5.utils.dataloaders import create_dataloader
-from yolov5.utils.general import (LOGGER, Profile, check_dataset, check_img_size, check_requirements, check_yaml,
- coco80_to_coco91_class, colorstr, increment_path, non_max_suppression, print_args,
- scale_boxes, xywh2xyxy, xyxy2xywh)
+from yolov5.utils.general import (LOGGER, TQDM_BAR_FORMAT, Profile, check_dataset, check_img_size, check_requirements,
+ check_yaml, coco80_to_coco91_class, colorstr, increment_path, non_max_suppression,
+ print_args, scale_boxes, xywh2xyxy, xyxy2xywh)
from yolov5.utils.metrics import ConfusionMatrix, ap_per_class, box_iou
from yolov5.utils.plots import output_to_target, plot_images, plot_val_study
from yolov5.utils.torch_utils import select_device, smart_inference_mode
@@ -146,7 +146,7 @@ def run(
model.half() if half else model.float()
else: # called directly
device = select_device(device, batch_size=batch_size)
- half &= device.type != 'cpu' # half precision only supported on CUDA, dont remove!
+ half &= device.type != 'cpu' # half precision only supported on CUDA, dont remove!
# Directories
save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) # increment run
@@ -207,7 +207,7 @@ def run(
loss = torch.zeros(3, device=device)
jdict, stats, ap, ap_class = [], [], [], []
callbacks.run('on_val_start')
- pbar = tqdm(dataloader, desc=s, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}') # progress bar
+ pbar = tqdm(dataloader, desc=s, bar_format=TQDM_BAR_FORMAT) # progress bar
for batch_i, (im, targets, paths, shapes) in enumerate(pbar):
callbacks.run('on_val_batch_start')
with dt[0]:
@@ -329,8 +329,8 @@ def run(
# Save JSON
if save_json and len(jdict):
w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else '' # weights
- anno_json = str(Path(data.get('path', '../coco')) / 'annotations/instances_val2017.json') # annotations json
- pred_json = str(save_dir / f"{w}_predictions.json") # predictions json
+ anno_json = str(Path('../datasets/coco/annotations/instances_val2017.json')) # annotations
+ pred_json = str(save_dir / f"{w}_predictions.json") # predictions
LOGGER.info(f'\nEvaluating pycocotools mAP... saving {pred_json}...')
with open(pred_json, 'w') as f:
json.dump(jdict, f)
@@ -411,7 +411,7 @@ def main():
else:
weights = opt.weights if isinstance(opt.weights, list) else [opt.weights]
- opt.half = True # FP16 for fastest results
+ opt.half = torch.cuda.is_available() and opt.device != 'cpu' # FP16 for fastest results
if opt.task == 'speed': # speed benchmarks
# python val.py --task speed --data coco.yaml --batch 1 --weights yolov5n.pt yolov5s.pt...
opt.conf_thres, opt.iou_thres, opt.save_json = 0.25, 0.45, False
@@ -430,6 +430,8 @@ def main():
np.savetxt(f, y, fmt='%10.4g') # save
os.system('zip -r study.zip study_*.txt')
plot_val_study(x=x) # plot
+ else:
+ raise NotImplementedError(f'--task {opt.task} not in ("train", "val", "test", "speed", "study")')
if __name__ == "__main__":