Add EdgeTPU support (#3630)
Browse files* Add models/tf.py for TensorFlow and TFLite export
* Set auto=False for int8 calibration
* Update requirements.txt for TensorFlow and TFLite export
* Read anchors directly from PyTorch weights
* Add --tf-nms to append NMS in TensorFlow SavedModel and GraphDef export
* Remove check_anchor_order, check_file, set_logging from import
* Reformat code and optimize imports
* Autodownload model and check cfg
* update --source path, img-size to 320, single output
* Adjust representative_dataset
* Put representative dataset in tfl_int8 block
* detect.py TF inference
* weights to string
* weights to string
* cleanup tf.py
* Add --dynamic-batch-size
* Add xywh normalization to reduce calibration error
* Update requirements.txt
TensorFlow 2.3.1 -> 2.4.0 to avoid int8 quantization error
* Fix imports
Move C3 from models.experimental to models.common
* Add models/tf.py for TensorFlow and TFLite export
* Set auto=False for int8 calibration
* Update requirements.txt for TensorFlow and TFLite export
* Read anchors directly from PyTorch weights
* Add --tf-nms to append NMS in TensorFlow SavedModel and GraphDef export
* Remove check_anchor_order, check_file, set_logging from import
* Reformat code and optimize imports
* Autodownload model and check cfg
* update --source path, img-size to 320, single output
* Adjust representative_dataset
* detect.py TF inference
* Put representative dataset in tfl_int8 block
* weights to string
* weights to string
* cleanup tf.py
* Add --dynamic-batch-size
* Add xywh normalization to reduce calibration error
* Update requirements.txt
TensorFlow 2.3.1 -> 2.4.0 to avoid int8 quantization error
* Fix imports
Move C3 from models.experimental to models.common
* implement C3() and SiLU()
* Add TensorFlow and TFLite Detection
* Add --tfl-detect for TFLite Detection
* Add int8 quantized TFLite inference in detect.py
* Add --edgetpu for Edge TPU detection
* Fix --img-size to add rectangle TensorFlow and TFLite input
* Add --no-tf-nms to detect objects using models combined with TensorFlow NMS
* Fix --img-size list type input
* Update README.md
* Add Android project for TFLite inference
* Upgrade TensorFlow v2.3.1 -> v2.4.0
* Disable normalization of xywh
* Rewrite names init in detect.py
* Change input resolution 640 -> 320 on Android
* Disable NNAPI
* Update README.me --img 640 -> 320
* Update README.me for Edge TPU
* Update README.md
* Fix reshape dim to support dynamic batching
* Fix reshape dim to support dynamic batching
* Add epsilon argument in tf_BN, which is different between TF and PT
* Set stride to None if not using PyTorch, and do not warmup without PyTorch
* Add list support in check_img_size()
* Add list input support in detect.py
* sys.path.append('./') to run from yolov5/
* Add int8 quantization support for TensorFlow 2.5
* Add get_coco128.sh
* Remove --no-tfl-detect in models/tf.py (Use tf-android-tfl-detect branch for EdgeTPU)
* Update requirements.txt
* Replace torch.load() with attempt_load()
* Update requirements.txt
* Add --tf-raw-resize to set half_pixel_centers=False
* Remove android directory
* Update README.md
* Update README.md
* Add multiple OS support for EdgeTPU detection
* Fix export and detect
* Export 3 YOLO heads with Edge TPU models
* Remove xywh denormalization with Edge TPU models in detect.py
* Fix saved_model and pb detect error
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Fix pre-commit.ci failure
* Add edgetpu in export.py docstring
* Fix Edge TPU model detection exported by TF 2.7
* Add class names for TF/TFLite in DetectMultibackend
* Fix assignment with nl in TFLite Detection
* Add check when getting Edge TPU compiler version
* Add UTF-8 encoding in opening --data file for Windows
* Remove redundant TensorFlow import
* Add Edge TPU in export.py's docstring
* Add the detect layer in Edge TPU model conversion
* Default `dnn=False`
* Cleanup data.yaml loading
* Update detect.py
* Update val.py
* Comments and generalize data.yaml names
Co-authored-by: Glenn Jocher <[email protected]>
Co-authored-by: unknown <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
@@ -38,6 +38,7 @@ from utils.torch_utils import select_device, time_sync
|
|
38 |
@torch.no_grad()
|
39 |
def run(weights=ROOT / 'yolov5s.pt', # model.pt path(s)
|
40 |
source=ROOT / 'data/images', # file/dir/URL/glob, 0 for webcam
|
|
|
41 |
imgsz=(640, 640), # inference size (height, width)
|
42 |
conf_thres=0.25, # confidence threshold
|
43 |
iou_thres=0.45, # NMS IOU threshold
|
@@ -76,7 +77,7 @@ def run(weights=ROOT / 'yolov5s.pt', # model.pt path(s)
|
|
76 |
|
77 |
# Load model
|
78 |
device = select_device(device)
|
79 |
-
model = DetectMultiBackend(weights, device=device, dnn=dnn)
|
80 |
stride, names, pt, jit, onnx, engine = model.stride, model.names, model.pt, model.jit, model.onnx, model.engine
|
81 |
imgsz = check_img_size(imgsz, s=stride) # check image size
|
82 |
|
@@ -204,6 +205,7 @@ def parse_opt():
|
|
204 |
parser = argparse.ArgumentParser()
|
205 |
parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model path(s)')
|
206 |
parser.add_argument('--source', type=str, default=ROOT / 'data/images', help='file/dir/URL/glob, 0 for webcam')
|
|
|
207 |
parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')
|
208 |
parser.add_argument('--conf-thres', type=float, default=0.25, help='confidence threshold')
|
209 |
parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold')
|
|
|
38 |
@torch.no_grad()
|
39 |
def run(weights=ROOT / 'yolov5s.pt', # model.pt path(s)
|
40 |
source=ROOT / 'data/images', # file/dir/URL/glob, 0 for webcam
|
41 |
+
data=ROOT / 'data/coco128.yaml', # dataset.yaml path
|
42 |
imgsz=(640, 640), # inference size (height, width)
|
43 |
conf_thres=0.25, # confidence threshold
|
44 |
iou_thres=0.45, # NMS IOU threshold
|
|
|
77 |
|
78 |
# Load model
|
79 |
device = select_device(device)
|
80 |
+
model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data)
|
81 |
stride, names, pt, jit, onnx, engine = model.stride, model.names, model.pt, model.jit, model.onnx, model.engine
|
82 |
imgsz = check_img_size(imgsz, s=stride) # check image size
|
83 |
|
|
|
205 |
parser = argparse.ArgumentParser()
|
206 |
parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model path(s)')
|
207 |
parser.add_argument('--source', type=str, default=ROOT / 'data/images', help='file/dir/URL/glob, 0 for webcam')
|
208 |
+
parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='(optional) dataset.yaml path')
|
209 |
parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')
|
210 |
parser.add_argument('--conf-thres', type=float, default=0.25, help='confidence threshold')
|
211 |
parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold')
|
@@ -248,6 +248,24 @@ def export_tflite(keras_model, im, file, int8, data, ncalib, prefix=colorstr('Te
|
|
248 |
LOGGER.info(f'\n{prefix} export failure: {e}')
|
249 |
|
250 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
251 |
def export_tfjs(keras_model, im, file, prefix=colorstr('TensorFlow.js:')):
|
252 |
# YOLOv5 TensorFlow.js export
|
253 |
try:
|
@@ -285,6 +303,7 @@ def export_tfjs(keras_model, im, file, prefix=colorstr('TensorFlow.js:')):
|
|
285 |
|
286 |
|
287 |
def export_engine(model, im, file, train, half, simplify, workspace=4, verbose=False, prefix=colorstr('TensorRT:')):
|
|
|
288 |
try:
|
289 |
check_requirements(('tensorrt',))
|
290 |
import tensorrt as trt
|
@@ -356,7 +375,7 @@ def run(data=ROOT / 'data/coco128.yaml', # 'dataset.yaml path'
|
|
356 |
):
|
357 |
t = time.time()
|
358 |
include = [x.lower() for x in include]
|
359 |
-
tf_exports = list(x in include for x in ('saved_model', 'pb', 'tflite', 'tfjs')) # TensorFlow exports
|
360 |
file = Path(url2file(weights) if str(weights).startswith(('http:/', 'https:/')) else weights)
|
361 |
|
362 |
# Checks
|
@@ -405,15 +424,17 @@ def run(data=ROOT / 'data/coco128.yaml', # 'dataset.yaml path'
|
|
405 |
|
406 |
# TensorFlow Exports
|
407 |
if any(tf_exports):
|
408 |
-
pb, tflite, tfjs = tf_exports[1:]
|
409 |
assert not (tflite and tfjs), 'TFLite and TF.js models must be exported separately, please pass only one type.'
|
410 |
model = export_saved_model(model, im, file, dynamic, tf_nms=nms or agnostic_nms or tfjs,
|
411 |
agnostic_nms=agnostic_nms or tfjs, topk_per_class=topk_per_class, topk_all=topk_all,
|
412 |
conf_thres=conf_thres, iou_thres=iou_thres) # keras model
|
413 |
if pb or tfjs: # pb prerequisite to tfjs
|
414 |
export_pb(model, im, file)
|
415 |
-
if tflite:
|
416 |
-
export_tflite(model, im, file, int8=int8, data=data, ncalib=100)
|
|
|
|
|
417 |
if tfjs:
|
418 |
export_tfjs(model, im, file)
|
419 |
|
|
|
248 |
LOGGER.info(f'\n{prefix} export failure: {e}')
|
249 |
|
250 |
|
251 |
+
def export_edgetpu(keras_model, im, file, prefix=colorstr('Edge TPU:')):
|
252 |
+
# YOLOv5 Edge TPU export https://coral.ai/docs/edgetpu/models-intro/
|
253 |
+
try:
|
254 |
+
cmd = 'edgetpu_compiler --version'
|
255 |
+
out = subprocess.run(cmd, shell=True, capture_output=True, check=True)
|
256 |
+
ver = out.stdout.decode().split()[-1]
|
257 |
+
LOGGER.info(f'\n{prefix} starting export with Edge TPU compiler {ver}...')
|
258 |
+
f = str(file).replace('.pt', '-int8_edgetpu.tflite')
|
259 |
+
f_tfl = str(file).replace('.pt', '-int8.tflite') # TFLite model
|
260 |
+
|
261 |
+
cmd = f"edgetpu_compiler -s {f_tfl}"
|
262 |
+
subprocess.run(cmd, shell=True, check=True)
|
263 |
+
|
264 |
+
LOGGER.info(f'{prefix} export success, saved as {f} ({file_size(f):.1f} MB)')
|
265 |
+
except Exception as e:
|
266 |
+
LOGGER.info(f'\n{prefix} export failure: {e}')
|
267 |
+
|
268 |
+
|
269 |
def export_tfjs(keras_model, im, file, prefix=colorstr('TensorFlow.js:')):
|
270 |
# YOLOv5 TensorFlow.js export
|
271 |
try:
|
|
|
303 |
|
304 |
|
305 |
def export_engine(model, im, file, train, half, simplify, workspace=4, verbose=False, prefix=colorstr('TensorRT:')):
|
306 |
+
# YOLOv5 TensorRT export https://developer.nvidia.com/tensorrt
|
307 |
try:
|
308 |
check_requirements(('tensorrt',))
|
309 |
import tensorrt as trt
|
|
|
375 |
):
|
376 |
t = time.time()
|
377 |
include = [x.lower() for x in include]
|
378 |
+
tf_exports = list(x in include for x in ('saved_model', 'pb', 'tflite', 'edgetpu', 'tfjs')) # TensorFlow exports
|
379 |
file = Path(url2file(weights) if str(weights).startswith(('http:/', 'https:/')) else weights)
|
380 |
|
381 |
# Checks
|
|
|
424 |
|
425 |
# TensorFlow Exports
|
426 |
if any(tf_exports):
|
427 |
+
pb, tflite, edgetpu, tfjs = tf_exports[1:]
|
428 |
assert not (tflite and tfjs), 'TFLite and TF.js models must be exported separately, please pass only one type.'
|
429 |
model = export_saved_model(model, im, file, dynamic, tf_nms=nms or agnostic_nms or tfjs,
|
430 |
agnostic_nms=agnostic_nms or tfjs, topk_per_class=topk_per_class, topk_all=topk_all,
|
431 |
conf_thres=conf_thres, iou_thres=iou_thres) # keras model
|
432 |
if pb or tfjs: # pb prerequisite to tfjs
|
433 |
export_pb(model, im, file)
|
434 |
+
if tflite or edgetpu:
|
435 |
+
export_tflite(model, im, file, int8=int8 or edgetpu, data=data, ncalib=100)
|
436 |
+
if edgetpu:
|
437 |
+
export_edgetpu(model, im, file)
|
438 |
if tfjs:
|
439 |
export_tfjs(model, im, file)
|
440 |
|
@@ -17,6 +17,7 @@ import pandas as pd
|
|
17 |
import requests
|
18 |
import torch
|
19 |
import torch.nn as nn
|
|
|
20 |
from PIL import Image
|
21 |
from torch.cuda import amp
|
22 |
|
@@ -276,7 +277,7 @@ class Concat(nn.Module):
|
|
276 |
|
277 |
class DetectMultiBackend(nn.Module):
|
278 |
# YOLOv5 MultiBackend class for python inference on various backends
|
279 |
-
def __init__(self, weights='yolov5s.pt', device=None, dnn=False):
|
280 |
# Usage:
|
281 |
# PyTorch: weights = *.pt
|
282 |
# TorchScript: *.torchscript
|
@@ -284,6 +285,7 @@ class DetectMultiBackend(nn.Module):
|
|
284 |
# TensorFlow: *_saved_model
|
285 |
# TensorFlow: *.pb
|
286 |
# TensorFlow Lite: *.tflite
|
|
|
287 |
# ONNX Runtime: *.onnx
|
288 |
# OpenCV DNN: *.onnx with dnn=True
|
289 |
# TensorRT: *.engine
|
@@ -297,6 +299,9 @@ class DetectMultiBackend(nn.Module):
|
|
297 |
pt, jit, onnx, engine, tflite, pb, saved_model, coreml = (suffix == x for x in suffixes) # backend booleans
|
298 |
stride, names = 64, [f'class{i}' for i in range(1000)] # assign defaults
|
299 |
w = attempt_download(w) # download if not local
|
|
|
|
|
|
|
300 |
|
301 |
if jit: # TorchScript
|
302 |
LOGGER.info(f'Loading {w} for TorchScript inference...')
|
@@ -343,7 +348,7 @@ class DetectMultiBackend(nn.Module):
|
|
343 |
binding_addrs = OrderedDict((n, d.ptr) for n, d in bindings.items())
|
344 |
context = model.create_execution_context()
|
345 |
batch_size = bindings['images'].shape[0]
|
346 |
-
else: # TensorFlow
|
347 |
if pb: # https://www.tensorflow.org/guide/migrate#a_graphpb_or_graphpbtxt
|
348 |
LOGGER.info(f'Loading {w} for TensorFlow *.pb inference...')
|
349 |
import tensorflow as tf
|
@@ -425,6 +430,7 @@ class DetectMultiBackend(nn.Module):
|
|
425 |
y[..., 1] *= h # y
|
426 |
y[..., 2] *= w # w
|
427 |
y[..., 3] *= h # h
|
|
|
428 |
y = torch.tensor(y) if isinstance(y, np.ndarray) else y
|
429 |
return (y, []) if val else y
|
430 |
|
|
|
17 |
import requests
|
18 |
import torch
|
19 |
import torch.nn as nn
|
20 |
+
import yaml
|
21 |
from PIL import Image
|
22 |
from torch.cuda import amp
|
23 |
|
|
|
277 |
|
278 |
class DetectMultiBackend(nn.Module):
|
279 |
# YOLOv5 MultiBackend class for python inference on various backends
|
280 |
+
def __init__(self, weights='yolov5s.pt', device=None, dnn=False, data=None):
|
281 |
# Usage:
|
282 |
# PyTorch: weights = *.pt
|
283 |
# TorchScript: *.torchscript
|
|
|
285 |
# TensorFlow: *_saved_model
|
286 |
# TensorFlow: *.pb
|
287 |
# TensorFlow Lite: *.tflite
|
288 |
+
# TensorFlow Edge TPU: *_edgetpu.tflite
|
289 |
# ONNX Runtime: *.onnx
|
290 |
# OpenCV DNN: *.onnx with dnn=True
|
291 |
# TensorRT: *.engine
|
|
|
299 |
pt, jit, onnx, engine, tflite, pb, saved_model, coreml = (suffix == x for x in suffixes) # backend booleans
|
300 |
stride, names = 64, [f'class{i}' for i in range(1000)] # assign defaults
|
301 |
w = attempt_download(w) # download if not local
|
302 |
+
if data: # data.yaml path (optional)
|
303 |
+
with open(data, errors='ignore') as f:
|
304 |
+
names = yaml.safe_load(f)['names'] # class names
|
305 |
|
306 |
if jit: # TorchScript
|
307 |
LOGGER.info(f'Loading {w} for TorchScript inference...')
|
|
|
348 |
binding_addrs = OrderedDict((n, d.ptr) for n, d in bindings.items())
|
349 |
context = model.create_execution_context()
|
350 |
batch_size = bindings['images'].shape[0]
|
351 |
+
else: # TensorFlow (TFLite, pb, saved_model)
|
352 |
if pb: # https://www.tensorflow.org/guide/migrate#a_graphpb_or_graphpbtxt
|
353 |
LOGGER.info(f'Loading {w} for TensorFlow *.pb inference...')
|
354 |
import tensorflow as tf
|
|
|
430 |
y[..., 1] *= h # y
|
431 |
y[..., 2] *= w # w
|
432 |
y[..., 3] *= h # h
|
433 |
+
|
434 |
y = torch.tensor(y) if isinstance(y, np.ndarray) else y
|
435 |
return (y, []) if val else y
|
436 |
|
@@ -124,7 +124,7 @@ def run(data,
|
|
124 |
(save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir
|
125 |
|
126 |
# Load model
|
127 |
-
model = DetectMultiBackend(weights, device=device, dnn=dnn)
|
128 |
stride, pt, jit, engine = model.stride, model.pt, model.jit, model.engine
|
129 |
imgsz = check_img_size(imgsz, s=stride) # check image size
|
130 |
half &= (pt or jit or engine) and device.type != 'cpu' # half precision only supported by PyTorch on CUDA
|
|
|
124 |
(save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir
|
125 |
|
126 |
# Load model
|
127 |
+
model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data)
|
128 |
stride, pt, jit, engine = model.stride, model.pt, model.jit, model.engine
|
129 |
imgsz = check_img_size(imgsz, s=stride) # check image size
|
130 |
half &= (pt or jit or engine) and device.type != 'cpu' # half precision only supported by PyTorch on CUDA
|