megadetector 5.0.5__py3-none-any.whl → 5.0.7__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.
Potentially problematic release.
This version of megadetector might be problematic. Click here for more details.
- api/batch_processing/data_preparation/manage_local_batch.py +302 -263
- api/batch_processing/data_preparation/manage_video_batch.py +81 -2
- api/batch_processing/postprocessing/add_max_conf.py +1 -0
- api/batch_processing/postprocessing/categorize_detections_by_size.py +50 -19
- api/batch_processing/postprocessing/compare_batch_results.py +110 -60
- api/batch_processing/postprocessing/load_api_results.py +56 -70
- api/batch_processing/postprocessing/md_to_coco.py +1 -1
- api/batch_processing/postprocessing/md_to_labelme.py +2 -1
- api/batch_processing/postprocessing/postprocess_batch_results.py +240 -81
- api/batch_processing/postprocessing/render_detection_confusion_matrix.py +625 -0
- api/batch_processing/postprocessing/repeat_detection_elimination/find_repeat_detections.py +71 -23
- api/batch_processing/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +1 -1
- api/batch_processing/postprocessing/repeat_detection_elimination/repeat_detections_core.py +227 -75
- api/batch_processing/postprocessing/subset_json_detector_output.py +132 -5
- api/batch_processing/postprocessing/top_folders_to_bottom.py +1 -1
- api/synchronous/api_core/animal_detection_api/detection/run_detector_batch.py +2 -2
- classification/prepare_classification_script.py +191 -191
- data_management/coco_to_yolo.py +68 -45
- data_management/databases/integrity_check_json_db.py +7 -5
- data_management/generate_crops_from_cct.py +3 -3
- data_management/get_image_sizes.py +8 -6
- data_management/importers/add_timestamps_to_icct.py +79 -0
- data_management/importers/animl_results_to_md_results.py +160 -0
- data_management/importers/auckland_doc_test_to_json.py +4 -4
- data_management/importers/auckland_doc_to_json.py +1 -1
- data_management/importers/awc_to_json.py +5 -5
- data_management/importers/bellevue_to_json.py +5 -5
- data_management/importers/carrizo_shrubfree_2018.py +5 -5
- data_management/importers/carrizo_trail_cam_2017.py +5 -5
- data_management/importers/cct_field_adjustments.py +2 -3
- data_management/importers/channel_islands_to_cct.py +4 -4
- data_management/importers/ena24_to_json.py +5 -5
- data_management/importers/helena_to_cct.py +10 -10
- data_management/importers/idaho-camera-traps.py +12 -12
- data_management/importers/idfg_iwildcam_lila_prep.py +8 -8
- data_management/importers/jb_csv_to_json.py +4 -4
- data_management/importers/missouri_to_json.py +1 -1
- data_management/importers/noaa_seals_2019.py +1 -1
- data_management/importers/pc_to_json.py +5 -5
- data_management/importers/prepare-noaa-fish-data-for-lila.py +4 -4
- data_management/importers/prepare_zsl_imerit.py +5 -5
- data_management/importers/rspb_to_json.py +4 -4
- data_management/importers/save_the_elephants_survey_A.py +5 -5
- data_management/importers/save_the_elephants_survey_B.py +6 -6
- data_management/importers/snapshot_safari_importer.py +9 -9
- data_management/importers/snapshot_serengeti_lila.py +9 -9
- data_management/importers/timelapse_csv_set_to_json.py +5 -7
- data_management/importers/ubc_to_json.py +4 -4
- data_management/importers/umn_to_json.py +4 -4
- data_management/importers/wellington_to_json.py +1 -1
- data_management/importers/wi_to_json.py +2 -2
- data_management/importers/zamba_results_to_md_results.py +181 -0
- data_management/labelme_to_coco.py +35 -7
- data_management/labelme_to_yolo.py +229 -0
- data_management/lila/add_locations_to_island_camera_traps.py +1 -1
- data_management/lila/add_locations_to_nacti.py +147 -0
- data_management/lila/create_lila_blank_set.py +474 -0
- data_management/lila/create_lila_test_set.py +2 -1
- data_management/lila/create_links_to_md_results_files.py +106 -0
- data_management/lila/download_lila_subset.py +46 -21
- data_management/lila/generate_lila_per_image_labels.py +23 -14
- data_management/lila/get_lila_annotation_counts.py +17 -11
- data_management/lila/lila_common.py +14 -11
- data_management/lila/test_lila_metadata_urls.py +116 -0
- data_management/ocr_tools.py +829 -0
- data_management/resize_coco_dataset.py +13 -11
- data_management/yolo_output_to_md_output.py +84 -12
- data_management/yolo_to_coco.py +38 -20
- detection/process_video.py +36 -14
- detection/pytorch_detector.py +23 -8
- detection/run_detector.py +76 -19
- detection/run_detector_batch.py +178 -63
- detection/run_inference_with_yolov5_val.py +326 -57
- detection/run_tiled_inference.py +153 -43
- detection/video_utils.py +34 -8
- md_utils/ct_utils.py +172 -1
- md_utils/md_tests.py +372 -51
- md_utils/path_utils.py +167 -39
- md_utils/process_utils.py +26 -7
- md_utils/split_locations_into_train_val.py +215 -0
- md_utils/string_utils.py +10 -0
- md_utils/url_utils.py +0 -2
- md_utils/write_html_image_list.py +9 -26
- md_visualization/plot_utils.py +12 -8
- md_visualization/visualization_utils.py +106 -7
- md_visualization/visualize_db.py +16 -8
- md_visualization/visualize_detector_output.py +208 -97
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/METADATA +3 -6
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/RECORD +98 -121
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/WHEEL +1 -1
- taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +1 -1
- taxonomy_mapping/map_new_lila_datasets.py +43 -39
- taxonomy_mapping/prepare_lila_taxonomy_release.py +5 -2
- taxonomy_mapping/preview_lila_taxonomy.py +27 -27
- taxonomy_mapping/species_lookup.py +33 -13
- taxonomy_mapping/taxonomy_csv_checker.py +7 -5
- api/synchronous/api_core/yolov5/detect.py +0 -252
- api/synchronous/api_core/yolov5/export.py +0 -607
- api/synchronous/api_core/yolov5/hubconf.py +0 -146
- api/synchronous/api_core/yolov5/models/__init__.py +0 -0
- api/synchronous/api_core/yolov5/models/common.py +0 -738
- api/synchronous/api_core/yolov5/models/experimental.py +0 -104
- api/synchronous/api_core/yolov5/models/tf.py +0 -574
- api/synchronous/api_core/yolov5/models/yolo.py +0 -338
- api/synchronous/api_core/yolov5/train.py +0 -670
- api/synchronous/api_core/yolov5/utils/__init__.py +0 -36
- api/synchronous/api_core/yolov5/utils/activations.py +0 -103
- api/synchronous/api_core/yolov5/utils/augmentations.py +0 -284
- api/synchronous/api_core/yolov5/utils/autoanchor.py +0 -170
- api/synchronous/api_core/yolov5/utils/autobatch.py +0 -66
- api/synchronous/api_core/yolov5/utils/aws/__init__.py +0 -0
- api/synchronous/api_core/yolov5/utils/aws/resume.py +0 -40
- api/synchronous/api_core/yolov5/utils/benchmarks.py +0 -148
- api/synchronous/api_core/yolov5/utils/callbacks.py +0 -71
- api/synchronous/api_core/yolov5/utils/dataloaders.py +0 -1087
- api/synchronous/api_core/yolov5/utils/downloads.py +0 -178
- api/synchronous/api_core/yolov5/utils/flask_rest_api/example_request.py +0 -19
- api/synchronous/api_core/yolov5/utils/flask_rest_api/restapi.py +0 -46
- api/synchronous/api_core/yolov5/utils/general.py +0 -1018
- api/synchronous/api_core/yolov5/utils/loggers/__init__.py +0 -187
- api/synchronous/api_core/yolov5/utils/loggers/wandb/__init__.py +0 -0
- api/synchronous/api_core/yolov5/utils/loggers/wandb/log_dataset.py +0 -27
- api/synchronous/api_core/yolov5/utils/loggers/wandb/sweep.py +0 -41
- api/synchronous/api_core/yolov5/utils/loggers/wandb/wandb_utils.py +0 -577
- api/synchronous/api_core/yolov5/utils/loss.py +0 -234
- api/synchronous/api_core/yolov5/utils/metrics.py +0 -355
- api/synchronous/api_core/yolov5/utils/plots.py +0 -489
- api/synchronous/api_core/yolov5/utils/torch_utils.py +0 -314
- api/synchronous/api_core/yolov5/val.py +0 -394
- md_utils/matlab_porting_tools.py +0 -97
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/LICENSE +0 -0
- {megadetector-5.0.5.dist-info → megadetector-5.0.7.dist-info}/top_level.txt +0 -0
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
|
|
2
|
-
"""
|
|
3
|
-
Experimental modules
|
|
4
|
-
"""
|
|
5
|
-
import math
|
|
6
|
-
|
|
7
|
-
import numpy as np
|
|
8
|
-
import torch
|
|
9
|
-
import torch.nn as nn
|
|
10
|
-
|
|
11
|
-
from models.common import Conv
|
|
12
|
-
from utils.downloads import attempt_download
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class Sum(nn.Module):
|
|
16
|
-
# Weighted sum of 2 or more layers https://arxiv.org/abs/1911.09070
|
|
17
|
-
def __init__(self, n, weight=False): # n: number of inputs
|
|
18
|
-
super().__init__()
|
|
19
|
-
self.weight = weight # apply weights boolean
|
|
20
|
-
self.iter = range(n - 1) # iter object
|
|
21
|
-
if weight:
|
|
22
|
-
self.w = nn.Parameter(-torch.arange(1.0, n) / 2, requires_grad=True) # layer weights
|
|
23
|
-
|
|
24
|
-
def forward(self, x):
|
|
25
|
-
y = x[0] # no weight
|
|
26
|
-
if self.weight:
|
|
27
|
-
w = torch.sigmoid(self.w) * 2
|
|
28
|
-
for i in self.iter:
|
|
29
|
-
y = y + x[i + 1] * w[i]
|
|
30
|
-
else:
|
|
31
|
-
for i in self.iter:
|
|
32
|
-
y = y + x[i + 1]
|
|
33
|
-
return y
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class MixConv2d(nn.Module):
|
|
37
|
-
# Mixed Depth-wise Conv https://arxiv.org/abs/1907.09595
|
|
38
|
-
def __init__(self, c1, c2, k=(1, 3), s=1, equal_ch=True): # ch_in, ch_out, kernel, stride, ch_strategy
|
|
39
|
-
super().__init__()
|
|
40
|
-
n = len(k) # number of convolutions
|
|
41
|
-
if equal_ch: # equal c_ per group
|
|
42
|
-
i = torch.linspace(0, n - 1E-6, c2).floor() # c2 indices
|
|
43
|
-
c_ = [(i == g).sum() for g in range(n)] # intermediate channels
|
|
44
|
-
else: # equal weight.numel() per group
|
|
45
|
-
b = [c2] + [0] * n
|
|
46
|
-
a = np.eye(n + 1, n, k=-1)
|
|
47
|
-
a -= np.roll(a, 1, axis=1)
|
|
48
|
-
a *= np.array(k) ** 2
|
|
49
|
-
a[0] = 1
|
|
50
|
-
c_ = np.linalg.lstsq(a, b, rcond=None)[0].round() # solve for equal weight indices, ax = b
|
|
51
|
-
|
|
52
|
-
self.m = nn.ModuleList([
|
|
53
|
-
nn.Conv2d(c1, int(c_), k, s, k // 2, groups=math.gcd(c1, int(c_)), bias=False) for k, c_ in zip(k, c_)])
|
|
54
|
-
self.bn = nn.BatchNorm2d(c2)
|
|
55
|
-
self.act = nn.SiLU()
|
|
56
|
-
|
|
57
|
-
def forward(self, x):
|
|
58
|
-
return self.act(self.bn(torch.cat([m(x) for m in self.m], 1)))
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class Ensemble(nn.ModuleList):
|
|
62
|
-
# Ensemble of models
|
|
63
|
-
def __init__(self):
|
|
64
|
-
super().__init__()
|
|
65
|
-
|
|
66
|
-
def forward(self, x, augment=False, profile=False, visualize=False):
|
|
67
|
-
y = [module(x, augment, profile, visualize)[0] for module in self]
|
|
68
|
-
# y = torch.stack(y).max(0)[0] # max ensemble
|
|
69
|
-
# y = torch.stack(y).mean(0) # mean ensemble
|
|
70
|
-
y = torch.cat(y, 1) # nms ensemble
|
|
71
|
-
return y, None # inference, train output
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def attempt_load(weights, device=None, inplace=True, fuse=True):
|
|
75
|
-
# Loads an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a
|
|
76
|
-
from models.yolo import Detect, Model
|
|
77
|
-
|
|
78
|
-
model = Ensemble()
|
|
79
|
-
for w in weights if isinstance(weights, list) else [weights]:
|
|
80
|
-
ckpt = torch.load(attempt_download(w), map_location='cpu') # load
|
|
81
|
-
ckpt = (ckpt.get('ema') or ckpt['model']).to(device).float() # FP32 model
|
|
82
|
-
model.append(ckpt.fuse().eval() if fuse else ckpt.eval()) # fused or un-fused model in eval mode
|
|
83
|
-
|
|
84
|
-
# Compatibility updates
|
|
85
|
-
for m in model.modules():
|
|
86
|
-
t = type(m)
|
|
87
|
-
if t in (nn.Hardswish, nn.LeakyReLU, nn.ReLU, nn.ReLU6, nn.SiLU, Detect, Model):
|
|
88
|
-
m.inplace = inplace # torch 1.7.0 compatibility
|
|
89
|
-
if t is Detect and not isinstance(m.anchor_grid, list):
|
|
90
|
-
delattr(m, 'anchor_grid')
|
|
91
|
-
setattr(m, 'anchor_grid', [torch.zeros(1)] * m.nl)
|
|
92
|
-
elif t is Conv:
|
|
93
|
-
m._non_persistent_buffers_set = set() # torch 1.6.0 compatibility
|
|
94
|
-
elif t is nn.Upsample and not hasattr(m, 'recompute_scale_factor'):
|
|
95
|
-
m.recompute_scale_factor = None # torch 1.11.0 compatibility
|
|
96
|
-
|
|
97
|
-
if len(model) == 1:
|
|
98
|
-
return model[-1] # return model
|
|
99
|
-
print(f'Ensemble created with {weights}\n')
|
|
100
|
-
for k in 'names', 'nc', 'yaml':
|
|
101
|
-
setattr(model, k, getattr(model[0], k))
|
|
102
|
-
model.stride = model[torch.argmax(torch.tensor([m.stride.max() for m in model])).int()].stride # max stride
|
|
103
|
-
assert all(model[0].nc == m.nc for m in model), f'Models have different class counts: {[m.nc for m in model]}'
|
|
104
|
-
return model # return ensemble
|
|
@@ -1,574 +0,0 @@
|
|
|
1
|
-
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
|
|
2
|
-
"""
|
|
3
|
-
TensorFlow, Keras and TFLite versions of YOLOv5
|
|
4
|
-
Authored by https://github.com/zldrobit in PR https://github.com/ultralytics/yolov5/pull/1127
|
|
5
|
-
|
|
6
|
-
Usage:
|
|
7
|
-
$ python models/tf.py --weights yolov5s.pt
|
|
8
|
-
|
|
9
|
-
Export:
|
|
10
|
-
$ python path/to/export.py --weights yolov5s.pt --include saved_model pb tflite tfjs
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import argparse
|
|
14
|
-
import sys
|
|
15
|
-
from copy import deepcopy
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
|
|
18
|
-
FILE = Path(__file__).resolve()
|
|
19
|
-
ROOT = FILE.parents[1] # YOLOv5 root directory
|
|
20
|
-
if str(ROOT) not in sys.path:
|
|
21
|
-
sys.path.append(str(ROOT)) # add ROOT to PATH
|
|
22
|
-
# ROOT = ROOT.relative_to(Path.cwd()) # relative
|
|
23
|
-
|
|
24
|
-
import numpy as np
|
|
25
|
-
import tensorflow as tf
|
|
26
|
-
import torch
|
|
27
|
-
import torch.nn as nn
|
|
28
|
-
from tensorflow import keras
|
|
29
|
-
|
|
30
|
-
from models.common import (C3, SPP, SPPF, Bottleneck, BottleneckCSP, C3x, Concat, Conv, CrossConv, DWConv,
|
|
31
|
-
DWConvTranspose2d, Focus, autopad)
|
|
32
|
-
from models.experimental import MixConv2d, attempt_load
|
|
33
|
-
from models.yolo import Detect
|
|
34
|
-
from utils.activations import SiLU
|
|
35
|
-
from utils.general import LOGGER, make_divisible, print_args
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class TFBN(keras.layers.Layer):
|
|
39
|
-
# TensorFlow BatchNormalization wrapper
|
|
40
|
-
def __init__(self, w=None):
|
|
41
|
-
super().__init__()
|
|
42
|
-
self.bn = keras.layers.BatchNormalization(
|
|
43
|
-
beta_initializer=keras.initializers.Constant(w.bias.numpy()),
|
|
44
|
-
gamma_initializer=keras.initializers.Constant(w.weight.numpy()),
|
|
45
|
-
moving_mean_initializer=keras.initializers.Constant(w.running_mean.numpy()),
|
|
46
|
-
moving_variance_initializer=keras.initializers.Constant(w.running_var.numpy()),
|
|
47
|
-
epsilon=w.eps)
|
|
48
|
-
|
|
49
|
-
def call(self, inputs):
|
|
50
|
-
return self.bn(inputs)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class TFPad(keras.layers.Layer):
|
|
54
|
-
# Pad inputs in spatial dimensions 1 and 2
|
|
55
|
-
def __init__(self, pad):
|
|
56
|
-
super().__init__()
|
|
57
|
-
if isinstance(pad, int):
|
|
58
|
-
self.pad = tf.constant([[0, 0], [pad, pad], [pad, pad], [0, 0]])
|
|
59
|
-
else: # tuple/list
|
|
60
|
-
self.pad = tf.constant([[0, 0], [pad[0], pad[0]], [pad[1], pad[1]], [0, 0]])
|
|
61
|
-
|
|
62
|
-
def call(self, inputs):
|
|
63
|
-
return tf.pad(inputs, self.pad, mode='constant', constant_values=0)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class TFConv(keras.layers.Layer):
|
|
67
|
-
# Standard convolution
|
|
68
|
-
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, w=None):
|
|
69
|
-
# ch_in, ch_out, weights, kernel, stride, padding, groups
|
|
70
|
-
super().__init__()
|
|
71
|
-
assert g == 1, "TF v2.2 Conv2D does not support 'groups' argument"
|
|
72
|
-
# TensorFlow convolution padding is inconsistent with PyTorch (e.g. k=3 s=2 'SAME' padding)
|
|
73
|
-
# see https://stackoverflow.com/questions/52975843/comparing-conv2d-with-padding-between-tensorflow-and-pytorch
|
|
74
|
-
conv = keras.layers.Conv2D(
|
|
75
|
-
filters=c2,
|
|
76
|
-
kernel_size=k,
|
|
77
|
-
strides=s,
|
|
78
|
-
padding='SAME' if s == 1 else 'VALID',
|
|
79
|
-
use_bias=not hasattr(w, 'bn'),
|
|
80
|
-
kernel_initializer=keras.initializers.Constant(w.conv.weight.permute(2, 3, 1, 0).numpy()),
|
|
81
|
-
bias_initializer='zeros' if hasattr(w, 'bn') else keras.initializers.Constant(w.conv.bias.numpy()))
|
|
82
|
-
self.conv = conv if s == 1 else keras.Sequential([TFPad(autopad(k, p)), conv])
|
|
83
|
-
self.bn = TFBN(w.bn) if hasattr(w, 'bn') else tf.identity
|
|
84
|
-
self.act = activations(w.act) if act else tf.identity
|
|
85
|
-
|
|
86
|
-
def call(self, inputs):
|
|
87
|
-
return self.act(self.bn(self.conv(inputs)))
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
class TFDWConv(keras.layers.Layer):
|
|
91
|
-
# Depthwise convolution
|
|
92
|
-
def __init__(self, c1, c2, k=1, s=1, p=None, act=True, w=None):
|
|
93
|
-
# ch_in, ch_out, weights, kernel, stride, padding, groups
|
|
94
|
-
super().__init__()
|
|
95
|
-
assert c2 % c1 == 0, f'TFDWConv() output={c2} must be a multiple of input={c1} channels'
|
|
96
|
-
conv = keras.layers.DepthwiseConv2D(
|
|
97
|
-
kernel_size=k,
|
|
98
|
-
depth_multiplier=c2 // c1,
|
|
99
|
-
strides=s,
|
|
100
|
-
padding='SAME' if s == 1 else 'VALID',
|
|
101
|
-
use_bias=not hasattr(w, 'bn'),
|
|
102
|
-
depthwise_initializer=keras.initializers.Constant(w.conv.weight.permute(2, 3, 1, 0).numpy()),
|
|
103
|
-
bias_initializer='zeros' if hasattr(w, 'bn') else keras.initializers.Constant(w.conv.bias.numpy()))
|
|
104
|
-
self.conv = conv if s == 1 else keras.Sequential([TFPad(autopad(k, p)), conv])
|
|
105
|
-
self.bn = TFBN(w.bn) if hasattr(w, 'bn') else tf.identity
|
|
106
|
-
self.act = activations(w.act) if act else tf.identity
|
|
107
|
-
|
|
108
|
-
def call(self, inputs):
|
|
109
|
-
return self.act(self.bn(self.conv(inputs)))
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
class TFDWConvTranspose2d(keras.layers.Layer):
|
|
113
|
-
# Depthwise ConvTranspose2d
|
|
114
|
-
def __init__(self, c1, c2, k=1, s=1, p1=0, p2=0, w=None):
|
|
115
|
-
# ch_in, ch_out, weights, kernel, stride, padding, groups
|
|
116
|
-
super().__init__()
|
|
117
|
-
assert c1 == c2, f'TFDWConv() output={c2} must be equal to input={c1} channels'
|
|
118
|
-
assert k == 4 and p1 == 1, 'TFDWConv() only valid for k=4 and p1=1'
|
|
119
|
-
weight, bias = w.weight.permute(2, 3, 1, 0).numpy(), w.bias.numpy()
|
|
120
|
-
self.c1 = c1
|
|
121
|
-
self.conv = [
|
|
122
|
-
keras.layers.Conv2DTranspose(filters=1,
|
|
123
|
-
kernel_size=k,
|
|
124
|
-
strides=s,
|
|
125
|
-
padding='VALID',
|
|
126
|
-
output_padding=p2,
|
|
127
|
-
use_bias=True,
|
|
128
|
-
kernel_initializer=keras.initializers.Constant(weight[..., i:i + 1]),
|
|
129
|
-
bias_initializer=keras.initializers.Constant(bias[i])) for i in range(c1)]
|
|
130
|
-
|
|
131
|
-
def call(self, inputs):
|
|
132
|
-
return tf.concat([m(x) for m, x in zip(self.conv, tf.split(inputs, self.c1, 3))], 3)[:, 1:-1, 1:-1]
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
class TFFocus(keras.layers.Layer):
|
|
136
|
-
# Focus wh information into c-space
|
|
137
|
-
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, w=None):
|
|
138
|
-
# ch_in, ch_out, kernel, stride, padding, groups
|
|
139
|
-
super().__init__()
|
|
140
|
-
self.conv = TFConv(c1 * 4, c2, k, s, p, g, act, w.conv)
|
|
141
|
-
|
|
142
|
-
def call(self, inputs): # x(b,w,h,c) -> y(b,w/2,h/2,4c)
|
|
143
|
-
# inputs = inputs / 255 # normalize 0-255 to 0-1
|
|
144
|
-
inputs = [inputs[:, ::2, ::2, :], inputs[:, 1::2, ::2, :], inputs[:, ::2, 1::2, :], inputs[:, 1::2, 1::2, :]]
|
|
145
|
-
return self.conv(tf.concat(inputs, 3))
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
class TFBottleneck(keras.layers.Layer):
|
|
149
|
-
# Standard bottleneck
|
|
150
|
-
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5, w=None): # ch_in, ch_out, shortcut, groups, expansion
|
|
151
|
-
super().__init__()
|
|
152
|
-
c_ = int(c2 * e) # hidden channels
|
|
153
|
-
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
|
154
|
-
self.cv2 = TFConv(c_, c2, 3, 1, g=g, w=w.cv2)
|
|
155
|
-
self.add = shortcut and c1 == c2
|
|
156
|
-
|
|
157
|
-
def call(self, inputs):
|
|
158
|
-
return inputs + self.cv2(self.cv1(inputs)) if self.add else self.cv2(self.cv1(inputs))
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
class TFCrossConv(keras.layers.Layer):
|
|
162
|
-
# Cross Convolution
|
|
163
|
-
def __init__(self, c1, c2, k=3, s=1, g=1, e=1.0, shortcut=False, w=None):
|
|
164
|
-
super().__init__()
|
|
165
|
-
c_ = int(c2 * e) # hidden channels
|
|
166
|
-
self.cv1 = TFConv(c1, c_, (1, k), (1, s), w=w.cv1)
|
|
167
|
-
self.cv2 = TFConv(c_, c2, (k, 1), (s, 1), g=g, w=w.cv2)
|
|
168
|
-
self.add = shortcut and c1 == c2
|
|
169
|
-
|
|
170
|
-
def call(self, inputs):
|
|
171
|
-
return inputs + self.cv2(self.cv1(inputs)) if self.add else self.cv2(self.cv1(inputs))
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
class TFConv2d(keras.layers.Layer):
|
|
175
|
-
# Substitution for PyTorch nn.Conv2D
|
|
176
|
-
def __init__(self, c1, c2, k, s=1, g=1, bias=True, w=None):
|
|
177
|
-
super().__init__()
|
|
178
|
-
assert g == 1, "TF v2.2 Conv2D does not support 'groups' argument"
|
|
179
|
-
self.conv = keras.layers.Conv2D(filters=c2,
|
|
180
|
-
kernel_size=k,
|
|
181
|
-
strides=s,
|
|
182
|
-
padding='VALID',
|
|
183
|
-
use_bias=bias,
|
|
184
|
-
kernel_initializer=keras.initializers.Constant(
|
|
185
|
-
w.weight.permute(2, 3, 1, 0).numpy()),
|
|
186
|
-
bias_initializer=keras.initializers.Constant(w.bias.numpy()) if bias else None)
|
|
187
|
-
|
|
188
|
-
def call(self, inputs):
|
|
189
|
-
return self.conv(inputs)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
class TFBottleneckCSP(keras.layers.Layer):
|
|
193
|
-
# CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
|
|
194
|
-
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
|
|
195
|
-
# ch_in, ch_out, number, shortcut, groups, expansion
|
|
196
|
-
super().__init__()
|
|
197
|
-
c_ = int(c2 * e) # hidden channels
|
|
198
|
-
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
|
199
|
-
self.cv2 = TFConv2d(c1, c_, 1, 1, bias=False, w=w.cv2)
|
|
200
|
-
self.cv3 = TFConv2d(c_, c_, 1, 1, bias=False, w=w.cv3)
|
|
201
|
-
self.cv4 = TFConv(2 * c_, c2, 1, 1, w=w.cv4)
|
|
202
|
-
self.bn = TFBN(w.bn)
|
|
203
|
-
self.act = lambda x: keras.activations.swish(x)
|
|
204
|
-
self.m = keras.Sequential([TFBottleneck(c_, c_, shortcut, g, e=1.0, w=w.m[j]) for j in range(n)])
|
|
205
|
-
|
|
206
|
-
def call(self, inputs):
|
|
207
|
-
y1 = self.cv3(self.m(self.cv1(inputs)))
|
|
208
|
-
y2 = self.cv2(inputs)
|
|
209
|
-
return self.cv4(self.act(self.bn(tf.concat((y1, y2), axis=3))))
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
class TFC3(keras.layers.Layer):
|
|
213
|
-
# CSP Bottleneck with 3 convolutions
|
|
214
|
-
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
|
|
215
|
-
# ch_in, ch_out, number, shortcut, groups, expansion
|
|
216
|
-
super().__init__()
|
|
217
|
-
c_ = int(c2 * e) # hidden channels
|
|
218
|
-
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
|
219
|
-
self.cv2 = TFConv(c1, c_, 1, 1, w=w.cv2)
|
|
220
|
-
self.cv3 = TFConv(2 * c_, c2, 1, 1, w=w.cv3)
|
|
221
|
-
self.m = keras.Sequential([TFBottleneck(c_, c_, shortcut, g, e=1.0, w=w.m[j]) for j in range(n)])
|
|
222
|
-
|
|
223
|
-
def call(self, inputs):
|
|
224
|
-
return self.cv3(tf.concat((self.m(self.cv1(inputs)), self.cv2(inputs)), axis=3))
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
class TFC3x(keras.layers.Layer):
|
|
228
|
-
# 3 module with cross-convolutions
|
|
229
|
-
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
|
|
230
|
-
# ch_in, ch_out, number, shortcut, groups, expansion
|
|
231
|
-
super().__init__()
|
|
232
|
-
c_ = int(c2 * e) # hidden channels
|
|
233
|
-
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
|
234
|
-
self.cv2 = TFConv(c1, c_, 1, 1, w=w.cv2)
|
|
235
|
-
self.cv3 = TFConv(2 * c_, c2, 1, 1, w=w.cv3)
|
|
236
|
-
self.m = keras.Sequential([
|
|
237
|
-
TFCrossConv(c_, c_, k=3, s=1, g=g, e=1.0, shortcut=shortcut, w=w.m[j]) for j in range(n)])
|
|
238
|
-
|
|
239
|
-
def call(self, inputs):
|
|
240
|
-
return self.cv3(tf.concat((self.m(self.cv1(inputs)), self.cv2(inputs)), axis=3))
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
class TFSPP(keras.layers.Layer):
|
|
244
|
-
# Spatial pyramid pooling layer used in YOLOv3-SPP
|
|
245
|
-
def __init__(self, c1, c2, k=(5, 9, 13), w=None):
|
|
246
|
-
super().__init__()
|
|
247
|
-
c_ = c1 // 2 # hidden channels
|
|
248
|
-
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
|
249
|
-
self.cv2 = TFConv(c_ * (len(k) + 1), c2, 1, 1, w=w.cv2)
|
|
250
|
-
self.m = [keras.layers.MaxPool2D(pool_size=x, strides=1, padding='SAME') for x in k]
|
|
251
|
-
|
|
252
|
-
def call(self, inputs):
|
|
253
|
-
x = self.cv1(inputs)
|
|
254
|
-
return self.cv2(tf.concat([x] + [m(x) for m in self.m], 3))
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
class TFSPPF(keras.layers.Layer):
|
|
258
|
-
# Spatial pyramid pooling-Fast layer
|
|
259
|
-
def __init__(self, c1, c2, k=5, w=None):
|
|
260
|
-
super().__init__()
|
|
261
|
-
c_ = c1 // 2 # hidden channels
|
|
262
|
-
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
|
263
|
-
self.cv2 = TFConv(c_ * 4, c2, 1, 1, w=w.cv2)
|
|
264
|
-
self.m = keras.layers.MaxPool2D(pool_size=k, strides=1, padding='SAME')
|
|
265
|
-
|
|
266
|
-
def call(self, inputs):
|
|
267
|
-
x = self.cv1(inputs)
|
|
268
|
-
y1 = self.m(x)
|
|
269
|
-
y2 = self.m(y1)
|
|
270
|
-
return self.cv2(tf.concat([x, y1, y2, self.m(y2)], 3))
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
class TFDetect(keras.layers.Layer):
|
|
274
|
-
# TF YOLOv5 Detect layer
|
|
275
|
-
def __init__(self, nc=80, anchors=(), ch=(), imgsz=(640, 640), w=None): # detection layer
|
|
276
|
-
super().__init__()
|
|
277
|
-
self.stride = tf.convert_to_tensor(w.stride.numpy(), dtype=tf.float32)
|
|
278
|
-
self.nc = nc # number of classes
|
|
279
|
-
self.no = nc + 5 # number of outputs per anchor
|
|
280
|
-
self.nl = len(anchors) # number of detection layers
|
|
281
|
-
self.na = len(anchors[0]) // 2 # number of anchors
|
|
282
|
-
self.grid = [tf.zeros(1)] * self.nl # init grid
|
|
283
|
-
self.anchors = tf.convert_to_tensor(w.anchors.numpy(), dtype=tf.float32)
|
|
284
|
-
self.anchor_grid = tf.reshape(self.anchors * tf.reshape(self.stride, [self.nl, 1, 1]), [self.nl, 1, -1, 1, 2])
|
|
285
|
-
self.m = [TFConv2d(x, self.no * self.na, 1, w=w.m[i]) for i, x in enumerate(ch)]
|
|
286
|
-
self.training = False # set to False after building model
|
|
287
|
-
self.imgsz = imgsz
|
|
288
|
-
for i in range(self.nl):
|
|
289
|
-
ny, nx = self.imgsz[0] // self.stride[i], self.imgsz[1] // self.stride[i]
|
|
290
|
-
self.grid[i] = self._make_grid(nx, ny)
|
|
291
|
-
|
|
292
|
-
def call(self, inputs):
|
|
293
|
-
z = [] # inference output
|
|
294
|
-
x = []
|
|
295
|
-
for i in range(self.nl):
|
|
296
|
-
x.append(self.m[i](inputs[i]))
|
|
297
|
-
# x(bs,20,20,255) to x(bs,3,20,20,85)
|
|
298
|
-
ny, nx = self.imgsz[0] // self.stride[i], self.imgsz[1] // self.stride[i]
|
|
299
|
-
x[i] = tf.reshape(x[i], [-1, ny * nx, self.na, self.no])
|
|
300
|
-
|
|
301
|
-
if not self.training: # inference
|
|
302
|
-
y = tf.sigmoid(x[i])
|
|
303
|
-
grid = tf.transpose(self.grid[i], [0, 2, 1, 3]) - 0.5
|
|
304
|
-
anchor_grid = tf.transpose(self.anchor_grid[i], [0, 2, 1, 3]) * 4
|
|
305
|
-
xy = (y[..., 0:2] * 2 + grid) * self.stride[i] # xy
|
|
306
|
-
wh = y[..., 2:4] ** 2 * anchor_grid
|
|
307
|
-
# Normalize xywh to 0-1 to reduce calibration error
|
|
308
|
-
xy /= tf.constant([[self.imgsz[1], self.imgsz[0]]], dtype=tf.float32)
|
|
309
|
-
wh /= tf.constant([[self.imgsz[1], self.imgsz[0]]], dtype=tf.float32)
|
|
310
|
-
y = tf.concat([xy, wh, y[..., 4:]], -1)
|
|
311
|
-
z.append(tf.reshape(y, [-1, self.na * ny * nx, self.no]))
|
|
312
|
-
|
|
313
|
-
return tf.transpose(x, [0, 2, 1, 3]) if self.training else (tf.concat(z, 1), x)
|
|
314
|
-
|
|
315
|
-
@staticmethod
|
|
316
|
-
def _make_grid(nx=20, ny=20):
|
|
317
|
-
# yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)])
|
|
318
|
-
# return torch.stack((xv, yv), 2).view((1, 1, ny, nx, 2)).float()
|
|
319
|
-
xv, yv = tf.meshgrid(tf.range(nx), tf.range(ny))
|
|
320
|
-
return tf.cast(tf.reshape(tf.stack([xv, yv], 2), [1, 1, ny * nx, 2]), dtype=tf.float32)
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
class TFUpsample(keras.layers.Layer):
|
|
324
|
-
# TF version of torch.nn.Upsample()
|
|
325
|
-
def __init__(self, size, scale_factor, mode, w=None): # warning: all arguments needed including 'w'
|
|
326
|
-
super().__init__()
|
|
327
|
-
assert scale_factor == 2, "scale_factor must be 2"
|
|
328
|
-
self.upsample = lambda x: tf.image.resize(x, (x.shape[1] * 2, x.shape[2] * 2), method=mode)
|
|
329
|
-
# self.upsample = keras.layers.UpSampling2D(size=scale_factor, interpolation=mode)
|
|
330
|
-
# with default arguments: align_corners=False, half_pixel_centers=False
|
|
331
|
-
# self.upsample = lambda x: tf.raw_ops.ResizeNearestNeighbor(images=x,
|
|
332
|
-
# size=(x.shape[1] * 2, x.shape[2] * 2))
|
|
333
|
-
|
|
334
|
-
def call(self, inputs):
|
|
335
|
-
return self.upsample(inputs)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
class TFConcat(keras.layers.Layer):
|
|
339
|
-
# TF version of torch.concat()
|
|
340
|
-
def __init__(self, dimension=1, w=None):
|
|
341
|
-
super().__init__()
|
|
342
|
-
assert dimension == 1, "convert only NCHW to NHWC concat"
|
|
343
|
-
self.d = 3
|
|
344
|
-
|
|
345
|
-
def call(self, inputs):
|
|
346
|
-
return tf.concat(inputs, self.d)
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
def parse_model(d, ch, model, imgsz): # model_dict, input_channels(3)
|
|
350
|
-
LOGGER.info(f"\n{'':>3}{'from':>18}{'n':>3}{'params':>10} {'module':<40}{'arguments':<30}")
|
|
351
|
-
anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple']
|
|
352
|
-
na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors # number of anchors
|
|
353
|
-
no = na * (nc + 5) # number of outputs = anchors * (classes + 5)
|
|
354
|
-
|
|
355
|
-
layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out
|
|
356
|
-
for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): # from, number, module, args
|
|
357
|
-
m_str = m
|
|
358
|
-
m = eval(m) if isinstance(m, str) else m # eval strings
|
|
359
|
-
for j, a in enumerate(args):
|
|
360
|
-
try:
|
|
361
|
-
args[j] = eval(a) if isinstance(a, str) else a # eval strings
|
|
362
|
-
except NameError:
|
|
363
|
-
pass
|
|
364
|
-
|
|
365
|
-
n = max(round(n * gd), 1) if n > 1 else n # depth gain
|
|
366
|
-
if m in [
|
|
367
|
-
nn.Conv2d, Conv, DWConv, DWConvTranspose2d, Bottleneck, SPP, SPPF, MixConv2d, Focus, CrossConv,
|
|
368
|
-
BottleneckCSP, C3, C3x]:
|
|
369
|
-
c1, c2 = ch[f], args[0]
|
|
370
|
-
c2 = make_divisible(c2 * gw, 8) if c2 != no else c2
|
|
371
|
-
|
|
372
|
-
args = [c1, c2, *args[1:]]
|
|
373
|
-
if m in [BottleneckCSP, C3, C3x]:
|
|
374
|
-
args.insert(2, n)
|
|
375
|
-
n = 1
|
|
376
|
-
elif m is nn.BatchNorm2d:
|
|
377
|
-
args = [ch[f]]
|
|
378
|
-
elif m is Concat:
|
|
379
|
-
c2 = sum(ch[-1 if x == -1 else x + 1] for x in f)
|
|
380
|
-
elif m is Detect:
|
|
381
|
-
args.append([ch[x + 1] for x in f])
|
|
382
|
-
if isinstance(args[1], int): # number of anchors
|
|
383
|
-
args[1] = [list(range(args[1] * 2))] * len(f)
|
|
384
|
-
args.append(imgsz)
|
|
385
|
-
else:
|
|
386
|
-
c2 = ch[f]
|
|
387
|
-
|
|
388
|
-
tf_m = eval('TF' + m_str.replace('nn.', ''))
|
|
389
|
-
m_ = keras.Sequential([tf_m(*args, w=model.model[i][j]) for j in range(n)]) if n > 1 \
|
|
390
|
-
else tf_m(*args, w=model.model[i]) # module
|
|
391
|
-
|
|
392
|
-
torch_m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args) # module
|
|
393
|
-
t = str(m)[8:-2].replace('__main__.', '') # module type
|
|
394
|
-
np = sum(x.numel() for x in torch_m_.parameters()) # number params
|
|
395
|
-
m_.i, m_.f, m_.type, m_.np = i, f, t, np # attach index, 'from' index, type, number params
|
|
396
|
-
LOGGER.info(f'{i:>3}{str(f):>18}{str(n):>3}{np:>10} {t:<40}{str(args):<30}') # print
|
|
397
|
-
save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist
|
|
398
|
-
layers.append(m_)
|
|
399
|
-
ch.append(c2)
|
|
400
|
-
return keras.Sequential(layers), sorted(save)
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
class TFModel:
|
|
404
|
-
# TF YOLOv5 model
|
|
405
|
-
def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, model=None, imgsz=(640, 640)): # model, channels, classes
|
|
406
|
-
super().__init__()
|
|
407
|
-
if isinstance(cfg, dict):
|
|
408
|
-
self.yaml = cfg # model dict
|
|
409
|
-
else: # is *.yaml
|
|
410
|
-
import yaml # for torch hub
|
|
411
|
-
self.yaml_file = Path(cfg).name
|
|
412
|
-
with open(cfg) as f:
|
|
413
|
-
self.yaml = yaml.load(f, Loader=yaml.FullLoader) # model dict
|
|
414
|
-
|
|
415
|
-
# Define model
|
|
416
|
-
if nc and nc != self.yaml['nc']:
|
|
417
|
-
LOGGER.info(f"Overriding {cfg} nc={self.yaml['nc']} with nc={nc}")
|
|
418
|
-
self.yaml['nc'] = nc # override yaml value
|
|
419
|
-
self.model, self.savelist = parse_model(deepcopy(self.yaml), ch=[ch], model=model, imgsz=imgsz)
|
|
420
|
-
|
|
421
|
-
def predict(self,
|
|
422
|
-
inputs,
|
|
423
|
-
tf_nms=False,
|
|
424
|
-
agnostic_nms=False,
|
|
425
|
-
topk_per_class=100,
|
|
426
|
-
topk_all=100,
|
|
427
|
-
iou_thres=0.45,
|
|
428
|
-
conf_thres=0.25):
|
|
429
|
-
y = [] # outputs
|
|
430
|
-
x = inputs
|
|
431
|
-
for m in self.model.layers:
|
|
432
|
-
if m.f != -1: # if not from previous layer
|
|
433
|
-
x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f] # from earlier layers
|
|
434
|
-
|
|
435
|
-
x = m(x) # run
|
|
436
|
-
y.append(x if m.i in self.savelist else None) # save output
|
|
437
|
-
|
|
438
|
-
# Add TensorFlow NMS
|
|
439
|
-
if tf_nms:
|
|
440
|
-
boxes = self._xywh2xyxy(x[0][..., :4])
|
|
441
|
-
probs = x[0][:, :, 4:5]
|
|
442
|
-
classes = x[0][:, :, 5:]
|
|
443
|
-
scores = probs * classes
|
|
444
|
-
if agnostic_nms:
|
|
445
|
-
nms = AgnosticNMS()((boxes, classes, scores), topk_all, iou_thres, conf_thres)
|
|
446
|
-
else:
|
|
447
|
-
boxes = tf.expand_dims(boxes, 2)
|
|
448
|
-
nms = tf.image.combined_non_max_suppression(boxes,
|
|
449
|
-
scores,
|
|
450
|
-
topk_per_class,
|
|
451
|
-
topk_all,
|
|
452
|
-
iou_thres,
|
|
453
|
-
conf_thres,
|
|
454
|
-
clip_boxes=False)
|
|
455
|
-
return nms, x[1]
|
|
456
|
-
return x[0] # output only first tensor [1,6300,85] = [xywh, conf, class0, class1, ...]
|
|
457
|
-
# x = x[0][0] # [x(1,6300,85), ...] to x(6300,85)
|
|
458
|
-
# xywh = x[..., :4] # x(6300,4) boxes
|
|
459
|
-
# conf = x[..., 4:5] # x(6300,1) confidences
|
|
460
|
-
# cls = tf.reshape(tf.cast(tf.argmax(x[..., 5:], axis=1), tf.float32), (-1, 1)) # x(6300,1) classes
|
|
461
|
-
# return tf.concat([conf, cls, xywh], 1)
|
|
462
|
-
|
|
463
|
-
@staticmethod
|
|
464
|
-
def _xywh2xyxy(xywh):
|
|
465
|
-
# Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
|
|
466
|
-
x, y, w, h = tf.split(xywh, num_or_size_splits=4, axis=-1)
|
|
467
|
-
return tf.concat([x - w / 2, y - h / 2, x + w / 2, y + h / 2], axis=-1)
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
class AgnosticNMS(keras.layers.Layer):
|
|
471
|
-
# TF Agnostic NMS
|
|
472
|
-
def call(self, input, topk_all, iou_thres, conf_thres):
|
|
473
|
-
# wrap map_fn to avoid TypeSpec related error https://stackoverflow.com/a/65809989/3036450
|
|
474
|
-
return tf.map_fn(lambda x: self._nms(x, topk_all, iou_thres, conf_thres),
|
|
475
|
-
input,
|
|
476
|
-
fn_output_signature=(tf.float32, tf.float32, tf.float32, tf.int32),
|
|
477
|
-
name='agnostic_nms')
|
|
478
|
-
|
|
479
|
-
@staticmethod
|
|
480
|
-
def _nms(x, topk_all=100, iou_thres=0.45, conf_thres=0.25): # agnostic NMS
|
|
481
|
-
boxes, classes, scores = x
|
|
482
|
-
class_inds = tf.cast(tf.argmax(classes, axis=-1), tf.float32)
|
|
483
|
-
scores_inp = tf.reduce_max(scores, -1)
|
|
484
|
-
selected_inds = tf.image.non_max_suppression(boxes,
|
|
485
|
-
scores_inp,
|
|
486
|
-
max_output_size=topk_all,
|
|
487
|
-
iou_threshold=iou_thres,
|
|
488
|
-
score_threshold=conf_thres)
|
|
489
|
-
selected_boxes = tf.gather(boxes, selected_inds)
|
|
490
|
-
padded_boxes = tf.pad(selected_boxes,
|
|
491
|
-
paddings=[[0, topk_all - tf.shape(selected_boxes)[0]], [0, 0]],
|
|
492
|
-
mode="CONSTANT",
|
|
493
|
-
constant_values=0.0)
|
|
494
|
-
selected_scores = tf.gather(scores_inp, selected_inds)
|
|
495
|
-
padded_scores = tf.pad(selected_scores,
|
|
496
|
-
paddings=[[0, topk_all - tf.shape(selected_boxes)[0]]],
|
|
497
|
-
mode="CONSTANT",
|
|
498
|
-
constant_values=-1.0)
|
|
499
|
-
selected_classes = tf.gather(class_inds, selected_inds)
|
|
500
|
-
padded_classes = tf.pad(selected_classes,
|
|
501
|
-
paddings=[[0, topk_all - tf.shape(selected_boxes)[0]]],
|
|
502
|
-
mode="CONSTANT",
|
|
503
|
-
constant_values=-1.0)
|
|
504
|
-
valid_detections = tf.shape(selected_inds)[0]
|
|
505
|
-
return padded_boxes, padded_scores, padded_classes, valid_detections
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
def activations(act=nn.SiLU):
|
|
509
|
-
# Returns TF activation from input PyTorch activation
|
|
510
|
-
if isinstance(act, nn.LeakyReLU):
|
|
511
|
-
return lambda x: keras.activations.relu(x, alpha=0.1)
|
|
512
|
-
elif isinstance(act, nn.Hardswish):
|
|
513
|
-
return lambda x: x * tf.nn.relu6(x + 3) * 0.166666667
|
|
514
|
-
elif isinstance(act, (nn.SiLU, SiLU)):
|
|
515
|
-
return lambda x: keras.activations.swish(x)
|
|
516
|
-
else:
|
|
517
|
-
raise Exception(f'no matching TensorFlow activation found for PyTorch activation {act}')
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
def representative_dataset_gen(dataset, ncalib=100):
|
|
521
|
-
# Representative dataset generator for use with converter.representative_dataset, returns a generator of np arrays
|
|
522
|
-
for n, (path, img, im0s, vid_cap, string) in enumerate(dataset):
|
|
523
|
-
im = np.transpose(img, [1, 2, 0])
|
|
524
|
-
im = np.expand_dims(im, axis=0).astype(np.float32)
|
|
525
|
-
im /= 255
|
|
526
|
-
yield [im]
|
|
527
|
-
if n >= ncalib:
|
|
528
|
-
break
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
def run(
|
|
532
|
-
weights=ROOT / 'yolov5s.pt', # weights path
|
|
533
|
-
imgsz=(640, 640), # inference size h,w
|
|
534
|
-
batch_size=1, # batch size
|
|
535
|
-
dynamic=False, # dynamic batch size
|
|
536
|
-
):
|
|
537
|
-
# PyTorch model
|
|
538
|
-
im = torch.zeros((batch_size, 3, *imgsz)) # BCHW image
|
|
539
|
-
model = attempt_load(weights, device=torch.device('cpu'), inplace=True, fuse=False)
|
|
540
|
-
_ = model(im) # inference
|
|
541
|
-
model.info()
|
|
542
|
-
|
|
543
|
-
# TensorFlow model
|
|
544
|
-
im = tf.zeros((batch_size, *imgsz, 3)) # BHWC image
|
|
545
|
-
tf_model = TFModel(cfg=model.yaml, model=model, nc=model.nc, imgsz=imgsz)
|
|
546
|
-
_ = tf_model.predict(im) # inference
|
|
547
|
-
|
|
548
|
-
# Keras model
|
|
549
|
-
im = keras.Input(shape=(*imgsz, 3), batch_size=None if dynamic else batch_size)
|
|
550
|
-
keras_model = keras.Model(inputs=im, outputs=tf_model.predict(im))
|
|
551
|
-
keras_model.summary()
|
|
552
|
-
|
|
553
|
-
LOGGER.info('PyTorch, TensorFlow and Keras models successfully verified.\nUse export.py for TF model export.')
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
def parse_opt():
|
|
557
|
-
parser = argparse.ArgumentParser()
|
|
558
|
-
parser.add_argument('--weights', type=str, default=ROOT / 'yolov5s.pt', help='weights path')
|
|
559
|
-
parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')
|
|
560
|
-
parser.add_argument('--batch-size', type=int, default=1, help='batch size')
|
|
561
|
-
parser.add_argument('--dynamic', action='store_true', help='dynamic batch size')
|
|
562
|
-
opt = parser.parse_args()
|
|
563
|
-
opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand
|
|
564
|
-
print_args(vars(opt))
|
|
565
|
-
return opt
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
def main(opt):
|
|
569
|
-
run(**vars(opt))
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if __name__ == "__main__":
|
|
573
|
-
opt = parse_opt()
|
|
574
|
-
main(opt)
|