glenn-jocher commited on
Commit
3b57cb5
·
unverified ·
1 Parent(s): c67e722

Simplified inference (#1153)

Browse files
Files changed (7) hide show
  1. detect.py +2 -2
  2. hubconf.py +1 -4
  3. models/common.py +59 -5
  4. models/yolo.py +20 -11
  5. sotabench.py +3 -6
  6. utils/datasets.py +1 -1
  7. 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.4, help='object confidence threshold')
153
- parser.add_argument('--iou-thres', type=float, default=0.5, 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')
 
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
- from utils.general import non_max_suppression
 
 
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.3 # confidence threshold
105
- iou = 0.6 # IoU threshold
106
  classes = None # (optional list) filter by class
107
 
108
- def __init__(self, dimension=1):
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
- time_synchronized, fuse_conv_and_bn, model_info, scale_img, initialize_weights, select_device)
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 add_nms(self): # fuse model Conv2d() + BatchNorm2d() layers
174
- if type(self.model[-1]) is not NMS: # if missing NMS
175
- print('Adding NMS module... ')
 
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, plot_images, xywh2xyxy, box_iou, output_to_target, ap_per_class, set_logging)
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