Upload 10 files
#1
by
wangfangyuan
- opened
- coco.yaml +19 -1
- general_json2yolo.py +8 -3
- onnx_eval.py +45 -10
- onnx_inference.py +17 -5
- utils.py +349 -66
- yolov5s_qat.onnx +2 -2
coco.yaml
CHANGED
|
@@ -25,4 +25,22 @@ names: ['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 't
|
|
| 25 |
'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
|
| 26 |
'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
|
| 27 |
'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear',
|
| 28 |
-
'hair drier', 'toothbrush'] # class names
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
|
| 26 |
'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
|
| 27 |
'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear',
|
| 28 |
+
'hair drier', 'toothbrush'] # class names
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# Download script/URL (optional)
|
| 32 |
+
download: |
|
| 33 |
+
from utils.general import download, Path
|
| 34 |
+
|
| 35 |
+
# Download labels
|
| 36 |
+
segments = False # segment or box labels
|
| 37 |
+
dir = Path(yaml['path']) # dataset root dir
|
| 38 |
+
url = 'https://github.com/ultralytics/yolov5/releases/download/v1.0/'
|
| 39 |
+
urls = [url + ('coco2017labels-segments.zip' if segments else 'coco2017labels.zip')] # labels
|
| 40 |
+
download(urls, dir=dir.parent)
|
| 41 |
+
|
| 42 |
+
# Download data
|
| 43 |
+
urls = ['http://images.cocodataset.org/zips/train2017.zip', # 19G, 118k images
|
| 44 |
+
'http://images.cocodataset.org/zips/val2017.zip', # 1G, 5k images
|
| 45 |
+
'http://images.cocodataset.org/zips/test2017.zip'] # 7G, 41k images (optional)
|
| 46 |
+
download(urls, dir=dir / 'images', threads=3)
|
general_json2yolo.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
|
|
| 1 |
import json
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from collections import defaultdict
|
| 3 |
import sys
|
| 4 |
import pathlib
|
| 5 |
-
import numpy as np
|
| 6 |
-
from tqdm import tqdm
|
| 7 |
CURRENT_DIR = pathlib.Path(__file__).parent
|
| 8 |
sys.path.append(str(CURRENT_DIR))
|
| 9 |
from utils import *
|
|
@@ -15,7 +18,7 @@ def convert_coco_json(json_dir='../coco/annotations/', use_segments=False, cls91
|
|
| 15 |
|
| 16 |
# Import json
|
| 17 |
for json_file in sorted(Path(json_dir).resolve().glob('*.json')):
|
| 18 |
-
if
|
| 19 |
continue
|
| 20 |
fn = Path(save_dir) / 'labels' / json_file.stem.replace('instances_', '') # folder name
|
| 21 |
fn.mkdir()
|
|
@@ -139,3 +142,5 @@ if __name__ == '__main__':
|
|
| 139 |
convert_coco_json('./datasets/coco/annotations', # directory with *.json
|
| 140 |
use_segments=True,
|
| 141 |
cls91to80=True)
|
|
|
|
|
|
|
|
|
| 1 |
+
import contextlib
|
| 2 |
import json
|
| 3 |
+
|
| 4 |
+
import cv2
|
| 5 |
+
import pandas as pd
|
| 6 |
+
from PIL import Image
|
| 7 |
from collections import defaultdict
|
| 8 |
import sys
|
| 9 |
import pathlib
|
|
|
|
|
|
|
| 10 |
CURRENT_DIR = pathlib.Path(__file__).parent
|
| 11 |
sys.path.append(str(CURRENT_DIR))
|
| 12 |
from utils import *
|
|
|
|
| 18 |
|
| 19 |
# Import json
|
| 20 |
for json_file in sorted(Path(json_dir).resolve().glob('*.json')):
|
| 21 |
+
if str(json_file).split("/")[-1] != "instances_val2017.json":
|
| 22 |
continue
|
| 23 |
fn = Path(save_dir) / 'labels' / json_file.stem.replace('instances_', '') # folder name
|
| 24 |
fn.mkdir()
|
|
|
|
| 142 |
convert_coco_json('./datasets/coco/annotations', # directory with *.json
|
| 143 |
use_segments=True,
|
| 144 |
cls91to80=True)
|
| 145 |
+
# zip results
|
| 146 |
+
# os.system('zip -r ../coco.zip ../coco')
|
onnx_eval.py
CHANGED
|
@@ -3,9 +3,11 @@ import json
|
|
| 3 |
import os
|
| 4 |
import sys
|
| 5 |
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
| 6 |
import onnxruntime
|
| 7 |
import numpy as np
|
| 8 |
-
import torch
|
| 9 |
from tqdm import tqdm
|
| 10 |
from pycocotools.coco import COCO
|
| 11 |
from pycocotools.cocoeval import COCOeval
|
|
@@ -20,7 +22,7 @@ import pathlib
|
|
| 20 |
CURRENT_DIR = pathlib.Path(__file__).parent
|
| 21 |
sys.path.append(str(CURRENT_DIR))
|
| 22 |
from utils import create_dataloader, coco80_to_coco91_class, check_dataset, box_iou, non_max_suppression, post_process, scale_coords, xyxy2xywh, xywh2xyxy, \
|
| 23 |
-
increment_path, colorstr, ap_per_class
|
| 24 |
|
| 25 |
|
| 26 |
def save_one_txt(predn, save_conf, shape, file):
|
|
@@ -75,8 +77,11 @@ def run(data,
|
|
| 75 |
imgsz=640, # inference size (pixels)
|
| 76 |
conf_thres=0.001, # confidence threshold
|
| 77 |
iou_thres=0.6, # NMS IoU threshold
|
| 78 |
-
task='val', # val, test
|
|
|
|
| 79 |
single_cls=False, # treat as single-class dataset
|
|
|
|
|
|
|
| 80 |
save_txt=False, # save results to *.txt
|
| 81 |
save_hybrid=False, # save label+prediction hybrid results to *.txt
|
| 82 |
save_conf=False, # save confidences in --save-txt labels
|
|
@@ -85,7 +90,21 @@ def run(data,
|
|
| 85 |
name='exp', # save to project/name
|
| 86 |
exist_ok=False, # existing project/name ok, do not increment
|
| 87 |
half=True, # use FP16 half-precision inference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
plots=False,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
onnx_weights="./yolov5s_qat.onnx",
|
| 90 |
ipu=False,
|
| 91 |
provider_config='',
|
|
@@ -118,7 +137,7 @@ def run(data,
|
|
| 118 |
|
| 119 |
# Dataloader
|
| 120 |
pad = 0.0 if task == 'speed' else 0.5
|
| 121 |
-
task = 'val' # path to val/test images
|
| 122 |
dataloader = create_dataloader(data[task], imgsz, batch_size, gs, single_cls, pad=pad, rect=False,
|
| 123 |
prefix=colorstr(f'{task}: '), workers=8)[0]
|
| 124 |
|
|
@@ -144,9 +163,10 @@ def run(data,
|
|
| 144 |
img /= 255.0 # 0 - 255 to 0.0 - 1.0
|
| 145 |
targets = targets.to(device)
|
| 146 |
nb, _, height, width = img.shape # batch size, channels, height, width
|
| 147 |
-
|
| 148 |
-
outputs = onnx_model.run(None, {onnx_model.get_inputs()[0].name: img.cpu().numpy()})
|
| 149 |
-
outputs = [torch.tensor(item).to(device) for item in outputs]
|
|
|
|
| 150 |
outputs = post_process(outputs)
|
| 151 |
out, train_out = outputs[0], outputs[1]
|
| 152 |
|
|
@@ -204,6 +224,11 @@ def run(data,
|
|
| 204 |
pf = '%20s' + '%11i' * 2 + '%11.3g' * 4 # print format
|
| 205 |
print(pf % ('all', seen, nt.sum(), mp, mr, map50, map))
|
| 206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
# Save JSON
|
| 208 |
if save_json and len(jdict):
|
| 209 |
w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else '' # weights
|
|
@@ -236,14 +261,17 @@ def run(data,
|
|
| 236 |
|
| 237 |
def parse_opt():
|
| 238 |
parser = argparse.ArgumentParser()
|
| 239 |
-
parser.add_argument('--data', type=str, default='./coco.yaml', help='
|
| 240 |
parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model.pt path(s)')
|
| 241 |
parser.add_argument('--batch-size', type=int, default=1, help='batch size')
|
| 242 |
parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='inference size (pixels)')
|
| 243 |
parser.add_argument('--conf-thres', type=float, default=0.001, help='confidence threshold')
|
| 244 |
parser.add_argument('--iou-thres', type=float, default=0.65, help='NMS IoU threshold')
|
| 245 |
-
parser.add_argument('--task', default='val', help='val, test')
|
|
|
|
| 246 |
parser.add_argument('--single-cls', action='store_true', help='treat as single-class dataset')
|
|
|
|
|
|
|
| 247 |
parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
|
| 248 |
parser.add_argument('--save-hybrid', action='store_true', help='save label+prediction hybrid results to *.txt')
|
| 249 |
parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
|
|
@@ -252,7 +280,14 @@ def parse_opt():
|
|
| 252 |
parser.add_argument('--name', default='exp', help='save to project/name')
|
| 253 |
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
|
| 254 |
parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')
|
| 255 |
-
parser.add_argument('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
parser.add_argument('--ipu', action='store_true', help='flag for ryzen ai')
|
| 257 |
parser.add_argument('--provider_config', default='', type=str, help='provider config for ryzen ai')
|
| 258 |
opt = parser.parse_args()
|
|
|
|
| 3 |
import os
|
| 4 |
import sys
|
| 5 |
from pathlib import Path
|
| 6 |
+
from threading import Thread
|
| 7 |
+
from functools import partial
|
| 8 |
+
import torch
|
| 9 |
import onnxruntime
|
| 10 |
import numpy as np
|
|
|
|
| 11 |
from tqdm import tqdm
|
| 12 |
from pycocotools.coco import COCO
|
| 13 |
from pycocotools.cocoeval import COCOeval
|
|
|
|
| 22 |
CURRENT_DIR = pathlib.Path(__file__).parent
|
| 23 |
sys.path.append(str(CURRENT_DIR))
|
| 24 |
from utils import create_dataloader, coco80_to_coco91_class, check_dataset, box_iou, non_max_suppression, post_process, scale_coords, xyxy2xywh, xywh2xyxy, \
|
| 25 |
+
increment_path, colorstr, ap_per_class, ConfusionMatrix, output_to_target, plot_val_study, check_yaml
|
| 26 |
|
| 27 |
|
| 28 |
def save_one_txt(predn, save_conf, shape, file):
|
|
|
|
| 77 |
imgsz=640, # inference size (pixels)
|
| 78 |
conf_thres=0.001, # confidence threshold
|
| 79 |
iou_thres=0.6, # NMS IoU threshold
|
| 80 |
+
task='val', # train, val, test, speed or study
|
| 81 |
+
device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu
|
| 82 |
single_cls=False, # treat as single-class dataset
|
| 83 |
+
augment=False, # augmented inference
|
| 84 |
+
verbose=False, # verbose output
|
| 85 |
save_txt=False, # save results to *.txt
|
| 86 |
save_hybrid=False, # save label+prediction hybrid results to *.txt
|
| 87 |
save_conf=False, # save confidences in --save-txt labels
|
|
|
|
| 90 |
name='exp', # save to project/name
|
| 91 |
exist_ok=False, # existing project/name ok, do not increment
|
| 92 |
half=True, # use FP16 half-precision inference
|
| 93 |
+
nndct_quant=False,
|
| 94 |
+
nndct_bitwidth=8,
|
| 95 |
+
model=None,
|
| 96 |
+
dataloader=None,
|
| 97 |
+
save_dir=Path(''),
|
| 98 |
plots=False,
|
| 99 |
+
callbacks=None,
|
| 100 |
+
compute_loss=None,
|
| 101 |
+
quant_mode='calib',
|
| 102 |
+
dump_xmodel=False,
|
| 103 |
+
dump_onnx=False,
|
| 104 |
+
dump_torch_script=False,
|
| 105 |
+
nndct_stat=0,
|
| 106 |
+
with_postprocess=False,
|
| 107 |
+
onnx_runtime=True,
|
| 108 |
onnx_weights="./yolov5s_qat.onnx",
|
| 109 |
ipu=False,
|
| 110 |
provider_config='',
|
|
|
|
| 137 |
|
| 138 |
# Dataloader
|
| 139 |
pad = 0.0 if task == 'speed' else 0.5
|
| 140 |
+
task = task if task in ('train', 'val', 'test') else 'val' # path to train/val/test images
|
| 141 |
dataloader = create_dataloader(data[task], imgsz, batch_size, gs, single_cls, pad=pad, rect=False,
|
| 142 |
prefix=colorstr(f'{task}: '), workers=8)[0]
|
| 143 |
|
|
|
|
| 163 |
img /= 255.0 # 0 - 255 to 0.0 - 1.0
|
| 164 |
targets = targets.to(device)
|
| 165 |
nb, _, height, width = img.shape # batch size, channels, height, width
|
| 166 |
+
# outputs = onnx_model.run(None, {onnx_model.get_inputs()[0].name: img.cpu().numpy()})
|
| 167 |
+
outputs = onnx_model.run(None, {onnx_model.get_inputs()[0].name: img.permute(0, 2, 3, 1).cpu().numpy()})
|
| 168 |
+
# outputs = [torch.tensor(item).to(device) for item in outputs]
|
| 169 |
+
outputs = [torch.tensor(item).permute(0, 3, 1, 2).to(device) for item in outputs]
|
| 170 |
outputs = post_process(outputs)
|
| 171 |
out, train_out = outputs[0], outputs[1]
|
| 172 |
|
|
|
|
| 224 |
pf = '%20s' + '%11i' * 2 + '%11.3g' * 4 # print format
|
| 225 |
print(pf % ('all', seen, nt.sum(), mp, mr, map50, map))
|
| 226 |
|
| 227 |
+
# Print results per class
|
| 228 |
+
if (verbose or (nc < 50)) and nc > 1 and len(stats):
|
| 229 |
+
for i, c in enumerate(ap_class):
|
| 230 |
+
print(pf % (names[c], seen, nt[c], p[i], r[i], ap50[i], ap[i]))
|
| 231 |
+
|
| 232 |
# Save JSON
|
| 233 |
if save_json and len(jdict):
|
| 234 |
w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else '' # weights
|
|
|
|
| 261 |
|
| 262 |
def parse_opt():
|
| 263 |
parser = argparse.ArgumentParser()
|
| 264 |
+
parser.add_argument('--data', type=str, default='./coco.yaml', help='dataset.yaml path')
|
| 265 |
parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model.pt path(s)')
|
| 266 |
parser.add_argument('--batch-size', type=int, default=1, help='batch size')
|
| 267 |
parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='inference size (pixels)')
|
| 268 |
parser.add_argument('--conf-thres', type=float, default=0.001, help='confidence threshold')
|
| 269 |
parser.add_argument('--iou-thres', type=float, default=0.65, help='NMS IoU threshold')
|
| 270 |
+
parser.add_argument('--task', default='val', help='train, val, test, speed or study')
|
| 271 |
+
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
|
| 272 |
parser.add_argument('--single-cls', action='store_true', help='treat as single-class dataset')
|
| 273 |
+
parser.add_argument('--augment', action='store_true', help='augmented inference')
|
| 274 |
+
parser.add_argument('--verbose', action='store_true', help='report mAP by class')
|
| 275 |
parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
|
| 276 |
parser.add_argument('--save-hybrid', action='store_true', help='save label+prediction hybrid results to *.txt')
|
| 277 |
parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
|
|
|
|
| 280 |
parser.add_argument('--name', default='exp', help='save to project/name')
|
| 281 |
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
|
| 282 |
parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')
|
| 283 |
+
parser.add_argument('--quant_mode', default='calib', help='nndct quant mode')
|
| 284 |
+
parser.add_argument('--nndct_quant', action='store_true', help='use nndct quant model for inference')
|
| 285 |
+
parser.add_argument('--dump_xmodel', action='store_true', help='dump nndct xmodel')
|
| 286 |
+
parser.add_argument('--dump_onnx', action='store_true', help='dump nndct onnx xmodel')
|
| 287 |
+
parser.add_argument('--with_postprocess', action='store_true', help='nndct model with postprocess')
|
| 288 |
+
parser.add_argument('--onnx_runtime', default=True, action='store_true', help='onnx_runtime')
|
| 289 |
+
parser.add_argument('-m', '--onnx_weights', default='./yolov5s_qat.onnx', nargs='+', type=str, help='onnx_weights')
|
| 290 |
+
parser.add_argument('--nndct_stat', type=int, required=False, default=0)
|
| 291 |
parser.add_argument('--ipu', action='store_true', help='flag for ryzen ai')
|
| 292 |
parser.add_argument('--provider_config', default='', type=str, help='provider config for ryzen ai')
|
| 293 |
opt = parser.parse_args()
|
onnx_inference.py
CHANGED
|
@@ -1,15 +1,26 @@
|
|
| 1 |
-
import onnxruntime
|
| 2 |
import numpy as np
|
|
|
|
|
|
|
| 3 |
import cv2
|
|
|
|
|
|
|
| 4 |
import torch
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
import sys
|
| 6 |
import pathlib
|
| 7 |
CURRENT_DIR = pathlib.Path(__file__).parent
|
| 8 |
sys.path.append(str(CURRENT_DIR))
|
| 9 |
import argparse
|
| 10 |
from utils import (
|
|
|
|
|
|
|
| 11 |
letterbox,
|
|
|
|
| 12 |
non_max_suppression,
|
|
|
|
| 13 |
scale_coords,
|
| 14 |
Annotator,
|
| 15 |
Colors,
|
|
@@ -55,21 +66,21 @@ def make_parser():
|
|
| 55 |
"--model",
|
| 56 |
type=str,
|
| 57 |
default="./yolov5s_qat.onnx",
|
| 58 |
-
help="
|
| 59 |
)
|
| 60 |
parser.add_argument(
|
| 61 |
"-i",
|
| 62 |
"--image_path",
|
| 63 |
type=str,
|
| 64 |
default='./demo.jpg',
|
| 65 |
-
help="
|
| 66 |
)
|
| 67 |
parser.add_argument(
|
| 68 |
"-o",
|
| 69 |
"--output_path",
|
| 70 |
type=str,
|
| 71 |
default='./demo_infer.jpg',
|
| 72 |
-
help="
|
| 73 |
)
|
| 74 |
parser.add_argument(
|
| 75 |
'--ipu',
|
|
@@ -113,8 +124,9 @@ if __name__ == '__main__':
|
|
| 113 |
|
| 114 |
img0 = cv2.imread(path)
|
| 115 |
img = pre_process(img0)
|
| 116 |
-
onnx_input = {onnx_model.get_inputs()[0].name: img}
|
| 117 |
onnx_output = onnx_model.run(None, onnx_input)
|
|
|
|
| 118 |
onnx_output = post_process(onnx_output)
|
| 119 |
pred = non_max_suppression(
|
| 120 |
onnx_output[0], conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det
|
|
|
|
|
|
|
| 1 |
import numpy as np
|
| 2 |
+
import onnx
|
| 3 |
+
import copy
|
| 4 |
import cv2
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
import matplotlib.pyplot as plt
|
| 7 |
import torch
|
| 8 |
+
import onnxruntime
|
| 9 |
+
import time
|
| 10 |
+
import torchvision
|
| 11 |
+
import re
|
| 12 |
import sys
|
| 13 |
import pathlib
|
| 14 |
CURRENT_DIR = pathlib.Path(__file__).parent
|
| 15 |
sys.path.append(str(CURRENT_DIR))
|
| 16 |
import argparse
|
| 17 |
from utils import (
|
| 18 |
+
is_ascii,
|
| 19 |
+
is_chinese,
|
| 20 |
letterbox,
|
| 21 |
+
xywh2xyxy,
|
| 22 |
non_max_suppression,
|
| 23 |
+
clip_coords,
|
| 24 |
scale_coords,
|
| 25 |
Annotator,
|
| 26 |
Colors,
|
|
|
|
| 66 |
"--model",
|
| 67 |
type=str,
|
| 68 |
default="./yolov5s_qat.onnx",
|
| 69 |
+
help="Input your onnx model.",
|
| 70 |
)
|
| 71 |
parser.add_argument(
|
| 72 |
"-i",
|
| 73 |
"--image_path",
|
| 74 |
type=str,
|
| 75 |
default='./demo.jpg',
|
| 76 |
+
help="Path to your input image.",
|
| 77 |
)
|
| 78 |
parser.add_argument(
|
| 79 |
"-o",
|
| 80 |
"--output_path",
|
| 81 |
type=str,
|
| 82 |
default='./demo_infer.jpg',
|
| 83 |
+
help="Path to your output directory.",
|
| 84 |
)
|
| 85 |
parser.add_argument(
|
| 86 |
'--ipu',
|
|
|
|
| 124 |
|
| 125 |
img0 = cv2.imread(path)
|
| 126 |
img = pre_process(img0)
|
| 127 |
+
onnx_input = {onnx_model.get_inputs()[0].name: img.transpose(0, 2, 3, 1)}
|
| 128 |
onnx_output = onnx_model.run(None, onnx_input)
|
| 129 |
+
onnx_output = [torch.tensor(item).permute(0, 3, 1, 2) for item in onnx_output]
|
| 130 |
onnx_output = post_process(onnx_output)
|
| 131 |
pred = non_max_suppression(
|
| 132 |
onnx_output[0], conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det
|
utils.py
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
|
|
| 1 |
import numpy as np
|
|
|
|
|
|
|
| 2 |
import cv2
|
| 3 |
from pathlib import Path
|
|
|
|
| 4 |
import torch
|
| 5 |
import time
|
| 6 |
import torchvision
|
| 7 |
import re
|
| 8 |
import glob
|
|
|
|
| 9 |
from torch.utils.data import Dataset
|
| 10 |
import yaml
|
| 11 |
import os
|
|
@@ -15,6 +20,7 @@ from itertools import repeat
|
|
| 15 |
import logging
|
| 16 |
from PIL import Image, ExifTags
|
| 17 |
import hashlib
|
|
|
|
| 18 |
import sys
|
| 19 |
import pathlib
|
| 20 |
CURRENT_DIR = pathlib.Path(__file__).parent
|
|
@@ -277,40 +283,75 @@ class Annotator:
|
|
| 277 |
im.data.contiguous
|
| 278 |
), "Image not contiguous. Apply np.ascontiguousarray(im) to Annotator() input images."
|
| 279 |
self.pil = pil or not is_ascii(example) or is_chinese(example)
|
| 280 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
self.lw = line_width or max(round(sum(im.shape) / 2 * 0.003), 2) # line width
|
| 282 |
|
| 283 |
def box_label(
|
| 284 |
self, box, label="", color=(128, 128, 128), txt_color=(255, 255, 255)
|
| 285 |
):
|
| 286 |
# Add one xyxy box to image with label
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
label,
|
| 302 |
-
(
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 309 |
|
| 310 |
def rectangle(self, xy, fill=None, outline=None, width=1):
|
| 311 |
# Add rectangle to image (PIL-only)
|
| 312 |
self.draw.rectangle(xy, fill, outline, width)
|
| 313 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 314 |
def result(self):
|
| 315 |
# Return annotated image as array
|
| 316 |
return np.asarray(self.im)
|
|
@@ -354,19 +395,32 @@ class Colors:
|
|
| 354 |
return tuple(int(h[1 + i : 1 + i + 2], 16) for i in (0, 2, 4))
|
| 355 |
|
| 356 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
def create_dataloader(path, imgsz, batch_size, stride, single_cls=False, hyp=None, augment=False, cache=False, pad=0.0,
|
| 358 |
rect=False, rank=-1, workers=8, image_weights=False, quad=False, prefix=''):
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
|
|
|
| 370 |
|
| 371 |
batch_size = min(batch_size, len(dataset))
|
| 372 |
nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, workers]) # number of workers
|
|
@@ -378,7 +432,7 @@ def create_dataloader(path, imgsz, batch_size, stride, single_cls=False, hyp=Non
|
|
| 378 |
num_workers=nw,
|
| 379 |
sampler=sampler,
|
| 380 |
pin_memory=True,
|
| 381 |
-
collate_fn=LoadImagesAndLabels.collate_fn)
|
| 382 |
return dataloader, dataset
|
| 383 |
|
| 384 |
|
|
@@ -393,29 +447,32 @@ class LoadImagesAndLabels(Dataset):
|
|
| 393 |
self.hyp = hyp
|
| 394 |
self.image_weights = image_weights
|
| 395 |
self.rect = False if image_weights else rect
|
| 396 |
-
self.mosaic =
|
| 397 |
self.mosaic_border = [-img_size // 2, -img_size // 2]
|
| 398 |
self.stride = stride
|
| 399 |
self.path = path
|
| 400 |
-
self.albumentations = None
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
p
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
|
|
|
|
|
|
|
|
|
| 419 |
|
| 420 |
# Check cache
|
| 421 |
self.label_files = img2label_paths(self.img_files) # labels
|
|
@@ -434,6 +491,7 @@ class LoadImagesAndLabels(Dataset):
|
|
| 434 |
tqdm(None, desc=prefix + d, total=n, initial=n) # display cache results
|
| 435 |
if cache['msgs']:
|
| 436 |
logging.info('\n'.join(cache['msgs'])) # display warnings
|
|
|
|
| 437 |
|
| 438 |
# Read cache
|
| 439 |
[cache.pop(k) for k in ('hash', 'version', 'msgs')] # remove items
|
|
@@ -520,6 +578,8 @@ class LoadImagesAndLabels(Dataset):
|
|
| 520 |
pbar.close()
|
| 521 |
if msgs:
|
| 522 |
logging.info('\n'.join(msgs))
|
|
|
|
|
|
|
| 523 |
x['hash'] = get_hash(self.label_files + self.img_files)
|
| 524 |
x['results'] = nf, nm, ne, nc, len(self.img_files)
|
| 525 |
x['msgs'] = msgs # warnings
|
|
@@ -545,24 +605,64 @@ class LoadImagesAndLabels(Dataset):
|
|
| 545 |
index = self.indices[index] # linear, shuffled, or image_weights
|
| 546 |
|
| 547 |
hyp = self.hyp
|
| 548 |
-
mosaic = self.mosaic
|
|
|
|
|
|
|
|
|
|
|
|
|
| 549 |
|
| 550 |
-
|
| 551 |
-
|
|
|
|
| 552 |
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 561 |
|
| 562 |
nl = len(labels) # number of labels
|
| 563 |
if nl:
|
| 564 |
labels[:, 1:5] = xyxy2xywhn(labels[:, 1:5], w=img.shape[1], h=img.shape[0], clip=True, eps=1E-3)
|
| 565 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 566 |
labels_out = torch.zeros((nl, 6))
|
| 567 |
if nl:
|
| 568 |
labels_out[:, 1:] = torch.from_numpy(labels)
|
|
@@ -580,6 +680,32 @@ class LoadImagesAndLabels(Dataset):
|
|
| 580 |
l[:, 0] = i # add target image index for build_targets()
|
| 581 |
return torch.stack(img, 0), torch.cat(label, 0), path, shapes
|
| 582 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
|
| 584 |
def coco80_to_coco91_class(): # converts 80-index (val2014) to 91-index (paper)
|
| 585 |
# https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/
|
|
@@ -599,6 +725,10 @@ def check_dataset(data, autodownload=True):
|
|
| 599 |
|
| 600 |
# Download (optional)
|
| 601 |
extract_dir = ''
|
|
|
|
|
|
|
|
|
|
|
|
|
| 602 |
|
| 603 |
# Read yaml (optional)
|
| 604 |
if isinstance(data, (str, Path)):
|
|
@@ -619,6 +749,24 @@ def check_dataset(data, autodownload=True):
|
|
| 619 |
val = [Path(x).resolve() for x in (val if isinstance(val, list) else [val])] # val path
|
| 620 |
if not all(x.exists() for x in val):
|
| 621 |
print('\nWARNING: Dataset not found, nonexistent paths: %s' % [str(x) for x in val if not x.exists()])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 622 |
|
| 623 |
return data # dictionary
|
| 624 |
|
|
@@ -743,6 +891,11 @@ def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, save_dir='.', names
|
|
| 743 |
|
| 744 |
# Compute F1 (harmonic mean of precision and recall)
|
| 745 |
f1 = 2 * p * r / (p + r + 1e-16)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 746 |
|
| 747 |
i = f1.mean(0).argmax() # max F1 index
|
| 748 |
return p[:, i], r[:, i], ap, f1[:, i], unique_classes.astype('int32')
|
|
@@ -776,6 +929,84 @@ def compute_ap(recall, precision):
|
|
| 776 |
return ap, mpre, mrec
|
| 777 |
|
| 778 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 779 |
def output_to_target(output):
|
| 780 |
# Convert model output to target format [batch_id, class_id, x, y, w, h, conf]
|
| 781 |
targets = []
|
|
@@ -785,6 +1016,43 @@ def output_to_target(output):
|
|
| 785 |
return np.array(targets)
|
| 786 |
|
| 787 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 788 |
def check_yaml(file, suffix=('.yaml', '.yml')):
|
| 789 |
# Search/download YAML file (if necessary) and return path, checking suffix
|
| 790 |
return check_file(file, suffix)
|
|
@@ -794,7 +1062,22 @@ def check_file(file, suffix=''):
|
|
| 794 |
# Search/download file (if necessary) and return path
|
| 795 |
check_suffix(file, suffix) # optional
|
| 796 |
file = str(file) # convert to str()
|
| 797 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 798 |
|
| 799 |
|
| 800 |
def check_suffix(file='yolov5s.pt', suffix=('.pt',), msg=''):
|
|
|
|
| 1 |
+
import onnxruntime
|
| 2 |
import numpy as np
|
| 3 |
+
import onnx
|
| 4 |
+
import copy
|
| 5 |
import cv2
|
| 6 |
from pathlib import Path
|
| 7 |
+
import matplotlib.pyplot as plt
|
| 8 |
import torch
|
| 9 |
import time
|
| 10 |
import torchvision
|
| 11 |
import re
|
| 12 |
import glob
|
| 13 |
+
from contextlib import contextmanager
|
| 14 |
from torch.utils.data import Dataset
|
| 15 |
import yaml
|
| 16 |
import os
|
|
|
|
| 20 |
import logging
|
| 21 |
from PIL import Image, ExifTags
|
| 22 |
import hashlib
|
| 23 |
+
import shutil
|
| 24 |
import sys
|
| 25 |
import pathlib
|
| 26 |
CURRENT_DIR = pathlib.Path(__file__).parent
|
|
|
|
| 283 |
im.data.contiguous
|
| 284 |
), "Image not contiguous. Apply np.ascontiguousarray(im) to Annotator() input images."
|
| 285 |
self.pil = pil or not is_ascii(example) or is_chinese(example)
|
| 286 |
+
if self.pil: # use PIL
|
| 287 |
+
self.im = im if isinstance(im, Image.Image) else Image.fromarray(im)
|
| 288 |
+
self.draw = ImageDraw.Draw(self.im)
|
| 289 |
+
self.font = check_font(
|
| 290 |
+
font="Arial.Unicode.ttf" if is_chinese(example) else font,
|
| 291 |
+
size=font_size or max(round(sum(self.im.size) / 2 * 0.035), 12),
|
| 292 |
+
)
|
| 293 |
+
else: # use cv2
|
| 294 |
+
self.im = im
|
| 295 |
self.lw = line_width or max(round(sum(im.shape) / 2 * 0.003), 2) # line width
|
| 296 |
|
| 297 |
def box_label(
|
| 298 |
self, box, label="", color=(128, 128, 128), txt_color=(255, 255, 255)
|
| 299 |
):
|
| 300 |
# Add one xyxy box to image with label
|
| 301 |
+
if self.pil or not is_ascii(label):
|
| 302 |
+
self.draw.rectangle(box, width=self.lw, outline=color) # box
|
| 303 |
+
if label:
|
| 304 |
+
w, h = self.font.getsize(label) # text width, height
|
| 305 |
+
outside = box[1] - h >= 0 # label fits outside box
|
| 306 |
+
self.draw.rectangle(
|
| 307 |
+
[
|
| 308 |
+
box[0],
|
| 309 |
+
box[1] - h if outside else box[1],
|
| 310 |
+
box[0] + w + 1,
|
| 311 |
+
box[1] + 1 if outside else box[1] + h + 1,
|
| 312 |
+
],
|
| 313 |
+
fill=color,
|
| 314 |
+
)
|
| 315 |
+
# self.draw.text((box[0], box[1]), label, fill=txt_color, font=self.font, anchor='ls') # for PIL>8.0
|
| 316 |
+
self.draw.text(
|
| 317 |
+
(box[0], box[1] - h if outside else box[1]),
|
| 318 |
+
label,
|
| 319 |
+
fill=txt_color,
|
| 320 |
+
font=self.font,
|
| 321 |
+
)
|
| 322 |
+
else: # cv2
|
| 323 |
+
p1, p2 = (int(box[0]), int(box[1])), (int(box[2]), int(box[3]))
|
| 324 |
+
cv2.rectangle(
|
| 325 |
+
self.im, p1, p2, color, thickness=self.lw, lineType=cv2.LINE_AA
|
| 326 |
)
|
| 327 |
+
if label:
|
| 328 |
+
tf = max(self.lw - 1, 1) # font thickness
|
| 329 |
+
w, h = cv2.getTextSize(label, 0, fontScale=self.lw / 3, thickness=tf)[
|
| 330 |
+
0
|
| 331 |
+
] # text width, height
|
| 332 |
+
outside = p1[1] - h - 3 >= 0 # label fits outside box
|
| 333 |
+
p2 = p1[0] + w, p1[1] - h - 3 if outside else p1[1] + h + 3
|
| 334 |
+
cv2.rectangle(self.im, p1, p2, color, -1, cv2.LINE_AA) # filled
|
| 335 |
+
cv2.putText(
|
| 336 |
+
self.im,
|
| 337 |
+
label,
|
| 338 |
+
(p1[0], p1[1] - 2 if outside else p1[1] + h + 2),
|
| 339 |
+
0,
|
| 340 |
+
self.lw / 3,
|
| 341 |
+
txt_color,
|
| 342 |
+
thickness=tf,
|
| 343 |
+
lineType=cv2.LINE_AA,
|
| 344 |
+
)
|
| 345 |
|
| 346 |
def rectangle(self, xy, fill=None, outline=None, width=1):
|
| 347 |
# Add rectangle to image (PIL-only)
|
| 348 |
self.draw.rectangle(xy, fill, outline, width)
|
| 349 |
|
| 350 |
+
def text(self, xy, text, txt_color=(255, 255, 255)):
|
| 351 |
+
# Add text to image (PIL-only)
|
| 352 |
+
w, h = self.font.getsize(text) # text width, height
|
| 353 |
+
self.draw.text((xy[0], xy[1] - h + 1), text, fill=txt_color, font=self.font)
|
| 354 |
+
|
| 355 |
def result(self):
|
| 356 |
# Return annotated image as array
|
| 357 |
return np.asarray(self.im)
|
|
|
|
| 395 |
return tuple(int(h[1 + i : 1 + i + 2], 16) for i in (0, 2, 4))
|
| 396 |
|
| 397 |
|
| 398 |
+
@contextmanager
|
| 399 |
+
def torch_distributed_zero_first(local_rank: int):
|
| 400 |
+
"""
|
| 401 |
+
Decorator to make all processes in distributed training wait for each local_master to do something.
|
| 402 |
+
"""
|
| 403 |
+
if local_rank not in [-1, 0]:
|
| 404 |
+
dist.barrier(device_ids=[local_rank])
|
| 405 |
+
yield
|
| 406 |
+
if local_rank == 0:
|
| 407 |
+
dist.barrier(device_ids=[0])
|
| 408 |
+
|
| 409 |
+
|
| 410 |
def create_dataloader(path, imgsz, batch_size, stride, single_cls=False, hyp=None, augment=False, cache=False, pad=0.0,
|
| 411 |
rect=False, rank=-1, workers=8, image_weights=False, quad=False, prefix=''):
|
| 412 |
+
# Make sure only the first process in DDP process the dataset first, and the following others can use the cache
|
| 413 |
+
with torch_distributed_zero_first(rank):
|
| 414 |
+
dataset = LoadImagesAndLabels(path, imgsz, batch_size,
|
| 415 |
+
augment=augment, # augment images
|
| 416 |
+
hyp=hyp, # augmentation hyperparameters
|
| 417 |
+
rect=rect, # rectangular training
|
| 418 |
+
cache_images=cache,
|
| 419 |
+
single_cls=single_cls,
|
| 420 |
+
stride=int(stride),
|
| 421 |
+
pad=pad,
|
| 422 |
+
image_weights=image_weights,
|
| 423 |
+
prefix=prefix)
|
| 424 |
|
| 425 |
batch_size = min(batch_size, len(dataset))
|
| 426 |
nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, workers]) # number of workers
|
|
|
|
| 432 |
num_workers=nw,
|
| 433 |
sampler=sampler,
|
| 434 |
pin_memory=True,
|
| 435 |
+
collate_fn=LoadImagesAndLabels.collate_fn4 if quad else LoadImagesAndLabels.collate_fn)
|
| 436 |
return dataloader, dataset
|
| 437 |
|
| 438 |
|
|
|
|
| 447 |
self.hyp = hyp
|
| 448 |
self.image_weights = image_weights
|
| 449 |
self.rect = False if image_weights else rect
|
| 450 |
+
self.mosaic = self.augment and not self.rect # load 4 images at a time into a mosaic (only during training)
|
| 451 |
self.mosaic_border = [-img_size // 2, -img_size // 2]
|
| 452 |
self.stride = stride
|
| 453 |
self.path = path
|
| 454 |
+
self.albumentations = Albumentations() if augment else None
|
| 455 |
+
|
| 456 |
+
try:
|
| 457 |
+
f = [] # image files
|
| 458 |
+
for p in path if isinstance(path, list) else [path]:
|
| 459 |
+
p = Path(p) # os-agnostic
|
| 460 |
+
if p.is_dir(): # dir
|
| 461 |
+
f += glob.glob(str(p / '**' / '*.*'), recursive=True)
|
| 462 |
+
# f = list(p.rglob('**/*.*')) # pathlib
|
| 463 |
+
elif p.is_file(): # file
|
| 464 |
+
with open(p, 'r') as t:
|
| 465 |
+
t = t.read().strip().splitlines()
|
| 466 |
+
parent = str(p.parent) + os.sep
|
| 467 |
+
f += [x.replace('./', parent) if x.startswith('./') else x for x in t] # local to global path
|
| 468 |
+
# f += [p.parent / x.lstrip(os.sep) for x in t] # local to global path (pathlib)
|
| 469 |
+
else:
|
| 470 |
+
raise Exception(f'{prefix}{p} does not exist')
|
| 471 |
+
self.img_files = sorted([x.replace('/', os.sep) for x in f if x.split('.')[-1].lower() in IMG_FORMATS])
|
| 472 |
+
# self.img_files = sorted([x for x in f if x.suffix[1:].lower() in img_formats]) # pathlib
|
| 473 |
+
assert self.img_files, f'{prefix}No images found'
|
| 474 |
+
except Exception as e:
|
| 475 |
+
raise Exception(f'{prefix}Error loading data from {path}: {e}\nSee {HELP_URL}')
|
| 476 |
|
| 477 |
# Check cache
|
| 478 |
self.label_files = img2label_paths(self.img_files) # labels
|
|
|
|
| 491 |
tqdm(None, desc=prefix + d, total=n, initial=n) # display cache results
|
| 492 |
if cache['msgs']:
|
| 493 |
logging.info('\n'.join(cache['msgs'])) # display warnings
|
| 494 |
+
assert nf > 0 or not augment, f'{prefix}No labels in {cache_path}. Can not train without labels. See {HELP_URL}'
|
| 495 |
|
| 496 |
# Read cache
|
| 497 |
[cache.pop(k) for k in ('hash', 'version', 'msgs')] # remove items
|
|
|
|
| 578 |
pbar.close()
|
| 579 |
if msgs:
|
| 580 |
logging.info('\n'.join(msgs))
|
| 581 |
+
if nf == 0:
|
| 582 |
+
logging.info(f'{prefix}WARNING: No labels found in {path}. See {HELP_URL}')
|
| 583 |
x['hash'] = get_hash(self.label_files + self.img_files)
|
| 584 |
x['results'] = nf, nm, ne, nc, len(self.img_files)
|
| 585 |
x['msgs'] = msgs # warnings
|
|
|
|
| 605 |
index = self.indices[index] # linear, shuffled, or image_weights
|
| 606 |
|
| 607 |
hyp = self.hyp
|
| 608 |
+
mosaic = self.mosaic and random.random() < hyp['mosaic']
|
| 609 |
+
if mosaic:
|
| 610 |
+
# Load mosaic
|
| 611 |
+
img, labels = load_mosaic(self, index)
|
| 612 |
+
shapes = None
|
| 613 |
|
| 614 |
+
# MixUp augmentation
|
| 615 |
+
if random.random() < hyp['mixup']:
|
| 616 |
+
img, labels = mixup(img, labels, *load_mosaic(self, random.randint(0, self.n - 1)))
|
| 617 |
|
| 618 |
+
else:
|
| 619 |
+
# Load image
|
| 620 |
+
img, (h0, w0), (h, w) = load_image(self, index)
|
| 621 |
+
|
| 622 |
+
# Letterbox
|
| 623 |
+
shape = self.batch_shapes[self.batch[index]] if self.rect else self.img_size # final letterboxed shape
|
| 624 |
+
img, ratio, pad = letterbox(img, shape, auto=False, scaleup=self.augment)
|
| 625 |
+
shapes = (h0, w0), ((h / h0, w / w0), pad) # for COCO mAP rescaling
|
| 626 |
+
|
| 627 |
+
labels = self.labels[index].copy()
|
| 628 |
+
if labels.size: # normalized xywh to pixel xyxy format
|
| 629 |
+
labels[:, 1:] = xywhn2xyxy(labels[:, 1:], ratio[0] * w, ratio[1] * h, padw=pad[0], padh=pad[1])
|
| 630 |
+
|
| 631 |
+
if self.augment:
|
| 632 |
+
img, labels = random_perspective(img, labels,
|
| 633 |
+
degrees=hyp['degrees'],
|
| 634 |
+
translate=hyp['translate'],
|
| 635 |
+
scale=hyp['scale'],
|
| 636 |
+
shear=hyp['shear'],
|
| 637 |
+
perspective=hyp['perspective'])
|
| 638 |
|
| 639 |
nl = len(labels) # number of labels
|
| 640 |
if nl:
|
| 641 |
labels[:, 1:5] = xyxy2xywhn(labels[:, 1:5], w=img.shape[1], h=img.shape[0], clip=True, eps=1E-3)
|
| 642 |
|
| 643 |
+
if self.augment:
|
| 644 |
+
# Albumentations
|
| 645 |
+
img, labels = self.albumentations(img, labels)
|
| 646 |
+
nl = len(labels) # update after albumentations
|
| 647 |
+
|
| 648 |
+
# HSV color-space
|
| 649 |
+
augment_hsv(img, hgain=hyp['hsv_h'], sgain=hyp['hsv_s'], vgain=hyp['hsv_v'])
|
| 650 |
+
|
| 651 |
+
# Flip up-down
|
| 652 |
+
if random.random() < hyp['flipud']:
|
| 653 |
+
img = np.flipud(img)
|
| 654 |
+
if nl:
|
| 655 |
+
labels[:, 2] = 1 - labels[:, 2]
|
| 656 |
+
|
| 657 |
+
# Flip left-right
|
| 658 |
+
if random.random() < hyp['fliplr']:
|
| 659 |
+
img = np.fliplr(img)
|
| 660 |
+
if nl:
|
| 661 |
+
labels[:, 1] = 1 - labels[:, 1]
|
| 662 |
+
|
| 663 |
+
# Cutouts
|
| 664 |
+
# labels = cutout(img, labels, p=0.5)
|
| 665 |
+
|
| 666 |
labels_out = torch.zeros((nl, 6))
|
| 667 |
if nl:
|
| 668 |
labels_out[:, 1:] = torch.from_numpy(labels)
|
|
|
|
| 680 |
l[:, 0] = i # add target image index for build_targets()
|
| 681 |
return torch.stack(img, 0), torch.cat(label, 0), path, shapes
|
| 682 |
|
| 683 |
+
@staticmethod
|
| 684 |
+
def collate_fn4(batch):
|
| 685 |
+
img, label, path, shapes = zip(*batch) # transposed
|
| 686 |
+
n = len(shapes) // 4
|
| 687 |
+
img4, label4, path4, shapes4 = [], [], path[:n], shapes[:n]
|
| 688 |
+
|
| 689 |
+
ho = torch.tensor([[0., 0, 0, 1, 0, 0]])
|
| 690 |
+
wo = torch.tensor([[0., 0, 1, 0, 0, 0]])
|
| 691 |
+
s = torch.tensor([[1, 1, .5, .5, .5, .5]]) # scale
|
| 692 |
+
for i in range(n): # zidane torch.zeros(16,3,720,1280) # BCHW
|
| 693 |
+
i *= 4
|
| 694 |
+
if random.random() < 0.5:
|
| 695 |
+
im = F.interpolate(img[i].unsqueeze(0).float(), scale_factor=2., mode='bilinear', align_corners=False)[
|
| 696 |
+
0].type(img[i].type())
|
| 697 |
+
l = label[i]
|
| 698 |
+
else:
|
| 699 |
+
im = torch.cat((torch.cat((img[i], img[i + 1]), 1), torch.cat((img[i + 2], img[i + 3]), 1)), 2)
|
| 700 |
+
l = torch.cat((label[i], label[i + 1] + ho, label[i + 2] + wo, label[i + 3] + ho + wo), 0) * s
|
| 701 |
+
img4.append(im)
|
| 702 |
+
label4.append(l)
|
| 703 |
+
|
| 704 |
+
for i, l in enumerate(label4):
|
| 705 |
+
l[:, 0] = i # add target image index for build_targets()
|
| 706 |
+
|
| 707 |
+
return torch.stack(img4, 0), torch.cat(label4, 0), path4, shapes4
|
| 708 |
+
|
| 709 |
|
| 710 |
def coco80_to_coco91_class(): # converts 80-index (val2014) to 91-index (paper)
|
| 711 |
# https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/
|
|
|
|
| 725 |
|
| 726 |
# Download (optional)
|
| 727 |
extract_dir = ''
|
| 728 |
+
if isinstance(data, (str, Path)) and str(data).endswith('.zip'): # i.e. gs://bucket/dir/coco128.zip
|
| 729 |
+
download(data, dir='../datasets', unzip=True, delete=False, curl=False, threads=1)
|
| 730 |
+
data = next((Path('../datasets') / Path(data).stem).rglob('*.yaml'))
|
| 731 |
+
extract_dir, autodownload = data.parent, False
|
| 732 |
|
| 733 |
# Read yaml (optional)
|
| 734 |
if isinstance(data, (str, Path)):
|
|
|
|
| 749 |
val = [Path(x).resolve() for x in (val if isinstance(val, list) else [val])] # val path
|
| 750 |
if not all(x.exists() for x in val):
|
| 751 |
print('\nWARNING: Dataset not found, nonexistent paths: %s' % [str(x) for x in val if not x.exists()])
|
| 752 |
+
if s and autodownload: # download script
|
| 753 |
+
root = path.parent if 'path' in data else '..' # unzip directory i.e. '../'
|
| 754 |
+
if s.startswith('http') and s.endswith('.zip'): # URL
|
| 755 |
+
f = Path(s).name # filename
|
| 756 |
+
print(f'Downloading {s} to {f}...')
|
| 757 |
+
torch.hub.download_url_to_file(s, f)
|
| 758 |
+
Path(root).mkdir(parents=True, exist_ok=True) # create root
|
| 759 |
+
ZipFile(f).extractall(path=root) # unzip
|
| 760 |
+
Path(f).unlink() # remove zip
|
| 761 |
+
r = None # success
|
| 762 |
+
elif s.startswith('bash '): # bash script
|
| 763 |
+
print(f'Running {s} ...')
|
| 764 |
+
r = os.system(s)
|
| 765 |
+
else: # python script
|
| 766 |
+
r = exec(s, {'yaml': data}) # return None
|
| 767 |
+
print(f"Dataset autodownload {f'success, saved to {root}' if r in (0, None) else 'failure'}\n")
|
| 768 |
+
else:
|
| 769 |
+
raise Exception('Dataset not found.')
|
| 770 |
|
| 771 |
return data # dictionary
|
| 772 |
|
|
|
|
| 891 |
|
| 892 |
# Compute F1 (harmonic mean of precision and recall)
|
| 893 |
f1 = 2 * p * r / (p + r + 1e-16)
|
| 894 |
+
if plot:
|
| 895 |
+
plot_pr_curve(px, py, ap, Path(save_dir) / 'PR_curve.png', names)
|
| 896 |
+
plot_mc_curve(px, f1, Path(save_dir) / 'F1_curve.png', names, ylabel='F1')
|
| 897 |
+
plot_mc_curve(px, p, Path(save_dir) / 'P_curve.png', names, ylabel='Precision')
|
| 898 |
+
plot_mc_curve(px, r, Path(save_dir) / 'R_curve.png', names, ylabel='Recall')
|
| 899 |
|
| 900 |
i = f1.mean(0).argmax() # max F1 index
|
| 901 |
return p[:, i], r[:, i], ap, f1[:, i], unique_classes.astype('int32')
|
|
|
|
| 929 |
return ap, mpre, mrec
|
| 930 |
|
| 931 |
|
| 932 |
+
class ConfusionMatrix:
|
| 933 |
+
# Updated version of https://github.com/kaanakan/object_detection_confusion_matrix
|
| 934 |
+
def __init__(self, nc, conf=0.25, iou_thres=0.45):
|
| 935 |
+
self.matrix = np.zeros((nc + 1, nc + 1))
|
| 936 |
+
self.nc = nc # number of classes
|
| 937 |
+
self.conf = conf
|
| 938 |
+
self.iou_thres = iou_thres
|
| 939 |
+
|
| 940 |
+
def process_batch(self, detections, labels):
|
| 941 |
+
"""
|
| 942 |
+
Return intersection-over-union (Jaccard index) of boxes.
|
| 943 |
+
Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
|
| 944 |
+
Arguments:
|
| 945 |
+
detections (Array[N, 6]), x1, y1, x2, y2, conf, class
|
| 946 |
+
labels (Array[M, 5]), class, x1, y1, x2, y2
|
| 947 |
+
Returns:
|
| 948 |
+
None, updates confusion matrix accordingly
|
| 949 |
+
"""
|
| 950 |
+
detections = detections[detections[:, 4] > self.conf]
|
| 951 |
+
gt_classes = labels[:, 0].int()
|
| 952 |
+
detection_classes = detections[:, 5].int()
|
| 953 |
+
iou = box_iou(labels[:, 1:], detections[:, :4])
|
| 954 |
+
|
| 955 |
+
x = torch.where(iou > self.iou_thres)
|
| 956 |
+
if x[0].shape[0]:
|
| 957 |
+
matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy()
|
| 958 |
+
if x[0].shape[0] > 1:
|
| 959 |
+
matches = matches[matches[:, 2].argsort()[::-1]]
|
| 960 |
+
matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
|
| 961 |
+
matches = matches[matches[:, 2].argsort()[::-1]]
|
| 962 |
+
matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
|
| 963 |
+
else:
|
| 964 |
+
matches = np.zeros((0, 3))
|
| 965 |
+
|
| 966 |
+
n = matches.shape[0] > 0
|
| 967 |
+
m0, m1, _ = matches.transpose().astype(np.int16)
|
| 968 |
+
for i, gc in enumerate(gt_classes):
|
| 969 |
+
j = m0 == i
|
| 970 |
+
if n and sum(j) == 1:
|
| 971 |
+
self.matrix[detection_classes[m1[j]], gc] += 1 # correct
|
| 972 |
+
else:
|
| 973 |
+
self.matrix[self.nc, gc] += 1 # background FP
|
| 974 |
+
|
| 975 |
+
if n:
|
| 976 |
+
for i, dc in enumerate(detection_classes):
|
| 977 |
+
if not any(m1 == i):
|
| 978 |
+
self.matrix[dc, self.nc] += 1 # background FN
|
| 979 |
+
|
| 980 |
+
def matrix(self):
|
| 981 |
+
return self.matrix
|
| 982 |
+
|
| 983 |
+
def plot(self, normalize=True, save_dir='', names=()):
|
| 984 |
+
try:
|
| 985 |
+
import seaborn as sn
|
| 986 |
+
|
| 987 |
+
array = self.matrix / ((self.matrix.sum(0).reshape(1, -1) + 1E-6) if normalize else 1) # normalize columns
|
| 988 |
+
array[array < 0.005] = np.nan # don't annotate (would appear as 0.00)
|
| 989 |
+
|
| 990 |
+
fig = plt.figure(figsize=(12, 9), tight_layout=True)
|
| 991 |
+
sn.set(font_scale=1.0 if self.nc < 50 else 0.8) # for label size
|
| 992 |
+
labels = (0 < len(names) < 99) and len(names) == self.nc # apply names to ticklabels
|
| 993 |
+
with warnings.catch_warnings():
|
| 994 |
+
warnings.simplefilter('ignore') # suppress empty matrix RuntimeWarning: All-NaN slice encountered
|
| 995 |
+
sn.heatmap(array, annot=self.nc < 30, annot_kws={"size": 8}, cmap='Blues', fmt='.2f', square=True,
|
| 996 |
+
xticklabels=names + ['background FP'] if labels else "auto",
|
| 997 |
+
yticklabels=names + ['background FN'] if labels else "auto").set_facecolor((1, 1, 1))
|
| 998 |
+
fig.axes[0].set_xlabel('True')
|
| 999 |
+
fig.axes[0].set_ylabel('Predicted')
|
| 1000 |
+
fig.savefig(Path(save_dir) / 'confusion_matrix.png', dpi=250)
|
| 1001 |
+
plt.close()
|
| 1002 |
+
except Exception as e:
|
| 1003 |
+
print(f'WARNING: ConfusionMatrix plot failure: {e}')
|
| 1004 |
+
|
| 1005 |
+
def print(self):
|
| 1006 |
+
for i in range(self.nc + 1):
|
| 1007 |
+
print(' '.join(map(str, self.matrix[i])))
|
| 1008 |
+
|
| 1009 |
+
|
| 1010 |
def output_to_target(output):
|
| 1011 |
# Convert model output to target format [batch_id, class_id, x, y, w, h, conf]
|
| 1012 |
targets = []
|
|
|
|
| 1016 |
return np.array(targets)
|
| 1017 |
|
| 1018 |
|
| 1019 |
+
def plot_val_study(file='', dir='', x=None): # from utils.plots import *; plot_val_study()
|
| 1020 |
+
# Plot file=study.txt generated by val.py (or plot all study*.txt in dir)
|
| 1021 |
+
save_dir = Path(file).parent if file else Path(dir)
|
| 1022 |
+
plot2 = False # plot additional results
|
| 1023 |
+
if plot2:
|
| 1024 |
+
ax = plt.subplots(2, 4, figsize=(10, 6), tight_layout=True)[1].ravel()
|
| 1025 |
+
|
| 1026 |
+
fig2, ax2 = plt.subplots(1, 1, figsize=(8, 4), tight_layout=True)
|
| 1027 |
+
# for f in [save_dir / f'study_coco_{x}.txt' for x in ['yolov5n6', 'yolov5s6', 'yolov5m6', 'yolov5l6', 'yolov5x6']]:
|
| 1028 |
+
for f in sorted(save_dir.glob('study*.txt')):
|
| 1029 |
+
y = np.loadtxt(f, dtype=np.float32, usecols=[0, 1, 2, 3, 7, 8, 9], ndmin=2).T
|
| 1030 |
+
x = np.arange(y.shape[1]) if x is None else np.array(x)
|
| 1031 |
+
if plot2:
|
| 1032 |
+
s = ['P', 'R', 'mAP@.5', 'mAP@.5:.95', 't_preprocess (ms/img)', 't_inference (ms/img)', 't_NMS (ms/img)']
|
| 1033 |
+
for i in range(7):
|
| 1034 |
+
ax[i].plot(x, y[i], '.-', linewidth=2, markersize=8)
|
| 1035 |
+
ax[i].set_title(s[i])
|
| 1036 |
+
|
| 1037 |
+
j = y[3].argmax() + 1
|
| 1038 |
+
ax2.plot(y[5, 1:j], y[3, 1:j] * 1E2, '.-', linewidth=2, markersize=8,
|
| 1039 |
+
label=f.stem.replace('study_coco_', '').replace('yolo', 'YOLO'))
|
| 1040 |
+
|
| 1041 |
+
ax2.plot(1E3 / np.array([209, 140, 97, 58, 35, 18]), [34.6, 40.5, 43.0, 47.5, 49.7, 51.5],
|
| 1042 |
+
'k.-', linewidth=2, markersize=8, alpha=.25, label='EfficientDet')
|
| 1043 |
+
|
| 1044 |
+
ax2.grid(alpha=0.2)
|
| 1045 |
+
ax2.set_yticks(np.arange(20, 60, 5))
|
| 1046 |
+
ax2.set_xlim(0, 57)
|
| 1047 |
+
ax2.set_ylim(25, 55)
|
| 1048 |
+
ax2.set_xlabel('GPU Speed (ms/img)')
|
| 1049 |
+
ax2.set_ylabel('COCO AP val')
|
| 1050 |
+
ax2.legend(loc='lower right')
|
| 1051 |
+
f = save_dir / 'study.png'
|
| 1052 |
+
print(f'Saving {f}...')
|
| 1053 |
+
plt.savefig(f, dpi=300)
|
| 1054 |
+
|
| 1055 |
+
|
| 1056 |
def check_yaml(file, suffix=('.yaml', '.yml')):
|
| 1057 |
# Search/download YAML file (if necessary) and return path, checking suffix
|
| 1058 |
return check_file(file, suffix)
|
|
|
|
| 1062 |
# Search/download file (if necessary) and return path
|
| 1063 |
check_suffix(file, suffix) # optional
|
| 1064 |
file = str(file) # convert to str()
|
| 1065 |
+
if Path(file).is_file() or file == '': # exists
|
| 1066 |
+
return file
|
| 1067 |
+
elif file.startswith(('http:/', 'https:/')): # download
|
| 1068 |
+
url = str(Path(file)).replace(':/', '://') # Pathlib turns :// -> :/
|
| 1069 |
+
file = Path(urllib.parse.unquote(file).split('?')[0]).name # '%2F' to '/', split https://url.com/file.txt?auth
|
| 1070 |
+
print(f'Downloading {url} to {file}...')
|
| 1071 |
+
torch.hub.download_url_to_file(url, file)
|
| 1072 |
+
assert Path(file).exists() and Path(file).stat().st_size > 0, f'File download failed: {url}' # check
|
| 1073 |
+
return file
|
| 1074 |
+
else: # search
|
| 1075 |
+
files = []
|
| 1076 |
+
for d in 'data', 'models', 'utils': # search directories
|
| 1077 |
+
files.extend(glob.glob(str(ROOT / d / '**' / file), recursive=True)) # find file
|
| 1078 |
+
assert len(files), f'File not found: {file}' # assert file was found
|
| 1079 |
+
assert len(files) == 1, f"Multiple files match '{file}', specify exact path: {files}" # assert unique
|
| 1080 |
+
return files[0] # return file
|
| 1081 |
|
| 1082 |
|
| 1083 |
def check_suffix(file='yolov5s.pt', suffix=('.pt',), msg=''):
|
yolov5s_qat.onnx
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5f05e2860614a4d10757405f5e4ad2849d380631e16915f91aa0f69597d10575
|
| 3 |
+
size 29142007
|