segment-everything 0.1.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.
- segment_everything/__init__.py +5 -0
- segment_everything/augmentation/albumentations_helper.py +0 -0
- segment_everything/detect_and_segment.py +131 -0
- segment_everything/napari_helper.py +15 -0
- segment_everything/prompt_generator.py +188 -0
- segment_everything/py.typed +5 -0
- segment_everything/stacked_label_dataset.py +113 -0
- segment_everything/stacked_labels.py +428 -0
- segment_everything/vendored/PromptGuidedDecoder/Prompt_guided_Mask_Decoder.pt +0 -0
- segment_everything/vendored/__init__.py +5 -0
- segment_everything/vendored/dice.py +158 -0
- segment_everything/vendored/efficientvit/__init__.py +0 -0
- segment_everything/vendored/efficientvit/apps/__init__.py +0 -0
- segment_everything/vendored/efficientvit/apps/data_provider/__init__.py +7 -0
- segment_everything/vendored/efficientvit/apps/data_provider/augment/__init__.py +6 -0
- segment_everything/vendored/efficientvit/apps/data_provider/augment/bbox.py +30 -0
- segment_everything/vendored/efficientvit/apps/data_provider/augment/color_aug.py +78 -0
- segment_everything/vendored/efficientvit/apps/data_provider/base.py +254 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/__init__.py +6 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/_data_loader.py +1538 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/_data_worker.py +357 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/controller.py +100 -0
- segment_everything/vendored/efficientvit/apps/setup.py +150 -0
- segment_everything/vendored/efficientvit/apps/trainer/__init__.py +6 -0
- segment_everything/vendored/efficientvit/apps/trainer/base.py +318 -0
- segment_everything/vendored/efficientvit/apps/trainer/run_config.py +129 -0
- segment_everything/vendored/efficientvit/apps/utils/__init__.py +12 -0
- segment_everything/vendored/efficientvit/apps/utils/dist.py +32 -0
- segment_everything/vendored/efficientvit/apps/utils/ema.py +52 -0
- segment_everything/vendored/efficientvit/apps/utils/export.py +45 -0
- segment_everything/vendored/efficientvit/apps/utils/init.py +66 -0
- segment_everything/vendored/efficientvit/apps/utils/lr.py +52 -0
- segment_everything/vendored/efficientvit/apps/utils/metric.py +43 -0
- segment_everything/vendored/efficientvit/apps/utils/misc.py +101 -0
- segment_everything/vendored/efficientvit/apps/utils/opt.py +28 -0
- segment_everything/vendored/efficientvit/cls_model_zoo.py +79 -0
- segment_everything/vendored/efficientvit/clscore/__init__.py +0 -0
- segment_everything/vendored/efficientvit/clscore/data_provider/__init__.py +5 -0
- segment_everything/vendored/efficientvit/clscore/data_provider/imagenet.py +142 -0
- segment_everything/vendored/efficientvit/clscore/trainer/__init__.py +6 -0
- segment_everything/vendored/efficientvit/clscore/trainer/cls_run_config.py +18 -0
- segment_everything/vendored/efficientvit/clscore/trainer/cls_trainer.py +265 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/__init__.py +7 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/label_smooth.py +18 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/metric.py +23 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/mixup.py +67 -0
- segment_everything/vendored/efficientvit/models/__init__.py +0 -0
- segment_everything/vendored/efficientvit/models/efficientvit/__init__.py +8 -0
- segment_everything/vendored/efficientvit/models/efficientvit/backbone.py +380 -0
- segment_everything/vendored/efficientvit/models/efficientvit/cls.py +188 -0
- segment_everything/vendored/efficientvit/models/efficientvit/sam.py +181 -0
- segment_everything/vendored/efficientvit/models/efficientvit/seg.py +373 -0
- segment_everything/vendored/efficientvit/models/nn/__init__.py +8 -0
- segment_everything/vendored/efficientvit/models/nn/act.py +30 -0
- segment_everything/vendored/efficientvit/models/nn/drop.py +104 -0
- segment_everything/vendored/efficientvit/models/nn/norm.py +164 -0
- segment_everything/vendored/efficientvit/models/nn/ops.py +597 -0
- segment_everything/vendored/efficientvit/models/utils/__init__.py +7 -0
- segment_everything/vendored/efficientvit/models/utils/list.py +53 -0
- segment_everything/vendored/efficientvit/models/utils/network.py +73 -0
- segment_everything/vendored/efficientvit/models/utils/random.py +65 -0
- segment_everything/vendored/efficientvit/sam_model_zoo.py +45 -0
- segment_everything/vendored/efficientvit/seg_model_zoo.py +70 -0
- segment_everything/vendored/get_object_aware.py +26 -0
- segment_everything/vendored/mobilesamv2/__init__.py +16 -0
- segment_everything/vendored/mobilesamv2/automatic_mask_generator.py +415 -0
- segment_everything/vendored/mobilesamv2/build_sam.py +246 -0
- segment_everything/vendored/mobilesamv2/modeling/__init__.py +11 -0
- segment_everything/vendored/mobilesamv2/modeling/common.py +43 -0
- segment_everything/vendored/mobilesamv2/modeling/image_encoder.py +394 -0
- segment_everything/vendored/mobilesamv2/modeling/mask_decoder.py +213 -0
- segment_everything/vendored/mobilesamv2/modeling/prompt_encoder.py +217 -0
- segment_everything/vendored/mobilesamv2/modeling/sam.py +203 -0
- segment_everything/vendored/mobilesamv2/modeling/transformer.py +240 -0
- segment_everything/vendored/mobilesamv2/predictor.py +384 -0
- segment_everything/vendored/mobilesamv2/utils/__init__.py +5 -0
- segment_everything/vendored/mobilesamv2/utils/amg.py +347 -0
- segment_everything/vendored/mobilesamv2/utils/onnx.py +144 -0
- segment_everything/vendored/mobilesamv2/utils/transforms.py +103 -0
- segment_everything/vendored/object_detection/__init__.py +0 -0
- segment_everything/vendored/object_detection/ultralytics/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/nn/__init__.py +9 -0
- segment_everything/vendored/object_detection/ultralytics/nn/autobackend.py +658 -0
- segment_everything/vendored/object_detection/ultralytics/nn/autoshape.py +397 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/__init__.py +110 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/block.py +304 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/conv.py +297 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/head.py +468 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/transformer.py +378 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/utils.py +78 -0
- segment_everything/vendored/object_detection/ultralytics/nn/tasks.py +1049 -0
- segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/__init__.py +6 -0
- segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/model.py +104 -0
- segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/predict.py +95 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/cfg/__init__.py +588 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/cfg/default.yaml +117 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/__init__.py +9 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/annotator.py +53 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/augment.py +899 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/base.py +286 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/build.py +213 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/converter.py +358 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataloaders/__init__.py +0 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataloaders/stream_loaders.py +459 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataset.py +274 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataset_wrappers.py +53 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/download_weights.sh +18 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_coco.sh +60 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_coco128.sh +17 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_imagenet.sh +51 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/utils.py +716 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/__init__.py +0 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/exporter.py +1214 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/model.py +641 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/predictor.py +461 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/results.py +741 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/__init__.py +893 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/autobatch.py +108 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/callbacks/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/callbacks/base.py +212 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/checks.py +547 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/dist.py +67 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/downloads.py +353 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/errors.py +12 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/files.py +100 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/instance.py +391 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/loss.py +579 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/metrics.py +1189 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/ops.py +870 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/patches.py +45 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/plotting.py +767 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/tal.py +276 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/torch_utils.py +684 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/tuner.py +54 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/v8/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/v8/detect/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/v8/detect/predict.py +69 -0
- segment_everything/vendored/tinyvit/__init__.py +2 -0
- segment_everything/vendored/tinyvit/tiny_vit.py +867 -0
- segment_everything/weights_helper.py +124 -0
- segment_everything-0.1.0.dist-info/METADATA +53 -0
- segment_everything-0.1.0.dist-info/RECORD +145 -0
- segment_everything-0.1.0.dist-info/WHEEL +4 -0
- segment_everything-0.1.0.dist-info/licenses/LICENSE +28 -0
|
@@ -0,0 +1,1214 @@
|
|
|
1
|
+
# Ultralytics YOLO 🚀, AGPL-3.0 license
|
|
2
|
+
"""
|
|
3
|
+
Export a YOLOv8 PyTorch model to other formats. TensorFlow exports authored by https://github.com/zldrobit
|
|
4
|
+
|
|
5
|
+
Format | `format=argument` | Model
|
|
6
|
+
--- | --- | ---
|
|
7
|
+
PyTorch | - | yolov8n.pt
|
|
8
|
+
TorchScript | `torchscript` | yolov8n.torchscript
|
|
9
|
+
ONNX | `onnx` | yolov8n.onnx
|
|
10
|
+
OpenVINO | `openvino` | yolov8n_openvino_model/
|
|
11
|
+
TensorRT | `engine` | yolov8n.engine
|
|
12
|
+
CoreML | `coreml` | yolov8n.mlmodel
|
|
13
|
+
TensorFlow SavedModel | `saved_model` | yolov8n_saved_model/
|
|
14
|
+
TensorFlow GraphDef | `pb` | yolov8n.pb
|
|
15
|
+
TensorFlow Lite | `tflite` | yolov8n.tflite
|
|
16
|
+
TensorFlow Edge TPU | `edgetpu` | yolov8n_edgetpu.tflite
|
|
17
|
+
TensorFlow.js | `tfjs` | yolov8n_web_model/
|
|
18
|
+
PaddlePaddle | `paddle` | yolov8n_paddle_model/
|
|
19
|
+
|
|
20
|
+
Requirements:
|
|
21
|
+
$ pip install ultralytics[export]
|
|
22
|
+
|
|
23
|
+
Python:
|
|
24
|
+
from ultralytics import YOLO
|
|
25
|
+
model = YOLO('yolov8n.pt')
|
|
26
|
+
results = model.export(format='onnx')
|
|
27
|
+
|
|
28
|
+
CLI:
|
|
29
|
+
$ yolo mode=export model=yolov8n.pt format=onnx
|
|
30
|
+
|
|
31
|
+
Inference:
|
|
32
|
+
$ yolo predict model=yolov8n.pt # PyTorch
|
|
33
|
+
yolov8n.torchscript # TorchScript
|
|
34
|
+
yolov8n.onnx # ONNX Runtime or OpenCV DNN with --dnn
|
|
35
|
+
yolov8n_openvino_model # OpenVINO
|
|
36
|
+
yolov8n.engine # TensorRT
|
|
37
|
+
yolov8n.mlmodel # CoreML (macOS-only)
|
|
38
|
+
yolov8n_saved_model # TensorFlow SavedModel
|
|
39
|
+
yolov8n.pb # TensorFlow GraphDef
|
|
40
|
+
yolov8n.tflite # TensorFlow Lite
|
|
41
|
+
yolov8n_edgetpu.tflite # TensorFlow Edge TPU
|
|
42
|
+
yolov8n_paddle_model # PaddlePaddle
|
|
43
|
+
|
|
44
|
+
TensorFlow.js:
|
|
45
|
+
$ cd .. && git clone https://github.com/zldrobit/tfjs-yolov5-example.git && cd tfjs-yolov5-example
|
|
46
|
+
$ npm install
|
|
47
|
+
$ ln -s ../../yolov5/yolov8n_web_model public/yolov8n_web_model
|
|
48
|
+
$ npm start
|
|
49
|
+
"""
|
|
50
|
+
import json
|
|
51
|
+
import os
|
|
52
|
+
import platform
|
|
53
|
+
import subprocess
|
|
54
|
+
import time
|
|
55
|
+
import warnings
|
|
56
|
+
from copy import deepcopy
|
|
57
|
+
from pathlib import Path
|
|
58
|
+
|
|
59
|
+
import torch
|
|
60
|
+
|
|
61
|
+
from ...nn.autobackend import check_class_names
|
|
62
|
+
from ...nn.modules import C2f, Detect, Segment
|
|
63
|
+
from ...nn.tasks import DetectionModel, SegmentationModel
|
|
64
|
+
from ..cfg import get_cfg
|
|
65
|
+
from ..utils import (
|
|
66
|
+
DEFAULT_CFG,
|
|
67
|
+
LINUX,
|
|
68
|
+
LOGGER,
|
|
69
|
+
MACOS,
|
|
70
|
+
__version__,
|
|
71
|
+
callbacks,
|
|
72
|
+
colorstr,
|
|
73
|
+
get_default_args,
|
|
74
|
+
yaml_save,
|
|
75
|
+
)
|
|
76
|
+
from ..utils.checks import (
|
|
77
|
+
check_imgsz,
|
|
78
|
+
check_requirements,
|
|
79
|
+
check_version,
|
|
80
|
+
)
|
|
81
|
+
from ..utils.files import file_size
|
|
82
|
+
from ..utils.ops import Profile
|
|
83
|
+
from ..utils.torch_utils import (
|
|
84
|
+
get_latest_opset,
|
|
85
|
+
select_device,
|
|
86
|
+
smart_inference_mode,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
ARM64 = platform.machine() in ("arm64", "aarch64")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def export_formats():
|
|
93
|
+
"""YOLOv8 export formats."""
|
|
94
|
+
import pandas
|
|
95
|
+
|
|
96
|
+
x = [
|
|
97
|
+
["PyTorch", "-", ".pt", True, True],
|
|
98
|
+
["TorchScript", "torchscript", ".torchscript", True, True],
|
|
99
|
+
["ONNX", "onnx", ".onnx", True, True],
|
|
100
|
+
["OpenVINO", "openvino", "_openvino_model", True, False],
|
|
101
|
+
["TensorRT", "engine", ".engine", False, True],
|
|
102
|
+
["CoreML", "coreml", ".mlmodel", True, False],
|
|
103
|
+
["TensorFlow SavedModel", "saved_model", "_saved_model", True, True],
|
|
104
|
+
["TensorFlow GraphDef", "pb", ".pb", True, True],
|
|
105
|
+
["TensorFlow Lite", "tflite", ".tflite", True, False],
|
|
106
|
+
["TensorFlow Edge TPU", "edgetpu", "_edgetpu.tflite", True, False],
|
|
107
|
+
["TensorFlow.js", "tfjs", "_web_model", True, False],
|
|
108
|
+
["PaddlePaddle", "paddle", "_paddle_model", True, True],
|
|
109
|
+
]
|
|
110
|
+
return pandas.DataFrame(
|
|
111
|
+
x, columns=["Format", "Argument", "Suffix", "CPU", "GPU"]
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def gd_outputs(gd):
|
|
116
|
+
"""TensorFlow GraphDef model output node names."""
|
|
117
|
+
name_list, input_list = [], []
|
|
118
|
+
for node in gd.node: # tensorflow.core.framework.node_def_pb2.NodeDef
|
|
119
|
+
name_list.append(node.name)
|
|
120
|
+
input_list.extend(node.input)
|
|
121
|
+
return sorted(
|
|
122
|
+
f"{x}:0"
|
|
123
|
+
for x in list(set(name_list) - set(input_list))
|
|
124
|
+
if not x.startswith("NoOp")
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def try_export(inner_func):
|
|
129
|
+
"""YOLOv8 export decorator, i..e @try_export."""
|
|
130
|
+
inner_args = get_default_args(inner_func)
|
|
131
|
+
|
|
132
|
+
def outer_func(*args, **kwargs):
|
|
133
|
+
"""Export a model."""
|
|
134
|
+
prefix = inner_args["prefix"]
|
|
135
|
+
try:
|
|
136
|
+
with Profile() as dt:
|
|
137
|
+
f, model = inner_func(*args, **kwargs)
|
|
138
|
+
LOGGER.info(
|
|
139
|
+
f"{prefix} export success ✅ {dt.t:.1f}s, saved as {f} ({file_size(f):.1f} MB)"
|
|
140
|
+
)
|
|
141
|
+
return f, model
|
|
142
|
+
except Exception as e:
|
|
143
|
+
LOGGER.info(f"{prefix} export failure ❌ {dt.t:.1f}s: {e}")
|
|
144
|
+
return None, None
|
|
145
|
+
|
|
146
|
+
return outer_func
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class Exporter:
|
|
150
|
+
"""
|
|
151
|
+
A class for exporting a model.
|
|
152
|
+
|
|
153
|
+
Attributes:
|
|
154
|
+
args (SimpleNamespace): Configuration for the exporter.
|
|
155
|
+
save_dir (Path): Directory to save results.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __init__(self, cfg=DEFAULT_CFG, overrides=None, _callbacks=None):
|
|
159
|
+
"""
|
|
160
|
+
Initializes the Exporter class.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
cfg (str, optional): Path to a configuration file. Defaults to DEFAULT_CFG.
|
|
164
|
+
overrides (dict, optional): Configuration overrides. Defaults to None.
|
|
165
|
+
_callbacks (list, optional): List of callback functions. Defaults to None.
|
|
166
|
+
"""
|
|
167
|
+
self.args = get_cfg(cfg, overrides)
|
|
168
|
+
self.callbacks = _callbacks or callbacks.get_default_callbacks()
|
|
169
|
+
|
|
170
|
+
@smart_inference_mode()
|
|
171
|
+
def __call__(self, model=None):
|
|
172
|
+
"""Returns list of exported files/dirs after running callbacks."""
|
|
173
|
+
self.run_callbacks("on_export_start")
|
|
174
|
+
t = time.time()
|
|
175
|
+
format = self.args.format.lower() # to lowercase
|
|
176
|
+
if format in ("tensorrt", "trt"): # engine aliases
|
|
177
|
+
format = "engine"
|
|
178
|
+
fmts = tuple(
|
|
179
|
+
export_formats()["Argument"][1:]
|
|
180
|
+
) # available export formats
|
|
181
|
+
flags = [x == format for x in fmts]
|
|
182
|
+
if sum(flags) != 1:
|
|
183
|
+
raise ValueError(
|
|
184
|
+
f"Invalid export format='{format}'. Valid formats are {fmts}"
|
|
185
|
+
)
|
|
186
|
+
(
|
|
187
|
+
jit,
|
|
188
|
+
onnx,
|
|
189
|
+
xml,
|
|
190
|
+
engine,
|
|
191
|
+
coreml,
|
|
192
|
+
saved_model,
|
|
193
|
+
pb,
|
|
194
|
+
tflite,
|
|
195
|
+
edgetpu,
|
|
196
|
+
tfjs,
|
|
197
|
+
paddle,
|
|
198
|
+
) = flags # export booleans
|
|
199
|
+
|
|
200
|
+
# Load PyTorch model
|
|
201
|
+
self.device = select_device(
|
|
202
|
+
"cpu" if self.args.device is None else self.args.device
|
|
203
|
+
)
|
|
204
|
+
if self.args.half and onnx and self.device.type == "cpu":
|
|
205
|
+
LOGGER.warning(
|
|
206
|
+
"WARNING ⚠️ half=True only compatible with GPU export, i.e. use device=0"
|
|
207
|
+
)
|
|
208
|
+
self.args.half = False
|
|
209
|
+
assert (
|
|
210
|
+
not self.args.dynamic
|
|
211
|
+
), "half=True not compatible with dynamic=True, i.e. use only one."
|
|
212
|
+
|
|
213
|
+
# Checks
|
|
214
|
+
model.names = check_class_names(model.names)
|
|
215
|
+
self.imgsz = check_imgsz(
|
|
216
|
+
self.args.imgsz, stride=model.stride, min_dim=2
|
|
217
|
+
) # check image size
|
|
218
|
+
if self.args.optimize:
|
|
219
|
+
assert (
|
|
220
|
+
self.device.type == "cpu"
|
|
221
|
+
), "--optimize not compatible with cuda devices, i.e. use --device cpu"
|
|
222
|
+
if edgetpu and not LINUX:
|
|
223
|
+
raise SystemError(
|
|
224
|
+
"Edge TPU export only supported on Linux. See https://coral.ai/docs/edgetpu/compiler/"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Input
|
|
228
|
+
im = torch.zeros(self.args.batch, 3, *self.imgsz).to(self.device)
|
|
229
|
+
file = Path(
|
|
230
|
+
getattr(model, "pt_path", None)
|
|
231
|
+
or getattr(model, "yaml_file", None)
|
|
232
|
+
or model.yaml.get("yaml_file", "")
|
|
233
|
+
)
|
|
234
|
+
if file.suffix == ".yaml":
|
|
235
|
+
file = Path(file.name)
|
|
236
|
+
|
|
237
|
+
# Update model
|
|
238
|
+
model = deepcopy(model).to(self.device)
|
|
239
|
+
for p in model.parameters():
|
|
240
|
+
p.requires_grad = False
|
|
241
|
+
model.eval()
|
|
242
|
+
model.float()
|
|
243
|
+
model = model.fuse()
|
|
244
|
+
for k, m in model.named_modules():
|
|
245
|
+
if isinstance(m, (Detect, Segment)):
|
|
246
|
+
m.dynamic = self.args.dynamic
|
|
247
|
+
m.export = True
|
|
248
|
+
m.format = self.args.format
|
|
249
|
+
elif isinstance(m, C2f) and not any(
|
|
250
|
+
(saved_model, pb, tflite, edgetpu, tfjs)
|
|
251
|
+
):
|
|
252
|
+
# EdgeTPU does not support FlexSplitV while split provides cleaner ONNX graph
|
|
253
|
+
m.forward = m.forward_split
|
|
254
|
+
|
|
255
|
+
y = None
|
|
256
|
+
for _ in range(2):
|
|
257
|
+
y = model(im) # dry runs
|
|
258
|
+
if self.args.half and (engine or onnx) and self.device.type != "cpu":
|
|
259
|
+
im, model = im.half(), model.half() # to FP16
|
|
260
|
+
|
|
261
|
+
# Warnings
|
|
262
|
+
warnings.filterwarnings(
|
|
263
|
+
"ignore", category=torch.jit.TracerWarning
|
|
264
|
+
) # suppress TracerWarning
|
|
265
|
+
warnings.filterwarnings(
|
|
266
|
+
"ignore", category=UserWarning
|
|
267
|
+
) # suppress shape prim::Constant missing ONNX warning
|
|
268
|
+
warnings.filterwarnings(
|
|
269
|
+
"ignore", category=DeprecationWarning
|
|
270
|
+
) # suppress CoreML np.bool deprecation warning
|
|
271
|
+
|
|
272
|
+
# Assign
|
|
273
|
+
self.im = im
|
|
274
|
+
self.model = model
|
|
275
|
+
self.file = file
|
|
276
|
+
self.output_shape = (
|
|
277
|
+
tuple(y.shape)
|
|
278
|
+
if isinstance(y, torch.Tensor)
|
|
279
|
+
else tuple(
|
|
280
|
+
tuple(x.shape if isinstance(x, torch.Tensor) else [])
|
|
281
|
+
for x in y
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
self.pretty_name = Path(
|
|
285
|
+
self.model.yaml.get("yaml_file", self.file)
|
|
286
|
+
).stem.replace("yolo", "YOLO")
|
|
287
|
+
trained_on = (
|
|
288
|
+
f"trained on {Path(self.args.data).name}"
|
|
289
|
+
if self.args.data
|
|
290
|
+
else "(untrained)"
|
|
291
|
+
)
|
|
292
|
+
description = f"Ultralytics {self.pretty_name} model {trained_on}"
|
|
293
|
+
self.metadata = {
|
|
294
|
+
"description": description,
|
|
295
|
+
"author": "Ultralytics",
|
|
296
|
+
"license": "AGPL-3.0 https://ultralytics.com/license",
|
|
297
|
+
"version": __version__,
|
|
298
|
+
"stride": int(max(model.stride)),
|
|
299
|
+
"task": model.task,
|
|
300
|
+
"batch": self.args.batch,
|
|
301
|
+
"imgsz": self.imgsz,
|
|
302
|
+
"names": model.names,
|
|
303
|
+
} # model metadata
|
|
304
|
+
if model.task == "pose":
|
|
305
|
+
self.metadata["kpt_shape"] = model.kpt_shape
|
|
306
|
+
|
|
307
|
+
LOGGER.info(
|
|
308
|
+
f"\n{colorstr('PyTorch:')} starting from {file} with input shape {tuple(im.shape)} BCHW and "
|
|
309
|
+
f"output shape(s) {self.output_shape} ({file_size(file):.1f} MB)"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# Exports
|
|
313
|
+
f = [""] * len(fmts) # exported filenames
|
|
314
|
+
if jit: # TorchScript
|
|
315
|
+
f[0], _ = self.export_torchscript()
|
|
316
|
+
if engine: # TensorRT required before ONNX
|
|
317
|
+
f[1], _ = self.export_engine()
|
|
318
|
+
if onnx or xml: # OpenVINO requires ONNX
|
|
319
|
+
f[2], _ = self.export_onnx()
|
|
320
|
+
if xml: # OpenVINO
|
|
321
|
+
f[3], _ = self.export_openvino()
|
|
322
|
+
if coreml: # CoreML
|
|
323
|
+
f[4], _ = self.export_coreml()
|
|
324
|
+
if any((saved_model, pb, tflite, edgetpu, tfjs)): # TensorFlow formats
|
|
325
|
+
self.args.int8 |= edgetpu
|
|
326
|
+
f[5], s_model = self.export_saved_model()
|
|
327
|
+
if pb or tfjs: # pb prerequisite to tfjs
|
|
328
|
+
f[6], _ = self.export_pb(s_model)
|
|
329
|
+
if tflite:
|
|
330
|
+
f[7], _ = self.export_tflite(
|
|
331
|
+
s_model, nms=False, agnostic_nms=self.args.agnostic_nms
|
|
332
|
+
)
|
|
333
|
+
if edgetpu:
|
|
334
|
+
f[8], _ = self.export_edgetpu(
|
|
335
|
+
tflite_model=Path(f[5])
|
|
336
|
+
/ f"{self.file.stem}_full_integer_quant.tflite"
|
|
337
|
+
)
|
|
338
|
+
if tfjs:
|
|
339
|
+
f[9], _ = self.export_tfjs()
|
|
340
|
+
if paddle: # PaddlePaddle
|
|
341
|
+
f[10], _ = self.export_paddle()
|
|
342
|
+
|
|
343
|
+
# Finish
|
|
344
|
+
f = [str(x) for x in f if x] # filter out '' and None
|
|
345
|
+
if any(f):
|
|
346
|
+
f = str(Path(f[-1]))
|
|
347
|
+
square = self.imgsz[0] == self.imgsz[1]
|
|
348
|
+
s = (
|
|
349
|
+
""
|
|
350
|
+
if square
|
|
351
|
+
else f"WARNING ⚠️ non-PyTorch val requires square images, 'imgsz={self.imgsz}' will not "
|
|
352
|
+
f"work. Use export 'imgsz={max(self.imgsz)}' if val is required."
|
|
353
|
+
)
|
|
354
|
+
imgsz = (
|
|
355
|
+
self.imgsz[0]
|
|
356
|
+
if square
|
|
357
|
+
else str(self.imgsz)[1:-1].replace(" ", "")
|
|
358
|
+
)
|
|
359
|
+
data = (
|
|
360
|
+
f"data={self.args.data}"
|
|
361
|
+
if model.task == "segment" and format == "pb"
|
|
362
|
+
else ""
|
|
363
|
+
)
|
|
364
|
+
LOGGER.info(
|
|
365
|
+
f"\nExport complete ({time.time() - t:.1f}s)"
|
|
366
|
+
f"\nResults saved to {colorstr('bold', file.parent.resolve())}"
|
|
367
|
+
f"\nPredict: yolo predict task={model.task} model={f} imgsz={imgsz} {data}"
|
|
368
|
+
f"\nValidate: yolo val task={model.task} model={f} imgsz={imgsz} data={self.args.data} {s}"
|
|
369
|
+
f"\nVisualize: https://netron.app"
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
self.run_callbacks("on_export_end")
|
|
373
|
+
return f # return list of exported files/dirs
|
|
374
|
+
|
|
375
|
+
@try_export
|
|
376
|
+
def export_torchscript(self, prefix=colorstr("TorchScript:")):
|
|
377
|
+
"""YOLOv8 TorchScript model export."""
|
|
378
|
+
LOGGER.info(
|
|
379
|
+
f"\n{prefix} starting export with torch {torch.__version__}..."
|
|
380
|
+
)
|
|
381
|
+
f = self.file.with_suffix(".torchscript")
|
|
382
|
+
|
|
383
|
+
ts = torch.jit.trace(self.model, self.im, strict=False)
|
|
384
|
+
extra_files = {
|
|
385
|
+
"config.txt": json.dumps(self.metadata)
|
|
386
|
+
} # torch._C.ExtraFilesMap()
|
|
387
|
+
if (
|
|
388
|
+
self.args.optimize
|
|
389
|
+
): # https://pytorch.org/tutorials/recipes/mobile_interpreter.html
|
|
390
|
+
LOGGER.info(f"{prefix} optimizing for mobile...")
|
|
391
|
+
from torch.utils.mobile_optimizer import optimize_for_mobile
|
|
392
|
+
|
|
393
|
+
optimize_for_mobile(ts)._save_for_lite_interpreter(
|
|
394
|
+
str(f), _extra_files=extra_files
|
|
395
|
+
)
|
|
396
|
+
else:
|
|
397
|
+
ts.save(str(f), _extra_files=extra_files)
|
|
398
|
+
return f, None
|
|
399
|
+
|
|
400
|
+
@try_export
|
|
401
|
+
def export_onnx(self, prefix=colorstr("ONNX:")):
|
|
402
|
+
"""YOLOv8 ONNX export."""
|
|
403
|
+
requirements = ["onnx>=1.12.0"]
|
|
404
|
+
if self.args.simplify:
|
|
405
|
+
requirements += [
|
|
406
|
+
"onnxsim>=0.4.17",
|
|
407
|
+
(
|
|
408
|
+
"onnxruntime-gpu"
|
|
409
|
+
if torch.cuda.is_available()
|
|
410
|
+
else "onnxruntime"
|
|
411
|
+
),
|
|
412
|
+
]
|
|
413
|
+
check_requirements(requirements)
|
|
414
|
+
import onnx # noqa
|
|
415
|
+
|
|
416
|
+
opset_version = self.args.opset or get_latest_opset()
|
|
417
|
+
LOGGER.info(
|
|
418
|
+
f"\n{prefix} starting export with onnx {onnx.__version__} opset {opset_version}..."
|
|
419
|
+
)
|
|
420
|
+
f = str(self.file.with_suffix(".onnx"))
|
|
421
|
+
|
|
422
|
+
output_names = (
|
|
423
|
+
["output0", "output1"]
|
|
424
|
+
if isinstance(self.model, SegmentationModel)
|
|
425
|
+
else ["output0"]
|
|
426
|
+
)
|
|
427
|
+
dynamic = self.args.dynamic
|
|
428
|
+
if dynamic:
|
|
429
|
+
dynamic = {
|
|
430
|
+
"images": {0: "batch", 2: "height", 3: "width"}
|
|
431
|
+
} # shape(1,3,640,640)
|
|
432
|
+
if isinstance(self.model, SegmentationModel):
|
|
433
|
+
dynamic["output0"] = {
|
|
434
|
+
0: "batch",
|
|
435
|
+
1: "anchors",
|
|
436
|
+
} # shape(1,25200,85)
|
|
437
|
+
dynamic["output1"] = {
|
|
438
|
+
0: "batch",
|
|
439
|
+
2: "mask_height",
|
|
440
|
+
3: "mask_width",
|
|
441
|
+
} # shape(1,32,160,160)
|
|
442
|
+
elif isinstance(self.model, DetectionModel):
|
|
443
|
+
dynamic["output0"] = {
|
|
444
|
+
0: "batch",
|
|
445
|
+
1: "anchors",
|
|
446
|
+
} # shape(1,25200,85)
|
|
447
|
+
|
|
448
|
+
torch.onnx.export(
|
|
449
|
+
(
|
|
450
|
+
self.model.cpu() if dynamic else self.model
|
|
451
|
+
), # --dynamic only compatible with cpu
|
|
452
|
+
self.im.cpu() if dynamic else self.im,
|
|
453
|
+
f,
|
|
454
|
+
verbose=False,
|
|
455
|
+
opset_version=opset_version,
|
|
456
|
+
do_constant_folding=True, # WARNING: DNN inference with torch>=1.12 may require do_constant_folding=False
|
|
457
|
+
input_names=["images"],
|
|
458
|
+
output_names=output_names,
|
|
459
|
+
dynamic_axes=dynamic or None,
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# Checks
|
|
463
|
+
model_onnx = onnx.load(f) # load onnx model
|
|
464
|
+
# onnx.checker.check_model(model_onnx) # check onnx model
|
|
465
|
+
|
|
466
|
+
# Simplify
|
|
467
|
+
if self.args.simplify:
|
|
468
|
+
try:
|
|
469
|
+
import onnxsim
|
|
470
|
+
|
|
471
|
+
LOGGER.info(
|
|
472
|
+
f"{prefix} simplifying with onnxsim {onnxsim.__version__}..."
|
|
473
|
+
)
|
|
474
|
+
# subprocess.run(f'onnxsim {f} {f}', shell=True)
|
|
475
|
+
model_onnx, check = onnxsim.simplify(model_onnx)
|
|
476
|
+
assert check, "Simplified ONNX model could not be validated"
|
|
477
|
+
except Exception as e:
|
|
478
|
+
LOGGER.info(f"{prefix} simplifier failure: {e}")
|
|
479
|
+
|
|
480
|
+
# Metadata
|
|
481
|
+
for k, v in self.metadata.items():
|
|
482
|
+
meta = model_onnx.metadata_props.add()
|
|
483
|
+
meta.key, meta.value = k, str(v)
|
|
484
|
+
|
|
485
|
+
onnx.save(model_onnx, f)
|
|
486
|
+
return f, model_onnx
|
|
487
|
+
|
|
488
|
+
@try_export
|
|
489
|
+
def export_openvino(self, prefix=colorstr("OpenVINO:")):
|
|
490
|
+
"""YOLOv8 OpenVINO export."""
|
|
491
|
+
check_requirements(
|
|
492
|
+
"openvino-dev>=2022.3"
|
|
493
|
+
) # requires openvino-dev: https://pypi.org/project/openvino-dev/
|
|
494
|
+
import openvino.runtime as ov # noqa
|
|
495
|
+
from openvino.tools import mo # noqa
|
|
496
|
+
|
|
497
|
+
LOGGER.info(
|
|
498
|
+
f"\n{prefix} starting export with openvino {ov.__version__}..."
|
|
499
|
+
)
|
|
500
|
+
f = str(self.file).replace(
|
|
501
|
+
self.file.suffix, f"_openvino_model{os.sep}"
|
|
502
|
+
)
|
|
503
|
+
f_onnx = self.file.with_suffix(".onnx")
|
|
504
|
+
f_ov = str(Path(f) / self.file.with_suffix(".xml").name)
|
|
505
|
+
|
|
506
|
+
ov_model = mo.convert_model(
|
|
507
|
+
f_onnx,
|
|
508
|
+
model_name=self.pretty_name,
|
|
509
|
+
framework="onnx",
|
|
510
|
+
compress_to_fp16=self.args.half,
|
|
511
|
+
) # export
|
|
512
|
+
|
|
513
|
+
# Set RT info
|
|
514
|
+
ov_model.set_rt_info("YOLOv8", ["model_info", "model_type"])
|
|
515
|
+
ov_model.set_rt_info(True, ["model_info", "reverse_input_channels"])
|
|
516
|
+
ov_model.set_rt_info(114, ["model_info", "pad_value"])
|
|
517
|
+
ov_model.set_rt_info([255.0], ["model_info", "scale_values"])
|
|
518
|
+
ov_model.set_rt_info(self.args.iou, ["model_info", "iou_threshold"])
|
|
519
|
+
ov_model.set_rt_info(
|
|
520
|
+
[v.replace(" ", "_") for k, v in sorted(self.model.names.items())],
|
|
521
|
+
["model_info", "labels"],
|
|
522
|
+
)
|
|
523
|
+
if self.model.task != "classify":
|
|
524
|
+
ov_model.set_rt_info(
|
|
525
|
+
"fit_to_window_letterbox", ["model_info", "resize_type"]
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
ov.serialize(ov_model, f_ov) # save
|
|
529
|
+
yaml_save(
|
|
530
|
+
Path(f) / "metadata.yaml", self.metadata
|
|
531
|
+
) # add metadata.yaml
|
|
532
|
+
return f, None
|
|
533
|
+
|
|
534
|
+
@try_export
|
|
535
|
+
def export_paddle(self, prefix=colorstr("PaddlePaddle:")):
|
|
536
|
+
"""YOLOv8 Paddle export."""
|
|
537
|
+
check_requirements(("paddlepaddle", "x2paddle"))
|
|
538
|
+
import x2paddle # noqa
|
|
539
|
+
from x2paddle.convert import pytorch2paddle # noqa
|
|
540
|
+
|
|
541
|
+
LOGGER.info(
|
|
542
|
+
f"\n{prefix} starting export with X2Paddle {x2paddle.__version__}..."
|
|
543
|
+
)
|
|
544
|
+
f = str(self.file).replace(self.file.suffix, f"_paddle_model{os.sep}")
|
|
545
|
+
|
|
546
|
+
pytorch2paddle(
|
|
547
|
+
module=self.model,
|
|
548
|
+
save_dir=f,
|
|
549
|
+
jit_type="trace",
|
|
550
|
+
input_examples=[self.im],
|
|
551
|
+
) # export
|
|
552
|
+
yaml_save(
|
|
553
|
+
Path(f) / "metadata.yaml", self.metadata
|
|
554
|
+
) # add metadata.yaml
|
|
555
|
+
return f, None
|
|
556
|
+
|
|
557
|
+
@try_export
|
|
558
|
+
def export_coreml(self, prefix=colorstr("CoreML:")):
|
|
559
|
+
"""YOLOv8 CoreML export."""
|
|
560
|
+
check_requirements("coremltools>=6.0")
|
|
561
|
+
import coremltools as ct # noqa
|
|
562
|
+
|
|
563
|
+
LOGGER.info(
|
|
564
|
+
f"\n{prefix} starting export with coremltools {ct.__version__}..."
|
|
565
|
+
)
|
|
566
|
+
f = self.file.with_suffix(".mlmodel")
|
|
567
|
+
|
|
568
|
+
bias = [0.0, 0.0, 0.0]
|
|
569
|
+
scale = 1 / 255
|
|
570
|
+
classifier_config = None
|
|
571
|
+
if self.model.task == "classify":
|
|
572
|
+
classifier_config = (
|
|
573
|
+
ct.ClassifierConfig(list(self.model.names.values()))
|
|
574
|
+
if self.args.nms
|
|
575
|
+
else None
|
|
576
|
+
)
|
|
577
|
+
model = self.model
|
|
578
|
+
elif self.model.task == "detect":
|
|
579
|
+
model = (
|
|
580
|
+
iOSDetectModel(self.model, self.im)
|
|
581
|
+
if self.args.nms
|
|
582
|
+
else self.model
|
|
583
|
+
)
|
|
584
|
+
else:
|
|
585
|
+
# TODO CoreML Segment and Pose model pipelining
|
|
586
|
+
model = self.model
|
|
587
|
+
|
|
588
|
+
ts = torch.jit.trace(
|
|
589
|
+
model.eval(), self.im, strict=False
|
|
590
|
+
) # TorchScript model
|
|
591
|
+
ct_model = ct.convert(
|
|
592
|
+
ts,
|
|
593
|
+
inputs=[
|
|
594
|
+
ct.ImageType(
|
|
595
|
+
"image", shape=self.im.shape, scale=scale, bias=bias
|
|
596
|
+
)
|
|
597
|
+
],
|
|
598
|
+
classifier_config=classifier_config,
|
|
599
|
+
)
|
|
600
|
+
bits, mode = (
|
|
601
|
+
(8, "kmeans_lut")
|
|
602
|
+
if self.args.int8
|
|
603
|
+
else (16, "linear") if self.args.half else (32, None)
|
|
604
|
+
)
|
|
605
|
+
if bits < 32:
|
|
606
|
+
if "kmeans" in mode:
|
|
607
|
+
check_requirements(
|
|
608
|
+
"scikit-learn"
|
|
609
|
+
) # scikit-learn package required for k-means quantization
|
|
610
|
+
ct_model = (
|
|
611
|
+
ct.models.neural_network.quantization_utils.quantize_weights(
|
|
612
|
+
ct_model, bits, mode
|
|
613
|
+
)
|
|
614
|
+
)
|
|
615
|
+
if self.args.nms and self.model.task == "detect":
|
|
616
|
+
ct_model = self._pipeline_coreml(ct_model)
|
|
617
|
+
|
|
618
|
+
m = self.metadata # metadata dict
|
|
619
|
+
ct_model.short_description = m.pop("description")
|
|
620
|
+
ct_model.author = m.pop("author")
|
|
621
|
+
ct_model.license = m.pop("license")
|
|
622
|
+
ct_model.version = m.pop("version")
|
|
623
|
+
ct_model.user_defined_metadata.update(
|
|
624
|
+
{k: str(v) for k, v in m.items()}
|
|
625
|
+
)
|
|
626
|
+
ct_model.save(str(f))
|
|
627
|
+
return f, ct_model
|
|
628
|
+
|
|
629
|
+
@try_export
|
|
630
|
+
def export_engine(self, prefix=colorstr("TensorRT:")):
|
|
631
|
+
"""YOLOv8 TensorRT export https://developer.nvidia.com/tensorrt."""
|
|
632
|
+
assert (
|
|
633
|
+
self.im.device.type != "cpu"
|
|
634
|
+
), "export running on CPU but must be on GPU, i.e. use 'device=0'"
|
|
635
|
+
try:
|
|
636
|
+
import tensorrt as trt # noqa
|
|
637
|
+
except ImportError:
|
|
638
|
+
if LINUX:
|
|
639
|
+
check_requirements(
|
|
640
|
+
"nvidia-tensorrt",
|
|
641
|
+
cmds="-U --index-url https://pypi.ngc.nvidia.com",
|
|
642
|
+
)
|
|
643
|
+
import tensorrt as trt # noqa
|
|
644
|
+
|
|
645
|
+
check_version(
|
|
646
|
+
trt.__version__, "7.0.0", hard=True
|
|
647
|
+
) # require tensorrt>=8.0.0
|
|
648
|
+
self.args.simplify = True
|
|
649
|
+
f_onnx, _ = self.export_onnx()
|
|
650
|
+
|
|
651
|
+
LOGGER.info(
|
|
652
|
+
f"\n{prefix} starting export with TensorRT {trt.__version__}..."
|
|
653
|
+
)
|
|
654
|
+
assert Path(f_onnx).exists(), f"failed to export ONNX file: {f_onnx}"
|
|
655
|
+
f = self.file.with_suffix(".engine") # TensorRT engine file
|
|
656
|
+
logger = trt.Logger(trt.Logger.INFO)
|
|
657
|
+
if self.args.verbose:
|
|
658
|
+
logger.min_severity = trt.Logger.Severity.VERBOSE
|
|
659
|
+
|
|
660
|
+
builder = trt.Builder(logger)
|
|
661
|
+
config = builder.create_builder_config()
|
|
662
|
+
config.max_workspace_size = self.args.workspace * 1 << 30
|
|
663
|
+
# config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, workspace << 30) # fix TRT 8.4 deprecation notice
|
|
664
|
+
|
|
665
|
+
flag = 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
|
|
666
|
+
network = builder.create_network(flag)
|
|
667
|
+
parser = trt.OnnxParser(network, logger)
|
|
668
|
+
if not parser.parse_from_file(f_onnx):
|
|
669
|
+
raise RuntimeError(f"failed to load ONNX file: {f_onnx}")
|
|
670
|
+
|
|
671
|
+
inputs = [network.get_input(i) for i in range(network.num_inputs)]
|
|
672
|
+
outputs = [network.get_output(i) for i in range(network.num_outputs)]
|
|
673
|
+
for inp in inputs:
|
|
674
|
+
LOGGER.info(
|
|
675
|
+
f'{prefix} input "{inp.name}" with shape{inp.shape} {inp.dtype}'
|
|
676
|
+
)
|
|
677
|
+
for out in outputs:
|
|
678
|
+
LOGGER.info(
|
|
679
|
+
f'{prefix} output "{out.name}" with shape{out.shape} {out.dtype}'
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
if self.args.dynamic:
|
|
683
|
+
shape = self.im.shape
|
|
684
|
+
if shape[0] <= 1:
|
|
685
|
+
LOGGER.warning(
|
|
686
|
+
f"{prefix} WARNING ⚠️ --dynamic model requires maximum --batch-size argument"
|
|
687
|
+
)
|
|
688
|
+
profile = builder.create_optimization_profile()
|
|
689
|
+
for inp in inputs:
|
|
690
|
+
profile.set_shape(
|
|
691
|
+
inp.name,
|
|
692
|
+
(1, *shape[1:]),
|
|
693
|
+
(max(1, shape[0] // 2), *shape[1:]),
|
|
694
|
+
shape,
|
|
695
|
+
)
|
|
696
|
+
config.add_optimization_profile(profile)
|
|
697
|
+
|
|
698
|
+
LOGGER.info(
|
|
699
|
+
f"{prefix} building FP{16 if builder.platform_has_fast_fp16 and self.args.half else 32} engine as {f}"
|
|
700
|
+
)
|
|
701
|
+
if builder.platform_has_fast_fp16 and self.args.half:
|
|
702
|
+
config.set_flag(trt.BuilderFlag.FP16)
|
|
703
|
+
|
|
704
|
+
# Write file
|
|
705
|
+
with builder.build_engine(network, config) as engine, open(
|
|
706
|
+
f, "wb"
|
|
707
|
+
) as t:
|
|
708
|
+
# Metadata
|
|
709
|
+
meta = json.dumps(self.metadata)
|
|
710
|
+
t.write(len(meta).to_bytes(4, byteorder="little", signed=True))
|
|
711
|
+
t.write(meta.encode())
|
|
712
|
+
# Model
|
|
713
|
+
t.write(engine.serialize())
|
|
714
|
+
|
|
715
|
+
return f, None
|
|
716
|
+
|
|
717
|
+
@try_export
|
|
718
|
+
def export_saved_model(self, prefix=colorstr("TensorFlow SavedModel:")):
|
|
719
|
+
"""YOLOv8 TensorFlow SavedModel export."""
|
|
720
|
+
try:
|
|
721
|
+
import tensorflow as tf # noqa
|
|
722
|
+
except ImportError:
|
|
723
|
+
cuda = torch.cuda.is_available()
|
|
724
|
+
check_requirements(
|
|
725
|
+
f"tensorflow{'-macos' if MACOS else '-aarch64' if ARM64 else '' if cuda else '-cpu'}"
|
|
726
|
+
)
|
|
727
|
+
import tensorflow as tf # noqa
|
|
728
|
+
check_requirements(
|
|
729
|
+
(
|
|
730
|
+
"onnx",
|
|
731
|
+
"onnx2tf>=1.7.7",
|
|
732
|
+
"sng4onnx>=1.0.1",
|
|
733
|
+
"onnxsim>=0.4.17",
|
|
734
|
+
"onnx_graphsurgeon>=0.3.26",
|
|
735
|
+
"tflite_support",
|
|
736
|
+
(
|
|
737
|
+
"onnxruntime-gpu"
|
|
738
|
+
if torch.cuda.is_available()
|
|
739
|
+
else "onnxruntime"
|
|
740
|
+
),
|
|
741
|
+
),
|
|
742
|
+
cmds="--extra-index-url https://pypi.ngc.nvidia.com",
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
LOGGER.info(
|
|
746
|
+
f"\n{prefix} starting export with tensorflow {tf.__version__}..."
|
|
747
|
+
)
|
|
748
|
+
f = Path(str(self.file).replace(self.file.suffix, "_saved_model"))
|
|
749
|
+
if f.is_dir():
|
|
750
|
+
import shutil
|
|
751
|
+
|
|
752
|
+
shutil.rmtree(f) # delete output folder
|
|
753
|
+
|
|
754
|
+
# Export to ONNX
|
|
755
|
+
self.args.simplify = True
|
|
756
|
+
f_onnx, _ = self.export_onnx()
|
|
757
|
+
|
|
758
|
+
# Export to TF
|
|
759
|
+
int8 = "-oiqt -qt per-tensor" if self.args.int8 else ""
|
|
760
|
+
cmd = f"onnx2tf -i {f_onnx} -o {f} -nuo --non_verbose {int8}"
|
|
761
|
+
LOGGER.info(f"\n{prefix} running '{cmd.strip()}'")
|
|
762
|
+
subprocess.run(cmd, shell=True)
|
|
763
|
+
yaml_save(f / "metadata.yaml", self.metadata) # add metadata.yaml
|
|
764
|
+
|
|
765
|
+
# Remove/rename TFLite models
|
|
766
|
+
if self.args.int8:
|
|
767
|
+
for file in f.rglob("*_dynamic_range_quant.tflite"):
|
|
768
|
+
file.rename(
|
|
769
|
+
file.with_stem(
|
|
770
|
+
file.stem.replace("_dynamic_range_quant", "_int8")
|
|
771
|
+
)
|
|
772
|
+
)
|
|
773
|
+
for file in f.rglob("*_integer_quant_with_int16_act.tflite"):
|
|
774
|
+
file.unlink() # delete extra fp16 activation TFLite files
|
|
775
|
+
|
|
776
|
+
# Add TFLite metadata
|
|
777
|
+
for file in f.rglob("*.tflite"):
|
|
778
|
+
(
|
|
779
|
+
f.unlink()
|
|
780
|
+
if "quant_with_int16_act.tflite" in str(f)
|
|
781
|
+
else self._add_tflite_metadata(file)
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
# Load saved_model
|
|
785
|
+
keras_model = tf.saved_model.load(f, tags=None, options=None)
|
|
786
|
+
|
|
787
|
+
return str(f), keras_model
|
|
788
|
+
|
|
789
|
+
@try_export
|
|
790
|
+
def export_pb(self, keras_model, prefix=colorstr("TensorFlow GraphDef:")):
|
|
791
|
+
"""YOLOv8 TensorFlow GraphDef *.pb export https://github.com/leimao/Frozen_Graph_TensorFlow."""
|
|
792
|
+
import tensorflow as tf # noqa
|
|
793
|
+
from tensorflow.python.framework.convert_to_constants import (
|
|
794
|
+
convert_variables_to_constants_v2,
|
|
795
|
+
) # noqa
|
|
796
|
+
|
|
797
|
+
LOGGER.info(
|
|
798
|
+
f"\n{prefix} starting export with tensorflow {tf.__version__}..."
|
|
799
|
+
)
|
|
800
|
+
f = self.file.with_suffix(".pb")
|
|
801
|
+
|
|
802
|
+
m = tf.function(lambda x: keras_model(x)) # full model
|
|
803
|
+
m = m.get_concrete_function(
|
|
804
|
+
tf.TensorSpec(
|
|
805
|
+
keras_model.inputs[0].shape, keras_model.inputs[0].dtype
|
|
806
|
+
)
|
|
807
|
+
)
|
|
808
|
+
frozen_func = convert_variables_to_constants_v2(m)
|
|
809
|
+
frozen_func.graph.as_graph_def()
|
|
810
|
+
tf.io.write_graph(
|
|
811
|
+
graph_or_graph_def=frozen_func.graph,
|
|
812
|
+
logdir=str(f.parent),
|
|
813
|
+
name=f.name,
|
|
814
|
+
as_text=False,
|
|
815
|
+
)
|
|
816
|
+
return f, None
|
|
817
|
+
|
|
818
|
+
@try_export
|
|
819
|
+
def export_tflite(
|
|
820
|
+
self,
|
|
821
|
+
keras_model,
|
|
822
|
+
nms,
|
|
823
|
+
agnostic_nms,
|
|
824
|
+
prefix=colorstr("TensorFlow Lite:"),
|
|
825
|
+
):
|
|
826
|
+
"""YOLOv8 TensorFlow Lite export."""
|
|
827
|
+
import tensorflow as tf # noqa
|
|
828
|
+
|
|
829
|
+
LOGGER.info(
|
|
830
|
+
f"\n{prefix} starting export with tensorflow {tf.__version__}..."
|
|
831
|
+
)
|
|
832
|
+
saved_model = Path(
|
|
833
|
+
str(self.file).replace(self.file.suffix, "_saved_model")
|
|
834
|
+
)
|
|
835
|
+
if self.args.int8:
|
|
836
|
+
f = saved_model / f"{self.file.stem}_int8.tflite" # fp32 in/out
|
|
837
|
+
elif self.args.half:
|
|
838
|
+
f = saved_model / f"{self.file.stem}_float16.tflite" # fp32 in/out
|
|
839
|
+
else:
|
|
840
|
+
f = saved_model / f"{self.file.stem}_float32.tflite"
|
|
841
|
+
return str(f), None
|
|
842
|
+
|
|
843
|
+
@try_export
|
|
844
|
+
def export_edgetpu(self, tflite_model="", prefix=colorstr("Edge TPU:")):
|
|
845
|
+
"""YOLOv8 Edge TPU export https://coral.ai/docs/edgetpu/models-intro/."""
|
|
846
|
+
LOGGER.warning(
|
|
847
|
+
f"{prefix} WARNING ⚠️ Edge TPU known bug https://github.com/ultralytics/ultralytics/issues/1185"
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
cmd = "edgetpu_compiler --version"
|
|
851
|
+
help_url = "https://coral.ai/docs/edgetpu/compiler/"
|
|
852
|
+
assert LINUX, f"export only supported on Linux. See {help_url}"
|
|
853
|
+
if (
|
|
854
|
+
subprocess.run(
|
|
855
|
+
cmd,
|
|
856
|
+
stdout=subprocess.DEVNULL,
|
|
857
|
+
stderr=subprocess.DEVNULL,
|
|
858
|
+
shell=True,
|
|
859
|
+
).returncode
|
|
860
|
+
!= 0
|
|
861
|
+
):
|
|
862
|
+
LOGGER.info(
|
|
863
|
+
f"\n{prefix} export requires Edge TPU compiler. Attempting install from {help_url}"
|
|
864
|
+
)
|
|
865
|
+
sudo = (
|
|
866
|
+
subprocess.run(
|
|
867
|
+
"sudo --version >/dev/null", shell=True
|
|
868
|
+
).returncode
|
|
869
|
+
== 0
|
|
870
|
+
) # sudo installed on system
|
|
871
|
+
for c in (
|
|
872
|
+
"curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -",
|
|
873
|
+
'echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | sudo tee /etc/apt/sources.list.d/coral-edgetpu.list',
|
|
874
|
+
"sudo apt-get update",
|
|
875
|
+
"sudo apt-get install edgetpu-compiler",
|
|
876
|
+
):
|
|
877
|
+
subprocess.run(
|
|
878
|
+
c if sudo else c.replace("sudo ", ""),
|
|
879
|
+
shell=True,
|
|
880
|
+
check=True,
|
|
881
|
+
)
|
|
882
|
+
ver = (
|
|
883
|
+
subprocess.run(cmd, shell=True, capture_output=True, check=True)
|
|
884
|
+
.stdout.decode()
|
|
885
|
+
.split()[-1]
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
LOGGER.info(
|
|
889
|
+
f"\n{prefix} starting export with Edge TPU compiler {ver}..."
|
|
890
|
+
)
|
|
891
|
+
f = str(tflite_model).replace(
|
|
892
|
+
".tflite", "_edgetpu.tflite"
|
|
893
|
+
) # Edge TPU model
|
|
894
|
+
|
|
895
|
+
cmd = f"edgetpu_compiler -s -d -k 10 --out_dir {Path(f).parent} {tflite_model}"
|
|
896
|
+
LOGGER.info(f"{prefix} running '{cmd}'")
|
|
897
|
+
subprocess.run(cmd.split(), check=True)
|
|
898
|
+
self._add_tflite_metadata(f)
|
|
899
|
+
return f, None
|
|
900
|
+
|
|
901
|
+
@try_export
|
|
902
|
+
def export_tfjs(self, prefix=colorstr("TensorFlow.js:")):
|
|
903
|
+
"""YOLOv8 TensorFlow.js export."""
|
|
904
|
+
check_requirements("tensorflowjs")
|
|
905
|
+
import tensorflow as tf
|
|
906
|
+
import tensorflowjs as tfjs # noqa
|
|
907
|
+
|
|
908
|
+
LOGGER.info(
|
|
909
|
+
f"\n{prefix} starting export with tensorflowjs {tfjs.__version__}..."
|
|
910
|
+
)
|
|
911
|
+
f = str(self.file).replace(self.file.suffix, "_web_model") # js dir
|
|
912
|
+
f_pb = self.file.with_suffix(".pb") # *.pb path
|
|
913
|
+
|
|
914
|
+
gd = tf.Graph().as_graph_def() # TF GraphDef
|
|
915
|
+
with open(f_pb, "rb") as file:
|
|
916
|
+
gd.ParseFromString(file.read())
|
|
917
|
+
outputs = ",".join(gd_outputs(gd))
|
|
918
|
+
LOGGER.info(f"\n{prefix} output node names: {outputs}")
|
|
919
|
+
|
|
920
|
+
cmd = f"tensorflowjs_converter --input_format=tf_frozen_model --output_node_names={outputs} {f_pb} {f}"
|
|
921
|
+
subprocess.run(cmd.split(), check=True)
|
|
922
|
+
|
|
923
|
+
# f_json = Path(f) / 'model.json' # *.json path
|
|
924
|
+
# with open(f_json, 'w') as j: # sort JSON Identity_* in ascending order
|
|
925
|
+
# subst = re.sub(
|
|
926
|
+
# r'{"outputs": {"Identity.?.?": {"name": "Identity.?.?"}, '
|
|
927
|
+
# r'"Identity.?.?": {"name": "Identity.?.?"}, '
|
|
928
|
+
# r'"Identity.?.?": {"name": "Identity.?.?"}, '
|
|
929
|
+
# r'"Identity.?.?": {"name": "Identity.?.?"}}}',
|
|
930
|
+
# r'{"outputs": {"Identity": {"name": "Identity"}, '
|
|
931
|
+
# r'"Identity_1": {"name": "Identity_1"}, '
|
|
932
|
+
# r'"Identity_2": {"name": "Identity_2"}, '
|
|
933
|
+
# r'"Identity_3": {"name": "Identity_3"}}}',
|
|
934
|
+
# f_json.read_text(),
|
|
935
|
+
# )
|
|
936
|
+
# j.write(subst)
|
|
937
|
+
yaml_save(
|
|
938
|
+
Path(f) / "metadata.yaml", self.metadata
|
|
939
|
+
) # add metadata.yaml
|
|
940
|
+
return f, None
|
|
941
|
+
|
|
942
|
+
def _add_tflite_metadata(self, file):
|
|
943
|
+
"""Add metadata to *.tflite models per https://www.tensorflow.org/lite/models/convert/metadata."""
|
|
944
|
+
from tflite_support import flatbuffers # noqa
|
|
945
|
+
from tflite_support import metadata as _metadata # noqa
|
|
946
|
+
from tflite_support import (
|
|
947
|
+
metadata_schema_py_generated as _metadata_fb,
|
|
948
|
+
) # noqa
|
|
949
|
+
|
|
950
|
+
# Create model info
|
|
951
|
+
model_meta = _metadata_fb.ModelMetadataT()
|
|
952
|
+
model_meta.name = self.metadata["description"]
|
|
953
|
+
model_meta.version = self.metadata["version"]
|
|
954
|
+
model_meta.author = self.metadata["author"]
|
|
955
|
+
model_meta.license = self.metadata["license"]
|
|
956
|
+
|
|
957
|
+
# Label file
|
|
958
|
+
tmp_file = Path(file).parent / "temp_meta.txt"
|
|
959
|
+
with open(tmp_file, "w") as f:
|
|
960
|
+
f.write(str(self.metadata))
|
|
961
|
+
|
|
962
|
+
label_file = _metadata_fb.AssociatedFileT()
|
|
963
|
+
label_file.name = tmp_file.name
|
|
964
|
+
label_file.type = _metadata_fb.AssociatedFileType.TENSOR_AXIS_LABELS
|
|
965
|
+
|
|
966
|
+
# Create input info
|
|
967
|
+
input_meta = _metadata_fb.TensorMetadataT()
|
|
968
|
+
input_meta.name = "image"
|
|
969
|
+
input_meta.description = "Input image to be detected."
|
|
970
|
+
input_meta.content = _metadata_fb.ContentT()
|
|
971
|
+
input_meta.content.contentProperties = _metadata_fb.ImagePropertiesT()
|
|
972
|
+
input_meta.content.contentProperties.colorSpace = (
|
|
973
|
+
_metadata_fb.ColorSpaceType.RGB
|
|
974
|
+
)
|
|
975
|
+
input_meta.content.contentPropertiesType = (
|
|
976
|
+
_metadata_fb.ContentProperties.ImageProperties
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
# Create output info
|
|
980
|
+
output1 = _metadata_fb.TensorMetadataT()
|
|
981
|
+
output1.name = "output"
|
|
982
|
+
output1.description = "Coordinates of detected objects, class labels, and confidence score"
|
|
983
|
+
output1.associatedFiles = [label_file]
|
|
984
|
+
if self.model.task == "segment":
|
|
985
|
+
output2 = _metadata_fb.TensorMetadataT()
|
|
986
|
+
output2.name = "output"
|
|
987
|
+
output2.description = "Mask protos"
|
|
988
|
+
output2.associatedFiles = [label_file]
|
|
989
|
+
|
|
990
|
+
# Create subgraph info
|
|
991
|
+
subgraph = _metadata_fb.SubGraphMetadataT()
|
|
992
|
+
subgraph.inputTensorMetadata = [input_meta]
|
|
993
|
+
subgraph.outputTensorMetadata = (
|
|
994
|
+
[output1, output2] if self.model.task == "segment" else [output1]
|
|
995
|
+
)
|
|
996
|
+
model_meta.subgraphMetadata = [subgraph]
|
|
997
|
+
|
|
998
|
+
b = flatbuffers.Builder(0)
|
|
999
|
+
b.Finish(
|
|
1000
|
+
model_meta.Pack(b),
|
|
1001
|
+
_metadata.MetadataPopulator.METADATA_FILE_IDENTIFIER,
|
|
1002
|
+
)
|
|
1003
|
+
metadata_buf = b.Output()
|
|
1004
|
+
|
|
1005
|
+
populator = _metadata.MetadataPopulator.with_model_file(str(file))
|
|
1006
|
+
populator.load_metadata_buffer(metadata_buf)
|
|
1007
|
+
populator.load_associated_files([str(tmp_file)])
|
|
1008
|
+
populator.populate()
|
|
1009
|
+
tmp_file.unlink()
|
|
1010
|
+
|
|
1011
|
+
def _pipeline_coreml(self, model, prefix=colorstr("CoreML Pipeline:")):
|
|
1012
|
+
"""YOLOv8 CoreML pipeline."""
|
|
1013
|
+
import coremltools as ct # noqa
|
|
1014
|
+
|
|
1015
|
+
LOGGER.info(
|
|
1016
|
+
f"{prefix} starting pipeline with coremltools {ct.__version__}..."
|
|
1017
|
+
)
|
|
1018
|
+
batch_size, ch, h, w = list(self.im.shape) # BCHW
|
|
1019
|
+
|
|
1020
|
+
# Output shapes
|
|
1021
|
+
spec = model.get_spec()
|
|
1022
|
+
out0, out1 = iter(spec.description.output)
|
|
1023
|
+
if MACOS:
|
|
1024
|
+
from PIL import Image
|
|
1025
|
+
|
|
1026
|
+
img = Image.new("RGB", (w, h)) # img(192 width, 320 height)
|
|
1027
|
+
# img = torch.zeros((*opt.img_size, 3)).numpy() # img size(320,192,3) iDetection
|
|
1028
|
+
out = model.predict({"image": img})
|
|
1029
|
+
out0_shape = out[out0.name].shape
|
|
1030
|
+
out1_shape = out[out1.name].shape
|
|
1031
|
+
else: # linux and windows can not run model.predict(), get sizes from pytorch output y
|
|
1032
|
+
out0_shape = (
|
|
1033
|
+
self.output_shape[2],
|
|
1034
|
+
self.output_shape[1] - 4,
|
|
1035
|
+
) # (3780, 80)
|
|
1036
|
+
out1_shape = self.output_shape[2], 4 # (3780, 4)
|
|
1037
|
+
|
|
1038
|
+
# Checks
|
|
1039
|
+
names = self.metadata["names"]
|
|
1040
|
+
nx, ny = (
|
|
1041
|
+
spec.description.input[0].type.imageType.width,
|
|
1042
|
+
spec.description.input[0].type.imageType.height,
|
|
1043
|
+
)
|
|
1044
|
+
na, nc = out0_shape
|
|
1045
|
+
# na, nc = out0.type.multiArrayType.shape # number anchors, classes
|
|
1046
|
+
assert (
|
|
1047
|
+
len(names) == nc
|
|
1048
|
+
), f"{len(names)} names found for nc={nc}" # check
|
|
1049
|
+
|
|
1050
|
+
# Define output shapes (missing)
|
|
1051
|
+
out0.type.multiArrayType.shape[:] = out0_shape # (3780, 80)
|
|
1052
|
+
out1.type.multiArrayType.shape[:] = out1_shape # (3780, 4)
|
|
1053
|
+
# spec.neuralNetwork.preprocessing[0].featureName = '0'
|
|
1054
|
+
|
|
1055
|
+
# Flexible input shapes
|
|
1056
|
+
# from coremltools.models.neural_network import flexible_shape_utils
|
|
1057
|
+
# s = [] # shapes
|
|
1058
|
+
# s.append(flexible_shape_utils.NeuralNetworkImageSize(320, 192))
|
|
1059
|
+
# s.append(flexible_shape_utils.NeuralNetworkImageSize(640, 384)) # (height, width)
|
|
1060
|
+
# flexible_shape_utils.add_enumerated_image_sizes(spec, feature_name='image', sizes=s)
|
|
1061
|
+
# r = flexible_shape_utils.NeuralNetworkImageSizeRange() # shape ranges
|
|
1062
|
+
# r.add_height_range((192, 640))
|
|
1063
|
+
# r.add_width_range((192, 640))
|
|
1064
|
+
# flexible_shape_utils.update_image_size_range(spec, feature_name='image', size_range=r)
|
|
1065
|
+
|
|
1066
|
+
# Print
|
|
1067
|
+
# print(spec.description)
|
|
1068
|
+
|
|
1069
|
+
# Model from spec
|
|
1070
|
+
model = ct.models.MLModel(spec)
|
|
1071
|
+
|
|
1072
|
+
# 3. Create NMS protobuf
|
|
1073
|
+
nms_spec = ct.proto.Model_pb2.Model()
|
|
1074
|
+
nms_spec.specificationVersion = 5
|
|
1075
|
+
for i in range(2):
|
|
1076
|
+
decoder_output = model._spec.description.output[
|
|
1077
|
+
i
|
|
1078
|
+
].SerializeToString()
|
|
1079
|
+
nms_spec.description.input.add()
|
|
1080
|
+
nms_spec.description.input[i].ParseFromString(decoder_output)
|
|
1081
|
+
nms_spec.description.output.add()
|
|
1082
|
+
nms_spec.description.output[i].ParseFromString(decoder_output)
|
|
1083
|
+
|
|
1084
|
+
nms_spec.description.output[0].name = "confidence"
|
|
1085
|
+
nms_spec.description.output[1].name = "coordinates"
|
|
1086
|
+
|
|
1087
|
+
output_sizes = [nc, 4]
|
|
1088
|
+
for i in range(2):
|
|
1089
|
+
ma_type = nms_spec.description.output[i].type.multiArrayType
|
|
1090
|
+
ma_type.shapeRange.sizeRanges.add()
|
|
1091
|
+
ma_type.shapeRange.sizeRanges[0].lowerBound = 0
|
|
1092
|
+
ma_type.shapeRange.sizeRanges[0].upperBound = -1
|
|
1093
|
+
ma_type.shapeRange.sizeRanges.add()
|
|
1094
|
+
ma_type.shapeRange.sizeRanges[1].lowerBound = output_sizes[i]
|
|
1095
|
+
ma_type.shapeRange.sizeRanges[1].upperBound = output_sizes[i]
|
|
1096
|
+
del ma_type.shape[:]
|
|
1097
|
+
|
|
1098
|
+
nms = nms_spec.nonMaximumSuppression
|
|
1099
|
+
nms.confidenceInputFeatureName = out0.name # 1x507x80
|
|
1100
|
+
nms.coordinatesInputFeatureName = out1.name # 1x507x4
|
|
1101
|
+
nms.confidenceOutputFeatureName = "confidence"
|
|
1102
|
+
nms.coordinatesOutputFeatureName = "coordinates"
|
|
1103
|
+
nms.iouThresholdInputFeatureName = "iouThreshold"
|
|
1104
|
+
nms.confidenceThresholdInputFeatureName = "confidenceThreshold"
|
|
1105
|
+
nms.iouThreshold = 0.45
|
|
1106
|
+
nms.confidenceThreshold = 0.25
|
|
1107
|
+
nms.pickTop.perClass = True
|
|
1108
|
+
nms.stringClassLabels.vector.extend(names.values())
|
|
1109
|
+
nms_model = ct.models.MLModel(nms_spec)
|
|
1110
|
+
|
|
1111
|
+
# 4. Pipeline models together
|
|
1112
|
+
pipeline = ct.models.pipeline.Pipeline(
|
|
1113
|
+
input_features=[
|
|
1114
|
+
("image", ct.models.datatypes.Array(3, ny, nx)),
|
|
1115
|
+
("iouThreshold", ct.models.datatypes.Double()),
|
|
1116
|
+
("confidenceThreshold", ct.models.datatypes.Double()),
|
|
1117
|
+
],
|
|
1118
|
+
output_features=["confidence", "coordinates"],
|
|
1119
|
+
)
|
|
1120
|
+
pipeline.add_model(model)
|
|
1121
|
+
pipeline.add_model(nms_model)
|
|
1122
|
+
|
|
1123
|
+
# Correct datatypes
|
|
1124
|
+
pipeline.spec.description.input[0].ParseFromString(
|
|
1125
|
+
model._spec.description.input[0].SerializeToString()
|
|
1126
|
+
)
|
|
1127
|
+
pipeline.spec.description.output[0].ParseFromString(
|
|
1128
|
+
nms_model._spec.description.output[0].SerializeToString()
|
|
1129
|
+
)
|
|
1130
|
+
pipeline.spec.description.output[1].ParseFromString(
|
|
1131
|
+
nms_model._spec.description.output[1].SerializeToString()
|
|
1132
|
+
)
|
|
1133
|
+
|
|
1134
|
+
# Update metadata
|
|
1135
|
+
pipeline.spec.specificationVersion = 5
|
|
1136
|
+
pipeline.spec.description.metadata.userDefined.update(
|
|
1137
|
+
{
|
|
1138
|
+
"IoU threshold": str(nms.iouThreshold),
|
|
1139
|
+
"Confidence threshold": str(nms.confidenceThreshold),
|
|
1140
|
+
}
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
# Save the model
|
|
1144
|
+
model = ct.models.MLModel(pipeline.spec)
|
|
1145
|
+
model.input_description["image"] = "Input image"
|
|
1146
|
+
model.input_description["iouThreshold"] = (
|
|
1147
|
+
f"(optional) IOU threshold override (default: {nms.iouThreshold})"
|
|
1148
|
+
)
|
|
1149
|
+
model.input_description["confidenceThreshold"] = (
|
|
1150
|
+
f"(optional) Confidence threshold override (default: {nms.confidenceThreshold})"
|
|
1151
|
+
)
|
|
1152
|
+
model.output_description["confidence"] = (
|
|
1153
|
+
'Boxes × Class confidence (see user-defined metadata "classes")'
|
|
1154
|
+
)
|
|
1155
|
+
model.output_description["coordinates"] = (
|
|
1156
|
+
"Boxes × [x, y, width, height] (relative to image size)"
|
|
1157
|
+
)
|
|
1158
|
+
LOGGER.info(f"{prefix} pipeline success")
|
|
1159
|
+
return model
|
|
1160
|
+
|
|
1161
|
+
def add_callback(self, event: str, callback):
|
|
1162
|
+
"""
|
|
1163
|
+
Appends the given callback.
|
|
1164
|
+
"""
|
|
1165
|
+
self.callbacks[event].append(callback)
|
|
1166
|
+
|
|
1167
|
+
def run_callbacks(self, event: str):
|
|
1168
|
+
"""Execute all callbacks for a given event."""
|
|
1169
|
+
for callback in self.callbacks.get(event, []):
|
|
1170
|
+
callback(self)
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
class iOSDetectModel(torch.nn.Module):
|
|
1174
|
+
"""Wrap an Ultralytics YOLO model for iOS export."""
|
|
1175
|
+
|
|
1176
|
+
def __init__(self, model, im):
|
|
1177
|
+
"""Initialize the iOSDetectModel class with a YOLO model and example image."""
|
|
1178
|
+
super().__init__()
|
|
1179
|
+
b, c, h, w = im.shape # batch, channel, height, width
|
|
1180
|
+
self.model = model
|
|
1181
|
+
self.nc = len(model.names) # number of classes
|
|
1182
|
+
if w == h:
|
|
1183
|
+
self.normalize = 1.0 / w # scalar
|
|
1184
|
+
else:
|
|
1185
|
+
self.normalize = torch.tensor(
|
|
1186
|
+
[1.0 / w, 1.0 / h, 1.0 / w, 1.0 / h]
|
|
1187
|
+
) # broadcast (slower, smaller)
|
|
1188
|
+
|
|
1189
|
+
def forward(self, x):
|
|
1190
|
+
"""Normalize predictions of object detection model with input size-dependent factors."""
|
|
1191
|
+
xywh, cls = self.model(x)[0].transpose(0, 1).split((4, self.nc), 1)
|
|
1192
|
+
return (
|
|
1193
|
+
cls,
|
|
1194
|
+
xywh * self.normalize,
|
|
1195
|
+
) # confidence (3780, 80), coordinates (3780, 4)
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
def export(cfg=DEFAULT_CFG):
|
|
1199
|
+
"""Export a YOLOv model to a specific format."""
|
|
1200
|
+
cfg.model = cfg.model or "yolov8n.yaml"
|
|
1201
|
+
cfg.format = cfg.format or "torchscript"
|
|
1202
|
+
|
|
1203
|
+
from ultralytics import YOLO
|
|
1204
|
+
|
|
1205
|
+
model = YOLO(cfg.model)
|
|
1206
|
+
model.export(**vars(cfg))
|
|
1207
|
+
|
|
1208
|
+
|
|
1209
|
+
if __name__ == "__main__":
|
|
1210
|
+
"""
|
|
1211
|
+
CLI:
|
|
1212
|
+
yolo mode=export model=yolov8n.yaml format=onnx
|
|
1213
|
+
"""
|
|
1214
|
+
export()
|