Skip to content

Commit 1fdf2f1

Browse files
committed
New PR for "ultralytics#7736"
1 parent 3c1afd9 commit 1fdf2f1

File tree

4 files changed

+342
-5
lines changed

4 files changed

+342
-5
lines changed

examples/export.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env bash
2+
cd ../
3+
mkdir -p weights
4+
5+
# download official weights
6+
wget https://gh.ddlc.top/https://github.com/ultralytics/yolov5/releases/download/v6.1/yolov5s.pt -P weights
7+
# export yolov5s.onnx
8+
python3 export.py --weights weights/yolov5s.pt --include onnx engine --nms
9+
mv weights/yolov5s.onnx ./examples/yolov5s_nms.onnx
10+
cd examples
11+
trtexec --onnx=./yolov5s_nms.onnx --saveEngine=./yolov5s_nms_fp16.engine --fp16
12+
13+
# result test
14+
wget https://oneflow-static.oss-cn-beijing.aliyuncs.com/tripleMu/image1.jpg
15+
python3 trt_infer.py
16+
trtexec --loadEngine=./yolov5s_nms_fp16.engine --verbose --useCudaGraph --noDataTransfers --shapes=images:1x3x640x640

examples/trt_infer.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import sys
2+
3+
import cv2
4+
5+
sys.path.append('../')
6+
import random
7+
import time
8+
from collections import OrderedDict, namedtuple
9+
10+
import numpy as np
11+
import tensorrt as trt
12+
import torch
13+
from PIL import Image
14+
15+
from utils.augmentations import letterbox
16+
17+
names = [
18+
'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light',
19+
'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant',
20+
'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard',
21+
'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle',
22+
'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli',
23+
'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'dining table', 'toilet',
24+
'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator',
25+
'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush']
26+
colors = {name: [random.randint(0, 255) for _ in range(3)] for i, name in enumerate(names)}
27+
28+
w = './yolov5s_nms_fp16.engine'
29+
image_path = './image1.jpg'
30+
device = torch.device('cuda:0')
31+
32+
# Infer TensorRT Engine
33+
Binding = namedtuple('Binding', ('name', 'dtype', 'shape', 'data', 'ptr'))
34+
logger = trt.Logger(trt.Logger.INFO)
35+
trt.init_libnvinfer_plugins(logger, namespace="")
36+
with open(w, 'rb') as f, trt.Runtime(logger) as runtime:
37+
model = runtime.deserialize_cuda_engine(f.read())
38+
bindings = OrderedDict()
39+
fp16 = False # default updated below
40+
for index in range(model.num_bindings):
41+
name = model.get_binding_name(index)
42+
dtype = trt.nptype(model.get_binding_dtype(index))
43+
shape = tuple(model.get_binding_shape(index))
44+
data = torch.from_numpy(np.empty(shape, dtype=np.dtype(dtype))).to(device)
45+
bindings[name] = Binding(name, dtype, shape, data, int(data.data_ptr()))
46+
if model.binding_is_input(index) and dtype == np.float16:
47+
fp16 = True
48+
binding_addrs = OrderedDict((n, d.ptr) for n, d in bindings.items())
49+
context = model.create_execution_context()
50+
51+
image = cv2.imread(image_path)
52+
image, ratio, dwdh = letterbox(image, auto=False)
53+
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
54+
55+
image_copy = image.copy()
56+
57+
image = image.transpose((2, 0, 1))
58+
image = np.expand_dims(image, 0)
59+
image = np.ascontiguousarray(image)
60+
im = torch.from_numpy(image).to(device)
61+
im = im.float()
62+
im /= 255
63+
64+
# warmup for 10 times
65+
for _ in range(10):
66+
tmp = torch.randn(1, 3, 640, 640).to(device)
67+
binding_addrs['images'] = int(tmp.data_ptr())
68+
context.execute_v2(list(binding_addrs.values()))
69+
70+
start = time.perf_counter()
71+
binding_addrs['images'] = int(im.data_ptr())
72+
context.execute_v2(list(binding_addrs.values()))
73+
print(f'Cost {time.perf_counter()-start} s')
74+
75+
nums = bindings['num_dets'].data
76+
boxes = bindings['det_boxes'].data
77+
scores = bindings['det_scores'].data
78+
classes = bindings['det_classes'].data
79+
80+
print(nums)
81+
print(boxes)
82+
print(scores)
83+
print(classes)
84+
85+
num = int(nums[0][0])
86+
box_img = boxes[0, :num].round().int()
87+
score_img = scores[0, :num]
88+
clss_img = classes[0, :num]
89+
for i, (box, score, clss) in enumerate(zip(box_img, score_img, clss_img)):
90+
name = names[clss]
91+
color = colors[name]
92+
cv2.rectangle(image_copy, box[:2].tolist(), box[2:].tolist(), color, 2)
93+
cv2.putText(image_copy,
94+
name, (int(box[0]), int(box[1]) - 2),
95+
cv2.FONT_HERSHEY_SIMPLEX,
96+
0.75, [225, 255, 255],
97+
thickness=2)
98+
99+
Image.fromarray(image_copy).show()

