trtyolo-export 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trtyolo_export/cli.py +570 -0
- trtyolo_export/head.py +725 -0
- trtyolo_export/ppyoloe.py +299 -0
- trtyolo_export-1.0.0.dist-info/METADATA +957 -0
- trtyolo_export-1.0.0.dist-info/RECORD +9 -0
- trtyolo_export-1.0.0.dist-info/WHEEL +5 -0
- trtyolo_export-1.0.0.dist-info/entry_points.txt +2 -0
- trtyolo_export-1.0.0.dist-info/licenses/LICENSE +674 -0
- trtyolo_export-1.0.0.dist-info/top_level.txt +1 -0
trtyolo_export/cli.py
ADDED
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*-coding:utf-8 -*-
|
|
3
|
+
# ==============================================================================
|
|
4
|
+
# Copyright (c) 2024 laugh12321 Authors. All Rights Reserved.
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the GNU General Public License v3.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# https://www.gnu.org/licenses/gpl-3.0.html
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
# ==============================================================================
|
|
18
|
+
# File : cli.py
|
|
19
|
+
# Version : 6.4.0
|
|
20
|
+
# Author : laugh12321
|
|
21
|
+
# Contact : laugh12321@vip.qq.com
|
|
22
|
+
# Date : 2024/07/05 14:26:53
|
|
23
|
+
# Desc : trtyolo cli.
|
|
24
|
+
# ==============================================================================
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import sys
|
|
28
|
+
import warnings
|
|
29
|
+
from collections.abc import Sequence
|
|
30
|
+
from copy import deepcopy
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import List, Optional, Tuple, Union
|
|
33
|
+
|
|
34
|
+
import onnx
|
|
35
|
+
import rich_click as click
|
|
36
|
+
import torch
|
|
37
|
+
from loguru import logger
|
|
38
|
+
|
|
39
|
+
# Filter warnings
|
|
40
|
+
warnings.filterwarnings("ignore")
|
|
41
|
+
|
|
42
|
+
# For scripts
|
|
43
|
+
logger.configure(handlers=[{'sink': sys.stdout, 'colorize': True, 'format': "<level>[{level.name[0]}]</level> <level>{message}</level>"}])
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ModelExporter:
|
|
47
|
+
"""Base class for model export functionality."""
|
|
48
|
+
|
|
49
|
+
def __init__(self) -> None:
|
|
50
|
+
from .head import (
|
|
51
|
+
UltralyticsClassify,
|
|
52
|
+
UltralyticsDetect,
|
|
53
|
+
UltralyticsOBB,
|
|
54
|
+
UltralyticsPose,
|
|
55
|
+
UltralyticsSegment,
|
|
56
|
+
YOLOClassify,
|
|
57
|
+
YOLODetect,
|
|
58
|
+
YOLOEDetectHead,
|
|
59
|
+
YOLOESegmentHead,
|
|
60
|
+
YOLOSegment,
|
|
61
|
+
YOLOV10Detect,
|
|
62
|
+
YOLOWorldDetect,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
self.__batch = None
|
|
66
|
+
self.__model = None
|
|
67
|
+
self.__head_name = None
|
|
68
|
+
self.__version = None
|
|
69
|
+
|
|
70
|
+
self.__max_boxes = None
|
|
71
|
+
self.__dynamic = None
|
|
72
|
+
self.__weight_name = None
|
|
73
|
+
|
|
74
|
+
self.__output_names = ["num_dets", "det_boxes", "det_scores", "det_classes"]
|
|
75
|
+
self.__dynamic_axes = {
|
|
76
|
+
"images": {0: "batch", 2: "height", 3: "width"},
|
|
77
|
+
"num_dets": {0: "batch"},
|
|
78
|
+
"det_boxes": {0: "batch"},
|
|
79
|
+
"det_scores": {0: "batch"},
|
|
80
|
+
"det_classes": {0: "batch"},
|
|
81
|
+
}
|
|
82
|
+
self.__head_config = {
|
|
83
|
+
"Detect": {
|
|
84
|
+
"class_map": {
|
|
85
|
+
"yolov3": YOLODetect,
|
|
86
|
+
"yolov5": YOLODetect,
|
|
87
|
+
"yolov8": UltralyticsDetect,
|
|
88
|
+
"yolo11": UltralyticsDetect,
|
|
89
|
+
"yolo12": UltralyticsDetect,
|
|
90
|
+
"ultralytics": UltralyticsDetect,
|
|
91
|
+
},
|
|
92
|
+
"output_names": self.__output_names,
|
|
93
|
+
"dynamic_axes": self.__dynamic_axes,
|
|
94
|
+
},
|
|
95
|
+
"v10Detect": {
|
|
96
|
+
"class_map": {"yolov10": YOLOV10Detect, "ultralytics": YOLOV10Detect},
|
|
97
|
+
"output_names": self.__output_names,
|
|
98
|
+
"dynamic_axes": self.__dynamic_axes,
|
|
99
|
+
},
|
|
100
|
+
"WorldDetect": {
|
|
101
|
+
"class_map": {"yolo-world": YOLOWorldDetect, "ultralytics": YOLOWorldDetect},
|
|
102
|
+
"output_names": self.__output_names,
|
|
103
|
+
"dynamic_axes": self.__dynamic_axes,
|
|
104
|
+
},
|
|
105
|
+
"YOLOEDetect": {
|
|
106
|
+
"class_map": {"yoloe": YOLOEDetectHead, "ultralytics": YOLOEDetectHead},
|
|
107
|
+
"output_names": self.__output_names,
|
|
108
|
+
"dynamic_axes": self.__dynamic_axes,
|
|
109
|
+
},
|
|
110
|
+
"YOLOESegment": {
|
|
111
|
+
"class_map": {"yoloe": YOLOESegmentHead, "ultralytics": YOLOESegmentHead},
|
|
112
|
+
"output_names": self.__output_names + ["det_masks"],
|
|
113
|
+
"dynamic_axes": {**self.__dynamic_axes, "det_masks": {0: "batch", 2: "height", 3: "width"}},
|
|
114
|
+
},
|
|
115
|
+
"OBB": {
|
|
116
|
+
"class_map": {
|
|
117
|
+
"yolov8": UltralyticsOBB,
|
|
118
|
+
"yolo11": UltralyticsOBB,
|
|
119
|
+
"yolo12": UltralyticsOBB,
|
|
120
|
+
"ultralytics": UltralyticsOBB,
|
|
121
|
+
},
|
|
122
|
+
"output_names": self.__output_names,
|
|
123
|
+
"dynamic_axes": self.__dynamic_axes,
|
|
124
|
+
},
|
|
125
|
+
"Segment": {
|
|
126
|
+
"class_map": {
|
|
127
|
+
"yolov3": YOLOSegment,
|
|
128
|
+
"yolov5": YOLOSegment,
|
|
129
|
+
"yolov8": UltralyticsSegment,
|
|
130
|
+
"yolo11": UltralyticsSegment,
|
|
131
|
+
"yolo12": UltralyticsSegment,
|
|
132
|
+
"ultralytics": UltralyticsSegment,
|
|
133
|
+
},
|
|
134
|
+
"output_names": self.__output_names + ["det_masks"],
|
|
135
|
+
"dynamic_axes": {**self.__dynamic_axes, "det_masks": {0: "batch", 2: "height", 3: "width"}},
|
|
136
|
+
},
|
|
137
|
+
"Pose": {
|
|
138
|
+
"class_map": {
|
|
139
|
+
"yolov8": UltralyticsPose,
|
|
140
|
+
"yolo11": UltralyticsPose,
|
|
141
|
+
"yolo12": UltralyticsPose,
|
|
142
|
+
"ultralytics": UltralyticsPose,
|
|
143
|
+
},
|
|
144
|
+
"output_names": self.__output_names + ["det_kpts"],
|
|
145
|
+
"dynamic_axes": {**self.__dynamic_axes, "det_kpts": {0: "batch"}},
|
|
146
|
+
},
|
|
147
|
+
"Classify": {
|
|
148
|
+
"class_map": {
|
|
149
|
+
"yolov3": YOLOClassify,
|
|
150
|
+
"yolov5": YOLOClassify,
|
|
151
|
+
"yolov8": UltralyticsClassify,
|
|
152
|
+
"yolo11": UltralyticsClassify,
|
|
153
|
+
"yolo12": UltralyticsClassify,
|
|
154
|
+
"ultralytics": UltralyticsClassify,
|
|
155
|
+
},
|
|
156
|
+
"output_names": ["topk"],
|
|
157
|
+
"dynamic_axes": {"images": {0: "batch", 2: "height", 3: "width"}, "topk": {0: "batch"}},
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
self.__export_info = {
|
|
161
|
+
'yolov6': "https://github.com/meituan/YOLOv6/tree/main/deploy/ONNX#tensorrt-backend-tensorrt-version-800",
|
|
162
|
+
'yolov7': "https://github.com/WongKinYiu/yolov7#export",
|
|
163
|
+
'yolov9': "https://github.com/WongKinYiu/yolov9/issues/130#issue-2162045461",
|
|
164
|
+
}
|
|
165
|
+
logger.info("Starting export with Pytorch.")
|
|
166
|
+
|
|
167
|
+
def load(self, weights: str, version: str, repo_dir: Optional[str] = None, custom_classes: Optional[Sequence[str]] = None) -> None:
|
|
168
|
+
self.__version = version
|
|
169
|
+
self.__weight_name = Path(weights).stem
|
|
170
|
+
|
|
171
|
+
yolo_versions_with_repo = {
|
|
172
|
+
'yolov3': 'ultralytics/yolov3',
|
|
173
|
+
'yolov5': 'ultralytics/yolov5',
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
source = 'github' if repo_dir is None else 'local'
|
|
177
|
+
|
|
178
|
+
if version in yolo_versions_with_repo:
|
|
179
|
+
repo_dir = yolo_versions_with_repo[version] if repo_dir is None else repo_dir
|
|
180
|
+
self.__model = torch.hub.load(repo_dir, 'custom', path=weights, source=source, _verbose=False)
|
|
181
|
+
elif version in ['yolov8', 'yolov10', 'yolo11', 'yolo12', 'yolo-world', 'yoloe', 'ultralytics']:
|
|
182
|
+
from ultralytics import YOLO
|
|
183
|
+
|
|
184
|
+
self.__model = YOLO(model=weights, verbose=False).model
|
|
185
|
+
|
|
186
|
+
if custom_classes is not None:
|
|
187
|
+
if version == 'yolo-world':
|
|
188
|
+
self.__model.set_classes(custom_classes)
|
|
189
|
+
elif version == 'yoloe':
|
|
190
|
+
self.__model.set_classes(custom_classes, self.__model.get_text_pe(custom_classes))
|
|
191
|
+
else:
|
|
192
|
+
logger.error(
|
|
193
|
+
f"YOLO version '{version}' is unsupported for export with trtyolo CLI tool. "
|
|
194
|
+
"Please provide a valid version, e.g., yolov3, yolov5, yolov8, yolov10, yolo11, yolo12, yolo-world, yoloe, ultralytics."
|
|
195
|
+
)
|
|
196
|
+
if version in self.__export_info:
|
|
197
|
+
logger.warning(
|
|
198
|
+
f"The official {version} repository supports exporting an ONNX model with the EfficientNMS_TRT plugin. "
|
|
199
|
+
f"Please refer to {self.__export_info[version]} for instructions on how to export it."
|
|
200
|
+
)
|
|
201
|
+
sys.exit(1)
|
|
202
|
+
|
|
203
|
+
def register(self, batch: int, max_boxes: int, iou_thres: float, conf_thres: float) -> None:
|
|
204
|
+
self.__model = deepcopy(self.__model).to(torch.device("cpu"))
|
|
205
|
+
supported = False
|
|
206
|
+
|
|
207
|
+
self.__max_boxes = max_boxes
|
|
208
|
+
self.__dynamic = batch <= 0
|
|
209
|
+
self.__batch = 1 if batch <= 0 else batch
|
|
210
|
+
|
|
211
|
+
for m in self.__model.modules():
|
|
212
|
+
class_name = m.__class__.__name__
|
|
213
|
+
if class_name in self.__head_config:
|
|
214
|
+
self.__head_name = class_name
|
|
215
|
+
detect_head = self.__head_config[class_name]["class_map"].get(self.__version)
|
|
216
|
+
if detect_head:
|
|
217
|
+
supported = True
|
|
218
|
+
if class_name != "Classify":
|
|
219
|
+
detect_head.dynamic = self.__dynamic
|
|
220
|
+
detect_head.max_det = max_boxes
|
|
221
|
+
detect_head.iou_thres = iou_thres
|
|
222
|
+
detect_head.conf_thres = conf_thres
|
|
223
|
+
m.__class__ = detect_head
|
|
224
|
+
break
|
|
225
|
+
|
|
226
|
+
if not supported:
|
|
227
|
+
logger.error(f"YOLO version '{self.__version}' output head not supported!")
|
|
228
|
+
sys.exit(1)
|
|
229
|
+
|
|
230
|
+
def export(self, output: str, imgsz: Union[int, Sequence[int]], opset_version: int, simplify: bool) -> None:
|
|
231
|
+
from ultralytics.utils.checks import check_imgsz
|
|
232
|
+
|
|
233
|
+
imgsz = check_imgsz(imgsz, stride=self.__model.stride, min_dim=2)
|
|
234
|
+
im = torch.zeros(self.__batch, 3, *imgsz).to(torch.device("cpu"))
|
|
235
|
+
|
|
236
|
+
for p in self.__model.parameters():
|
|
237
|
+
p.requires_grad = False
|
|
238
|
+
self.__model.eval()
|
|
239
|
+
self.__model.float()
|
|
240
|
+
|
|
241
|
+
preds = self.__model(im)
|
|
242
|
+
|
|
243
|
+
output_path = Path(output)
|
|
244
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
245
|
+
onnx_path = output_path / (self.__weight_name + ".onnx")
|
|
246
|
+
|
|
247
|
+
torch.onnx.export(
|
|
248
|
+
model=self.__model,
|
|
249
|
+
args=im,
|
|
250
|
+
f=str(onnx_path),
|
|
251
|
+
opset_version=opset_version,
|
|
252
|
+
input_names=['images'],
|
|
253
|
+
output_names=self.__head_config[self.__head_name]["output_names"],
|
|
254
|
+
dynamic_axes=self.__head_config[self.__head_name]["dynamic_axes"] if self.__dynamic else None,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
onnx_model = onnx.load(onnx_path)
|
|
258
|
+
onnx.checker.check_model(onnx_model)
|
|
259
|
+
|
|
260
|
+
# Update dynamic axes names
|
|
261
|
+
output_shapes = {
|
|
262
|
+
'num_dets': ["batch" if self.__dynamic else self.__batch, 1],
|
|
263
|
+
'det_boxes': ["batch" if self.__dynamic else self.__batch, self.__max_boxes, 4],
|
|
264
|
+
'det_scores': ["batch" if self.__dynamic else self.__batch, self.__max_boxes],
|
|
265
|
+
'det_classes': ["batch" if self.__dynamic else self.__batch, self.__max_boxes],
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if self.__head_name == "OBB":
|
|
269
|
+
output_shapes['det_boxes'] = ["batch" if self.__dynamic else self.__batch, self.__max_boxes, 5]
|
|
270
|
+
elif self.__head_name == "Pose":
|
|
271
|
+
output_shapes['det_kpts'] = [
|
|
272
|
+
"batch" if self.__dynamic else self.__batch,
|
|
273
|
+
self.__max_boxes,
|
|
274
|
+
preds[-1].shape[-2],
|
|
275
|
+
preds[-1].shape[-1],
|
|
276
|
+
]
|
|
277
|
+
elif self.__head_name == "Segment" or self.__head_name == 'YOLOESegment':
|
|
278
|
+
output_shapes['det_masks'] = [
|
|
279
|
+
"batch" if self.__dynamic else self.__batch,
|
|
280
|
+
self.__max_boxes,
|
|
281
|
+
"height" if self.__dynamic else imgsz[0] // 4,
|
|
282
|
+
"width" if self.__dynamic else imgsz[1] // 4,
|
|
283
|
+
]
|
|
284
|
+
elif self.__head_name == "Classify":
|
|
285
|
+
output_shapes = {'topk': ["batch" if self.__dynamic else self.__batch, preds[-1].shape[1], 2]}
|
|
286
|
+
|
|
287
|
+
for node in onnx_model.graph.output:
|
|
288
|
+
for idx, dim in enumerate(node.type.tensor_type.shape.dim):
|
|
289
|
+
dim.dim_param = str(output_shapes[node.name][idx])
|
|
290
|
+
|
|
291
|
+
if simplify:
|
|
292
|
+
try:
|
|
293
|
+
import onnxsim
|
|
294
|
+
|
|
295
|
+
logger.success(f"Simplifying ONNX model with onnxsim version {onnxsim.__version__}...")
|
|
296
|
+
onnx_model, check = onnxsim.simplify(onnx_model)
|
|
297
|
+
assert check, "Simplified ONNX model could not be validated"
|
|
298
|
+
except ImportError:
|
|
299
|
+
logger.warning('onnxsim not found. Please install onnx-simplifier for example: `pip install onnx-simplifier>=0.4.1`.')
|
|
300
|
+
except Exception as e:
|
|
301
|
+
logger.warning(f"Simplifier failure: {e}")
|
|
302
|
+
|
|
303
|
+
onnx.save(onnx_model, onnx_path)
|
|
304
|
+
|
|
305
|
+
logger.success(f'Export complete, results saved to {output}, visualize at https://netron.app')
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def torch_export(
|
|
309
|
+
weights: str,
|
|
310
|
+
output: str,
|
|
311
|
+
version: str,
|
|
312
|
+
imgsz: Optional[Sequence[int]] = [640, 640],
|
|
313
|
+
batch: Optional[int] = 1,
|
|
314
|
+
max_boxes: Optional[int] = 100,
|
|
315
|
+
iou_thres: Optional[float] = 0.45,
|
|
316
|
+
conf_thres: Optional[float] = 0.25,
|
|
317
|
+
opset_version: Optional[int] = 12,
|
|
318
|
+
simplify: Optional[bool] = True,
|
|
319
|
+
repo_dir: Optional[str] = None,
|
|
320
|
+
custom_classes: Optional[Sequence[str]] = None,
|
|
321
|
+
) -> None:
|
|
322
|
+
"""
|
|
323
|
+
Export YOLO model to ONNX format using Torch.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
weights (str): Path to YOLO weights for PyTorch.
|
|
327
|
+
output (str): Directory path to save the exported model.
|
|
328
|
+
version (str): YOLO version, e.g., yolov3, yolov5, yolov8, yolov10, yolo11, yolo12, yolo-world, yoloe, ultralytics.
|
|
329
|
+
imgsz (Optional[Sequence[int]], optional): Inference image size (height, width). Defaults to [640, 640].
|
|
330
|
+
batch (Optional[int], optional): Total batch size for the model. Use -1 for dynamic batch size. Defaults to 1.
|
|
331
|
+
max_boxes (Optional[int], optional): Maximum number of detections to output per image. Defaults to 100.
|
|
332
|
+
iou_thres (Optional[float], optional): NMS IoU threshold for post-processing. Defaults to 0.45.
|
|
333
|
+
conf_thres (Optional[float], optional): Confidence threshold for object detection. Defaults to 0.25.
|
|
334
|
+
opset_version (Optional[int], optional): ONNX opset version. Defaults to 12.
|
|
335
|
+
simplify (Optional[bool], optional): Whether to simplify the exported ONNX. Defaults to True.
|
|
336
|
+
repo_dir (Optional[str], optional): Directory containing the local repository (if using torch.hub.load). Defaults to None.
|
|
337
|
+
custom_classes (Optional[Sequence[str]], optional): Custom class names for the YOLO-World and YOLOE. Defaults to None.
|
|
338
|
+
"""
|
|
339
|
+
exporter = ModelExporter()
|
|
340
|
+
exporter.load(weights, version, repo_dir, custom_classes)
|
|
341
|
+
exporter.register(batch, max_boxes, iou_thres, conf_thres)
|
|
342
|
+
exporter.export(output, imgsz, opset_version, simplify)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def paddle_export(
|
|
346
|
+
model_dir: str,
|
|
347
|
+
model_filename: str,
|
|
348
|
+
params_filename: str,
|
|
349
|
+
output: str,
|
|
350
|
+
batch: Optional[int] = 1,
|
|
351
|
+
max_boxes: Optional[int] = 100,
|
|
352
|
+
iou_thres: Optional[float] = 0.45,
|
|
353
|
+
conf_thres: Optional[float] = 0.25,
|
|
354
|
+
opset_version: Optional[int] = 12,
|
|
355
|
+
simplify: Optional[bool] = True,
|
|
356
|
+
) -> None:
|
|
357
|
+
"""
|
|
358
|
+
Export YOLO model to ONNX format using PaddleDetection.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
model_dir (str): Path to the directory containing the PaddleDetection PP-YOLOE model.
|
|
362
|
+
model_filename (str): The filename of the PP-YOLOE model.
|
|
363
|
+
params_filename (str): The filename of the PP-YOLOE parameters.
|
|
364
|
+
output (str): Directory path to save the exported model.
|
|
365
|
+
batch (Optional[int], optional): Total batch size for the model. Use -1 for dynamic batch size. Defaults to 1.
|
|
366
|
+
max_boxes (Optional[int], optional): Maximum number of detections to output per image. Defaults to 100.
|
|
367
|
+
iou_thres (Optional[float], optional): NMS IoU threshold for post-processing. Defaults to 0.45.
|
|
368
|
+
conf_thres (Optional[float], optional): Confidence threshold for object detection. Defaults to 0.25.
|
|
369
|
+
opset_version (Optional[int], optional): ONNX opset version. Defaults to 12.
|
|
370
|
+
simplify (Optional[bool], optional): Whether to simplify the exported ONNX. Defaults to True.
|
|
371
|
+
"""
|
|
372
|
+
from .ppyoloe import PPYOLOEGraphSurgeon
|
|
373
|
+
|
|
374
|
+
logger.info("Starting export with PaddlePaddle.")
|
|
375
|
+
|
|
376
|
+
output_path = Path(output)
|
|
377
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
378
|
+
onnx_filepath = output_path / (Path(model_filename).stem + ".onnx")
|
|
379
|
+
|
|
380
|
+
ppyoloe_gs = PPYOLOEGraphSurgeon(
|
|
381
|
+
model_dir=model_dir,
|
|
382
|
+
onnx_path=str(onnx_filepath),
|
|
383
|
+
model_filename=model_filename,
|
|
384
|
+
params_filename=params_filename,
|
|
385
|
+
opset=opset_version,
|
|
386
|
+
batch_size=1 if batch <= 0 else batch,
|
|
387
|
+
dynamic=batch <= 0,
|
|
388
|
+
simplify=simplify,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Register the `EfficientNMS_TRT` into the graph.
|
|
392
|
+
ppyoloe_gs.register_nms(score_thresh=conf_thres, nms_thresh=iou_thres, detections_per_img=max_boxes)
|
|
393
|
+
|
|
394
|
+
# Save the exported ONNX models.
|
|
395
|
+
ppyoloe_gs.save(str(onnx_filepath))
|
|
396
|
+
|
|
397
|
+
logger.success(f'Export complete, results saved to {output}, visualize at https://netron.app')
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def validate_imgsz(ctx: click.Context, param: click.Parameter, value: str) -> Tuple[int, int]:
|
|
401
|
+
"""Validate and parse the imgsz parameter."""
|
|
402
|
+
try:
|
|
403
|
+
if ',' in value:
|
|
404
|
+
h, w = map(int, value.split(','))
|
|
405
|
+
return (h, w)
|
|
406
|
+
size = int(value)
|
|
407
|
+
return (size, size)
|
|
408
|
+
except ValueError:
|
|
409
|
+
raise click.BadParameter('Image size must be in format "size" or "height,width" (e.g., 640 or 640,480)')
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def validate_names(ctx: click.Context, param: click.Parameter, value: str) -> Optional[List[str]]:
|
|
413
|
+
"""Validate and parse the names parameter."""
|
|
414
|
+
if value is None:
|
|
415
|
+
return None
|
|
416
|
+
return [name.strip() for name in value.split(',')]
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def validate_export_params(ctx: click.Context, param: click.Parameter, value: str) -> str:
|
|
420
|
+
"""Validate the combination of export parameters."""
|
|
421
|
+
version = ctx.params.get('version')
|
|
422
|
+
model_dir = ctx.params.get('model_dir')
|
|
423
|
+
model_filename = ctx.params.get('model_filename')
|
|
424
|
+
params_filename = ctx.params.get('params_filename')
|
|
425
|
+
weights = ctx.params.get('weights')
|
|
426
|
+
|
|
427
|
+
if version == 'pp-yoloe':
|
|
428
|
+
if not all([model_dir, model_filename, params_filename]):
|
|
429
|
+
raise click.BadParameter('For PP-YOLOE, --model_dir, --model_filename and --params_filename are required')
|
|
430
|
+
elif version and version != 'pp-yoloe':
|
|
431
|
+
if not weights:
|
|
432
|
+
raise click.BadParameter('For non PP-YOLOE models, --weights is required')
|
|
433
|
+
|
|
434
|
+
return value
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
@click.group()
|
|
438
|
+
def trtyolo():
|
|
439
|
+
"""Command line tool for exporting models and performing inference with TensorRT-YOLO."""
|
|
440
|
+
pass
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
@trtyolo.command(
|
|
444
|
+
help="Export YOLO models to ONNX format compatible with TensorRT-YOLO. "
|
|
445
|
+
"Supports YOLOv3, YOLOv5, YOLOv8, YOLOv10, YOLO11, YOLO12, YOLO-World, YOLOE, PP-YOLOE, and PP-YOLOE+."
|
|
446
|
+
)
|
|
447
|
+
@click.option(
|
|
448
|
+
'-v',
|
|
449
|
+
'--version',
|
|
450
|
+
help='Model version. Options include yolov3, yolov5, yolov8, yolov10, yolo11, yolo12, yolo-world, yoloe, pp-yoloe, ultralytics.',
|
|
451
|
+
type=str,
|
|
452
|
+
required=True,
|
|
453
|
+
callback=validate_export_params,
|
|
454
|
+
)
|
|
455
|
+
@click.option(
|
|
456
|
+
'-o',
|
|
457
|
+
'--output',
|
|
458
|
+
help='Directory path to save the exported model.',
|
|
459
|
+
type=click.Path(file_okay=False, dir_okay=True, path_type=str),
|
|
460
|
+
required=True,
|
|
461
|
+
)
|
|
462
|
+
@click.option(
|
|
463
|
+
'-w',
|
|
464
|
+
'--weights',
|
|
465
|
+
help='Path to PyTorch YOLO weights (required for non PP-YOLOE models).',
|
|
466
|
+
type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=str),
|
|
467
|
+
)
|
|
468
|
+
@click.option(
|
|
469
|
+
'--model_dir',
|
|
470
|
+
help='Directory path containing the PaddleDetection PP-YOLOE model (required for PP-YOLOE).',
|
|
471
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=str),
|
|
472
|
+
)
|
|
473
|
+
@click.option('--model_filename', help='Filename of the PaddleDetection PP-YOLOE model (required for PP-YOLOE).', type=str)
|
|
474
|
+
@click.option('--params_filename', help='Filename of the PaddleDetection PP-YOLOE parameters (required for PP-YOLOE).', type=str)
|
|
475
|
+
@click.option('-b', '--batch', default=1, help='Total batch size for the model. Use -1 for dynamic batch size. Defaults to 1.', type=int)
|
|
476
|
+
@click.option(
|
|
477
|
+
'--max_boxes',
|
|
478
|
+
default=100,
|
|
479
|
+
help='Maximum number of detections per image (not applicable for classification models). Defaults to 100.',
|
|
480
|
+
type=int,
|
|
481
|
+
)
|
|
482
|
+
@click.option(
|
|
483
|
+
'--iou_thres',
|
|
484
|
+
default=0.45,
|
|
485
|
+
help='NMS IoU threshold for post-processing (not applicable for classification models). Defaults to 0.45.',
|
|
486
|
+
type=float,
|
|
487
|
+
)
|
|
488
|
+
@click.option(
|
|
489
|
+
'--conf_thres',
|
|
490
|
+
default=0.25,
|
|
491
|
+
help='Confidence threshold for object detection (not applicable for classification models). Defaults to 0.25.',
|
|
492
|
+
type=float,
|
|
493
|
+
)
|
|
494
|
+
@click.option(
|
|
495
|
+
'--imgsz',
|
|
496
|
+
default='640',
|
|
497
|
+
help='Image size (single value for square or "height,width"). Defaults to "640" (for non PP-YOLOE models).',
|
|
498
|
+
type=str,
|
|
499
|
+
callback=validate_imgsz,
|
|
500
|
+
)
|
|
501
|
+
@click.option(
|
|
502
|
+
'-n',
|
|
503
|
+
'--names',
|
|
504
|
+
help='Custom class names for YOLO-World and YOLOE (comma-separated, e.g., "person,car,dog"). Only applicable for YOLO-World and YOLOE models.',
|
|
505
|
+
type=str,
|
|
506
|
+
callback=validate_names,
|
|
507
|
+
)
|
|
508
|
+
@click.option(
|
|
509
|
+
'--repo_dir',
|
|
510
|
+
help='Directory containing the local repository (if using torch.hub.load). Only applicable for YOLOv3 and YOLOv5 models.',
|
|
511
|
+
type=str,
|
|
512
|
+
)
|
|
513
|
+
@click.option('--opset', default=12, help='ONNX opset version. Defaults to 12.', type=int)
|
|
514
|
+
@click.option('-s', '--simplify', is_flag=True, help='Whether to simplify the exported ONNX model. Defaults to False.')
|
|
515
|
+
def export(
|
|
516
|
+
version: str,
|
|
517
|
+
output: str,
|
|
518
|
+
weights: Optional[str],
|
|
519
|
+
model_dir: Optional[str],
|
|
520
|
+
model_filename: Optional[str],
|
|
521
|
+
params_filename: Optional[str],
|
|
522
|
+
imgsz: Optional[Tuple[int, int]],
|
|
523
|
+
names: Optional[List[str]],
|
|
524
|
+
repo_dir: Optional[str],
|
|
525
|
+
batch: Optional[int],
|
|
526
|
+
max_boxes: Optional[int],
|
|
527
|
+
iou_thres: Optional[float],
|
|
528
|
+
conf_thres: Optional[float],
|
|
529
|
+
opset: Optional[int],
|
|
530
|
+
simplify: Optional[bool],
|
|
531
|
+
):
|
|
532
|
+
"""Export models for TensorRT-YOLO.
|
|
533
|
+
|
|
534
|
+
This command allows exporting models for both PaddlePaddle and PyTorch frameworks to be used with TensorRT-YOLO.
|
|
535
|
+
"""
|
|
536
|
+
|
|
537
|
+
if version == 'pp-yoloe':
|
|
538
|
+
paddle_export(
|
|
539
|
+
model_dir=model_dir,
|
|
540
|
+
model_filename=model_filename,
|
|
541
|
+
params_filename=params_filename,
|
|
542
|
+
batch=batch,
|
|
543
|
+
output=output,
|
|
544
|
+
max_boxes=max_boxes,
|
|
545
|
+
iou_thres=iou_thres,
|
|
546
|
+
conf_thres=conf_thres,
|
|
547
|
+
opset_version=opset,
|
|
548
|
+
simplify=simplify,
|
|
549
|
+
)
|
|
550
|
+
else:
|
|
551
|
+
torch_export(
|
|
552
|
+
weights=weights,
|
|
553
|
+
output=output,
|
|
554
|
+
version=version,
|
|
555
|
+
imgsz=imgsz,
|
|
556
|
+
batch=batch,
|
|
557
|
+
max_boxes=max_boxes,
|
|
558
|
+
iou_thres=iou_thres,
|
|
559
|
+
conf_thres=conf_thres,
|
|
560
|
+
opset_version=opset,
|
|
561
|
+
simplify=simplify,
|
|
562
|
+
repo_dir=repo_dir,
|
|
563
|
+
custom_classes=names,
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
logger.success("Export completed successfully!")
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
if __name__ == '__main__':
|
|
570
|
+
trtyolo()
|