bplusplus 1.1.0__py3-none-any.whl → 1.2.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.
Potentially problematic release.
This version of bplusplus might be problematic. Click here for more details.
- bplusplus/__init__.py +4 -2
- bplusplus/collect.py +69 -5
- bplusplus/hierarchical/test.py +670 -0
- bplusplus/hierarchical/train.py +676 -0
- bplusplus/prepare.py +228 -64
- bplusplus/resnet/test.py +473 -0
- bplusplus/resnet/train.py +329 -0
- bplusplus-1.2.0.dist-info/METADATA +249 -0
- bplusplus-1.2.0.dist-info/RECORD +12 -0
- bplusplus/yolov5detect/__init__.py +0 -1
- bplusplus/yolov5detect/detect.py +0 -444
- bplusplus/yolov5detect/export.py +0 -1530
- bplusplus/yolov5detect/insect.yaml +0 -8
- bplusplus/yolov5detect/models/__init__.py +0 -0
- bplusplus/yolov5detect/models/common.py +0 -1109
- bplusplus/yolov5detect/models/experimental.py +0 -130
- bplusplus/yolov5detect/models/hub/anchors.yaml +0 -56
- bplusplus/yolov5detect/models/hub/yolov3-spp.yaml +0 -52
- bplusplus/yolov5detect/models/hub/yolov3-tiny.yaml +0 -42
- bplusplus/yolov5detect/models/hub/yolov3.yaml +0 -52
- bplusplus/yolov5detect/models/hub/yolov5-bifpn.yaml +0 -49
- bplusplus/yolov5detect/models/hub/yolov5-fpn.yaml +0 -43
- bplusplus/yolov5detect/models/hub/yolov5-p2.yaml +0 -55
- bplusplus/yolov5detect/models/hub/yolov5-p34.yaml +0 -42
- bplusplus/yolov5detect/models/hub/yolov5-p6.yaml +0 -57
- bplusplus/yolov5detect/models/hub/yolov5-p7.yaml +0 -68
- bplusplus/yolov5detect/models/hub/yolov5-panet.yaml +0 -49
- bplusplus/yolov5detect/models/hub/yolov5l6.yaml +0 -61
- bplusplus/yolov5detect/models/hub/yolov5m6.yaml +0 -61
- bplusplus/yolov5detect/models/hub/yolov5n6.yaml +0 -61
- bplusplus/yolov5detect/models/hub/yolov5s-LeakyReLU.yaml +0 -50
- bplusplus/yolov5detect/models/hub/yolov5s-ghost.yaml +0 -49
- bplusplus/yolov5detect/models/hub/yolov5s-transformer.yaml +0 -49
- bplusplus/yolov5detect/models/hub/yolov5s6.yaml +0 -61
- bplusplus/yolov5detect/models/hub/yolov5x6.yaml +0 -61
- bplusplus/yolov5detect/models/segment/yolov5l-seg.yaml +0 -49
- bplusplus/yolov5detect/models/segment/yolov5m-seg.yaml +0 -49
- bplusplus/yolov5detect/models/segment/yolov5n-seg.yaml +0 -49
- bplusplus/yolov5detect/models/segment/yolov5s-seg.yaml +0 -49
- bplusplus/yolov5detect/models/segment/yolov5x-seg.yaml +0 -49
- bplusplus/yolov5detect/models/tf.py +0 -797
- bplusplus/yolov5detect/models/yolo.py +0 -495
- bplusplus/yolov5detect/models/yolov5l.yaml +0 -49
- bplusplus/yolov5detect/models/yolov5m.yaml +0 -49
- bplusplus/yolov5detect/models/yolov5n.yaml +0 -49
- bplusplus/yolov5detect/models/yolov5s.yaml +0 -49
- bplusplus/yolov5detect/models/yolov5x.yaml +0 -49
- bplusplus/yolov5detect/utils/__init__.py +0 -97
- bplusplus/yolov5detect/utils/activations.py +0 -134
- bplusplus/yolov5detect/utils/augmentations.py +0 -448
- bplusplus/yolov5detect/utils/autoanchor.py +0 -175
- bplusplus/yolov5detect/utils/autobatch.py +0 -70
- bplusplus/yolov5detect/utils/aws/__init__.py +0 -0
- bplusplus/yolov5detect/utils/aws/mime.sh +0 -26
- bplusplus/yolov5detect/utils/aws/resume.py +0 -41
- bplusplus/yolov5detect/utils/aws/userdata.sh +0 -27
- bplusplus/yolov5detect/utils/callbacks.py +0 -72
- bplusplus/yolov5detect/utils/dataloaders.py +0 -1385
- bplusplus/yolov5detect/utils/docker/Dockerfile +0 -73
- bplusplus/yolov5detect/utils/docker/Dockerfile-arm64 +0 -40
- bplusplus/yolov5detect/utils/docker/Dockerfile-cpu +0 -42
- bplusplus/yolov5detect/utils/downloads.py +0 -136
- bplusplus/yolov5detect/utils/flask_rest_api/README.md +0 -70
- bplusplus/yolov5detect/utils/flask_rest_api/example_request.py +0 -17
- bplusplus/yolov5detect/utils/flask_rest_api/restapi.py +0 -49
- bplusplus/yolov5detect/utils/general.py +0 -1294
- bplusplus/yolov5detect/utils/google_app_engine/Dockerfile +0 -25
- bplusplus/yolov5detect/utils/google_app_engine/additional_requirements.txt +0 -6
- bplusplus/yolov5detect/utils/google_app_engine/app.yaml +0 -16
- bplusplus/yolov5detect/utils/loggers/__init__.py +0 -476
- bplusplus/yolov5detect/utils/loggers/clearml/README.md +0 -222
- bplusplus/yolov5detect/utils/loggers/clearml/__init__.py +0 -0
- bplusplus/yolov5detect/utils/loggers/clearml/clearml_utils.py +0 -230
- bplusplus/yolov5detect/utils/loggers/clearml/hpo.py +0 -90
- bplusplus/yolov5detect/utils/loggers/comet/README.md +0 -250
- bplusplus/yolov5detect/utils/loggers/comet/__init__.py +0 -551
- bplusplus/yolov5detect/utils/loggers/comet/comet_utils.py +0 -151
- bplusplus/yolov5detect/utils/loggers/comet/hpo.py +0 -126
- bplusplus/yolov5detect/utils/loggers/comet/optimizer_config.json +0 -135
- bplusplus/yolov5detect/utils/loggers/wandb/__init__.py +0 -0
- bplusplus/yolov5detect/utils/loggers/wandb/wandb_utils.py +0 -210
- bplusplus/yolov5detect/utils/loss.py +0 -259
- bplusplus/yolov5detect/utils/metrics.py +0 -381
- bplusplus/yolov5detect/utils/plots.py +0 -517
- bplusplus/yolov5detect/utils/segment/__init__.py +0 -0
- bplusplus/yolov5detect/utils/segment/augmentations.py +0 -100
- bplusplus/yolov5detect/utils/segment/dataloaders.py +0 -366
- bplusplus/yolov5detect/utils/segment/general.py +0 -160
- bplusplus/yolov5detect/utils/segment/loss.py +0 -198
- bplusplus/yolov5detect/utils/segment/metrics.py +0 -225
- bplusplus/yolov5detect/utils/segment/plots.py +0 -152
- bplusplus/yolov5detect/utils/torch_utils.py +0 -482
- bplusplus/yolov5detect/utils/triton.py +0 -90
- bplusplus-1.1.0.dist-info/METADATA +0 -179
- bplusplus-1.1.0.dist-info/RECORD +0 -92
- {bplusplus-1.1.0.dist-info → bplusplus-1.2.0.dist-info}/LICENSE +0 -0
- {bplusplus-1.1.0.dist-info → bplusplus-1.2.0.dist-info}/WHEEL +0 -0
|
@@ -1,797 +0,0 @@
|
|
|
1
|
-
# Ultralytics YOLOv5 🚀, AGPL-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 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 (
|
|
31
|
-
C3,
|
|
32
|
-
SPP,
|
|
33
|
-
SPPF,
|
|
34
|
-
Bottleneck,
|
|
35
|
-
BottleneckCSP,
|
|
36
|
-
C3x,
|
|
37
|
-
Concat,
|
|
38
|
-
Conv,
|
|
39
|
-
CrossConv,
|
|
40
|
-
DWConv,
|
|
41
|
-
DWConvTranspose2d,
|
|
42
|
-
Focus,
|
|
43
|
-
autopad,
|
|
44
|
-
)
|
|
45
|
-
from models.experimental import MixConv2d, attempt_load
|
|
46
|
-
from models.yolo import Detect, Segment
|
|
47
|
-
from utils.activations import SiLU
|
|
48
|
-
from utils.general import LOGGER, make_divisible, print_args
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class TFBN(keras.layers.Layer):
|
|
52
|
-
"""TensorFlow BatchNormalization wrapper for initializing with optional pretrained weights."""
|
|
53
|
-
|
|
54
|
-
def __init__(self, w=None):
|
|
55
|
-
"""Initializes a TensorFlow BatchNormalization layer with optional pretrained weights."""
|
|
56
|
-
super().__init__()
|
|
57
|
-
self.bn = keras.layers.BatchNormalization(
|
|
58
|
-
beta_initializer=keras.initializers.Constant(w.bias.numpy()),
|
|
59
|
-
gamma_initializer=keras.initializers.Constant(w.weight.numpy()),
|
|
60
|
-
moving_mean_initializer=keras.initializers.Constant(w.running_mean.numpy()),
|
|
61
|
-
moving_variance_initializer=keras.initializers.Constant(w.running_var.numpy()),
|
|
62
|
-
epsilon=w.eps,
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
def call(self, inputs):
|
|
66
|
-
"""Applies batch normalization to the inputs."""
|
|
67
|
-
return self.bn(inputs)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class TFPad(keras.layers.Layer):
|
|
71
|
-
"""Pads input tensors in spatial dimensions 1 and 2 with specified integer or tuple padding values."""
|
|
72
|
-
|
|
73
|
-
def __init__(self, pad):
|
|
74
|
-
"""
|
|
75
|
-
Initializes a padding layer for spatial dimensions 1 and 2 with specified padding, supporting both int and tuple
|
|
76
|
-
inputs.
|
|
77
|
-
|
|
78
|
-
Inputs are
|
|
79
|
-
"""
|
|
80
|
-
super().__init__()
|
|
81
|
-
if isinstance(pad, int):
|
|
82
|
-
self.pad = tf.constant([[0, 0], [pad, pad], [pad, pad], [0, 0]])
|
|
83
|
-
else: # tuple/list
|
|
84
|
-
self.pad = tf.constant([[0, 0], [pad[0], pad[0]], [pad[1], pad[1]], [0, 0]])
|
|
85
|
-
|
|
86
|
-
def call(self, inputs):
|
|
87
|
-
"""Pads input tensor with zeros using specified padding, suitable for int and tuple pad dimensions."""
|
|
88
|
-
return tf.pad(inputs, self.pad, mode="constant", constant_values=0)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
class TFConv(keras.layers.Layer):
|
|
92
|
-
"""Implements a standard convolutional layer with optional batch normalization and activation for TensorFlow."""
|
|
93
|
-
|
|
94
|
-
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, w=None):
|
|
95
|
-
"""
|
|
96
|
-
Initializes a standard convolution layer with optional batch normalization and activation; supports only
|
|
97
|
-
group=1.
|
|
98
|
-
|
|
99
|
-
Inputs are ch_in, ch_out, weights, kernel, stride, padding, groups.
|
|
100
|
-
"""
|
|
101
|
-
super().__init__()
|
|
102
|
-
assert g == 1, "TF v2.2 Conv2D does not support 'groups' argument"
|
|
103
|
-
# TensorFlow convolution padding is inconsistent with PyTorch (e.g. k=3 s=2 'SAME' padding)
|
|
104
|
-
# see https://stackoverflow.com/questions/52975843/comparing-conv2d-with-padding-between-tensorflow-and-pytorch
|
|
105
|
-
conv = keras.layers.Conv2D(
|
|
106
|
-
filters=c2,
|
|
107
|
-
kernel_size=k,
|
|
108
|
-
strides=s,
|
|
109
|
-
padding="SAME" if s == 1 else "VALID",
|
|
110
|
-
use_bias=not hasattr(w, "bn"),
|
|
111
|
-
kernel_initializer=keras.initializers.Constant(w.conv.weight.permute(2, 3, 1, 0).numpy()),
|
|
112
|
-
bias_initializer="zeros" if hasattr(w, "bn") else keras.initializers.Constant(w.conv.bias.numpy()),
|
|
113
|
-
)
|
|
114
|
-
self.conv = conv if s == 1 else keras.Sequential([TFPad(autopad(k, p)), conv])
|
|
115
|
-
self.bn = TFBN(w.bn) if hasattr(w, "bn") else tf.identity
|
|
116
|
-
self.act = activations(w.act) if act else tf.identity
|
|
117
|
-
|
|
118
|
-
def call(self, inputs):
|
|
119
|
-
"""Applies convolution, batch normalization, and activation function to input tensors."""
|
|
120
|
-
return self.act(self.bn(self.conv(inputs)))
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
class TFDWConv(keras.layers.Layer):
|
|
124
|
-
"""Initializes a depthwise convolution layer with optional batch normalization and activation for TensorFlow."""
|
|
125
|
-
|
|
126
|
-
def __init__(self, c1, c2, k=1, s=1, p=None, act=True, w=None):
|
|
127
|
-
"""
|
|
128
|
-
Initializes a depthwise convolution layer with optional batch normalization and activation for TensorFlow
|
|
129
|
-
models.
|
|
130
|
-
|
|
131
|
-
Input are ch_in, ch_out, weights, kernel, stride, padding, groups.
|
|
132
|
-
"""
|
|
133
|
-
super().__init__()
|
|
134
|
-
assert c2 % c1 == 0, f"TFDWConv() output={c2} must be a multiple of input={c1} channels"
|
|
135
|
-
conv = keras.layers.DepthwiseConv2D(
|
|
136
|
-
kernel_size=k,
|
|
137
|
-
depth_multiplier=c2 // c1,
|
|
138
|
-
strides=s,
|
|
139
|
-
padding="SAME" if s == 1 else "VALID",
|
|
140
|
-
use_bias=not hasattr(w, "bn"),
|
|
141
|
-
depthwise_initializer=keras.initializers.Constant(w.conv.weight.permute(2, 3, 1, 0).numpy()),
|
|
142
|
-
bias_initializer="zeros" if hasattr(w, "bn") else keras.initializers.Constant(w.conv.bias.numpy()),
|
|
143
|
-
)
|
|
144
|
-
self.conv = conv if s == 1 else keras.Sequential([TFPad(autopad(k, p)), conv])
|
|
145
|
-
self.bn = TFBN(w.bn) if hasattr(w, "bn") else tf.identity
|
|
146
|
-
self.act = activations(w.act) if act else tf.identity
|
|
147
|
-
|
|
148
|
-
def call(self, inputs):
|
|
149
|
-
"""Applies convolution, batch normalization, and activation function to input tensors."""
|
|
150
|
-
return self.act(self.bn(self.conv(inputs)))
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
class TFDWConvTranspose2d(keras.layers.Layer):
|
|
154
|
-
"""Implements a depthwise ConvTranspose2D layer for TensorFlow with specific settings."""
|
|
155
|
-
|
|
156
|
-
def __init__(self, c1, c2, k=1, s=1, p1=0, p2=0, w=None):
|
|
157
|
-
"""
|
|
158
|
-
Initializes depthwise ConvTranspose2D layer with specific channel, kernel, stride, and padding settings.
|
|
159
|
-
|
|
160
|
-
Inputs are ch_in, ch_out, weights, kernel, stride, padding, groups.
|
|
161
|
-
"""
|
|
162
|
-
super().__init__()
|
|
163
|
-
assert c1 == c2, f"TFDWConv() output={c2} must be equal to input={c1} channels"
|
|
164
|
-
assert k == 4 and p1 == 1, "TFDWConv() only valid for k=4 and p1=1"
|
|
165
|
-
weight, bias = w.weight.permute(2, 3, 1, 0).numpy(), w.bias.numpy()
|
|
166
|
-
self.c1 = c1
|
|
167
|
-
self.conv = [
|
|
168
|
-
keras.layers.Conv2DTranspose(
|
|
169
|
-
filters=1,
|
|
170
|
-
kernel_size=k,
|
|
171
|
-
strides=s,
|
|
172
|
-
padding="VALID",
|
|
173
|
-
output_padding=p2,
|
|
174
|
-
use_bias=True,
|
|
175
|
-
kernel_initializer=keras.initializers.Constant(weight[..., i : i + 1]),
|
|
176
|
-
bias_initializer=keras.initializers.Constant(bias[i]),
|
|
177
|
-
)
|
|
178
|
-
for i in range(c1)
|
|
179
|
-
]
|
|
180
|
-
|
|
181
|
-
def call(self, inputs):
|
|
182
|
-
"""Processes input through parallel convolutions and concatenates results, trimming border pixels."""
|
|
183
|
-
return tf.concat([m(x) for m, x in zip(self.conv, tf.split(inputs, self.c1, 3))], 3)[:, 1:-1, 1:-1]
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
class TFFocus(keras.layers.Layer):
|
|
187
|
-
"""Focuses spatial information into channel space using pixel shuffling and convolution for TensorFlow models."""
|
|
188
|
-
|
|
189
|
-
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, w=None):
|
|
190
|
-
"""
|
|
191
|
-
Initializes TFFocus layer to focus width and height information into channel space with custom convolution
|
|
192
|
-
parameters.
|
|
193
|
-
|
|
194
|
-
Inputs are ch_in, ch_out, kernel, stride, padding, groups.
|
|
195
|
-
"""
|
|
196
|
-
super().__init__()
|
|
197
|
-
self.conv = TFConv(c1 * 4, c2, k, s, p, g, act, w.conv)
|
|
198
|
-
|
|
199
|
-
def call(self, inputs):
|
|
200
|
-
"""
|
|
201
|
-
Performs pixel shuffling and convolution on input tensor, downsampling by 2 and expanding channels by 4.
|
|
202
|
-
|
|
203
|
-
Example x(b,w,h,c) -> y(b,w/2,h/2,4c).
|
|
204
|
-
"""
|
|
205
|
-
inputs = [inputs[:, ::2, ::2, :], inputs[:, 1::2, ::2, :], inputs[:, ::2, 1::2, :], inputs[:, 1::2, 1::2, :]]
|
|
206
|
-
return self.conv(tf.concat(inputs, 3))
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
class TFBottleneck(keras.layers.Layer):
|
|
210
|
-
"""Implements a TensorFlow bottleneck layer with optional shortcut connections for efficient feature extraction."""
|
|
211
|
-
|
|
212
|
-
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5, w=None):
|
|
213
|
-
"""
|
|
214
|
-
Initializes a standard bottleneck layer for TensorFlow models, expanding and contracting channels with optional
|
|
215
|
-
shortcut.
|
|
216
|
-
|
|
217
|
-
Arguments are ch_in, ch_out, shortcut, groups, expansion.
|
|
218
|
-
"""
|
|
219
|
-
super().__init__()
|
|
220
|
-
c_ = int(c2 * e) # hidden channels
|
|
221
|
-
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
|
222
|
-
self.cv2 = TFConv(c_, c2, 3, 1, g=g, w=w.cv2)
|
|
223
|
-
self.add = shortcut and c1 == c2
|
|
224
|
-
|
|
225
|
-
def call(self, inputs):
|
|
226
|
-
"""Performs forward pass; if shortcut is True & input/output channels match, adds input to the convolution
|
|
227
|
-
result.
|
|
228
|
-
"""
|
|
229
|
-
return inputs + self.cv2(self.cv1(inputs)) if self.add else self.cv2(self.cv1(inputs))
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
class TFCrossConv(keras.layers.Layer):
|
|
233
|
-
"""Implements a cross convolutional layer with optional expansion, grouping, and shortcut for TensorFlow."""
|
|
234
|
-
|
|
235
|
-
def __init__(self, c1, c2, k=3, s=1, g=1, e=1.0, shortcut=False, w=None):
|
|
236
|
-
"""Initializes cross convolution layer with optional expansion, grouping, and shortcut addition capabilities."""
|
|
237
|
-
super().__init__()
|
|
238
|
-
c_ = int(c2 * e) # hidden channels
|
|
239
|
-
self.cv1 = TFConv(c1, c_, (1, k), (1, s), w=w.cv1)
|
|
240
|
-
self.cv2 = TFConv(c_, c2, (k, 1), (s, 1), g=g, w=w.cv2)
|
|
241
|
-
self.add = shortcut and c1 == c2
|
|
242
|
-
|
|
243
|
-
def call(self, inputs):
|
|
244
|
-
"""Passes input through two convolutions optionally adding the input if channel dimensions match."""
|
|
245
|
-
return inputs + self.cv2(self.cv1(inputs)) if self.add else self.cv2(self.cv1(inputs))
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
class TFConv2d(keras.layers.Layer):
|
|
249
|
-
"""Implements a TensorFlow 2D convolution layer, mimicking PyTorch's nn.Conv2D for specified filters and stride."""
|
|
250
|
-
|
|
251
|
-
def __init__(self, c1, c2, k, s=1, g=1, bias=True, w=None):
|
|
252
|
-
"""Initializes a TensorFlow 2D convolution layer, mimicking PyTorch's nn.Conv2D functionality for given filter
|
|
253
|
-
sizes and stride.
|
|
254
|
-
"""
|
|
255
|
-
super().__init__()
|
|
256
|
-
assert g == 1, "TF v2.2 Conv2D does not support 'groups' argument"
|
|
257
|
-
self.conv = keras.layers.Conv2D(
|
|
258
|
-
filters=c2,
|
|
259
|
-
kernel_size=k,
|
|
260
|
-
strides=s,
|
|
261
|
-
padding="VALID",
|
|
262
|
-
use_bias=bias,
|
|
263
|
-
kernel_initializer=keras.initializers.Constant(w.weight.permute(2, 3, 1, 0).numpy()),
|
|
264
|
-
bias_initializer=keras.initializers.Constant(w.bias.numpy()) if bias else None,
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
def call(self, inputs):
|
|
268
|
-
"""Applies a convolution operation to the inputs and returns the result."""
|
|
269
|
-
return self.conv(inputs)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
class TFBottleneckCSP(keras.layers.Layer):
|
|
273
|
-
"""Implements a CSP bottleneck layer for TensorFlow models to enhance gradient flow and efficiency."""
|
|
274
|
-
|
|
275
|
-
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
|
|
276
|
-
"""
|
|
277
|
-
Initializes CSP bottleneck layer with specified channel sizes, count, shortcut option, groups, and expansion
|
|
278
|
-
ratio.
|
|
279
|
-
|
|
280
|
-
Inputs are ch_in, ch_out, number, shortcut, groups, expansion.
|
|
281
|
-
"""
|
|
282
|
-
super().__init__()
|
|
283
|
-
c_ = int(c2 * e) # hidden channels
|
|
284
|
-
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
|
285
|
-
self.cv2 = TFConv2d(c1, c_, 1, 1, bias=False, w=w.cv2)
|
|
286
|
-
self.cv3 = TFConv2d(c_, c_, 1, 1, bias=False, w=w.cv3)
|
|
287
|
-
self.cv4 = TFConv(2 * c_, c2, 1, 1, w=w.cv4)
|
|
288
|
-
self.bn = TFBN(w.bn)
|
|
289
|
-
self.act = lambda x: keras.activations.swish(x)
|
|
290
|
-
self.m = keras.Sequential([TFBottleneck(c_, c_, shortcut, g, e=1.0, w=w.m[j]) for j in range(n)])
|
|
291
|
-
|
|
292
|
-
def call(self, inputs):
|
|
293
|
-
"""Processes input through the model layers, concatenates, normalizes, activates, and reduces the output
|
|
294
|
-
dimensions.
|
|
295
|
-
"""
|
|
296
|
-
y1 = self.cv3(self.m(self.cv1(inputs)))
|
|
297
|
-
y2 = self.cv2(inputs)
|
|
298
|
-
return self.cv4(self.act(self.bn(tf.concat((y1, y2), axis=3))))
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
class TFC3(keras.layers.Layer):
|
|
302
|
-
"""CSP bottleneck layer with 3 convolutions for TensorFlow, supporting optional shortcuts and group convolutions."""
|
|
303
|
-
|
|
304
|
-
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
|
|
305
|
-
"""
|
|
306
|
-
Initializes CSP Bottleneck with 3 convolutions, supporting optional shortcuts and group convolutions.
|
|
307
|
-
|
|
308
|
-
Inputs are ch_in, ch_out, number, shortcut, groups, expansion.
|
|
309
|
-
"""
|
|
310
|
-
super().__init__()
|
|
311
|
-
c_ = int(c2 * e) # hidden channels
|
|
312
|
-
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
|
313
|
-
self.cv2 = TFConv(c1, c_, 1, 1, w=w.cv2)
|
|
314
|
-
self.cv3 = TFConv(2 * c_, c2, 1, 1, w=w.cv3)
|
|
315
|
-
self.m = keras.Sequential([TFBottleneck(c_, c_, shortcut, g, e=1.0, w=w.m[j]) for j in range(n)])
|
|
316
|
-
|
|
317
|
-
def call(self, inputs):
|
|
318
|
-
"""
|
|
319
|
-
Processes input through a sequence of transformations for object detection (YOLOv5).
|
|
320
|
-
|
|
321
|
-
See https://github.com/ultralytics/yolov5.
|
|
322
|
-
"""
|
|
323
|
-
return self.cv3(tf.concat((self.m(self.cv1(inputs)), self.cv2(inputs)), axis=3))
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
class TFC3x(keras.layers.Layer):
|
|
327
|
-
"""A TensorFlow layer for enhanced feature extraction using cross-convolutions in object detection models."""
|
|
328
|
-
|
|
329
|
-
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
|
|
330
|
-
"""
|
|
331
|
-
Initializes layer with cross-convolutions for enhanced feature extraction in object detection models.
|
|
332
|
-
|
|
333
|
-
Inputs are ch_in, ch_out, number, shortcut, groups, expansion.
|
|
334
|
-
"""
|
|
335
|
-
super().__init__()
|
|
336
|
-
c_ = int(c2 * e) # hidden channels
|
|
337
|
-
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
|
338
|
-
self.cv2 = TFConv(c1, c_, 1, 1, w=w.cv2)
|
|
339
|
-
self.cv3 = TFConv(2 * c_, c2, 1, 1, w=w.cv3)
|
|
340
|
-
self.m = keras.Sequential(
|
|
341
|
-
[TFCrossConv(c_, c_, k=3, s=1, g=g, e=1.0, shortcut=shortcut, w=w.m[j]) for j in range(n)]
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
def call(self, inputs):
|
|
345
|
-
"""Processes input through cascaded convolutions and merges features, returning the final tensor output."""
|
|
346
|
-
return self.cv3(tf.concat((self.m(self.cv1(inputs)), self.cv2(inputs)), axis=3))
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
class TFSPP(keras.layers.Layer):
|
|
350
|
-
"""Implements spatial pyramid pooling for YOLOv3-SPP with specific channels and kernel sizes."""
|
|
351
|
-
|
|
352
|
-
def __init__(self, c1, c2, k=(5, 9, 13), w=None):
|
|
353
|
-
"""Initializes a YOLOv3-SPP layer with specific input/output channels and kernel sizes for pooling."""
|
|
354
|
-
super().__init__()
|
|
355
|
-
c_ = c1 // 2 # hidden channels
|
|
356
|
-
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
|
357
|
-
self.cv2 = TFConv(c_ * (len(k) + 1), c2, 1, 1, w=w.cv2)
|
|
358
|
-
self.m = [keras.layers.MaxPool2D(pool_size=x, strides=1, padding="SAME") for x in k]
|
|
359
|
-
|
|
360
|
-
def call(self, inputs):
|
|
361
|
-
"""Processes input through two TFConv layers and concatenates with max-pooled outputs at intermediate stage."""
|
|
362
|
-
x = self.cv1(inputs)
|
|
363
|
-
return self.cv2(tf.concat([x] + [m(x) for m in self.m], 3))
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
class TFSPPF(keras.layers.Layer):
|
|
367
|
-
"""Implements a fast spatial pyramid pooling layer for TensorFlow with optimized feature extraction."""
|
|
368
|
-
|
|
369
|
-
def __init__(self, c1, c2, k=5, w=None):
|
|
370
|
-
"""Initializes a fast spatial pyramid pooling layer with customizable in/out channels, kernel size, and
|
|
371
|
-
weights.
|
|
372
|
-
"""
|
|
373
|
-
super().__init__()
|
|
374
|
-
c_ = c1 // 2 # hidden channels
|
|
375
|
-
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
|
376
|
-
self.cv2 = TFConv(c_ * 4, c2, 1, 1, w=w.cv2)
|
|
377
|
-
self.m = keras.layers.MaxPool2D(pool_size=k, strides=1, padding="SAME")
|
|
378
|
-
|
|
379
|
-
def call(self, inputs):
|
|
380
|
-
"""Executes the model's forward pass, concatenating input features with three max-pooled versions before final
|
|
381
|
-
convolution.
|
|
382
|
-
"""
|
|
383
|
-
x = self.cv1(inputs)
|
|
384
|
-
y1 = self.m(x)
|
|
385
|
-
y2 = self.m(y1)
|
|
386
|
-
return self.cv2(tf.concat([x, y1, y2, self.m(y2)], 3))
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
class TFDetect(keras.layers.Layer):
|
|
390
|
-
"""Implements YOLOv5 object detection layer in TensorFlow for predicting bounding boxes and class probabilities."""
|
|
391
|
-
|
|
392
|
-
def __init__(self, nc=80, anchors=(), ch=(), imgsz=(640, 640), w=None):
|
|
393
|
-
"""Initializes YOLOv5 detection layer for TensorFlow with configurable classes, anchors, channels, and image
|
|
394
|
-
size.
|
|
395
|
-
"""
|
|
396
|
-
super().__init__()
|
|
397
|
-
self.stride = tf.convert_to_tensor(w.stride.numpy(), dtype=tf.float32)
|
|
398
|
-
self.nc = nc # number of classes
|
|
399
|
-
self.no = nc + 5 # number of outputs per anchor
|
|
400
|
-
self.nl = len(anchors) # number of detection layers
|
|
401
|
-
self.na = len(anchors[0]) // 2 # number of anchors
|
|
402
|
-
self.grid = [tf.zeros(1)] * self.nl # init grid
|
|
403
|
-
self.anchors = tf.convert_to_tensor(w.anchors.numpy(), dtype=tf.float32)
|
|
404
|
-
self.anchor_grid = tf.reshape(self.anchors * tf.reshape(self.stride, [self.nl, 1, 1]), [self.nl, 1, -1, 1, 2])
|
|
405
|
-
self.m = [TFConv2d(x, self.no * self.na, 1, w=w.m[i]) for i, x in enumerate(ch)]
|
|
406
|
-
self.training = False # set to False after building model
|
|
407
|
-
self.imgsz = imgsz
|
|
408
|
-
for i in range(self.nl):
|
|
409
|
-
ny, nx = self.imgsz[0] // self.stride[i], self.imgsz[1] // self.stride[i]
|
|
410
|
-
self.grid[i] = self._make_grid(nx, ny)
|
|
411
|
-
|
|
412
|
-
def call(self, inputs):
|
|
413
|
-
"""Performs forward pass through the model layers to predict object bounding boxes and classifications."""
|
|
414
|
-
z = [] # inference output
|
|
415
|
-
x = []
|
|
416
|
-
for i in range(self.nl):
|
|
417
|
-
x.append(self.m[i](inputs[i]))
|
|
418
|
-
# x(bs,20,20,255) to x(bs,3,20,20,85)
|
|
419
|
-
ny, nx = self.imgsz[0] // self.stride[i], self.imgsz[1] // self.stride[i]
|
|
420
|
-
x[i] = tf.reshape(x[i], [-1, ny * nx, self.na, self.no])
|
|
421
|
-
|
|
422
|
-
if not self.training: # inference
|
|
423
|
-
y = x[i]
|
|
424
|
-
grid = tf.transpose(self.grid[i], [0, 2, 1, 3]) - 0.5
|
|
425
|
-
anchor_grid = tf.transpose(self.anchor_grid[i], [0, 2, 1, 3]) * 4
|
|
426
|
-
xy = (tf.sigmoid(y[..., 0:2]) * 2 + grid) * self.stride[i] # xy
|
|
427
|
-
wh = tf.sigmoid(y[..., 2:4]) ** 2 * anchor_grid
|
|
428
|
-
# Normalize xywh to 0-1 to reduce calibration error
|
|
429
|
-
xy /= tf.constant([[self.imgsz[1], self.imgsz[0]]], dtype=tf.float32)
|
|
430
|
-
wh /= tf.constant([[self.imgsz[1], self.imgsz[0]]], dtype=tf.float32)
|
|
431
|
-
y = tf.concat([xy, wh, tf.sigmoid(y[..., 4 : 5 + self.nc]), y[..., 5 + self.nc :]], -1)
|
|
432
|
-
z.append(tf.reshape(y, [-1, self.na * ny * nx, self.no]))
|
|
433
|
-
|
|
434
|
-
return tf.transpose(x, [0, 2, 1, 3]) if self.training else (tf.concat(z, 1),)
|
|
435
|
-
|
|
436
|
-
@staticmethod
|
|
437
|
-
def _make_grid(nx=20, ny=20):
|
|
438
|
-
"""Generates a 2D grid of coordinates in (x, y) format with shape [1, 1, ny*nx, 2]."""
|
|
439
|
-
# return torch.stack((xv, yv), 2).view((1, 1, ny, nx, 2)).float()
|
|
440
|
-
xv, yv = tf.meshgrid(tf.range(nx), tf.range(ny))
|
|
441
|
-
return tf.cast(tf.reshape(tf.stack([xv, yv], 2), [1, 1, ny * nx, 2]), dtype=tf.float32)
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
class TFSegment(TFDetect):
|
|
445
|
-
"""YOLOv5 segmentation head for TensorFlow, combining detection and segmentation."""
|
|
446
|
-
|
|
447
|
-
def __init__(self, nc=80, anchors=(), nm=32, npr=256, ch=(), imgsz=(640, 640), w=None):
|
|
448
|
-
"""Initializes YOLOv5 Segment head with specified channel depths, anchors, and input size for segmentation
|
|
449
|
-
models.
|
|
450
|
-
"""
|
|
451
|
-
super().__init__(nc, anchors, ch, imgsz, w)
|
|
452
|
-
self.nm = nm # number of masks
|
|
453
|
-
self.npr = npr # number of protos
|
|
454
|
-
self.no = 5 + nc + self.nm # number of outputs per anchor
|
|
455
|
-
self.m = [TFConv2d(x, self.no * self.na, 1, w=w.m[i]) for i, x in enumerate(ch)] # output conv
|
|
456
|
-
self.proto = TFProto(ch[0], self.npr, self.nm, w=w.proto) # protos
|
|
457
|
-
self.detect = TFDetect.call
|
|
458
|
-
|
|
459
|
-
def call(self, x):
|
|
460
|
-
"""Applies detection and proto layers on input, returning detections and optionally protos if training."""
|
|
461
|
-
p = self.proto(x[0])
|
|
462
|
-
# p = TFUpsample(None, scale_factor=4, mode='nearest')(self.proto(x[0])) # (optional) full-size protos
|
|
463
|
-
p = tf.transpose(p, [0, 3, 1, 2]) # from shape(1,160,160,32) to shape(1,32,160,160)
|
|
464
|
-
x = self.detect(self, x)
|
|
465
|
-
return (x, p) if self.training else (x[0], p)
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
class TFProto(keras.layers.Layer):
|
|
469
|
-
"""Implements convolutional and upsampling layers for feature extraction in YOLOv5 segmentation."""
|
|
470
|
-
|
|
471
|
-
def __init__(self, c1, c_=256, c2=32, w=None):
|
|
472
|
-
"""Initializes TFProto layer with convolutional and upsampling layers for feature extraction and
|
|
473
|
-
transformation.
|
|
474
|
-
"""
|
|
475
|
-
super().__init__()
|
|
476
|
-
self.cv1 = TFConv(c1, c_, k=3, w=w.cv1)
|
|
477
|
-
self.upsample = TFUpsample(None, scale_factor=2, mode="nearest")
|
|
478
|
-
self.cv2 = TFConv(c_, c_, k=3, w=w.cv2)
|
|
479
|
-
self.cv3 = TFConv(c_, c2, w=w.cv3)
|
|
480
|
-
|
|
481
|
-
def call(self, inputs):
|
|
482
|
-
"""Performs forward pass through the model, applying convolutions and upscaling on input tensor."""
|
|
483
|
-
return self.cv3(self.cv2(self.upsample(self.cv1(inputs))))
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
class TFUpsample(keras.layers.Layer):
|
|
487
|
-
"""Implements a TensorFlow upsampling layer with specified size, scale factor, and interpolation mode."""
|
|
488
|
-
|
|
489
|
-
def __init__(self, size, scale_factor, mode, w=None):
|
|
490
|
-
"""
|
|
491
|
-
Initializes a TensorFlow upsampling layer with specified size, scale_factor, and mode, ensuring scale_factor is
|
|
492
|
-
even.
|
|
493
|
-
|
|
494
|
-
Warning: all arguments needed including 'w'
|
|
495
|
-
"""
|
|
496
|
-
super().__init__()
|
|
497
|
-
assert scale_factor % 2 == 0, "scale_factor must be multiple of 2"
|
|
498
|
-
self.upsample = lambda x: tf.image.resize(x, (x.shape[1] * scale_factor, x.shape[2] * scale_factor), mode)
|
|
499
|
-
# self.upsample = keras.layers.UpSampling2D(size=scale_factor, interpolation=mode)
|
|
500
|
-
# with default arguments: align_corners=False, half_pixel_centers=False
|
|
501
|
-
# self.upsample = lambda x: tf.raw_ops.ResizeNearestNeighbor(images=x,
|
|
502
|
-
# size=(x.shape[1] * 2, x.shape[2] * 2))
|
|
503
|
-
|
|
504
|
-
def call(self, inputs):
|
|
505
|
-
"""Applies upsample operation to inputs using nearest neighbor interpolation."""
|
|
506
|
-
return self.upsample(inputs)
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
class TFConcat(keras.layers.Layer):
|
|
510
|
-
"""Implements TensorFlow's version of torch.concat() for concatenating tensors along the last dimension."""
|
|
511
|
-
|
|
512
|
-
def __init__(self, dimension=1, w=None):
|
|
513
|
-
"""Initializes a TensorFlow layer for NCHW to NHWC concatenation, requiring dimension=1."""
|
|
514
|
-
super().__init__()
|
|
515
|
-
assert dimension == 1, "convert only NCHW to NHWC concat"
|
|
516
|
-
self.d = 3
|
|
517
|
-
|
|
518
|
-
def call(self, inputs):
|
|
519
|
-
"""Concatenates a list of tensors along the last dimension, used for NCHW to NHWC conversion."""
|
|
520
|
-
return tf.concat(inputs, self.d)
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
def parse_model(d, ch, model, imgsz):
|
|
524
|
-
"""Parses a model definition dict `d` to create YOLOv5 model layers, including dynamic channel adjustments."""
|
|
525
|
-
LOGGER.info(f"\n{'':>3}{'from':>18}{'n':>3}{'params':>10} {'module':<40}{'arguments':<30}")
|
|
526
|
-
anchors, nc, gd, gw, ch_mul = (
|
|
527
|
-
d["anchors"],
|
|
528
|
-
d["nc"],
|
|
529
|
-
d["depth_multiple"],
|
|
530
|
-
d["width_multiple"],
|
|
531
|
-
d.get("channel_multiple"),
|
|
532
|
-
)
|
|
533
|
-
na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors # number of anchors
|
|
534
|
-
no = na * (nc + 5) # number of outputs = anchors * (classes + 5)
|
|
535
|
-
if not ch_mul:
|
|
536
|
-
ch_mul = 8
|
|
537
|
-
|
|
538
|
-
layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out
|
|
539
|
-
for i, (f, n, m, args) in enumerate(d["backbone"] + d["head"]): # from, number, module, args
|
|
540
|
-
m_str = m
|
|
541
|
-
m = eval(m) if isinstance(m, str) else m # eval strings
|
|
542
|
-
for j, a in enumerate(args):
|
|
543
|
-
try:
|
|
544
|
-
args[j] = eval(a) if isinstance(a, str) else a # eval strings
|
|
545
|
-
except NameError:
|
|
546
|
-
pass
|
|
547
|
-
|
|
548
|
-
n = max(round(n * gd), 1) if n > 1 else n # depth gain
|
|
549
|
-
if m in [
|
|
550
|
-
nn.Conv2d,
|
|
551
|
-
Conv,
|
|
552
|
-
DWConv,
|
|
553
|
-
DWConvTranspose2d,
|
|
554
|
-
Bottleneck,
|
|
555
|
-
SPP,
|
|
556
|
-
SPPF,
|
|
557
|
-
MixConv2d,
|
|
558
|
-
Focus,
|
|
559
|
-
CrossConv,
|
|
560
|
-
BottleneckCSP,
|
|
561
|
-
C3,
|
|
562
|
-
C3x,
|
|
563
|
-
]:
|
|
564
|
-
c1, c2 = ch[f], args[0]
|
|
565
|
-
c2 = make_divisible(c2 * gw, ch_mul) if c2 != no else c2
|
|
566
|
-
|
|
567
|
-
args = [c1, c2, *args[1:]]
|
|
568
|
-
if m in [BottleneckCSP, C3, C3x]:
|
|
569
|
-
args.insert(2, n)
|
|
570
|
-
n = 1
|
|
571
|
-
elif m is nn.BatchNorm2d:
|
|
572
|
-
args = [ch[f]]
|
|
573
|
-
elif m is Concat:
|
|
574
|
-
c2 = sum(ch[-1 if x == -1 else x + 1] for x in f)
|
|
575
|
-
elif m in [Detect, Segment]:
|
|
576
|
-
args.append([ch[x + 1] for x in f])
|
|
577
|
-
if isinstance(args[1], int): # number of anchors
|
|
578
|
-
args[1] = [list(range(args[1] * 2))] * len(f)
|
|
579
|
-
if m is Segment:
|
|
580
|
-
args[3] = make_divisible(args[3] * gw, ch_mul)
|
|
581
|
-
args.append(imgsz)
|
|
582
|
-
else:
|
|
583
|
-
c2 = ch[f]
|
|
584
|
-
|
|
585
|
-
tf_m = eval("TF" + m_str.replace("nn.", ""))
|
|
586
|
-
m_ = (
|
|
587
|
-
keras.Sequential([tf_m(*args, w=model.model[i][j]) for j in range(n)])
|
|
588
|
-
if n > 1
|
|
589
|
-
else tf_m(*args, w=model.model[i])
|
|
590
|
-
) # module
|
|
591
|
-
|
|
592
|
-
torch_m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args) # module
|
|
593
|
-
t = str(m)[8:-2].replace("__main__.", "") # module type
|
|
594
|
-
np = sum(x.numel() for x in torch_m_.parameters()) # number params
|
|
595
|
-
m_.i, m_.f, m_.type, m_.np = i, f, t, np # attach index, 'from' index, type, number params
|
|
596
|
-
LOGGER.info(f"{i:>3}{str(f):>18}{str(n):>3}{np:>10} {t:<40}{str(args):<30}") # print
|
|
597
|
-
save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist
|
|
598
|
-
layers.append(m_)
|
|
599
|
-
ch.append(c2)
|
|
600
|
-
return keras.Sequential(layers), sorted(save)
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
class TFModel:
|
|
604
|
-
"""Implements YOLOv5 model in TensorFlow, supporting TensorFlow, Keras, and TFLite formats for object detection."""
|
|
605
|
-
|
|
606
|
-
def __init__(self, cfg="yolov5s.yaml", ch=3, nc=None, model=None, imgsz=(640, 640)):
|
|
607
|
-
"""Initializes TF YOLOv5 model with specified configuration, channels, classes, model instance, and input
|
|
608
|
-
size.
|
|
609
|
-
"""
|
|
610
|
-
super().__init__()
|
|
611
|
-
if isinstance(cfg, dict):
|
|
612
|
-
self.yaml = cfg # model dict
|
|
613
|
-
else: # is *.yaml
|
|
614
|
-
import yaml # for torch hub
|
|
615
|
-
|
|
616
|
-
self.yaml_file = Path(cfg).name
|
|
617
|
-
with open(cfg) as f:
|
|
618
|
-
self.yaml = yaml.load(f, Loader=yaml.FullLoader) # model dict
|
|
619
|
-
|
|
620
|
-
# Define model
|
|
621
|
-
if nc and nc != self.yaml["nc"]:
|
|
622
|
-
LOGGER.info(f"Overriding {cfg} nc={self.yaml['nc']} with nc={nc}")
|
|
623
|
-
self.yaml["nc"] = nc # override yaml value
|
|
624
|
-
self.model, self.savelist = parse_model(deepcopy(self.yaml), ch=[ch], model=model, imgsz=imgsz)
|
|
625
|
-
|
|
626
|
-
def predict(
|
|
627
|
-
self,
|
|
628
|
-
inputs,
|
|
629
|
-
tf_nms=False,
|
|
630
|
-
agnostic_nms=False,
|
|
631
|
-
topk_per_class=100,
|
|
632
|
-
topk_all=100,
|
|
633
|
-
iou_thres=0.45,
|
|
634
|
-
conf_thres=0.25,
|
|
635
|
-
):
|
|
636
|
-
"""Runs inference on input data, with an option for TensorFlow NMS."""
|
|
637
|
-
y = [] # outputs
|
|
638
|
-
x = inputs
|
|
639
|
-
for m in self.model.layers:
|
|
640
|
-
if m.f != -1: # if not from previous layer
|
|
641
|
-
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
|
|
642
|
-
|
|
643
|
-
x = m(x) # run
|
|
644
|
-
y.append(x if m.i in self.savelist else None) # save output
|
|
645
|
-
|
|
646
|
-
# Add TensorFlow NMS
|
|
647
|
-
if tf_nms:
|
|
648
|
-
boxes = self._xywh2xyxy(x[0][..., :4])
|
|
649
|
-
probs = x[0][:, :, 4:5]
|
|
650
|
-
classes = x[0][:, :, 5:]
|
|
651
|
-
scores = probs * classes
|
|
652
|
-
if agnostic_nms:
|
|
653
|
-
nms = AgnosticNMS()((boxes, classes, scores), topk_all, iou_thres, conf_thres)
|
|
654
|
-
else:
|
|
655
|
-
boxes = tf.expand_dims(boxes, 2)
|
|
656
|
-
nms = tf.image.combined_non_max_suppression(
|
|
657
|
-
boxes, scores, topk_per_class, topk_all, iou_thres, conf_thres, clip_boxes=False
|
|
658
|
-
)
|
|
659
|
-
return (nms,)
|
|
660
|
-
return x # output [1,6300,85] = [xywh, conf, class0, class1, ...]
|
|
661
|
-
# x = x[0] # [x(1,6300,85), ...] to x(6300,85)
|
|
662
|
-
# xywh = x[..., :4] # x(6300,4) boxes
|
|
663
|
-
# conf = x[..., 4:5] # x(6300,1) confidences
|
|
664
|
-
# cls = tf.reshape(tf.cast(tf.argmax(x[..., 5:], axis=1), tf.float32), (-1, 1)) # x(6300,1) classes
|
|
665
|
-
# return tf.concat([conf, cls, xywh], 1)
|
|
666
|
-
|
|
667
|
-
@staticmethod
|
|
668
|
-
def _xywh2xyxy(xywh):
|
|
669
|
-
"""Converts bounding box format from [x, y, w, h] to [x1, y1, x2, y2], where xy1=top-left and xy2=bottom-
|
|
670
|
-
right.
|
|
671
|
-
"""
|
|
672
|
-
x, y, w, h = tf.split(xywh, num_or_size_splits=4, axis=-1)
|
|
673
|
-
return tf.concat([x - w / 2, y - h / 2, x + w / 2, y + h / 2], axis=-1)
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
class AgnosticNMS(keras.layers.Layer):
|
|
677
|
-
"""Performs agnostic non-maximum suppression (NMS) on detected objects using IoU and confidence thresholds."""
|
|
678
|
-
|
|
679
|
-
def call(self, input, topk_all, iou_thres, conf_thres):
|
|
680
|
-
"""Performs agnostic NMS on input tensors using given thresholds and top-K selection."""
|
|
681
|
-
return tf.map_fn(
|
|
682
|
-
lambda x: self._nms(x, topk_all, iou_thres, conf_thres),
|
|
683
|
-
input,
|
|
684
|
-
fn_output_signature=(tf.float32, tf.float32, tf.float32, tf.int32),
|
|
685
|
-
name="agnostic_nms",
|
|
686
|
-
)
|
|
687
|
-
|
|
688
|
-
@staticmethod
|
|
689
|
-
def _nms(x, topk_all=100, iou_thres=0.45, conf_thres=0.25):
|
|
690
|
-
"""Performs agnostic non-maximum suppression (NMS) on detected objects, filtering based on IoU and confidence
|
|
691
|
-
thresholds.
|
|
692
|
-
"""
|
|
693
|
-
boxes, classes, scores = x
|
|
694
|
-
class_inds = tf.cast(tf.argmax(classes, axis=-1), tf.float32)
|
|
695
|
-
scores_inp = tf.reduce_max(scores, -1)
|
|
696
|
-
selected_inds = tf.image.non_max_suppression(
|
|
697
|
-
boxes, scores_inp, max_output_size=topk_all, iou_threshold=iou_thres, score_threshold=conf_thres
|
|
698
|
-
)
|
|
699
|
-
selected_boxes = tf.gather(boxes, selected_inds)
|
|
700
|
-
padded_boxes = tf.pad(
|
|
701
|
-
selected_boxes,
|
|
702
|
-
paddings=[[0, topk_all - tf.shape(selected_boxes)[0]], [0, 0]],
|
|
703
|
-
mode="CONSTANT",
|
|
704
|
-
constant_values=0.0,
|
|
705
|
-
)
|
|
706
|
-
selected_scores = tf.gather(scores_inp, selected_inds)
|
|
707
|
-
padded_scores = tf.pad(
|
|
708
|
-
selected_scores,
|
|
709
|
-
paddings=[[0, topk_all - tf.shape(selected_boxes)[0]]],
|
|
710
|
-
mode="CONSTANT",
|
|
711
|
-
constant_values=-1.0,
|
|
712
|
-
)
|
|
713
|
-
selected_classes = tf.gather(class_inds, selected_inds)
|
|
714
|
-
padded_classes = tf.pad(
|
|
715
|
-
selected_classes,
|
|
716
|
-
paddings=[[0, topk_all - tf.shape(selected_boxes)[0]]],
|
|
717
|
-
mode="CONSTANT",
|
|
718
|
-
constant_values=-1.0,
|
|
719
|
-
)
|
|
720
|
-
valid_detections = tf.shape(selected_inds)[0]
|
|
721
|
-
return padded_boxes, padded_scores, padded_classes, valid_detections
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
def activations(act=nn.SiLU):
|
|
725
|
-
"""Converts PyTorch activations to TensorFlow equivalents, supporting LeakyReLU, Hardswish, and SiLU/Swish."""
|
|
726
|
-
if isinstance(act, nn.LeakyReLU):
|
|
727
|
-
return lambda x: keras.activations.relu(x, alpha=0.1)
|
|
728
|
-
elif isinstance(act, nn.Hardswish):
|
|
729
|
-
return lambda x: x * tf.nn.relu6(x + 3) * 0.166666667
|
|
730
|
-
elif isinstance(act, (nn.SiLU, SiLU)):
|
|
731
|
-
return lambda x: keras.activations.swish(x)
|
|
732
|
-
else:
|
|
733
|
-
raise Exception(f"no matching TensorFlow activation found for PyTorch activation {act}")
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
def representative_dataset_gen(dataset, ncalib=100):
|
|
737
|
-
"""Generates a representative dataset for calibration by yielding transformed numpy arrays from the input
|
|
738
|
-
dataset.
|
|
739
|
-
"""
|
|
740
|
-
for n, (path, img, im0s, vid_cap, string) in enumerate(dataset):
|
|
741
|
-
im = np.transpose(img, [1, 2, 0])
|
|
742
|
-
im = np.expand_dims(im, axis=0).astype(np.float32)
|
|
743
|
-
im /= 255
|
|
744
|
-
yield [im]
|
|
745
|
-
if n >= ncalib:
|
|
746
|
-
break
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
def run(
|
|
750
|
-
weights=ROOT / "yolov5s.pt", # weights path
|
|
751
|
-
imgsz=(640, 640), # inference size h,w
|
|
752
|
-
batch_size=1, # batch size
|
|
753
|
-
dynamic=False, # dynamic batch size
|
|
754
|
-
):
|
|
755
|
-
# PyTorch model
|
|
756
|
-
"""Exports YOLOv5 model from PyTorch to TensorFlow and Keras formats, performing inference for validation."""
|
|
757
|
-
im = torch.zeros((batch_size, 3, *imgsz)) # BCHW image
|
|
758
|
-
model = attempt_load(weights, device=torch.device("cpu"), inplace=True, fuse=False)
|
|
759
|
-
_ = model(im) # inference
|
|
760
|
-
model.info()
|
|
761
|
-
|
|
762
|
-
# TensorFlow model
|
|
763
|
-
im = tf.zeros((batch_size, *imgsz, 3)) # BHWC image
|
|
764
|
-
tf_model = TFModel(cfg=model.yaml, model=model, nc=model.nc, imgsz=imgsz)
|
|
765
|
-
_ = tf_model.predict(im) # inference
|
|
766
|
-
|
|
767
|
-
# Keras model
|
|
768
|
-
im = keras.Input(shape=(*imgsz, 3), batch_size=None if dynamic else batch_size)
|
|
769
|
-
keras_model = keras.Model(inputs=im, outputs=tf_model.predict(im))
|
|
770
|
-
keras_model.summary()
|
|
771
|
-
|
|
772
|
-
LOGGER.info("PyTorch, TensorFlow and Keras models successfully verified.\nUse export.py for TF model export.")
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
def parse_opt():
|
|
776
|
-
"""Parses and returns command-line options for model inference, including weights path, image size, batch size, and
|
|
777
|
-
dynamic batching.
|
|
778
|
-
"""
|
|
779
|
-
parser = argparse.ArgumentParser()
|
|
780
|
-
parser.add_argument("--weights", type=str, default=ROOT / "yolov5s.pt", help="weights path")
|
|
781
|
-
parser.add_argument("--imgsz", "--img", "--img-size", nargs="+", type=int, default=[640], help="inference size h,w")
|
|
782
|
-
parser.add_argument("--batch-size", type=int, default=1, help="batch size")
|
|
783
|
-
parser.add_argument("--dynamic", action="store_true", help="dynamic batch size")
|
|
784
|
-
opt = parser.parse_args()
|
|
785
|
-
opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand
|
|
786
|
-
print_args(vars(opt))
|
|
787
|
-
return opt
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
def main(opt):
|
|
791
|
-
"""Executes the YOLOv5 model run function with parsed command line options."""
|
|
792
|
-
run(**vars(opt))
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
if __name__ == "__main__":
|
|
796
|
-
opt = parse_opt()
|
|
797
|
-
main(opt)
|