Merge branch 'master' into advanced_logging
Browse files- detect.py +3 -4
- models/experimental.py +21 -1
- models/export.py +2 -1
- requirements.txt +1 -1
- test.py +12 -15
- train.py +10 -12
- utils/datasets.py +3 -3
- utils/utils.py +1 -1
detect.py
CHANGED
@@ -2,7 +2,7 @@ import argparse
|
|
2 |
|
3 |
import torch.backends.cudnn as cudnn
|
4 |
|
5 |
-
from
|
6 |
from utils.datasets import *
|
7 |
from utils.utils import *
|
8 |
|
@@ -20,8 +20,7 @@ def detect(save_img=False):
|
|
20 |
half = device.type != 'cpu' # half precision only supported on CUDA
|
21 |
|
22 |
# Load model
|
23 |
-
|
24 |
-
model = torch.load(weights, map_location=device)['model'].float().eval() # load FP32 model
|
25 |
imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size
|
26 |
if half:
|
27 |
model.half() # to FP16
|
@@ -137,7 +136,7 @@ def detect(save_img=False):
|
|
137 |
|
138 |
if __name__ == '__main__':
|
139 |
parser = argparse.ArgumentParser()
|
140 |
-
parser.add_argument('--weights', type=str, default='
|
141 |
parser.add_argument('--source', type=str, default='inference/images', help='source') # file/folder, 0 for webcam
|
142 |
parser.add_argument('--output', type=str, default='inference/output', help='output folder') # output folder
|
143 |
parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
|
|
|
2 |
|
3 |
import torch.backends.cudnn as cudnn
|
4 |
|
5 |
+
from models.experimental import *
|
6 |
from utils.datasets import *
|
7 |
from utils.utils import *
|
8 |
|
|
|
20 |
half = device.type != 'cpu' # half precision only supported on CUDA
|
21 |
|
22 |
# Load model
|
23 |
+
model = attempt_load(weights, map_location=device) # load FP32 model
|
|
|
24 |
imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size
|
25 |
if half:
|
26 |
model.half() # to FP16
|
|
|
136 |
|
137 |
if __name__ == '__main__':
|
138 |
parser = argparse.ArgumentParser()
|
139 |
+
parser.add_argument('--weights', nargs='+', type=str, default='yolov5s.pt', help='model.pt path(s)')
|
140 |
parser.add_argument('--source', type=str, default='inference/images', help='source') # file/folder, 0 for webcam
|
141 |
parser.add_argument('--output', type=str, default='inference/output', help='output folder') # output folder
|
142 |
parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
|
models/experimental.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
# This file contains experimental modules
|
2 |
|
3 |
from models.common import *
|
|
|
4 |
|
5 |
|
6 |
class CrossConv(nn.Module):
|
@@ -118,4 +119,23 @@ class Ensemble(nn.ModuleList):
|
|
118 |
y = []
|
119 |
for module in self:
|
120 |
y.append(module(x, augment)[0])
|
121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
# This file contains experimental modules
|
2 |
|
3 |
from models.common import *
|
4 |
+
from utils import google_utils
|
5 |
|
6 |
|
7 |
class CrossConv(nn.Module):
|
|
|
119 |
y = []
|
120 |
for module in self:
|
121 |
y.append(module(x, augment)[0])
|
122 |
+
# y = torch.stack(y).max(0)[0] # max ensemble
|
123 |
+
# y = torch.cat(y, 1) # nms ensemble
|
124 |
+
y = torch.stack(y).mean(0) # mean ensemble
|
125 |
+
return y, None # inference, train output
|
126 |
+
|
127 |
+
|
128 |
+
def attempt_load(weights, map_location=None):
|
129 |
+
# Loads an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a
|
130 |
+
model = Ensemble()
|
131 |
+
for w in weights if isinstance(weights, list) else [weights]:
|
132 |
+
google_utils.attempt_download(w)
|
133 |
+
model.append(torch.load(w, map_location=map_location)['model'].float().fuse().eval()) # load FP32 model
|
134 |
+
|
135 |
+
if len(model) == 1:
|
136 |
+
return model[-1] # return model
|
137 |
+
else:
|
138 |
+
print('Ensemble created with %s\n' % weights)
|
139 |
+
for k in ['names', 'stride']:
|
140 |
+
setattr(model, k, getattr(model[-1], k))
|
141 |
+
return model # return ensemble
|
models/export.py
CHANGED
@@ -61,7 +61,8 @@ if __name__ == '__main__':
|
|
61 |
import coremltools as ct
|
62 |
|
63 |
print('\nStarting CoreML export with coremltools %s...' % ct.__version__)
|
64 |
-
model
|
|
|
65 |
f = opt.weights.replace('.pt', '.mlmodel') # filename
|
66 |
model.save(f)
|
67 |
print('CoreML export success, saved as %s' % f)
|
|
|
61 |
import coremltools as ct
|
62 |
|
63 |
print('\nStarting CoreML export with coremltools %s...' % ct.__version__)
|
64 |
+
# convert model from torchscript and apply pixel scaling as per detect.py
|
65 |
+
model = ct.convert(ts, inputs=[ct.ImageType(name='images', shape=img.shape, scale=1/255.0, bias=[0, 0, 0])])
|
66 |
f = opt.weights.replace('.pt', '.mlmodel') # filename
|
67 |
model.save(f)
|
68 |
print('CoreML export success, saved as %s' % f)
|
requirements.txt
CHANGED
@@ -2,7 +2,7 @@
|
|
2 |
Cython
|
3 |
numpy==1.17
|
4 |
opencv-python
|
5 |
-
torch>=1.
|
6 |
matplotlib
|
7 |
pillow
|
8 |
tensorboard
|
|
|
2 |
Cython
|
3 |
numpy==1.17
|
4 |
opencv-python
|
5 |
+
torch>=1.5.1
|
6 |
matplotlib
|
7 |
pillow
|
8 |
tensorboard
|
test.py
CHANGED
@@ -1,9 +1,8 @@
|
|
1 |
import argparse
|
2 |
import json
|
3 |
|
4 |
-
from
|
5 |
from utils.datasets import *
|
6 |
-
from utils.utils import *
|
7 |
|
8 |
|
9 |
def test(data,
|
@@ -22,28 +21,26 @@ def test(data,
|
|
22 |
merge=False):
|
23 |
|
24 |
# Initialize/load model and set device
|
25 |
-
|
26 |
-
|
27 |
-
|
|
|
|
|
28 |
device = torch_utils.select_device(opt.device, batch_size=batch_size)
|
|
|
29 |
|
30 |
# Remove previous
|
31 |
for f in glob.glob(str(Path(save_dir) / 'test_batch*.jpg')):
|
32 |
os.remove(f)
|
33 |
|
34 |
# Load model
|
35 |
-
|
36 |
-
model = torch.load(weights, map_location=device)['model'].float().fuse().to(device) # load to FP32
|
37 |
imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size
|
38 |
|
39 |
# Multi-GPU disabled, incompatible with .half() https://github.com/ultralytics/yolov5/issues/99
|
40 |
# if device.type != 'cpu' and torch.cuda.device_count() > 1:
|
41 |
# model = nn.DataParallel(model)
|
42 |
|
43 |
-
else: # called by train.py
|
44 |
-
training = True
|
45 |
-
device = next(model.parameters()).device # get model device
|
46 |
-
|
47 |
# Half
|
48 |
half = device.type != 'cpu' and torch.cuda.device_count() == 1 # half precision only supported on single-GPU
|
49 |
if half:
|
@@ -58,11 +55,11 @@ def test(data,
|
|
58 |
niou = iouv.numel()
|
59 |
|
60 |
# Dataloader
|
61 |
-
if
|
62 |
img = torch.zeros((1, 3, imgsz, imgsz), device=device) # init img
|
63 |
_ = model(img.half() if half else img) if device.type != 'cpu' else None # run once
|
64 |
path = data['test'] if opt.task == 'test' else data['val'] # path to val/test images
|
65 |
-
dataloader = create_dataloader(path, imgsz, batch_size,
|
66 |
hyp=None, augment=False, cache=False, pad=0.5, rect=True)[0]
|
67 |
|
68 |
seen = 0
|
@@ -195,7 +192,7 @@ def test(data,
|
|
195 |
if save_json and map50 and len(jdict):
|
196 |
imgIds = [int(Path(x).stem.split('_')[-1]) for x in dataloader.dataset.img_files]
|
197 |
f = 'detections_val2017_%s_results.json' % \
|
198 |
-
(weights.split(os.sep)[-1].replace('.pt', '') if weights else '') # filename
|
199 |
print('\nCOCO mAP with pycocotools... saving %s...' % f)
|
200 |
with open(f, 'w') as file:
|
201 |
json.dump(jdict, file)
|
@@ -228,7 +225,7 @@ def test(data,
|
|
228 |
|
229 |
if __name__ == '__main__':
|
230 |
parser = argparse.ArgumentParser(prog='test.py')
|
231 |
-
parser.add_argument('--weights', type=str, default='
|
232 |
parser.add_argument('--data', type=str, default='data/coco128.yaml', help='*.data path')
|
233 |
parser.add_argument('--batch-size', type=int, default=32, help='size of each image batch')
|
234 |
parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
|
|
|
1 |
import argparse
|
2 |
import json
|
3 |
|
4 |
+
from models.experimental import *
|
5 |
from utils.datasets import *
|
|
|
6 |
|
7 |
|
8 |
def test(data,
|
|
|
21 |
merge=False):
|
22 |
|
23 |
# Initialize/load model and set device
|
24 |
+
training = model is not None
|
25 |
+
if training: # called by train.py
|
26 |
+
device = next(model.parameters()).device # get model device
|
27 |
+
|
28 |
+
else: # called directly
|
29 |
device = torch_utils.select_device(opt.device, batch_size=batch_size)
|
30 |
+
merge = opt.merge # use Merge NMS
|
31 |
|
32 |
# Remove previous
|
33 |
for f in glob.glob(str(Path(save_dir) / 'test_batch*.jpg')):
|
34 |
os.remove(f)
|
35 |
|
36 |
# Load model
|
37 |
+
model = attempt_load(weights, map_location=device) # load FP32 model
|
|
|
38 |
imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size
|
39 |
|
40 |
# Multi-GPU disabled, incompatible with .half() https://github.com/ultralytics/yolov5/issues/99
|
41 |
# if device.type != 'cpu' and torch.cuda.device_count() > 1:
|
42 |
# model = nn.DataParallel(model)
|
43 |
|
|
|
|
|
|
|
|
|
44 |
# Half
|
45 |
half = device.type != 'cpu' and torch.cuda.device_count() == 1 # half precision only supported on single-GPU
|
46 |
if half:
|
|
|
55 |
niou = iouv.numel()
|
56 |
|
57 |
# Dataloader
|
58 |
+
if not training:
|
59 |
img = torch.zeros((1, 3, imgsz, imgsz), device=device) # init img
|
60 |
_ = model(img.half() if half else img) if device.type != 'cpu' else None # run once
|
61 |
path = data['test'] if opt.task == 'test' else data['val'] # path to val/test images
|
62 |
+
dataloader = create_dataloader(path, imgsz, batch_size, model.stride.max(), opt,
|
63 |
hyp=None, augment=False, cache=False, pad=0.5, rect=True)[0]
|
64 |
|
65 |
seen = 0
|
|
|
192 |
if save_json and map50 and len(jdict):
|
193 |
imgIds = [int(Path(x).stem.split('_')[-1]) for x in dataloader.dataset.img_files]
|
194 |
f = 'detections_val2017_%s_results.json' % \
|
195 |
+
(weights.split(os.sep)[-1].replace('.pt', '') if isinstance(weights, str) else '') # filename
|
196 |
print('\nCOCO mAP with pycocotools... saving %s...' % f)
|
197 |
with open(f, 'w') as file:
|
198 |
json.dump(jdict, file)
|
|
|
225 |
|
226 |
if __name__ == '__main__':
|
227 |
parser = argparse.ArgumentParser(prog='test.py')
|
228 |
+
parser.add_argument('--weights', nargs='+', type=str, default='yolov5s.pt', help='model.pt path(s)')
|
229 |
parser.add_argument('--data', type=str, default='data/coco128.yaml', help='*.data path')
|
230 |
parser.add_argument('--batch-size', type=int, default=32, help='size of each image batch')
|
231 |
parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
|
train.py
CHANGED
@@ -96,11 +96,13 @@ def train(hyp):
|
|
96 |
|
97 |
optimizer.add_param_group({'params': pg1, 'weight_decay': hyp['weight_decay']}) # add pg1 with weight_decay
|
98 |
optimizer.add_param_group({'params': pg2}) # add pg2 (biases)
|
|
|
|
|
|
|
99 |
# Scheduler https://arxiv.org/pdf/1812.01187.pdf
|
100 |
lf = lambda x: (((1 + math.cos(x * math.pi / epochs)) / 2) ** 1.0) * 0.9 + 0.1 # cosine
|
101 |
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)
|
102 |
-
|
103 |
-
del pg0, pg1, pg2
|
104 |
|
105 |
# Load Model
|
106 |
google_utils.attempt_download(weights)
|
@@ -142,12 +144,7 @@ def train(hyp):
|
|
142 |
if mixed_precision:
|
143 |
model, optimizer = amp.initialize(model, optimizer, opt_level='O1', verbosity=0)
|
144 |
|
145 |
-
|
146 |
-
scheduler.last_epoch = start_epoch - 1 # do not move
|
147 |
-
# https://discuss.pytorch.org/t/a-problem-occured-when-resuming-an-optimizer/28822
|
148 |
-
plot_lr_scheduler(optimizer, scheduler, epochs, save_dir=log_dir)
|
149 |
-
|
150 |
-
# Initialize distributed training
|
151 |
if device.type != 'cpu' and torch.cuda.device_count() > 1 and torch.distributed.is_available():
|
152 |
dist.init_process_group(backend='nccl', # distributed backend
|
153 |
init_method='tcp://127.0.0.1:9999', # init method
|
@@ -199,9 +196,10 @@ def train(hyp):
|
|
199 |
# Start training
|
200 |
t0 = time.time()
|
201 |
nb = len(dataloader) # number of batches
|
202 |
-
|
203 |
maps = np.zeros(nc) # mAP per class
|
204 |
results = (0, 0, 0, 0, 0, 0, 0) # 'P', 'R', 'mAP', 'F1', 'val GIoU', 'val Objectness', 'val Classification'
|
|
|
205 |
print('Image sizes %g train, %g test' % (imgsz, imgsz_test))
|
206 |
print('Using %g dataloader workers' % dataloader.num_workers)
|
207 |
print('Starting training for %g epochs...' % epochs)
|
@@ -226,9 +224,9 @@ def train(hyp):
|
|
226 |
ni = i + nb * epoch # number integrated batches (since train start)
|
227 |
imgs = imgs.to(device).float() / 255.0 # uint8 to float32, 0 - 255 to 0.0 - 1.0
|
228 |
|
229 |
-
#
|
230 |
-
if ni <=
|
231 |
-
xi = [0,
|
232 |
# model.gr = np.interp(ni, xi, [0.0, 1.0]) # giou loss ratio (obj_loss = 1.0 or giou)
|
233 |
accumulate = max(1, np.interp(ni, xi, [1, nbs / batch_size]).round())
|
234 |
for j, x in enumerate(optimizer.param_groups):
|
|
|
96 |
|
97 |
optimizer.add_param_group({'params': pg1, 'weight_decay': hyp['weight_decay']}) # add pg1 with weight_decay
|
98 |
optimizer.add_param_group({'params': pg2}) # add pg2 (biases)
|
99 |
+
print('Optimizer groups: %g .bias, %g conv.weight, %g other' % (len(pg2), len(pg1), len(pg0)))
|
100 |
+
del pg0, pg1, pg2
|
101 |
+
|
102 |
# Scheduler https://arxiv.org/pdf/1812.01187.pdf
|
103 |
lf = lambda x: (((1 + math.cos(x * math.pi / epochs)) / 2) ** 1.0) * 0.9 + 0.1 # cosine
|
104 |
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)
|
105 |
+
plot_lr_scheduler(optimizer, scheduler, epochs, save_dir=log_dir)
|
|
|
106 |
|
107 |
# Load Model
|
108 |
google_utils.attempt_download(weights)
|
|
|
144 |
if mixed_precision:
|
145 |
model, optimizer = amp.initialize(model, optimizer, opt_level='O1', verbosity=0)
|
146 |
|
147 |
+
# Distributed training
|
|
|
|
|
|
|
|
|
|
|
148 |
if device.type != 'cpu' and torch.cuda.device_count() > 1 and torch.distributed.is_available():
|
149 |
dist.init_process_group(backend='nccl', # distributed backend
|
150 |
init_method='tcp://127.0.0.1:9999', # init method
|
|
|
196 |
# Start training
|
197 |
t0 = time.time()
|
198 |
nb = len(dataloader) # number of batches
|
199 |
+
nw = max(3 * nb, 1e3) # number of warmup iterations, max(3 epochs, 1k iterations)
|
200 |
maps = np.zeros(nc) # mAP per class
|
201 |
results = (0, 0, 0, 0, 0, 0, 0) # 'P', 'R', 'mAP', 'F1', 'val GIoU', 'val Objectness', 'val Classification'
|
202 |
+
scheduler.last_epoch = start_epoch - 1 # do not move
|
203 |
print('Image sizes %g train, %g test' % (imgsz, imgsz_test))
|
204 |
print('Using %g dataloader workers' % dataloader.num_workers)
|
205 |
print('Starting training for %g epochs...' % epochs)
|
|
|
224 |
ni = i + nb * epoch # number integrated batches (since train start)
|
225 |
imgs = imgs.to(device).float() / 255.0 # uint8 to float32, 0 - 255 to 0.0 - 1.0
|
226 |
|
227 |
+
# Warmup
|
228 |
+
if ni <= nw:
|
229 |
+
xi = [0, nw] # x interp
|
230 |
# model.gr = np.interp(ni, xi, [0.0, 1.0]) # giou loss ratio (obj_loss = 1.0 or giou)
|
231 |
accumulate = max(1, np.interp(ni, xi, [1, nbs / batch_size]).round())
|
232 |
for j, x in enumerate(optimizer.param_groups):
|
utils/datasets.py
CHANGED
@@ -48,7 +48,7 @@ def create_dataloader(path, imgsz, batch_size, stride, opt, hyp=None, augment=Fa
|
|
48 |
rect=rect, # rectangular training
|
49 |
cache_images=cache,
|
50 |
single_cls=opt.single_cls,
|
51 |
-
stride=stride,
|
52 |
pad=pad)
|
53 |
|
54 |
batch_size = min(batch_size, len(dataset))
|
@@ -679,8 +679,8 @@ def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scale
|
|
679 |
dw, dh = np.mod(dw, 64), np.mod(dh, 64) # wh padding
|
680 |
elif scaleFill: # stretch
|
681 |
dw, dh = 0.0, 0.0
|
682 |
-
new_unpad = new_shape
|
683 |
-
ratio = new_shape[
|
684 |
|
685 |
dw /= 2 # divide padding into 2 sides
|
686 |
dh /= 2
|
|
|
48 |
rect=rect, # rectangular training
|
49 |
cache_images=cache,
|
50 |
single_cls=opt.single_cls,
|
51 |
+
stride=int(stride),
|
52 |
pad=pad)
|
53 |
|
54 |
batch_size = min(batch_size, len(dataset))
|
|
|
679 |
dw, dh = np.mod(dw, 64), np.mod(dh, 64) # wh padding
|
680 |
elif scaleFill: # stretch
|
681 |
dw, dh = 0.0, 0.0
|
682 |
+
new_unpad = (new_shape[1], new_shape[0])
|
683 |
+
ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios
|
684 |
|
685 |
dw /= 2 # divide padding into 2 sides
|
686 |
dh /= 2
|
utils/utils.py
CHANGED
@@ -179,7 +179,7 @@ def xywh2xyxy(x):
|
|
179 |
def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):
|
180 |
# Rescale coords (xyxy) from img1_shape to img0_shape
|
181 |
if ratio_pad is None: # calculate from img0_shape
|
182 |
-
gain =
|
183 |
pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding
|
184 |
else:
|
185 |
gain = ratio_pad[0][0]
|
|
|
179 |
def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):
|
180 |
# Rescale coords (xyxy) from img1_shape to img0_shape
|
181 |
if ratio_pad is None: # calculate from img0_shape
|
182 |
+
gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1]) # gain = old / new
|
183 |
pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding
|
184 |
else:
|
185 |
gain = ratio_pad[0][0]
|