export.py

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def export_formats():
9191
['TensorFlow Lite', 'tflite', '.tflite', True, False],
9292
['TensorFlow Edge TPU', 'edgetpu', '_edgetpu.tflite', False, False],
9393
['TensorFlow.js', 'tfjs', '_web_model', False, False],
94-
['PaddlePaddle', 'paddle', '_paddle_model', True, True],]
94+
['PaddlePaddle', 'paddle', '_paddle_model', True, True], ]
9595
return pd.DataFrame(x, columns=['Format', 'Argument', 'Suffix', 'CPU', 'GPU'])
9696

9797

@@ -185,6 +185,66 @@ def export_onnx(model, im, file, opset, dynamic, simplify, prefix=colorstr('ONNX
185185
return f, model_onnx
186186

187187

188+
@try_export
189+
def export_onnx_for_backend(model, im, file, opset, nms_cfg, dynamic, simplify, prefix=colorstr('ONNX:')):
190+
# YOLOv5 ONNX export
191+
check_requirements(('onnx',))
192+
import onnx
193+
194+
LOGGER.info(f'\n{prefix} starting export with onnx {onnx.__version__}...')
195+
f = file.with_suffix('.onnx')
196+
197+
from models.common import End2End
198+
model = End2End(model, *nms_cfg, device=im.device)
199+
if nms_cfg[-1] == 'ort':
200+
output_names = ['outputs']
201+
elif nms_cfg[-1] == 'trt':
202+
output_names = ['num_dets', 'det_boxes', 'det_scores', 'det_classes']
203+
204+
if dynamic and nms_cfg[-1] == 'ort':
205+
dynamic_cfg = {n: {0: 'batch'} for n in output_names}
206+
elif dynamic and nms_cfg[-1] == 'trt':
207+
dynamic_cfg = {n: {0: 'batch'} for n in output_names}
208+
209+
torch.onnx.export(
210+
model.cpu() if dynamic else model, # --dynamic only compatible with cpu
211+
im.cpu() if dynamic else im,
212+
f,
213+
verbose=False,
214+
opset_version=opset,
215+
training=torch.onnx.TrainingMode.EVAL,
216+
do_constant_folding=True,
217+
input_names=['images'],
218+
output_names=output_names,
219+
dynamic_axes=dynamic_cfg if dynamic else None)
220+
221+
# Checks
222+
model_onnx = onnx.load(f) # load onnx model
223+
onnx.checker.check_model(model_onnx) # check onnx model
224+
225+
# Metadata
226+
d = {'stride': int(max(model.stride)), 'names': model.names}
227+
for k, v in d.items():
228+
meta = model_onnx.metadata_props.add()
229+
meta.key, meta.value = k, str(v)
230+
onnx.save(model_onnx, f)
231+
232+
# Simplify
233+
if simplify:
234+
try:
235+
cuda = torch.cuda.is_available()
236+
check_requirements(('onnxruntime-gpu' if cuda else 'onnxruntime', 'onnx-simplifier>=0.4.1'))
237+
import onnxsim
238+
239+
LOGGER.info(f'{prefix} simplifying with onnx-simplifier {onnxsim.__version__}...')
240+
model_onnx, check = onnxsim.simplify(model_onnx)
241+
assert check, 'assert check failed'
242+
onnx.save(model_onnx, f)
243+
except Exception as e:
244+
LOGGER.info(f'{prefix} simplifier failure: {e}')
245+
return f, model_onnx
246+
247+
188248
@try_export
189249
def export_openvino(file, metadata, half, prefix=colorstr('OpenVINO:')):
190250
# YOLOv5 OpenVINO export
@@ -447,9 +507,9 @@ def export_tfjs(file, prefix=colorstr('TensorFlow.js:')):
447507
r'"Identity.?.?": {"name": "Identity.?.?"}, '
448508
r'"Identity.?.?": {"name": "Identity.?.?"}, '
449509
r'"Identity.?.?": {"name": "Identity.?.?"}}}', r'{"outputs": {"Identity": {"name": "Identity"}, '
450-
r'"Identity_1": {"name": "Identity_1"}, '
451-
r'"Identity_2": {"name": "Identity_2"}, '
452-
r'"Identity_3": {"name": "Identity_3"}}}', json)
510+
r'"Identity_1": {"name": "Identity_1"}, '
511+
r'"Identity_2": {"name": "Identity_2"}, '
512+
r'"Identity_3": {"name": "Identity_3"}}}', json)
453513
j.write(subst)
454514
return f, None
455515

