export.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. # YOLOv5 🚀 by Ultralytics, GPL-3.0 license
  2. """
  3. Export a YOLOv5 PyTorch model to other formats. TensorFlow exports authored by https://github.com/zldrobit
  4. Format | `export.py --include` | Model
  5. --- | --- | ---
  6. PyTorch | - | yolov5s.pt
  7. TorchScript | `torchscript` | yolov5s.torchscript
  8. ONNX | `onnx` | yolov5s.onnx
  9. OpenVINO | `openvino` | yolov5s_openvino_model/
  10. TensorRT | `engine` | yolov5s.engine
  11. CoreML | `coreml` | yolov5s.mlmodel
  12. TensorFlow SavedModel | `saved_model` | yolov5s_saved_model/
  13. TensorFlow GraphDef | `pb` | yolov5s.pb
  14. TensorFlow Lite | `tflite` | yolov5s.tflite
  15. TensorFlow Edge TPU | `edgetpu` | yolov5s_edgetpu.tflite
  16. TensorFlow.js | `tfjs` | yolov5s_web_model/
  17. PaddlePaddle | `paddle` | yolov5s_paddle_model/
  18. Requirements:
  19. $ pip install -r requirements.txt coremltools onnx onnx-simplifier onnxruntime openvino-dev tensorflow-cpu # CPU
  20. $ pip install -r requirements.txt coremltools onnx onnx-simplifier onnxruntime-gpu openvino-dev tensorflow # GPU
  21. Usage:
  22. $ python export.py --weights yolov5s.pt --include torchscript onnx openvino engine coreml tflite ...
  23. Inference:
  24. $ python detect.py --weights yolov5s.pt # PyTorch
  25. yolov5s.torchscript # TorchScript
  26. yolov5s.onnx # ONNX Runtime or OpenCV DNN with --dnn
  27. yolov5s_openvino_model # OpenVINO
  28. yolov5s.engine # TensorRT
  29. yolov5s.mlmodel # CoreML (macOS-only)
  30. yolov5s_saved_model # TensorFlow SavedModel
  31. yolov5s.pb # TensorFlow GraphDef
  32. yolov5s.tflite # TensorFlow Lite
  33. yolov5s_edgetpu.tflite # TensorFlow Edge TPU
  34. yolov5s_paddle_model # PaddlePaddle
  35. TensorFlow.js:
  36. $ cd .. && git clone https://github.com/zldrobit/tfjs-yolov5-example.git && cd tfjs-yolov5-example
  37. $ npm install
  38. $ ln -s ../../yolov5/yolov5s_web_model public/yolov5s_web_model
  39. $ npm start
  40. """
  41. import argparse
  42. import contextlib
  43. import json
  44. import os
  45. import platform
  46. import re
  47. import subprocess
  48. import sys
  49. import time
  50. import warnings
  51. from pathlib import Path
  52. import pandas as pd
  53. import torch
  54. from torch.utils.mobile_optimizer import optimize_for_mobile
  55. FILE = Path(__file__).resolve()
  56. ROOT = FILE.parents[0] # YOLOv5 root directory
  57. if str(ROOT) not in sys.path:
  58. sys.path.append(str(ROOT)) # add ROOT to PATH
  59. if platform.system() != 'Windows':
  60. ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative
  61. from models.experimental import attempt_load
  62. from models.yolo import ClassificationModel, Detect, DetectionModel, SegmentationModel
  63. from utils.dataloaders import LoadImages
  64. from utils.general import (LOGGER, Profile, check_dataset, check_img_size, check_requirements, check_version,
  65. check_yaml, colorstr, file_size, get_default_args, print_args, url2file, yaml_save)
  66. from utils.torch_utils import select_device, smart_inference_mode
  67. MACOS = platform.system() == 'Darwin' # macOS environment
  68. def export_formats():
  69. # YOLOv5 export formats
  70. x = [
  71. ['PyTorch', '-', '.pt', True, True],
  72. ['TorchScript', 'torchscript', '.torchscript', True, True],
  73. ['ONNX', 'onnx', '.onnx', True, True],
  74. ['OpenVINO', 'openvino', '_openvino_model', True, False],
  75. ['TensorRT', 'engine', '.engine', False, True],
  76. ['CoreML', 'coreml', '.mlmodel', True, False],
  77. ['TensorFlow SavedModel', 'saved_model', '_saved_model', True, True],
  78. ['TensorFlow GraphDef', 'pb', '.pb', True, True],
  79. ['TensorFlow Lite', 'tflite', '.tflite', True, False],
  80. ['TensorFlow Edge TPU', 'edgetpu', '_edgetpu.tflite', False, False],
  81. ['TensorFlow.js', 'tfjs', '_web_model', False, False],
  82. ['PaddlePaddle', 'paddle', '_paddle_model', True, True],]
  83. return pd.DataFrame(x, columns=['Format', 'Argument', 'Suffix', 'CPU', 'GPU'])
  84. def try_export(inner_func):
  85. # YOLOv5 export decorator, i..e @try_export
  86. inner_args = get_default_args(inner_func)
  87. def outer_func(*args, **kwargs):
  88. prefix = inner_args['prefix']
  89. try:
  90. with Profile() as dt:
  91. f, model = inner_func(*args, **kwargs)
  92. LOGGER.info(f'{prefix} export success ✅ {dt.t:.1f}s, saved as {f} ({file_size(f):.1f} MB)')
  93. return f, model
  94. except Exception as e:
  95. LOGGER.info(f'{prefix} export failure ❌ {dt.t:.1f}s: {e}')
  96. return None, None
  97. return outer_func
  98. @try_export
  99. def export_torchscript(model, im, file, optimize, prefix=colorstr('TorchScript:')):
  100. # YOLOv5 TorchScript model export
  101. LOGGER.info(f'\n{prefix} starting export with torch {torch.__version__}...')
  102. f = file.with_suffix('.torchscript')
  103. ts = torch.jit.trace(model, im, strict=False)
  104. d = {"shape": im.shape, "stride": int(max(model.stride)), "names": model.names}
  105. extra_files = {'config.txt': json.dumps(d)} # torch._C.ExtraFilesMap()
  106. if optimize: # https://pytorch.org/tutorials/recipes/mobile_interpreter.html
  107. optimize_for_mobile(ts)._save_for_lite_interpreter(str(f), _extra_files=extra_files)
  108. else:
  109. ts.save(str(f), _extra_files=extra_files)
  110. return f, None
  111. @try_export
  112. def export_onnx(model, im, file, opset, dynamic, simplify, prefix=colorstr('ONNX:')):
  113. # YOLOv5 ONNX export
  114. check_requirements('onnx')
  115. import onnx
  116. LOGGER.info(f'\n{prefix} starting export with onnx {onnx.__version__}...')
  117. f = file.with_suffix('.onnx')
  118. output_names = ['output0', 'output1'] if isinstance(model, SegmentationModel) else ['output0']
  119. if dynamic:
  120. dynamic = {'images': {0: 'batch', 2: 'height', 3: 'width'}} # shape(1,3,640,640)
  121. if isinstance(model, SegmentationModel):
  122. dynamic['output0'] = {0: 'batch', 1: 'anchors'} # shape(1,25200,85)
  123. dynamic['output1'] = {0: 'batch', 2: 'mask_height', 3: 'mask_width'} # shape(1,32,160,160)
  124. elif isinstance(model, DetectionModel):
  125. dynamic['output0'] = {0: 'batch', 1: 'anchors'} # shape(1,25200,85)
  126. torch.onnx.export(
  127. model.cpu() if dynamic else model, # --dynamic only compatible with cpu
  128. im.cpu() if dynamic else im,
  129. f,
  130. verbose=False,
  131. opset_version=opset,
  132. do_constant_folding=True, # WARNING: DNN inference with torch>=1.12 may require do_constant_folding=False
  133. input_names=['images'],
  134. output_names=output_names,
  135. dynamic_axes=dynamic or None)
  136. # Checks
  137. model_onnx = onnx.load(f) # load onnx model
  138. onnx.checker.check_model(model_onnx) # check onnx model
  139. # Metadata
  140. d = {'stride': int(max(model.stride)), 'names': model.names}
  141. for k, v in d.items():
  142. meta = model_onnx.metadata_props.add()
  143. meta.key, meta.value = k, str(v)
  144. onnx.save(model_onnx, f)
  145. # Simplify
  146. if simplify:
  147. try:
  148. cuda = torch.cuda.is_available()
  149. check_requirements(('onnxruntime-gpu' if cuda else 'onnxruntime', 'onnx-simplifier>=0.4.1'))
  150. import onnxsim
  151. LOGGER.info(f'{prefix} simplifying with onnx-simplifier {onnxsim.__version__}...')
  152. model_onnx, check = onnxsim.simplify(model_onnx)
  153. assert check, 'assert check failed'
  154. onnx.save(model_onnx, f)
  155. except Exception as e:
  156. LOGGER.info(f'{prefix} simplifier failure: {e}')
  157. return f, model_onnx
  158. @try_export
  159. def export_openvino(file, metadata, half, prefix=colorstr('OpenVINO:')):
  160. # YOLOv5 OpenVINO export
  161. check_requirements('openvino-dev') # requires openvino-dev: https://pypi.org/project/openvino-dev/
  162. import openvino.inference_engine as ie
  163. LOGGER.info(f'\n{prefix} starting export with openvino {ie.__version__}...')
  164. f = str(file).replace('.pt', f'_openvino_model{os.sep}')
  165. cmd = f"mo --input_model {file.with_suffix('.onnx')} --output_dir {f} --data_type {'FP16' if half else 'FP32'}"
  166. subprocess.run(cmd.split(), check=True, env=os.environ) # export
  167. yaml_save(Path(f) / file.with_suffix('.yaml').name, metadata) # add metadata.yaml
  168. return f, None
  169. @try_export
  170. def export_paddle(model, im, file, metadata, prefix=colorstr('PaddlePaddle:')):
  171. # YOLOv5 Paddle export
  172. check_requirements(('paddlepaddle', 'x2paddle'))
  173. import x2paddle
  174. from x2paddle.convert import pytorch2paddle
  175. LOGGER.info(f'\n{prefix} starting export with X2Paddle {x2paddle.__version__}...')
  176. f = str(file).replace('.pt', f'_paddle_model{os.sep}')
  177. pytorch2paddle(module=model, save_dir=f, jit_type='trace', input_examples=[im]) # export
  178. yaml_save(Path(f) / file.with_suffix('.yaml').name, metadata) # add metadata.yaml
  179. return f, None
  180. @try_export
  181. def export_coreml(model, im, file, int8, half, prefix=colorstr('CoreML:')):
  182. # YOLOv5 CoreML export
  183. check_requirements('coremltools')
  184. import coremltools as ct
  185. LOGGER.info(f'\n{prefix} starting export with coremltools {ct.__version__}...')
  186. f = file.with_suffix('.mlmodel')
  187. ts = torch.jit.trace(model, im, strict=False) # TorchScript model
  188. ct_model = ct.convert(ts, inputs=[ct.ImageType('image', shape=im.shape, scale=1 / 255, bias=[0, 0, 0])])
  189. bits, mode = (8, 'kmeans_lut') if int8 else (16, 'linear') if half else (32, None)
  190. if bits < 32:
  191. if MACOS: # quantization only supported on macOS
  192. with warnings.catch_warnings():
  193. warnings.filterwarnings("ignore", category=DeprecationWarning) # suppress numpy==1.20 float warning
  194. ct_model = ct.models.neural_network.quantization_utils.quantize_weights(ct_model, bits, mode)
  195. else:
  196. print(f'{prefix} quantization only supported on macOS, skipping...')
  197. ct_model.save(f)
  198. return f, ct_model
  199. @try_export
  200. def export_engine(model, im, file, half, dynamic, simplify, workspace=4, verbose=False, prefix=colorstr('TensorRT:')):
  201. # YOLOv5 TensorRT export https://developer.nvidia.com/tensorrt
  202. assert im.device.type != 'cpu', 'export running on CPU but must be on GPU, i.e. `python export.py --device 0`'
  203. try:
  204. import tensorrt as trt
  205. except Exception:
  206. if platform.system() == 'Linux':
  207. check_requirements('nvidia-tensorrt', cmds='-U --index-url https://pypi.ngc.nvidia.com')
  208. import tensorrt as trt
  209. if trt.__version__[0] == '7': # TensorRT 7 handling https://github.com/ultralytics/yolov5/issues/6012
  210. grid = model.model[-1].anchor_grid
  211. model.model[-1].anchor_grid = [a[..., :1, :1, :] for a in grid]
  212. export_onnx(model, im, file, 12, dynamic, simplify) # opset 12
  213. model.model[-1].anchor_grid = grid
  214. else: # TensorRT >= 8
  215. check_version(trt.__version__, '8.0.0', hard=True) # require tensorrt>=8.0.0
  216. export_onnx(model, im, file, 12, dynamic, simplify) # opset 12
  217. onnx = file.with_suffix('.onnx')
  218. LOGGER.info(f'\n{prefix} starting export with TensorRT {trt.__version__}...')
  219. assert onnx.exists(), f'failed to export ONNX file: {onnx}'
  220. f = file.with_suffix('.engine') # TensorRT engine file
  221. logger = trt.Logger(trt.Logger.INFO)
  222. if verbose:
  223. logger.min_severity = trt.Logger.Severity.VERBOSE
  224. builder = trt.Builder(logger)
  225. config = builder.create_builder_config()
  226. config.max_workspace_size = workspace * 1 << 30
  227. # config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, workspace << 30) # fix TRT 8.4 deprecation notice
  228. flag = (1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
  229. network = builder.create_network(flag)
  230. parser = trt.OnnxParser(network, logger)
  231. if not parser.parse_from_file(str(onnx)):
  232. raise RuntimeError(f'failed to load ONNX file: {onnx}')
  233. inputs = [network.get_input(i) for i in range(network.num_inputs)]
  234. outputs = [network.get_output(i) for i in range(network.num_outputs)]
  235. for inp in inputs:
  236. LOGGER.info(f'{prefix} input "{inp.name}" with shape{inp.shape} {inp.dtype}')
  237. for out in outputs:
  238. LOGGER.info(f'{prefix} output "{out.name}" with shape{out.shape} {out.dtype}')
  239. if dynamic:
  240. if im.shape[0] <= 1:
  241. LOGGER.warning(f"{prefix} WARNING ⚠️ --dynamic model requires maximum --batch-size argument")
  242. profile = builder.create_optimization_profile()
  243. for inp in inputs:
  244. profile.set_shape(inp.name, (1, *im.shape[1:]), (max(1, im.shape[0] // 2), *im.shape[1:]), im.shape)
  245. config.add_optimization_profile(profile)
  246. LOGGER.info(f'{prefix} building FP{16 if builder.platform_has_fast_fp16 and half else 32} engine as {f}')
  247. if builder.platform_has_fast_fp16 and half:
  248. config.set_flag(trt.BuilderFlag.FP16)
  249. with builder.build_engine(network, config) as engine, open(f, 'wb') as t:
  250. t.write(engine.serialize())
  251. return f, None
  252. @try_export
  253. def export_saved_model(model,
  254. im,
  255. file,
  256. dynamic,
  257. tf_nms=False,
  258. agnostic_nms=False,
  259. topk_per_class=100,
  260. topk_all=100,
  261. iou_thres=0.45,
  262. conf_thres=0.25,
  263. keras=False,
  264. prefix=colorstr('TensorFlow SavedModel:')):
  265. # YOLOv5 TensorFlow SavedModel export
  266. try:
  267. import tensorflow as tf
  268. except Exception:
  269. check_requirements(f"tensorflow{'' if torch.cuda.is_available() else '-macos' if MACOS else '-cpu'}")
  270. import tensorflow as tf
  271. from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2
  272. from models.tf import TFModel
  273. LOGGER.info(f'\n{prefix} starting export with tensorflow {tf.__version__}...')
  274. f = str(file).replace('.pt', '_saved_model')
  275. batch_size, ch, *imgsz = list(im.shape) # BCHW
  276. tf_model = TFModel(cfg=model.yaml, model=model, nc=model.nc, imgsz=imgsz)
  277. im = tf.zeros((batch_size, *imgsz, ch)) # BHWC order for TensorFlow
  278. _ = tf_model.predict(im, tf_nms, agnostic_nms, topk_per_class, topk_all, iou_thres, conf_thres)
  279. inputs = tf.keras.Input(shape=(*imgsz, ch), batch_size=None if dynamic else batch_size)
  280. outputs = tf_model.predict(inputs, tf_nms, agnostic_nms, topk_per_class, topk_all, iou_thres, conf_thres)
  281. keras_model = tf.keras.Model(inputs=inputs, outputs=outputs)
  282. keras_model.trainable = False
  283. keras_model.summary()
  284. if keras:
  285. keras_model.save(f, save_format='tf')
  286. else:
  287. spec = tf.TensorSpec(keras_model.inputs[0].shape, keras_model.inputs[0].dtype)
  288. m = tf.function(lambda x: keras_model(x)) # full model
  289. m = m.get_concrete_function(spec)
  290. frozen_func = convert_variables_to_constants_v2(m)
  291. tfm = tf.Module()
  292. tfm.__call__ = tf.function(lambda x: frozen_func(x)[:4] if tf_nms else frozen_func(x), [spec])
  293. tfm.__call__(im)
  294. tf.saved_model.save(tfm,
  295. f,
  296. options=tf.saved_model.SaveOptions(experimental_custom_gradients=False) if check_version(
  297. tf.__version__, '2.6') else tf.saved_model.SaveOptions())
  298. return f, keras_model
  299. @try_export
  300. def export_pb(keras_model, file, prefix=colorstr('TensorFlow GraphDef:')):
  301. # YOLOv5 TensorFlow GraphDef *.pb export https://github.com/leimao/Frozen_Graph_TensorFlow
  302. import tensorflow as tf
  303. from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2
  304. LOGGER.info(f'\n{prefix} starting export with tensorflow {tf.__version__}...')
  305. f = file.with_suffix('.pb')
  306. m = tf.function(lambda x: keras_model(x)) # full model
  307. m = m.get_concrete_function(tf.TensorSpec(keras_model.inputs[0].shape, keras_model.inputs[0].dtype))
  308. frozen_func = convert_variables_to_constants_v2(m)
  309. frozen_func.graph.as_graph_def()
  310. tf.io.write_graph(graph_or_graph_def=frozen_func.graph, logdir=str(f.parent), name=f.name, as_text=False)
  311. return f, None
  312. @try_export
  313. def export_tflite(keras_model, im, file, int8, data, nms, agnostic_nms, prefix=colorstr('TensorFlow Lite:')):
  314. # YOLOv5 TensorFlow Lite export
  315. import tensorflow as tf
  316. LOGGER.info(f'\n{prefix} starting export with tensorflow {tf.__version__}...')
  317. batch_size, ch, *imgsz = list(im.shape) # BCHW
  318. f = str(file).replace('.pt', '-fp16.tflite')
  319. converter = tf.lite.TFLiteConverter.from_keras_model(keras_model)
  320. converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS]
  321. converter.target_spec.supported_types = [tf.float16]
  322. converter.optimizations = [tf.lite.Optimize.DEFAULT]
  323. if int8:
  324. from models.tf import representative_dataset_gen
  325. dataset = LoadImages(check_dataset(check_yaml(data))['train'], img_size=imgsz, auto=False)
  326. converter.representative_dataset = lambda: representative_dataset_gen(dataset, ncalib=100)
  327. converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
  328. converter.target_spec.supported_types = []
  329. converter.inference_input_type = tf.uint8 # or tf.int8
  330. converter.inference_output_type = tf.uint8 # or tf.int8
  331. converter.experimental_new_quantizer = True
  332. f = str(file).replace('.pt', '-int8.tflite')
  333. if nms or agnostic_nms:
  334. converter.target_spec.supported_ops.append(tf.lite.OpsSet.SELECT_TF_OPS)
  335. tflite_model = converter.convert()
  336. open(f, "wb").write(tflite_model)
  337. return f, None
  338. @try_export
  339. def export_edgetpu(file, prefix=colorstr('Edge TPU:')):
  340. # YOLOv5 Edge TPU export https://coral.ai/docs/edgetpu/models-intro/
  341. cmd = 'edgetpu_compiler --version'
  342. help_url = 'https://coral.ai/docs/edgetpu/compiler/'
  343. assert platform.system() == 'Linux', f'export only supported on Linux. See {help_url}'
  344. if subprocess.run(f'{cmd} >/dev/null', shell=True).returncode != 0:
  345. LOGGER.info(f'\n{prefix} export requires Edge TPU compiler. Attempting install from {help_url}')
  346. sudo = subprocess.run('sudo --version >/dev/null', shell=True).returncode == 0 # sudo installed on system
  347. for c in (
  348. 'curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -',
  349. 'echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | sudo tee /etc/apt/sources.list.d/coral-edgetpu.list',
  350. 'sudo apt-get update', 'sudo apt-get install edgetpu-compiler'):
  351. subprocess.run(c if sudo else c.replace('sudo ', ''), shell=True, check=True)
  352. ver = subprocess.run(cmd, shell=True, capture_output=True, check=True).stdout.decode().split()[-1]
  353. LOGGER.info(f'\n{prefix} starting export with Edge TPU compiler {ver}...')
  354. f = str(file).replace('.pt', '-int8_edgetpu.tflite') # Edge TPU model
  355. f_tfl = str(file).replace('.pt', '-int8.tflite') # TFLite model
  356. cmd = f"edgetpu_compiler -s -d -k 10 --out_dir {file.parent} {f_tfl}"
  357. subprocess.run(cmd.split(), check=True)
  358. return f, None
  359. @try_export
  360. def export_tfjs(file, prefix=colorstr('TensorFlow.js:')):
  361. # YOLOv5 TensorFlow.js export
  362. check_requirements('tensorflowjs')
  363. import tensorflowjs as tfjs
  364. LOGGER.info(f'\n{prefix} starting export with tensorflowjs {tfjs.__version__}...')
  365. f = str(file).replace('.pt', '_web_model') # js dir
  366. f_pb = file.with_suffix('.pb') # *.pb path
  367. f_json = f'{f}/model.json' # *.json path
  368. cmd = f'tensorflowjs_converter --input_format=tf_frozen_model ' \
  369. f'--output_node_names=Identity,Identity_1,Identity_2,Identity_3 {f_pb} {f}'
  370. subprocess.run(cmd.split())
  371. json = Path(f_json).read_text()
  372. with open(f_json, 'w') as j: # sort JSON Identity_* in ascending order
  373. subst = re.sub(
  374. r'{"outputs": {"Identity.?.?": {"name": "Identity.?.?"}, '
  375. r'"Identity.?.?": {"name": "Identity.?.?"}, '
  376. r'"Identity.?.?": {"name": "Identity.?.?"}, '
  377. r'"Identity.?.?": {"name": "Identity.?.?"}}}', r'{"outputs": {"Identity": {"name": "Identity"}, '
  378. r'"Identity_1": {"name": "Identity_1"}, '
  379. r'"Identity_2": {"name": "Identity_2"}, '
  380. r'"Identity_3": {"name": "Identity_3"}}}', json)
  381. j.write(subst)
  382. return f, None
  383. def add_tflite_metadata(file, metadata, num_outputs):
  384. # Add metadata to *.tflite models per https://www.tensorflow.org/lite/models/convert/metadata
  385. with contextlib.suppress(ImportError):
  386. # check_requirements('tflite_support')
  387. from tflite_support import flatbuffers
  388. from tflite_support import metadata as _metadata
  389. from tflite_support import metadata_schema_py_generated as _metadata_fb
  390. tmp_file = Path('/tmp/meta.txt')
  391. with open(tmp_file, 'w') as meta_f:
  392. meta_f.write(str(metadata))
  393. model_meta = _metadata_fb.ModelMetadataT()
  394. label_file = _metadata_fb.AssociatedFileT()
  395. label_file.name = tmp_file.name
  396. model_meta.associatedFiles = [label_file]
  397. subgraph = _metadata_fb.SubGraphMetadataT()
  398. subgraph.inputTensorMetadata = [_metadata_fb.TensorMetadataT()]
  399. subgraph.outputTensorMetadata = [_metadata_fb.TensorMetadataT()] * num_outputs
  400. model_meta.subgraphMetadata = [subgraph]
  401. b = flatbuffers.Builder(0)
  402. b.Finish(model_meta.Pack(b), _metadata.MetadataPopulator.METADATA_FILE_IDENTIFIER)
  403. metadata_buf = b.Output()
  404. populator = _metadata.MetadataPopulator.with_model_file(file)
  405. populator.load_metadata_buffer(metadata_buf)
  406. populator.load_associated_files([str(tmp_file)])
  407. populator.populate()
  408. tmp_file.unlink()
  409. @smart_inference_mode()
  410. def run(
  411. data=ROOT / 'data/coco128.yaml', # 'dataset.yaml path'
  412. weights=ROOT / 'yolov5s.pt', # weights path
  413. imgsz=(640, 640), # image (height, width)
  414. batch_size=1, # batch size
  415. device='cpu', # cuda device, i.e. 0 or 0,1,2,3 or cpu
  416. include=('torchscript', 'onnx'), # include formats
  417. half=False, # FP16 half-precision export
  418. inplace=False, # set YOLOv5 Detect() inplace=True
  419. keras=False, # use Keras
  420. optimize=False, # TorchScript: optimize for mobile
  421. int8=False, # CoreML/TF INT8 quantization
  422. dynamic=False, # ONNX/TF/TensorRT: dynamic axes
  423. simplify=False, # ONNX: simplify model
  424. opset=12, # ONNX: opset version
  425. verbose=False, # TensorRT: verbose log
  426. workspace=4, # TensorRT: workspace size (GB)
  427. nms=False, # TF: add NMS to model
  428. agnostic_nms=False, # TF: add agnostic NMS to model
  429. topk_per_class=100, # TF.js NMS: topk per class to keep
  430. topk_all=100, # TF.js NMS: topk for all classes to keep
  431. iou_thres=0.45, # TF.js NMS: IoU threshold
  432. conf_thres=0.25, # TF.js NMS: confidence threshold
  433. ):
  434. t = time.time()
  435. include = [x.lower() for x in include] # to lowercase
  436. fmts = tuple(export_formats()['Argument'][1:]) # --include arguments
  437. flags = [x in include for x in fmts]
  438. assert sum(flags) == len(include), f'ERROR: Invalid --include {include}, valid --include arguments are {fmts}'
  439. jit, onnx, xml, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs, paddle = flags # export booleans
  440. file = Path(url2file(weights) if str(weights).startswith(('http:/', 'https:/')) else weights) # PyTorch weights
  441. # Load PyTorch model
  442. device = select_device(device)
  443. if half:
  444. assert device.type != 'cpu' or coreml, '--half only compatible with GPU export, i.e. use --device 0'
  445. assert not dynamic, '--half not compatible with --dynamic, i.e. use either --half or --dynamic but not both'
  446. model = attempt_load(weights, device=device, inplace=True, fuse=True) # load FP32 model
  447. # Checks
  448. imgsz *= 2 if len(imgsz) == 1 else 1 # expand
  449. if optimize:
  450. assert device.type == 'cpu', '--optimize not compatible with cuda devices, i.e. use --device cpu'
  451. # Input
  452. gs = int(max(model.stride)) # grid size (max stride)
  453. imgsz = [check_img_size(x, gs) for x in imgsz] # verify img_size are gs-multiples
  454. im = torch.zeros(batch_size, 3, *imgsz).to(device) # image size(1,3,320,192) BCHW iDetection
  455. # Update model
  456. model.eval()
  457. for k, m in model.named_modules():
  458. if isinstance(m, Detect):
  459. m.inplace = inplace
  460. m.dynamic = dynamic
  461. m.export = True
  462. for _ in range(2):
  463. y = model(im) # dry runs
  464. if half and not coreml:
  465. im, model = im.half(), model.half() # to FP16
  466. shape = tuple((y[0] if isinstance(y, tuple) else y).shape) # model output shape
  467. metadata = {'stride': int(max(model.stride)), 'names': model.names} # model metadata
  468. LOGGER.info(f"\n{colorstr('PyTorch:')} starting from {file} with output shape {shape} ({file_size(file):.1f} MB)")
  469. # Exports
  470. f = [''] * len(fmts) # exported filenames
  471. warnings.filterwarnings(action='ignore', category=torch.jit.TracerWarning) # suppress TracerWarning
  472. if jit: # TorchScript
  473. f[0], _ = export_torchscript(model, im, file, optimize)
  474. if engine: # TensorRT required before ONNX
  475. f[1], _ = export_engine(model, im, file, half, dynamic, simplify, workspace, verbose)
  476. if onnx or xml: # OpenVINO requires ONNX
  477. f[2], _ = export_onnx(model, im, file, opset, dynamic, simplify)
  478. if xml: # OpenVINO
  479. f[3], _ = export_openvino(file, metadata, half)
  480. if coreml: # CoreML
  481. f[4], _ = export_coreml(model, im, file, int8, half)
  482. if any((saved_model, pb, tflite, edgetpu, tfjs)): # TensorFlow formats
  483. assert not tflite or not tfjs, 'TFLite and TF.js models must be exported separately, please pass only one type.'
  484. assert not isinstance(model, ClassificationModel), 'ClassificationModel export to TF formats not yet supported.'
  485. f[5], s_model = export_saved_model(model.cpu(),
  486. im,
  487. file,
  488. dynamic,
  489. tf_nms=nms or agnostic_nms or tfjs,
  490. agnostic_nms=agnostic_nms or tfjs,
  491. topk_per_class=topk_per_class,
  492. topk_all=topk_all,
  493. iou_thres=iou_thres,
  494. conf_thres=conf_thres,
  495. keras=keras)
  496. if pb or tfjs: # pb prerequisite to tfjs
  497. f[6], _ = export_pb(s_model, file)
  498. if tflite or edgetpu:
  499. f[7], _ = export_tflite(s_model, im, file, int8 or edgetpu, data=data, nms=nms, agnostic_nms=agnostic_nms)
  500. if edgetpu:
  501. f[8], _ = export_edgetpu(file)
  502. add_tflite_metadata(f[8] or f[7], metadata, num_outputs=len(s_model.outputs))
  503. if tfjs:
  504. f[9], _ = export_tfjs(file)
  505. if paddle: # PaddlePaddle
  506. f[10], _ = export_paddle(model, im, file, metadata)
  507. # Finish
  508. f = [str(x) for x in f if x] # filter out '' and None
  509. if any(f):
  510. cls, det, seg = (isinstance(model, x) for x in (ClassificationModel, DetectionModel, SegmentationModel)) # type
  511. det &= not seg # segmentation models inherit from SegmentationModel(DetectionModel)
  512. dir = Path('segment' if seg else 'classify' if cls else '')
  513. h = '--half' if half else '' # --half FP16 inference arg
  514. s = "# WARNING ⚠️ ClassificationModel not yet supported for PyTorch Hub AutoShape inference" if cls else \
  515. "# WARNING ⚠️ SegmentationModel not yet supported for PyTorch Hub AutoShape inference" if seg else ''
  516. LOGGER.info(f'\nExport complete ({time.time() - t:.1f}s)'
  517. f"\nResults saved to {colorstr('bold', file.parent.resolve())}"
  518. f"\nDetect: python {dir / ('detect.py' if det else 'predict.py')} --weights {f[-1]} {h}"
  519. f"\nValidate: python {dir / 'val.py'} --weights {f[-1]} {h}"
  520. f"\nPyTorch Hub: model = torch.hub.load('ultralytics/yolov5', 'custom', '{f[-1]}') {s}"
  521. f"\nVisualize: https://netron.app")
  522. return f # return list of exported files/dirs
  523. def parse_opt():
  524. parser = argparse.ArgumentParser()
  525. parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='dataset.yaml path')
  526. parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model.pt path(s)')
  527. parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640, 640], help='image (h, w)')
  528. parser.add_argument('--batch-size', type=int, default=1, help='batch size')
  529. parser.add_argument('--device', default='cpu', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
  530. parser.add_argument('--half', action='store_true', help='FP16 half-precision export')
  531. parser.add_argument('--inplace', action='store_true', help='set YOLOv5 Detect() inplace=True')
  532. parser.add_argument('--keras', action='store_true', help='TF: use Keras')
  533. parser.add_argument('--optimize', action='store_true', help='TorchScript: optimize for mobile')
  534. parser.add_argument('--int8', action='store_true', help='CoreML/TF INT8 quantization')
  535. parser.add_argument('--dynamic', action='store_true', help='ONNX/TF/TensorRT: dynamic axes')
  536. parser.add_argument('--simplify', action='store_true', help='ONNX: simplify model')
  537. parser.add_argument('--opset', type=int, default=12, help='ONNX: opset version')
  538. parser.add_argument('--verbose', action='store_true', help='TensorRT: verbose log')
  539. parser.add_argument('--workspace', type=int, default=4, help='TensorRT: workspace size (GB)')
  540. parser.add_argument('--nms', action='store_true', help='TF: add NMS to model')
  541. parser.add_argument('--agnostic-nms', action='store_true', help='TF: add agnostic NMS to model')
  542. parser.add_argument('--topk-per-class', type=int, default=100, help='TF.js NMS: topk per class to keep')
  543. parser.add_argument('--topk-all', type=int, default=100, help='TF.js NMS: topk for all classes to keep')
  544. parser.add_argument('--iou-thres', type=float, default=0.45, help='TF.js NMS: IoU threshold')
  545. parser.add_argument('--conf-thres', type=float, default=0.25, help='TF.js NMS: confidence threshold')
  546. parser.add_argument(
  547. '--include',
  548. nargs='+',
  549. default=['torchscript'],
  550. help='torchscript, onnx, openvino, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs, paddle')
  551. opt = parser.parse_args()
  552. print_args(vars(opt))
  553. return opt
  554. def main(opt):
  555. for opt.weights in (opt.weights if isinstance(opt.weights, list) else [opt.weights]):
  556. run(**vars(opt))
  557. if __name__ == "__main__":
  558. opt = parse_opt()
  559. main(opt)