Simplified inference (#1153)
Browse files- detect.py +2 -2
- hubconf.py +1 -4
- models/common.py +59 -5
- models/yolo.py +20 -11
- sotabench.py +3 -6
- utils/datasets.py +1 -1
- utils/torch_utils.py +1 -1
detect.py
CHANGED
@@ -149,8 +149,8 @@ if __name__ == '__main__':
|
|
149 |
parser.add_argument('--source', type=str, default='inference/images', help='source') # file/folder, 0 for webcam
|
150 |
parser.add_argument('--output', type=str, default='inference/output', help='output folder') # output folder
|
151 |
parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
|
152 |
-
parser.add_argument('--conf-thres', type=float, default=0.
|
153 |
-
parser.add_argument('--iou-thres', type=float, default=0.
|
154 |
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
|
155 |
parser.add_argument('--view-img', action='store_true', help='display results')
|
156 |
parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
|
|
|
149 |
parser.add_argument('--source', type=str, default='inference/images', help='source') # file/folder, 0 for webcam
|
150 |
parser.add_argument('--output', type=str, default='inference/output', help='output folder') # output folder
|
151 |
parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
|
152 |
+
parser.add_argument('--conf-thres', type=float, default=0.25, help='object confidence threshold')
|
153 |
+
parser.add_argument('--iou-thres', type=float, default=0.45, help='IOU threshold for NMS')
|
154 |
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
|
155 |
parser.add_argument('--view-img', action='store_true', help='display results')
|
156 |
parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
|
hubconf.py
CHANGED
@@ -10,7 +10,6 @@ import os
|
|
10 |
|
11 |
import torch
|
12 |
|
13 |
-
from models.common import NMS
|
14 |
from models.yolo import Model
|
15 |
from utils.google_utils import attempt_download
|
16 |
|
@@ -36,9 +35,7 @@ def create(name, pretrained, channels, classes):
|
|
36 |
state_dict = torch.load(ckpt, map_location=torch.device('cpu'))['model'].float().state_dict() # to FP32
|
37 |
state_dict = {k: v for k, v in state_dict.items() if model.state_dict()[k].shape == v.shape} # filter
|
38 |
model.load_state_dict(state_dict, strict=False) # load
|
39 |
-
|
40 |
-
model.add_nms() # add NMS module
|
41 |
-
model.eval()
|
42 |
return model
|
43 |
|
44 |
except Exception as e:
|
|
|
10 |
|
11 |
import torch
|
12 |
|
|
|
13 |
from models.yolo import Model
|
14 |
from utils.google_utils import attempt_download
|
15 |
|
|
|
35 |
state_dict = torch.load(ckpt, map_location=torch.device('cpu'))['model'].float().state_dict() # to FP32
|
36 |
state_dict = {k: v for k, v in state_dict.items() if model.state_dict()[k].shape == v.shape} # filter
|
37 |
model.load_state_dict(state_dict, strict=False) # load
|
38 |
+
# model = model.autoshape() # cv2/PIL/np/torch inference: predictions = model(Image.open('image.jpg'))
|
|
|
|
|
39 |
return model
|
40 |
|
41 |
except Exception as e:
|
models/common.py
CHANGED
@@ -1,9 +1,12 @@
|
|
1 |
# This file contains modules common to various models
|
2 |
-
import math
|
3 |
|
|
|
|
|
4 |
import torch
|
5 |
import torch.nn as nn
|
6 |
-
|
|
|
|
|
7 |
|
8 |
|
9 |
def autopad(k, p=None): # kernel, padding
|
@@ -101,17 +104,68 @@ class Concat(nn.Module):
|
|
101 |
|
102 |
class NMS(nn.Module):
|
103 |
# Non-Maximum Suppression (NMS) module
|
104 |
-
conf = 0.
|
105 |
-
iou = 0.
|
106 |
classes = None # (optional list) filter by class
|
107 |
|
108 |
-
def __init__(self
|
109 |
super(NMS, self).__init__()
|
110 |
|
111 |
def forward(self, x):
|
112 |
return non_max_suppression(x[0], conf_thres=self.conf, iou_thres=self.iou, classes=self.classes)
|
113 |
|
114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
class Flatten(nn.Module):
|
116 |
# Use after nn.AdaptiveAvgPool2d(1) to remove last 2 dimensions
|
117 |
@staticmethod
|
|
|
1 |
# This file contains modules common to various models
|
|
|
2 |
|
3 |
+
import math
|
4 |
+
import numpy as np
|
5 |
import torch
|
6 |
import torch.nn as nn
|
7 |
+
|
8 |
+
from utils.datasets import letterbox
|
9 |
+
from utils.general import non_max_suppression, make_divisible, scale_coords
|
10 |
|
11 |
|
12 |
def autopad(k, p=None): # kernel, padding
|
|
|
104 |
|
105 |
class NMS(nn.Module):
|
106 |
# Non-Maximum Suppression (NMS) module
|
107 |
+
conf = 0.25 # confidence threshold
|
108 |
+
iou = 0.45 # IoU threshold
|
109 |
classes = None # (optional list) filter by class
|
110 |
|
111 |
+
def __init__(self):
|
112 |
super(NMS, self).__init__()
|
113 |
|
114 |
def forward(self, x):
|
115 |
return non_max_suppression(x[0], conf_thres=self.conf, iou_thres=self.iou, classes=self.classes)
|
116 |
|
117 |
|
118 |
+
class autoShape(nn.Module):
|
119 |
+
# input-robust model wrapper for passing cv2/np/PIL/torch inputs. Includes preprocessing, inference and NMS
|
120 |
+
img_size = 640 # inference size (pixels)
|
121 |
+
conf = 0.25 # NMS confidence threshold
|
122 |
+
iou = 0.45 # NMS IoU threshold
|
123 |
+
classes = None # (optional list) filter by class
|
124 |
+
|
125 |
+
def __init__(self, model):
|
126 |
+
super(autoShape, self).__init__()
|
127 |
+
self.model = model
|
128 |
+
|
129 |
+
def forward(self, x, size=640, augment=False, profile=False):
|
130 |
+
# supports inference from various sources. For height=720, width=1280, RGB images example inputs are:
|
131 |
+
# opencv: x = cv2.imread('image.jpg')[:,:,::-1] # HWC BGR to RGB x(720,1280,3)
|
132 |
+
# PIL: x = Image.open('image.jpg') # HWC x(720,1280,3)
|
133 |
+
# numpy: x = np.zeros((720,1280,3)) # HWC
|
134 |
+
# torch: x = torch.zeros(16,3,720,1280) # BCHW
|
135 |
+
# multiple: x = [Image.open('image1.jpg'), Image.open('image2.jpg'), ...] # list of images
|
136 |
+
|
137 |
+
p = next(self.model.parameters()) # for device and type
|
138 |
+
if isinstance(x, torch.Tensor): # torch
|
139 |
+
return self.model(x.to(p.device).type_as(p), augment, profile) # inference
|
140 |
+
|
141 |
+
# Pre-process
|
142 |
+
if not isinstance(x, list):
|
143 |
+
x = [x]
|
144 |
+
shape0, shape1 = [], [] # image and inference shapes
|
145 |
+
batch = range(len(x)) # batch size
|
146 |
+
for i in batch:
|
147 |
+
x[i] = np.array(x[i])[:, :, :3] # up to 3 channels if png
|
148 |
+
s = x[i].shape[:2] # HWC
|
149 |
+
shape0.append(s) # image shape
|
150 |
+
g = (size / max(s)) # gain
|
151 |
+
shape1.append([y * g for y in s])
|
152 |
+
shape1 = [make_divisible(x, int(self.stride.max())) for x in np.stack(shape1, 0).max(0)] # inference shape
|
153 |
+
x = [letterbox(x[i], new_shape=shape1, auto=False)[0] for i in batch] # pad
|
154 |
+
x = np.stack(x, 0) if batch[-1] else x[0][None] # stack
|
155 |
+
x = np.ascontiguousarray(x.transpose((0, 3, 1, 2))) # BHWC to BCHW
|
156 |
+
x = torch.from_numpy(x).to(p.device).type_as(p) / 255. # uint8 to fp16/32
|
157 |
+
|
158 |
+
# Inference
|
159 |
+
x = self.model(x, augment, profile) # forward
|
160 |
+
x = non_max_suppression(x[0], conf_thres=self.conf, iou_thres=self.iou, classes=self.classes) # NMS
|
161 |
+
|
162 |
+
# Post-process
|
163 |
+
for i in batch:
|
164 |
+
if x[i] is not None:
|
165 |
+
x[i][:, :4] = scale_coords(shape1, x[i][:, :4], shape0[i])
|
166 |
+
return x
|
167 |
+
|
168 |
+
|
169 |
class Flatten(nn.Module):
|
170 |
# Use after nn.AdaptiveAvgPool2d(1) to remove last 2 dimensions
|
171 |
@staticmethod
|
models/yolo.py
CHANGED
@@ -1,21 +1,22 @@
|
|
1 |
import argparse
|
2 |
import logging
|
3 |
-
import math
|
4 |
import sys
|
5 |
from copy import deepcopy
|
6 |
from pathlib import Path
|
7 |
|
|
|
|
|
8 |
sys.path.append('./') # to run '$ python *.py' files in subdirectories
|
9 |
logger = logging.getLogger(__name__)
|
10 |
|
11 |
import torch
|
12 |
import torch.nn as nn
|
13 |
|
14 |
-
from models.common import Conv, Bottleneck, SPP, DWConv, Focus, BottleneckCSP, Concat, NMS
|
15 |
from models.experimental import MixConv2d, CrossConv, C3
|
16 |
from utils.general import check_anchor_order, make_divisible, check_file, set_logging
|
17 |
-
from utils.torch_utils import
|
18 |
-
|
19 |
|
20 |
|
21 |
class Detect(nn.Module):
|
@@ -140,6 +141,7 @@ class Model(nn.Module):
|
|
140 |
return x
|
141 |
|
142 |
def _initialize_biases(self, cf=None): # initialize biases into Detect(), cf is class frequency
|
|
|
143 |
# cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1.
|
144 |
m = self.model[-1] # Detect() module
|
145 |
for mi, s in zip(m.m, m.stride): # from
|
@@ -170,15 +172,26 @@ class Model(nn.Module):
|
|
170 |
self.info()
|
171 |
return self
|
172 |
|
173 |
-
def
|
174 |
-
|
175 |
-
|
|
|
176 |
m = NMS() # module
|
177 |
m.f = -1 # from
|
178 |
m.i = self.model[-1].i + 1 # index
|
179 |
self.model.add_module(name='%s' % m.i, module=m) # add
|
|
|
|
|
|
|
|
|
180 |
return self
|
181 |
|
|
|
|
|
|
|
|
|
|
|
|
|
182 |
def info(self, verbose=False): # print model information
|
183 |
model_info(self, verbose)
|
184 |
|
@@ -263,10 +276,6 @@ if __name__ == '__main__':
|
|
263 |
# img = torch.rand(8 if torch.cuda.is_available() else 1, 3, 640, 640).to(device)
|
264 |
# y = model(img, profile=True)
|
265 |
|
266 |
-
# ONNX export
|
267 |
-
# model.model[-1].export = True
|
268 |
-
# torch.onnx.export(model, img, opt.cfg.replace('.yaml', '.onnx'), verbose=True, opset_version=11)
|
269 |
-
|
270 |
# Tensorboard
|
271 |
# from torch.utils.tensorboard import SummaryWriter
|
272 |
# tb_writer = SummaryWriter()
|
|
|
1 |
import argparse
|
2 |
import logging
|
|
|
3 |
import sys
|
4 |
from copy import deepcopy
|
5 |
from pathlib import Path
|
6 |
|
7 |
+
import math
|
8 |
+
|
9 |
sys.path.append('./') # to run '$ python *.py' files in subdirectories
|
10 |
logger = logging.getLogger(__name__)
|
11 |
|
12 |
import torch
|
13 |
import torch.nn as nn
|
14 |
|
15 |
+
from models.common import Conv, Bottleneck, SPP, DWConv, Focus, BottleneckCSP, Concat, NMS, autoShape
|
16 |
from models.experimental import MixConv2d, CrossConv, C3
|
17 |
from utils.general import check_anchor_order, make_divisible, check_file, set_logging
|
18 |
+
from utils.torch_utils import time_synchronized, fuse_conv_and_bn, model_info, scale_img, initialize_weights, \
|
19 |
+
select_device, copy_attr
|
20 |
|
21 |
|
22 |
class Detect(nn.Module):
|
|
|
141 |
return x
|
142 |
|
143 |
def _initialize_biases(self, cf=None): # initialize biases into Detect(), cf is class frequency
|
144 |
+
# https://arxiv.org/abs/1708.02002 section 3.3
|
145 |
# cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1.
|
146 |
m = self.model[-1] # Detect() module
|
147 |
for mi, s in zip(m.m, m.stride): # from
|
|
|
172 |
self.info()
|
173 |
return self
|
174 |
|
175 |
+
def nms(self, mode=True): # add or remove NMS module
|
176 |
+
present = type(self.model[-1]) is NMS # last layer is NMS
|
177 |
+
if mode and not present:
|
178 |
+
print('Adding NMS... ')
|
179 |
m = NMS() # module
|
180 |
m.f = -1 # from
|
181 |
m.i = self.model[-1].i + 1 # index
|
182 |
self.model.add_module(name='%s' % m.i, module=m) # add
|
183 |
+
self.eval()
|
184 |
+
elif not mode and present:
|
185 |
+
print('Removing NMS... ')
|
186 |
+
self.model = self.model[:-1] # remove
|
187 |
return self
|
188 |
|
189 |
+
def autoshape(self): # add autoShape module
|
190 |
+
print('Adding autoShape... ')
|
191 |
+
m = autoShape(self) # wrap model
|
192 |
+
copy_attr(m, self, include=('yaml', 'nc', 'hyp', 'names', 'stride'), exclude=()) # copy attributes
|
193 |
+
return m
|
194 |
+
|
195 |
def info(self, verbose=False): # print model information
|
196 |
model_info(self, verbose)
|
197 |
|
|
|
276 |
# img = torch.rand(8 if torch.cuda.is_available() else 1, 3, 640, 640).to(device)
|
277 |
# y = model(img, profile=True)
|
278 |
|
|
|
|
|
|
|
|
|
279 |
# Tensorboard
|
280 |
# from torch.utils.tensorboard import SummaryWriter
|
281 |
# tb_writer = SummaryWriter()
|
sotabench.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1 |
import argparse
|
2 |
import glob
|
3 |
-
import json
|
4 |
import os
|
5 |
import shutil
|
6 |
from pathlib import Path
|
@@ -8,19 +7,17 @@ from pathlib import Path
|
|
8 |
import numpy as np
|
9 |
import torch
|
10 |
import yaml
|
|
|
|
|
11 |
from tqdm import tqdm
|
12 |
|
13 |
from models.experimental import attempt_load
|
14 |
from utils.datasets import create_dataloader
|
15 |
from utils.general import (
|
16 |
coco80_to_coco91_class, check_dataset, check_file, check_img_size, compute_loss, non_max_suppression, scale_coords,
|
17 |
-
xyxy2xywh, clip_coords,
|
18 |
from utils.torch_utils import select_device, time_synchronized
|
19 |
|
20 |
-
|
21 |
-
from sotabencheval.object_detection import COCOEvaluator
|
22 |
-
from sotabencheval.utils import is_server
|
23 |
-
|
24 |
DATA_ROOT = './.data/vision/coco' if is_server() else '../coco' # sotabench data dir
|
25 |
|
26 |
|
|
|
1 |
import argparse
|
2 |
import glob
|
|
|
3 |
import os
|
4 |
import shutil
|
5 |
from pathlib import Path
|
|
|
7 |
import numpy as np
|
8 |
import torch
|
9 |
import yaml
|
10 |
+
from sotabencheval.object_detection import COCOEvaluator
|
11 |
+
from sotabencheval.utils import is_server
|
12 |
from tqdm import tqdm
|
13 |
|
14 |
from models.experimental import attempt_load
|
15 |
from utils.datasets import create_dataloader
|
16 |
from utils.general import (
|
17 |
coco80_to_coco91_class, check_dataset, check_file, check_img_size, compute_loss, non_max_suppression, scale_coords,
|
18 |
+
xyxy2xywh, clip_coords, set_logging)
|
19 |
from utils.torch_utils import select_device, time_synchronized
|
20 |
|
|
|
|
|
|
|
|
|
21 |
DATA_ROOT = './.data/vision/coco' if is_server() else '../coco' # sotabench data dir
|
22 |
|
23 |
|
utils/datasets.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
import glob
|
2 |
-
import math
|
3 |
import os
|
4 |
import random
|
5 |
import shutil
|
@@ -8,6 +7,7 @@ from pathlib import Path
|
|
8 |
from threading import Thread
|
9 |
|
10 |
import cv2
|
|
|
11 |
import numpy as np
|
12 |
import torch
|
13 |
from PIL import Image, ExifTags
|
|
|
1 |
import glob
|
|
|
2 |
import os
|
3 |
import random
|
4 |
import shutil
|
|
|
7 |
from threading import Thread
|
8 |
|
9 |
import cv2
|
10 |
+
import math
|
11 |
import numpy as np
|
12 |
import torch
|
13 |
from PIL import Image, ExifTags
|
utils/torch_utils.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1 |
import logging
|
2 |
-
import math
|
3 |
import os
|
4 |
import time
|
5 |
from copy import deepcopy
|
6 |
|
|
|
7 |
import torch
|
8 |
import torch.backends.cudnn as cudnn
|
9 |
import torch.nn as nn
|
|
|
1 |
import logging
|
|
|
2 |
import os
|
3 |
import time
|
4 |
from copy import deepcopy
|
5 |
|
6 |
+
import math
|
7 |
import torch
|
8 |
import torch.backends.cudnn as cudnn
|
9 |
import torch.nn as nn
|