@@ -506,6 +566,7 @@ def run(
506566
verbose=False, # TensorRT: verbose log
507567
workspace=4, # TensorRT: workspace size (GB)
508568
nms=False, # TF: add NMS to model
569+
backend='ort', # Backend for export NMS
509570
agnostic_nms=False, # TF: add agnostic NMS to model
510571
topk_per_class=100, # TF.js NMS: topk per class to keep
511572
topk_all=100, # TF.js NMS: topk for all classes to keep
@@ -518,6 +579,7 @@ def run(
518579
flags = [x in include for x in fmts]
519580
assert sum(flags) == len(include), f'ERROR: Invalid --include {include}, valid --include arguments are {fmts}'
520581
jit, onnx, xml, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs, paddle = flags # export booleans
582+
end2end, onnx = onnx and nms, onnx and not nms
521583
file = Path(url2file(weights) if str(weights).startswith(('http:/', 'https:/')) else weights) # PyTorch weights
522584

523585
# Load PyTorch model
@@ -554,7 +616,7 @@ def run(
554616
LOGGER.info(f"\n{colorstr('PyTorch:')} starting from {file} with output shape {shape} ({file_size(file):.1f} MB)")
555617

556618
# Exports
557-
f = [''] * len(fmts) # exported filenames
619+
f = [''] * (len(fmts)) + 1 # exported filenames
558620
warnings.filterwarnings(action='ignore', category=torch.jit.TracerWarning) # suppress TracerWarning
559621
if jit: # TorchScript
560622
f[0], _ = export_torchscript(model, im, file, optimize)
@@ -592,6 +654,9 @@ def run(
592654
if paddle: # PaddlePaddle
593655
f[10], _ = export_paddle(model, im, file, metadata)
594656

657+
if end2end:
658+
nms_cfg = [topk_all, iou_thres, conf_thres, backend]
659+
f[10], _ = export_onnx_for_backend(model, im, file, opset, nms_cfg, dynamic, simplify)
595660
# Finish
596661
f = [str(x) for x in f if x] # filter out '' and None
597662
if any(f):
@@ -628,6 +693,7 @@ def parse_opt():
628693
parser.add_argument('--verbose', action='store_true', help='TensorRT: verbose log')
629694
parser.add_argument('--workspace', type=int, default=4, help='TensorRT: workspace size (GB)')
630695
parser.add_argument('--nms', action='store_true', help='TF: add NMS to model')
696+
parser.add_argument('--backend', type=str, default='ort', help='Backend for export NMS')
631697
parser.add_argument('--agnostic-nms', action='store_true', help='TF: add agnostic NMS to model')
632698
parser.add_argument('--topk-per-class', type=int, default=100, help='TF.js NMS: topk per class to keep')
633699
parser.add_argument('--topk-all', type=int, default=100, help='TF.js NMS: topk for all classes to keep')

0 commit comments

Comments
 (0)