Add docstrings to YOLOv5 functions (#12760)
* Add docstrings to top level files * Auto-format by https://ultralytics.com/actions * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Auto-format by https://ultralytics.com/actions * Add docstrings * Auto-format by https://ultralytics.com/actions * Update activations.py Signed-off-by: Glenn Jocher <glenn.jocher@ultralytics.com> * Auto-format by https://ultralytics.com/actions --------- Signed-off-by: Glenn Jocher <glenn.jocher@ultralytics.com> Co-authored-by: UltralyticsAssistant <web@ultralytics.com>
This commit is contained in:
1 parent
ca00c34c2a
commit
41603da162
42 files changed
+983
-322
No files matched your search
+122
-11
@@ -22,7 +22,7 @@ import torch.nn as nn
|
||||
from PIL import Image
|
||||
from torch.cuda import amp
|
||||
|
||||
# Import 'ultralytics' package or install if if missing
|
||||
# Import 'ultralytics' package or install if missing
|
||||
try:
|
||||
import ultralytics
|
||||
|
||||
@@ -71,15 +71,18 @@ class Conv(nn.Module):
|
||||
default_act = nn.SiLU() # default activation
|
||||
|
||||
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
|
||||
"""Initializes a standard convolution layer with optional batch normalization and activation."""
|
||||
super().__init__()
|
||||
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)
|
||||
self.bn = nn.BatchNorm2d(c2)
|
||||
self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()
|
||||
|
||||
def forward(self, x):
|
||||
"""Applies a convolution followed by batch normalization and an activation function to the input tensor `x`."""
|
||||
return self.act(self.bn(self.conv(x)))
|
||||
|
||||
def forward_fuse(self, x):
|
||||
"""Applies a fused convolution and activation function to the input tensor `x`."""
|
||||
return self.act(self.conv(x))
|
||||
|
||||
|
||||
@@ -98,6 +101,11 @@ class DWConvTranspose2d(nn.ConvTranspose2d):
|
||||
class TransformerLayer(nn.Module):
|
||||
# Transformer layer https://arxiv.org/abs/2010.11929 (LayerNorm layers removed for better performance)
|
||||
def __init__(self, c, num_heads):
|
||||
"""
|
||||
Initializes a transformer layer, sans LayerNorm for performance, with multihead attention and linear layers.
|
||||
|
||||
See as described in https://arxiv.org/abs/2010.11929.
|
||||
"""
|
||||
super().__init__()
|
||||
self.q = nn.Linear(c, c, bias=False)
|
||||
self.k = nn.Linear(c, c, bias=False)
|
||||
@@ -107,6 +115,7 @@ class TransformerLayer(nn.Module):
|
||||
self.fc2 = nn.Linear(c, c, bias=False)
|
||||
|
||||
def forward(self, x):
|
||||
"""Performs forward pass using MultiheadAttention and two linear transformations with residual connections."""
|
||||
x = self.ma(self.q(x), self.k(x), self.v(x))[0] + x
|
||||
x = self.fc2(self.fc1(x)) + x
|
||||
return x
|
||||
@@ -115,6 +124,9 @@ class TransformerLayer(nn.Module):
|
||||
class TransformerBlock(nn.Module):
|
||||
# Vision Transformer https://arxiv.org/abs/2010.11929
|
||||
def __init__(self, c1, c2, num_heads, num_layers):
|
||||
"""Initializes a Transformer block for vision tasks, adapting dimensions if necessary and stacking specified
|
||||
layers.
|
||||
"""
|
||||
super().__init__()
|
||||
self.conv = None
|
||||
if c1 != c2:
|
||||
@@ -124,6 +136,9 @@ class TransformerBlock(nn.Module):
|
||||
self.c2 = c2
|
||||
|
||||
def forward(self, x):
|
||||
"""Processes input through an optional convolution, followed by Transformer layers and position embeddings for
|
||||
object detection.
|
||||
"""
|
||||
if self.conv is not None:
|
||||
x = self.conv(x)
|
||||
b, _, w, h = x.shape
|
||||
@@ -141,6 +156,9 @@ class Bottleneck(nn.Module):
|
||||
self.add = shortcut and c1 == c2
|
||||
|
||||
def forward(self, x):
|
||||
"""Processes input through two convolutions, optionally adds shortcut if channel dimensions match; input is a
|
||||
tensor.
|
||||
"""
|
||||
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
|
||||
|
||||
|
||||
@@ -158,6 +176,9 @@ class BottleneckCSP(nn.Module):
|
||||
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
|
||||
|
||||
def forward(self, x):
|
||||
"""Performs forward pass by applying layers, activation, and concatenation on input x, returning feature-
|
||||
enhanced output.
|
||||
"""
|
||||
y1 = self.cv3(self.m(self.cv1(x)))
|
||||
y2 = self.cv2(x)
|
||||
return self.cv4(self.act(self.bn(torch.cat((y1, y2), 1))))
|
||||
@@ -166,7 +187,12 @@ class BottleneckCSP(nn.Module):
|
||||
class CrossConv(nn.Module):
|
||||
# Cross Convolution Downsample
|
||||
def __init__(self, c1, c2, k=3, s=1, g=1, e=1.0, shortcut=False):
|
||||
# ch_in, ch_out, kernel, stride, groups, expansion, shortcut
|
||||
"""
|
||||
Initializes CrossConv with downsampling, expanding, and optionally shortcutting; `c1` input, `c2` output
|
||||
channels.
|
||||
|
||||
Inputs are ch_in, ch_out, kernel, stride, groups, expansion, shortcut.
|
||||
"""
|
||||
super().__init__()
|
||||
c_ = int(c2 * e) # hidden channels
|
||||
self.cv1 = Conv(c1, c_, (1, k), (1, s))
|
||||
@@ -174,6 +200,7 @@ class CrossConv(nn.Module):
|
||||
self.add = shortcut and c1 == c2
|
||||
|
||||
def forward(self, x):
|
||||
"""Performs feature sampling, expanding, and applies shortcut if channels match; expects `x` input tensor."""
|
||||
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
|
||||
|
||||
|
||||
@@ -188,12 +215,16 @@ class C3(nn.Module):
|
||||
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
|
||||
|
||||
def forward(self, x):
|
||||
"""Performs forward propagation using concatenated outputs from two convolutions and a Bottleneck sequence."""
|
||||
return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1))
|
||||
|
||||
|
||||
class C3x(C3):
|
||||
# C3 module with cross-convolutions
|
||||
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
|
||||
"""Initializes C3x module with cross-convolutions, extending C3 with customizable channel dimensions, groups,
|
||||
and expansion.
|
||||
"""
|
||||
super().__init__(c1, c2, n, shortcut, g, e)
|
||||
c_ = int(c2 * e)
|
||||
self.m = nn.Sequential(*(CrossConv(c_, c_, 3, 1, g, 1.0, shortcut) for _ in range(n)))
|
||||
@@ -202,6 +233,9 @@ class C3x(C3):
|
||||
class C3TR(C3):
|
||||
# C3 module with TransformerBlock()
|
||||
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
|
||||
"""Initializes C3 module with TransformerBlock for enhanced feature extraction, accepts channel sizes, shortcut
|
||||
config, group, and expansion.
|
||||
"""
|
||||
super().__init__(c1, c2, n, shortcut, g, e)
|
||||
c_ = int(c2 * e)
|
||||
self.m = TransformerBlock(c_, c_, 4, n)
|
||||
@@ -210,6 +244,9 @@ class C3TR(C3):
|
||||
class C3SPP(C3):
|
||||
# C3 module with SPP()
|
||||
def __init__(self, c1, c2, k=(5, 9, 13), n=1, shortcut=True, g=1, e=0.5):
|
||||
"""Initializes a C3 module with SPP layer for advanced spatial feature extraction, given channel sizes, kernel
|
||||
sizes, shortcut, group, and expansion ratio.
|
||||
"""
|
||||
super().__init__(c1, c2, n, shortcut, g, e)
|
||||
c_ = int(c2 * e)
|
||||
self.m = SPP(c_, c_, k)
|
||||
@@ -218,6 +255,7 @@ class C3SPP(C3):
|
||||
class C3Ghost(C3):
|
||||
# C3 module with GhostBottleneck()
|
||||
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
|
||||
"""Initializes YOLOv5's C3 module with Ghost Bottlenecks for efficient feature extraction."""
|
||||
super().__init__(c1, c2, n, shortcut, g, e)
|
||||
c_ = int(c2 * e) # hidden channels
|
||||
self.m = nn.Sequential(*(GhostBottleneck(c_, c_) for _ in range(n)))
|
||||
@@ -226,6 +264,7 @@ class C3Ghost(C3):
|
||||
class SPP(nn.Module):
|
||||
# Spatial Pyramid Pooling (SPP) layer https://arxiv.org/abs/1406.4729
|
||||
def __init__(self, c1, c2, k=(5, 9, 13)):
|
||||
"""Initializes SPP layer with Spatial Pyramid Pooling, ref: https://arxiv.org/abs/1406.4729, args: c1 (input channels), c2 (output channels), k (kernel sizes)."""
|
||||
super().__init__()
|
||||
c_ = c1 // 2 # hidden channels
|
||||
self.cv1 = Conv(c1, c_, 1, 1)
|
||||
@@ -233,6 +272,9 @@ class SPP(nn.Module):
|
||||
self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])
|
||||
|
||||
def forward(self, x):
|
||||
"""Applies convolution and max pooling layers to the input tensor `x`, concatenates results, and returns output
|
||||
tensor.
|
||||
"""
|
||||
x = self.cv1(x)
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore") # suppress torch 1.9.0 max_pool2d() warning
|
||||
@@ -249,6 +291,7 @@ class SPPF(nn.Module):
|
||||
self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)
|
||||
|
||||
def forward(self, x):
|
||||
"""Processes input through a series of convolutions and max pooling operations for feature extraction."""
|
||||
x = self.cv1(x)
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore") # suppress torch 1.9.0 max_pool2d() warning
|
||||
@@ -278,6 +321,7 @@ class GhostConv(nn.Module):
|
||||
self.cv2 = Conv(c_, c_, 5, 1, None, c_, act=act)
|
||||
|
||||
def forward(self, x):
|
||||
"""Performs forward pass, concatenating outputs of two convolutions on input `x`: shape (B,C,H,W)."""
|
||||
y = self.cv1(x)
|
||||
return torch.cat((y, self.cv2(y)), 1)
|
||||
|
||||
@@ -297,16 +341,23 @@ class GhostBottleneck(nn.Module):
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
"""Processes input through conv and shortcut layers, returning their summed output."""
|
||||
return self.conv(x) + self.shortcut(x)
|
||||
|
||||
|
||||
class Contract(nn.Module):
|
||||
# Contract width-height into channels, i.e. x(1,64,80,80) to x(1,256,40,40)
|
||||
def __init__(self, gain=2):
|
||||
"""Initializes a layer to contract spatial dimensions (width-height) into channels, e.g., input shape
|
||||
(1,64,80,80) to (1,256,40,40).
|
||||
"""
|
||||
super().__init__()
|
||||
self.gain = gain
|
||||
|
||||
def forward(self, x):
|
||||
"""Processes input tensor to expand channel dimensions by contracting spatial dimensions, yielding output shape
|
||||
`(b, c*s*s, h//s, w//s)`.
|
||||
"""
|
||||
b, c, h, w = x.size() # assert (h / s == 0) and (W / s == 0), 'Indivisible gain'
|
||||
s = self.gain
|
||||
x = x.view(b, c, h // s, s, w // s, s) # x(1,64,40,2,40,2)
|
||||
@@ -317,10 +368,19 @@ class Contract(nn.Module):
|
||||
class Expand(nn.Module):
|
||||
# Expand channels into width-height, i.e. x(1,64,80,80) to x(1,16,160,160)
|
||||
def __init__(self, gain=2):
|
||||
"""
|
||||
Initializes the Expand module to increase spatial dimensions by redistributing channels, with an optional gain
|
||||
factor.
|
||||
|
||||
Example: x(1,64,80,80) to x(1,16,160,160).
|
||||
"""
|
||||
super().__init__()
|
||||
self.gain = gain
|
||||
|
||||
def forward(self, x):
|
||||
"""Processes input tensor x to expand spatial dimensions by redistributing channels, requiring C / gain^2 ==
|
||||
0.
|
||||
"""
|
||||
b, c, h, w = x.size() # assert C / s ** 2 == 0, 'Indivisible gain'
|
||||
s = self.gain
|
||||
x = x.view(b, s, s, c // s**2, h, w) # x(1,2,2,16,80,80)
|
||||
@@ -331,17 +391,21 @@ class Expand(nn.Module):
|
||||
class Concat(nn.Module):
|
||||
# Concatenate a list of tensors along dimension
|
||||
def __init__(self, dimension=1):
|
||||
"""Initializes a Concat module to concatenate tensors along a specified dimension."""
|
||||
super().__init__()
|
||||
self.d = dimension
|
||||
|
||||
def forward(self, x):
|
||||
"""Concatenates a list of tensors along a specified dimension; `x` is a list of tensors, `dimension` is an
|
||||
int.
|
||||
"""
|
||||
return torch.cat(x, self.d)
|
||||
|
||||
|
||||
class DetectMultiBackend(nn.Module):
|
||||
# YOLOv5 MultiBackend class for python inference on various backends
|
||||
def __init__(self, weights="yolov5s.pt", device=torch.device("cpu"), dnn=False, data=None, fp16=False, fuse=True):
|
||||
# Usage:
|
||||
"""Initializes DetectMultiBackend with support for various inference backends, including PyTorch and ONNX."""
|
||||
# PyTorch: weights = *.pt
|
||||
# TorchScript: *.torchscript
|
||||
# ONNX Runtime: *.onnx
|
||||
@@ -462,11 +526,13 @@ class DetectMultiBackend(nn.Module):
|
||||
import tensorflow as tf
|
||||
|
||||
def wrap_frozen_graph(gd, inputs, outputs):
|
||||
"""Wraps a TensorFlow GraphDef for inference, returning a pruned function."""
|
||||
x = tf.compat.v1.wrap_function(lambda: tf.compat.v1.import_graph_def(gd, name=""), []) # wrapped
|
||||
ge = x.graph.as_graph_element
|
||||
return x.prune(tf.nest.map_structure(ge, inputs), tf.nest.map_structure(ge, outputs))
|
||||
|
||||
def gd_outputs(gd):
|
||||
"""Generates a sorted list of graph outputs excluding NoOp nodes and inputs, formatted as '<name>:0'."""
|
||||
name_list, input_list = [], []
|
||||
for node in gd.node: # tensorflow.core.framework.node_def_pb2.NodeDef
|
||||
name_list.append(node.name)
|
||||
@@ -540,7 +606,7 @@ class DetectMultiBackend(nn.Module):
|
||||
self.__dict__.update(locals()) # assign all variables to self
|
||||
|
||||
def forward(self, im, augment=False, visualize=False):
|
||||
# YOLOv5 MultiBackend inference
|
||||
"""Performs YOLOv5 inference on input images with options for augmentation and visualization."""
|
||||
b, ch, h, w = im.shape # batch, channel, height, width
|
||||
if self.fp16 and im.dtype != torch.float16:
|
||||
im = im.half() # to FP16
|
||||
@@ -622,10 +688,11 @@ class DetectMultiBackend(nn.Module):
|
||||
return self.from_numpy(y)
|
||||
|
||||
def from_numpy(self, x):
|
||||
"""Converts a NumPy array to a torch tensor, maintaining device compatibility."""
|
||||
return torch.from_numpy(x).to(self.device) if isinstance(x, np.ndarray) else x
|
||||
|
||||
def warmup(self, imgsz=(1, 3, 640, 640)):
|
||||
# Warmup model by running inference once
|
||||
"""Performs a single inference warmup to initialize model weights, accepting an `imgsz` tuple for image size."""
|
||||
warmup_types = self.pt, self.jit, self.onnx, self.engine, self.saved_model, self.pb, self.triton
|
||||
if any(warmup_types) and (self.device.type != "cpu" or self.triton):
|
||||
im = torch.empty(*imgsz, dtype=torch.half if self.fp16 else torch.float, device=self.device) # input
|
||||
@@ -634,7 +701,11 @@ class DetectMultiBackend(nn.Module):
|
||||
|
||||
@staticmethod
|
||||
def _model_type(p="path/to/model.pt"):
|
||||
# Return model type from model path, i.e. path='path/to/model.onnx' -> type=onnx
|
||||
"""
|
||||
Determines model type from file path or URL, supporting various export formats.
|
||||
|
||||
Example: path='path/to/model.onnx' -> type=onnx
|
||||
"""
|
||||
# types = [pt, jit, onnx, xml, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs, paddle]
|
||||
from export import export_formats
|
||||
from utils.downloads import is_url
|
||||
@@ -650,7 +721,7 @@ class DetectMultiBackend(nn.Module):
|
||||
|
||||
@staticmethod
|
||||
def _load_metadata(f=Path("path/to/meta.yaml")):
|
||||
# Load metadata from meta.yaml if it exists
|
||||
"""Loads metadata from a YAML file, returning strides and names if the file exists, otherwise `None`."""
|
||||
if f.exists():
|
||||
d = yaml_load(f)
|
||||
return d["stride"], d["names"] # assign stride, names
|
||||
@@ -668,6 +739,7 @@ class AutoShape(nn.Module):
|
||||
amp = False # Automatic Mixed Precision (AMP) inference
|
||||
|
||||
def __init__(self, model, verbose=True):
|
||||
"""Initializes YOLOv5 model for inference, setting up attributes and preparing model for evaluation."""
|
||||
super().__init__()
|
||||
if verbose:
|
||||
LOGGER.info("Adding AutoShape... ")
|
||||
@@ -681,7 +753,11 @@ class AutoShape(nn.Module):
|
||||
m.export = True # do not output loss values
|
||||
|
||||
def _apply(self, fn):
|
||||
# Apply to(), cpu(), cuda(), half() to model tensors that are not parameters or registered buffers
|
||||
"""
|
||||
Applies to(), cpu(), cuda(), half() etc.
|
||||
|
||||
to model tensors excluding parameters or registered buffers.
|
||||
"""
|
||||
self = super()._apply(fn)
|
||||
if self.pt:
|
||||
m = self.model.model.model[-1] if self.dmb else self.model.model[-1] # Detect()
|
||||
@@ -693,7 +769,12 @@ class AutoShape(nn.Module):
|
||||
|
||||
@smart_inference_mode()
|
||||
def forward(self, ims, size=640, augment=False, profile=False):
|
||||
# Inference from various sources. For size(height=640, width=1280), RGB images example inputs are:
|
||||
"""
|
||||
Performs inference on inputs with optional augment & profiling.
|
||||
|
||||
Supports various formats including file, URI, OpenCV, PIL, numpy, torch.
|
||||
"""
|
||||
# For size(height=640, width=1280), RGB images example inputs are:
|
||||
# file: ims = 'data/images/zidane.jpg' # str or PosixPath
|
||||
# URI: = 'https://ultralytics.com/images/zidane.jpg'
|
||||
# OpenCV: = cv2.imread('image.jpg')[:,:,::-1] # HWC BGR to RGB x(640,1280,3)
|
||||
@@ -761,6 +842,7 @@ class AutoShape(nn.Module):
|
||||
class Detections:
|
||||
# YOLOv5 detections class for inference results
|
||||
def __init__(self, ims, pred, files, times=(0, 0, 0), names=None, shape=None):
|
||||
"""Initializes the YOLOv5 Detections class with image info, predictions, filenames, timing and normalization."""
|
||||
super().__init__()
|
||||
d = pred[0].device # device
|
||||
gn = [torch.tensor([*(im.shape[i] for i in [1, 0, 1, 0]), 1, 1], device=d) for im in ims] # normalizations
|
||||
@@ -778,6 +860,7 @@ class Detections:
|
||||
self.s = tuple(shape) # inference BCHW shape
|
||||
|
||||
def _run(self, pprint=False, show=False, save=False, crop=False, render=False, labels=True, save_dir=Path("")):
|
||||
"""Executes model predictions, displaying and/or saving outputs with optional crops and labels."""
|
||||
s, crops = "", []
|
||||
for i, (im, pred) in enumerate(zip(self.ims, self.pred)):
|
||||
s += f"\nimage {i + 1}/{len(self.pred)}: {im.shape[0]}x{im.shape[1]} " # string
|
||||
@@ -832,22 +915,42 @@ class Detections:
|
||||
|
||||
@TryExcept("Showing images is not supported in this environment")
|
||||
def show(self, labels=True):
|
||||
"""
|
||||
Displays detection results with optional labels.
|
||||
|
||||
Usage: show(labels=True)
|
||||
"""
|
||||
self._run(show=True, labels=labels) # show results
|
||||
|
||||
def save(self, labels=True, save_dir="runs/detect/exp", exist_ok=False):
|
||||
"""
|
||||
Saves detection results with optional labels to a specified directory.
|
||||
|
||||
Usage: save(labels=True, save_dir='runs/detect/exp', exist_ok=False)
|
||||
"""
|
||||
save_dir = increment_path(save_dir, exist_ok, mkdir=True) # increment save_dir
|
||||
self._run(save=True, labels=labels, save_dir=save_dir) # save results
|
||||
|
||||
def crop(self, save=True, save_dir="runs/detect/exp", exist_ok=False):
|
||||
"""
|
||||
Crops detection results, optionally saves them to a directory.
|
||||
|
||||
Args: save (bool), save_dir (str), exist_ok (bool).
|
||||
"""
|
||||
save_dir = increment_path(save_dir, exist_ok, mkdir=True) if save else None
|
||||
return self._run(crop=True, save=save, save_dir=save_dir) # crop results
|
||||
|
||||
def render(self, labels=True):
|
||||
"""Renders detection results with optional labels on images; args: labels (bool) indicating label inclusion."""
|
||||
self._run(render=True, labels=labels) # render results
|
||||
return self.ims
|
||||
|
||||
def pandas(self):
|
||||
# return detections as pandas DataFrames, i.e. print(results.pandas().xyxy[0])
|
||||
"""
|
||||
Returns detections as pandas DataFrames for various box formats (xyxy, xyxyn, xywh, xywhn).
|
||||
|
||||
Example: print(results.pandas().xyxy[0]).
|
||||
"""
|
||||
new = copy(self) # return copy
|
||||
ca = "xmin", "ymin", "xmax", "ymax", "confidence", "class", "name" # xyxy columns
|
||||
cb = "xcenter", "ycenter", "width", "height", "confidence", "class", "name" # xywh columns
|
||||
@@ -857,7 +960,11 @@ class Detections:
|
||||
return new
|
||||
|
||||
def tolist(self):
|
||||
# return a list of Detections objects, i.e. 'for result in results.tolist():'
|
||||
"""
|
||||
Converts a Detections object into a list of individual detection results for iteration.
|
||||
|
||||
Example: for result in results.tolist():
|
||||
"""
|
||||
r = range(self.n) # iterable
|
||||
return [
|
||||
Detections(
|
||||
@@ -872,6 +979,7 @@ class Detections:
|
||||
]
|
||||
|
||||
def print(self):
|
||||
"""Logs the string representation of the current object's state via the LOGGER."""
|
||||
LOGGER.info(self.__str__())
|
||||
|
||||
def __len__(self): # override len(results)
|
||||
@@ -881,6 +989,7 @@ class Detections:
|
||||
return self._run(pprint=True) # print results
|
||||
|
||||
def __repr__(self):
|
||||
"""Returns a string representation of the YOLOv5 object, including its class and formatted results."""
|
||||
return f"YOLOv5 {self.__class__} instance\n" + self.__str__()
|
||||
|
||||
|
||||
@@ -894,6 +1003,7 @@ class Proto(nn.Module):
|
||||
self.cv3 = Conv(c_, c2)
|
||||
|
||||
def forward(self, x):
|
||||
"""Performs a forward pass using convolutional layers and upsampling on input tensor `x`."""
|
||||
return self.cv3(self.cv2(self.upsample(self.cv1(x))))
|
||||
|
||||
|
||||
@@ -910,6 +1020,7 @@ class Classify(nn.Module):
|
||||
self.linear = nn.Linear(c_, c2) # to x(b,c2)
|
||||
|
||||
def forward(self, x):
|
||||
"""Processes input through conv, pool, drop, and linear layers; supports list concatenation input."""
|
||||
if isinstance(x, list):
|
||||
x = torch.cat(x, 1)
|
||||
return self.linear(self.drop(self.pool(self.conv(x)).flatten(1)))
|
||||
+13
-2
@@ -19,6 +19,7 @@ class Sum(nn.Module):
|
||||
self.w = nn.Parameter(-torch.arange(1.0, n) / 2, requires_grad=True) # layer weights
|
||||
|
||||
def forward(self, x):
|
||||
"""Processes input through a customizable weighted sum of `n` inputs, optionally applying learned weights."""
|
||||
y = x[0] # no weight
|
||||
if self.weight:
|
||||
w = torch.sigmoid(self.w) * 2
|
||||
@@ -53,15 +54,21 @@ class MixConv2d(nn.Module):
|
||||
self.act = nn.SiLU()
|
||||
|
||||
def forward(self, x):
|
||||
"""Performs forward pass by applying SiLU activation on batch-normalized concatenated convolutional layer
|
||||
outputs.
|
||||
"""
|
||||
return self.act(self.bn(torch.cat([m(x) for m in self.m], 1)))
|
||||
|
||||
|
||||
class Ensemble(nn.ModuleList):
|
||||
# Ensemble of models
|
||||
"""Ensemble of models."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initializes an ensemble of models to be used for aggregated predictions."""
|
||||
super().__init__()
|
||||
|
||||
def forward(self, x, augment=False, profile=False, visualize=False):
|
||||
"""Performs forward pass aggregating outputs from an ensemble of models.."""
|
||||
y = [module(x, augment, profile, visualize)[0] for module in self]
|
||||
# y = torch.stack(y).max(0)[0] # max ensemble
|
||||
# y = torch.stack(y).mean(0) # mean ensemble
|
||||
@@ -70,7 +77,11 @@ class Ensemble(nn.ModuleList):
|
||||
|
||||
|
||||
def attempt_load(weights, device=None, inplace=True, fuse=True):
|
||||
# Loads an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a
|
||||
"""
|
||||
Loads and fuses an ensemble or single YOLOv5 model from weights, handling device placement and model adjustments.
|
||||
|
||||
Example inputs: weights=[a,b,c] or a single model weights=[a] or weights=a.
|
||||
"""
|
||||
from models.yolo import Detect, Model
|
||||
|
||||
model = Ensemble()
|
||||
|
||||
+102
-12
@@ -51,6 +51,7 @@ from utils.general import LOGGER, make_divisible, print_args
|
||||
class TFBN(keras.layers.Layer):
|
||||
# TensorFlow BatchNormalization wrapper
|
||||
def __init__(self, w=None):
|
||||
"""Initializes a TensorFlow BatchNormalization layer with optional pretrained weights."""
|
||||
super().__init__()
|
||||
self.bn = keras.layers.BatchNormalization(
|
||||
beta_initializer=keras.initializers.Constant(w.bias.numpy()),
|
||||
@@ -61,12 +62,19 @@ class TFBN(keras.layers.Layer):
|
||||
)
|
||||
|
||||
def call(self, inputs):
|
||||
"""Applies batch normalization to the inputs."""
|
||||
return self.bn(inputs)
|
||||
|
||||
|
||||
class TFPad(keras.layers.Layer):
|
||||
# Pad inputs in spatial dimensions 1 and 2
|
||||
def __init__(self, pad):
|
||||
"""
|
||||
Initializes a padding layer for spatial dimensions 1 and 2 with specified padding, supporting both int and tuple
|
||||
inputs.
|
||||
|
||||
Inputs are
|
||||
"""
|
||||
super().__init__()
|
||||
if isinstance(pad, int):
|
||||
self.pad = tf.constant([[0, 0], [pad, pad], [pad, pad], [0, 0]])
|
||||
@@ -74,13 +82,19 @@ class TFPad(keras.layers.Layer):
|
||||
self.pad = tf.constant([[0, 0], [pad[0], pad[0]], [pad[1], pad[1]], [0, 0]])
|
||||
|
||||
def call(self, inputs):
|
||||
"""Pads input tensor with zeros using specified padding, suitable for int and tuple pad dimensions."""
|
||||
return tf.pad(inputs, self.pad, mode="constant", constant_values=0)
|
||||
|
||||
|
||||
class TFConv(keras.layers.Layer):
|
||||
# Standard convolution
|
||||
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, w=None):
|
||||
# ch_in, ch_out, weights, kernel, stride, padding, groups
|
||||
"""
|
||||
Initializes a standard convolution layer with optional batch normalization and activation; supports only
|
||||
group=1.
|
||||
|
||||
Inputs are ch_in, ch_out, weights, kernel, stride, padding, groups.
|
||||
"""
|
||||
super().__init__()
|
||||
assert g == 1, "TF v2.2 Conv2D does not support 'groups' argument"
|
||||
# TensorFlow convolution padding is inconsistent with PyTorch (e.g. k=3 s=2 'SAME' padding)
|
||||
@@ -99,13 +113,19 @@ class TFConv(keras.layers.Layer):
|
||||
self.act = activations(w.act) if act else tf.identity
|
||||
|
||||
def call(self, inputs):
|
||||
"""Applies convolution, batch normalization, and activation function to input tensors."""
|
||||
return self.act(self.bn(self.conv(inputs)))
|
||||
|
||||
|
||||
class TFDWConv(keras.layers.Layer):
|
||||
# Depthwise convolution
|
||||
def __init__(self, c1, c2, k=1, s=1, p=None, act=True, w=None):
|
||||
# ch_in, ch_out, weights, kernel, stride, padding, groups
|
||||
"""
|
||||
Initializes a depthwise convolution layer with optional batch normalization and activation for TensorFlow
|
||||
models.
|
||||
|
||||
Input are ch_in, ch_out, weights, kernel, stride, padding, groups.
|
||||
"""
|
||||
super().__init__()
|
||||
assert c2 % c1 == 0, f"TFDWConv() output={c2} must be a multiple of input={c1} channels"
|
||||
conv = keras.layers.DepthwiseConv2D(
|
||||
@@ -122,13 +142,18 @@ class TFDWConv(keras.layers.Layer):
|
||||
self.act = activations(w.act) if act else tf.identity
|
||||
|
||||
def call(self, inputs):
|
||||
"""Applies convolution, batch normalization, and activation function to input tensors."""
|
||||
return self.act(self.bn(self.conv(inputs)))
|
||||
|
||||
|
||||
class TFDWConvTranspose2d(keras.layers.Layer):
|
||||
# Depthwise ConvTranspose2d
|
||||
def __init__(self, c1, c2, k=1, s=1, p1=0, p2=0, w=None):
|
||||
# ch_in, ch_out, weights, kernel, stride, padding, groups
|
||||
"""
|
||||
Initializes depthwise ConvTranspose2D layer with specific channel, kernel, stride, and padding settings.
|
||||
|
||||
Inputs are ch_in, ch_out, weights, kernel, stride, padding, groups.
|
||||
"""
|
||||
super().__init__()
|
||||
assert c1 == c2, f"TFDWConv() output={c2} must be equal to input={c1} channels"
|
||||
assert k == 4 and p1 == 1, "TFDWConv() only valid for k=4 and p1=1"
|
||||
@@ -149,13 +174,19 @@ class TFDWConvTranspose2d(keras.layers.Layer):
|
||||
]
|
||||
|
||||
def call(self, inputs):
|
||||
"""Processes input through parallel convolutions and concatenates results, trimming border pixels."""
|
||||
return tf.concat([m(x) for m, x in zip(self.conv, tf.split(inputs, self.c1, 3))], 3)[:, 1:-1, 1:-1]
|
||||
|
||||
|
||||
class TFFocus(keras.layers.Layer):
|
||||
# Focus wh information into c-space
|
||||
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, w=None):
|
||||
# ch_in, ch_out, kernel, stride, padding, groups
|
||||
"""
|
||||
Initializes TFFocus layer to focus width and height information into channel space with custom convolution
|
||||
parameters.
|
||||
|
||||
Inputs are ch_in, ch_out, kernel, stride, padding, groups.
|
||||
"""
|
||||
super().__init__()
|
||||
self.conv = TFConv(c1 * 4, c2, k, s, p, g, act, w.conv)
|
||||
|
||||
@@ -175,12 +206,16 @@ class TFBottleneck(keras.layers.Layer):
|
||||
self.add = shortcut and c1 == c2
|
||||
|
||||
def call(self, inputs):
|
||||
"""Performs forward pass; if shortcut is True & input/output channels match, adds input to the convolution
|
||||
result.
|
||||
"""
|
||||
return inputs + self.cv2(self.cv1(inputs)) if self.add else self.cv2(self.cv1(inputs))
|
||||
|
||||
|
||||
class TFCrossConv(keras.layers.Layer):
|
||||
# Cross Convolution
|
||||
def __init__(self, c1, c2, k=3, s=1, g=1, e=1.0, shortcut=False, w=None):
|
||||
"""Initializes cross convolution layer with optional expansion, grouping, and shortcut addition capabilities."""
|
||||
super().__init__()
|
||||
c_ = int(c2 * e) # hidden channels
|
||||
self.cv1 = TFConv(c1, c_, (1, k), (1, s), w=w.cv1)
|
||||
@@ -188,12 +223,16 @@ class TFCrossConv(keras.layers.Layer):
|
||||
self.add = shortcut and c1 == c2
|
||||
|
||||
def call(self, inputs):
|
||||
"""Passes input through two convolutions optionally adding the input if channel dimensions match."""
|
||||
return inputs + self.cv2(self.cv1(inputs)) if self.add else self.cv2(self.cv1(inputs))
|
||||
|
||||
|
||||
class TFConv2d(keras.layers.Layer):
|
||||
# Substitution for PyTorch nn.Conv2D
|
||||
def __init__(self, c1, c2, k, s=1, g=1, bias=True, w=None):
|
||||
"""Initializes a TensorFlow 2D convolution layer, mimicking PyTorch's nn.Conv2D functionality for given filter
|
||||
sizes and stride.
|
||||
"""
|
||||
super().__init__()
|
||||
assert g == 1, "TF v2.2 Conv2D does not support 'groups' argument"
|
||||
self.conv = keras.layers.Conv2D(
|
||||
@@ -207,13 +246,19 @@ class TFConv2d(keras.layers.Layer):
|
||||
)
|
||||
|
||||
def call(self, inputs):
|
||||
"""Applies a convolution operation to the inputs and returns the result."""
|
||||
return self.conv(inputs)
|
||||
|
||||
|
||||
class TFBottleneckCSP(keras.layers.Layer):
|
||||
# CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
|
||||
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
|
||||
# ch_in, ch_out, number, shortcut, groups, expansion
|
||||
"""
|
||||
Initializes CSP bottleneck layer with specified channel sizes, count, shortcut option, groups, and expansion
|
||||
ratio.
|
||||
|
||||
Inputs are ch_in, ch_out, number, shortcut, groups, expansion.
|
||||
"""
|
||||
super().__init__()
|
||||
c_ = int(c2 * e) # hidden channels
|
||||
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
||||
@@ -225,6 +270,9 @@ class TFBottleneckCSP(keras.layers.Layer):
|
||||
self.m = keras.Sequential([TFBottleneck(c_, c_, shortcut, g, e=1.0, w=w.m[j]) for j in range(n)])
|
||||
|
||||
def call(self, inputs):
|
||||
"""Processes input through the model layers, concatenates, normalizes, activates, and reduces the output
|
||||
dimensions.
|
||||
"""
|
||||
y1 = self.cv3(self.m(self.cv1(inputs)))
|
||||
y2 = self.cv2(inputs)
|
||||
return self.cv4(self.act(self.bn(tf.concat((y1, y2), axis=3))))
|
||||
@@ -233,7 +281,11 @@ class TFBottleneckCSP(keras.layers.Layer):
|
||||
class TFC3(keras.layers.Layer):
|
||||
# CSP Bottleneck with 3 convolutions
|
||||
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
|
||||
# ch_in, ch_out, number, shortcut, groups, expansion
|
||||
"""
|
||||
Initializes CSP Bottleneck with 3 convolutions, supporting optional shortcuts and group convolutions.
|
||||
|
||||
Inputs are ch_in, ch_out, number, shortcut, groups, expansion.
|
||||
"""
|
||||
super().__init__()
|
||||
c_ = int(c2 * e) # hidden channels
|
||||
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
||||
@@ -242,13 +294,22 @@ class TFC3(keras.layers.Layer):
|
||||
self.m = keras.Sequential([TFBottleneck(c_, c_, shortcut, g, e=1.0, w=w.m[j]) for j in range(n)])
|
||||
|
||||
def call(self, inputs):
|
||||
"""
|
||||
Processes input through a sequence of transformations for object detection (YOLOv5).
|
||||
|
||||
See https://github.com/ultralytics/yolov5.
|
||||
"""
|
||||
return self.cv3(tf.concat((self.m(self.cv1(inputs)), self.cv2(inputs)), axis=3))
|
||||
|
||||
|
||||
class TFC3x(keras.layers.Layer):
|
||||
# 3 module with cross-convolutions
|
||||
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, w=None):
|
||||
# ch_in, ch_out, number, shortcut, groups, expansion
|
||||
"""
|
||||
Initializes layer with cross-convolutions for enhanced feature extraction in object detection models.
|
||||
|
||||
Inputs are ch_in, ch_out, number, shortcut, groups, expansion.
|
||||
"""
|
||||
super().__init__()
|
||||
c_ = int(c2 * e) # hidden channels
|
||||
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
||||
@@ -259,12 +320,14 @@ class TFC3x(keras.layers.Layer):
|
||||
)
|
||||
|
||||
def call(self, inputs):
|
||||
"""Processes input through cascaded convolutions and merges features, returning the final tensor output."""
|
||||
return self.cv3(tf.concat((self.m(self.cv1(inputs)), self.cv2(inputs)), axis=3))
|
||||
|
||||
|
||||
class TFSPP(keras.layers.Layer):
|
||||
# Spatial pyramid pooling layer used in YOLOv3-SPP
|
||||
def __init__(self, c1, c2, k=(5, 9, 13), w=None):
|
||||
"""Initializes a YOLOv3-SPP layer with specific input/output channels and kernel sizes for pooling."""
|
||||
super().__init__()
|
||||
c_ = c1 // 2 # hidden channels
|
||||
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
||||
@@ -272,6 +335,7 @@ class TFSPP(keras.layers.Layer):
|
||||
self.m = [keras.layers.MaxPool2D(pool_size=x, strides=1, padding="SAME") for x in k]
|
||||
|
||||
def call(self, inputs):
|
||||
"""Processes input through two TFConv layers and concatenates with max-pooled outputs at intermediate stage."""
|
||||
x = self.cv1(inputs)
|
||||
return self.cv2(tf.concat([x] + [m(x) for m in self.m], 3))
|
||||
|
||||
@@ -279,6 +343,9 @@ class TFSPP(keras.layers.Layer):
|
||||
class TFSPPF(keras.layers.Layer):
|
||||
# Spatial pyramid pooling-Fast layer
|
||||
def __init__(self, c1, c2, k=5, w=None):
|
||||
"""Initializes a fast spatial pyramid pooling layer with customizable in/out channels, kernel size, and
|
||||
weights.
|
||||
"""
|
||||
super().__init__()
|
||||
c_ = c1 // 2 # hidden channels
|
||||
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
||||
@@ -286,6 +353,9 @@ class TFSPPF(keras.layers.Layer):
|
||||
self.m = keras.layers.MaxPool2D(pool_size=k, strides=1, padding="SAME")
|
||||
|
||||
def call(self, inputs):
|
||||
"""Executes the model's forward pass, concatenating input features with three max-pooled versions before final
|
||||
convolution.
|
||||
"""
|
||||
x = self.cv1(inputs)
|
||||
y1 = self.m(x)
|
||||
y2 = self.m(y1)
|
||||
@@ -312,6 +382,7 @@ class TFDetect(keras.layers.Layer):
|
||||
self.grid[i] = self._make_grid(nx, ny)
|
||||
|
||||
def call(self, inputs):
|
||||
"""Performs forward pass through the model layers to predict object bounding boxes and classifications."""
|
||||
z = [] # inference output
|
||||
x = []
|
||||
for i in range(self.nl):
|
||||
@@ -336,7 +407,7 @@ class TFDetect(keras.layers.Layer):
|
||||
|
||||
@staticmethod
|
||||
def _make_grid(nx=20, ny=20):
|
||||
# yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)])
|
||||
"""Generates a 2D grid of coordinates in (x, y) format with shape [1, 1, ny*nx, 2]."""
|
||||
# return torch.stack((xv, yv), 2).view((1, 1, ny, nx, 2)).float()
|
||||
xv, yv = tf.meshgrid(tf.range(nx), tf.range(ny))
|
||||
return tf.cast(tf.reshape(tf.stack([xv, yv], 2), [1, 1, ny * nx, 2]), dtype=tf.float32)
|
||||
@@ -345,6 +416,9 @@ class TFDetect(keras.layers.Layer):
|
||||
class TFSegment(TFDetect):
|
||||
# YOLOv5 Segment head for segmentation models
|
||||
def __init__(self, nc=80, anchors=(), nm=32, npr=256, ch=(), imgsz=(640, 640), w=None):
|
||||
"""Initializes YOLOv5 Segment head with specified channel depths, anchors, and input size for segmentation
|
||||
models.
|
||||
"""
|
||||
super().__init__(nc, anchors, ch, imgsz, w)
|
||||
self.nm = nm # number of masks
|
||||
self.npr = npr # number of protos
|
||||
@@ -354,6 +428,7 @@ class TFSegment(TFDetect):
|
||||
self.detect = TFDetect.call
|
||||
|
||||
def call(self, x):
|
||||
"""Applies detection and proto layers on input, returning detections and optionally protos if training."""
|
||||
p = self.proto(x[0])
|
||||
# p = TFUpsample(None, scale_factor=4, mode='nearest')(self.proto(x[0])) # (optional) full-size protos
|
||||
p = tf.transpose(p, [0, 3, 1, 2]) # from shape(1,160,160,32) to shape(1,32,160,160)
|
||||
@@ -363,6 +438,9 @@ class TFSegment(TFDetect):
|
||||
|
||||
class TFProto(keras.layers.Layer):
|
||||
def __init__(self, c1, c_=256, c2=32, w=None):
|
||||
"""Initializes TFProto layer with convolutional and upsampling layers for feature extraction and
|
||||
transformation.
|
||||
"""
|
||||
super().__init__()
|
||||
self.cv1 = TFConv(c1, c_, k=3, w=w.cv1)
|
||||
self.upsample = TFUpsample(None, scale_factor=2, mode="nearest")
|
||||
@@ -370,6 +448,7 @@ class TFProto(keras.layers.Layer):
|
||||
self.cv3 = TFConv(c_, c2, w=w.cv3)
|
||||
|
||||
def call(self, inputs):
|
||||
"""Performs forward pass through the model, applying convolutions and upscaling on input tensor."""
|
||||
return self.cv3(self.cv2(self.upsample(self.cv1(inputs))))
|
||||
|
||||
|
||||
@@ -385,17 +464,20 @@ class TFUpsample(keras.layers.Layer):
|
||||
# size=(x.shape[1] * 2, x.shape[2] * 2))
|
||||
|
||||
def call(self, inputs):
|
||||
"""Applies upsample operation to inputs using nearest neighbor interpolation."""
|
||||
return self.upsample(inputs)
|
||||
|
||||
|
||||
class TFConcat(keras.layers.Layer):
|
||||
# TF version of torch.concat()
|
||||
def __init__(self, dimension=1, w=None):
|
||||
"""Initializes a TensorFlow layer for NCHW to NHWC concatenation, requiring dimension=1."""
|
||||
super().__init__()
|
||||
assert dimension == 1, "convert only NCHW to NHWC concat"
|
||||
self.d = 3
|
||||
|
||||
def call(self, inputs):
|
||||
"""Concatenates a list of tensors along the last dimension, used for NCHW to NHWC conversion."""
|
||||
return tf.concat(inputs, self.d)
|
||||
|
||||
|
||||
@@ -539,7 +621,9 @@ class TFModel:
|
||||
|
||||
@staticmethod
|
||||
def _xywh2xyxy(xywh):
|
||||
# Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
|
||||
"""Converts bounding box format from [x, y, w, h] to [x1, y1, x2, y2], where xy1=top-left and xy2=bottom-
|
||||
right.
|
||||
"""
|
||||
x, y, w, h = tf.split(xywh, num_or_size_splits=4, axis=-1)
|
||||
return tf.concat([x - w / 2, y - h / 2, x + w / 2, y + h / 2], axis=-1)
|
||||
|
||||
@@ -547,7 +631,7 @@ class TFModel:
|
||||
class AgnosticNMS(keras.layers.Layer):
|
||||
# TF Agnostic NMS
|
||||
def call(self, input, topk_all, iou_thres, conf_thres):
|
||||
# wrap map_fn to avoid TypeSpec related error https://stackoverflow.com/a/65809989/3036450
|
||||
"""Performs agnostic NMS on input tensors using given thresholds and top-K selection."""
|
||||
return tf.map_fn(
|
||||
lambda x: self._nms(x, topk_all, iou_thres, conf_thres),
|
||||
input,
|
||||
@@ -589,7 +673,7 @@ class AgnosticNMS(keras.layers.Layer):
|
||||
|
||||
|
||||
def activations(act=nn.SiLU):
|
||||
# Returns TF activation from input PyTorch activation
|
||||
"""Converts PyTorch activations to TensorFlow equivalents, supporting LeakyReLU, Hardswish, and SiLU/Swish."""
|
||||
if isinstance(act, nn.LeakyReLU):
|
||||
return lambda x: keras.activations.relu(x, alpha=0.1)
|
||||
elif isinstance(act, nn.Hardswish):
|
||||
@@ -601,7 +685,9 @@ def activations(act=nn.SiLU):
|
||||
|
||||
|
||||
def representative_dataset_gen(dataset, ncalib=100):
|
||||
# Representative dataset generator for use with converter.representative_dataset, returns a generator of np arrays
|
||||
"""Generates a representative dataset for calibration by yielding transformed numpy arrays from the input
|
||||
dataset.
|
||||
"""
|
||||
for n, (path, img, im0s, vid_cap, string) in enumerate(dataset):
|
||||
im = np.transpose(img, [1, 2, 0])
|
||||
im = np.expand_dims(im, axis=0).astype(np.float32)
|
||||
@@ -637,6 +723,9 @@ def run(
|
||||
|
||||
|
||||
def parse_opt():
|
||||
"""Parses and returns command-line options for model inference, including weights path, image size, batch size, and
|
||||
dynamic batching.
|
||||
"""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--weights", type=str, default=ROOT / "yolov5s.pt", help="weights path")
|
||||
parser.add_argument("--imgsz", "--img", "--img-size", nargs="+", type=int, default=[640], help="inference size h,w")
|
||||
@@ -649,6 +738,7 @@ def parse_opt():
|
||||
|
||||
|
||||
def main(opt):
|
||||
"""Executes the YOLOv5 model run function with parsed command line options."""
|
||||
run(**vars(opt))
|
||||
|
||||
|
||||
|
||||
+27
-6
@@ -88,6 +88,7 @@ class Detect(nn.Module):
|
||||
self.inplace = inplace # use inplace ops (e.g. slice assignment)
|
||||
|
||||
def forward(self, x):
|
||||
"""Processes input through YOLOv5 layers, altering shape for detection: `x(bs, 3, ny, nx, 85)`."""
|
||||
z = [] # inference output
|
||||
for i in range(self.nl):
|
||||
x[i] = self.m[i](x[i]) # conv
|
||||
@@ -113,6 +114,7 @@ class Detect(nn.Module):
|
||||
return x if self.training else (torch.cat(z, 1),) if self.export else (torch.cat(z, 1), x)
|
||||
|
||||
def _make_grid(self, nx=20, ny=20, i=0, torch_1_10=check_version(torch.__version__, "1.10.0")):
|
||||
"""Generates a mesh grid for anchor boxes with optional compatibility for torch versions < 1.10."""
|
||||
d = self.anchors[i].device
|
||||
t = self.anchors[i].dtype
|
||||
shape = 1, self.na, ny, nx, 2 # grid shape
|
||||
@@ -126,6 +128,7 @@ class Detect(nn.Module):
|
||||
class Segment(Detect):
|
||||
# YOLOv5 Segment head for segmentation models
|
||||
def __init__(self, nc=80, anchors=(), nm=32, npr=256, ch=(), inplace=True):
|
||||
"""Initializes YOLOv5 Segment head with options for mask count, protos, and channel adjustments."""
|
||||
super().__init__(nc, anchors, ch, inplace)
|
||||
self.nm = nm # number of masks
|
||||
self.npr = npr # number of protos
|
||||
@@ -135,17 +138,25 @@ class Segment(Detect):
|
||||
self.detect = Detect.forward
|
||||
|
||||
def forward(self, x):
|
||||
"""Processes input through the network, returning detections and prototypes; adjusts output based on
|
||||
training/export mode.
|
||||
"""
|
||||
p = self.proto(x[0])
|
||||
x = self.detect(self, x)
|
||||
return (x, p) if self.training else (x[0], p) if self.export else (x[0], p, x[1])
|
||||
|
||||
|
||||
class BaseModel(nn.Module):
|
||||
# YOLOv5 base model
|
||||
"""YOLOv5 base model."""
|
||||
|
||||
def forward(self, x, profile=False, visualize=False):
|
||||
"""Executes a single-scale inference or training pass on the YOLOv5 base model, with options for profiling and
|
||||
visualization.
|
||||
"""
|
||||
return self._forward_once(x, profile, visualize) # single-scale inference, train
|
||||
|
||||
def _forward_once(self, x, profile=False, visualize=False):
|
||||
"""Performs a forward pass on the YOLOv5 model, enabling profiling and feature visualization options."""
|
||||
y, dt = [], [] # outputs
|
||||
for m in self.model:
|
||||
if m.f != -1: # if not from previous layer
|
||||
@@ -159,6 +170,7 @@ class BaseModel(nn.Module):
|
||||
return x
|
||||
|
||||
def _profile_one_layer(self, m, x, dt):
|
||||
"""Profiles a single layer's performance by computing GFLOPs, execution time, and parameters."""
|
||||
c = m == self.model[-1] # is final layer, copy input as inplace fix
|
||||
o = thop.profile(m, inputs=(x.copy() if c else x,), verbose=False)[0] / 1e9 * 2 if thop else 0 # FLOPs
|
||||
t = time_sync()
|
||||
@@ -185,7 +197,9 @@ class BaseModel(nn.Module):
|
||||
model_info(self, verbose, img_size)
|
||||
|
||||
def _apply(self, fn):
|
||||
# Apply to(), cpu(), cuda(), half() to model tensors that are not parameters or registered buffers
|
||||
"""Applies transformations like to(), cpu(), cuda(), half() to model tensors excluding parameters or registered
|
||||
buffers.
|
||||
"""
|
||||
self = super()._apply(fn)
|
||||
m = self.model[-1] # Detect()
|
||||
if isinstance(m, (Detect, Segment)):
|
||||
@@ -239,11 +253,13 @@ class DetectionModel(BaseModel):
|
||||
LOGGER.info("")
|
||||
|
||||
def forward(self, x, augment=False, profile=False, visualize=False):
|
||||
"""Performs single-scale or augmented inference and may include profiling or visualization."""
|
||||
if augment:
|
||||
return self._forward_augment(x) # augmented inference, None
|
||||
return self._forward_once(x, profile, visualize) # single-scale inference, train
|
||||
|
||||
def _forward_augment(self, x):
|
||||
"""Performs augmented inference across different scales and flips, returning combined detections."""
|
||||
img_size = x.shape[-2:] # height, width
|
||||
s = [1, 0.83, 0.67] # scales
|
||||
f = [None, 3, None] # flips (2-ud, 3-lr)
|
||||
@@ -258,7 +274,7 @@ class DetectionModel(BaseModel):
|
||||
return torch.cat(y, 1), None # augmented inference, train
|
||||
|
||||
def _descale_pred(self, p, flips, scale, img_size):
|
||||
# de-scale predictions following augmented inference (inverse operation)
|
||||
"""De-scales predictions from augmented inference, adjusting for flips and image size."""
|
||||
if self.inplace:
|
||||
p[..., :4] /= scale # de-scale
|
||||
if flips == 2:
|
||||
@@ -275,7 +291,9 @@ class DetectionModel(BaseModel):
|
||||
return p
|
||||
|
||||
def _clip_augmented(self, y):
|
||||
# Clip YOLOv5 augmented inference tails
|
||||
"""Clips augmented inference tails for YOLOv5 models, affecting first and last tensors based on grid points and
|
||||
layer counts.
|
||||
"""
|
||||
nl = self.model[-1].nl # number of detection layers (P3-P5)
|
||||
g = sum(4**x for x in range(nl)) # grid points
|
||||
e = 1 # exclude layer count
|
||||
@@ -304,6 +322,7 @@ Model = DetectionModel # retain YOLOv5 'Model' class for backwards compatibilit
|
||||
class SegmentationModel(DetectionModel):
|
||||
# YOLOv5 segmentation model
|
||||
def __init__(self, cfg="yolov5s-seg.yaml", ch=3, nc=None, anchors=None):
|
||||
"""Initializes a YOLOv5 segmentation model with configurable params: cfg (str) for configuration, ch (int) for channels, nc (int) for num classes, anchors (list)."""
|
||||
super().__init__(cfg, ch, nc, anchors)
|
||||
|
||||
|
||||
@@ -314,7 +333,9 @@ class ClassificationModel(BaseModel):
|
||||
self._from_detection_model(model, nc, cutoff) if model is not None else self._from_yaml(cfg)
|
||||
|
||||
def _from_detection_model(self, model, nc=1000, cutoff=10):
|
||||
# Create a YOLOv5 classification model from a YOLOv5 detection model
|
||||
"""Creates a classification model from a YOLOv5 detection model, slicing at `cutoff` and adding a classification
|
||||
layer.
|
||||
"""
|
||||
if isinstance(model, DetectMultiBackend):
|
||||
model = model.model # unwrap DetectMultiBackend
|
||||
model.model = model.model[:cutoff] # backbone
|
||||
@@ -329,7 +350,7 @@ class ClassificationModel(BaseModel):
|
||||
self.nc = nc
|
||||
|
||||
def _from_yaml(self, cfg):
|
||||
# Create a YOLOv5 classification model from a *.yaml file
|
||||
"""Creates a YOLOv5 classification model from a specified *.yaml configuration file."""
|
||||
self.model = None
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user