ros package to run yolo detection with ros
This commit is contained in:
parent
88944d72c2
commit
9069d699be
25
ros2_ws/src/yolov3_ros/launch/pipe_detection.launch.py
Normal file
25
ros2_ws/src/yolov3_ros/launch/pipe_detection.launch.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
from launch import LaunchDescription
|
||||||
|
from launch.actions import DeclareLaunchArgument, SetEnvironmentVariable
|
||||||
|
from launch.substitutions import LaunchConfiguration
|
||||||
|
from launch_ros.actions import Node
|
||||||
|
|
||||||
|
def generate_launch_description():
|
||||||
|
|
||||||
|
return LaunchDescription([
|
||||||
|
|
||||||
|
# Launch arguments
|
||||||
|
DeclareLaunchArgument('best_weights', default_value='src/pipe_weights.pt', description='Path to best weights file (.pt)'),
|
||||||
|
|
||||||
|
# Nodes to launch
|
||||||
|
Node(
|
||||||
|
package='yolov3_ros', executable='yolov3_ros_node', output='screen',
|
||||||
|
parameters=[{
|
||||||
|
'best_weights':LaunchConfiguration('best_weights'),
|
||||||
|
}],
|
||||||
|
# remappings=[
|
||||||
|
# ('rgb_img', LaunchConfiguration('rgb_topic')),
|
||||||
|
# ('depth_img', LaunchConfiguration('depth_topic')),
|
||||||
|
# ('camera_info', LaunchConfiguration('camera_info_topic'))]
|
||||||
|
),
|
||||||
|
])
|
||||||
0
ros2_ws/src/yolov3_ros/models/__init__.py
Normal file
0
ros2_ws/src/yolov3_ros/models/__init__.py
Normal file
Binary file not shown.
BIN
ros2_ws/src/yolov3_ros/models/__pycache__/common.cpython-310.pyc
Normal file
BIN
ros2_ws/src/yolov3_ros/models/__pycache__/common.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
ros2_ws/src/yolov3_ros/models/__pycache__/yolo.cpython-310.pyc
Normal file
BIN
ros2_ws/src/yolov3_ros/models/__pycache__/yolo.cpython-310.pyc
Normal file
Binary file not shown.
593
ros2_ws/src/yolov3_ros/models/common.py
Normal file
593
ros2_ws/src/yolov3_ros/models/common.py
Normal file
@ -0,0 +1,593 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Common modules
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import platform
|
||||||
|
import warnings
|
||||||
|
from copy import copy
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
import requests
|
||||||
|
import torch
|
||||||
|
import torch.nn as nn
|
||||||
|
from PIL import Image
|
||||||
|
from torch.cuda import amp
|
||||||
|
|
||||||
|
from utils.datasets import exif_transpose, letterbox
|
||||||
|
from utils.general import (LOGGER, check_requirements, check_suffix, colorstr, increment_path, make_divisible,
|
||||||
|
non_max_suppression, scale_coords, xywh2xyxy, xyxy2xywh)
|
||||||
|
from utils.plots import Annotator, colors, save_one_box
|
||||||
|
from utils.torch_utils import time_sync
|
||||||
|
|
||||||
|
|
||||||
|
def autopad(k, p=None): # kernel, padding
|
||||||
|
# Pad to 'same'
|
||||||
|
if p is None:
|
||||||
|
p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
class Conv(nn.Module):
|
||||||
|
# Standard convolution
|
||||||
|
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
|
||||||
|
super().__init__()
|
||||||
|
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
|
||||||
|
self.bn = nn.BatchNorm2d(c2)
|
||||||
|
self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
return self.act(self.bn(self.conv(x)))
|
||||||
|
|
||||||
|
def forward_fuse(self, x):
|
||||||
|
return self.act(self.conv(x))
|
||||||
|
|
||||||
|
|
||||||
|
class DWConv(Conv):
|
||||||
|
# Depth-wise convolution class
|
||||||
|
def __init__(self, c1, c2, k=1, s=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
|
||||||
|
super().__init__(c1, c2, k, s, g=math.gcd(c1, c2), act=act)
|
||||||
|
|
||||||
|
|
||||||
|
class TransformerLayer(nn.Module):
|
||||||
|
# Transformer layer https://arxiv.org/abs/2010.11929 (LayerNorm layers removed for better performance)
|
||||||
|
def __init__(self, c, num_heads):
|
||||||
|
super().__init__()
|
||||||
|
self.q = nn.Linear(c, c, bias=False)
|
||||||
|
self.k = nn.Linear(c, c, bias=False)
|
||||||
|
self.v = nn.Linear(c, c, bias=False)
|
||||||
|
self.ma = nn.MultiheadAttention(embed_dim=c, num_heads=num_heads)
|
||||||
|
self.fc1 = nn.Linear(c, c, bias=False)
|
||||||
|
self.fc2 = nn.Linear(c, c, bias=False)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
x = self.ma(self.q(x), self.k(x), self.v(x))[0] + x
|
||||||
|
x = self.fc2(self.fc1(x)) + x
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class TransformerBlock(nn.Module):
|
||||||
|
# Vision Transformer https://arxiv.org/abs/2010.11929
|
||||||
|
def __init__(self, c1, c2, num_heads, num_layers):
|
||||||
|
super().__init__()
|
||||||
|
self.conv = None
|
||||||
|
if c1 != c2:
|
||||||
|
self.conv = Conv(c1, c2)
|
||||||
|
self.linear = nn.Linear(c2, c2) # learnable position embedding
|
||||||
|
self.tr = nn.Sequential(*(TransformerLayer(c2, num_heads) for _ in range(num_layers)))
|
||||||
|
self.c2 = c2
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
if self.conv is not None:
|
||||||
|
x = self.conv(x)
|
||||||
|
b, _, w, h = x.shape
|
||||||
|
p = x.flatten(2).unsqueeze(0).transpose(0, 3).squeeze(3)
|
||||||
|
return self.tr(p + self.linear(p)).unsqueeze(3).transpose(0, 3).reshape(b, self.c2, w, h)
|
||||||
|
|
||||||
|
|
||||||
|
class Bottleneck(nn.Module):
|
||||||
|
# Standard bottleneck
|
||||||
|
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion
|
||||||
|
super().__init__()
|
||||||
|
c_ = int(c2 * e) # hidden channels
|
||||||
|
self.cv1 = Conv(c1, c_, 1, 1)
|
||||||
|
self.cv2 = Conv(c_, c2, 3, 1, g=g)
|
||||||
|
self.add = shortcut and c1 == c2
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
|
||||||
|
|
||||||
|
|
||||||
|
class BottleneckCSP(nn.Module):
|
||||||
|
# CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
|
||||||
|
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
|
||||||
|
super().__init__()
|
||||||
|
c_ = int(c2 * e) # hidden channels
|
||||||
|
self.cv1 = Conv(c1, c_, 1, 1)
|
||||||
|
self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False)
|
||||||
|
self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False)
|
||||||
|
self.cv4 = Conv(2 * c_, c2, 1, 1)
|
||||||
|
self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3)
|
||||||
|
self.act = nn.SiLU()
|
||||||
|
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
y1 = self.cv3(self.m(self.cv1(x)))
|
||||||
|
y2 = self.cv2(x)
|
||||||
|
return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1))))
|
||||||
|
|
||||||
|
|
||||||
|
class C3(nn.Module):
|
||||||
|
# CSP Bottleneck with 3 convolutions
|
||||||
|
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
|
||||||
|
super().__init__()
|
||||||
|
c_ = int(c2 * e) # hidden channels
|
||||||
|
self.cv1 = Conv(c1, c_, 1, 1)
|
||||||
|
self.cv2 = Conv(c1, c_, 1, 1)
|
||||||
|
self.cv3 = Conv(2 * c_, c2, 1) # act=FReLU(c2)
|
||||||
|
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
|
||||||
|
# self.m = nn.Sequential(*[CrossConv(c_, c_, 3, 1, g, 1.0, shortcut) for _ in range(n)])
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1))
|
||||||
|
|
||||||
|
|
||||||
|
class C3TR(C3):
|
||||||
|
# C3 module with TransformerBlock()
|
||||||
|
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
|
||||||
|
super().__init__(c1, c2, n, shortcut, g, e)
|
||||||
|
c_ = int(c2 * e)
|
||||||
|
self.m = TransformerBlock(c_, c_, 4, n)
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
super().__init__(c1, c2, n, shortcut, g, e)
|
||||||
|
c_ = int(c2 * e)
|
||||||
|
self.m = SPP(c_, c_, k)
|
||||||
|
|
||||||
|
|
||||||
|
class C3Ghost(C3):
|
||||||
|
# C3 module with GhostBottleneck()
|
||||||
|
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
|
||||||
|
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)))
|
||||||
|
|
||||||
|
|
||||||
|
class SPP(nn.Module):
|
||||||
|
# Spatial Pyramid Pooling (SPP) layer https://arxiv.org/abs/1406.4729
|
||||||
|
def __init__(self, c1, c2, k=(5, 9, 13)):
|
||||||
|
super().__init__()
|
||||||
|
c_ = c1 // 2 # hidden channels
|
||||||
|
self.cv1 = Conv(c1, c_, 1, 1)
|
||||||
|
self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)
|
||||||
|
self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
x = self.cv1(x)
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore') # suppress torch 1.9.0 max_pool2d() warning
|
||||||
|
return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))
|
||||||
|
|
||||||
|
|
||||||
|
class SPPF(nn.Module):
|
||||||
|
# Spatial Pyramid Pooling - Fast (SPPF) layer for by Glenn Jocher
|
||||||
|
def __init__(self, c1, c2, k=5): # equivalent to SPP(k=(5, 9, 13))
|
||||||
|
super().__init__()
|
||||||
|
c_ = c1 // 2 # hidden channels
|
||||||
|
self.cv1 = Conv(c1, c_, 1, 1)
|
||||||
|
self.cv2 = Conv(c_ * 4, c2, 1, 1)
|
||||||
|
self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
x = self.cv1(x)
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore') # suppress torch 1.9.0 max_pool2d() warning
|
||||||
|
y1 = self.m(x)
|
||||||
|
y2 = self.m(y1)
|
||||||
|
return self.cv2(torch.cat([x, y1, y2, self.m(y2)], 1))
|
||||||
|
|
||||||
|
|
||||||
|
class Focus(nn.Module):
|
||||||
|
# Focus wh information into c-space
|
||||||
|
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
|
||||||
|
super().__init__()
|
||||||
|
self.conv = Conv(c1 * 4, c2, k, s, p, g, act)
|
||||||
|
# self.contract = Contract(gain=2)
|
||||||
|
|
||||||
|
def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2)
|
||||||
|
return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))
|
||||||
|
# return self.conv(self.contract(x))
|
||||||
|
|
||||||
|
|
||||||
|
class GhostConv(nn.Module):
|
||||||
|
# Ghost Convolution https://github.com/huawei-noah/ghostnet
|
||||||
|
def __init__(self, c1, c2, k=1, s=1, g=1, act=True): # ch_in, ch_out, kernel, stride, groups
|
||||||
|
super().__init__()
|
||||||
|
c_ = c2 // 2 # hidden channels
|
||||||
|
self.cv1 = Conv(c1, c_, k, s, None, g, act)
|
||||||
|
self.cv2 = Conv(c_, c_, 5, 1, None, c_, act)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
y = self.cv1(x)
|
||||||
|
return torch.cat([y, self.cv2(y)], 1)
|
||||||
|
|
||||||
|
|
||||||
|
class GhostBottleneck(nn.Module):
|
||||||
|
# Ghost Bottleneck https://github.com/huawei-noah/ghostnet
|
||||||
|
def __init__(self, c1, c2, k=3, s=1): # ch_in, ch_out, kernel, stride
|
||||||
|
super().__init__()
|
||||||
|
c_ = c2 // 2
|
||||||
|
self.conv = nn.Sequential(GhostConv(c1, c_, 1, 1), # pw
|
||||||
|
DWConv(c_, c_, k, s, act=False) if s == 2 else nn.Identity(), # dw
|
||||||
|
GhostConv(c_, c2, 1, 1, act=False)) # pw-linear
|
||||||
|
self.shortcut = nn.Sequential(DWConv(c1, c1, k, s, act=False),
|
||||||
|
Conv(c1, c2, 1, 1, act=False)) if s == 2 else nn.Identity()
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
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):
|
||||||
|
super().__init__()
|
||||||
|
self.gain = gain
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
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)
|
||||||
|
x = x.permute(0, 3, 5, 1, 2, 4).contiguous() # x(1,2,2,64,40,40)
|
||||||
|
return x.view(b, c * s * s, h // s, w // s) # x(1,256,40,40)
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
super().__init__()
|
||||||
|
self.gain = gain
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
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)
|
||||||
|
x = x.permute(0, 3, 4, 1, 5, 2).contiguous() # x(1,16,80,2,80,2)
|
||||||
|
return x.view(b, c // s ** 2, h * s, w * s) # x(1,16,160,160)
|
||||||
|
|
||||||
|
|
||||||
|
class Concat(nn.Module):
|
||||||
|
# Concatenate a list of tensors along dimension
|
||||||
|
def __init__(self, dimension=1):
|
||||||
|
super().__init__()
|
||||||
|
self.d = dimension
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
return torch.cat(x, self.d)
|
||||||
|
|
||||||
|
|
||||||
|
class DetectMultiBackend(nn.Module):
|
||||||
|
# MultiBackend class for python inference on various backends
|
||||||
|
def __init__(self, weights='yolov3.pt', device=None, dnn=True):
|
||||||
|
# Usage:
|
||||||
|
# PyTorch: weights = *.pt
|
||||||
|
# TorchScript: *.torchscript.pt
|
||||||
|
# CoreML: *.mlmodel
|
||||||
|
# TensorFlow: *_saved_model
|
||||||
|
# TensorFlow: *.pb
|
||||||
|
# TensorFlow Lite: *.tflite
|
||||||
|
# ONNX Runtime: *.onnx
|
||||||
|
# OpenCV DNN: *.onnx with dnn=True
|
||||||
|
super().__init__()
|
||||||
|
w = str(weights[0] if isinstance(weights, list) else weights)
|
||||||
|
suffix, suffixes = Path(w).suffix.lower(), ['.pt', '.onnx', '.tflite', '.pb', '', '.mlmodel']
|
||||||
|
check_suffix(w, suffixes) # check weights have acceptable suffix
|
||||||
|
pt, onnx, tflite, pb, saved_model, coreml = (suffix == x for x in suffixes) # backend booleans
|
||||||
|
jit = pt and 'torchscript' in w.lower()
|
||||||
|
stride, names = 64, [f'class{i}' for i in range(1000)] # assign defaults
|
||||||
|
|
||||||
|
if jit: # TorchScript
|
||||||
|
LOGGER.info(f'Loading {w} for TorchScript inference...')
|
||||||
|
extra_files = {'config.txt': ''} # model metadata
|
||||||
|
model = torch.jit.load(w, _extra_files=extra_files)
|
||||||
|
if extra_files['config.txt']:
|
||||||
|
d = json.loads(extra_files['config.txt']) # extra_files dict
|
||||||
|
stride, names = int(d['stride']), d['names']
|
||||||
|
elif pt: # PyTorch
|
||||||
|
from models.experimental import attempt_load # scoped to avoid circular import
|
||||||
|
model = torch.jit.load(w) if 'torchscript' in w else attempt_load(weights, map_location=device)
|
||||||
|
stride = int(model.stride.max()) # model stride
|
||||||
|
names = model.module.names if hasattr(model, 'module') else model.names # get class names
|
||||||
|
elif coreml: # CoreML *.mlmodel
|
||||||
|
import coremltools as ct
|
||||||
|
model = ct.models.MLModel(w)
|
||||||
|
elif dnn: # ONNX OpenCV DNN
|
||||||
|
LOGGER.info(f'Loading {w} for ONNX OpenCV DNN inference...')
|
||||||
|
check_requirements(('opencv-python>=4.5.4',))
|
||||||
|
net = cv2.dnn.readNetFromONNX(w)
|
||||||
|
elif onnx: # ONNX Runtime
|
||||||
|
LOGGER.info(f'Loading {w} for ONNX Runtime inference...')
|
||||||
|
cuda = torch.cuda.is_available()
|
||||||
|
check_requirements(('onnx', 'onnxruntime-gpu' if cuda else 'onnxruntime'))
|
||||||
|
import onnxruntime
|
||||||
|
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider'] if cuda else ['CPUExecutionProvider']
|
||||||
|
session = onnxruntime.InferenceSession(w, providers=providers)
|
||||||
|
else: # TensorFlow model (TFLite, pb, saved_model)
|
||||||
|
import tensorflow as tf
|
||||||
|
if pb: # https://www.tensorflow.org/guide/migrate#a_graphpb_or_graphpbtxt
|
||||||
|
def wrap_frozen_graph(gd, inputs, outputs):
|
||||||
|
x = tf.compat.v1.wrap_function(lambda: tf.compat.v1.import_graph_def(gd, name=""), []) # wrapped
|
||||||
|
return x.prune(tf.nest.map_structure(x.graph.as_graph_element, inputs),
|
||||||
|
tf.nest.map_structure(x.graph.as_graph_element, outputs))
|
||||||
|
|
||||||
|
LOGGER.info(f'Loading {w} for TensorFlow *.pb inference...')
|
||||||
|
graph_def = tf.Graph().as_graph_def()
|
||||||
|
graph_def.ParseFromString(open(w, 'rb').read())
|
||||||
|
frozen_func = wrap_frozen_graph(gd=graph_def, inputs="x:0", outputs="Identity:0")
|
||||||
|
elif saved_model:
|
||||||
|
LOGGER.info(f'Loading {w} for TensorFlow saved_model inference...')
|
||||||
|
model = tf.keras.models.load_model(w)
|
||||||
|
elif tflite: # https://www.tensorflow.org/lite/guide/python#install_tensorflow_lite_for_python
|
||||||
|
if 'edgetpu' in w.lower():
|
||||||
|
LOGGER.info(f'Loading {w} for TensorFlow Edge TPU inference...')
|
||||||
|
import tflite_runtime.interpreter as tfli
|
||||||
|
delegate = {'Linux': 'libedgetpu.so.1', # install https://coral.ai/software/#edgetpu-runtime
|
||||||
|
'Darwin': 'libedgetpu.1.dylib',
|
||||||
|
'Windows': 'edgetpu.dll'}[platform.system()]
|
||||||
|
interpreter = tfli.Interpreter(model_path=w, experimental_delegates=[tfli.load_delegate(delegate)])
|
||||||
|
else:
|
||||||
|
LOGGER.info(f'Loading {w} for TensorFlow Lite inference...')
|
||||||
|
interpreter = tf.lite.Interpreter(model_path=w) # load TFLite model
|
||||||
|
interpreter.allocate_tensors() # allocate
|
||||||
|
input_details = interpreter.get_input_details() # inputs
|
||||||
|
output_details = interpreter.get_output_details() # outputs
|
||||||
|
self.__dict__.update(locals()) # assign all variables to self
|
||||||
|
|
||||||
|
def forward(self, im, augment=False, visualize=False, val=False):
|
||||||
|
# MultiBackend inference
|
||||||
|
b, ch, h, w = im.shape # batch, channel, height, width
|
||||||
|
if self.pt: # PyTorch
|
||||||
|
y = self.model(im) if self.jit else self.model(im, augment=augment, visualize=visualize)
|
||||||
|
return y if val else y[0]
|
||||||
|
elif self.coreml: # CoreML *.mlmodel
|
||||||
|
im = im.permute(0, 2, 3, 1).cpu().numpy() # torch BCHW to numpy BHWC shape(1,320,192,3)
|
||||||
|
im = Image.fromarray((im[0] * 255).astype('uint8'))
|
||||||
|
# im = im.resize((192, 320), Image.ANTIALIAS)
|
||||||
|
y = self.model.predict({'image': im}) # coordinates are xywh normalized
|
||||||
|
box = xywh2xyxy(y['coordinates'] * [[w, h, w, h]]) # xyxy pixels
|
||||||
|
conf, cls = y['confidence'].max(1), y['confidence'].argmax(1).astype(np.float)
|
||||||
|
y = np.concatenate((box, conf.reshape(-1, 1), cls.reshape(-1, 1)), 1)
|
||||||
|
elif self.onnx: # ONNX
|
||||||
|
im = im.cpu().numpy() # torch to numpy
|
||||||
|
if self.dnn: # ONNX OpenCV DNN
|
||||||
|
self.net.setInput(im)
|
||||||
|
y = self.net.forward()
|
||||||
|
else: # ONNX Runtime
|
||||||
|
y = self.session.run([self.session.get_outputs()[0].name], {self.session.get_inputs()[0].name: im})[0]
|
||||||
|
else: # TensorFlow model (TFLite, pb, saved_model)
|
||||||
|
im = im.permute(0, 2, 3, 1).cpu().numpy() # torch BCHW to numpy BHWC shape(1,320,192,3)
|
||||||
|
if self.pb:
|
||||||
|
y = self.frozen_func(x=self.tf.constant(im)).numpy()
|
||||||
|
elif self.saved_model:
|
||||||
|
y = self.model(im, training=False).numpy()
|
||||||
|
elif self.tflite:
|
||||||
|
input, output = self.input_details[0], self.output_details[0]
|
||||||
|
int8 = input['dtype'] == np.uint8 # is TFLite quantized uint8 model
|
||||||
|
if int8:
|
||||||
|
scale, zero_point = input['quantization']
|
||||||
|
im = (im / scale + zero_point).astype(np.uint8) # de-scale
|
||||||
|
self.interpreter.set_tensor(input['index'], im)
|
||||||
|
self.interpreter.invoke()
|
||||||
|
y = self.interpreter.get_tensor(output['index'])
|
||||||
|
if int8:
|
||||||
|
scale, zero_point = output['quantization']
|
||||||
|
y = (y.astype(np.float32) - zero_point) * scale # re-scale
|
||||||
|
y[..., 0] *= w # x
|
||||||
|
y[..., 1] *= h # y
|
||||||
|
y[..., 2] *= w # w
|
||||||
|
y[..., 3] *= h # h
|
||||||
|
y = torch.tensor(y)
|
||||||
|
return (y, []) if val else y
|
||||||
|
|
||||||
|
|
||||||
|
class AutoShape(nn.Module):
|
||||||
|
# input-robust model wrapper for passing cv2/np/PIL/torch inputs. Includes preprocessing, inference and NMS
|
||||||
|
conf = 0.25 # NMS confidence threshold
|
||||||
|
iou = 0.45 # NMS IoU threshold
|
||||||
|
classes = None # (optional list) filter by class, i.e. = [0, 15, 16] for COCO persons, cats and dogs
|
||||||
|
multi_label = False # NMS multiple labels per box
|
||||||
|
max_det = 1000 # maximum number of detections per image
|
||||||
|
|
||||||
|
def __init__(self, model):
|
||||||
|
super().__init__()
|
||||||
|
self.model = model.eval()
|
||||||
|
|
||||||
|
def autoshape(self):
|
||||||
|
LOGGER.info('AutoShape already enabled, skipping... ') # model already converted to model.autoshape()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _apply(self, fn):
|
||||||
|
# Apply to(), cpu(), cuda(), half() to model tensors that are not parameters or registered buffers
|
||||||
|
self = super()._apply(fn)
|
||||||
|
m = self.model.model[-1] # Detect()
|
||||||
|
m.stride = fn(m.stride)
|
||||||
|
m.grid = list(map(fn, m.grid))
|
||||||
|
if isinstance(m.anchor_grid, list):
|
||||||
|
m.anchor_grid = list(map(fn, m.anchor_grid))
|
||||||
|
return self
|
||||||
|
|
||||||
|
@torch.no_grad()
|
||||||
|
def forward(self, imgs, size=640, augment=False, profile=False):
|
||||||
|
# Inference from various sources. For height=640, width=1280, RGB images example inputs are:
|
||||||
|
# file: imgs = '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)
|
||||||
|
# PIL: = Image.open('image.jpg') or ImageGrab.grab() # HWC x(640,1280,3)
|
||||||
|
# numpy: = np.zeros((640,1280,3)) # HWC
|
||||||
|
# torch: = torch.zeros(16,3,320,640) # BCHW (scaled to size=640, 0-1 values)
|
||||||
|
# multiple: = [Image.open('image1.jpg'), Image.open('image2.jpg'), ...] # list of images
|
||||||
|
|
||||||
|
t = [time_sync()]
|
||||||
|
p = next(self.model.parameters()) # for device and type
|
||||||
|
if isinstance(imgs, torch.Tensor): # torch
|
||||||
|
with amp.autocast(enabled=p.device.type != 'cpu'):
|
||||||
|
return self.model(imgs.to(p.device).type_as(p), augment, profile) # inference
|
||||||
|
|
||||||
|
# Pre-process
|
||||||
|
n, imgs = (len(imgs), imgs) if isinstance(imgs, list) else (1, [imgs]) # number of images, list of images
|
||||||
|
shape0, shape1, files = [], [], [] # image and inference shapes, filenames
|
||||||
|
for i, im in enumerate(imgs):
|
||||||
|
f = f'image{i}' # filename
|
||||||
|
if isinstance(im, (str, Path)): # filename or uri
|
||||||
|
im, f = Image.open(requests.get(im, stream=True).raw if str(im).startswith('http') else im), im
|
||||||
|
im = np.asarray(exif_transpose(im))
|
||||||
|
elif isinstance(im, Image.Image): # PIL Image
|
||||||
|
im, f = np.asarray(exif_transpose(im)), getattr(im, 'filename', f) or f
|
||||||
|
files.append(Path(f).with_suffix('.jpg').name)
|
||||||
|
if im.shape[0] < 5: # image in CHW
|
||||||
|
im = im.transpose((1, 2, 0)) # reverse dataloader .transpose(2, 0, 1)
|
||||||
|
im = im[..., :3] if im.ndim == 3 else np.tile(im[..., None], 3) # enforce 3ch input
|
||||||
|
s = im.shape[:2] # HWC
|
||||||
|
shape0.append(s) # image shape
|
||||||
|
g = (size / max(s)) # gain
|
||||||
|
shape1.append([y * g for y in s])
|
||||||
|
imgs[i] = im if im.data.contiguous else np.ascontiguousarray(im) # update
|
||||||
|
shape1 = [make_divisible(x, int(self.stride.max())) for x in np.stack(shape1, 0).max(0)] # inference shape
|
||||||
|
x = [letterbox(im, new_shape=shape1, auto=False)[0] for im in imgs] # pad
|
||||||
|
x = np.stack(x, 0) if n > 1 else x[0][None] # stack
|
||||||
|
x = np.ascontiguousarray(x.transpose((0, 3, 1, 2))) # BHWC to BCHW
|
||||||
|
x = torch.from_numpy(x).to(p.device).type_as(p) / 255 # uint8 to fp16/32
|
||||||
|
t.append(time_sync())
|
||||||
|
|
||||||
|
with amp.autocast(enabled=p.device.type != 'cpu'):
|
||||||
|
# Inference
|
||||||
|
y = self.model(x, augment, profile)[0] # forward
|
||||||
|
t.append(time_sync())
|
||||||
|
|
||||||
|
# Post-process
|
||||||
|
y = non_max_suppression(y, self.conf, iou_thres=self.iou, classes=self.classes,
|
||||||
|
multi_label=self.multi_label, max_det=self.max_det) # NMS
|
||||||
|
for i in range(n):
|
||||||
|
scale_coords(shape1, y[i][:, :4], shape0[i])
|
||||||
|
|
||||||
|
t.append(time_sync())
|
||||||
|
return Detections(imgs, y, files, t, self.names, x.shape)
|
||||||
|
|
||||||
|
|
||||||
|
class Detections:
|
||||||
|
# detections class for inference results
|
||||||
|
def __init__(self, imgs, pred, files, times=None, names=None, shape=None):
|
||||||
|
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 imgs] # normalizations
|
||||||
|
self.imgs = imgs # list of images as numpy arrays
|
||||||
|
self.pred = pred # list of tensors pred[0] = (xyxy, conf, cls)
|
||||||
|
self.names = names # class names
|
||||||
|
self.files = files # image filenames
|
||||||
|
self.xyxy = pred # xyxy pixels
|
||||||
|
self.xywh = [xyxy2xywh(x) for x in pred] # xywh pixels
|
||||||
|
self.xyxyn = [x / g for x, g in zip(self.xyxy, gn)] # xyxy normalized
|
||||||
|
self.xywhn = [x / g for x, g in zip(self.xywh, gn)] # xywh normalized
|
||||||
|
self.n = len(self.pred) # number of images (batch size)
|
||||||
|
self.t = tuple((times[i + 1] - times[i]) * 1000 / self.n for i in range(3)) # timestamps (ms)
|
||||||
|
self.s = shape # inference BCHW shape
|
||||||
|
|
||||||
|
def display(self, pprint=False, show=False, save=False, crop=False, render=False, save_dir=Path('')):
|
||||||
|
crops = []
|
||||||
|
for i, (im, pred) in enumerate(zip(self.imgs, self.pred)):
|
||||||
|
s = f'image {i + 1}/{len(self.pred)}: {im.shape[0]}x{im.shape[1]} ' # string
|
||||||
|
if pred.shape[0]:
|
||||||
|
for c in pred[:, -1].unique():
|
||||||
|
n = (pred[:, -1] == c).sum() # detections per class
|
||||||
|
s += f"{n} {self.names[int(c)]}{'s' * (n > 1)}, " # add to string
|
||||||
|
if show or save or render or crop:
|
||||||
|
annotator = Annotator(im, example=str(self.names))
|
||||||
|
for *box, conf, cls in reversed(pred): # xyxy, confidence, class
|
||||||
|
label = f'{self.names[int(cls)]} {conf:.2f}'
|
||||||
|
if crop:
|
||||||
|
file = save_dir / 'crops' / self.names[int(cls)] / self.files[i] if save else None
|
||||||
|
crops.append({'box': box, 'conf': conf, 'cls': cls, 'label': label,
|
||||||
|
'im': save_one_box(box, im, file=file, save=save)})
|
||||||
|
else: # all others
|
||||||
|
annotator.box_label(box, label, color=colors(cls))
|
||||||
|
im = annotator.im
|
||||||
|
else:
|
||||||
|
s += '(no detections)'
|
||||||
|
|
||||||
|
im = Image.fromarray(im.astype(np.uint8)) if isinstance(im, np.ndarray) else im # from np
|
||||||
|
if pprint:
|
||||||
|
LOGGER.info(s.rstrip(', '))
|
||||||
|
if show:
|
||||||
|
im.show(self.files[i]) # show
|
||||||
|
if save:
|
||||||
|
f = self.files[i]
|
||||||
|
im.save(save_dir / f) # save
|
||||||
|
if i == self.n - 1:
|
||||||
|
LOGGER.info(f"Saved {self.n} image{'s' * (self.n > 1)} to {colorstr('bold', save_dir)}")
|
||||||
|
if render:
|
||||||
|
self.imgs[i] = np.asarray(im)
|
||||||
|
if crop:
|
||||||
|
if save:
|
||||||
|
LOGGER.info(f'Saved results to {save_dir}\n')
|
||||||
|
return crops
|
||||||
|
|
||||||
|
def print(self):
|
||||||
|
self.display(pprint=True) # print results
|
||||||
|
LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {tuple(self.s)}' %
|
||||||
|
self.t)
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
self.display(show=True) # show results
|
||||||
|
|
||||||
|
def save(self, save_dir='runs/detect/exp'):
|
||||||
|
save_dir = increment_path(save_dir, exist_ok=save_dir != 'runs/detect/exp', mkdir=True) # increment save_dir
|
||||||
|
self.display(save=True, save_dir=save_dir) # save results
|
||||||
|
|
||||||
|
def crop(self, save=True, save_dir='runs/detect/exp'):
|
||||||
|
save_dir = increment_path(save_dir, exist_ok=save_dir != 'runs/detect/exp', mkdir=True) if save else None
|
||||||
|
return self.display(crop=True, save=save, save_dir=save_dir) # crop results
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
self.display(render=True) # render results
|
||||||
|
return self.imgs
|
||||||
|
|
||||||
|
def pandas(self):
|
||||||
|
# return detections as pandas DataFrames, i.e. 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
|
||||||
|
for k, c in zip(['xyxy', 'xyxyn', 'xywh', 'xywhn'], [ca, ca, cb, cb]):
|
||||||
|
a = [[x[:5] + [int(x[5]), self.names[int(x[5])]] for x in x.tolist()] for x in getattr(self, k)] # update
|
||||||
|
setattr(new, k, [pd.DataFrame(x, columns=c) for x in a])
|
||||||
|
return new
|
||||||
|
|
||||||
|
def tolist(self):
|
||||||
|
# return a list of Detections objects, i.e. 'for result in results.tolist():'
|
||||||
|
x = [Detections([self.imgs[i]], [self.pred[i]], self.names, self.s) for i in range(self.n)]
|
||||||
|
for d in x:
|
||||||
|
for k in ['imgs', 'pred', 'xyxy', 'xyxyn', 'xywh', 'xywhn']:
|
||||||
|
setattr(d, k, getattr(d, k)[0]) # pop out of list
|
||||||
|
return x
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.n
|
||||||
|
|
||||||
|
|
||||||
|
class Classify(nn.Module):
|
||||||
|
# Classification head, i.e. x(b,c1,20,20) to x(b,c2)
|
||||||
|
def __init__(self, c1, c2, k=1, s=1, p=None, g=1): # ch_in, ch_out, kernel, stride, padding, groups
|
||||||
|
super().__init__()
|
||||||
|
self.aap = nn.AdaptiveAvgPool2d(1) # to x(b,c1,1,1)
|
||||||
|
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g) # to x(b,c2,1,1)
|
||||||
|
self.flat = nn.Flatten()
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
z = torch.cat([self.aap(y) for y in (x if isinstance(x, list) else [x])], 1) # cat if list
|
||||||
|
return self.flat(self.conv(z)) # flatten to x(b,c2)
|
||||||
121
ros2_ws/src/yolov3_ros/models/experimental.py
Normal file
121
ros2_ws/src/yolov3_ros/models/experimental.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Experimental modules
|
||||||
|
"""
|
||||||
|
import math
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
import torch.nn as nn
|
||||||
|
|
||||||
|
from models.common import Conv
|
||||||
|
from utils.downloads import attempt_download
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
super().__init__()
|
||||||
|
c_ = int(c2 * e) # hidden channels
|
||||||
|
self.cv1 = Conv(c1, c_, (1, k), (1, s))
|
||||||
|
self.cv2 = Conv(c_, c2, (k, 1), (s, 1), g=g)
|
||||||
|
self.add = shortcut and c1 == c2
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
|
||||||
|
|
||||||
|
|
||||||
|
class Sum(nn.Module):
|
||||||
|
# Weighted sum of 2 or more layers https://arxiv.org/abs/1911.09070
|
||||||
|
def __init__(self, n, weight=False): # n: number of inputs
|
||||||
|
super().__init__()
|
||||||
|
self.weight = weight # apply weights boolean
|
||||||
|
self.iter = range(n - 1) # iter object
|
||||||
|
if weight:
|
||||||
|
self.w = nn.Parameter(-torch.arange(1.0, n) / 2, requires_grad=True) # layer weights
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
y = x[0] # no weight
|
||||||
|
if self.weight:
|
||||||
|
w = torch.sigmoid(self.w) * 2
|
||||||
|
for i in self.iter:
|
||||||
|
y = y + x[i + 1] * w[i]
|
||||||
|
else:
|
||||||
|
for i in self.iter:
|
||||||
|
y = y + x[i + 1]
|
||||||
|
return y
|
||||||
|
|
||||||
|
|
||||||
|
class MixConv2d(nn.Module):
|
||||||
|
# Mixed Depth-wise Conv https://arxiv.org/abs/1907.09595
|
||||||
|
def __init__(self, c1, c2, k=(1, 3), s=1, equal_ch=True): # ch_in, ch_out, kernel, stride, ch_strategy
|
||||||
|
super().__init__()
|
||||||
|
n = len(k) # number of convolutions
|
||||||
|
if equal_ch: # equal c_ per group
|
||||||
|
i = torch.linspace(0, n - 1E-6, c2).floor() # c2 indices
|
||||||
|
c_ = [(i == g).sum() for g in range(n)] # intermediate channels
|
||||||
|
else: # equal weight.numel() per group
|
||||||
|
b = [c2] + [0] * n
|
||||||
|
a = np.eye(n + 1, n, k=-1)
|
||||||
|
a -= np.roll(a, 1, axis=1)
|
||||||
|
a *= np.array(k) ** 2
|
||||||
|
a[0] = 1
|
||||||
|
c_ = np.linalg.lstsq(a, b, rcond=None)[0].round() # solve for equal weight indices, ax = b
|
||||||
|
|
||||||
|
self.m = nn.ModuleList(
|
||||||
|
[nn.Conv2d(c1, int(c_), k, s, k // 2, groups=math.gcd(c1, int(c_)), bias=False) for k, c_ in zip(k, c_)])
|
||||||
|
self.bn = nn.BatchNorm2d(c2)
|
||||||
|
self.act = nn.SiLU()
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
return self.act(self.bn(torch.cat([m(x) for m in self.m], 1)))
|
||||||
|
|
||||||
|
|
||||||
|
class Ensemble(nn.ModuleList):
|
||||||
|
# Ensemble of models
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def forward(self, x, augment=False, profile=False, visualize=False):
|
||||||
|
y = []
|
||||||
|
for module in self:
|
||||||
|
y.append(module(x, augment, profile, visualize)[0])
|
||||||
|
# y = torch.stack(y).max(0)[0] # max ensemble
|
||||||
|
# y = torch.stack(y).mean(0) # mean ensemble
|
||||||
|
y = torch.cat(y, 1) # nms ensemble
|
||||||
|
return y, None # inference, train output
|
||||||
|
|
||||||
|
|
||||||
|
def attempt_load(weights, map_location=None, inplace=True, fuse=True):
|
||||||
|
from models.yolo import Detect, Model
|
||||||
|
|
||||||
|
# Loads an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a
|
||||||
|
model = Ensemble()
|
||||||
|
for w in weights if isinstance(weights, list) else [weights]:
|
||||||
|
ckpt = torch.load(attempt_download(w), map_location=map_location) # load
|
||||||
|
ckpt = (ckpt['ema'] or ckpt['model']).float() # FP32 model
|
||||||
|
model.append(ckpt.fuse().eval() if fuse else ckpt.eval()) # fused or un-fused model in eval mode
|
||||||
|
|
||||||
|
# Compatibility updates
|
||||||
|
for m in model.modules():
|
||||||
|
t = type(m)
|
||||||
|
if t in (nn.Hardswish, nn.LeakyReLU, nn.ReLU, nn.ReLU6, nn.SiLU, Detect, Model):
|
||||||
|
m.inplace = inplace # torch 1.7.0 compatibility
|
||||||
|
if t is Detect:
|
||||||
|
if not isinstance(m.anchor_grid, list): # new Detect Layer compatibility
|
||||||
|
delattr(m, 'anchor_grid')
|
||||||
|
setattr(m, 'anchor_grid', [torch.zeros(1)] * m.nl)
|
||||||
|
elif t is Conv:
|
||||||
|
m._non_persistent_buffers_set = set() # torch 1.6.0 compatibility
|
||||||
|
elif t is nn.Upsample and not hasattr(m, 'recompute_scale_factor'):
|
||||||
|
m.recompute_scale_factor = None # torch 1.11.0 compatibility
|
||||||
|
|
||||||
|
if len(model) == 1:
|
||||||
|
return model[-1] # return model
|
||||||
|
else:
|
||||||
|
print(f'Ensemble created with {weights}\n')
|
||||||
|
for k in ['names']:
|
||||||
|
setattr(model, k, getattr(model[-1], k))
|
||||||
|
model.stride = model[torch.argmax(torch.tensor([m.stride.max() for m in model])).int()].stride # max stride
|
||||||
|
return model # return ensemble
|
||||||
59
ros2_ws/src/yolov3_ros/models/hub/anchors.yaml
Normal file
59
ros2_ws/src/yolov3_ros/models/hub/anchors.yaml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
# Default anchors for COCO data
|
||||||
|
|
||||||
|
|
||||||
|
# P5 -------------------------------------------------------------------------------------------------------------------
|
||||||
|
# P5-640:
|
||||||
|
anchors_p5_640:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
|
||||||
|
# P6 -------------------------------------------------------------------------------------------------------------------
|
||||||
|
# P6-640: thr=0.25: 0.9964 BPR, 5.54 anchors past thr, n=12, img_size=640, metric_all=0.281/0.716-mean/best, past_thr=0.469-mean: 9,11, 21,19, 17,41, 43,32, 39,70, 86,64, 65,131, 134,130, 120,265, 282,180, 247,354, 512,387
|
||||||
|
anchors_p6_640:
|
||||||
|
- [9,11, 21,19, 17,41] # P3/8
|
||||||
|
- [43,32, 39,70, 86,64] # P4/16
|
||||||
|
- [65,131, 134,130, 120,265] # P5/32
|
||||||
|
- [282,180, 247,354, 512,387] # P6/64
|
||||||
|
|
||||||
|
# P6-1280: thr=0.25: 0.9950 BPR, 5.55 anchors past thr, n=12, img_size=1280, metric_all=0.281/0.714-mean/best, past_thr=0.468-mean: 19,27, 44,40, 38,94, 96,68, 86,152, 180,137, 140,301, 303,264, 238,542, 436,615, 739,380, 925,792
|
||||||
|
anchors_p6_1280:
|
||||||
|
- [19,27, 44,40, 38,94] # P3/8
|
||||||
|
- [96,68, 86,152, 180,137] # P4/16
|
||||||
|
- [140,301, 303,264, 238,542] # P5/32
|
||||||
|
- [436,615, 739,380, 925,792] # P6/64
|
||||||
|
|
||||||
|
# P6-1920: thr=0.25: 0.9950 BPR, 5.55 anchors past thr, n=12, img_size=1920, metric_all=0.281/0.714-mean/best, past_thr=0.468-mean: 28,41, 67,59, 57,141, 144,103, 129,227, 270,205, 209,452, 455,396, 358,812, 653,922, 1109,570, 1387,1187
|
||||||
|
anchors_p6_1920:
|
||||||
|
- [28,41, 67,59, 57,141] # P3/8
|
||||||
|
- [144,103, 129,227, 270,205] # P4/16
|
||||||
|
- [209,452, 455,396, 358,812] # P5/32
|
||||||
|
- [653,922, 1109,570, 1387,1187] # P6/64
|
||||||
|
|
||||||
|
|
||||||
|
# P7 -------------------------------------------------------------------------------------------------------------------
|
||||||
|
# P7-640: thr=0.25: 0.9962 BPR, 6.76 anchors past thr, n=15, img_size=640, metric_all=0.275/0.733-mean/best, past_thr=0.466-mean: 11,11, 13,30, 29,20, 30,46, 61,38, 39,92, 78,80, 146,66, 79,163, 149,150, 321,143, 157,303, 257,402, 359,290, 524,372
|
||||||
|
anchors_p7_640:
|
||||||
|
- [11,11, 13,30, 29,20] # P3/8
|
||||||
|
- [30,46, 61,38, 39,92] # P4/16
|
||||||
|
- [78,80, 146,66, 79,163] # P5/32
|
||||||
|
- [149,150, 321,143, 157,303] # P6/64
|
||||||
|
- [257,402, 359,290, 524,372] # P7/128
|
||||||
|
|
||||||
|
# P7-1280: thr=0.25: 0.9968 BPR, 6.71 anchors past thr, n=15, img_size=1280, metric_all=0.273/0.732-mean/best, past_thr=0.463-mean: 19,22, 54,36, 32,77, 70,83, 138,71, 75,173, 165,159, 148,334, 375,151, 334,317, 251,626, 499,474, 750,326, 534,814, 1079,818
|
||||||
|
anchors_p7_1280:
|
||||||
|
- [19,22, 54,36, 32,77] # P3/8
|
||||||
|
- [70,83, 138,71, 75,173] # P4/16
|
||||||
|
- [165,159, 148,334, 375,151] # P5/32
|
||||||
|
- [334,317, 251,626, 499,474] # P6/64
|
||||||
|
- [750,326, 534,814, 1079,818] # P7/128
|
||||||
|
|
||||||
|
# P7-1920: thr=0.25: 0.9968 BPR, 6.71 anchors past thr, n=15, img_size=1920, metric_all=0.273/0.732-mean/best, past_thr=0.463-mean: 29,34, 81,55, 47,115, 105,124, 207,107, 113,259, 247,238, 222,500, 563,227, 501,476, 376,939, 749,711, 1126,489, 801,1222, 1618,1227
|
||||||
|
anchors_p7_1920:
|
||||||
|
- [29,34, 81,55, 47,115] # P3/8
|
||||||
|
- [105,124, 207,107, 113,259] # P4/16
|
||||||
|
- [247,238, 222,500, 563,227] # P5/32
|
||||||
|
- [501,476, 376,939, 749,711] # P6/64
|
||||||
|
- [1126,489, 801,1222, 1618,1227] # P7/128
|
||||||
48
ros2_ws/src/yolov3_ros/models/hub/yolov5-bifpn.yaml
Normal file
48
ros2_ws/src/yolov3_ros/models/hub/yolov5-bifpn.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 1.0 # model depth multiple
|
||||||
|
width_multiple: 1.0 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 BiFPN head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14, 6], 1, Concat, [1]], # cat P4 <--- BiFPN change
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
42
ros2_ws/src/yolov3_ros/models/hub/yolov5-fpn.yaml
Normal file
42
ros2_ws/src/yolov3_ros/models/hub/yolov5-fpn.yaml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 1.0 # model depth multiple
|
||||||
|
width_multiple: 1.0 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 FPN head
|
||||||
|
head:
|
||||||
|
[[-1, 3, C3, [1024, False]], # 10 (P5/32-large)
|
||||||
|
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 3, C3, [512, False]], # 14 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 3, C3, [256, False]], # 18 (P3/8-small)
|
||||||
|
|
||||||
|
[[18, 14, 10], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
54
ros2_ws/src/yolov3_ros/models/hub/yolov5-p2.yaml
Normal file
54
ros2_ws/src/yolov3_ros/models/hub/yolov5-p2.yaml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 1.0 # model depth multiple
|
||||||
|
width_multiple: 1.0 # layer channel multiple
|
||||||
|
anchors: 3 # AutoAnchor evolves 3 anchors per P output layer
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head with (P2, P3, P4, P5) outputs
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [128, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 2], 1, Concat, [1]], # cat backbone P2
|
||||||
|
[-1, 1, C3, [128, False]], # 21 (P2/4-xsmall)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [128, 3, 2]],
|
||||||
|
[[-1, 18], 1, Concat, [1]], # cat head P3
|
||||||
|
[-1, 3, C3, [256, False]], # 24 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 27 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 30 (P5/32-large)
|
||||||
|
|
||||||
|
[[21, 24, 27, 30], 1, Detect, [nc, anchors]], # Detect(P2, P3, P4, P5)
|
||||||
|
]
|
||||||
41
ros2_ws/src/yolov3_ros/models/hub/yolov5-p34.yaml
Normal file
41
ros2_ws/src/yolov3_ros/models/hub/yolov5-p34.yaml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 0.33 # model depth multiple
|
||||||
|
width_multiple: 0.50 # layer channel multiple
|
||||||
|
anchors: 3 # AutoAnchor evolves 3 anchors per P output layer
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head with (P3, P4) outputs
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[[17, 20], 1, Detect, [nc, anchors]], # Detect(P3, P4)
|
||||||
|
]
|
||||||
56
ros2_ws/src/yolov3_ros/models/hub/yolov5-p6.yaml
Normal file
56
ros2_ws/src/yolov3_ros/models/hub/yolov5-p6.yaml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 1.0 # model depth multiple
|
||||||
|
width_multiple: 1.0 # layer channel multiple
|
||||||
|
anchors: 3 # AutoAnchor evolves 3 anchors per P output layer
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [768, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [768]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 9-P6/64
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 11
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head with (P3, P4, P5, P6) outputs
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [768, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 8], 1, Concat, [1]], # cat backbone P5
|
||||||
|
[-1, 3, C3, [768, False]], # 15
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 19
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 23 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 20], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 26 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 16], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [768, False]], # 29 (P5/32-large)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [768, 3, 2]],
|
||||||
|
[[-1, 12], 1, Concat, [1]], # cat head P6
|
||||||
|
[-1, 3, C3, [1024, False]], # 32 (P6/64-xlarge)
|
||||||
|
|
||||||
|
[[23, 26, 29, 32], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5, P6)
|
||||||
|
]
|
||||||
67
ros2_ws/src/yolov3_ros/models/hub/yolov5-p7.yaml
Normal file
67
ros2_ws/src/yolov3_ros/models/hub/yolov5-p7.yaml
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 1.0 # model depth multiple
|
||||||
|
width_multiple: 1.0 # layer channel multiple
|
||||||
|
anchors: 3 # AutoAnchor evolves 3 anchors per P output layer
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [768, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [768]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 9-P6/64
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, Conv, [1280, 3, 2]], # 11-P7/128
|
||||||
|
[-1, 3, C3, [1280]],
|
||||||
|
[-1, 1, SPPF, [1280, 5]], # 13
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head with (P3, P4, P5, P6, P7) outputs
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [1024, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat backbone P6
|
||||||
|
[-1, 3, C3, [1024, False]], # 17
|
||||||
|
|
||||||
|
[-1, 1, Conv, [768, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 8], 1, Concat, [1]], # cat backbone P5
|
||||||
|
[-1, 3, C3, [768, False]], # 21
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 25
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 29 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 26], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 32 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 22], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [768, False]], # 35 (P5/32-large)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [768, 3, 2]],
|
||||||
|
[[-1, 18], 1, Concat, [1]], # cat head P6
|
||||||
|
[-1, 3, C3, [1024, False]], # 38 (P6/64-xlarge)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P7
|
||||||
|
[-1, 3, C3, [1280, False]], # 41 (P7/128-xxlarge)
|
||||||
|
|
||||||
|
[[29, 32, 35, 38, 41], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5, P6, P7)
|
||||||
|
]
|
||||||
48
ros2_ws/src/yolov3_ros/models/hub/yolov5-panet.yaml
Normal file
48
ros2_ws/src/yolov3_ros/models/hub/yolov5-panet.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 1.0 # model depth multiple
|
||||||
|
width_multiple: 1.0 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 PANet head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
60
ros2_ws/src/yolov3_ros/models/hub/yolov5l6.yaml
Normal file
60
ros2_ws/src/yolov3_ros/models/hub/yolov5l6.yaml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 1.0 # model depth multiple
|
||||||
|
width_multiple: 1.0 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [19,27, 44,40, 38,94] # P3/8
|
||||||
|
- [96,68, 86,152, 180,137] # P4/16
|
||||||
|
- [140,301, 303,264, 238,542] # P5/32
|
||||||
|
- [436,615, 739,380, 925,792] # P6/64
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [768, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [768]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 9-P6/64
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 11
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [768, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 8], 1, Concat, [1]], # cat backbone P5
|
||||||
|
[-1, 3, C3, [768, False]], # 15
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 19
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 23 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 20], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 26 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 16], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [768, False]], # 29 (P5/32-large)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [768, 3, 2]],
|
||||||
|
[[-1, 12], 1, Concat, [1]], # cat head P6
|
||||||
|
[-1, 3, C3, [1024, False]], # 32 (P6/64-xlarge)
|
||||||
|
|
||||||
|
[[23, 26, 29, 32], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5, P6)
|
||||||
|
]
|
||||||
60
ros2_ws/src/yolov3_ros/models/hub/yolov5m6.yaml
Normal file
60
ros2_ws/src/yolov3_ros/models/hub/yolov5m6.yaml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 0.67 # model depth multiple
|
||||||
|
width_multiple: 0.75 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [19,27, 44,40, 38,94] # P3/8
|
||||||
|
- [96,68, 86,152, 180,137] # P4/16
|
||||||
|
- [140,301, 303,264, 238,542] # P5/32
|
||||||
|
- [436,615, 739,380, 925,792] # P6/64
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [768, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [768]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 9-P6/64
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 11
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [768, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 8], 1, Concat, [1]], # cat backbone P5
|
||||||
|
[-1, 3, C3, [768, False]], # 15
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 19
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 23 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 20], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 26 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 16], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [768, False]], # 29 (P5/32-large)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [768, 3, 2]],
|
||||||
|
[[-1, 12], 1, Concat, [1]], # cat head P6
|
||||||
|
[-1, 3, C3, [1024, False]], # 32 (P6/64-xlarge)
|
||||||
|
|
||||||
|
[[23, 26, 29, 32], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5, P6)
|
||||||
|
]
|
||||||
60
ros2_ws/src/yolov3_ros/models/hub/yolov5n6.yaml
Normal file
60
ros2_ws/src/yolov3_ros/models/hub/yolov5n6.yaml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 0.33 # model depth multiple
|
||||||
|
width_multiple: 0.25 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [19,27, 44,40, 38,94] # P3/8
|
||||||
|
- [96,68, 86,152, 180,137] # P4/16
|
||||||
|
- [140,301, 303,264, 238,542] # P5/32
|
||||||
|
- [436,615, 739,380, 925,792] # P6/64
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [768, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [768]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 9-P6/64
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 11
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [768, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 8], 1, Concat, [1]], # cat backbone P5
|
||||||
|
[-1, 3, C3, [768, False]], # 15
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 19
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 23 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 20], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 26 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 16], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [768, False]], # 29 (P5/32-large)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [768, 3, 2]],
|
||||||
|
[[-1, 12], 1, Concat, [1]], # cat head P6
|
||||||
|
[-1, 3, C3, [1024, False]], # 32 (P6/64-xlarge)
|
||||||
|
|
||||||
|
[[23, 26, 29, 32], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5, P6)
|
||||||
|
]
|
||||||
49
ros2_ws/src/yolov3_ros/models/hub/yolov5s-LeakyReLU.yaml
Normal file
49
ros2_ws/src/yolov3_ros/models/hub/yolov5s-LeakyReLU.yaml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
activation: nn.LeakyReLU(0.1) # <----- Conv() activation used throughout entire YOLOv5 model
|
||||||
|
depth_multiple: 0.33 # model depth multiple
|
||||||
|
width_multiple: 0.50 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
48
ros2_ws/src/yolov3_ros/models/hub/yolov5s-ghost.yaml
Normal file
48
ros2_ws/src/yolov3_ros/models/hub/yolov5s-ghost.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 0.33 # model depth multiple
|
||||||
|
width_multiple: 0.50 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, GhostConv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3Ghost, [128]],
|
||||||
|
[-1, 1, GhostConv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3Ghost, [256]],
|
||||||
|
[-1, 1, GhostConv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3Ghost, [512]],
|
||||||
|
[-1, 1, GhostConv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3Ghost, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, GhostConv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3Ghost, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, GhostConv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3Ghost, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, GhostConv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3Ghost, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, GhostConv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3Ghost, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
48
ros2_ws/src/yolov3_ros/models/hub/yolov5s-transformer.yaml
Normal file
48
ros2_ws/src/yolov3_ros/models/hub/yolov5s-transformer.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 0.33 # model depth multiple
|
||||||
|
width_multiple: 0.50 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3TR, [1024]], # 9 <--- C3TR() Transformer module
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
60
ros2_ws/src/yolov3_ros/models/hub/yolov5s6.yaml
Normal file
60
ros2_ws/src/yolov3_ros/models/hub/yolov5s6.yaml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 0.33 # model depth multiple
|
||||||
|
width_multiple: 0.50 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [19,27, 44,40, 38,94] # P3/8
|
||||||
|
- [96,68, 86,152, 180,137] # P4/16
|
||||||
|
- [140,301, 303,264, 238,542] # P5/32
|
||||||
|
- [436,615, 739,380, 925,792] # P6/64
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [768, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [768]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 9-P6/64
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 11
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [768, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 8], 1, Concat, [1]], # cat backbone P5
|
||||||
|
[-1, 3, C3, [768, False]], # 15
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 19
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 23 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 20], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 26 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 16], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [768, False]], # 29 (P5/32-large)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [768, 3, 2]],
|
||||||
|
[[-1, 12], 1, Concat, [1]], # cat head P6
|
||||||
|
[-1, 3, C3, [1024, False]], # 32 (P6/64-xlarge)
|
||||||
|
|
||||||
|
[[23, 26, 29, 32], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5, P6)
|
||||||
|
]
|
||||||
60
ros2_ws/src/yolov3_ros/models/hub/yolov5x6.yaml
Normal file
60
ros2_ws/src/yolov3_ros/models/hub/yolov5x6.yaml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 1.33 # model depth multiple
|
||||||
|
width_multiple: 1.25 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [19,27, 44,40, 38,94] # P3/8
|
||||||
|
- [96,68, 86,152, 180,137] # P4/16
|
||||||
|
- [140,301, 303,264, 238,542] # P5/32
|
||||||
|
- [436,615, 739,380, 925,792] # P6/64
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [768, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [768]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 9-P6/64
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 11
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [768, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 8], 1, Concat, [1]], # cat backbone P5
|
||||||
|
[-1, 3, C3, [768, False]], # 15
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 19
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 23 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 20], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 26 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 16], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [768, False]], # 29 (P5/32-large)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [768, 3, 2]],
|
||||||
|
[[-1, 12], 1, Concat, [1]], # cat head P6
|
||||||
|
[-1, 3, C3, [1024, False]], # 32 (P6/64-xlarge)
|
||||||
|
|
||||||
|
[[23, 26, 29, 32], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5, P6)
|
||||||
|
]
|
||||||
48
ros2_ws/src/yolov3_ros/models/segment/yolov5l-seg.yaml
Normal file
48
ros2_ws/src/yolov3_ros/models/segment/yolov5l-seg.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 1.0 # model depth multiple
|
||||||
|
width_multiple: 1.0 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Segment, [nc, anchors, 32, 256]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
48
ros2_ws/src/yolov3_ros/models/segment/yolov5m-seg.yaml
Normal file
48
ros2_ws/src/yolov3_ros/models/segment/yolov5m-seg.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 0.67 # model depth multiple
|
||||||
|
width_multiple: 0.75 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Segment, [nc, anchors, 32, 256]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
48
ros2_ws/src/yolov3_ros/models/segment/yolov5n-seg.yaml
Normal file
48
ros2_ws/src/yolov3_ros/models/segment/yolov5n-seg.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 0.33 # model depth multiple
|
||||||
|
width_multiple: 0.25 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Segment, [nc, anchors, 32, 256]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
48
ros2_ws/src/yolov3_ros/models/segment/yolov5s-seg.yaml
Normal file
48
ros2_ws/src/yolov3_ros/models/segment/yolov5s-seg.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 0.33 # model depth multiple
|
||||||
|
width_multiple: 0.5 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Segment, [nc, anchors, 32, 256]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
48
ros2_ws/src/yolov3_ros/models/segment/yolov5x-seg.yaml
Normal file
48
ros2_ws/src/yolov3_ros/models/segment/yolov5x-seg.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 1.33 # model depth multiple
|
||||||
|
width_multiple: 1.25 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Segment, [nc, anchors, 32, 256]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
628
ros2_ws/src/yolov3_ros/models/tf.py
Normal file
628
ros2_ws/src/yolov3_ros/models/tf.py
Normal file
@ -0,0 +1,628 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
TensorFlow, Keras and TFLite versions of
|
||||||
|
Authored by https://github.com/zldrobit in PR https://github.com/ultralytics/yolov5/pull/1127
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
$ python models/tf.py --weights yolov3.pt
|
||||||
|
|
||||||
|
Export:
|
||||||
|
$ python path/to/export.py --weights yolov3.pt --include saved_model pb tflite tfjs
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
from copy import deepcopy
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
|
FILE = Path(__file__).resolve()
|
||||||
|
ROOT = FILE.parents[1] # root directory
|
||||||
|
if str(ROOT) not in sys.path:
|
||||||
|
sys.path.append(str(ROOT)) # add ROOT to PATH
|
||||||
|
# ROOT = ROOT.relative_to(Path.cwd()) # relative
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import tensorflow as tf
|
||||||
|
import torch
|
||||||
|
import torch.nn as nn
|
||||||
|
from keras import backend
|
||||||
|
from keras.engine.base_layer import Layer
|
||||||
|
from keras.engine.input_spec import InputSpec
|
||||||
|
from keras.utils import conv_utils
|
||||||
|
from tensorflow import keras
|
||||||
|
|
||||||
|
from models.common import C3, SPP, SPPF, Bottleneck, BottleneckCSP, Concat, Conv, DWConv, Focus, autopad
|
||||||
|
from models.experimental import CrossConv, MixConv2d, attempt_load
|
||||||
|
from models.yolo import Detect
|
||||||
|
from utils.activations import SiLU
|
||||||
|
from utils.general import LOGGER, make_divisible, print_args
|
||||||
|
|
||||||
|
# isort: off
|
||||||
|
from tensorflow.python.util.tf_export import keras_export
|
||||||
|
|
||||||
|
|
||||||
|
class TFBN(keras.layers.Layer):
|
||||||
|
# TensorFlow BatchNormalization wrapper
|
||||||
|
def __init__(self, w=None):
|
||||||
|
super().__init__()
|
||||||
|
self.bn = keras.layers.BatchNormalization(
|
||||||
|
beta_initializer=keras.initializers.Constant(w.bias.numpy()),
|
||||||
|
gamma_initializer=keras.initializers.Constant(w.weight.numpy()),
|
||||||
|
moving_mean_initializer=keras.initializers.Constant(w.running_mean.numpy()),
|
||||||
|
moving_variance_initializer=keras.initializers.Constant(w.running_var.numpy()),
|
||||||
|
epsilon=w.eps)
|
||||||
|
|
||||||
|
def call(self, inputs):
|
||||||
|
return self.bn(inputs)
|
||||||
|
|
||||||
|
|
||||||
|
class TFMaxPool2d(keras.layers.Layer):
|
||||||
|
# TensorFlow MAX Pooling
|
||||||
|
def __init__(self, k, s, p, w=None):
|
||||||
|
super().__init__()
|
||||||
|
self.pool = keras.layers.MaxPool2D(pool_size=k, strides=s, padding='valid')
|
||||||
|
|
||||||
|
def call(self, inputs):
|
||||||
|
return self.pool(inputs)
|
||||||
|
|
||||||
|
|
||||||
|
class TFZeroPad2d(keras.layers.Layer):
|
||||||
|
# TensorFlow MAX Pooling
|
||||||
|
def __init__(self, p, w=None):
|
||||||
|
super().__init__()
|
||||||
|
if version.parse(tf.__version__) < version.parse('2.11.0'):
|
||||||
|
self.zero_pad = ZeroPadding2D(padding=p)
|
||||||
|
else:
|
||||||
|
self.zero_pad = keras.layers.ZeroPadding2D(padding=((p[0], p[1]), (p[2], p[3])))
|
||||||
|
|
||||||
|
def call(self, inputs):
|
||||||
|
return self.zero_pad(inputs)
|
||||||
|
|
||||||
|
|
||||||
|
class TFPad(keras.layers.Layer):
|
||||||
|
def __init__(self, pad):
|
||||||
|
super().__init__()
|
||||||
|
self.pad = tf.constant([[0, 0], [pad, pad], [pad, pad], [0, 0]])
|
||||||
|
|
||||||
|
def call(self, inputs):
|
||||||
|
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
|
||||||
|
super().__init__()
|
||||||
|
assert g == 1, "TF v2.2 Conv2D does not support 'groups' argument"
|
||||||
|
assert isinstance(k, int), "Convolution with multiple kernels are not allowed."
|
||||||
|
# TensorFlow convolution padding is inconsistent with PyTorch (e.g. k=3 s=2 'SAME' padding)
|
||||||
|
# see https://stackoverflow.com/questions/52975843/comparing-conv2d-with-padding-between-tensorflow-and-pytorch
|
||||||
|
|
||||||
|
conv = keras.layers.Conv2D(
|
||||||
|
c2, k, s, 'SAME' if s == 1 else 'VALID', use_bias=False if hasattr(w, 'bn') else True,
|
||||||
|
kernel_initializer=keras.initializers.Constant(w.conv.weight.permute(2, 3, 1, 0).numpy()),
|
||||||
|
bias_initializer='zeros' if hasattr(w, 'bn') else keras.initializers.Constant(w.conv.bias.numpy()))
|
||||||
|
self.conv = conv if s == 1 else keras.Sequential([TFPad(autopad(k, p)), conv])
|
||||||
|
self.bn = TFBN(w.bn) if hasattr(w, 'bn') else tf.identity
|
||||||
|
|
||||||
|
# activations
|
||||||
|
if isinstance(w.act, nn.LeakyReLU):
|
||||||
|
self.act = (lambda x: keras.activations.relu(x, alpha=0.1)) if act else tf.identity
|
||||||
|
elif isinstance(w.act, nn.Hardswish):
|
||||||
|
self.act = (lambda x: x * tf.nn.relu6(x + 3) * 0.166666667) if act else tf.identity
|
||||||
|
elif isinstance(w.act, (nn.SiLU, SiLU)):
|
||||||
|
self.act = (lambda x: keras.activations.swish(x)) if act else tf.identity
|
||||||
|
else:
|
||||||
|
raise Exception(f'no matching TensorFlow activation found for {w.act}')
|
||||||
|
|
||||||
|
def call(self, inputs):
|
||||||
|
return self.act(self.bn(self.conv(inputs)))
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
super().__init__()
|
||||||
|
self.conv = TFConv(c1 * 4, c2, k, s, p, g, act, w.conv)
|
||||||
|
|
||||||
|
def call(self, inputs): # x(b,w,h,c) -> y(b,w/2,h/2,4c)
|
||||||
|
# inputs = inputs / 255 # normalize 0-255 to 0-1
|
||||||
|
return self.conv(tf.concat([inputs[:, ::2, ::2, :],
|
||||||
|
inputs[:, 1::2, ::2, :],
|
||||||
|
inputs[:, ::2, 1::2, :],
|
||||||
|
inputs[:, 1::2, 1::2, :]], 3))
|
||||||
|
|
||||||
|
|
||||||
|
class TFBottleneck(keras.layers.Layer):
|
||||||
|
# Standard bottleneck
|
||||||
|
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5, w=None): # ch_in, ch_out, shortcut, groups, expansion
|
||||||
|
super().__init__()
|
||||||
|
c_ = int(c2 * e) # hidden channels
|
||||||
|
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
||||||
|
self.cv2 = TFConv(c_, c2, 3, 1, g=g, w=w.cv2)
|
||||||
|
self.add = shortcut and c1 == c2
|
||||||
|
|
||||||
|
def call(self, inputs):
|
||||||
|
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):
|
||||||
|
super().__init__()
|
||||||
|
assert g == 1, "TF v2.2 Conv2D does not support 'groups' argument"
|
||||||
|
self.conv = keras.layers.Conv2D(
|
||||||
|
c2, k, s, 'VALID', use_bias=bias,
|
||||||
|
kernel_initializer=keras.initializers.Constant(w.weight.permute(2, 3, 1, 0).numpy()),
|
||||||
|
bias_initializer=keras.initializers.Constant(w.bias.numpy()) if bias else None, )
|
||||||
|
|
||||||
|
def call(self, inputs):
|
||||||
|
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
|
||||||
|
super().__init__()
|
||||||
|
c_ = int(c2 * e) # hidden channels
|
||||||
|
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
||||||
|
self.cv2 = TFConv2d(c1, c_, 1, 1, bias=False, w=w.cv2)
|
||||||
|
self.cv3 = TFConv2d(c_, c_, 1, 1, bias=False, w=w.cv3)
|
||||||
|
self.cv4 = TFConv(2 * c_, c2, 1, 1, w=w.cv4)
|
||||||
|
self.bn = TFBN(w.bn)
|
||||||
|
self.act = lambda x: keras.activations.relu(x, alpha=0.1)
|
||||||
|
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):
|
||||||
|
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))))
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
super().__init__()
|
||||||
|
c_ = int(c2 * e) # hidden channels
|
||||||
|
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
||||||
|
self.cv2 = TFConv(c1, c_, 1, 1, w=w.cv2)
|
||||||
|
self.cv3 = TFConv(2 * c_, c2, 1, 1, w=w.cv3)
|
||||||
|
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):
|
||||||
|
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):
|
||||||
|
super().__init__()
|
||||||
|
c_ = c1 // 2 # hidden channels
|
||||||
|
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
||||||
|
self.cv2 = TFConv(c_ * (len(k) + 1), c2, 1, 1, w=w.cv2)
|
||||||
|
self.m = [keras.layers.MaxPool2D(pool_size=x, strides=1, padding='SAME') for x in k]
|
||||||
|
|
||||||
|
def call(self, inputs):
|
||||||
|
x = self.cv1(inputs)
|
||||||
|
return self.cv2(tf.concat([x] + [m(x) for m in self.m], 3))
|
||||||
|
|
||||||
|
|
||||||
|
class TFSPPF(keras.layers.Layer):
|
||||||
|
# Spatial pyramid pooling-Fast layer
|
||||||
|
def __init__(self, c1, c2, k=5, w=None):
|
||||||
|
super().__init__()
|
||||||
|
c_ = c1 // 2 # hidden channels
|
||||||
|
self.cv1 = TFConv(c1, c_, 1, 1, w=w.cv1)
|
||||||
|
self.cv2 = TFConv(c_ * 4, c2, 1, 1, w=w.cv2)
|
||||||
|
self.m = keras.layers.MaxPool2D(pool_size=k, strides=1, padding='SAME')
|
||||||
|
|
||||||
|
def call(self, inputs):
|
||||||
|
x = self.cv1(inputs)
|
||||||
|
y1 = self.m(x)
|
||||||
|
y2 = self.m(y1)
|
||||||
|
return self.cv2(tf.concat([x, y1, y2, self.m(y2)], 3))
|
||||||
|
|
||||||
|
|
||||||
|
class TFDetect(keras.layers.Layer):
|
||||||
|
def __init__(self, nc=80, anchors=(), ch=(), imgsz=(640, 640), w=None): # detection layer
|
||||||
|
super().__init__()
|
||||||
|
self.stride = tf.convert_to_tensor(w.stride.numpy(), dtype=tf.float32)
|
||||||
|
self.nc = nc # number of classes
|
||||||
|
self.no = nc + 5 # number of outputs per anchor
|
||||||
|
self.nl = len(anchors) # number of detection layers
|
||||||
|
self.na = len(anchors[0]) // 2 # number of anchors
|
||||||
|
self.grid = [tf.zeros(1)] * self.nl # init grid
|
||||||
|
self.anchors = tf.convert_to_tensor(w.anchors.numpy(), dtype=tf.float32)
|
||||||
|
self.anchor_grid = tf.reshape(self.anchors * tf.reshape(self.stride, [self.nl, 1, 1]),
|
||||||
|
[self.nl, 1, -1, 1, 2])
|
||||||
|
self.m = [TFConv2d(x, self.no * self.na, 1, w=w.m[i]) for i, x in enumerate(ch)]
|
||||||
|
self.training = False # set to False after building model
|
||||||
|
self.imgsz = imgsz
|
||||||
|
for i in range(self.nl):
|
||||||
|
ny, nx = self.imgsz[0] // self.stride[i], self.imgsz[1] // self.stride[i]
|
||||||
|
self.grid[i] = self._make_grid(nx, ny)
|
||||||
|
|
||||||
|
def call(self, inputs):
|
||||||
|
z = [] # inference output
|
||||||
|
x = []
|
||||||
|
for i in range(self.nl):
|
||||||
|
x.append(self.m[i](inputs[i]))
|
||||||
|
# x(bs,20,20,255) to x(bs,3,20,20,85)
|
||||||
|
ny, nx = self.imgsz[0] // self.stride[i], self.imgsz[1] // self.stride[i]
|
||||||
|
x[i] = tf.transpose(tf.reshape(x[i], [-1, ny * nx, self.na, self.no]), [0, 2, 1, 3])
|
||||||
|
|
||||||
|
if not self.training: # inference
|
||||||
|
y = tf.sigmoid(x[i])
|
||||||
|
xy = (y[..., 0:2] * 2 - 0.5 + self.grid[i]) * self.stride[i] # xy
|
||||||
|
wh = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]
|
||||||
|
# Normalize xywh to 0-1 to reduce calibration error
|
||||||
|
xy /= tf.constant([[self.imgsz[1], self.imgsz[0]]], dtype=tf.float32)
|
||||||
|
wh /= tf.constant([[self.imgsz[1], self.imgsz[0]]], dtype=tf.float32)
|
||||||
|
y = tf.concat([xy, wh, y[..., 4:]], -1)
|
||||||
|
z.append(tf.reshape(y, [-1, 3 * ny * nx, self.no]))
|
||||||
|
|
||||||
|
return x if self.training else (tf.concat(z, 1), x)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _make_grid(nx=20, ny=20):
|
||||||
|
# yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)])
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
||||||
|
class TFUpsample(keras.layers.Layer):
|
||||||
|
def __init__(self, size, scale_factor, mode, w=None): # warning: all arguments needed including 'w'
|
||||||
|
super().__init__()
|
||||||
|
assert scale_factor == 2, "scale_factor must be 2"
|
||||||
|
self.upsample = lambda x: tf.image.resize(x, (x.shape[1] * 2, x.shape[2] * 2), method=mode)
|
||||||
|
# self.upsample = keras.layers.UpSampling2D(size=scale_factor, interpolation=mode)
|
||||||
|
# with default arguments: align_corners=False, half_pixel_centers=False
|
||||||
|
# self.upsample = lambda x: tf.raw_ops.ResizeNearestNeighbor(images=x,
|
||||||
|
# size=(x.shape[1] * 2, x.shape[2] * 2))
|
||||||
|
|
||||||
|
def call(self, inputs):
|
||||||
|
return self.upsample(inputs)
|
||||||
|
|
||||||
|
|
||||||
|
class TFConcat(keras.layers.Layer):
|
||||||
|
def __init__(self, dimension=1, w=None):
|
||||||
|
super().__init__()
|
||||||
|
assert dimension == 1, "convert only NCHW to NHWC concat"
|
||||||
|
self.d = 3
|
||||||
|
|
||||||
|
def call(self, inputs):
|
||||||
|
return tf.concat(inputs, self.d)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_model(d, ch, model, imgsz): # model_dict, input_channels(3)
|
||||||
|
LOGGER.info(f"\n{'':>3}{'from':>18}{'n':>3}{'params':>10} {'module':<40}{'arguments':<30}")
|
||||||
|
anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple']
|
||||||
|
na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors # number of anchors
|
||||||
|
no = na * (nc + 5) # number of outputs = anchors * (classes + 5)
|
||||||
|
|
||||||
|
layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out
|
||||||
|
for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): # from, number, module, args
|
||||||
|
m_str = m
|
||||||
|
m = eval(m) if isinstance(m, str) else m # eval strings
|
||||||
|
for j, a in enumerate(args):
|
||||||
|
try:
|
||||||
|
args[j] = eval(a) if isinstance(a, str) else a # eval strings
|
||||||
|
except NameError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
n = max(round(n * gd), 1) if n > 1 else n # depth gain
|
||||||
|
if m in [nn.Conv2d, Conv, Bottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv, BottleneckCSP, C3]:
|
||||||
|
c1, c2 = ch[f], args[0]
|
||||||
|
c2 = make_divisible(c2 * gw, 8) if c2 != no else c2
|
||||||
|
|
||||||
|
args = [c1, c2, *args[1:]]
|
||||||
|
if m in [BottleneckCSP, C3]:
|
||||||
|
args.insert(2, n)
|
||||||
|
n = 1
|
||||||
|
elif m is nn.BatchNorm2d:
|
||||||
|
args = [ch[f]]
|
||||||
|
elif m is Concat:
|
||||||
|
c2 = sum(ch[-1 if x == -1 else x + 1] for x in f)
|
||||||
|
elif m is Detect:
|
||||||
|
args.append([ch[x + 1] for x in f])
|
||||||
|
if isinstance(args[1], int): # number of anchors
|
||||||
|
args[1] = [list(range(args[1] * 2))] * len(f)
|
||||||
|
args.append(imgsz)
|
||||||
|
else:
|
||||||
|
c2 = ch[f]
|
||||||
|
|
||||||
|
tf_m = eval('TF' + m_str.replace('nn.', ''))
|
||||||
|
m_ = keras.Sequential([tf_m(*args, w=model.model[i][j]) for j in range(n)]) if n > 1 \
|
||||||
|
else tf_m(*args, w=model.model[i]) # module
|
||||||
|
|
||||||
|
torch_m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args) # module
|
||||||
|
t = str(m)[8:-2].replace('__main__.', '') # module type
|
||||||
|
np = sum(x.numel() for x in torch_m_.parameters()) # number params
|
||||||
|
m_.i, m_.f, m_.type, m_.np = i, f, t, np # attach index, 'from' index, type, number params
|
||||||
|
LOGGER.info(f'{i:>3}{str(f):>18}{str(n):>3}{np:>10} {t:<40}{str(args):<30}') # print
|
||||||
|
save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist
|
||||||
|
layers.append(m_)
|
||||||
|
ch.append(c2)
|
||||||
|
return keras.Sequential(layers), sorted(save)
|
||||||
|
|
||||||
|
|
||||||
|
class TFModel:
|
||||||
|
def __init__(self, cfg='yolov3.yaml', ch=3, nc=None, model=None, imgsz=(640, 640)): # model, channels, classes
|
||||||
|
super().__init__()
|
||||||
|
if isinstance(cfg, dict):
|
||||||
|
self.yaml = cfg # model dict
|
||||||
|
else: # is *.yaml
|
||||||
|
import yaml # for torch hub
|
||||||
|
self.yaml_file = Path(cfg).name
|
||||||
|
with open(cfg) as f:
|
||||||
|
self.yaml = yaml.load(f, Loader=yaml.FullLoader) # model dict
|
||||||
|
|
||||||
|
# Define model
|
||||||
|
if nc and nc != self.yaml['nc']:
|
||||||
|
LOGGER.info(f"Overriding {cfg} nc={self.yaml['nc']} with nc={nc}")
|
||||||
|
self.yaml['nc'] = nc # override yaml value
|
||||||
|
self.model, self.savelist = parse_model(deepcopy(self.yaml), ch=[ch], model=model, imgsz=imgsz)
|
||||||
|
|
||||||
|
def predict(self, inputs, tf_nms=False, agnostic_nms=False, topk_per_class=100, topk_all=100, iou_thres=0.45,
|
||||||
|
conf_thres=0.25):
|
||||||
|
y = [] # outputs
|
||||||
|
x = inputs
|
||||||
|
for i, m in enumerate(self.model.layers):
|
||||||
|
if m.f != -1: # if not from previous layer
|
||||||
|
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
|
||||||
|
|
||||||
|
x = m(x) # run
|
||||||
|
y.append(x if m.i in self.savelist else None) # save output
|
||||||
|
|
||||||
|
# Add TensorFlow NMS
|
||||||
|
if tf_nms:
|
||||||
|
boxes = self._xywh2xyxy(x[0][..., :4])
|
||||||
|
probs = x[0][:, :, 4:5]
|
||||||
|
classes = x[0][:, :, 5:]
|
||||||
|
scores = probs * classes
|
||||||
|
if agnostic_nms:
|
||||||
|
nms = AgnosticNMS()((boxes, classes, scores), topk_all, iou_thres, conf_thres)
|
||||||
|
return nms, x[1]
|
||||||
|
else:
|
||||||
|
boxes = tf.expand_dims(boxes, 2)
|
||||||
|
nms = tf.image.combined_non_max_suppression(
|
||||||
|
boxes, scores, topk_per_class, topk_all, iou_thres, conf_thres, clip_boxes=False)
|
||||||
|
return nms, x[1]
|
||||||
|
|
||||||
|
return x[0] # output only first tensor [1,6300,85] = [xywh, conf, class0, class1, ...]
|
||||||
|
# x = x[0][0] # [x(1,6300,85), ...] to x(6300,85)
|
||||||
|
# xywh = x[..., :4] # x(6300,4) boxes
|
||||||
|
# conf = x[..., 4:5] # x(6300,1) confidences
|
||||||
|
# cls = tf.reshape(tf.cast(tf.argmax(x[..., 5:], axis=1), tf.float32), (-1, 1)) # x(6300,1) classes
|
||||||
|
# return tf.concat([conf, cls, xywh], 1)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _xywh2xyxy(xywh):
|
||||||
|
# Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, 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)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
return tf.map_fn(lambda x: self._nms(x, topk_all, iou_thres, conf_thres), input,
|
||||||
|
fn_output_signature=(tf.float32, tf.float32, tf.float32, tf.int32),
|
||||||
|
name='agnostic_nms')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _nms(x, topk_all=100, iou_thres=0.45, conf_thres=0.25): # agnostic NMS
|
||||||
|
boxes, classes, scores = x
|
||||||
|
class_inds = tf.cast(tf.argmax(classes, axis=-1), tf.float32)
|
||||||
|
scores_inp = tf.reduce_max(scores, -1)
|
||||||
|
selected_inds = tf.image.non_max_suppression(
|
||||||
|
boxes, scores_inp, max_output_size=topk_all, iou_threshold=iou_thres, score_threshold=conf_thres)
|
||||||
|
selected_boxes = tf.gather(boxes, selected_inds)
|
||||||
|
padded_boxes = tf.pad(selected_boxes,
|
||||||
|
paddings=[[0, topk_all - tf.shape(selected_boxes)[0]], [0, 0]],
|
||||||
|
mode="CONSTANT", constant_values=0.0)
|
||||||
|
selected_scores = tf.gather(scores_inp, selected_inds)
|
||||||
|
padded_scores = tf.pad(selected_scores,
|
||||||
|
paddings=[[0, topk_all - tf.shape(selected_boxes)[0]]],
|
||||||
|
mode="CONSTANT", constant_values=-1.0)
|
||||||
|
selected_classes = tf.gather(class_inds, selected_inds)
|
||||||
|
padded_classes = tf.pad(selected_classes,
|
||||||
|
paddings=[[0, topk_all - tf.shape(selected_boxes)[0]]],
|
||||||
|
mode="CONSTANT", constant_values=-1.0)
|
||||||
|
valid_detections = tf.shape(selected_inds)[0]
|
||||||
|
return padded_boxes, padded_scores, padded_classes, valid_detections
|
||||||
|
|
||||||
|
|
||||||
|
def representative_dataset_gen(dataset, ncalib=100):
|
||||||
|
# Representative dataset generator for use with converter.representative_dataset, returns a generator of np arrays
|
||||||
|
for n, (path, img, im0s, vid_cap, string) in enumerate(dataset):
|
||||||
|
input = np.transpose(img, [1, 2, 0])
|
||||||
|
input = np.expand_dims(input, axis=0).astype(np.float32)
|
||||||
|
input /= 255
|
||||||
|
yield [input]
|
||||||
|
if n >= ncalib:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def run(weights=ROOT / 'yolov3.pt', # weights path
|
||||||
|
imgsz=(640, 640), # inference size h,w
|
||||||
|
batch_size=1, # batch size
|
||||||
|
dynamic=False, # dynamic batch size
|
||||||
|
):
|
||||||
|
# PyTorch model
|
||||||
|
im = torch.zeros((batch_size, 3, *imgsz)) # BCHW image
|
||||||
|
model = attempt_load(weights, map_location=torch.device('cpu'), inplace=True, fuse=False)
|
||||||
|
y = model(im) # inference
|
||||||
|
model.info()
|
||||||
|
|
||||||
|
# TensorFlow model
|
||||||
|
im = tf.zeros((batch_size, *imgsz, 3)) # BHWC image
|
||||||
|
tf_model = TFModel(cfg=model.yaml, model=model, nc=model.nc, imgsz=imgsz)
|
||||||
|
y = tf_model.predict(im) # inference
|
||||||
|
|
||||||
|
# Keras model
|
||||||
|
im = keras.Input(shape=(*imgsz, 3), batch_size=None if dynamic else batch_size)
|
||||||
|
keras_model = keras.Model(inputs=im, outputs=tf_model.predict(im))
|
||||||
|
keras_model.summary()
|
||||||
|
|
||||||
|
LOGGER.info('PyTorch, TensorFlow and Keras models successfully verified.\nUse export.py for TF model export.')
|
||||||
|
|
||||||
|
|
||||||
|
@keras_export("keras.layers.ZeroPadding2D")
|
||||||
|
class ZeroPadding2D(Layer):
|
||||||
|
"""Zero-padding layer for 2D input (e.g. picture).
|
||||||
|
|
||||||
|
This layer can add rows and columns of zeros
|
||||||
|
at the top, bottom, left and right side of an image tensor.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
>>> input_shape = (1, 1, 2, 2)
|
||||||
|
>>> x = np.arange(np.prod(input_shape)).reshape(input_shape)
|
||||||
|
>>> print(x)
|
||||||
|
[[[[0 1]
|
||||||
|
[2 3]]]]
|
||||||
|
>>> y = tf.keras.layers.ZeroPadding2D(padding=1)(x)
|
||||||
|
>>> print(y)
|
||||||
|
tf.Tensor(
|
||||||
|
[[[[0 0]
|
||||||
|
[0 0]
|
||||||
|
[0 0]
|
||||||
|
[0 0]]
|
||||||
|
[[0 0]
|
||||||
|
[0 1]
|
||||||
|
[2 3]
|
||||||
|
[0 0]]
|
||||||
|
[[0 0]
|
||||||
|
[0 0]
|
||||||
|
[0 0]
|
||||||
|
[0 0]]]], shape=(1, 3, 4, 2), dtype=int64)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
padding: Int, or tuple of 2 ints, or tuple of 2 tuples of 2 ints.
|
||||||
|
- If int: the same symmetric padding
|
||||||
|
is applied to height and width.
|
||||||
|
- If tuple of 2 ints:
|
||||||
|
interpreted as two different
|
||||||
|
symmetric padding values for height and width:
|
||||||
|
`(symmetric_height_pad, symmetric_width_pad)`.
|
||||||
|
- If tuple of 2 tuples of 2 ints:
|
||||||
|
interpreted as
|
||||||
|
`((top_pad, bottom_pad), (left_pad, right_pad))`
|
||||||
|
data_format: A string,
|
||||||
|
one of `channels_last` (default) or `channels_first`.
|
||||||
|
The ordering of the dimensions in the inputs.
|
||||||
|
`channels_last` corresponds to inputs with shape
|
||||||
|
`(batch_size, height, width, channels)` while `channels_first`
|
||||||
|
corresponds to inputs with shape
|
||||||
|
`(batch_size, channels, height, width)`.
|
||||||
|
It defaults to the `image_data_format` value found in your
|
||||||
|
Keras config file at `~/.keras/keras.json`.
|
||||||
|
If you never set it, then it will be "channels_last".
|
||||||
|
|
||||||
|
Input shape:
|
||||||
|
4D tensor with shape:
|
||||||
|
- If `data_format` is `"channels_last"`:
|
||||||
|
`(batch_size, rows, cols, channels)`
|
||||||
|
- If `data_format` is `"channels_first"`:
|
||||||
|
`(batch_size, channels, rows, cols)`
|
||||||
|
|
||||||
|
Output shape:
|
||||||
|
4D tensor with shape:
|
||||||
|
- If `data_format` is `"channels_last"`:
|
||||||
|
`(batch_size, padded_rows, padded_cols, channels)`
|
||||||
|
- If `data_format` is `"channels_first"`:
|
||||||
|
`(batch_size, channels, padded_rows, padded_cols)`
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, padding=(1, 1), data_format=None, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.data_format = conv_utils.normalize_data_format(data_format)
|
||||||
|
if isinstance(padding, int):
|
||||||
|
self.padding = ((padding, padding), (padding, padding))
|
||||||
|
elif hasattr(padding, "__len__"):
|
||||||
|
if len(padding) == 4:
|
||||||
|
padding = ((padding[0], padding[1]), (padding[2], padding[3]))
|
||||||
|
if len(padding) != 2:
|
||||||
|
raise ValueError(
|
||||||
|
f"`padding` should have two elements. Received: {padding}."
|
||||||
|
)
|
||||||
|
height_padding = conv_utils.normalize_tuple(
|
||||||
|
padding[0], 2, "1st entry of padding", allow_zero=True
|
||||||
|
)
|
||||||
|
width_padding = conv_utils.normalize_tuple(
|
||||||
|
padding[1], 2, "2nd entry of padding", allow_zero=True
|
||||||
|
)
|
||||||
|
self.padding = (height_padding, width_padding)
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
"`padding` should be either an int, "
|
||||||
|
"a tuple of 2 ints "
|
||||||
|
"(symmetric_height_pad, symmetric_width_pad), "
|
||||||
|
"or a tuple of 2 tuples of 2 ints "
|
||||||
|
"((top_pad, bottom_pad), (left_pad, right_pad)). "
|
||||||
|
f"Received: {padding}."
|
||||||
|
)
|
||||||
|
self.input_spec = InputSpec(ndim=4)
|
||||||
|
|
||||||
|
def compute_output_shape(self, input_shape):
|
||||||
|
input_shape = tf.TensorShape(input_shape).as_list()
|
||||||
|
if self.data_format == "channels_first":
|
||||||
|
if input_shape[2] is not None:
|
||||||
|
rows = input_shape[2] + self.padding[0][0] + self.padding[0][1]
|
||||||
|
else:
|
||||||
|
rows = None
|
||||||
|
if input_shape[3] is not None:
|
||||||
|
cols = input_shape[3] + self.padding[1][0] + self.padding[1][1]
|
||||||
|
else:
|
||||||
|
cols = None
|
||||||
|
return tf.TensorShape([input_shape[0], input_shape[1], rows, cols])
|
||||||
|
elif self.data_format == "channels_last":
|
||||||
|
if input_shape[1] is not None:
|
||||||
|
rows = input_shape[1] + self.padding[0][0] + self.padding[0][1]
|
||||||
|
else:
|
||||||
|
rows = None
|
||||||
|
if input_shape[2] is not None:
|
||||||
|
cols = input_shape[2] + self.padding[1][0] + self.padding[1][1]
|
||||||
|
else:
|
||||||
|
cols = None
|
||||||
|
return tf.TensorShape([input_shape[0], rows, cols, input_shape[3]])
|
||||||
|
|
||||||
|
def call(self, inputs):
|
||||||
|
return backend.spatial_2d_padding(
|
||||||
|
inputs, padding=self.padding, data_format=self.data_format
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_config(self):
|
||||||
|
config = {"padding": self.padding, "data_format": self.data_format}
|
||||||
|
base_config = super().get_config()
|
||||||
|
return dict(list(base_config.items()) + list(config.items()))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_opt():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--weights', type=str, default=ROOT / 'yolov3.pt', help='weights path')
|
||||||
|
parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')
|
||||||
|
parser.add_argument('--batch-size', type=int, default=1, help='batch size')
|
||||||
|
parser.add_argument('--dynamic', action='store_true', help='dynamic batch size')
|
||||||
|
opt = parser.parse_args()
|
||||||
|
opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand
|
||||||
|
print_args(FILE.stem, opt)
|
||||||
|
return opt
|
||||||
|
|
||||||
|
|
||||||
|
def main(opt):
|
||||||
|
run(**vars(opt))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
opt = parse_opt()
|
||||||
|
main(opt)
|
||||||
336
ros2_ws/src/yolov3_ros/models/yolo.py
Normal file
336
ros2_ws/src/yolov3_ros/models/yolo.py
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
YOLO-specific modules
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
$ python path/to/models/yolo.py --cfg yolov3.yaml
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
from copy import deepcopy
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
FILE = Path(__file__).resolve()
|
||||||
|
ROOT = FILE.parents[1] # root directory
|
||||||
|
if str(ROOT) not in sys.path:
|
||||||
|
sys.path.append(str(ROOT)) # add ROOT to PATH
|
||||||
|
# ROOT = ROOT.relative_to(Path.cwd()) # relative
|
||||||
|
|
||||||
|
from models.common import *
|
||||||
|
from models.experimental import *
|
||||||
|
from utils.autoanchor import check_anchor_order
|
||||||
|
from utils.general import LOGGER, check_version, check_yaml, make_divisible, print_args
|
||||||
|
from utils.plots import feature_visualization
|
||||||
|
from utils.torch_utils import (copy_attr, fuse_conv_and_bn, initialize_weights, model_info, scale_img, select_device,
|
||||||
|
time_sync)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import thop # for FLOPs computation
|
||||||
|
except ImportError:
|
||||||
|
thop = None
|
||||||
|
|
||||||
|
|
||||||
|
class Detect(nn.Module):
|
||||||
|
stride = None # strides computed during build
|
||||||
|
onnx_dynamic = False # ONNX export parameter
|
||||||
|
|
||||||
|
def __init__(self, nc=80, anchors=(), ch=(), inplace=True): # detection layer
|
||||||
|
super().__init__()
|
||||||
|
self.nc = nc # number of classes
|
||||||
|
self.no = nc + 5 # number of outputs per anchor
|
||||||
|
self.nl = len(anchors) # number of detection layers
|
||||||
|
self.na = len(anchors[0]) // 2 # number of anchors
|
||||||
|
self.grid = [torch.zeros(1)] * self.nl # init grid
|
||||||
|
self.anchor_grid = [torch.zeros(1)] * self.nl # init anchor grid
|
||||||
|
self.register_buffer('anchors', torch.tensor(anchors).float().view(self.nl, -1, 2)) # shape(nl,na,2)
|
||||||
|
self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch) # output conv
|
||||||
|
self.inplace = inplace # use in-place ops (e.g. slice assignment)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
z = [] # inference output
|
||||||
|
for i in range(self.nl):
|
||||||
|
x[i] = self.m[i](x[i]) # conv
|
||||||
|
bs, _, ny, nx = x[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85)
|
||||||
|
x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
|
||||||
|
|
||||||
|
if not self.training: # inference
|
||||||
|
if self.onnx_dynamic or self.grid[i].shape[2:4] != x[i].shape[2:4]:
|
||||||
|
self.grid[i], self.anchor_grid[i] = self._make_grid(nx, ny, i)
|
||||||
|
|
||||||
|
y = x[i].sigmoid()
|
||||||
|
if self.inplace:
|
||||||
|
y[..., 0:2] = (y[..., 0:2] * 2 - 0.5 + self.grid[i]) * self.stride[i] # xy
|
||||||
|
y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh
|
||||||
|
else: # for on AWS Inferentia https://github.com/ultralytics/yolov5/pull/2953
|
||||||
|
xy = (y[..., 0:2] * 2 - 0.5 + self.grid[i]) * self.stride[i] # xy
|
||||||
|
wh = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh
|
||||||
|
y = torch.cat((xy, wh, y[..., 4:]), -1)
|
||||||
|
z.append(y.view(bs, -1, self.no))
|
||||||
|
|
||||||
|
return x if self.training else (torch.cat(z, 1), x)
|
||||||
|
|
||||||
|
def _make_grid(self, nx=20, ny=20, i=0):
|
||||||
|
d = self.anchors[i].device
|
||||||
|
if check_version(torch.__version__, '1.10.0'): # torch>=1.10.0 meshgrid workaround for torch>=0.7 compatibility
|
||||||
|
yv, xv = torch.meshgrid([torch.arange(ny).to(d), torch.arange(nx).to(d)], indexing='ij')
|
||||||
|
else:
|
||||||
|
yv, xv = torch.meshgrid([torch.arange(ny).to(d), torch.arange(nx).to(d)])
|
||||||
|
grid = torch.stack((xv, yv), 2).expand((1, self.na, ny, nx, 2)).float()
|
||||||
|
anchor_grid = (self.anchors[i].clone() * self.stride[i]) \
|
||||||
|
.view((1, self.na, 1, 1, 2)).expand((1, self.na, ny, nx, 2)).float()
|
||||||
|
return grid, anchor_grid
|
||||||
|
|
||||||
|
|
||||||
|
class Model(nn.Module):
|
||||||
|
def __init__(self, cfg='yolov3.yaml', ch=3, nc=None, anchors=None): # model, input channels, number of classes
|
||||||
|
super().__init__()
|
||||||
|
if isinstance(cfg, dict):
|
||||||
|
self.yaml = cfg # model dict
|
||||||
|
else: # is *.yaml
|
||||||
|
import yaml # for torch hub
|
||||||
|
self.yaml_file = Path(cfg).name
|
||||||
|
with open(cfg, encoding='ascii', errors='ignore') as f:
|
||||||
|
self.yaml = yaml.safe_load(f) # model dict
|
||||||
|
|
||||||
|
# Define model
|
||||||
|
ch = self.yaml['ch'] = self.yaml.get('ch', ch) # input channels
|
||||||
|
if nc and nc != self.yaml['nc']:
|
||||||
|
LOGGER.info(f"Overriding model.yaml nc={self.yaml['nc']} with nc={nc}")
|
||||||
|
self.yaml['nc'] = nc # override yaml value
|
||||||
|
if anchors:
|
||||||
|
LOGGER.info(f'Overriding model.yaml anchors with anchors={anchors}')
|
||||||
|
self.yaml['anchors'] = round(anchors) # override yaml value
|
||||||
|
self.model, self.save = parse_model(deepcopy(self.yaml), ch=[ch]) # model, savelist
|
||||||
|
self.names = [str(i) for i in range(self.yaml['nc'])] # default names
|
||||||
|
self.inplace = self.yaml.get('inplace', True)
|
||||||
|
|
||||||
|
# Build strides, anchors
|
||||||
|
m = self.model[-1] # Detect()
|
||||||
|
if isinstance(m, Detect):
|
||||||
|
s = 256 # 2x min stride
|
||||||
|
m.inplace = self.inplace
|
||||||
|
m.stride = torch.tensor([s / x.shape[-2] for x in self.forward(torch.zeros(1, ch, s, s))]) # forward
|
||||||
|
m.anchors /= m.stride.view(-1, 1, 1)
|
||||||
|
check_anchor_order(m)
|
||||||
|
self.stride = m.stride
|
||||||
|
self._initialize_biases() # only run once
|
||||||
|
|
||||||
|
# Init weights, biases
|
||||||
|
initialize_weights(self)
|
||||||
|
self.info()
|
||||||
|
LOGGER.info('')
|
||||||
|
|
||||||
|
def forward(self, x, augment=False, profile=False, visualize=False):
|
||||||
|
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):
|
||||||
|
img_size = x.shape[-2:] # height, width
|
||||||
|
s = [1, 0.83, 0.67] # scales
|
||||||
|
f = [None, 3, None] # flips (2-ud, 3-lr)
|
||||||
|
y = [] # outputs
|
||||||
|
for si, fi in zip(s, f):
|
||||||
|
xi = scale_img(x.flip(fi) if fi else x, si, gs=int(self.stride.max()))
|
||||||
|
yi = self._forward_once(xi)[0] # forward
|
||||||
|
# cv2.imwrite(f'img_{si}.jpg', 255 * xi[0].cpu().numpy().transpose((1, 2, 0))[:, :, ::-1]) # save
|
||||||
|
yi = self._descale_pred(yi, fi, si, img_size)
|
||||||
|
y.append(yi)
|
||||||
|
y = self._clip_augmented(y) # clip augmented tails
|
||||||
|
return torch.cat(y, 1), None # augmented inference, train
|
||||||
|
|
||||||
|
def _forward_once(self, x, profile=False, visualize=False):
|
||||||
|
y, dt = [], [] # outputs
|
||||||
|
for m in self.model:
|
||||||
|
if m.f != -1: # if not from previous layer
|
||||||
|
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
|
||||||
|
if profile:
|
||||||
|
self._profile_one_layer(m, x, dt)
|
||||||
|
x = m(x) # run
|
||||||
|
y.append(x if m.i in self.save else None) # save output
|
||||||
|
if visualize:
|
||||||
|
feature_visualization(x, m.type, m.i, save_dir=visualize)
|
||||||
|
return x
|
||||||
|
|
||||||
|
def _descale_pred(self, p, flips, scale, img_size):
|
||||||
|
# de-scale predictions following augmented inference (inverse operation)
|
||||||
|
if self.inplace:
|
||||||
|
p[..., :4] /= scale # de-scale
|
||||||
|
if flips == 2:
|
||||||
|
p[..., 1] = img_size[0] - p[..., 1] # de-flip ud
|
||||||
|
elif flips == 3:
|
||||||
|
p[..., 0] = img_size[1] - p[..., 0] # de-flip lr
|
||||||
|
else:
|
||||||
|
x, y, wh = p[..., 0:1] / scale, p[..., 1:2] / scale, p[..., 2:4] / scale # de-scale
|
||||||
|
if flips == 2:
|
||||||
|
y = img_size[0] - y # de-flip ud
|
||||||
|
elif flips == 3:
|
||||||
|
x = img_size[1] - x # de-flip lr
|
||||||
|
p = torch.cat((x, y, wh, p[..., 4:]), -1)
|
||||||
|
return p
|
||||||
|
|
||||||
|
def _clip_augmented(self, y):
|
||||||
|
# Clip augmented inference tails
|
||||||
|
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
|
||||||
|
i = (y[0].shape[1] // g) * sum(4 ** x for x in range(e)) # indices
|
||||||
|
y[0] = y[0][:, :-i] # large
|
||||||
|
i = (y[-1].shape[1] // g) * sum(4 ** (nl - 1 - x) for x in range(e)) # indices
|
||||||
|
y[-1] = y[-1][:, i:] # small
|
||||||
|
return y
|
||||||
|
|
||||||
|
def _profile_one_layer(self, m, x, dt):
|
||||||
|
c = isinstance(m, Detect) # 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()
|
||||||
|
for _ in range(10):
|
||||||
|
m(x.copy() if c else x)
|
||||||
|
dt.append((time_sync() - t) * 100)
|
||||||
|
if m == self.model[0]:
|
||||||
|
LOGGER.info(f"{'time (ms)':>10s} {'GFLOPs':>10s} {'params':>10s} {'module'}")
|
||||||
|
LOGGER.info(f'{dt[-1]:10.2f} {o:10.2f} {m.np:10.0f} {m.type}')
|
||||||
|
if c:
|
||||||
|
LOGGER.info(f"{sum(dt):10.2f} {'-':>10s} {'-':>10s} Total")
|
||||||
|
|
||||||
|
def _initialize_biases(self, cf=None): # initialize biases into Detect(), cf is class frequency
|
||||||
|
# https://arxiv.org/abs/1708.02002 section 3.3
|
||||||
|
# cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1.
|
||||||
|
m = self.model[-1] # Detect() module
|
||||||
|
for mi, s in zip(m.m, m.stride): # from
|
||||||
|
b = mi.bias.view(m.na, -1) # conv.bias(255) to (3,85)
|
||||||
|
b.data[:, 4] += math.log(8 / (640 / s) ** 2) # obj (8 objects per 640 image)
|
||||||
|
b.data[:, 5:] += math.log(0.6 / (m.nc - 0.999999)) if cf is None else torch.log(cf / cf.sum()) # cls
|
||||||
|
mi.bias = torch.nn.Parameter(b.view(-1), requires_grad=True)
|
||||||
|
|
||||||
|
def _print_biases(self):
|
||||||
|
m = self.model[-1] # Detect() module
|
||||||
|
for mi in m.m: # from
|
||||||
|
b = mi.bias.detach().view(m.na, -1).T # conv.bias(255) to (3,85)
|
||||||
|
LOGGER.info(
|
||||||
|
('%6g Conv2d.bias:' + '%10.3g' * 6) % (mi.weight.shape[1], *b[:5].mean(1).tolist(), b[5:].mean()))
|
||||||
|
|
||||||
|
# def _print_weights(self):
|
||||||
|
# for m in self.model.modules():
|
||||||
|
# if type(m) is Bottleneck:
|
||||||
|
# LOGGER.info('%10.3g' % (m.w.detach().sigmoid() * 2)) # shortcut weights
|
||||||
|
|
||||||
|
def fuse(self): # fuse model Conv2d() + BatchNorm2d() layers
|
||||||
|
LOGGER.info('Fusing layers... ')
|
||||||
|
for m in self.model.modules():
|
||||||
|
if isinstance(m, (Conv, DWConv)) and hasattr(m, 'bn'):
|
||||||
|
m.conv = fuse_conv_and_bn(m.conv, m.bn) # update conv
|
||||||
|
delattr(m, 'bn') # remove batchnorm
|
||||||
|
m.forward = m.forward_fuse # update forward
|
||||||
|
self.info()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def autoshape(self): # add AutoShape module
|
||||||
|
LOGGER.info('Adding AutoShape... ')
|
||||||
|
m = AutoShape(self) # wrap model
|
||||||
|
copy_attr(m, self, include=('yaml', 'nc', 'hyp', 'names', 'stride'), exclude=()) # copy attributes
|
||||||
|
return m
|
||||||
|
|
||||||
|
def info(self, verbose=False, img_size=640): # print model information
|
||||||
|
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
|
||||||
|
self = super()._apply(fn)
|
||||||
|
m = self.model[-1] # Detect()
|
||||||
|
if isinstance(m, Detect):
|
||||||
|
m.stride = fn(m.stride)
|
||||||
|
m.grid = list(map(fn, m.grid))
|
||||||
|
if isinstance(m.anchor_grid, list):
|
||||||
|
m.anchor_grid = list(map(fn, m.anchor_grid))
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
def parse_model(d, ch): # model_dict, input_channels(3)
|
||||||
|
LOGGER.info(f"\n{'':>3}{'from':>18}{'n':>3}{'params':>10} {'module':<40}{'arguments':<30}")
|
||||||
|
anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple']
|
||||||
|
na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors # number of anchors
|
||||||
|
no = na * (nc + 5) # number of outputs = anchors * (classes + 5)
|
||||||
|
|
||||||
|
layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out
|
||||||
|
for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): # from, number, module, args
|
||||||
|
m = eval(m) if isinstance(m, str) else m # eval strings
|
||||||
|
for j, a in enumerate(args):
|
||||||
|
try:
|
||||||
|
args[j] = eval(a) if isinstance(a, str) else a # eval strings
|
||||||
|
except NameError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
n = n_ = max(round(n * gd), 1) if n > 1 else n # depth gain
|
||||||
|
if m in [Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv,
|
||||||
|
BottleneckCSP, C3, C3TR, C3SPP, C3Ghost]:
|
||||||
|
c1, c2 = ch[f], args[0]
|
||||||
|
if c2 != no: # if not output
|
||||||
|
c2 = make_divisible(c2 * gw, 8)
|
||||||
|
|
||||||
|
args = [c1, c2, *args[1:]]
|
||||||
|
if m in [BottleneckCSP, C3, C3TR, C3Ghost]:
|
||||||
|
args.insert(2, n) # number of repeats
|
||||||
|
n = 1
|
||||||
|
elif m is nn.BatchNorm2d:
|
||||||
|
args = [ch[f]]
|
||||||
|
elif m is Concat:
|
||||||
|
c2 = sum(ch[x] for x in f)
|
||||||
|
elif m is Detect:
|
||||||
|
args.append([ch[x] for x in f])
|
||||||
|
if isinstance(args[1], int): # number of anchors
|
||||||
|
args[1] = [list(range(args[1] * 2))] * len(f)
|
||||||
|
elif m is Contract:
|
||||||
|
c2 = ch[f] * args[0] ** 2
|
||||||
|
elif m is Expand:
|
||||||
|
c2 = ch[f] // args[0] ** 2
|
||||||
|
else:
|
||||||
|
c2 = ch[f]
|
||||||
|
|
||||||
|
m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args) # module
|
||||||
|
t = str(m)[8:-2].replace('__main__.', '') # module type
|
||||||
|
np = sum(x.numel() for x in m_.parameters()) # number params
|
||||||
|
m_.i, m_.f, m_.type, m_.np = i, f, t, np # attach index, 'from' index, type, number params
|
||||||
|
LOGGER.info(f'{i:>3}{str(f):>18}{n_:>3}{np:10.0f} {t:<40}{str(args):<30}') # print
|
||||||
|
save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist
|
||||||
|
layers.append(m_)
|
||||||
|
if i == 0:
|
||||||
|
ch = []
|
||||||
|
ch.append(c2)
|
||||||
|
return nn.Sequential(*layers), sorted(save)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--cfg', type=str, default='yolov3yaml', help='model.yaml')
|
||||||
|
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
|
||||||
|
parser.add_argument('--profile', action='store_true', help='profile model speed')
|
||||||
|
parser.add_argument('--test', action='store_true', help='test all yolo*.yaml')
|
||||||
|
opt = parser.parse_args()
|
||||||
|
opt.cfg = check_yaml(opt.cfg) # check YAML
|
||||||
|
print_args(FILE.stem, opt)
|
||||||
|
device = select_device(opt.device)
|
||||||
|
|
||||||
|
# Create model
|
||||||
|
model = Model(opt.cfg).to(device)
|
||||||
|
model.train()
|
||||||
|
|
||||||
|
# Profile
|
||||||
|
if opt.profile:
|
||||||
|
img = torch.rand(8 if torch.cuda.is_available() else 1, 3, 640, 640).to(device)
|
||||||
|
y = model(img, profile=True)
|
||||||
|
|
||||||
|
# Test all models
|
||||||
|
if opt.test:
|
||||||
|
for cfg in Path(ROOT / 'models').rglob('yolo*.yaml'):
|
||||||
|
try:
|
||||||
|
_ = Model(cfg)
|
||||||
|
except Exception as e:
|
||||||
|
print(f'Error in {cfg}: {e}')
|
||||||
|
|
||||||
|
# Tensorboard (not working https://github.com/ultralytics/yolov5/issues/2898)
|
||||||
|
# from torch.utils.tensorboard import SummaryWriter
|
||||||
|
# tb_writer = SummaryWriter('.')
|
||||||
|
# LOGGER.info("Run 'tensorboard --logdir=models' to view tensorboard at http://localhost:6006/")
|
||||||
|
# tb_writer.add_graph(torch.jit.trace(model, img, strict=False), []) # add model graph
|
||||||
51
ros2_ws/src/yolov3_ros/models/yolov3-spp.yaml
Normal file
51
ros2_ws/src/yolov3_ros/models/yolov3-spp.yaml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 1.0 # model depth multiple
|
||||||
|
width_multiple: 1.0 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# darknet53 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [32, 3, 1]], # 0
|
||||||
|
[-1, 1, Conv, [64, 3, 2]], # 1-P1/2
|
||||||
|
[-1, 1, Bottleneck, [64]],
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 3-P2/4
|
||||||
|
[-1, 2, Bottleneck, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 5-P3/8
|
||||||
|
[-1, 8, Bottleneck, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 7-P4/16
|
||||||
|
[-1, 8, Bottleneck, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 9-P5/32
|
||||||
|
[-1, 4, Bottleneck, [1024]], # 10
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv3-SPP head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Bottleneck, [1024, False]],
|
||||||
|
[-1, 1, SPP, [512, [5, 9, 13]]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 1]],
|
||||||
|
[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 1]], # 15 (P5/32-large)
|
||||||
|
|
||||||
|
[-2, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 8], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 1, Bottleneck, [512, False]],
|
||||||
|
[-1, 1, Bottleneck, [512, False]],
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, Conv, [512, 3, 1]], # 22 (P4/16-medium)
|
||||||
|
|
||||||
|
[-2, 1, Conv, [128, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 1, Bottleneck, [256, False]],
|
||||||
|
[-1, 2, Bottleneck, [256, False]], # 27 (P3/8-small)
|
||||||
|
|
||||||
|
[[27, 22, 15], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
41
ros2_ws/src/yolov3_ros/models/yolov3-tiny.yaml
Normal file
41
ros2_ws/src/yolov3_ros/models/yolov3-tiny.yaml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 1.0 # model depth multiple
|
||||||
|
width_multiple: 1.0 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,14, 23,27, 37,58] # P4/16
|
||||||
|
- [81,82, 135,169, 344,319] # P5/32
|
||||||
|
|
||||||
|
# YOLOv3-tiny backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [16, 3, 1]], # 0
|
||||||
|
[-1, 1, nn.MaxPool2d, [2, 2, 0]], # 1-P1/2
|
||||||
|
[-1, 1, Conv, [32, 3, 1]],
|
||||||
|
[-1, 1, nn.MaxPool2d, [2, 2, 0]], # 3-P2/4
|
||||||
|
[-1, 1, Conv, [64, 3, 1]],
|
||||||
|
[-1, 1, nn.MaxPool2d, [2, 2, 0]], # 5-P3/8
|
||||||
|
[-1, 1, Conv, [128, 3, 1]],
|
||||||
|
[-1, 1, nn.MaxPool2d, [2, 2, 0]], # 7-P4/16
|
||||||
|
[-1, 1, Conv, [256, 3, 1]],
|
||||||
|
[-1, 1, nn.MaxPool2d, [2, 2, 0]], # 9-P5/32
|
||||||
|
[-1, 1, Conv, [512, 3, 1]],
|
||||||
|
[-1, 1, nn.ZeroPad2d, [[0, 1, 0, 1]]], # 11
|
||||||
|
[-1, 1, nn.MaxPool2d, [2, 1, 0]], # 12
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv3-tiny head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [1024, 3, 1]],
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, Conv, [512, 3, 1]], # 15 (P5/32-large)
|
||||||
|
|
||||||
|
[-2, 1, Conv, [128, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 8], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 1, Conv, [256, 3, 1]], # 19 (P4/16-medium)
|
||||||
|
|
||||||
|
[[19, 15], 1, Detect, [nc, anchors]], # Detect(P4, P5)
|
||||||
|
]
|
||||||
51
ros2_ws/src/yolov3_ros/models/yolov3.yaml
Normal file
51
ros2_ws/src/yolov3_ros/models/yolov3.yaml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 4 # number of classes
|
||||||
|
depth_multiple: 1.0 # model depth multiple
|
||||||
|
width_multiple: 1.0 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# darknet53 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [32, 3, 1]], # 0
|
||||||
|
[-1, 1, Conv, [64, 3, 2]], # 1-P1/2
|
||||||
|
[-1, 1, Bottleneck, [64]],
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 3-P2/4
|
||||||
|
[-1, 2, Bottleneck, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 5-P3/8
|
||||||
|
[-1, 8, Bottleneck, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 7-P4/16
|
||||||
|
[-1, 8, Bottleneck, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 9-P5/32
|
||||||
|
[-1, 4, Bottleneck, [1024]], # 10
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv3 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Bottleneck, [1024, False]],
|
||||||
|
[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 1]],
|
||||||
|
[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 1]], # 15 (P5/32-large)
|
||||||
|
|
||||||
|
[-2, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 8], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 1, Bottleneck, [512, False]],
|
||||||
|
[-1, 1, Bottleneck, [512, False]],
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, Conv, [512, 3, 1]], # 22 (P4/16-medium)
|
||||||
|
|
||||||
|
[-2, 1, Conv, [128, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 1, Bottleneck, [256, False]],
|
||||||
|
[-1, 2, Bottleneck, [256, False]], # 27 (P3/8-small)
|
||||||
|
|
||||||
|
[[27, 22, 15], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
48
ros2_ws/src/yolov3_ros/models/yolov5l.yaml
Normal file
48
ros2_ws/src/yolov3_ros/models/yolov5l.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 1.0 # model depth multiple
|
||||||
|
width_multiple: 1.0 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
48
ros2_ws/src/yolov3_ros/models/yolov5m.yaml
Normal file
48
ros2_ws/src/yolov3_ros/models/yolov5m.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 0.67 # model depth multiple
|
||||||
|
width_multiple: 0.75 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
48
ros2_ws/src/yolov3_ros/models/yolov5n.yaml
Normal file
48
ros2_ws/src/yolov3_ros/models/yolov5n.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 0.33 # model depth multiple
|
||||||
|
width_multiple: 0.25 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
48
ros2_ws/src/yolov3_ros/models/yolov5s.yaml
Normal file
48
ros2_ws/src/yolov3_ros/models/yolov5s.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 0.33 # model depth multiple
|
||||||
|
width_multiple: 0.50 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
48
ros2_ws/src/yolov3_ros/models/yolov5x.yaml
Normal file
48
ros2_ws/src/yolov3_ros/models/yolov5x.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
nc: 80 # number of classes
|
||||||
|
depth_multiple: 1.33 # model depth multiple
|
||||||
|
width_multiple: 1.25 # layer channel multiple
|
||||||
|
anchors:
|
||||||
|
- [10,13, 16,30, 33,23] # P3/8
|
||||||
|
- [30,61, 62,45, 59,119] # P4/16
|
||||||
|
- [116,90, 156,198, 373,326] # P5/32
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 backbone
|
||||||
|
backbone:
|
||||||
|
# [from, number, module, args]
|
||||||
|
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
|
||||||
|
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
|
||||||
|
[-1, 3, C3, [128]],
|
||||||
|
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
|
||||||
|
[-1, 6, C3, [256]],
|
||||||
|
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
|
||||||
|
[-1, 9, C3, [512]],
|
||||||
|
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
|
||||||
|
[-1, 3, C3, [1024]],
|
||||||
|
[-1, 1, SPPF, [1024, 5]], # 9
|
||||||
|
]
|
||||||
|
|
||||||
|
# YOLOv5 v6.0 head
|
||||||
|
head:
|
||||||
|
[[-1, 1, Conv, [512, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 6], 1, Concat, [1]], # cat backbone P4
|
||||||
|
[-1, 3, C3, [512, False]], # 13
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 1, 1]],
|
||||||
|
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
|
||||||
|
[[-1, 4], 1, Concat, [1]], # cat backbone P3
|
||||||
|
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [256, 3, 2]],
|
||||||
|
[[-1, 14], 1, Concat, [1]], # cat head P4
|
||||||
|
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
|
||||||
|
|
||||||
|
[-1, 1, Conv, [512, 3, 2]],
|
||||||
|
[[-1, 10], 1, Concat, [1]], # cat head P5
|
||||||
|
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
|
||||||
|
|
||||||
|
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
|
||||||
|
]
|
||||||
18
ros2_ws/src/yolov3_ros/package.xml
Normal file
18
ros2_ws/src/yolov3_ros/package.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||||
|
<package format="3">
|
||||||
|
<name>yolov3_ros</name>
|
||||||
|
<version>0.0.0</version>
|
||||||
|
<description>TODO: Package description</description>
|
||||||
|
<maintainer email="eic.apoorva@gmail.com">parallels</maintainer>
|
||||||
|
<license>TODO: License declaration</license>
|
||||||
|
|
||||||
|
<test_depend>ament_copyright</test_depend>
|
||||||
|
<test_depend>ament_flake8</test_depend>
|
||||||
|
<test_depend>ament_pep257</test_depend>
|
||||||
|
<test_depend>python3-pytest</test_depend>
|
||||||
|
|
||||||
|
<export>
|
||||||
|
<build_type>ament_python</build_type>
|
||||||
|
</export>
|
||||||
|
</package>
|
||||||
0
ros2_ws/src/yolov3_ros/resource/yolov3_ros
Normal file
0
ros2_ws/src/yolov3_ros/resource/yolov3_ros
Normal file
4
ros2_ws/src/yolov3_ros/setup.cfg
Normal file
4
ros2_ws/src/yolov3_ros/setup.cfg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[develop]
|
||||||
|
script_dir=$base/lib/yolov3_ros
|
||||||
|
[install]
|
||||||
|
install_scripts=$base/lib/yolov3_ros
|
||||||
43
ros2_ws/src/yolov3_ros/setup.py
Normal file
43
ros2_ws/src/yolov3_ros/setup.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
from setuptools import setup
|
||||||
|
import os
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
package_name = 'yolov3_ros'
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name=package_name,
|
||||||
|
version='0.0.0',
|
||||||
|
packages=[
|
||||||
|
package_name
|
||||||
|
],
|
||||||
|
data_files=[
|
||||||
|
('share/ament_index/resource_index/packages',
|
||||||
|
['resource/' + package_name]),
|
||||||
|
('share/' + package_name, ['package.xml']),
|
||||||
|
(os.path.join('lib', package_name, 'models'), glob('models/*.py')),
|
||||||
|
(os.path.join('lib', package_name, 'models'), glob('models/segment/*')),
|
||||||
|
(os.path.join('lib', package_name, 'models'), glob('models/hub/*')),
|
||||||
|
(os.path.join('lib', package_name, 'utils'), glob('utils/*.py')),
|
||||||
|
(os.path.join('lib', package_name, 'utils'), glob('utils/aws/*')),
|
||||||
|
(os.path.join('lib', package_name, 'utils'), glob('utils/docker/*')),
|
||||||
|
(os.path.join('lib', package_name, 'utils'), glob('utils/flask_rest_api/*')),
|
||||||
|
(os.path.join('lib', package_name, 'utils'), glob('utils/google_app_engine/*')),
|
||||||
|
(os.path.join('lib', package_name, 'utils'), glob('utils/loggers/comet/*')),
|
||||||
|
(os.path.join('lib', package_name, 'utils'), glob('utils/loggers/wandb/*')),
|
||||||
|
(os.path.join('lib', package_name, 'utils'), glob('utils/loggers/clearml/*')),
|
||||||
|
(os.path.join('lib', package_name, 'utils'), glob('utils/segment/*')),
|
||||||
|
(os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')),
|
||||||
|
],
|
||||||
|
install_requires=['setuptools'],
|
||||||
|
zip_safe=True,
|
||||||
|
maintainer='parallels',
|
||||||
|
maintainer_email='eic.apoorva@gmail.com',
|
||||||
|
description='TODO: Package description',
|
||||||
|
license='TODO: License declaration',
|
||||||
|
tests_require=['pytest'],
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'yolov3_ros_node = yolov3_ros.yolov3_ros_node:main'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
25
ros2_ws/src/yolov3_ros/test/test_copyright.py
Normal file
25
ros2_ws/src/yolov3_ros/test/test_copyright.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Copyright 2015 Open Source Robotics Foundation, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from ament_copyright.main import main
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
# Remove the `skip` decorator once the source file(s) have a copyright header
|
||||||
|
@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.')
|
||||||
|
@pytest.mark.copyright
|
||||||
|
@pytest.mark.linter
|
||||||
|
def test_copyright():
|
||||||
|
rc = main(argv=['.', 'test'])
|
||||||
|
assert rc == 0, 'Found errors'
|
||||||
25
ros2_ws/src/yolov3_ros/test/test_flake8.py
Normal file
25
ros2_ws/src/yolov3_ros/test/test_flake8.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Copyright 2017 Open Source Robotics Foundation, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from ament_flake8.main import main_with_errors
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.flake8
|
||||||
|
@pytest.mark.linter
|
||||||
|
def test_flake8():
|
||||||
|
rc, errors = main_with_errors(argv=[])
|
||||||
|
assert rc == 0, \
|
||||||
|
'Found %d code style errors / warnings:\n' % len(errors) + \
|
||||||
|
'\n'.join(errors)
|
||||||
23
ros2_ws/src/yolov3_ros/test/test_pep257.py
Normal file
23
ros2_ws/src/yolov3_ros/test/test_pep257.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Copyright 2015 Open Source Robotics Foundation, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from ament_pep257.main import main
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.linter
|
||||||
|
@pytest.mark.pep257
|
||||||
|
def test_pep257():
|
||||||
|
rc = main(argv=['.', 'test'])
|
||||||
|
assert rc == 0, 'Found code style errors / warnings'
|
||||||
18
ros2_ws/src/yolov3_ros/utils/__init__.py
Normal file
18
ros2_ws/src/yolov3_ros/utils/__init__.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
utils/initialization
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def notebook_init():
|
||||||
|
# For notebooks
|
||||||
|
print('Checking setup...')
|
||||||
|
from IPython import display # to display images and clear console output
|
||||||
|
|
||||||
|
from utils.general import emojis
|
||||||
|
from utils.torch_utils import select_device # imports
|
||||||
|
|
||||||
|
display.clear_output()
|
||||||
|
select_device(newline=False)
|
||||||
|
print(emojis('Setup complete ✅'))
|
||||||
|
return display
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
ros2_ws/src/yolov3_ros/utils/__pycache__/general.cpython-310.pyc
Normal file
BIN
ros2_ws/src/yolov3_ros/utils/__pycache__/general.cpython-310.pyc
Normal file
Binary file not shown.
BIN
ros2_ws/src/yolov3_ros/utils/__pycache__/metrics.cpython-310.pyc
Normal file
BIN
ros2_ws/src/yolov3_ros/utils/__pycache__/metrics.cpython-310.pyc
Normal file
Binary file not shown.
BIN
ros2_ws/src/yolov3_ros/utils/__pycache__/plots.cpython-310.pyc
Normal file
BIN
ros2_ws/src/yolov3_ros/utils/__pycache__/plots.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
101
ros2_ws/src/yolov3_ros/utils/activations.py
Normal file
101
ros2_ws/src/yolov3_ros/utils/activations.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Activation functions
|
||||||
|
"""
|
||||||
|
|
||||||
|
import torch
|
||||||
|
import torch.nn as nn
|
||||||
|
import torch.nn.functional as F
|
||||||
|
|
||||||
|
|
||||||
|
# SiLU https://arxiv.org/pdf/1606.08415.pdf ----------------------------------------------------------------------------
|
||||||
|
class SiLU(nn.Module): # export-friendly version of nn.SiLU()
|
||||||
|
@staticmethod
|
||||||
|
def forward(x):
|
||||||
|
return x * torch.sigmoid(x)
|
||||||
|
|
||||||
|
|
||||||
|
class Hardswish(nn.Module): # export-friendly version of nn.Hardswish()
|
||||||
|
@staticmethod
|
||||||
|
def forward(x):
|
||||||
|
# return x * F.hardsigmoid(x) # for torchscript and CoreML
|
||||||
|
return x * F.hardtanh(x + 3, 0.0, 6.0) / 6.0 # for torchscript, CoreML and ONNX
|
||||||
|
|
||||||
|
|
||||||
|
# Mish https://github.com/digantamisra98/Mish --------------------------------------------------------------------------
|
||||||
|
class Mish(nn.Module):
|
||||||
|
@staticmethod
|
||||||
|
def forward(x):
|
||||||
|
return x * F.softplus(x).tanh()
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryEfficientMish(nn.Module):
|
||||||
|
class F(torch.autograd.Function):
|
||||||
|
@staticmethod
|
||||||
|
def forward(ctx, x):
|
||||||
|
ctx.save_for_backward(x)
|
||||||
|
return x.mul(torch.tanh(F.softplus(x))) # x * tanh(ln(1 + exp(x)))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def backward(ctx, grad_output):
|
||||||
|
x = ctx.saved_tensors[0]
|
||||||
|
sx = torch.sigmoid(x)
|
||||||
|
fx = F.softplus(x).tanh()
|
||||||
|
return grad_output * (fx + x * sx * (1 - fx * fx))
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
return self.F.apply(x)
|
||||||
|
|
||||||
|
|
||||||
|
# FReLU https://arxiv.org/abs/2007.11824 -------------------------------------------------------------------------------
|
||||||
|
class FReLU(nn.Module):
|
||||||
|
def __init__(self, c1, k=3): # ch_in, kernel
|
||||||
|
super().__init__()
|
||||||
|
self.conv = nn.Conv2d(c1, c1, k, 1, 1, groups=c1, bias=False)
|
||||||
|
self.bn = nn.BatchNorm2d(c1)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
return torch.max(x, self.bn(self.conv(x)))
|
||||||
|
|
||||||
|
|
||||||
|
# ACON https://arxiv.org/pdf/2009.04759.pdf ----------------------------------------------------------------------------
|
||||||
|
class AconC(nn.Module):
|
||||||
|
r""" ACON activation (activate or not).
|
||||||
|
AconC: (p1*x-p2*x) * sigmoid(beta*(p1*x-p2*x)) + p2*x, beta is a learnable parameter
|
||||||
|
according to "Activate or Not: Learning Customized Activation" <https://arxiv.org/pdf/2009.04759.pdf>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, c1):
|
||||||
|
super().__init__()
|
||||||
|
self.p1 = nn.Parameter(torch.randn(1, c1, 1, 1))
|
||||||
|
self.p2 = nn.Parameter(torch.randn(1, c1, 1, 1))
|
||||||
|
self.beta = nn.Parameter(torch.ones(1, c1, 1, 1))
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
dpx = (self.p1 - self.p2) * x
|
||||||
|
return dpx * torch.sigmoid(self.beta * dpx) + self.p2 * x
|
||||||
|
|
||||||
|
|
||||||
|
class MetaAconC(nn.Module):
|
||||||
|
r""" ACON activation (activate or not).
|
||||||
|
MetaAconC: (p1*x-p2*x) * sigmoid(beta*(p1*x-p2*x)) + p2*x, beta is generated by a small network
|
||||||
|
according to "Activate or Not: Learning Customized Activation" <https://arxiv.org/pdf/2009.04759.pdf>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, c1, k=1, s=1, r=16): # ch_in, kernel, stride, r
|
||||||
|
super().__init__()
|
||||||
|
c2 = max(r, c1 // r)
|
||||||
|
self.p1 = nn.Parameter(torch.randn(1, c1, 1, 1))
|
||||||
|
self.p2 = nn.Parameter(torch.randn(1, c1, 1, 1))
|
||||||
|
self.fc1 = nn.Conv2d(c1, c2, k, s, bias=True)
|
||||||
|
self.fc2 = nn.Conv2d(c2, c1, k, s, bias=True)
|
||||||
|
# self.bn1 = nn.BatchNorm2d(c2)
|
||||||
|
# self.bn2 = nn.BatchNorm2d(c1)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
y = x.mean(dim=2, keepdims=True).mean(dim=3, keepdims=True)
|
||||||
|
# batch-size 1 bug/instabilities https://github.com/ultralytics/yolov5/issues/2891
|
||||||
|
# beta = torch.sigmoid(self.bn2(self.fc2(self.bn1(self.fc1(y))))) # bug/unstable
|
||||||
|
beta = torch.sigmoid(self.fc2(self.fc1(y))) # bug patch BN layers removed
|
||||||
|
dpx = (self.p1 - self.p2) * x
|
||||||
|
return dpx * torch.sigmoid(beta * dpx) + self.p2 * x
|
||||||
277
ros2_ws/src/yolov3_ros/utils/augmentations.py
Normal file
277
ros2_ws/src/yolov3_ros/utils/augmentations.py
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Image augmentation functions
|
||||||
|
"""
|
||||||
|
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from utils.general import LOGGER, check_version, colorstr, resample_segments, segment2box
|
||||||
|
from utils.metrics import bbox_ioa
|
||||||
|
|
||||||
|
|
||||||
|
class Albumentations:
|
||||||
|
# Albumentations class (optional, only used if package is installed)
|
||||||
|
def __init__(self):
|
||||||
|
self.transform = None
|
||||||
|
try:
|
||||||
|
import albumentations as A
|
||||||
|
check_version(A.__version__, '1.0.3', hard=True) # version requirement
|
||||||
|
|
||||||
|
self.transform = A.Compose([
|
||||||
|
A.Blur(p=0.01),
|
||||||
|
A.MedianBlur(p=0.01),
|
||||||
|
A.ToGray(p=0.01),
|
||||||
|
A.CLAHE(p=0.01),
|
||||||
|
A.RandomBrightnessContrast(p=0.0),
|
||||||
|
A.RandomGamma(p=0.0),
|
||||||
|
A.ImageCompression(quality_lower=75, p=0.0)],
|
||||||
|
bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels']))
|
||||||
|
|
||||||
|
LOGGER.info(colorstr('albumentations: ') + ', '.join(f'{x}' for x in self.transform.transforms if x.p))
|
||||||
|
except ImportError: # package not installed, skip
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
LOGGER.info(colorstr('albumentations: ') + f'{e}')
|
||||||
|
|
||||||
|
def __call__(self, im, labels, p=1.0):
|
||||||
|
if self.transform and random.random() < p:
|
||||||
|
new = self.transform(image=im, bboxes=labels[:, 1:], class_labels=labels[:, 0]) # transformed
|
||||||
|
im, labels = new['image'], np.array([[c, *b] for c, b in zip(new['class_labels'], new['bboxes'])])
|
||||||
|
return im, labels
|
||||||
|
|
||||||
|
|
||||||
|
def augment_hsv(im, hgain=0.5, sgain=0.5, vgain=0.5):
|
||||||
|
# HSV color-space augmentation
|
||||||
|
if hgain or sgain or vgain:
|
||||||
|
r = np.random.uniform(-1, 1, 3) * [hgain, sgain, vgain] + 1 # random gains
|
||||||
|
hue, sat, val = cv2.split(cv2.cvtColor(im, cv2.COLOR_BGR2HSV))
|
||||||
|
dtype = im.dtype # uint8
|
||||||
|
|
||||||
|
x = np.arange(0, 256, dtype=r.dtype)
|
||||||
|
lut_hue = ((x * r[0]) % 180).astype(dtype)
|
||||||
|
lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)
|
||||||
|
lut_val = np.clip(x * r[2], 0, 255).astype(dtype)
|
||||||
|
|
||||||
|
im_hsv = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val)))
|
||||||
|
cv2.cvtColor(im_hsv, cv2.COLOR_HSV2BGR, dst=im) # no return needed
|
||||||
|
|
||||||
|
|
||||||
|
def hist_equalize(im, clahe=True, bgr=False):
|
||||||
|
# Equalize histogram on BGR image 'im' with im.shape(n,m,3) and range 0-255
|
||||||
|
yuv = cv2.cvtColor(im, cv2.COLOR_BGR2YUV if bgr else cv2.COLOR_RGB2YUV)
|
||||||
|
if clahe:
|
||||||
|
c = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
|
||||||
|
yuv[:, :, 0] = c.apply(yuv[:, :, 0])
|
||||||
|
else:
|
||||||
|
yuv[:, :, 0] = cv2.equalizeHist(yuv[:, :, 0]) # equalize Y channel histogram
|
||||||
|
return cv2.cvtColor(yuv, cv2.COLOR_YUV2BGR if bgr else cv2.COLOR_YUV2RGB) # convert YUV image to RGB
|
||||||
|
|
||||||
|
|
||||||
|
def replicate(im, labels):
|
||||||
|
# Replicate labels
|
||||||
|
h, w = im.shape[:2]
|
||||||
|
boxes = labels[:, 1:].astype(int)
|
||||||
|
x1, y1, x2, y2 = boxes.T
|
||||||
|
s = ((x2 - x1) + (y2 - y1)) / 2 # side length (pixels)
|
||||||
|
for i in s.argsort()[:round(s.size * 0.5)]: # smallest indices
|
||||||
|
x1b, y1b, x2b, y2b = boxes[i]
|
||||||
|
bh, bw = y2b - y1b, x2b - x1b
|
||||||
|
yc, xc = int(random.uniform(0, h - bh)), int(random.uniform(0, w - bw)) # offset x, y
|
||||||
|
x1a, y1a, x2a, y2a = [xc, yc, xc + bw, yc + bh]
|
||||||
|
im[y1a:y2a, x1a:x2a] = im[y1b:y2b, x1b:x2b] # im4[ymin:ymax, xmin:xmax]
|
||||||
|
labels = np.append(labels, [[labels[i, 0], x1a, y1a, x2a, y2a]], axis=0)
|
||||||
|
|
||||||
|
return im, labels
|
||||||
|
|
||||||
|
|
||||||
|
def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32):
|
||||||
|
# Resize and pad image while meeting stride-multiple constraints
|
||||||
|
shape = im.shape[:2] # current shape [height, width]
|
||||||
|
if isinstance(new_shape, int):
|
||||||
|
new_shape = (new_shape, new_shape)
|
||||||
|
|
||||||
|
# Scale ratio (new / old)
|
||||||
|
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
|
||||||
|
if not scaleup: # only scale down, do not scale up (for better val mAP)
|
||||||
|
r = min(r, 1.0)
|
||||||
|
|
||||||
|
# Compute padding
|
||||||
|
ratio = r, r # width, height ratios
|
||||||
|
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
|
||||||
|
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
|
||||||
|
if auto: # minimum rectangle
|
||||||
|
dw, dh = np.mod(dw, stride), np.mod(dh, stride) # wh padding
|
||||||
|
elif scaleFill: # stretch
|
||||||
|
dw, dh = 0.0, 0.0
|
||||||
|
new_unpad = (new_shape[1], new_shape[0])
|
||||||
|
ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios
|
||||||
|
|
||||||
|
dw /= 2 # divide padding into 2 sides
|
||||||
|
dh /= 2
|
||||||
|
|
||||||
|
if shape[::-1] != new_unpad: # resize
|
||||||
|
im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
|
||||||
|
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
|
||||||
|
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
|
||||||
|
im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
|
||||||
|
return im, ratio, (dw, dh)
|
||||||
|
|
||||||
|
|
||||||
|
def random_perspective(im, targets=(), segments=(), degrees=10, translate=.1, scale=.1, shear=10, perspective=0.0,
|
||||||
|
border=(0, 0)):
|
||||||
|
# torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(0.1, 0.1), scale=(0.9, 1.1), shear=(-10, 10))
|
||||||
|
# targets = [cls, xyxy]
|
||||||
|
|
||||||
|
height = im.shape[0] + border[0] * 2 # shape(h,w,c)
|
||||||
|
width = im.shape[1] + border[1] * 2
|
||||||
|
|
||||||
|
# Center
|
||||||
|
C = np.eye(3)
|
||||||
|
C[0, 2] = -im.shape[1] / 2 # x translation (pixels)
|
||||||
|
C[1, 2] = -im.shape[0] / 2 # y translation (pixels)
|
||||||
|
|
||||||
|
# Perspective
|
||||||
|
P = np.eye(3)
|
||||||
|
P[2, 0] = random.uniform(-perspective, perspective) # x perspective (about y)
|
||||||
|
P[2, 1] = random.uniform(-perspective, perspective) # y perspective (about x)
|
||||||
|
|
||||||
|
# Rotation and Scale
|
||||||
|
R = np.eye(3)
|
||||||
|
a = random.uniform(-degrees, degrees)
|
||||||
|
# a += random.choice([-180, -90, 0, 90]) # add 90deg rotations to small rotations
|
||||||
|
s = random.uniform(1 - scale, 1 + scale)
|
||||||
|
# s = 2 ** random.uniform(-scale, scale)
|
||||||
|
R[:2] = cv2.getRotationMatrix2D(angle=a, center=(0, 0), scale=s)
|
||||||
|
|
||||||
|
# Shear
|
||||||
|
S = np.eye(3)
|
||||||
|
S[0, 1] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # x shear (deg)
|
||||||
|
S[1, 0] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # y shear (deg)
|
||||||
|
|
||||||
|
# Translation
|
||||||
|
T = np.eye(3)
|
||||||
|
T[0, 2] = random.uniform(0.5 - translate, 0.5 + translate) * width # x translation (pixels)
|
||||||
|
T[1, 2] = random.uniform(0.5 - translate, 0.5 + translate) * height # y translation (pixels)
|
||||||
|
|
||||||
|
# Combined rotation matrix
|
||||||
|
M = T @ S @ R @ P @ C # order of operations (right to left) is IMPORTANT
|
||||||
|
if (border[0] != 0) or (border[1] != 0) or (M != np.eye(3)).any(): # image changed
|
||||||
|
if perspective:
|
||||||
|
im = cv2.warpPerspective(im, M, dsize=(width, height), borderValue=(114, 114, 114))
|
||||||
|
else: # affine
|
||||||
|
im = cv2.warpAffine(im, M[:2], dsize=(width, height), borderValue=(114, 114, 114))
|
||||||
|
|
||||||
|
# Visualize
|
||||||
|
# import matplotlib.pyplot as plt
|
||||||
|
# ax = plt.subplots(1, 2, figsize=(12, 6))[1].ravel()
|
||||||
|
# ax[0].imshow(im[:, :, ::-1]) # base
|
||||||
|
# ax[1].imshow(im2[:, :, ::-1]) # warped
|
||||||
|
|
||||||
|
# Transform label coordinates
|
||||||
|
n = len(targets)
|
||||||
|
if n:
|
||||||
|
use_segments = any(x.any() for x in segments)
|
||||||
|
new = np.zeros((n, 4))
|
||||||
|
if use_segments: # warp segments
|
||||||
|
segments = resample_segments(segments) # upsample
|
||||||
|
for i, segment in enumerate(segments):
|
||||||
|
xy = np.ones((len(segment), 3))
|
||||||
|
xy[:, :2] = segment
|
||||||
|
xy = xy @ M.T # transform
|
||||||
|
xy = xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2] # perspective rescale or affine
|
||||||
|
|
||||||
|
# clip
|
||||||
|
new[i] = segment2box(xy, width, height)
|
||||||
|
|
||||||
|
else: # warp boxes
|
||||||
|
xy = np.ones((n * 4, 3))
|
||||||
|
xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2) # x1y1, x2y2, x1y2, x2y1
|
||||||
|
xy = xy @ M.T # transform
|
||||||
|
xy = (xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2]).reshape(n, 8) # perspective rescale or affine
|
||||||
|
|
||||||
|
# create new boxes
|
||||||
|
x = xy[:, [0, 2, 4, 6]]
|
||||||
|
y = xy[:, [1, 3, 5, 7]]
|
||||||
|
new = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T
|
||||||
|
|
||||||
|
# clip
|
||||||
|
new[:, [0, 2]] = new[:, [0, 2]].clip(0, width)
|
||||||
|
new[:, [1, 3]] = new[:, [1, 3]].clip(0, height)
|
||||||
|
|
||||||
|
# filter candidates
|
||||||
|
i = box_candidates(box1=targets[:, 1:5].T * s, box2=new.T, area_thr=0.01 if use_segments else 0.10)
|
||||||
|
targets = targets[i]
|
||||||
|
targets[:, 1:5] = new[i]
|
||||||
|
|
||||||
|
return im, targets
|
||||||
|
|
||||||
|
|
||||||
|
def copy_paste(im, labels, segments, p=0.5):
|
||||||
|
# Implement Copy-Paste augmentation https://arxiv.org/abs/2012.07177, labels as nx5 np.array(cls, xyxy)
|
||||||
|
n = len(segments)
|
||||||
|
if p and n:
|
||||||
|
h, w, c = im.shape # height, width, channels
|
||||||
|
im_new = np.zeros(im.shape, np.uint8)
|
||||||
|
for j in random.sample(range(n), k=round(p * n)):
|
||||||
|
l, s = labels[j], segments[j]
|
||||||
|
box = w - l[3], l[2], w - l[1], l[4]
|
||||||
|
ioa = bbox_ioa(box, labels[:, 1:5]) # intersection over area
|
||||||
|
if (ioa < 0.30).all(): # allow 30% obscuration of existing labels
|
||||||
|
labels = np.concatenate((labels, [[l[0], *box]]), 0)
|
||||||
|
segments.append(np.concatenate((w - s[:, 0:1], s[:, 1:2]), 1))
|
||||||
|
cv2.drawContours(im_new, [segments[j].astype(np.int32)], -1, (255, 255, 255), cv2.FILLED)
|
||||||
|
|
||||||
|
result = cv2.bitwise_and(src1=im, src2=im_new)
|
||||||
|
result = cv2.flip(result, 1) # augment segments (flip left-right)
|
||||||
|
i = result > 0 # pixels to replace
|
||||||
|
# i[:, :] = result.max(2).reshape(h, w, 1) # act over ch
|
||||||
|
im[i] = result[i] # cv2.imwrite('debug.jpg', im) # debug
|
||||||
|
|
||||||
|
return im, labels, segments
|
||||||
|
|
||||||
|
|
||||||
|
def cutout(im, labels, p=0.5):
|
||||||
|
# Applies image cutout augmentation https://arxiv.org/abs/1708.04552
|
||||||
|
if random.random() < p:
|
||||||
|
h, w = im.shape[:2]
|
||||||
|
scales = [0.5] * 1 + [0.25] * 2 + [0.125] * 4 + [0.0625] * 8 + [0.03125] * 16 # image size fraction
|
||||||
|
for s in scales:
|
||||||
|
mask_h = random.randint(1, int(h * s)) # create random masks
|
||||||
|
mask_w = random.randint(1, int(w * s))
|
||||||
|
|
||||||
|
# box
|
||||||
|
xmin = max(0, random.randint(0, w) - mask_w // 2)
|
||||||
|
ymin = max(0, random.randint(0, h) - mask_h // 2)
|
||||||
|
xmax = min(w, xmin + mask_w)
|
||||||
|
ymax = min(h, ymin + mask_h)
|
||||||
|
|
||||||
|
# apply random color mask
|
||||||
|
im[ymin:ymax, xmin:xmax] = [random.randint(64, 191) for _ in range(3)]
|
||||||
|
|
||||||
|
# return unobscured labels
|
||||||
|
if len(labels) and s > 0.03:
|
||||||
|
box = np.array([xmin, ymin, xmax, ymax], dtype=np.float32)
|
||||||
|
ioa = bbox_ioa(box, labels[:, 1:5]) # intersection over area
|
||||||
|
labels = labels[ioa < 0.60] # remove >60% obscured labels
|
||||||
|
|
||||||
|
return labels
|
||||||
|
|
||||||
|
|
||||||
|
def mixup(im, labels, im2, labels2):
|
||||||
|
# Applies MixUp augmentation https://arxiv.org/pdf/1710.09412.pdf
|
||||||
|
r = np.random.beta(32.0, 32.0) # mixup ratio, alpha=beta=32.0
|
||||||
|
im = (im * r + im2 * (1 - r)).astype(np.uint8)
|
||||||
|
labels = np.concatenate((labels, labels2), 0)
|
||||||
|
return im, labels
|
||||||
|
|
||||||
|
|
||||||
|
def box_candidates(box1, box2, wh_thr=2, ar_thr=20, area_thr=0.1, eps=1e-16): # box1(4,n), box2(4,n)
|
||||||
|
# Compute candidate boxes: box1 before augment, box2 after augment, wh_thr (pixels), aspect_ratio_thr, area_ratio
|
||||||
|
w1, h1 = box1[2] - box1[0], box1[3] - box1[1]
|
||||||
|
w2, h2 = box2[2] - box2[0], box2[3] - box2[1]
|
||||||
|
ar = np.maximum(w2 / (h2 + eps), h2 / (w2 + eps)) # aspect ratio
|
||||||
|
return (w2 > wh_thr) & (h2 > wh_thr) & (w2 * h2 / (w1 * h1 + eps) > area_thr) & (ar < ar_thr) # candidates
|
||||||
164
ros2_ws/src/yolov3_ros/utils/autoanchor.py
Normal file
164
ros2_ws/src/yolov3_ros/utils/autoanchor.py
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Auto-anchor utils
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
import yaml
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from utils.general import LOGGER, colorstr, emojis
|
||||||
|
|
||||||
|
PREFIX = colorstr('AutoAnchor: ')
|
||||||
|
|
||||||
|
|
||||||
|
def check_anchor_order(m):
|
||||||
|
# Check anchor order against stride order for Detect() module m, and correct if necessary
|
||||||
|
a = m.anchors.prod(-1).view(-1) # anchor area
|
||||||
|
da = a[-1] - a[0] # delta a
|
||||||
|
ds = m.stride[-1] - m.stride[0] # delta s
|
||||||
|
if da.sign() != ds.sign(): # same order
|
||||||
|
LOGGER.info(f'{PREFIX}Reversing anchor order')
|
||||||
|
m.anchors[:] = m.anchors.flip(0)
|
||||||
|
|
||||||
|
|
||||||
|
def check_anchors(dataset, model, thr=4.0, imgsz=640):
|
||||||
|
# Check anchor fit to data, recompute if necessary
|
||||||
|
m = model.module.model[-1] if hasattr(model, 'module') else model.model[-1] # Detect()
|
||||||
|
shapes = imgsz * dataset.shapes / dataset.shapes.max(1, keepdims=True)
|
||||||
|
scale = np.random.uniform(0.9, 1.1, size=(shapes.shape[0], 1)) # augment scale
|
||||||
|
wh = torch.tensor(np.concatenate([l[:, 3:5] * s for s, l in zip(shapes * scale, dataset.labels)])).float() # wh
|
||||||
|
|
||||||
|
def metric(k): # compute metric
|
||||||
|
r = wh[:, None] / k[None]
|
||||||
|
x = torch.min(r, 1 / r).min(2)[0] # ratio metric
|
||||||
|
best = x.max(1)[0] # best_x
|
||||||
|
aat = (x > 1 / thr).float().sum(1).mean() # anchors above threshold
|
||||||
|
bpr = (best > 1 / thr).float().mean() # best possible recall
|
||||||
|
return bpr, aat
|
||||||
|
|
||||||
|
anchors = m.anchors.clone() * m.stride.to(m.anchors.device).view(-1, 1, 1) # current anchors
|
||||||
|
bpr, aat = metric(anchors.cpu().view(-1, 2))
|
||||||
|
s = f'\n{PREFIX}{aat:.2f} anchors/target, {bpr:.3f} Best Possible Recall (BPR). '
|
||||||
|
if bpr > 0.98: # threshold to recompute
|
||||||
|
LOGGER.info(emojis(f'{s}Current anchors are a good fit to dataset ✅'))
|
||||||
|
else:
|
||||||
|
LOGGER.info(emojis(f'{s}Anchors are a poor fit to dataset ⚠️, attempting to improve...'))
|
||||||
|
na = m.anchors.numel() // 2 # number of anchors
|
||||||
|
try:
|
||||||
|
anchors = kmean_anchors(dataset, n=na, img_size=imgsz, thr=thr, gen=1000, verbose=False)
|
||||||
|
except Exception as e:
|
||||||
|
LOGGER.info(f'{PREFIX}ERROR: {e}')
|
||||||
|
new_bpr = metric(anchors)[0]
|
||||||
|
if new_bpr > bpr: # replace anchors
|
||||||
|
anchors = torch.tensor(anchors, device=m.anchors.device).type_as(m.anchors)
|
||||||
|
m.anchors[:] = anchors.clone().view_as(m.anchors) / m.stride.to(m.anchors.device).view(-1, 1, 1) # loss
|
||||||
|
check_anchor_order(m)
|
||||||
|
LOGGER.info(f'{PREFIX}New anchors saved to model. Update model *.yaml to use these anchors in the future.')
|
||||||
|
else:
|
||||||
|
LOGGER.info(f'{PREFIX}Original anchors better than new anchors. Proceeding with original anchors.')
|
||||||
|
|
||||||
|
|
||||||
|
def kmean_anchors(dataset='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen=1000, verbose=True):
|
||||||
|
""" Creates kmeans-evolved anchors from training dataset
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
dataset: path to data.yaml, or a loaded dataset
|
||||||
|
n: number of anchors
|
||||||
|
img_size: image size used for training
|
||||||
|
thr: anchor-label wh ratio threshold hyperparameter hyp['anchor_t'] used for training, default=4.0
|
||||||
|
gen: generations to evolve anchors using genetic algorithm
|
||||||
|
verbose: print all results
|
||||||
|
|
||||||
|
Return:
|
||||||
|
k: kmeans evolved anchors
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
from utils.autoanchor import *; _ = kmean_anchors()
|
||||||
|
"""
|
||||||
|
from scipy.cluster.vq import kmeans
|
||||||
|
|
||||||
|
thr = 1 / thr
|
||||||
|
|
||||||
|
def metric(k, wh): # compute metrics
|
||||||
|
r = wh[:, None] / k[None]
|
||||||
|
x = torch.min(r, 1 / r).min(2)[0] # ratio metric
|
||||||
|
# x = wh_iou(wh, torch.tensor(k)) # iou metric
|
||||||
|
return x, x.max(1)[0] # x, best_x
|
||||||
|
|
||||||
|
def anchor_fitness(k): # mutation fitness
|
||||||
|
_, best = metric(torch.tensor(k, dtype=torch.float32), wh)
|
||||||
|
return (best * (best > thr).float()).mean() # fitness
|
||||||
|
|
||||||
|
def print_results(k, verbose=True):
|
||||||
|
k = k[np.argsort(k.prod(1))] # sort small to large
|
||||||
|
x, best = metric(k, wh0)
|
||||||
|
bpr, aat = (best > thr).float().mean(), (x > thr).float().mean() * n # best possible recall, anch > thr
|
||||||
|
s = f'{PREFIX}thr={thr:.2f}: {bpr:.4f} best possible recall, {aat:.2f} anchors past thr\n' \
|
||||||
|
f'{PREFIX}n={n}, img_size={img_size}, metric_all={x.mean():.3f}/{best.mean():.3f}-mean/best, ' \
|
||||||
|
f'past_thr={x[x > thr].mean():.3f}-mean: '
|
||||||
|
for i, x in enumerate(k):
|
||||||
|
s += '%i,%i, ' % (round(x[0]), round(x[1]))
|
||||||
|
if verbose:
|
||||||
|
LOGGER.info(s[:-2])
|
||||||
|
return k
|
||||||
|
|
||||||
|
if isinstance(dataset, str): # *.yaml file
|
||||||
|
with open(dataset, errors='ignore') as f:
|
||||||
|
data_dict = yaml.safe_load(f) # model dict
|
||||||
|
from utils.datasets import LoadImagesAndLabels
|
||||||
|
dataset = LoadImagesAndLabels(data_dict['train'], augment=True, rect=True)
|
||||||
|
|
||||||
|
# Get label wh
|
||||||
|
shapes = img_size * dataset.shapes / dataset.shapes.max(1, keepdims=True)
|
||||||
|
wh0 = np.concatenate([l[:, 3:5] * s for s, l in zip(shapes, dataset.labels)]) # wh
|
||||||
|
|
||||||
|
# Filter
|
||||||
|
i = (wh0 < 3.0).any(1).sum()
|
||||||
|
if i:
|
||||||
|
LOGGER.info(f'{PREFIX}WARNING: Extremely small objects found. {i} of {len(wh0)} labels are < 3 pixels in size.')
|
||||||
|
wh = wh0[(wh0 >= 2.0).any(1)] # filter > 2 pixels
|
||||||
|
# wh = wh * (np.random.rand(wh.shape[0], 1) * 0.9 + 0.1) # multiply by random scale 0-1
|
||||||
|
|
||||||
|
# Kmeans calculation
|
||||||
|
LOGGER.info(f'{PREFIX}Running kmeans for {n} anchors on {len(wh)} points...')
|
||||||
|
s = wh.std(0) # sigmas for whitening
|
||||||
|
k, dist = kmeans(wh / s, n, iter=30) # points, mean distance
|
||||||
|
assert len(k) == n, f'{PREFIX}ERROR: scipy.cluster.vq.kmeans requested {n} points but returned only {len(k)}'
|
||||||
|
k *= s
|
||||||
|
wh = torch.tensor(wh, dtype=torch.float32) # filtered
|
||||||
|
wh0 = torch.tensor(wh0, dtype=torch.float32) # unfiltered
|
||||||
|
k = print_results(k, verbose=False)
|
||||||
|
|
||||||
|
# Plot
|
||||||
|
# k, d = [None] * 20, [None] * 20
|
||||||
|
# for i in tqdm(range(1, 21)):
|
||||||
|
# k[i-1], d[i-1] = kmeans(wh / s, i) # points, mean distance
|
||||||
|
# fig, ax = plt.subplots(1, 2, figsize=(14, 7), tight_layout=True)
|
||||||
|
# ax = ax.ravel()
|
||||||
|
# ax[0].plot(np.arange(1, 21), np.array(d) ** 2, marker='.')
|
||||||
|
# fig, ax = plt.subplots(1, 2, figsize=(14, 7)) # plot wh
|
||||||
|
# ax[0].hist(wh[wh[:, 0]<100, 0],400)
|
||||||
|
# ax[1].hist(wh[wh[:, 1]<100, 1],400)
|
||||||
|
# fig.savefig('wh.png', dpi=200)
|
||||||
|
|
||||||
|
# Evolve
|
||||||
|
npr = np.random
|
||||||
|
f, sh, mp, s = anchor_fitness(k), k.shape, 0.9, 0.1 # fitness, generations, mutation prob, sigma
|
||||||
|
pbar = tqdm(range(gen), desc=f'{PREFIX}Evolving anchors with Genetic Algorithm:') # progress bar
|
||||||
|
for _ in pbar:
|
||||||
|
v = np.ones(sh)
|
||||||
|
while (v == 1).all(): # mutate until a change occurs (prevent duplicates)
|
||||||
|
v = ((npr.random(sh) < mp) * random.random() * npr.randn(*sh) * s + 1).clip(0.3, 3.0)
|
||||||
|
kg = (k.copy() * v).clip(min=2.0)
|
||||||
|
fg = anchor_fitness(kg)
|
||||||
|
if fg > f:
|
||||||
|
f, k = fg, kg.copy()
|
||||||
|
pbar.desc = f'{PREFIX}Evolving anchors with Genetic Algorithm: fitness = {f:.4f}'
|
||||||
|
if verbose:
|
||||||
|
print_results(k, verbose)
|
||||||
|
|
||||||
|
return print_results(k)
|
||||||
57
ros2_ws/src/yolov3_ros/utils/autobatch.py
Normal file
57
ros2_ws/src/yolov3_ros/utils/autobatch.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Auto-batch utils
|
||||||
|
"""
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
from torch.cuda import amp
|
||||||
|
|
||||||
|
from utils.general import LOGGER, colorstr
|
||||||
|
from utils.torch_utils import profile
|
||||||
|
|
||||||
|
|
||||||
|
def check_train_batch_size(model, imgsz=640):
|
||||||
|
# Check training batch size
|
||||||
|
with amp.autocast():
|
||||||
|
return autobatch(deepcopy(model).train(), imgsz) # compute optimal batch size
|
||||||
|
|
||||||
|
|
||||||
|
def autobatch(model, imgsz=640, fraction=0.9, batch_size=16):
|
||||||
|
# Automatically estimate best batch size to use `fraction` of available CUDA memory
|
||||||
|
# Usage:
|
||||||
|
# import torch
|
||||||
|
# from utils.autobatch import autobatch
|
||||||
|
# model = torch.hub.load('ultralytics/yolov3', 'yolov3', autoshape=False)
|
||||||
|
# print(autobatch(model))
|
||||||
|
|
||||||
|
prefix = colorstr('AutoBatch: ')
|
||||||
|
LOGGER.info(f'{prefix}Computing optimal batch size for --imgsz {imgsz}')
|
||||||
|
device = next(model.parameters()).device # get model device
|
||||||
|
if device.type == 'cpu':
|
||||||
|
LOGGER.info(f'{prefix}CUDA not detected, using default CPU batch-size {batch_size}')
|
||||||
|
return batch_size
|
||||||
|
|
||||||
|
d = str(device).upper() # 'CUDA:0'
|
||||||
|
properties = torch.cuda.get_device_properties(device) # device properties
|
||||||
|
t = properties.total_memory / 1024 ** 3 # (GiB)
|
||||||
|
r = torch.cuda.memory_reserved(device) / 1024 ** 3 # (GiB)
|
||||||
|
a = torch.cuda.memory_allocated(device) / 1024 ** 3 # (GiB)
|
||||||
|
f = t - (r + a) # free inside reserved
|
||||||
|
LOGGER.info(f'{prefix}{d} ({properties.name}) {t:.2f}G total, {r:.2f}G reserved, {a:.2f}G allocated, {f:.2f}G free')
|
||||||
|
|
||||||
|
batch_sizes = [1, 2, 4, 8, 16]
|
||||||
|
try:
|
||||||
|
img = [torch.zeros(b, 3, imgsz, imgsz) for b in batch_sizes]
|
||||||
|
y = profile(img, model, n=3, device=device)
|
||||||
|
except Exception as e:
|
||||||
|
LOGGER.warning(f'{prefix}{e}')
|
||||||
|
|
||||||
|
y = [x[2] for x in y if x] # memory [2]
|
||||||
|
batch_sizes = batch_sizes[:len(y)]
|
||||||
|
p = np.polyfit(batch_sizes, y, deg=1) # first degree polynomial fit
|
||||||
|
b = int((f * fraction - p[1]) / p[0]) # y intercept (optimal batch size)
|
||||||
|
LOGGER.info(f'{prefix}Using batch-size {b} for {d} {t * fraction:.2f}G/{t:.2f}G ({fraction * 100:.0f}%)')
|
||||||
|
return b
|
||||||
0
ros2_ws/src/yolov3_ros/utils/aws/__init__.py
Normal file
0
ros2_ws/src/yolov3_ros/utils/aws/__init__.py
Normal file
26
ros2_ws/src/yolov3_ros/utils/aws/mime.sh
Normal file
26
ros2_ws/src/yolov3_ros/utils/aws/mime.sh
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# AWS EC2 instance startup 'MIME' script https://aws.amazon.com/premiumsupport/knowledge-center/execute-user-data-ec2/
|
||||||
|
# This script will run on every instance restart, not only on first start
|
||||||
|
# --- DO NOT COPY ABOVE COMMENTS WHEN PASTING INTO USERDATA ---
|
||||||
|
|
||||||
|
Content-Type: multipart/mixed; boundary="//"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
|
||||||
|
--//
|
||||||
|
Content-Type: text/cloud-config; charset="us-ascii"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
Content-Disposition: attachment; filename="cloud-config.txt"
|
||||||
|
|
||||||
|
#cloud-config
|
||||||
|
cloud_final_modules:
|
||||||
|
- [scripts-user, always]
|
||||||
|
|
||||||
|
--//
|
||||||
|
Content-Type: text/x-shellscript; charset="us-ascii"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
Content-Disposition: attachment; filename="userdata.txt"
|
||||||
|
|
||||||
|
#!/bin/bash
|
||||||
|
# --- paste contents of userdata.sh here ---
|
||||||
|
--//
|
||||||
40
ros2_ws/src/yolov3_ros/utils/aws/resume.py
Normal file
40
ros2_ws/src/yolov3_ros/utils/aws/resume.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Resume all interrupted trainings in yolov5/ dir including DDP trainings
|
||||||
|
# Usage: $ python utils/aws/resume.py
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import torch
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
FILE = Path(__file__).resolve()
|
||||||
|
ROOT = FILE.parents[2] # YOLOv5 root directory
|
||||||
|
if str(ROOT) not in sys.path:
|
||||||
|
sys.path.append(str(ROOT)) # add ROOT to PATH
|
||||||
|
|
||||||
|
port = 0 # --master_port
|
||||||
|
path = Path('').resolve()
|
||||||
|
for last in path.rglob('*/**/last.pt'):
|
||||||
|
ckpt = torch.load(last)
|
||||||
|
if ckpt['optimizer'] is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Load opt.yaml
|
||||||
|
with open(last.parent.parent / 'opt.yaml', errors='ignore') as f:
|
||||||
|
opt = yaml.safe_load(f)
|
||||||
|
|
||||||
|
# Get device count
|
||||||
|
d = opt['device'].split(',') # devices
|
||||||
|
nd = len(d) # number of devices
|
||||||
|
ddp = nd > 1 or (nd == 0 and torch.cuda.device_count() > 1) # distributed data parallel
|
||||||
|
|
||||||
|
if ddp: # multi-GPU
|
||||||
|
port += 1
|
||||||
|
cmd = f'python -m torch.distributed.run --nproc_per_node {nd} --master_port {port} train.py --resume {last}'
|
||||||
|
else: # single-GPU
|
||||||
|
cmd = f'python train.py --resume {last}'
|
||||||
|
|
||||||
|
cmd += ' > /dev/null 2>&1 &' # redirect output to dev/null and run in daemon thread
|
||||||
|
print(cmd)
|
||||||
|
os.system(cmd)
|
||||||
27
ros2_ws/src/yolov3_ros/utils/aws/userdata.sh
Normal file
27
ros2_ws/src/yolov3_ros/utils/aws/userdata.sh
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# AWS EC2 instance startup script https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html
|
||||||
|
# This script will run only once on first instance start (for a re-start script see mime.sh)
|
||||||
|
# /home/ubuntu (ubuntu) or /home/ec2-user (amazon-linux) is working dir
|
||||||
|
# Use >300 GB SSD
|
||||||
|
|
||||||
|
cd home/ubuntu
|
||||||
|
if [ ! -d yolov5 ]; then
|
||||||
|
echo "Running first-time script." # install dependencies, download COCO, pull Docker
|
||||||
|
git clone https://github.com/ultralytics/yolov5 -b master && sudo chmod -R 777 yolov5
|
||||||
|
cd yolov5
|
||||||
|
bash data/scripts/get_coco.sh && echo "COCO done." &
|
||||||
|
sudo docker pull ultralytics/yolov5:latest && echo "Docker done." &
|
||||||
|
python -m pip install --upgrade pip && pip install -r requirements.txt && python detect.py && echo "Requirements done." &
|
||||||
|
wait && echo "All tasks done." # finish background tasks
|
||||||
|
else
|
||||||
|
echo "Running re-start script." # resume interrupted runs
|
||||||
|
i=0
|
||||||
|
list=$(sudo docker ps -qa) # container list i.e. $'one\ntwo\nthree\nfour'
|
||||||
|
while IFS= read -r id; do
|
||||||
|
((i++))
|
||||||
|
echo "restarting container $i: $id"
|
||||||
|
sudo docker start $id
|
||||||
|
# sudo docker exec -it $id python train.py --resume # single-GPU
|
||||||
|
sudo docker exec -d $id python utils/aws/resume.py # multi-scenario
|
||||||
|
done <<<"$list"
|
||||||
|
fi
|
||||||
76
ros2_ws/src/yolov3_ros/utils/callbacks.py
Normal file
76
ros2_ws/src/yolov3_ros/utils/callbacks.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Callback utils
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Callbacks:
|
||||||
|
""""
|
||||||
|
Handles all registered callbacks for Hooks
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Define the available callbacks
|
||||||
|
_callbacks = {
|
||||||
|
'on_pretrain_routine_start': [],
|
||||||
|
'on_pretrain_routine_end': [],
|
||||||
|
|
||||||
|
'on_train_start': [],
|
||||||
|
'on_train_epoch_start': [],
|
||||||
|
'on_train_batch_start': [],
|
||||||
|
'optimizer_step': [],
|
||||||
|
'on_before_zero_grad': [],
|
||||||
|
'on_train_batch_end': [],
|
||||||
|
'on_train_epoch_end': [],
|
||||||
|
|
||||||
|
'on_val_start': [],
|
||||||
|
'on_val_batch_start': [],
|
||||||
|
'on_val_image_end': [],
|
||||||
|
'on_val_batch_end': [],
|
||||||
|
'on_val_end': [],
|
||||||
|
|
||||||
|
'on_fit_epoch_end': [], # fit = train + val
|
||||||
|
'on_model_save': [],
|
||||||
|
'on_train_end': [],
|
||||||
|
|
||||||
|
'teardown': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
def register_action(self, hook, name='', callback=None):
|
||||||
|
"""
|
||||||
|
Register a new action to a callback hook
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hook The callback hook name to register the action to
|
||||||
|
name The name of the action for later reference
|
||||||
|
callback The callback to fire
|
||||||
|
"""
|
||||||
|
assert hook in self._callbacks, f"hook '{hook}' not found in callbacks {self._callbacks}"
|
||||||
|
assert callable(callback), f"callback '{callback}' is not callable"
|
||||||
|
self._callbacks[hook].append({'name': name, 'callback': callback})
|
||||||
|
|
||||||
|
def get_registered_actions(self, hook=None):
|
||||||
|
""""
|
||||||
|
Returns all the registered actions by callback hook
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hook The name of the hook to check, defaults to all
|
||||||
|
"""
|
||||||
|
if hook:
|
||||||
|
return self._callbacks[hook]
|
||||||
|
else:
|
||||||
|
return self._callbacks
|
||||||
|
|
||||||
|
def run(self, hook, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Loop through the registered actions and fire all callbacks
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hook The name of the hook to check, defaults to all
|
||||||
|
args Arguments to receive from
|
||||||
|
kwargs Keyword Arguments to receive from
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert hook in self._callbacks, f"hook '{hook}' not found in callbacks {self._callbacks}"
|
||||||
|
|
||||||
|
for logger in self._callbacks[hook]:
|
||||||
|
logger['callback'](*args, **kwargs)
|
||||||
1221
ros2_ws/src/yolov3_ros/utils/dataloaders.py
Normal file
1221
ros2_ws/src/yolov3_ros/utils/dataloaders.py
Normal file
File diff suppressed because it is too large
Load Diff
1036
ros2_ws/src/yolov3_ros/utils/datasets.py
Executable file
1036
ros2_ws/src/yolov3_ros/utils/datasets.py
Executable file
File diff suppressed because it is too large
Load Diff
75
ros2_ws/src/yolov3_ros/utils/docker/Dockerfile
Normal file
75
ros2_ws/src/yolov3_ros/utils/docker/Dockerfile
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
# Builds ultralytics/yolov5:latest image on DockerHub https://hub.docker.com/r/ultralytics/yolov3
|
||||||
|
# Image is CUDA-optimized for YOLOv5 single/multi-GPU training and inference
|
||||||
|
|
||||||
|
# Start FROM NVIDIA PyTorch image https://ngc.nvidia.com/catalog/containers/nvidia:pytorch
|
||||||
|
# FROM docker.io/pytorch/pytorch:latest
|
||||||
|
FROM pytorch/pytorch:latest
|
||||||
|
|
||||||
|
# Downloads to user config dir
|
||||||
|
ADD https://ultralytics.com/assets/Arial.ttf https://ultralytics.com/assets/Arial.Unicode.ttf /root/.config/Ultralytics/
|
||||||
|
|
||||||
|
# Install linux packages
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
RUN apt update
|
||||||
|
RUN TZ=Etc/UTC apt install -y tzdata
|
||||||
|
RUN apt install --no-install-recommends -y gcc git zip curl htop libgl1-mesa-glx libglib2.0-0 libpython3-dev gnupg
|
||||||
|
# RUN alias python=python3
|
||||||
|
|
||||||
|
# Security updates
|
||||||
|
# https://security.snyk.io/vuln/SNYK-UBUNTU1804-OPENSSL-3314796
|
||||||
|
RUN apt upgrade --no-install-recommends -y openssl
|
||||||
|
|
||||||
|
# Create working directory
|
||||||
|
RUN rm -rf /usr/src/app && mkdir -p /usr/src/app
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Copy contents
|
||||||
|
# COPY . /usr/src/app (issues as not a .git directory)
|
||||||
|
RUN git clone https://github.com/ultralytics/yolov5 /usr/src/app
|
||||||
|
|
||||||
|
# Install pip packages
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN python3 -m pip install --upgrade pip wheel
|
||||||
|
RUN pip install --no-cache -r requirements.txt albumentations comet gsutil notebook \
|
||||||
|
coremltools onnx onnx-simplifier onnxruntime 'openvino-dev>=2022.3'
|
||||||
|
# tensorflow tensorflowjs \
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV OMP_NUM_THREADS=1
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
ENV DEBIAN_FRONTEND teletype
|
||||||
|
|
||||||
|
|
||||||
|
# Usage Examples -------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Build and Push
|
||||||
|
# t=ultralytics/yolov5:latest && sudo docker build -f utils/docker/Dockerfile -t $t . && sudo docker push $t
|
||||||
|
|
||||||
|
# Pull and Run
|
||||||
|
# t=ultralytics/yolov5:latest && sudo docker pull $t && sudo docker run -it --ipc=host --gpus all $t
|
||||||
|
|
||||||
|
# Pull and Run with local directory access
|
||||||
|
# t=ultralytics/yolov5:latest && sudo docker pull $t && sudo docker run -it --ipc=host --gpus all -v "$(pwd)"/datasets:/usr/src/datasets $t
|
||||||
|
|
||||||
|
# Kill all
|
||||||
|
# sudo docker kill $(sudo docker ps -q)
|
||||||
|
|
||||||
|
# Kill all image-based
|
||||||
|
# sudo docker kill $(sudo docker ps -qa --filter ancestor=ultralytics/yolov5:latest)
|
||||||
|
|
||||||
|
# DockerHub tag update
|
||||||
|
# t=ultralytics/yolov5:latest tnew=ultralytics/yolov5:v6.2 && sudo docker pull $t && sudo docker tag $t $tnew && sudo docker push $tnew
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
# sudo docker system prune -a --volumes
|
||||||
|
|
||||||
|
# Update Ubuntu drivers
|
||||||
|
# https://www.maketecheasier.com/install-nvidia-drivers-ubuntu/
|
||||||
|
|
||||||
|
# DDP test
|
||||||
|
# python -m torch.distributed.run --nproc_per_node 2 --master_port 1 train.py --epochs 3
|
||||||
|
|
||||||
|
# GCP VM from Image
|
||||||
|
# docker.io/ultralytics/yolov5:latest
|
||||||
41
ros2_ws/src/yolov3_ros/utils/docker/Dockerfile-arm64
Normal file
41
ros2_ws/src/yolov3_ros/utils/docker/Dockerfile-arm64
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
# Builds ultralytics/yolov5:latest-arm64 image on DockerHub https://hub.docker.com/r/ultralytics/yolov3
|
||||||
|
# Image is aarch64-compatible for Apple M1 and other ARM architectures i.e. Jetson Nano and Raspberry Pi
|
||||||
|
|
||||||
|
# Start FROM Ubuntu image https://hub.docker.com/_/ubuntu
|
||||||
|
FROM arm64v8/ubuntu:rolling
|
||||||
|
|
||||||
|
# Downloads to user config dir
|
||||||
|
ADD https://ultralytics.com/assets/Arial.ttf https://ultralytics.com/assets/Arial.Unicode.ttf /root/.config/Ultralytics/
|
||||||
|
|
||||||
|
# Install linux packages
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
RUN apt update
|
||||||
|
RUN TZ=Etc/UTC apt install -y tzdata
|
||||||
|
RUN apt install --no-install-recommends -y python3-pip git zip curl htop gcc libgl1-mesa-glx libglib2.0-0 libpython3-dev
|
||||||
|
# RUN alias python=python3
|
||||||
|
|
||||||
|
# Install pip packages
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN python3 -m pip install --upgrade pip wheel
|
||||||
|
RUN pip install --no-cache -r requirements.txt albumentations gsutil notebook \
|
||||||
|
coremltools onnx onnxruntime
|
||||||
|
# tensorflow-aarch64 tensorflowjs \
|
||||||
|
|
||||||
|
# Create working directory
|
||||||
|
RUN mkdir -p /usr/src/app
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Copy contents
|
||||||
|
# COPY . /usr/src/app (issues as not a .git directory)
|
||||||
|
RUN git clone https://github.com/ultralytics/yolov5 /usr/src/app
|
||||||
|
ENV DEBIAN_FRONTEND teletype
|
||||||
|
|
||||||
|
|
||||||
|
# Usage Examples -------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Build and Push
|
||||||
|
# t=ultralytics/yolov5:latest-arm64 && sudo docker build --platform linux/arm64 -f utils/docker/Dockerfile-arm64 -t $t . && sudo docker push $t
|
||||||
|
|
||||||
|
# Pull and Run
|
||||||
|
# t=ultralytics/yolov5:latest-arm64 && sudo docker pull $t && sudo docker run -it --ipc=host -v "$(pwd)"/datasets:/usr/src/datasets $t
|
||||||
42
ros2_ws/src/yolov3_ros/utils/docker/Dockerfile-cpu
Normal file
42
ros2_ws/src/yolov3_ros/utils/docker/Dockerfile-cpu
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
# Builds ultralytics/yolov5:latest-cpu image on DockerHub https://hub.docker.com/r/ultralytics/yolov3
|
||||||
|
# Image is CPU-optimized for ONNX, OpenVINO and PyTorch YOLOv5 deployments
|
||||||
|
|
||||||
|
# Start FROM Ubuntu image https://hub.docker.com/_/ubuntu
|
||||||
|
FROM ubuntu:rolling
|
||||||
|
|
||||||
|
# Downloads to user config dir
|
||||||
|
ADD https://ultralytics.com/assets/Arial.ttf https://ultralytics.com/assets/Arial.Unicode.ttf /root/.config/Ultralytics/
|
||||||
|
|
||||||
|
# Install linux packages
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
RUN apt update
|
||||||
|
RUN TZ=Etc/UTC apt install -y tzdata
|
||||||
|
RUN apt install --no-install-recommends -y python3-pip git zip curl htop libgl1-mesa-glx libglib2.0-0 libpython3-dev gnupg
|
||||||
|
# RUN alias python=python3
|
||||||
|
|
||||||
|
# Install pip packages
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN python3 -m pip install --upgrade pip wheel
|
||||||
|
RUN pip install --no-cache -r requirements.txt albumentations gsutil notebook \
|
||||||
|
coremltools onnx onnx-simplifier onnxruntime 'openvino-dev>=2022.3' \
|
||||||
|
# tensorflow tensorflowjs \
|
||||||
|
--extra-index-url https://download.pytorch.org/whl/cpu
|
||||||
|
|
||||||
|
# Create working directory
|
||||||
|
RUN mkdir -p /usr/src/app
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Copy contents
|
||||||
|
# COPY . /usr/src/app (issues as not a .git directory)
|
||||||
|
RUN git clone https://github.com/ultralytics/yolov5 /usr/src/app
|
||||||
|
ENV DEBIAN_FRONTEND teletype
|
||||||
|
|
||||||
|
|
||||||
|
# Usage Examples -------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Build and Push
|
||||||
|
# t=ultralytics/yolov5:latest-cpu && sudo docker build -f utils/docker/Dockerfile-cpu -t $t . && sudo docker push $t
|
||||||
|
|
||||||
|
# Pull and Run
|
||||||
|
# t=ultralytics/yolov5:latest-cpu && sudo docker pull $t && sudo docker run -it --ipc=host -v "$(pwd)"/datasets:/usr/src/datasets $t
|
||||||
149
ros2_ws/src/yolov3_ros/utils/downloads.py
Normal file
149
ros2_ws/src/yolov3_ros/utils/downloads.py
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Download utils
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import urllib
|
||||||
|
from pathlib import Path
|
||||||
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import torch
|
||||||
|
|
||||||
|
|
||||||
|
def gsutil_getsize(url=''):
|
||||||
|
# gs://bucket/file size https://cloud.google.com/storage/docs/gsutil/commands/du
|
||||||
|
s = subprocess.check_output(f'gsutil du {url}', shell=True).decode('utf-8')
|
||||||
|
return eval(s.split(' ')[0]) if len(s) else 0 # bytes
|
||||||
|
|
||||||
|
|
||||||
|
def safe_download(file, url, url2=None, min_bytes=1E0, error_msg=''):
|
||||||
|
# Attempts to download file from url or url2, checks and removes incomplete downloads < min_bytes
|
||||||
|
file = Path(file)
|
||||||
|
assert_msg = f"Downloaded file '{file}' does not exist or size is < min_bytes={min_bytes}"
|
||||||
|
try: # url1
|
||||||
|
print(f'Downloading {url} to {file}...')
|
||||||
|
torch.hub.download_url_to_file(url, str(file))
|
||||||
|
assert file.exists() and file.stat().st_size > min_bytes, assert_msg # check
|
||||||
|
except Exception as e: # url2
|
||||||
|
file.unlink(missing_ok=True) # remove partial downloads
|
||||||
|
print(f'ERROR: {e}\nRe-attempting {url2 or url} to {file}...')
|
||||||
|
os.system(f"curl -L '{url2 or url}' -o '{file}' --retry 3 -C -") # curl download, retry and resume on fail
|
||||||
|
finally:
|
||||||
|
if not file.exists() or file.stat().st_size < min_bytes: # check
|
||||||
|
file.unlink(missing_ok=True) # remove partial downloads
|
||||||
|
print(f"ERROR: {assert_msg}\n{error_msg}")
|
||||||
|
print('')
|
||||||
|
|
||||||
|
|
||||||
|
def attempt_download(file, repo='ultralytics/yolov3'): # from utils.downloads import *; attempt_download()
|
||||||
|
# Attempt file download if does not exist
|
||||||
|
file = Path(str(file).strip().replace("'", ''))
|
||||||
|
|
||||||
|
if not file.exists():
|
||||||
|
# URL specified
|
||||||
|
name = Path(urllib.parse.unquote(str(file))).name # decode '%2F' to '/' etc.
|
||||||
|
if str(file).startswith(('http:/', 'https:/')): # download
|
||||||
|
url = str(file).replace(':/', '://') # Pathlib turns :// -> :/
|
||||||
|
name = name.split('?')[0] # parse authentication https://url.com/file.txt?auth...
|
||||||
|
safe_download(file=name, url=url, min_bytes=1E5)
|
||||||
|
return name
|
||||||
|
|
||||||
|
# GitHub assets
|
||||||
|
file.parent.mkdir(parents=True, exist_ok=True) # make parent dir (if required)
|
||||||
|
try:
|
||||||
|
response = requests.get(f'https://api.github.com/repos/{repo}/releases/latest').json() # github api
|
||||||
|
assets = [x['name'] for x in response['assets']] # release assets, i.e. ['yolov3.pt'...]
|
||||||
|
tag = response['tag_name'] # i.e. 'v1.0'
|
||||||
|
except: # fallback plan
|
||||||
|
assets = ['yolov3.pt', 'yolov3-spp.pt', 'yolov3-tiny.pt']
|
||||||
|
try:
|
||||||
|
tag = subprocess.check_output('git tag', shell=True, stderr=subprocess.STDOUT).decode().split()[-1]
|
||||||
|
except:
|
||||||
|
tag = 'v9.5.0' # current release
|
||||||
|
|
||||||
|
if name in assets:
|
||||||
|
safe_download(file,
|
||||||
|
url=f'https://github.com/{repo}/releases/download/{tag}/{name}',
|
||||||
|
# url2=f'https://storage.googleapis.com/{repo}/ckpt/{name}', # backup url (optional)
|
||||||
|
min_bytes=1E5,
|
||||||
|
error_msg=f'{file} missing, try downloading from https://github.com/{repo}/releases/')
|
||||||
|
|
||||||
|
return str(file)
|
||||||
|
|
||||||
|
|
||||||
|
def gdrive_download(id='16TiPfZj7htmTyhntwcZyEEAejOUxuT6m', file='tmp.zip'):
|
||||||
|
# Downloads a file from Google Drive. from yolov3.utils.downloads import *; gdrive_download()
|
||||||
|
t = time.time()
|
||||||
|
file = Path(file)
|
||||||
|
cookie = Path('cookie') # gdrive cookie
|
||||||
|
print(f'Downloading https://drive.google.com/uc?export=download&id={id} as {file}... ', end='')
|
||||||
|
file.unlink(missing_ok=True) # remove existing file
|
||||||
|
cookie.unlink(missing_ok=True) # remove existing cookie
|
||||||
|
|
||||||
|
# Attempt file download
|
||||||
|
out = "NUL" if platform.system() == "Windows" else "/dev/null"
|
||||||
|
os.system(f'curl -c ./cookie -s -L "drive.google.com/uc?export=download&id={id}" > {out}')
|
||||||
|
if os.path.exists('cookie'): # large file
|
||||||
|
s = f'curl -Lb ./cookie "drive.google.com/uc?export=download&confirm={get_token()}&id={id}" -o {file}'
|
||||||
|
else: # small file
|
||||||
|
s = f'curl -s -L -o {file} "drive.google.com/uc?export=download&id={id}"'
|
||||||
|
r = os.system(s) # execute, capture return
|
||||||
|
cookie.unlink(missing_ok=True) # remove existing cookie
|
||||||
|
|
||||||
|
# Error check
|
||||||
|
if r != 0:
|
||||||
|
file.unlink(missing_ok=True) # remove partial
|
||||||
|
print('Download error ') # raise Exception('Download error')
|
||||||
|
return r
|
||||||
|
|
||||||
|
# Unzip if archive
|
||||||
|
if file.suffix == '.zip':
|
||||||
|
print('unzipping... ', end='')
|
||||||
|
ZipFile(file).extractall(path=file.parent) # unzip
|
||||||
|
file.unlink() # remove zip
|
||||||
|
|
||||||
|
print(f'Done ({time.time() - t:.1f}s)')
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def get_token(cookie="./cookie"):
|
||||||
|
with open(cookie) as f:
|
||||||
|
for line in f:
|
||||||
|
if "download" in line:
|
||||||
|
return line.split()[-1]
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Google utils: https://cloud.google.com/storage/docs/reference/libraries ----------------------------------------------
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def upload_blob(bucket_name, source_file_name, destination_blob_name):
|
||||||
|
# # Uploads a file to a bucket
|
||||||
|
# # https://cloud.google.com/storage/docs/uploading-objects#storage-upload-object-python
|
||||||
|
#
|
||||||
|
# storage_client = storage.Client()
|
||||||
|
# bucket = storage_client.get_bucket(bucket_name)
|
||||||
|
# blob = bucket.blob(destination_blob_name)
|
||||||
|
#
|
||||||
|
# blob.upload_from_filename(source_file_name)
|
||||||
|
#
|
||||||
|
# print('File {} uploaded to {}.'.format(
|
||||||
|
# source_file_name,
|
||||||
|
# destination_blob_name))
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def download_blob(bucket_name, source_blob_name, destination_file_name):
|
||||||
|
# # Uploads a blob from a bucket
|
||||||
|
# storage_client = storage.Client()
|
||||||
|
# bucket = storage_client.get_bucket(bucket_name)
|
||||||
|
# blob = bucket.blob(source_blob_name)
|
||||||
|
#
|
||||||
|
# blob.download_to_filename(destination_file_name)
|
||||||
|
#
|
||||||
|
# print('Blob {} downloaded to {}.'.format(
|
||||||
|
# source_blob_name,
|
||||||
|
# destination_file_name))
|
||||||
73
ros2_ws/src/yolov3_ros/utils/flask_rest_api/README.md
Normal file
73
ros2_ws/src/yolov3_ros/utils/flask_rest_api/README.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# Flask REST API
|
||||||
|
|
||||||
|
[REST](https://en.wikipedia.org/wiki/Representational_state_transfer) [API](https://en.wikipedia.org/wiki/API)s are
|
||||||
|
commonly used to expose Machine Learning (ML) models to other services. This folder contains an example REST API
|
||||||
|
created using Flask to expose the YOLOv5s model from [PyTorch Hub](https://pytorch.org/hub/ultralytics_yolov5/).
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
[Flask](https://palletsprojects.com/p/flask/) is required. Install with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ pip install Flask
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
After Flask installation run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ python3 restapi.py --port 5000
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use [curl](https://curl.se/) to perform a request:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ curl -X POST -F image=@zidane.jpg 'http://localhost:5000/v1/object-detection/yolov5s'
|
||||||
|
```
|
||||||
|
|
||||||
|
The model inference results are returned as a JSON response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"class": 0,
|
||||||
|
"confidence": 0.8900438547,
|
||||||
|
"height": 0.9318675399,
|
||||||
|
"name": "person",
|
||||||
|
"width": 0.3264600933,
|
||||||
|
"xcenter": 0.7438579798,
|
||||||
|
"ycenter": 0.5207948685
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": 0,
|
||||||
|
"confidence": 0.8440024257,
|
||||||
|
"height": 0.7155083418,
|
||||||
|
"name": "person",
|
||||||
|
"width": 0.6546785235,
|
||||||
|
"xcenter": 0.427829951,
|
||||||
|
"ycenter": 0.6334488392
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": 27,
|
||||||
|
"confidence": 0.3771208823,
|
||||||
|
"height": 0.3902671337,
|
||||||
|
"name": "tie",
|
||||||
|
"width": 0.0696444362,
|
||||||
|
"xcenter": 0.3675483763,
|
||||||
|
"ycenter": 0.7991207838
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": 27,
|
||||||
|
"confidence": 0.3527112305,
|
||||||
|
"height": 0.1540903747,
|
||||||
|
"name": "tie",
|
||||||
|
"width": 0.0336618312,
|
||||||
|
"xcenter": 0.7814827561,
|
||||||
|
"ycenter": 0.5065554976
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
An example python script to perform inference using [requests](https://docs.python-requests.org/en/master/) is given
|
||||||
|
in `example_request.py`
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Perform test request
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pprint
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
DETECTION_URL = 'http://localhost:5000/v1/object-detection/yolov5s'
|
||||||
|
IMAGE = 'zidane.jpg'
|
||||||
|
|
||||||
|
# Read image
|
||||||
|
with open(IMAGE, 'rb') as f:
|
||||||
|
image_data = f.read()
|
||||||
|
|
||||||
|
response = requests.post(DETECTION_URL, files={'image': image_data}).json()
|
||||||
|
|
||||||
|
pprint.pprint(response)
|
||||||
48
ros2_ws/src/yolov3_ros/utils/flask_rest_api/restapi.py
Normal file
48
ros2_ws/src/yolov3_ros/utils/flask_rest_api/restapi.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Run a Flask REST API exposing one or more YOLOv5s models
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import io
|
||||||
|
|
||||||
|
import torch
|
||||||
|
from flask import Flask, request
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
models = {}
|
||||||
|
|
||||||
|
DETECTION_URL = '/v1/object-detection/<model>'
|
||||||
|
|
||||||
|
|
||||||
|
@app.route(DETECTION_URL, methods=['POST'])
|
||||||
|
def predict(model):
|
||||||
|
if request.method != 'POST':
|
||||||
|
return
|
||||||
|
|
||||||
|
if request.files.get('image'):
|
||||||
|
# Method 1
|
||||||
|
# with request.files["image"] as f:
|
||||||
|
# im = Image.open(io.BytesIO(f.read()))
|
||||||
|
|
||||||
|
# Method 2
|
||||||
|
im_file = request.files['image']
|
||||||
|
im_bytes = im_file.read()
|
||||||
|
im = Image.open(io.BytesIO(im_bytes))
|
||||||
|
|
||||||
|
if model in models:
|
||||||
|
results = models[model](im, size=640) # reduce size=320 for faster inference
|
||||||
|
return results.pandas().xyxy[0].to_json(orient='records')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser(description='Flask API exposing YOLOv5 model')
|
||||||
|
parser.add_argument('--port', default=5000, type=int, help='port number')
|
||||||
|
parser.add_argument('--model', nargs='+', default=['yolov5s'], help='model(s) to run, i.e. --model yolov5n yolov5s')
|
||||||
|
opt = parser.parse_args()
|
||||||
|
|
||||||
|
for m in opt.model:
|
||||||
|
models[m] = torch.hub.load('ultralytics/yolov5', m, force_reload=True, skip_validation=True)
|
||||||
|
|
||||||
|
app.run(host='0.0.0.0', port=opt.port) # debug=True causes Restarting with stat
|
||||||
841
ros2_ws/src/yolov3_ros/utils/general.py
Executable file
841
ros2_ws/src/yolov3_ros/utils/general.py
Executable file
@ -0,0 +1,841 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
General utils
|
||||||
|
"""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import glob
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import signal
|
||||||
|
import time
|
||||||
|
import urllib
|
||||||
|
from itertools import repeat
|
||||||
|
from multiprocessing.pool import ThreadPool
|
||||||
|
from pathlib import Path
|
||||||
|
from subprocess import check_output
|
||||||
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
import pkg_resources as pkg
|
||||||
|
import torch
|
||||||
|
import torchvision
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from utils.downloads import gsutil_getsize
|
||||||
|
from utils.metrics import box_iou, fitness
|
||||||
|
|
||||||
|
# Settings
|
||||||
|
torch.set_printoptions(linewidth=320, precision=5, profile='long')
|
||||||
|
np.set_printoptions(linewidth=320, formatter={'float_kind': '{:11.5g}'.format}) # format short g, %precision=5
|
||||||
|
pd.options.display.max_columns = 10
|
||||||
|
cv2.setNumThreads(0) # prevent OpenCV from multithreading (incompatible with PyTorch DataLoader)
|
||||||
|
os.environ['NUMEXPR_MAX_THREADS'] = str(min(os.cpu_count(), 8)) # NumExpr max threads
|
||||||
|
|
||||||
|
FILE = Path(__file__).resolve()
|
||||||
|
ROOT = FILE.parents[1] # root directory
|
||||||
|
|
||||||
|
|
||||||
|
def set_logging(name=None, verbose=True):
|
||||||
|
# Sets level and returns logger
|
||||||
|
rank = int(os.getenv('RANK', -1)) # rank in world for Multi-GPU trainings
|
||||||
|
logging.basicConfig(format="%(message)s", level=logging.INFO if (verbose and rank in (-1, 0)) else logging.WARNING)
|
||||||
|
return logging.getLogger(name)
|
||||||
|
|
||||||
|
|
||||||
|
LOGGER = set_logging(__name__) # define globally (used in train.py, val.py, detect.py, etc.)
|
||||||
|
|
||||||
|
|
||||||
|
class Profile(contextlib.ContextDecorator):
|
||||||
|
# Usage: @Profile() decorator or 'with Profile():' context manager
|
||||||
|
def __enter__(self):
|
||||||
|
self.start = time.time()
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
print(f'Profile results: {time.time() - self.start:.5f}s')
|
||||||
|
|
||||||
|
|
||||||
|
class Timeout(contextlib.ContextDecorator):
|
||||||
|
# Usage: @Timeout(seconds) decorator or 'with Timeout(seconds):' context manager
|
||||||
|
def __init__(self, seconds, *, timeout_msg='', suppress_timeout_errors=True):
|
||||||
|
self.seconds = int(seconds)
|
||||||
|
self.timeout_message = timeout_msg
|
||||||
|
self.suppress = bool(suppress_timeout_errors)
|
||||||
|
|
||||||
|
def _timeout_handler(self, signum, frame):
|
||||||
|
raise TimeoutError(self.timeout_message)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
signal.signal(signal.SIGALRM, self._timeout_handler) # Set handler for SIGALRM
|
||||||
|
signal.alarm(self.seconds) # start countdown for SIGALRM to be raised
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
signal.alarm(0) # Cancel SIGALRM if it's scheduled
|
||||||
|
if self.suppress and exc_type is TimeoutError: # Suppress TimeoutError
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class WorkingDirectory(contextlib.ContextDecorator):
|
||||||
|
# Usage: @WorkingDirectory(dir) decorator or 'with WorkingDirectory(dir):' context manager
|
||||||
|
def __init__(self, new_dir):
|
||||||
|
self.dir = new_dir # new dir
|
||||||
|
self.cwd = Path.cwd().resolve() # current dir
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
os.chdir(self.dir)
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
os.chdir(self.cwd)
|
||||||
|
|
||||||
|
|
||||||
|
def try_except(func):
|
||||||
|
# try-except function. Usage: @try_except decorator
|
||||||
|
def handler(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
func(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
|
|
||||||
|
def methods(instance):
|
||||||
|
# Get class/instance methods
|
||||||
|
return [f for f in dir(instance) if callable(getattr(instance, f)) and not f.startswith("__")]
|
||||||
|
|
||||||
|
|
||||||
|
def print_args(name, opt):
|
||||||
|
# Print argparser arguments
|
||||||
|
LOGGER.info(colorstr(f'{name}: ') + ', '.join(f'{k}={v}' for k, v in vars(opt).items()))
|
||||||
|
|
||||||
|
|
||||||
|
def init_seeds(seed=0):
|
||||||
|
# Initialize random number generator (RNG) seeds https://pytorch.org/docs/stable/notes/randomness.html
|
||||||
|
# cudnn seed 0 settings are slower and more reproducible, else faster and less reproducible
|
||||||
|
import torch.backends.cudnn as cudnn
|
||||||
|
random.seed(seed)
|
||||||
|
np.random.seed(seed)
|
||||||
|
torch.manual_seed(seed)
|
||||||
|
cudnn.benchmark, cudnn.deterministic = (False, True) if seed == 0 else (True, False)
|
||||||
|
|
||||||
|
|
||||||
|
def intersect_dicts(da, db, exclude=()):
|
||||||
|
# Dictionary intersection of matching keys and shapes, omitting 'exclude' keys, using da values
|
||||||
|
return {k: v for k, v in da.items() if k in db and not any(x in k for x in exclude) and v.shape == db[k].shape}
|
||||||
|
|
||||||
|
|
||||||
|
def get_latest_run(search_dir='.'):
|
||||||
|
# Return path to most recent 'last.pt' in /runs (i.e. to --resume from)
|
||||||
|
last_list = glob.glob(f'{search_dir}/**/last*.pt', recursive=True)
|
||||||
|
return max(last_list, key=os.path.getctime) if last_list else ''
|
||||||
|
|
||||||
|
|
||||||
|
def user_config_dir(dir='Ultralytics', env_var='YOLOV3_CONFIG_DIR'):
|
||||||
|
# Return path of user configuration directory. Prefer environment variable if exists. Make dir if required.
|
||||||
|
env = os.getenv(env_var)
|
||||||
|
if env:
|
||||||
|
path = Path(env) # use environment variable
|
||||||
|
else:
|
||||||
|
cfg = {'Windows': 'AppData/Roaming', 'Linux': '.config', 'Darwin': 'Library/Application Support'} # 3 OS dirs
|
||||||
|
path = Path.home() / cfg.get(platform.system(), '') # OS-specific config dir
|
||||||
|
path = (path if is_writeable(path) else Path('/tmp')) / dir # GCP and AWS lambda fix, only /tmp is writeable
|
||||||
|
path.mkdir(exist_ok=True) # make if required
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def is_writeable(dir, test=False):
|
||||||
|
# Return True if directory has write permissions, test opening a file with write permissions if test=True
|
||||||
|
if test: # method 1
|
||||||
|
file = Path(dir) / 'tmp.txt'
|
||||||
|
try:
|
||||||
|
with open(file, 'w'): # open file with write permissions
|
||||||
|
pass
|
||||||
|
file.unlink() # remove file
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
else: # method 2
|
||||||
|
return os.access(dir, os.R_OK) # possible issues on Windows
|
||||||
|
|
||||||
|
|
||||||
|
def is_docker():
|
||||||
|
# Is environment a Docker container?
|
||||||
|
return Path('/workspace').exists() # or Path('/.dockerenv').exists()
|
||||||
|
|
||||||
|
|
||||||
|
def is_colab():
|
||||||
|
# Is environment a Google Colab instance?
|
||||||
|
try:
|
||||||
|
import google.colab
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_pip():
|
||||||
|
# Is file in a pip package?
|
||||||
|
return 'site-packages' in Path(__file__).resolve().parts
|
||||||
|
|
||||||
|
|
||||||
|
def is_ascii(s=''):
|
||||||
|
# Is string composed of all ASCII (no UTF) characters? (note str().isascii() introduced in python 3.7)
|
||||||
|
s = str(s) # convert list, tuple, None, etc. to str
|
||||||
|
return len(s.encode().decode('ascii', 'ignore')) == len(s)
|
||||||
|
|
||||||
|
|
||||||
|
def is_chinese(s='人工智能'):
|
||||||
|
# Is string composed of any Chinese characters?
|
||||||
|
return re.search('[\u4e00-\u9fff]', s)
|
||||||
|
|
||||||
|
|
||||||
|
def emojis(str=''):
|
||||||
|
# Return platform-dependent emoji-safe version of string
|
||||||
|
return str.encode().decode('ascii', 'ignore') if platform.system() == 'Windows' else str
|
||||||
|
|
||||||
|
|
||||||
|
def file_size(path):
|
||||||
|
# Return file/dir size (MB)
|
||||||
|
path = Path(path)
|
||||||
|
if path.is_file():
|
||||||
|
return path.stat().st_size / 1E6
|
||||||
|
elif path.is_dir():
|
||||||
|
return sum(f.stat().st_size for f in path.glob('**/*') if f.is_file()) / 1E6
|
||||||
|
else:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def check_online():
|
||||||
|
# Check internet connectivity
|
||||||
|
import socket
|
||||||
|
try:
|
||||||
|
socket.create_connection(("1.1.1.1", 443), 5) # check host accessibility
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@try_except
|
||||||
|
@WorkingDirectory(ROOT)
|
||||||
|
def check_git_status():
|
||||||
|
# Recommend 'git pull' if code is out of date
|
||||||
|
msg = ', for updates see https://github.com/ultralytics/yolov3'
|
||||||
|
print(colorstr('github: '), end='')
|
||||||
|
assert Path('.git').exists(), 'skipping check (not a git repository)' + msg
|
||||||
|
assert not is_docker(), 'skipping check (Docker image)' + msg
|
||||||
|
assert check_online(), 'skipping check (offline)' + msg
|
||||||
|
|
||||||
|
cmd = 'git fetch && git config --get remote.origin.url'
|
||||||
|
url = check_output(cmd, shell=True, timeout=5).decode().strip().rstrip('.git') # git fetch
|
||||||
|
branch = check_output('git rev-parse --abbrev-ref HEAD', shell=True).decode().strip() # checked out
|
||||||
|
n = int(check_output(f'git rev-list {branch}..origin/master --count', shell=True)) # commits behind
|
||||||
|
if n > 0:
|
||||||
|
s = f"⚠️ YOLOv3 is out of date by {n} commit{'s' * (n > 1)}. Use `git pull` or `git clone {url}` to update."
|
||||||
|
else:
|
||||||
|
s = f'up to date with {url} ✅'
|
||||||
|
print(emojis(s)) # emoji-safe
|
||||||
|
|
||||||
|
|
||||||
|
def check_python(minimum='3.6.2'):
|
||||||
|
# Check current python version vs. required python version
|
||||||
|
check_version(platform.python_version(), minimum, name='Python ', hard=True)
|
||||||
|
|
||||||
|
|
||||||
|
def check_version(current='0.0.0', minimum='0.0.0', name='version ', pinned=False, hard=False):
|
||||||
|
# Check version vs. required version
|
||||||
|
current, minimum = (pkg.parse_version(x) for x in (current, minimum))
|
||||||
|
result = (current == minimum) if pinned else (current >= minimum) # bool
|
||||||
|
if hard: # assert min requirements met
|
||||||
|
assert result, f'{name}{minimum} required by YOLOv3, but {name}{current} is currently installed'
|
||||||
|
else:
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@try_except
|
||||||
|
def check_requirements(requirements=ROOT / 'requirements.txt', exclude=(), install=True):
|
||||||
|
# Check installed dependencies meet requirements (pass *.txt file or list of packages)
|
||||||
|
prefix = colorstr('red', 'bold', 'requirements:')
|
||||||
|
check_python() # check python version
|
||||||
|
if isinstance(requirements, (str, Path)): # requirements.txt file
|
||||||
|
file = Path(requirements)
|
||||||
|
assert file.exists(), f"{prefix} {file.resolve()} not found, check failed."
|
||||||
|
with file.open() as f:
|
||||||
|
requirements = [f'{x.name}{x.specifier}' for x in pkg.parse_requirements(f) if x.name not in exclude]
|
||||||
|
else: # list or tuple of packages
|
||||||
|
requirements = [x for x in requirements if x not in exclude]
|
||||||
|
|
||||||
|
n = 0 # number of packages updates
|
||||||
|
for r in requirements:
|
||||||
|
try:
|
||||||
|
pkg.require(r)
|
||||||
|
except Exception as e: # DistributionNotFound or VersionConflict if requirements not met
|
||||||
|
s = f"{prefix} {r} not found and is required by YOLOv3"
|
||||||
|
if install:
|
||||||
|
print(f"{s}, attempting auto-update...")
|
||||||
|
try:
|
||||||
|
assert check_online(), f"'pip install {r}' skipped (offline)"
|
||||||
|
print(check_output(f"pip install '{r}'", shell=True).decode())
|
||||||
|
n += 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f'{prefix} {e}')
|
||||||
|
else:
|
||||||
|
print(f'{s}. Please install and rerun your command.')
|
||||||
|
|
||||||
|
if n: # if packages updated
|
||||||
|
source = file.resolve() if 'file' in locals() else requirements
|
||||||
|
s = f"{prefix} {n} package{'s' * (n > 1)} updated per {source}\n" \
|
||||||
|
f"{prefix} ⚠️ {colorstr('bold', 'Restart runtime or rerun command for updates to take effect')}\n"
|
||||||
|
print(emojis(s))
|
||||||
|
|
||||||
|
|
||||||
|
def check_img_size(imgsz, s=32, floor=0):
|
||||||
|
# Verify image size is a multiple of stride s in each dimension
|
||||||
|
if isinstance(imgsz, int): # integer i.e. img_size=640
|
||||||
|
new_size = max(make_divisible(imgsz, int(s)), floor)
|
||||||
|
else: # list i.e. img_size=[640, 480]
|
||||||
|
new_size = [max(make_divisible(x, int(s)), floor) for x in imgsz]
|
||||||
|
if new_size != imgsz:
|
||||||
|
print(f'WARNING: --img-size {imgsz} must be multiple of max stride {s}, updating to {new_size}')
|
||||||
|
return new_size
|
||||||
|
|
||||||
|
|
||||||
|
def check_imshow():
|
||||||
|
# Check if environment supports image displays
|
||||||
|
try:
|
||||||
|
assert not is_docker(), 'cv2.imshow() is disabled in Docker environments'
|
||||||
|
assert not is_colab(), 'cv2.imshow() is disabled in Google Colab environments'
|
||||||
|
cv2.imshow('test', np.zeros((1, 1, 3)))
|
||||||
|
cv2.waitKey(1)
|
||||||
|
cv2.destroyAllWindows()
|
||||||
|
cv2.waitKey(1)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f'WARNING: Environment does not support cv2.imshow() or PIL Image.show() image displays\n{e}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def check_suffix(file='yolov3.pt', suffix=('.pt',), msg=''):
|
||||||
|
# Check file(s) for acceptable suffix
|
||||||
|
if file and suffix:
|
||||||
|
if isinstance(suffix, str):
|
||||||
|
suffix = [suffix]
|
||||||
|
for f in file if isinstance(file, (list, tuple)) else [file]:
|
||||||
|
s = Path(f).suffix.lower() # file suffix
|
||||||
|
if len(s):
|
||||||
|
assert s in suffix, f"{msg}{f} acceptable suffix is {suffix}"
|
||||||
|
|
||||||
|
|
||||||
|
def check_yaml(file, suffix=('.yaml', '.yml')):
|
||||||
|
# Search/download YAML file (if necessary) and return path, checking suffix
|
||||||
|
return check_file(file, suffix)
|
||||||
|
|
||||||
|
|
||||||
|
def check_file(file, suffix=''):
|
||||||
|
# Search/download file (if necessary) and return path
|
||||||
|
check_suffix(file, suffix) # optional
|
||||||
|
file = str(file) # convert to str()
|
||||||
|
if Path(file).is_file() or file == '': # exists
|
||||||
|
return file
|
||||||
|
elif file.startswith(('http:/', 'https:/')): # download
|
||||||
|
url = str(Path(file).as_posix()).replace(':/', '://') # Pathlib turns :// -> :/
|
||||||
|
file = Path(urllib.parse.unquote(file).split('?')[0]).name # '%2F' to '/', split https://url.com/file.txt?auth
|
||||||
|
if Path(file).is_file():
|
||||||
|
print(f'Found {url} locally at {file}') # file already exists
|
||||||
|
else:
|
||||||
|
print(f'Downloading {url} to {file}...')
|
||||||
|
torch.hub.download_url_to_file(url, file)
|
||||||
|
assert Path(file).exists() and Path(file).stat().st_size > 0, f'File download failed: {url}' # check
|
||||||
|
return file
|
||||||
|
else: # search
|
||||||
|
files = []
|
||||||
|
for d in 'data', 'models', 'utils': # search directories
|
||||||
|
files.extend(glob.glob(str(ROOT / d / '**' / file), recursive=True)) # find file
|
||||||
|
assert len(files), f'File not found: {file}' # assert file was found
|
||||||
|
assert len(files) == 1, f"Multiple files match '{file}', specify exact path: {files}" # assert unique
|
||||||
|
return files[0] # return file
|
||||||
|
|
||||||
|
|
||||||
|
def check_dataset(data, autodownload=True):
|
||||||
|
# Download and/or unzip dataset if not found locally
|
||||||
|
# Usage: https://github.com/ultralytics/yolov5/releases/download/v1.0/coco128_with_yaml.zip
|
||||||
|
|
||||||
|
# Download (optional)
|
||||||
|
extract_dir = ''
|
||||||
|
if isinstance(data, (str, Path)) and str(data).endswith('.zip'): # i.e. gs://bucket/dir/coco128.zip
|
||||||
|
download(data, dir='../datasets', unzip=True, delete=False, curl=False, threads=1)
|
||||||
|
data = next((Path('../datasets') / Path(data).stem).rglob('*.yaml'))
|
||||||
|
extract_dir, autodownload = data.parent, False
|
||||||
|
|
||||||
|
# Read yaml (optional)
|
||||||
|
if isinstance(data, (str, Path)):
|
||||||
|
with open(data, errors='ignore') as f:
|
||||||
|
data = yaml.safe_load(f) # dictionary
|
||||||
|
|
||||||
|
# Parse yaml
|
||||||
|
path = extract_dir or Path(data.get('path') or '') # optional 'path' default to '.'
|
||||||
|
for k in 'train', 'val', 'test':
|
||||||
|
if data.get(k): # prepend path
|
||||||
|
data[k] = str(path / data[k]) if isinstance(data[k], str) else [str(path / x) for x in data[k]]
|
||||||
|
|
||||||
|
assert 'nc' in data, "Dataset 'nc' key missing."
|
||||||
|
if 'names' not in data:
|
||||||
|
data['names'] = [f'class{i}' for i in range(data['nc'])] # assign class names if missing
|
||||||
|
train, val, test, s = (data.get(x) for x in ('train', 'val', 'test', 'download'))
|
||||||
|
if val:
|
||||||
|
val = [Path(x).resolve() for x in (val if isinstance(val, list) else [val])] # val path
|
||||||
|
if not all(x.exists() for x in val):
|
||||||
|
print('\nWARNING: Dataset not found, nonexistent paths: %s' % [str(x) for x in val if not x.exists()])
|
||||||
|
if s and autodownload: # download script
|
||||||
|
root = path.parent if 'path' in data else '..' # unzip directory i.e. '../'
|
||||||
|
if s.startswith('http') and s.endswith('.zip'): # URL
|
||||||
|
f = Path(s).name # filename
|
||||||
|
print(f'Downloading {s} to {f}...')
|
||||||
|
torch.hub.download_url_to_file(s, f)
|
||||||
|
Path(root).mkdir(parents=True, exist_ok=True) # create root
|
||||||
|
ZipFile(f).extractall(path=root) # unzip
|
||||||
|
Path(f).unlink() # remove zip
|
||||||
|
r = None # success
|
||||||
|
elif s.startswith('bash '): # bash script
|
||||||
|
print(f'Running {s} ...')
|
||||||
|
r = os.system(s)
|
||||||
|
else: # python script
|
||||||
|
r = exec(s, {'yaml': data}) # return None
|
||||||
|
print(f"Dataset autodownload {f'success, saved to {root}' if r in (0, None) else 'failure'}\n")
|
||||||
|
else:
|
||||||
|
raise Exception('Dataset not found.')
|
||||||
|
|
||||||
|
return data # dictionary
|
||||||
|
|
||||||
|
|
||||||
|
def url2file(url):
|
||||||
|
# Convert URL to filename, i.e. https://url.com/file.txt?auth -> file.txt
|
||||||
|
url = str(Path(url)).replace(':/', '://') # Pathlib turns :// -> :/
|
||||||
|
file = Path(urllib.parse.unquote(url)).name.split('?')[0] # '%2F' to '/', split https://url.com/file.txt?auth
|
||||||
|
return file
|
||||||
|
|
||||||
|
|
||||||
|
def download(url, dir='.', unzip=True, delete=True, curl=False, threads=1):
|
||||||
|
# Multi-threaded file download and unzip function, used in data.yaml for autodownload
|
||||||
|
def download_one(url, dir):
|
||||||
|
# Download 1 file
|
||||||
|
f = dir / Path(url).name # filename
|
||||||
|
if Path(url).is_file(): # exists in current path
|
||||||
|
Path(url).rename(f) # move to dir
|
||||||
|
elif not f.exists():
|
||||||
|
print(f'Downloading {url} to {f}...')
|
||||||
|
if curl:
|
||||||
|
os.system(f"curl -L '{url}' -o '{f}' --retry 9 -C -") # curl download, retry and resume on fail
|
||||||
|
else:
|
||||||
|
torch.hub.download_url_to_file(url, f, progress=True) # torch download
|
||||||
|
if unzip and f.suffix in ('.zip', '.gz'):
|
||||||
|
print(f'Unzipping {f}...')
|
||||||
|
if f.suffix == '.zip':
|
||||||
|
ZipFile(f).extractall(path=dir) # unzip
|
||||||
|
elif f.suffix == '.gz':
|
||||||
|
os.system(f'tar xfz {f} --directory {f.parent}') # unzip
|
||||||
|
if delete:
|
||||||
|
f.unlink() # remove zip
|
||||||
|
|
||||||
|
dir = Path(dir)
|
||||||
|
dir.mkdir(parents=True, exist_ok=True) # make directory
|
||||||
|
if threads > 1:
|
||||||
|
pool = ThreadPool(threads)
|
||||||
|
pool.imap(lambda x: download_one(*x), zip(url, repeat(dir))) # multi-threaded
|
||||||
|
pool.close()
|
||||||
|
pool.join()
|
||||||
|
else:
|
||||||
|
for u in [url] if isinstance(url, (str, Path)) else url:
|
||||||
|
download_one(u, dir)
|
||||||
|
|
||||||
|
|
||||||
|
def make_divisible(x, divisor):
|
||||||
|
# Returns x evenly divisible by divisor
|
||||||
|
return math.ceil(x / divisor) * divisor
|
||||||
|
|
||||||
|
|
||||||
|
def clean_str(s):
|
||||||
|
# Cleans a string by replacing special characters with underscore _
|
||||||
|
return re.sub(pattern="[|@#!¡·$€%&()=?¿^*;:,¨´><+]", repl="_", string=s)
|
||||||
|
|
||||||
|
|
||||||
|
def one_cycle(y1=0.0, y2=1.0, steps=100):
|
||||||
|
# lambda function for sinusoidal ramp from y1 to y2 https://arxiv.org/pdf/1812.01187.pdf
|
||||||
|
return lambda x: ((1 - math.cos(x * math.pi / steps)) / 2) * (y2 - y1) + y1
|
||||||
|
|
||||||
|
|
||||||
|
def colorstr(*input):
|
||||||
|
# Colors a string https://en.wikipedia.org/wiki/ANSI_escape_code, i.e. colorstr('blue', 'hello world')
|
||||||
|
*args, string = input if len(input) > 1 else ('blue', 'bold', input[0]) # color arguments, string
|
||||||
|
colors = {'black': '\033[30m', # basic colors
|
||||||
|
'red': '\033[31m',
|
||||||
|
'green': '\033[32m',
|
||||||
|
'yellow': '\033[33m',
|
||||||
|
'blue': '\033[34m',
|
||||||
|
'magenta': '\033[35m',
|
||||||
|
'cyan': '\033[36m',
|
||||||
|
'white': '\033[37m',
|
||||||
|
'bright_black': '\033[90m', # bright colors
|
||||||
|
'bright_red': '\033[91m',
|
||||||
|
'bright_green': '\033[92m',
|
||||||
|
'bright_yellow': '\033[93m',
|
||||||
|
'bright_blue': '\033[94m',
|
||||||
|
'bright_magenta': '\033[95m',
|
||||||
|
'bright_cyan': '\033[96m',
|
||||||
|
'bright_white': '\033[97m',
|
||||||
|
'end': '\033[0m', # misc
|
||||||
|
'bold': '\033[1m',
|
||||||
|
'underline': '\033[4m'}
|
||||||
|
return ''.join(colors[x] for x in args) + f'{string}' + colors['end']
|
||||||
|
|
||||||
|
|
||||||
|
def labels_to_class_weights(labels, nc=80):
|
||||||
|
# Get class weights (inverse frequency) from training labels
|
||||||
|
if labels[0] is None: # no labels loaded
|
||||||
|
return torch.Tensor()
|
||||||
|
|
||||||
|
labels = np.concatenate(labels, 0) # labels.shape = (866643, 5) for COCO
|
||||||
|
classes = labels[:, 0].astype(np.int) # labels = [class xywh]
|
||||||
|
weights = np.bincount(classes, minlength=nc) # occurrences per class
|
||||||
|
|
||||||
|
# Prepend gridpoint count (for uCE training)
|
||||||
|
# gpi = ((320 / 32 * np.array([1, 2, 4])) ** 2 * 3).sum() # gridpoints per image
|
||||||
|
# weights = np.hstack([gpi * len(labels) - weights.sum() * 9, weights * 9]) ** 0.5 # prepend gridpoints to start
|
||||||
|
|
||||||
|
weights[weights == 0] = 1 # replace empty bins with 1
|
||||||
|
weights = 1 / weights # number of targets per class
|
||||||
|
weights /= weights.sum() # normalize
|
||||||
|
return torch.from_numpy(weights)
|
||||||
|
|
||||||
|
|
||||||
|
def labels_to_image_weights(labels, nc=80, class_weights=np.ones(80)):
|
||||||
|
# Produces image weights based on class_weights and image contents
|
||||||
|
class_counts = np.array([np.bincount(x[:, 0].astype(np.int), minlength=nc) for x in labels])
|
||||||
|
image_weights = (class_weights.reshape(1, nc) * class_counts).sum(1)
|
||||||
|
# index = random.choices(range(n), weights=image_weights, k=1) # weight image sample
|
||||||
|
return image_weights
|
||||||
|
|
||||||
|
|
||||||
|
def coco80_to_coco91_class(): # converts 80-index (val2014) to 91-index (paper)
|
||||||
|
# https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/
|
||||||
|
# a = np.loadtxt('data/coco.names', dtype='str', delimiter='\n')
|
||||||
|
# b = np.loadtxt('data/coco_paper.names', dtype='str', delimiter='\n')
|
||||||
|
# x1 = [list(a[i] == b).index(True) + 1 for i in range(80)] # darknet to coco
|
||||||
|
# x2 = [list(b[i] == a).index(True) if any(b[i] == a) else None for i in range(91)] # coco to darknet
|
||||||
|
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27, 28, 31, 32, 33, 34,
|
||||||
|
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
|
||||||
|
64, 65, 67, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90]
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def xyxy2xywh(x):
|
||||||
|
# Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] where xy1=top-left, xy2=bottom-right
|
||||||
|
y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
|
||||||
|
y[:, 0] = (x[:, 0] + x[:, 2]) / 2 # x center
|
||||||
|
y[:, 1] = (x[:, 1] + x[:, 3]) / 2 # y center
|
||||||
|
y[:, 2] = x[:, 2] - x[:, 0] # width
|
||||||
|
y[:, 3] = x[:, 3] - x[:, 1] # height
|
||||||
|
return y
|
||||||
|
|
||||||
|
|
||||||
|
def xywh2xyxy(x):
|
||||||
|
# Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
|
||||||
|
y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
|
||||||
|
y[:, 0] = x[:, 0] - x[:, 2] / 2 # top left x
|
||||||
|
y[:, 1] = x[:, 1] - x[:, 3] / 2 # top left y
|
||||||
|
y[:, 2] = x[:, 0] + x[:, 2] / 2 # bottom right x
|
||||||
|
y[:, 3] = x[:, 1] + x[:, 3] / 2 # bottom right y
|
||||||
|
return y
|
||||||
|
|
||||||
|
|
||||||
|
def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0):
|
||||||
|
# Convert nx4 boxes from [x, y, w, h] normalized to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
|
||||||
|
y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
|
||||||
|
y[:, 0] = w * (x[:, 0] - x[:, 2] / 2) + padw # top left x
|
||||||
|
y[:, 1] = h * (x[:, 1] - x[:, 3] / 2) + padh # top left y
|
||||||
|
y[:, 2] = w * (x[:, 0] + x[:, 2] / 2) + padw # bottom right x
|
||||||
|
y[:, 3] = h * (x[:, 1] + x[:, 3] / 2) + padh # bottom right y
|
||||||
|
return y
|
||||||
|
|
||||||
|
|
||||||
|
def xyxy2xywhn(x, w=640, h=640, clip=False, eps=0.0):
|
||||||
|
# Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] normalized where xy1=top-left, xy2=bottom-right
|
||||||
|
if clip:
|
||||||
|
clip_coords(x, (h - eps, w - eps)) # warning: inplace clip
|
||||||
|
y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
|
||||||
|
y[:, 0] = ((x[:, 0] + x[:, 2]) / 2) / w # x center
|
||||||
|
y[:, 1] = ((x[:, 1] + x[:, 3]) / 2) / h # y center
|
||||||
|
y[:, 2] = (x[:, 2] - x[:, 0]) / w # width
|
||||||
|
y[:, 3] = (x[:, 3] - x[:, 1]) / h # height
|
||||||
|
return y
|
||||||
|
|
||||||
|
|
||||||
|
def xyn2xy(x, w=640, h=640, padw=0, padh=0):
|
||||||
|
# Convert normalized segments into pixel segments, shape (n,2)
|
||||||
|
y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
|
||||||
|
y[:, 0] = w * x[:, 0] + padw # top left x
|
||||||
|
y[:, 1] = h * x[:, 1] + padh # top left y
|
||||||
|
return y
|
||||||
|
|
||||||
|
|
||||||
|
def segment2box(segment, width=640, height=640):
|
||||||
|
# Convert 1 segment label to 1 box label, applying inside-image constraint, i.e. (xy1, xy2, ...) to (xyxy)
|
||||||
|
x, y = segment.T # segment xy
|
||||||
|
inside = (x >= 0) & (y >= 0) & (x <= width) & (y <= height)
|
||||||
|
x, y, = x[inside], y[inside]
|
||||||
|
return np.array([x.min(), y.min(), x.max(), y.max()]) if any(x) else np.zeros((1, 4)) # xyxy
|
||||||
|
|
||||||
|
|
||||||
|
def segments2boxes(segments):
|
||||||
|
# Convert segment labels to box labels, i.e. (cls, xy1, xy2, ...) to (cls, xywh)
|
||||||
|
boxes = []
|
||||||
|
for s in segments:
|
||||||
|
x, y = s.T # segment xy
|
||||||
|
boxes.append([x.min(), y.min(), x.max(), y.max()]) # cls, xyxy
|
||||||
|
return xyxy2xywh(np.array(boxes)) # cls, xywh
|
||||||
|
|
||||||
|
|
||||||
|
def resample_segments(segments, n=1000):
|
||||||
|
# Up-sample an (n,2) segment
|
||||||
|
for i, s in enumerate(segments):
|
||||||
|
x = np.linspace(0, len(s) - 1, n)
|
||||||
|
xp = np.arange(len(s))
|
||||||
|
segments[i] = np.concatenate([np.interp(x, xp, s[:, i]) for i in range(2)]).reshape(2, -1).T # segment xy
|
||||||
|
return segments
|
||||||
|
|
||||||
|
|
||||||
|
def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):
|
||||||
|
# Rescale coords (xyxy) from img1_shape to img0_shape
|
||||||
|
if ratio_pad is None: # calculate from img0_shape
|
||||||
|
gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1]) # gain = old / new
|
||||||
|
pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding
|
||||||
|
else:
|
||||||
|
gain = ratio_pad[0][0]
|
||||||
|
pad = ratio_pad[1]
|
||||||
|
|
||||||
|
coords[:, [0, 2]] -= pad[0] # x padding
|
||||||
|
coords[:, [1, 3]] -= pad[1] # y padding
|
||||||
|
coords[:, :4] /= gain
|
||||||
|
clip_coords(coords, img0_shape)
|
||||||
|
return coords
|
||||||
|
|
||||||
|
|
||||||
|
def clip_coords(boxes, shape):
|
||||||
|
# Clip bounding xyxy bounding boxes to image shape (height, width)
|
||||||
|
if isinstance(boxes, torch.Tensor): # faster individually
|
||||||
|
boxes[:, 0].clamp_(0, shape[1]) # x1
|
||||||
|
boxes[:, 1].clamp_(0, shape[0]) # y1
|
||||||
|
boxes[:, 2].clamp_(0, shape[1]) # x2
|
||||||
|
boxes[:, 3].clamp_(0, shape[0]) # y2
|
||||||
|
else: # np.array (faster grouped)
|
||||||
|
boxes[:, [0, 2]] = boxes[:, [0, 2]].clip(0, shape[1]) # x1, x2
|
||||||
|
boxes[:, [1, 3]] = boxes[:, [1, 3]].clip(0, shape[0]) # y1, y2
|
||||||
|
|
||||||
|
|
||||||
|
def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False,
|
||||||
|
labels=(), max_det=300):
|
||||||
|
"""Runs Non-Maximum Suppression (NMS) on inference results
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list of detections, on (n,6) tensor per image [xyxy, conf, cls]
|
||||||
|
"""
|
||||||
|
|
||||||
|
nc = prediction.shape[2] - 5 # number of classes
|
||||||
|
xc = prediction[..., 4] > conf_thres # candidates
|
||||||
|
|
||||||
|
# Checks
|
||||||
|
assert 0 <= conf_thres <= 1, f'Invalid Confidence threshold {conf_thres}, valid values are between 0.0 and 1.0'
|
||||||
|
assert 0 <= iou_thres <= 1, f'Invalid IoU {iou_thres}, valid values are between 0.0 and 1.0'
|
||||||
|
|
||||||
|
# Settings
|
||||||
|
min_wh, max_wh = 2, 4096 # (pixels) minimum and maximum box width and height
|
||||||
|
max_nms = 30000 # maximum number of boxes into torchvision.ops.nms()
|
||||||
|
time_limit = 10.0 # seconds to quit after
|
||||||
|
redundant = True # require redundant detections
|
||||||
|
multi_label &= nc > 1 # multiple labels per box (adds 0.5ms/img)
|
||||||
|
merge = False # use merge-NMS
|
||||||
|
|
||||||
|
t = time.time()
|
||||||
|
output = [torch.zeros((0, 6), device=prediction.device)] * prediction.shape[0]
|
||||||
|
for xi, x in enumerate(prediction): # image index, image inference
|
||||||
|
# Apply constraints
|
||||||
|
# x[((x[..., 2:4] < min_wh) | (x[..., 2:4] > max_wh)).any(1), 4] = 0 # width-height
|
||||||
|
x = x[xc[xi]] # confidence
|
||||||
|
|
||||||
|
# Cat apriori labels if autolabelling
|
||||||
|
if labels and len(labels[xi]):
|
||||||
|
l = labels[xi]
|
||||||
|
v = torch.zeros((len(l), nc + 5), device=x.device)
|
||||||
|
v[:, :4] = l[:, 1:5] # box
|
||||||
|
v[:, 4] = 1.0 # conf
|
||||||
|
v[range(len(l)), l[:, 0].long() + 5] = 1.0 # cls
|
||||||
|
x = torch.cat((x, v), 0)
|
||||||
|
|
||||||
|
# If none remain process next image
|
||||||
|
if not x.shape[0]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Compute conf
|
||||||
|
x[:, 5:] *= x[:, 4:5] # conf = obj_conf * cls_conf
|
||||||
|
|
||||||
|
# Box (center x, center y, width, height) to (x1, y1, x2, y2)
|
||||||
|
box = xywh2xyxy(x[:, :4])
|
||||||
|
|
||||||
|
# Detections matrix nx6 (xyxy, conf, cls)
|
||||||
|
if multi_label:
|
||||||
|
i, j = (x[:, 5:] > conf_thres).nonzero(as_tuple=False).T
|
||||||
|
x = torch.cat((box[i], x[i, j + 5, None], j[:, None].float()), 1)
|
||||||
|
else: # best class only
|
||||||
|
conf, j = x[:, 5:].max(1, keepdim=True)
|
||||||
|
x = torch.cat((box, conf, j.float()), 1)[conf.view(-1) > conf_thres]
|
||||||
|
|
||||||
|
# Filter by class
|
||||||
|
if classes is not None:
|
||||||
|
x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]
|
||||||
|
|
||||||
|
# Apply finite constraint
|
||||||
|
# if not torch.isfinite(x).all():
|
||||||
|
# x = x[torch.isfinite(x).all(1)]
|
||||||
|
|
||||||
|
# Check shape
|
||||||
|
n = x.shape[0] # number of boxes
|
||||||
|
if not n: # no boxes
|
||||||
|
continue
|
||||||
|
elif n > max_nms: # excess boxes
|
||||||
|
x = x[x[:, 4].argsort(descending=True)[:max_nms]] # sort by confidence
|
||||||
|
|
||||||
|
# Batched NMS
|
||||||
|
c = x[:, 5:6] * (0 if agnostic else max_wh) # classes
|
||||||
|
boxes, scores = x[:, :4] + c, x[:, 4] # boxes (offset by class), scores
|
||||||
|
i = torchvision.ops.nms(boxes, scores, iou_thres) # NMS
|
||||||
|
if i.shape[0] > max_det: # limit detections
|
||||||
|
i = i[:max_det]
|
||||||
|
if merge and (1 < n < 3E3): # Merge NMS (boxes merged using weighted mean)
|
||||||
|
# update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)
|
||||||
|
iou = box_iou(boxes[i], boxes) > iou_thres # iou matrix
|
||||||
|
weights = iou * scores[None] # box weights
|
||||||
|
x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True) # merged boxes
|
||||||
|
if redundant:
|
||||||
|
i = i[iou.sum(1) > 1] # require redundancy
|
||||||
|
|
||||||
|
output[xi] = x[i]
|
||||||
|
if (time.time() - t) > time_limit:
|
||||||
|
print(f'WARNING: NMS time limit {time_limit}s exceeded')
|
||||||
|
break # time limit exceeded
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def strip_optimizer(f='best.pt', s=''): # from utils.general import *; strip_optimizer()
|
||||||
|
# Strip optimizer from 'f' to finalize training, optionally save as 's'
|
||||||
|
x = torch.load(f, map_location=torch.device('cpu'))
|
||||||
|
if x.get('ema'):
|
||||||
|
x['model'] = x['ema'] # replace model with ema
|
||||||
|
for k in 'optimizer', 'training_results', 'wandb_id', 'ema', 'updates': # keys
|
||||||
|
x[k] = None
|
||||||
|
x['epoch'] = -1
|
||||||
|
x['model'].half() # to FP16
|
||||||
|
for p in x['model'].parameters():
|
||||||
|
p.requires_grad = False
|
||||||
|
torch.save(x, s or f)
|
||||||
|
mb = os.path.getsize(s or f) / 1E6 # filesize
|
||||||
|
print(f"Optimizer stripped from {f},{(' saved as %s,' % s) if s else ''} {mb:.1f}MB")
|
||||||
|
|
||||||
|
|
||||||
|
def print_mutation(results, hyp, save_dir, bucket):
|
||||||
|
evolve_csv, results_csv, evolve_yaml = save_dir / 'evolve.csv', save_dir / 'results.csv', save_dir / 'hyp_evolve.yaml'
|
||||||
|
keys = ('metrics/precision', 'metrics/recall', 'metrics/mAP_0.5', 'metrics/mAP_0.5:0.95',
|
||||||
|
'val/box_loss', 'val/obj_loss', 'val/cls_loss') + tuple(hyp.keys()) # [results + hyps]
|
||||||
|
keys = tuple(x.strip() for x in keys)
|
||||||
|
vals = results + tuple(hyp.values())
|
||||||
|
n = len(keys)
|
||||||
|
|
||||||
|
# Download (optional)
|
||||||
|
if bucket:
|
||||||
|
url = f'gs://{bucket}/evolve.csv'
|
||||||
|
if gsutil_getsize(url) > (os.path.getsize(evolve_csv) if os.path.exists(evolve_csv) else 0):
|
||||||
|
os.system(f'gsutil cp {url} {save_dir}') # download evolve.csv if larger than local
|
||||||
|
|
||||||
|
# Log to evolve.csv
|
||||||
|
s = '' if evolve_csv.exists() else (('%20s,' * n % keys).rstrip(',') + '\n') # add header
|
||||||
|
with open(evolve_csv, 'a') as f:
|
||||||
|
f.write(s + ('%20.5g,' * n % vals).rstrip(',') + '\n')
|
||||||
|
|
||||||
|
# Print to screen
|
||||||
|
print(colorstr('evolve: ') + ', '.join(f'{x.strip():>20s}' for x in keys))
|
||||||
|
print(colorstr('evolve: ') + ', '.join(f'{x:20.5g}' for x in vals), end='\n\n\n')
|
||||||
|
|
||||||
|
# Save yaml
|
||||||
|
with open(evolve_yaml, 'w') as f:
|
||||||
|
data = pd.read_csv(evolve_csv)
|
||||||
|
data = data.rename(columns=lambda x: x.strip()) # strip keys
|
||||||
|
i = np.argmax(fitness(data.values[:, :7])) #
|
||||||
|
f.write('# YOLOv3 Hyperparameter Evolution Results\n' +
|
||||||
|
f'# Best generation: {i}\n' +
|
||||||
|
f'# Last generation: {len(data)}\n' +
|
||||||
|
'# ' + ', '.join(f'{x.strip():>20s}' for x in keys[:7]) + '\n' +
|
||||||
|
'# ' + ', '.join(f'{x:>20.5g}' for x in data.values[i, :7]) + '\n\n')
|
||||||
|
yaml.safe_dump(hyp, f, sort_keys=False)
|
||||||
|
|
||||||
|
if bucket:
|
||||||
|
os.system(f'gsutil cp {evolve_csv} {evolve_yaml} gs://{bucket}') # upload
|
||||||
|
|
||||||
|
|
||||||
|
def apply_classifier(x, model, img, im0):
|
||||||
|
# Apply a second stage classifier to YOLO outputs
|
||||||
|
# Example model = torchvision.models.__dict__['efficientnet_b0'](pretrained=True).to(device).eval()
|
||||||
|
im0 = [im0] if isinstance(im0, np.ndarray) else im0
|
||||||
|
for i, d in enumerate(x): # per image
|
||||||
|
if d is not None and len(d):
|
||||||
|
d = d.clone()
|
||||||
|
|
||||||
|
# Reshape and pad cutouts
|
||||||
|
b = xyxy2xywh(d[:, :4]) # boxes
|
||||||
|
b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1) # rectangle to square
|
||||||
|
b[:, 2:] = b[:, 2:] * 1.3 + 30 # pad
|
||||||
|
d[:, :4] = xywh2xyxy(b).long()
|
||||||
|
|
||||||
|
# Rescale boxes from img_size to im0 size
|
||||||
|
scale_coords(img.shape[2:], d[:, :4], im0[i].shape)
|
||||||
|
|
||||||
|
# Classes
|
||||||
|
pred_cls1 = d[:, 5].long()
|
||||||
|
ims = []
|
||||||
|
for j, a in enumerate(d): # per item
|
||||||
|
cutout = im0[i][int(a[1]):int(a[3]), int(a[0]):int(a[2])]
|
||||||
|
im = cv2.resize(cutout, (224, 224)) # BGR
|
||||||
|
# cv2.imwrite('example%i.jpg' % j, cutout)
|
||||||
|
|
||||||
|
im = im[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, to 3x416x416
|
||||||
|
im = np.ascontiguousarray(im, dtype=np.float32) # uint8 to float32
|
||||||
|
im /= 255 # 0 - 255 to 0.0 - 1.0
|
||||||
|
ims.append(im)
|
||||||
|
|
||||||
|
pred_cls2 = model(torch.Tensor(ims).to(d.device)).argmax(1) # classifier prediction
|
||||||
|
x[i] = x[i][pred_cls1 == pred_cls2] # retain matching class detections
|
||||||
|
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def increment_path(path, exist_ok=False, sep='', mkdir=False):
|
||||||
|
# Increment file or directory path, i.e. runs/exp --> runs/exp{sep}2, runs/exp{sep}3, ... etc.
|
||||||
|
path = Path(path) # os-agnostic
|
||||||
|
if path.exists() and not exist_ok:
|
||||||
|
path, suffix = (path.with_suffix(''), path.suffix) if path.is_file() else (path, '')
|
||||||
|
dirs = glob.glob(f"{path}{sep}*") # similar paths
|
||||||
|
matches = [re.search(rf"%s{sep}(\d+)" % path.stem, d) for d in dirs]
|
||||||
|
i = [int(m.groups()[0]) for m in matches if m] # indices
|
||||||
|
n = max(i) + 1 if i else 2 # increment number
|
||||||
|
path = Path(f"{path}{sep}{n}{suffix}") # increment path
|
||||||
|
if mkdir:
|
||||||
|
path.mkdir(parents=True, exist_ok=True) # make directory
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
NCOLS = 0 if is_docker() else shutil.get_terminal_size().columns # terminal window size
|
||||||
25
ros2_ws/src/yolov3_ros/utils/google_app_engine/Dockerfile
Normal file
25
ros2_ws/src/yolov3_ros/utils/google_app_engine/Dockerfile
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
FROM gcr.io/google-appengine/python
|
||||||
|
|
||||||
|
# Create a virtualenv for dependencies. This isolates these packages from
|
||||||
|
# system-level packages.
|
||||||
|
# Use -p python3 or -p python3.7 to select python version. Default is version 2.
|
||||||
|
RUN virtualenv /env -p python3
|
||||||
|
|
||||||
|
# Setting these environment variables are the same as running
|
||||||
|
# source /env/bin/activate.
|
||||||
|
ENV VIRTUAL_ENV /env
|
||||||
|
ENV PATH /env/bin:$PATH
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y python-opencv
|
||||||
|
|
||||||
|
# Copy the application's requirements.txt and run pip to install all
|
||||||
|
# dependencies into the virtualenv.
|
||||||
|
ADD requirements.txt /app/requirements.txt
|
||||||
|
RUN pip install -r /app/requirements.txt
|
||||||
|
|
||||||
|
# Add the application source code.
|
||||||
|
ADD . /app
|
||||||
|
|
||||||
|
# Run a WSGI server to serve the application. gunicorn must be declared as
|
||||||
|
# a dependency in requirements.txt.
|
||||||
|
CMD gunicorn -b :$PORT main:app
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
# add these requirements in your app on top of the existing ones
|
||||||
|
pip==21.1
|
||||||
|
Flask==1.0.2
|
||||||
|
gunicorn==19.10.0
|
||||||
14
ros2_ws/src/yolov3_ros/utils/google_app_engine/app.yaml
Normal file
14
ros2_ws/src/yolov3_ros/utils/google_app_engine/app.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
runtime: custom
|
||||||
|
env: flex
|
||||||
|
|
||||||
|
service: yolov5app
|
||||||
|
|
||||||
|
liveness_check:
|
||||||
|
initial_delay_sec: 600
|
||||||
|
|
||||||
|
manual_scaling:
|
||||||
|
instances: 1
|
||||||
|
resources:
|
||||||
|
cpu: 1
|
||||||
|
memory_gb: 4
|
||||||
|
disk_size_gb: 20
|
||||||
159
ros2_ws/src/yolov3_ros/utils/loggers/__init__.py
Normal file
159
ros2_ws/src/yolov3_ros/utils/loggers/__init__.py
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Logging utils
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
import pkg_resources as pkg
|
||||||
|
import torch
|
||||||
|
from torch.utils.tensorboard import SummaryWriter
|
||||||
|
|
||||||
|
from utils.general import colorstr, emojis
|
||||||
|
from utils.loggers.wandb.wandb_utils import WandbLogger
|
||||||
|
from utils.plots import plot_images, plot_results
|
||||||
|
from utils.torch_utils import de_parallel
|
||||||
|
|
||||||
|
LOGGERS = ('csv', 'tb', 'wandb') # text-file, TensorBoard, Weights & Biases
|
||||||
|
RANK = int(os.getenv('RANK', -1))
|
||||||
|
|
||||||
|
try:
|
||||||
|
import wandb
|
||||||
|
|
||||||
|
assert hasattr(wandb, '__version__') # verify package import not local dir
|
||||||
|
if pkg.parse_version(wandb.__version__) >= pkg.parse_version('0.12.2') and RANK in [0, -1]:
|
||||||
|
try:
|
||||||
|
wandb_login_success = wandb.login(timeout=30)
|
||||||
|
except wandb.errors.UsageError: # known non-TTY terminal issue
|
||||||
|
wandb_login_success = False
|
||||||
|
if not wandb_login_success:
|
||||||
|
wandb = None
|
||||||
|
except (ImportError, AssertionError):
|
||||||
|
wandb = None
|
||||||
|
|
||||||
|
|
||||||
|
class Loggers():
|
||||||
|
# Loggers class
|
||||||
|
def __init__(self, save_dir=None, weights=None, opt=None, hyp=None, logger=None, include=LOGGERS):
|
||||||
|
self.save_dir = save_dir
|
||||||
|
self.weights = weights
|
||||||
|
self.opt = opt
|
||||||
|
self.hyp = hyp
|
||||||
|
self.logger = logger # for printing results to console
|
||||||
|
self.include = include
|
||||||
|
self.keys = ['train/box_loss', 'train/obj_loss', 'train/cls_loss', # train loss
|
||||||
|
'metrics/precision', 'metrics/recall', 'metrics/mAP_0.5', 'metrics/mAP_0.5:0.95', # metrics
|
||||||
|
'val/box_loss', 'val/obj_loss', 'val/cls_loss', # val loss
|
||||||
|
'x/lr0', 'x/lr1', 'x/lr2'] # params
|
||||||
|
for k in LOGGERS:
|
||||||
|
setattr(self, k, None) # init empty logger dictionary
|
||||||
|
self.csv = True # always log to csv
|
||||||
|
|
||||||
|
# Message
|
||||||
|
if not wandb:
|
||||||
|
prefix = colorstr('Weights & Biases: ')
|
||||||
|
s = f"{prefix}run 'pip install wandb' to automatically track and visualize YOLOv3 🚀 runs (RECOMMENDED)"
|
||||||
|
print(emojis(s))
|
||||||
|
|
||||||
|
# TensorBoard
|
||||||
|
s = self.save_dir
|
||||||
|
if 'tb' in self.include and not self.opt.evolve:
|
||||||
|
prefix = colorstr('TensorBoard: ')
|
||||||
|
self.logger.info(f"{prefix}Start with 'tensorboard --logdir {s.parent}', view at http://localhost:6006/")
|
||||||
|
self.tb = SummaryWriter(str(s))
|
||||||
|
|
||||||
|
# W&B
|
||||||
|
if wandb and 'wandb' in self.include:
|
||||||
|
wandb_artifact_resume = isinstance(self.opt.resume, str) and self.opt.resume.startswith('wandb-artifact://')
|
||||||
|
run_id = torch.load(self.weights).get('wandb_id') if self.opt.resume and not wandb_artifact_resume else None
|
||||||
|
self.opt.hyp = self.hyp # add hyperparameters
|
||||||
|
self.wandb = WandbLogger(self.opt, run_id)
|
||||||
|
else:
|
||||||
|
self.wandb = None
|
||||||
|
|
||||||
|
def on_pretrain_routine_end(self):
|
||||||
|
# Callback runs on pre-train routine end
|
||||||
|
paths = self.save_dir.glob('*labels*.jpg') # training labels
|
||||||
|
if self.wandb:
|
||||||
|
self.wandb.log({"Labels": [wandb.Image(str(x), caption=x.name) for x in paths]})
|
||||||
|
|
||||||
|
def on_train_batch_end(self, ni, model, imgs, targets, paths, plots, sync_bn):
|
||||||
|
# Callback runs on train batch end
|
||||||
|
if plots:
|
||||||
|
if ni == 0:
|
||||||
|
if not sync_bn: # tb.add_graph() --sync known issue https://github.com/ultralytics/yolov5/issues/3754
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore') # suppress jit trace warning
|
||||||
|
self.tb.add_graph(torch.jit.trace(de_parallel(model), imgs[0:1], strict=False), [])
|
||||||
|
if ni < 3:
|
||||||
|
f = self.save_dir / f'train_batch{ni}.jpg' # filename
|
||||||
|
Thread(target=plot_images, args=(imgs, targets, paths, f), daemon=True).start()
|
||||||
|
if self.wandb and ni == 10:
|
||||||
|
files = sorted(self.save_dir.glob('train*.jpg'))
|
||||||
|
self.wandb.log({'Mosaics': [wandb.Image(str(f), caption=f.name) for f in files if f.exists()]})
|
||||||
|
|
||||||
|
def on_train_epoch_end(self, epoch):
|
||||||
|
# Callback runs on train epoch end
|
||||||
|
if self.wandb:
|
||||||
|
self.wandb.current_epoch = epoch + 1
|
||||||
|
|
||||||
|
def on_val_image_end(self, pred, predn, path, names, im):
|
||||||
|
# Callback runs on val image end
|
||||||
|
if self.wandb:
|
||||||
|
self.wandb.val_one_image(pred, predn, path, names, im)
|
||||||
|
|
||||||
|
def on_val_end(self):
|
||||||
|
# Callback runs on val end
|
||||||
|
if self.wandb:
|
||||||
|
files = sorted(self.save_dir.glob('val*.jpg'))
|
||||||
|
self.wandb.log({"Validation": [wandb.Image(str(f), caption=f.name) for f in files]})
|
||||||
|
|
||||||
|
def on_fit_epoch_end(self, vals, epoch, best_fitness, fi):
|
||||||
|
# Callback runs at the end of each fit (train+val) epoch
|
||||||
|
x = {k: v for k, v in zip(self.keys, vals)} # dict
|
||||||
|
if self.csv:
|
||||||
|
file = self.save_dir / 'results.csv'
|
||||||
|
n = len(x) + 1 # number of cols
|
||||||
|
s = '' if file.exists() else (('%20s,' * n % tuple(['epoch'] + self.keys)).rstrip(',') + '\n') # add header
|
||||||
|
with open(file, 'a') as f:
|
||||||
|
f.write(s + ('%20.5g,' * n % tuple([epoch] + vals)).rstrip(',') + '\n')
|
||||||
|
|
||||||
|
if self.tb:
|
||||||
|
for k, v in x.items():
|
||||||
|
self.tb.add_scalar(k, v, epoch)
|
||||||
|
|
||||||
|
if self.wandb:
|
||||||
|
self.wandb.log(x)
|
||||||
|
self.wandb.end_epoch(best_result=best_fitness == fi)
|
||||||
|
|
||||||
|
def on_model_save(self, last, epoch, final_epoch, best_fitness, fi):
|
||||||
|
# Callback runs on model save event
|
||||||
|
if self.wandb:
|
||||||
|
if ((epoch + 1) % self.opt.save_period == 0 and not final_epoch) and self.opt.save_period != -1:
|
||||||
|
self.wandb.log_model(last.parent, self.opt, epoch, fi, best_model=best_fitness == fi)
|
||||||
|
|
||||||
|
def on_train_end(self, last, best, plots, epoch, results):
|
||||||
|
# Callback runs on training end
|
||||||
|
if plots:
|
||||||
|
plot_results(file=self.save_dir / 'results.csv') # save results.png
|
||||||
|
files = ['results.png', 'confusion_matrix.png', *(f'{x}_curve.png' for x in ('F1', 'PR', 'P', 'R'))]
|
||||||
|
files = [(self.save_dir / f) for f in files if (self.save_dir / f).exists()] # filter
|
||||||
|
|
||||||
|
if self.tb:
|
||||||
|
import cv2
|
||||||
|
for f in files:
|
||||||
|
self.tb.add_image(f.stem, cv2.imread(str(f))[..., ::-1], epoch, dataformats='HWC')
|
||||||
|
|
||||||
|
if self.wandb:
|
||||||
|
self.wandb.log({"Results": [wandb.Image(str(f), caption=f.name) for f in files]})
|
||||||
|
# Calling wandb.log. TODO: Refactor this into WandbLogger.log_model
|
||||||
|
if not self.opt.evolve:
|
||||||
|
wandb.log_artifact(str(best if best.exists() else last), type='model',
|
||||||
|
name='run_' + self.wandb.wandb_run.id + '_model',
|
||||||
|
aliases=['latest', 'best', 'stripped'])
|
||||||
|
self.wandb.finish_run()
|
||||||
|
else:
|
||||||
|
self.wandb.finish_run()
|
||||||
|
self.wandb = WandbLogger(self.opt)
|
||||||
271
ros2_ws/src/yolov3_ros/utils/loggers/clearml/README.md
Normal file
271
ros2_ws/src/yolov3_ros/utils/loggers/clearml/README.md
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
# ClearML Integration
|
||||||
|
|
||||||
|
<img align="center" src="https://github.com/thepycoder/clearml_screenshots/raw/main/logos_dark.png#gh-light-mode-only" alt="Clear|ML"><img align="center" src="https://github.com/thepycoder/clearml_screenshots/raw/main/logos_light.png#gh-dark-mode-only" alt="Clear|ML">
|
||||||
|
|
||||||
|
## About ClearML
|
||||||
|
|
||||||
|
[ClearML](https://cutt.ly/yolov5-tutorial-clearml) is an [open-source](https://github.com/allegroai/clearml) toolbox
|
||||||
|
designed to save you time ⏱️.
|
||||||
|
|
||||||
|
🔨 Track every YOLOv5 training run in the <b>experiment manager</b>
|
||||||
|
|
||||||
|
🔧 Version and easily access your custom training data with the integrated ClearML <b>Data Versioning Tool</b>
|
||||||
|
|
||||||
|
🔦 <b>Remotely train and monitor</b> your YOLOv5 training runs using ClearML Agent
|
||||||
|
|
||||||
|
🔬 Get the very best mAP using ClearML <b>Hyperparameter Optimization</b>
|
||||||
|
|
||||||
|
🔭 Turn your newly trained <b>YOLOv5 model into an API</b> with just a few commands using ClearML Serving
|
||||||
|
|
||||||
|
<br />
|
||||||
|
And so much more. It's up to you how many of these tools you want to use, you can stick to the experiment manager, or chain them all together into an impressive pipeline!
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
## 🦾 Setting Things Up
|
||||||
|
|
||||||
|
To keep track of your experiments and/or data, ClearML needs to communicate to a server. You have 2 options to get one:
|
||||||
|
|
||||||
|
Either sign up for free to the [ClearML Hosted Service](https://cutt.ly/yolov5-tutorial-clearml) or you can set up your
|
||||||
|
own server, see [here](https://clear.ml/docs/latest/docs/deploying_clearml/clearml_server). Even the server is
|
||||||
|
open-source, so even if you're dealing with sensitive data, you should be good to go!
|
||||||
|
|
||||||
|
1. Install the `clearml` python package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install clearml
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Connect the ClearML SDK to the server
|
||||||
|
by [creating credentials](https://app.clear.ml/settings/workspace-configuration) (go right top to Settings ->
|
||||||
|
Workspace -> Create new credentials), then execute the command below and follow the instructions:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clearml-init
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! You're done 😎
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
## 🚀 Training YOLOv5 With ClearML
|
||||||
|
|
||||||
|
To enable ClearML experiment tracking, simply install the ClearML pip package.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install clearml>=1.2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
This will enable integration with the YOLOv5 training script. Every training run from now on, will be captured and
|
||||||
|
stored by the ClearML experiment manager.
|
||||||
|
|
||||||
|
If you want to change the `project_name` or `task_name`, use the `--project` and `--name` arguments of the `train.py`
|
||||||
|
script, by default the project will be called `YOLOv5` and the task `Training`.
|
||||||
|
PLEASE NOTE: ClearML uses `/` as a delimiter for subprojects, so be careful when using `/` in your project name!
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python train.py --img 640 --batch 16 --epochs 3 --data coco128.yaml --weights yolov5s.pt --cache
|
||||||
|
```
|
||||||
|
|
||||||
|
or with custom project and task name:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python train.py --project my_project --name my_training --img 640 --batch 16 --epochs 3 --data coco128.yaml --weights yolov5s.pt --cache
|
||||||
|
```
|
||||||
|
|
||||||
|
This will capture:
|
||||||
|
|
||||||
|
- Source code + uncommitted changes
|
||||||
|
- Installed packages
|
||||||
|
- (Hyper)parameters
|
||||||
|
- Model files (use `--save-period n` to save a checkpoint every n epochs)
|
||||||
|
- Console output
|
||||||
|
- Scalars (mAP_0.5, mAP_0.5:0.95, precision, recall, losses, learning rates, ...)
|
||||||
|
- General info such as machine details, runtime, creation date etc.
|
||||||
|
- All produced plots such as label correlogram and confusion matrix
|
||||||
|
- Images with bounding boxes per epoch
|
||||||
|
- Mosaic per epoch
|
||||||
|
- Validation images per epoch
|
||||||
|
- ...
|
||||||
|
|
||||||
|
That's a lot right? 🤯
|
||||||
|
Now, we can visualize all of this information in the ClearML UI to get an overview of our training progress. Add custom
|
||||||
|
columns to the table view (such as e.g. mAP_0.5) so you can easily sort on the best performing model. Or select multiple
|
||||||
|
experiments and directly compare them!
|
||||||
|
|
||||||
|
There even more we can do with all of this information, like hyperparameter optimization and remote execution, so keep
|
||||||
|
reading if you want to see how that works!
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
## 🔗 Dataset Version Management
|
||||||
|
|
||||||
|
Versioning your data separately from your code is generally a good idea and makes it easy to acquire the latest version
|
||||||
|
too. This repository supports supplying a dataset version ID, and it will make sure to get the data if it's not there
|
||||||
|
yet. Next to that, this workflow also saves the used dataset ID as part of the task parameters, so you will always know
|
||||||
|
for sure which data was used in which experiment!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Prepare Your Dataset
|
||||||
|
|
||||||
|
The YOLOv5 repository supports a number of different datasets by using yaml files containing their information. By
|
||||||
|
default datasets are downloaded to the `../datasets` folder in relation to the repository root folder. So if you
|
||||||
|
downloaded the `coco128` dataset using the link in the yaml or with the scripts provided by yolov5, you get this folder
|
||||||
|
structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
..
|
||||||
|
|_ yolov5
|
||||||
|
|_ datasets
|
||||||
|
|_ coco128
|
||||||
|
|_ images
|
||||||
|
|_ labels
|
||||||
|
|_ LICENSE
|
||||||
|
|_ README.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
But this can be any dataset you wish. Feel free to use your own, as long as you keep to this folder structure.
|
||||||
|
|
||||||
|
Next, ⚠️**copy the corresponding yaml file to the root of the dataset folder**⚠️. This yaml files contains the
|
||||||
|
information ClearML will need to properly use the dataset. You can make this yourself too, of course, just follow the
|
||||||
|
structure of the example yamls.
|
||||||
|
|
||||||
|
Basically we need the following keys: `path`, `train`, `test`, `val`, `nc`, `names`.
|
||||||
|
|
||||||
|
```
|
||||||
|
..
|
||||||
|
|_ yolov5
|
||||||
|
|_ datasets
|
||||||
|
|_ coco128
|
||||||
|
|_ images
|
||||||
|
|_ labels
|
||||||
|
|_ coco128.yaml # <---- HERE!
|
||||||
|
|_ LICENSE
|
||||||
|
|_ README.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Upload Your Dataset
|
||||||
|
|
||||||
|
To get this dataset into ClearML as a versioned dataset, go to the dataset root folder and run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd coco128
|
||||||
|
clearml-data sync --project YOLOv5 --name coco128 --folder .
|
||||||
|
```
|
||||||
|
|
||||||
|
The command `clearml-data sync` is actually a shorthand command. You could also run these commands one after the other:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Optionally add --parent <parent_dataset_id> if you want to base
|
||||||
|
# this version on another dataset version, so no duplicate files are uploaded!
|
||||||
|
clearml-data create --name coco128 --project YOLOv5
|
||||||
|
clearml-data add --files .
|
||||||
|
clearml-data close
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Training Using A ClearML Dataset
|
||||||
|
|
||||||
|
Now that you have a ClearML dataset, you can very simply use it to train custom YOLOv5 🚀 models!
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python train.py --img 640 --batch 16 --epochs 3 --data clearml://<your_dataset_id> --weights yolov5s.pt --cache
|
||||||
|
```
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
## 👀 Hyperparameter Optimization
|
||||||
|
|
||||||
|
Now that we have our experiments and data versioned, it's time to take a look at what we can build on top!
|
||||||
|
|
||||||
|
Using the code information, installed packages and environment details, the experiment itself is now **completely
|
||||||
|
reproducible**. In fact, ClearML allows you to clone an experiment and even change its parameters. We can then just
|
||||||
|
rerun it with these new parameters automatically, this is basically what HPO does!
|
||||||
|
|
||||||
|
To **run hyperparameter optimization locally**, we've included a pre-made script for you. Just make sure a training task
|
||||||
|
has been run at least once, so it is in the ClearML experiment manager, we will essentially clone it and change its
|
||||||
|
hyperparameters.
|
||||||
|
|
||||||
|
You'll need to fill in the ID of this `template task` in the script found at `utils/loggers/clearml/hpo.py` and then
|
||||||
|
just run it :) You can change `task.execute_locally()` to `task.execute()` to put it in a ClearML queue and have a
|
||||||
|
remote agent work on it instead.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# To use optuna, install it first, otherwise you can change the optimizer to just be RandomSearch
|
||||||
|
pip install optuna
|
||||||
|
python utils/loggers/clearml/hpo.py
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 🤯 Remote Execution (advanced)
|
||||||
|
|
||||||
|
Running HPO locally is really handy, but what if we want to run our experiments on a remote machine instead? Maybe you
|
||||||
|
have access to a very powerful GPU machine on-site, or you have some budget to use cloud GPUs.
|
||||||
|
This is where the ClearML Agent comes into play. Check out what the agent can do here:
|
||||||
|
|
||||||
|
- [YouTube video](https://youtu.be/MX3BrXnaULs)
|
||||||
|
- [Documentation](https://clear.ml/docs/latest/docs/clearml_agent)
|
||||||
|
|
||||||
|
In short: every experiment tracked by the experiment manager contains enough information to reproduce it on a different
|
||||||
|
machine (installed packages, uncommitted changes etc.). So a ClearML agent does just that: it listens to a queue for
|
||||||
|
incoming tasks and when it finds one, it recreates the environment and runs it while still reporting scalars, plots etc.
|
||||||
|
to the experiment manager.
|
||||||
|
|
||||||
|
You can turn any machine (a cloud VM, a local GPU machine, your own laptop ... ) into a ClearML agent by simply running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clearml-agent daemon --queue <queues_to_listen_to> [--docker]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cloning, Editing And Enqueuing
|
||||||
|
|
||||||
|
With our agent running, we can give it some work. Remember from the HPO section that we can clone a task and edit the
|
||||||
|
hyperparameters? We can do that from the interface too!
|
||||||
|
|
||||||
|
🪄 Clone the experiment by right-clicking it
|
||||||
|
|
||||||
|
🎯 Edit the hyperparameters to what you wish them to be
|
||||||
|
|
||||||
|
⏳ Enqueue the task to any of the queues by right-clicking it
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Executing A Task Remotely
|
||||||
|
|
||||||
|
Now you can clone a task like we explained above, or simply mark your current script by adding `task.execute_remotely()`
|
||||||
|
and on execution it will be put into a queue, for the agent to start working on!
|
||||||
|
|
||||||
|
To run the YOLOv5 training script remotely, all you have to do is add this line to the training.py script after the
|
||||||
|
clearml logger has been instantiated:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# ...
|
||||||
|
# Loggers
|
||||||
|
data_dict = None
|
||||||
|
if RANK in {-1, 0}:
|
||||||
|
loggers = Loggers(save_dir, weights, opt, hyp, LOGGER) # loggers instance
|
||||||
|
if loggers.clearml:
|
||||||
|
loggers.clearml.task.execute_remotely(queue="my_queue") # <------ ADD THIS LINE
|
||||||
|
# Data_dict is either None is user did not choose for ClearML dataset or is filled in by ClearML
|
||||||
|
data_dict = loggers.clearml.data_dict
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
When running the training script after this change, python will run the script up until that line, after which it will
|
||||||
|
package the code and send it to the queue instead!
|
||||||
|
|
||||||
|
### Autoscaling workers
|
||||||
|
|
||||||
|
ClearML comes with autoscalers too! This tool will automatically spin up new remote machines in the cloud of your
|
||||||
|
choice (AWS, GCP, Azure) and turn them into ClearML agents for you whenever there are experiments detected in the queue.
|
||||||
|
Once the tasks are processed, the autoscaler will automatically shut down the remote machines, and you stop paying!
|
||||||
|
|
||||||
|
Check out the autoscalers getting started video below.
|
||||||
|
|
||||||
|
[](https://youtu.be/j4XVMAaUt3E)
|
||||||
164
ros2_ws/src/yolov3_ros/utils/loggers/clearml/clearml_utils.py
Normal file
164
ros2_ws/src/yolov3_ros/utils/loggers/clearml/clearml_utils.py
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
"""Main Logger class for ClearML experiment tracking."""
|
||||||
|
import glob
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from utils.plots import Annotator, colors
|
||||||
|
|
||||||
|
try:
|
||||||
|
import clearml
|
||||||
|
from clearml import Dataset, Task
|
||||||
|
|
||||||
|
assert hasattr(clearml, '__version__') # verify package import not local dir
|
||||||
|
except (ImportError, AssertionError):
|
||||||
|
clearml = None
|
||||||
|
|
||||||
|
|
||||||
|
def construct_dataset(clearml_info_string):
|
||||||
|
"""Load in a clearml dataset and fill the internal data_dict with its contents.
|
||||||
|
"""
|
||||||
|
dataset_id = clearml_info_string.replace('clearml://', '')
|
||||||
|
dataset = Dataset.get(dataset_id=dataset_id)
|
||||||
|
dataset_root_path = Path(dataset.get_local_copy())
|
||||||
|
|
||||||
|
# We'll search for the yaml file definition in the dataset
|
||||||
|
yaml_filenames = list(glob.glob(str(dataset_root_path / '*.yaml')) + glob.glob(str(dataset_root_path / '*.yml')))
|
||||||
|
if len(yaml_filenames) > 1:
|
||||||
|
raise ValueError('More than one yaml file was found in the dataset root, cannot determine which one contains '
|
||||||
|
'the dataset definition this way.')
|
||||||
|
elif len(yaml_filenames) == 0:
|
||||||
|
raise ValueError('No yaml definition found in dataset root path, check that there is a correct yaml file '
|
||||||
|
'inside the dataset root path.')
|
||||||
|
with open(yaml_filenames[0]) as f:
|
||||||
|
dataset_definition = yaml.safe_load(f)
|
||||||
|
|
||||||
|
assert set(dataset_definition.keys()).issuperset(
|
||||||
|
{'train', 'test', 'val', 'nc', 'names'}
|
||||||
|
), "The right keys were not found in the yaml file, make sure it at least has the following keys: ('train', 'test', 'val', 'nc', 'names')"
|
||||||
|
|
||||||
|
data_dict = dict()
|
||||||
|
data_dict['train'] = str(
|
||||||
|
(dataset_root_path / dataset_definition['train']).resolve()) if dataset_definition['train'] else None
|
||||||
|
data_dict['test'] = str(
|
||||||
|
(dataset_root_path / dataset_definition['test']).resolve()) if dataset_definition['test'] else None
|
||||||
|
data_dict['val'] = str(
|
||||||
|
(dataset_root_path / dataset_definition['val']).resolve()) if dataset_definition['val'] else None
|
||||||
|
data_dict['nc'] = dataset_definition['nc']
|
||||||
|
data_dict['names'] = dataset_definition['names']
|
||||||
|
|
||||||
|
return data_dict
|
||||||
|
|
||||||
|
|
||||||
|
class ClearmlLogger:
|
||||||
|
"""Log training runs, datasets, models, and predictions to ClearML.
|
||||||
|
|
||||||
|
This logger sends information to ClearML at app.clear.ml or to your own hosted server. By default,
|
||||||
|
this information includes hyperparameters, system configuration and metrics, model metrics, code information and
|
||||||
|
basic data metrics and analyses.
|
||||||
|
|
||||||
|
By providing additional command line arguments to train.py, datasets,
|
||||||
|
models and predictions can also be logged.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, opt, hyp):
|
||||||
|
"""
|
||||||
|
- Initialize ClearML Task, this object will capture the experiment
|
||||||
|
- Upload dataset version to ClearML Data if opt.upload_dataset is True
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
opt (namespace) -- Commandline arguments for this run
|
||||||
|
hyp (dict) -- Hyperparameters for this run
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.current_epoch = 0
|
||||||
|
# Keep tracked of amount of logged images to enforce a limit
|
||||||
|
self.current_epoch_logged_images = set()
|
||||||
|
# Maximum number of images to log to clearML per epoch
|
||||||
|
self.max_imgs_to_log_per_epoch = 16
|
||||||
|
# Get the interval of epochs when bounding box images should be logged
|
||||||
|
self.bbox_interval = opt.bbox_interval
|
||||||
|
self.clearml = clearml
|
||||||
|
self.task = None
|
||||||
|
self.data_dict = None
|
||||||
|
if self.clearml:
|
||||||
|
self.task = Task.init(
|
||||||
|
project_name=opt.project if opt.project != 'runs/train' else 'YOLOv5',
|
||||||
|
task_name=opt.name if opt.name != 'exp' else 'Training',
|
||||||
|
tags=['YOLOv5'],
|
||||||
|
output_uri=True,
|
||||||
|
reuse_last_task_id=opt.exist_ok,
|
||||||
|
auto_connect_frameworks={'pytorch': False}
|
||||||
|
# We disconnect pytorch auto-detection, because we added manual model save points in the code
|
||||||
|
)
|
||||||
|
# ClearML's hooks will already grab all general parameters
|
||||||
|
# Only the hyperparameters coming from the yaml config file
|
||||||
|
# will have to be added manually!
|
||||||
|
self.task.connect(hyp, name='Hyperparameters')
|
||||||
|
self.task.connect(opt, name='Args')
|
||||||
|
|
||||||
|
# Make sure the code is easily remotely runnable by setting the docker image to use by the remote agent
|
||||||
|
self.task.set_base_docker('ultralytics/yolov5:latest',
|
||||||
|
docker_arguments='--ipc=host -e="CLEARML_AGENT_SKIP_PYTHON_ENV_INSTALL=1"',
|
||||||
|
docker_setup_bash_script='pip install clearml')
|
||||||
|
|
||||||
|
# Get ClearML Dataset Version if requested
|
||||||
|
if opt.data.startswith('clearml://'):
|
||||||
|
# data_dict should have the following keys:
|
||||||
|
# names, nc (number of classes), test, train, val (all three relative paths to ../datasets)
|
||||||
|
self.data_dict = construct_dataset(opt.data)
|
||||||
|
# Set data to data_dict because wandb will crash without this information and opt is the best way
|
||||||
|
# to give it to them
|
||||||
|
opt.data = self.data_dict
|
||||||
|
|
||||||
|
def log_debug_samples(self, files, title='Debug Samples'):
|
||||||
|
"""
|
||||||
|
Log files (images) as debug samples in the ClearML task.
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
files (List(PosixPath)) a list of file paths in PosixPath format
|
||||||
|
title (str) A title that groups together images with the same values
|
||||||
|
"""
|
||||||
|
for f in files:
|
||||||
|
if f.exists():
|
||||||
|
it = re.search(r'_batch(\d+)', f.name)
|
||||||
|
iteration = int(it.groups()[0]) if it else 0
|
||||||
|
self.task.get_logger().report_image(title=title,
|
||||||
|
series=f.name.replace(it.group(), ''),
|
||||||
|
local_path=str(f),
|
||||||
|
iteration=iteration)
|
||||||
|
|
||||||
|
def log_image_with_boxes(self, image_path, boxes, class_names, image, conf_threshold=0.25):
|
||||||
|
"""
|
||||||
|
Draw the bounding boxes on a single image and report the result as a ClearML debug sample.
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
image_path (PosixPath) the path the original image file
|
||||||
|
boxes (list): list of scaled predictions in the format - [xmin, ymin, xmax, ymax, confidence, class]
|
||||||
|
class_names (dict): dict containing mapping of class int to class name
|
||||||
|
image (Tensor): A torch tensor containing the actual image data
|
||||||
|
"""
|
||||||
|
if len(self.current_epoch_logged_images) < self.max_imgs_to_log_per_epoch and self.current_epoch >= 0:
|
||||||
|
# Log every bbox_interval times and deduplicate for any intermittend extra eval runs
|
||||||
|
if self.current_epoch % self.bbox_interval == 0 and image_path not in self.current_epoch_logged_images:
|
||||||
|
im = np.ascontiguousarray(np.moveaxis(image.mul(255).clamp(0, 255).byte().cpu().numpy(), 0, 2))
|
||||||
|
annotator = Annotator(im=im, pil=True)
|
||||||
|
for i, (conf, class_nr, box) in enumerate(zip(boxes[:, 4], boxes[:, 5], boxes[:, :4])):
|
||||||
|
color = colors(i)
|
||||||
|
|
||||||
|
class_name = class_names[int(class_nr)]
|
||||||
|
confidence_percentage = round(float(conf) * 100, 2)
|
||||||
|
label = f'{class_name}: {confidence_percentage}%'
|
||||||
|
|
||||||
|
if conf > conf_threshold:
|
||||||
|
annotator.rectangle(box.cpu().numpy(), outline=color)
|
||||||
|
annotator.box_label(box.cpu().numpy(), label=label, color=color)
|
||||||
|
|
||||||
|
annotated_image = annotator.result()
|
||||||
|
self.task.get_logger().report_image(title='Bounding Boxes',
|
||||||
|
series=image_path.name,
|
||||||
|
iteration=self.current_epoch,
|
||||||
|
image=annotated_image)
|
||||||
|
self.current_epoch_logged_images.add(image_path)
|
||||||
84
ros2_ws/src/yolov3_ros/utils/loggers/clearml/hpo.py
Normal file
84
ros2_ws/src/yolov3_ros/utils/loggers/clearml/hpo.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
from clearml import Task
|
||||||
|
# Connecting ClearML with the current process,
|
||||||
|
# from here on everything is logged automatically
|
||||||
|
from clearml.automation import HyperParameterOptimizer, UniformParameterRange
|
||||||
|
from clearml.automation.optuna import OptimizerOptuna
|
||||||
|
|
||||||
|
task = Task.init(project_name='Hyper-Parameter Optimization',
|
||||||
|
task_name='YOLOv5',
|
||||||
|
task_type=Task.TaskTypes.optimizer,
|
||||||
|
reuse_last_task_id=False)
|
||||||
|
|
||||||
|
# Example use case:
|
||||||
|
optimizer = HyperParameterOptimizer(
|
||||||
|
# This is the experiment we want to optimize
|
||||||
|
base_task_id='<your_template_task_id>',
|
||||||
|
# here we define the hyper-parameters to optimize
|
||||||
|
# Notice: The parameter name should exactly match what you see in the UI: <section_name>/<parameter>
|
||||||
|
# For Example, here we see in the base experiment a section Named: "General"
|
||||||
|
# under it a parameter named "batch_size", this becomes "General/batch_size"
|
||||||
|
# If you have `argparse` for example, then arguments will appear under the "Args" section,
|
||||||
|
# and you should instead pass "Args/batch_size"
|
||||||
|
hyper_parameters=[
|
||||||
|
UniformParameterRange('Hyperparameters/lr0', min_value=1e-5, max_value=1e-1),
|
||||||
|
UniformParameterRange('Hyperparameters/lrf', min_value=0.01, max_value=1.0),
|
||||||
|
UniformParameterRange('Hyperparameters/momentum', min_value=0.6, max_value=0.98),
|
||||||
|
UniformParameterRange('Hyperparameters/weight_decay', min_value=0.0, max_value=0.001),
|
||||||
|
UniformParameterRange('Hyperparameters/warmup_epochs', min_value=0.0, max_value=5.0),
|
||||||
|
UniformParameterRange('Hyperparameters/warmup_momentum', min_value=0.0, max_value=0.95),
|
||||||
|
UniformParameterRange('Hyperparameters/warmup_bias_lr', min_value=0.0, max_value=0.2),
|
||||||
|
UniformParameterRange('Hyperparameters/box', min_value=0.02, max_value=0.2),
|
||||||
|
UniformParameterRange('Hyperparameters/cls', min_value=0.2, max_value=4.0),
|
||||||
|
UniformParameterRange('Hyperparameters/cls_pw', min_value=0.5, max_value=2.0),
|
||||||
|
UniformParameterRange('Hyperparameters/obj', min_value=0.2, max_value=4.0),
|
||||||
|
UniformParameterRange('Hyperparameters/obj_pw', min_value=0.5, max_value=2.0),
|
||||||
|
UniformParameterRange('Hyperparameters/iou_t', min_value=0.1, max_value=0.7),
|
||||||
|
UniformParameterRange('Hyperparameters/anchor_t', min_value=2.0, max_value=8.0),
|
||||||
|
UniformParameterRange('Hyperparameters/fl_gamma', min_value=0.0, max_value=4.0),
|
||||||
|
UniformParameterRange('Hyperparameters/hsv_h', min_value=0.0, max_value=0.1),
|
||||||
|
UniformParameterRange('Hyperparameters/hsv_s', min_value=0.0, max_value=0.9),
|
||||||
|
UniformParameterRange('Hyperparameters/hsv_v', min_value=0.0, max_value=0.9),
|
||||||
|
UniformParameterRange('Hyperparameters/degrees', min_value=0.0, max_value=45.0),
|
||||||
|
UniformParameterRange('Hyperparameters/translate', min_value=0.0, max_value=0.9),
|
||||||
|
UniformParameterRange('Hyperparameters/scale', min_value=0.0, max_value=0.9),
|
||||||
|
UniformParameterRange('Hyperparameters/shear', min_value=0.0, max_value=10.0),
|
||||||
|
UniformParameterRange('Hyperparameters/perspective', min_value=0.0, max_value=0.001),
|
||||||
|
UniformParameterRange('Hyperparameters/flipud', min_value=0.0, max_value=1.0),
|
||||||
|
UniformParameterRange('Hyperparameters/fliplr', min_value=0.0, max_value=1.0),
|
||||||
|
UniformParameterRange('Hyperparameters/mosaic', min_value=0.0, max_value=1.0),
|
||||||
|
UniformParameterRange('Hyperparameters/mixup', min_value=0.0, max_value=1.0),
|
||||||
|
UniformParameterRange('Hyperparameters/copy_paste', min_value=0.0, max_value=1.0)],
|
||||||
|
# this is the objective metric we want to maximize/minimize
|
||||||
|
objective_metric_title='metrics',
|
||||||
|
objective_metric_series='mAP_0.5',
|
||||||
|
# now we decide if we want to maximize it or minimize it (accuracy we maximize)
|
||||||
|
objective_metric_sign='max',
|
||||||
|
# let us limit the number of concurrent experiments,
|
||||||
|
# this in turn will make sure we do dont bombard the scheduler with experiments.
|
||||||
|
# if we have an auto-scaler connected, this, by proxy, will limit the number of machine
|
||||||
|
max_number_of_concurrent_tasks=1,
|
||||||
|
# this is the optimizer class (actually doing the optimization)
|
||||||
|
# Currently, we can choose from GridSearch, RandomSearch or OptimizerBOHB (Bayesian optimization Hyper-Band)
|
||||||
|
optimizer_class=OptimizerOptuna,
|
||||||
|
# If specified only the top K performing Tasks will be kept, the others will be automatically archived
|
||||||
|
save_top_k_tasks_only=5, # 5,
|
||||||
|
compute_time_limit=None,
|
||||||
|
total_max_jobs=20,
|
||||||
|
min_iteration_per_job=None,
|
||||||
|
max_iteration_per_job=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# report every 10 seconds, this is way too often, but we are testing here
|
||||||
|
optimizer.set_report_period(10 / 60)
|
||||||
|
# You can also use the line below instead to run all the optimizer tasks locally, without using queues or agent
|
||||||
|
# an_optimizer.start_locally(job_complete_callback=job_complete_callback)
|
||||||
|
# set the time limit for the optimization process (2 hours)
|
||||||
|
optimizer.set_time_limit(in_minutes=120.0)
|
||||||
|
# Start the optimization process in the local environment
|
||||||
|
optimizer.start_locally()
|
||||||
|
# wait until process is done (notice we are controlling the optimization process in the background)
|
||||||
|
optimizer.wait()
|
||||||
|
# make sure background optimization stopped
|
||||||
|
optimizer.stop()
|
||||||
|
|
||||||
|
print('We are done, good bye')
|
||||||
284
ros2_ws/src/yolov3_ros/utils/loggers/comet/README.md
Normal file
284
ros2_ws/src/yolov3_ros/utils/loggers/comet/README.md
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
<img src="https://cdn.comet.ml/img/notebook_logo.png">
|
||||||
|
|
||||||
|
# YOLOv5 with Comet
|
||||||
|
|
||||||
|
This guide will cover how to use YOLOv5 with [Comet](https://bit.ly/yolov5-readme-comet2)
|
||||||
|
|
||||||
|
# About Comet
|
||||||
|
|
||||||
|
Comet builds tools that help data scientists, engineers, and team leaders accelerate and optimize machine learning and
|
||||||
|
deep learning models.
|
||||||
|
|
||||||
|
Track and visualize model metrics in real time, save your hyperparameters, datasets, and model checkpoints, and
|
||||||
|
visualize your model predictions
|
||||||
|
with [Comet Custom Panels](https://www.comet.com/docs/v2/guides/comet-dashboard/code-panels/about-panels/?utm_source=yolov5&utm_medium=partner&utm_campaign=partner_yolov5_2022&utm_content=github)!
|
||||||
|
Comet makes sure you never lose track of your work and makes it easy to share results and collaborate across teams of
|
||||||
|
all sizes!
|
||||||
|
|
||||||
|
# Getting Started
|
||||||
|
|
||||||
|
## Install Comet
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pip install comet_ml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configure Comet Credentials
|
||||||
|
|
||||||
|
There are two ways to configure Comet with YOLOv5.
|
||||||
|
|
||||||
|
You can either set your credentials through environment variables
|
||||||
|
|
||||||
|
**Environment Variables**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
export COMET_API_KEY=<Your Comet API Key>
|
||||||
|
export COMET_PROJECT_NAME=<Your Comet Project Name> # This will default to 'yolov5'
|
||||||
|
```
|
||||||
|
|
||||||
|
Or create a `.comet.config` file in your working directory and set your credentials there.
|
||||||
|
|
||||||
|
**Comet Configuration File**
|
||||||
|
|
||||||
|
```
|
||||||
|
[comet]
|
||||||
|
api_key=<Your Comet API Key>
|
||||||
|
project_name=<Your Comet Project Name> # This will default to 'yolov5'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run the Training Script
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Train YOLOv5s on COCO128 for 5 epochs
|
||||||
|
python train.py --img 640 --batch 16 --epochs 5 --data coco128.yaml --weights yolov5s.pt
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! Comet will automatically log your hyperparameters, command line arguments, training and validation metrics.
|
||||||
|
You can visualize and analyze your runs in the Comet UI
|
||||||
|
|
||||||
|
<img width="1920" alt="yolo-ui" src="https://user-images.githubusercontent.com/26833433/202851203-164e94e1-2238-46dd-91f8-de020e9d6b41.png">
|
||||||
|
|
||||||
|
# Try out an Example!
|
||||||
|
|
||||||
|
Check out an example of
|
||||||
|
a [completed run here](https://www.comet.com/examples/comet-example-yolov5/a0e29e0e9b984e4a822db2a62d0cb357?experiment-tab=chart&showOutliers=true&smoothing=0&transformY=smoothing&xAxis=step&utm_source=yolov5&utm_medium=partner&utm_campaign=partner_yolov5_2022&utm_content=github)
|
||||||
|
|
||||||
|
Or better yet, try it out yourself in this Colab Notebook
|
||||||
|
|
||||||
|
[](https://colab.research.google.com/drive/1RG0WOQyxlDlo5Km8GogJpIEJlg_5lyYO?usp=sharing)
|
||||||
|
|
||||||
|
# Log automatically
|
||||||
|
|
||||||
|
By default, Comet will log the following items
|
||||||
|
|
||||||
|
## Metrics
|
||||||
|
|
||||||
|
- Box Loss, Object Loss, Classification Loss for the training and validation data
|
||||||
|
- mAP_0.5, mAP_0.5:0.95 metrics for the validation data.
|
||||||
|
- Precision and Recall for the validation data
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- Model Hyperparameters
|
||||||
|
- All parameters passed through the command line options
|
||||||
|
|
||||||
|
## Visualizations
|
||||||
|
|
||||||
|
- Confusion Matrix of the model predictions on the validation data
|
||||||
|
- Plots for the PR and F1 curves across all classes
|
||||||
|
- Correlogram of the Class Labels
|
||||||
|
|
||||||
|
# Configure Comet Logging
|
||||||
|
|
||||||
|
Comet can be configured to log additional data either through command line flags passed to the training script
|
||||||
|
or through environment variables.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
export COMET_MODE=online # Set whether to run Comet in 'online' or 'offline' mode. Defaults to online
|
||||||
|
export COMET_MODEL_NAME=<your model name> #Set the name for the saved model. Defaults to yolov5
|
||||||
|
export COMET_LOG_CONFUSION_MATRIX=false # Set to disable logging a Comet Confusion Matrix. Defaults to true
|
||||||
|
export COMET_MAX_IMAGE_UPLOADS=<number of allowed images to upload to Comet> # Controls how many total image predictions to log to Comet. Defaults to 100.
|
||||||
|
export COMET_LOG_PER_CLASS_METRICS=true # Set to log evaluation metrics for each detected class at the end of training. Defaults to false
|
||||||
|
export COMET_DEFAULT_CHECKPOINT_FILENAME=<your checkpoint filename> # Set this if you would like to resume training from a different checkpoint. Defaults to 'last.pt'
|
||||||
|
export COMET_LOG_BATCH_LEVEL_METRICS=true # Set this if you would like to log training metrics at the batch level. Defaults to false.
|
||||||
|
export COMET_LOG_PREDICTIONS=true # Set this to false to disable logging model predictions
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logging Checkpoints with Comet
|
||||||
|
|
||||||
|
Logging Models to Comet is disabled by default. To enable it, pass the `save-period` argument to the training script.
|
||||||
|
This will save the
|
||||||
|
logged checkpoints to Comet based on the interval value provided by `save-period`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python train.py \
|
||||||
|
--img 640 \
|
||||||
|
--batch 16 \
|
||||||
|
--epochs 5 \
|
||||||
|
--data coco128.yaml \
|
||||||
|
--weights yolov5s.pt \
|
||||||
|
--save-period 1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logging Model Predictions
|
||||||
|
|
||||||
|
By default, model predictions (images, ground truth labels and bounding boxes) will be logged to Comet.
|
||||||
|
|
||||||
|
You can control the frequency of logged predictions and the associated images by passing the `bbox_interval` command
|
||||||
|
line argument. Predictions can be visualized using Comet's Object Detection Custom Panel. This frequency corresponds to
|
||||||
|
every Nth batch of data per epoch. In the example below, we are logging every 2nd batch of data for each epoch.
|
||||||
|
|
||||||
|
**Note:** The YOLOv5 validation dataloader will default to a batch size of 32, so you will have to set the logging
|
||||||
|
frequency accordingly.
|
||||||
|
|
||||||
|
Here is
|
||||||
|
an [example project using the Panel](https://www.comet.com/examples/comet-example-yolov5?shareable=YcwMiJaZSXfcEXpGOHDD12vA1&utm_source=yolov5&utm_medium=partner&utm_campaign=partner_yolov5_2022&utm_content=github)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python train.py \
|
||||||
|
--img 640 \
|
||||||
|
--batch 16 \
|
||||||
|
--epochs 5 \
|
||||||
|
--data coco128.yaml \
|
||||||
|
--weights yolov5s.pt \
|
||||||
|
--bbox_interval 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Controlling the number of Prediction Images logged to Comet
|
||||||
|
|
||||||
|
When logging predictions from YOLOv5, Comet will log the images associated with each set of predictions. By default a
|
||||||
|
maximum of 100 validation images are logged. You can increase or decrease this number using
|
||||||
|
the `COMET_MAX_IMAGE_UPLOADS` environment variable.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
env COMET_MAX_IMAGE_UPLOADS=200 python train.py \
|
||||||
|
--img 640 \
|
||||||
|
--batch 16 \
|
||||||
|
--epochs 5 \
|
||||||
|
--data coco128.yaml \
|
||||||
|
--weights yolov5s.pt \
|
||||||
|
--bbox_interval 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logging Class Level Metrics
|
||||||
|
|
||||||
|
Use the `COMET_LOG_PER_CLASS_METRICS` environment variable to log mAP, precision, recall, f1 for each class.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
env COMET_LOG_PER_CLASS_METRICS=true python train.py \
|
||||||
|
--img 640 \
|
||||||
|
--batch 16 \
|
||||||
|
--epochs 5 \
|
||||||
|
--data coco128.yaml \
|
||||||
|
--weights yolov5s.pt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uploading a Dataset to Comet Artifacts
|
||||||
|
|
||||||
|
If you would like to store your data
|
||||||
|
using [Comet Artifacts](https://www.comet.com/docs/v2/guides/data-management/using-artifacts/#learn-more?utm_source=yolov5&utm_medium=partner&utm_campaign=partner_yolov5_2022&utm_content=github),
|
||||||
|
you can do so using the `upload_dataset` flag.
|
||||||
|
|
||||||
|
The dataset be organized in the way described in
|
||||||
|
the [YOLOv5 documentation](https://docs.ultralytics.com/tutorials/train-custom-datasets/#3-organize-directories). The
|
||||||
|
dataset config `yaml` file must follow the same format as that of the `coco128.yaml` file.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python train.py \
|
||||||
|
--img 640 \
|
||||||
|
--batch 16 \
|
||||||
|
--epochs 5 \
|
||||||
|
--data coco128.yaml \
|
||||||
|
--weights yolov5s.pt \
|
||||||
|
--upload_dataset
|
||||||
|
```
|
||||||
|
|
||||||
|
You can find the uploaded dataset in the Artifacts tab in your Comet Workspace
|
||||||
|
<img width="1073" alt="artifact-1" src="https://user-images.githubusercontent.com/7529846/186929193-162718bf-ec7b-4eb9-8c3b-86b3763ef8ea.png">
|
||||||
|
|
||||||
|
You can preview the data directly in the Comet UI.
|
||||||
|
<img width="1082" alt="artifact-2" src="https://user-images.githubusercontent.com/7529846/186929215-432c36a9-c109-4eb0-944b-84c2786590d6.png">
|
||||||
|
|
||||||
|
Artifacts are versioned and also support adding metadata about the dataset. Comet will automatically log the metadata
|
||||||
|
from your dataset `yaml` file
|
||||||
|
<img width="963" alt="artifact-3" src="https://user-images.githubusercontent.com/7529846/186929256-9d44d6eb-1a19-42de-889a-bcbca3018f2e.png">
|
||||||
|
|
||||||
|
### Using a saved Artifact
|
||||||
|
|
||||||
|
If you would like to use a dataset from Comet Artifacts, set the `path` variable in your dataset `yaml` file to point to
|
||||||
|
the following Artifact resource URL.
|
||||||
|
|
||||||
|
```
|
||||||
|
# contents of artifact.yaml file
|
||||||
|
path: "comet://<workspace name>/<artifact name>:<artifact version or alias>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then pass this file to your training script in the following way
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python train.py \
|
||||||
|
--img 640 \
|
||||||
|
--batch 16 \
|
||||||
|
--epochs 5 \
|
||||||
|
--data artifact.yaml \
|
||||||
|
--weights yolov5s.pt
|
||||||
|
```
|
||||||
|
|
||||||
|
Artifacts also allow you to track the lineage of data as it flows through your Experimentation workflow. Here you can
|
||||||
|
see a graph that shows you all the experiments that have used your uploaded dataset.
|
||||||
|
<img width="1391" alt="artifact-4" src="https://user-images.githubusercontent.com/7529846/186929264-4c4014fa-fe51-4f3c-a5c5-f6d24649b1b4.png">
|
||||||
|
|
||||||
|
## Resuming a Training Run
|
||||||
|
|
||||||
|
If your training run is interrupted for any reason, e.g. disrupted internet connection, you can resume the run using
|
||||||
|
the `resume` flag and the Comet Run Path.
|
||||||
|
|
||||||
|
The Run Path has the following format `comet://<your workspace name>/<your project name>/<experiment id>`.
|
||||||
|
|
||||||
|
This will restore the run to its state before the interruption, which includes restoring the model from a checkpoint,
|
||||||
|
restoring all hyperparameters and training arguments and downloading Comet dataset Artifacts if they were used in the
|
||||||
|
original run. The resumed run will continue logging to the existing Experiment in the Comet UI
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python train.py \
|
||||||
|
--resume "comet://<your run path>"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hyperparameter Search with the Comet Optimizer
|
||||||
|
|
||||||
|
YOLOv5 is also integrated with Comet's Optimizer, making is simple to visualize hyperparameter sweeps in the Comet UI.
|
||||||
|
|
||||||
|
### Configuring an Optimizer Sweep
|
||||||
|
|
||||||
|
To configure the Comet Optimizer, you will have to create a JSON file with the information about the sweep. An example
|
||||||
|
file has been provided in `utils/loggers/comet/optimizer_config.json`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python utils/loggers/comet/hpo.py \
|
||||||
|
--comet_optimizer_config "utils/loggers/comet/optimizer_config.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
The `hpo.py` script accepts the same arguments as `train.py`. If you wish to pass additional arguments to your sweep
|
||||||
|
simply add them after
|
||||||
|
the script.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python utils/loggers/comet/hpo.py \
|
||||||
|
--comet_optimizer_config "utils/loggers/comet/optimizer_config.json" \
|
||||||
|
--save-period 1 \
|
||||||
|
--bbox_interval 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running a Sweep in Parallel
|
||||||
|
|
||||||
|
```shell
|
||||||
|
comet optimizer -j <set number of workers> utils/loggers/comet/hpo.py \
|
||||||
|
utils/loggers/comet/optimizer_config.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Visualizing Results
|
||||||
|
|
||||||
|
Comet provides a number of ways to visualize the results of your sweep. Take a look at
|
||||||
|
a [project with a completed sweep here](https://www.comet.com/examples/comet-example-yolov5/view/PrlArHGuuhDTKC1UuBmTtOSXD/panels?utm_source=yolov5&utm_medium=partner&utm_campaign=partner_yolov5_2022&utm_content=github)
|
||||||
|
|
||||||
|
<img width="1626" alt="hyperparameter-yolo" src="https://user-images.githubusercontent.com/7529846/186914869-7dc1de14-583f-4323-967b-c9a66a29e495.png">
|
||||||
508
ros2_ws/src/yolov3_ros/utils/loggers/comet/__init__.py
Normal file
508
ros2_ws/src/yolov3_ros/utils/loggers/comet/__init__.py
Normal file
@ -0,0 +1,508 @@
|
|||||||
|
import glob
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
FILE = Path(__file__).resolve()
|
||||||
|
ROOT = FILE.parents[3] # YOLOv5 root directory
|
||||||
|
if str(ROOT) not in sys.path:
|
||||||
|
sys.path.append(str(ROOT)) # add ROOT to PATH
|
||||||
|
|
||||||
|
try:
|
||||||
|
import comet_ml
|
||||||
|
|
||||||
|
# Project Configuration
|
||||||
|
config = comet_ml.config.get_config()
|
||||||
|
COMET_PROJECT_NAME = config.get_string(os.getenv('COMET_PROJECT_NAME'), 'comet.project_name', default='yolov5')
|
||||||
|
except (ModuleNotFoundError, ImportError):
|
||||||
|
comet_ml = None
|
||||||
|
COMET_PROJECT_NAME = None
|
||||||
|
|
||||||
|
import PIL
|
||||||
|
import torch
|
||||||
|
import torchvision.transforms as T
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from utils.dataloaders import img2label_paths
|
||||||
|
from utils.general import check_dataset, scale_boxes, xywh2xyxy
|
||||||
|
from utils.metrics import box_iou
|
||||||
|
|
||||||
|
COMET_PREFIX = 'comet://'
|
||||||
|
|
||||||
|
COMET_MODE = os.getenv('COMET_MODE', 'online')
|
||||||
|
|
||||||
|
# Model Saving Settings
|
||||||
|
COMET_MODEL_NAME = os.getenv('COMET_MODEL_NAME', 'yolov5')
|
||||||
|
|
||||||
|
# Dataset Artifact Settings
|
||||||
|
COMET_UPLOAD_DATASET = os.getenv('COMET_UPLOAD_DATASET', 'false').lower() == 'true'
|
||||||
|
|
||||||
|
# Evaluation Settings
|
||||||
|
COMET_LOG_CONFUSION_MATRIX = os.getenv('COMET_LOG_CONFUSION_MATRIX', 'true').lower() == 'true'
|
||||||
|
COMET_LOG_PREDICTIONS = os.getenv('COMET_LOG_PREDICTIONS', 'true').lower() == 'true'
|
||||||
|
COMET_MAX_IMAGE_UPLOADS = int(os.getenv('COMET_MAX_IMAGE_UPLOADS', 100))
|
||||||
|
|
||||||
|
# Confusion Matrix Settings
|
||||||
|
CONF_THRES = float(os.getenv('CONF_THRES', 0.001))
|
||||||
|
IOU_THRES = float(os.getenv('IOU_THRES', 0.6))
|
||||||
|
|
||||||
|
# Batch Logging Settings
|
||||||
|
COMET_LOG_BATCH_METRICS = os.getenv('COMET_LOG_BATCH_METRICS', 'false').lower() == 'true'
|
||||||
|
COMET_BATCH_LOGGING_INTERVAL = os.getenv('COMET_BATCH_LOGGING_INTERVAL', 1)
|
||||||
|
COMET_PREDICTION_LOGGING_INTERVAL = os.getenv('COMET_PREDICTION_LOGGING_INTERVAL', 1)
|
||||||
|
COMET_LOG_PER_CLASS_METRICS = os.getenv('COMET_LOG_PER_CLASS_METRICS', 'false').lower() == 'true'
|
||||||
|
|
||||||
|
RANK = int(os.getenv('RANK', -1))
|
||||||
|
|
||||||
|
to_pil = T.ToPILImage()
|
||||||
|
|
||||||
|
|
||||||
|
class CometLogger:
|
||||||
|
"""Log metrics, parameters, source code, models and much more
|
||||||
|
with Comet
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, opt, hyp, run_id=None, job_type='Training', **experiment_kwargs) -> None:
|
||||||
|
self.job_type = job_type
|
||||||
|
self.opt = opt
|
||||||
|
self.hyp = hyp
|
||||||
|
|
||||||
|
# Comet Flags
|
||||||
|
self.comet_mode = COMET_MODE
|
||||||
|
|
||||||
|
self.save_model = opt.save_period > -1
|
||||||
|
self.model_name = COMET_MODEL_NAME
|
||||||
|
|
||||||
|
# Batch Logging Settings
|
||||||
|
self.log_batch_metrics = COMET_LOG_BATCH_METRICS
|
||||||
|
self.comet_log_batch_interval = COMET_BATCH_LOGGING_INTERVAL
|
||||||
|
|
||||||
|
# Dataset Artifact Settings
|
||||||
|
self.upload_dataset = self.opt.upload_dataset if self.opt.upload_dataset else COMET_UPLOAD_DATASET
|
||||||
|
self.resume = self.opt.resume
|
||||||
|
|
||||||
|
# Default parameters to pass to Experiment objects
|
||||||
|
self.default_experiment_kwargs = {
|
||||||
|
'log_code': False,
|
||||||
|
'log_env_gpu': True,
|
||||||
|
'log_env_cpu': True,
|
||||||
|
'project_name': COMET_PROJECT_NAME,}
|
||||||
|
self.default_experiment_kwargs.update(experiment_kwargs)
|
||||||
|
self.experiment = self._get_experiment(self.comet_mode, run_id)
|
||||||
|
|
||||||
|
self.data_dict = self.check_dataset(self.opt.data)
|
||||||
|
self.class_names = self.data_dict['names']
|
||||||
|
self.num_classes = self.data_dict['nc']
|
||||||
|
|
||||||
|
self.logged_images_count = 0
|
||||||
|
self.max_images = COMET_MAX_IMAGE_UPLOADS
|
||||||
|
|
||||||
|
if run_id is None:
|
||||||
|
self.experiment.log_other('Created from', 'YOLOv5')
|
||||||
|
if not isinstance(self.experiment, comet_ml.OfflineExperiment):
|
||||||
|
workspace, project_name, experiment_id = self.experiment.url.split('/')[-3:]
|
||||||
|
self.experiment.log_other(
|
||||||
|
'Run Path',
|
||||||
|
f'{workspace}/{project_name}/{experiment_id}',
|
||||||
|
)
|
||||||
|
self.log_parameters(vars(opt))
|
||||||
|
self.log_parameters(self.opt.hyp)
|
||||||
|
self.log_asset_data(
|
||||||
|
self.opt.hyp,
|
||||||
|
name='hyperparameters.json',
|
||||||
|
metadata={'type': 'hyp-config-file'},
|
||||||
|
)
|
||||||
|
self.log_asset(
|
||||||
|
f'{self.opt.save_dir}/opt.yaml',
|
||||||
|
metadata={'type': 'opt-config-file'},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.comet_log_confusion_matrix = COMET_LOG_CONFUSION_MATRIX
|
||||||
|
|
||||||
|
if hasattr(self.opt, 'conf_thres'):
|
||||||
|
self.conf_thres = self.opt.conf_thres
|
||||||
|
else:
|
||||||
|
self.conf_thres = CONF_THRES
|
||||||
|
if hasattr(self.opt, 'iou_thres'):
|
||||||
|
self.iou_thres = self.opt.iou_thres
|
||||||
|
else:
|
||||||
|
self.iou_thres = IOU_THRES
|
||||||
|
|
||||||
|
self.log_parameters({'val_iou_threshold': self.iou_thres, 'val_conf_threshold': self.conf_thres})
|
||||||
|
|
||||||
|
self.comet_log_predictions = COMET_LOG_PREDICTIONS
|
||||||
|
if self.opt.bbox_interval == -1:
|
||||||
|
self.comet_log_prediction_interval = 1 if self.opt.epochs < 10 else self.opt.epochs // 10
|
||||||
|
else:
|
||||||
|
self.comet_log_prediction_interval = self.opt.bbox_interval
|
||||||
|
|
||||||
|
if self.comet_log_predictions:
|
||||||
|
self.metadata_dict = {}
|
||||||
|
self.logged_image_names = []
|
||||||
|
|
||||||
|
self.comet_log_per_class_metrics = COMET_LOG_PER_CLASS_METRICS
|
||||||
|
|
||||||
|
self.experiment.log_others({
|
||||||
|
'comet_mode': COMET_MODE,
|
||||||
|
'comet_max_image_uploads': COMET_MAX_IMAGE_UPLOADS,
|
||||||
|
'comet_log_per_class_metrics': COMET_LOG_PER_CLASS_METRICS,
|
||||||
|
'comet_log_batch_metrics': COMET_LOG_BATCH_METRICS,
|
||||||
|
'comet_log_confusion_matrix': COMET_LOG_CONFUSION_MATRIX,
|
||||||
|
'comet_model_name': COMET_MODEL_NAME,})
|
||||||
|
|
||||||
|
# Check if running the Experiment with the Comet Optimizer
|
||||||
|
if hasattr(self.opt, 'comet_optimizer_id'):
|
||||||
|
self.experiment.log_other('optimizer_id', self.opt.comet_optimizer_id)
|
||||||
|
self.experiment.log_other('optimizer_objective', self.opt.comet_optimizer_objective)
|
||||||
|
self.experiment.log_other('optimizer_metric', self.opt.comet_optimizer_metric)
|
||||||
|
self.experiment.log_other('optimizer_parameters', json.dumps(self.hyp))
|
||||||
|
|
||||||
|
def _get_experiment(self, mode, experiment_id=None):
|
||||||
|
if mode == 'offline':
|
||||||
|
if experiment_id is not None:
|
||||||
|
return comet_ml.ExistingOfflineExperiment(
|
||||||
|
previous_experiment=experiment_id,
|
||||||
|
**self.default_experiment_kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return comet_ml.OfflineExperiment(**self.default_experiment_kwargs,)
|
||||||
|
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if experiment_id is not None:
|
||||||
|
return comet_ml.ExistingExperiment(
|
||||||
|
previous_experiment=experiment_id,
|
||||||
|
**self.default_experiment_kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return comet_ml.Experiment(**self.default_experiment_kwargs)
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
logger.warning('COMET WARNING: '
|
||||||
|
'Comet credentials have not been set. '
|
||||||
|
'Comet will default to offline logging. '
|
||||||
|
'Please set your credentials to enable online logging.')
|
||||||
|
return self._get_experiment('offline', experiment_id)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def log_metrics(self, log_dict, **kwargs):
|
||||||
|
self.experiment.log_metrics(log_dict, **kwargs)
|
||||||
|
|
||||||
|
def log_parameters(self, log_dict, **kwargs):
|
||||||
|
self.experiment.log_parameters(log_dict, **kwargs)
|
||||||
|
|
||||||
|
def log_asset(self, asset_path, **kwargs):
|
||||||
|
self.experiment.log_asset(asset_path, **kwargs)
|
||||||
|
|
||||||
|
def log_asset_data(self, asset, **kwargs):
|
||||||
|
self.experiment.log_asset_data(asset, **kwargs)
|
||||||
|
|
||||||
|
def log_image(self, img, **kwargs):
|
||||||
|
self.experiment.log_image(img, **kwargs)
|
||||||
|
|
||||||
|
def log_model(self, path, opt, epoch, fitness_score, best_model=False):
|
||||||
|
if not self.save_model:
|
||||||
|
return
|
||||||
|
|
||||||
|
model_metadata = {
|
||||||
|
'fitness_score': fitness_score[-1],
|
||||||
|
'epochs_trained': epoch + 1,
|
||||||
|
'save_period': opt.save_period,
|
||||||
|
'total_epochs': opt.epochs,}
|
||||||
|
|
||||||
|
model_files = glob.glob(f'{path}/*.pt')
|
||||||
|
for model_path in model_files:
|
||||||
|
name = Path(model_path).name
|
||||||
|
|
||||||
|
self.experiment.log_model(
|
||||||
|
self.model_name,
|
||||||
|
file_or_folder=model_path,
|
||||||
|
file_name=name,
|
||||||
|
metadata=model_metadata,
|
||||||
|
overwrite=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_dataset(self, data_file):
|
||||||
|
with open(data_file) as f:
|
||||||
|
data_config = yaml.safe_load(f)
|
||||||
|
|
||||||
|
if data_config['path'].startswith(COMET_PREFIX):
|
||||||
|
path = data_config['path'].replace(COMET_PREFIX, '')
|
||||||
|
data_dict = self.download_dataset_artifact(path)
|
||||||
|
|
||||||
|
return data_dict
|
||||||
|
|
||||||
|
self.log_asset(self.opt.data, metadata={'type': 'data-config-file'})
|
||||||
|
|
||||||
|
return check_dataset(data_file)
|
||||||
|
|
||||||
|
def log_predictions(self, image, labelsn, path, shape, predn):
|
||||||
|
if self.logged_images_count >= self.max_images:
|
||||||
|
return
|
||||||
|
detections = predn[predn[:, 4] > self.conf_thres]
|
||||||
|
iou = box_iou(labelsn[:, 1:], detections[:, :4])
|
||||||
|
mask, _ = torch.where(iou > self.iou_thres)
|
||||||
|
if len(mask) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
filtered_detections = detections[mask]
|
||||||
|
filtered_labels = labelsn[mask]
|
||||||
|
|
||||||
|
image_id = path.split('/')[-1].split('.')[0]
|
||||||
|
image_name = f'{image_id}_curr_epoch_{self.experiment.curr_epoch}'
|
||||||
|
if image_name not in self.logged_image_names:
|
||||||
|
native_scale_image = PIL.Image.open(path)
|
||||||
|
self.log_image(native_scale_image, name=image_name)
|
||||||
|
self.logged_image_names.append(image_name)
|
||||||
|
|
||||||
|
metadata = []
|
||||||
|
for cls, *xyxy in filtered_labels.tolist():
|
||||||
|
metadata.append({
|
||||||
|
'label': f'{self.class_names[int(cls)]}-gt',
|
||||||
|
'score': 100,
|
||||||
|
'box': {
|
||||||
|
'x': xyxy[0],
|
||||||
|
'y': xyxy[1],
|
||||||
|
'x2': xyxy[2],
|
||||||
|
'y2': xyxy[3]},})
|
||||||
|
for *xyxy, conf, cls in filtered_detections.tolist():
|
||||||
|
metadata.append({
|
||||||
|
'label': f'{self.class_names[int(cls)]}',
|
||||||
|
'score': conf * 100,
|
||||||
|
'box': {
|
||||||
|
'x': xyxy[0],
|
||||||
|
'y': xyxy[1],
|
||||||
|
'x2': xyxy[2],
|
||||||
|
'y2': xyxy[3]},})
|
||||||
|
|
||||||
|
self.metadata_dict[image_name] = metadata
|
||||||
|
self.logged_images_count += 1
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def preprocess_prediction(self, image, labels, shape, pred):
|
||||||
|
nl, _ = labels.shape[0], pred.shape[0]
|
||||||
|
|
||||||
|
# Predictions
|
||||||
|
if self.opt.single_cls:
|
||||||
|
pred[:, 5] = 0
|
||||||
|
|
||||||
|
predn = pred.clone()
|
||||||
|
scale_boxes(image.shape[1:], predn[:, :4], shape[0], shape[1])
|
||||||
|
|
||||||
|
labelsn = None
|
||||||
|
if nl:
|
||||||
|
tbox = xywh2xyxy(labels[:, 1:5]) # target boxes
|
||||||
|
scale_boxes(image.shape[1:], tbox, shape[0], shape[1]) # native-space labels
|
||||||
|
labelsn = torch.cat((labels[:, 0:1], tbox), 1) # native-space labels
|
||||||
|
scale_boxes(image.shape[1:], predn[:, :4], shape[0], shape[1]) # native-space pred
|
||||||
|
|
||||||
|
return predn, labelsn
|
||||||
|
|
||||||
|
def add_assets_to_artifact(self, artifact, path, asset_path, split):
|
||||||
|
img_paths = sorted(glob.glob(f'{asset_path}/*'))
|
||||||
|
label_paths = img2label_paths(img_paths)
|
||||||
|
|
||||||
|
for image_file, label_file in zip(img_paths, label_paths):
|
||||||
|
image_logical_path, label_logical_path = map(lambda x: os.path.relpath(x, path), [image_file, label_file])
|
||||||
|
|
||||||
|
try:
|
||||||
|
artifact.add(image_file, logical_path=image_logical_path, metadata={'split': split})
|
||||||
|
artifact.add(label_file, logical_path=label_logical_path, metadata={'split': split})
|
||||||
|
except ValueError as e:
|
||||||
|
logger.error('COMET ERROR: Error adding file to Artifact. Skipping file.')
|
||||||
|
logger.error(f'COMET ERROR: {e}')
|
||||||
|
continue
|
||||||
|
|
||||||
|
return artifact
|
||||||
|
|
||||||
|
def upload_dataset_artifact(self):
|
||||||
|
dataset_name = self.data_dict.get('dataset_name', 'yolov5-dataset')
|
||||||
|
path = str((ROOT / Path(self.data_dict['path'])).resolve())
|
||||||
|
|
||||||
|
metadata = self.data_dict.copy()
|
||||||
|
for key in ['train', 'val', 'test']:
|
||||||
|
split_path = metadata.get(key)
|
||||||
|
if split_path is not None:
|
||||||
|
metadata[key] = split_path.replace(path, '')
|
||||||
|
|
||||||
|
artifact = comet_ml.Artifact(name=dataset_name, artifact_type='dataset', metadata=metadata)
|
||||||
|
for key in metadata.keys():
|
||||||
|
if key in ['train', 'val', 'test']:
|
||||||
|
if isinstance(self.upload_dataset, str) and (key != self.upload_dataset):
|
||||||
|
continue
|
||||||
|
|
||||||
|
asset_path = self.data_dict.get(key)
|
||||||
|
if asset_path is not None:
|
||||||
|
artifact = self.add_assets_to_artifact(artifact, path, asset_path, key)
|
||||||
|
|
||||||
|
self.experiment.log_artifact(artifact)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def download_dataset_artifact(self, artifact_path):
|
||||||
|
logged_artifact = self.experiment.get_artifact(artifact_path)
|
||||||
|
artifact_save_dir = str(Path(self.opt.save_dir) / logged_artifact.name)
|
||||||
|
logged_artifact.download(artifact_save_dir)
|
||||||
|
|
||||||
|
metadata = logged_artifact.metadata
|
||||||
|
data_dict = metadata.copy()
|
||||||
|
data_dict['path'] = artifact_save_dir
|
||||||
|
|
||||||
|
metadata_names = metadata.get('names')
|
||||||
|
if type(metadata_names) == dict:
|
||||||
|
data_dict['names'] = {int(k): v for k, v in metadata.get('names').items()}
|
||||||
|
elif type(metadata_names) == list:
|
||||||
|
data_dict['names'] = {int(k): v for k, v in zip(range(len(metadata_names)), metadata_names)}
|
||||||
|
else:
|
||||||
|
raise "Invalid 'names' field in dataset yaml file. Please use a list or dictionary"
|
||||||
|
|
||||||
|
data_dict = self.update_data_paths(data_dict)
|
||||||
|
return data_dict
|
||||||
|
|
||||||
|
def update_data_paths(self, data_dict):
|
||||||
|
path = data_dict.get('path', '')
|
||||||
|
|
||||||
|
for split in ['train', 'val', 'test']:
|
||||||
|
if data_dict.get(split):
|
||||||
|
split_path = data_dict.get(split)
|
||||||
|
data_dict[split] = (f'{path}/{split_path}' if isinstance(split, str) else [
|
||||||
|
f'{path}/{x}' for x in split_path])
|
||||||
|
|
||||||
|
return data_dict
|
||||||
|
|
||||||
|
def on_pretrain_routine_end(self, paths):
|
||||||
|
if self.opt.resume:
|
||||||
|
return
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
self.log_asset(str(path))
|
||||||
|
|
||||||
|
if self.upload_dataset:
|
||||||
|
if not self.resume:
|
||||||
|
self.upload_dataset_artifact()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_train_start(self):
|
||||||
|
self.log_parameters(self.hyp)
|
||||||
|
|
||||||
|
def on_train_epoch_start(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_train_epoch_end(self, epoch):
|
||||||
|
self.experiment.curr_epoch = epoch
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_train_batch_start(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_train_batch_end(self, log_dict, step):
|
||||||
|
self.experiment.curr_step = step
|
||||||
|
if self.log_batch_metrics and (step % self.comet_log_batch_interval == 0):
|
||||||
|
self.log_metrics(log_dict, step=step)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_train_end(self, files, save_dir, last, best, epoch, results):
|
||||||
|
if self.comet_log_predictions:
|
||||||
|
curr_epoch = self.experiment.curr_epoch
|
||||||
|
self.experiment.log_asset_data(self.metadata_dict, 'image-metadata.json', epoch=curr_epoch)
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
self.log_asset(f, metadata={'epoch': epoch})
|
||||||
|
self.log_asset(f'{save_dir}/results.csv', metadata={'epoch': epoch})
|
||||||
|
|
||||||
|
if not self.opt.evolve:
|
||||||
|
model_path = str(best if best.exists() else last)
|
||||||
|
name = Path(model_path).name
|
||||||
|
if self.save_model:
|
||||||
|
self.experiment.log_model(
|
||||||
|
self.model_name,
|
||||||
|
file_or_folder=model_path,
|
||||||
|
file_name=name,
|
||||||
|
overwrite=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if running Experiment with Comet Optimizer
|
||||||
|
if hasattr(self.opt, 'comet_optimizer_id'):
|
||||||
|
metric = results.get(self.opt.comet_optimizer_metric)
|
||||||
|
self.experiment.log_other('optimizer_metric_value', metric)
|
||||||
|
|
||||||
|
self.finish_run()
|
||||||
|
|
||||||
|
def on_val_start(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_val_batch_start(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_val_batch_end(self, batch_i, images, targets, paths, shapes, outputs):
|
||||||
|
if not (self.comet_log_predictions and ((batch_i + 1) % self.comet_log_prediction_interval == 0)):
|
||||||
|
return
|
||||||
|
|
||||||
|
for si, pred in enumerate(outputs):
|
||||||
|
if len(pred) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
image = images[si]
|
||||||
|
labels = targets[targets[:, 0] == si, 1:]
|
||||||
|
shape = shapes[si]
|
||||||
|
path = paths[si]
|
||||||
|
predn, labelsn = self.preprocess_prediction(image, labels, shape, pred)
|
||||||
|
if labelsn is not None:
|
||||||
|
self.log_predictions(image, labelsn, path, shape, predn)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_val_end(self, nt, tp, fp, p, r, f1, ap, ap50, ap_class, confusion_matrix):
|
||||||
|
if self.comet_log_per_class_metrics:
|
||||||
|
if self.num_classes > 1:
|
||||||
|
for i, c in enumerate(ap_class):
|
||||||
|
class_name = self.class_names[c]
|
||||||
|
self.experiment.log_metrics(
|
||||||
|
{
|
||||||
|
'mAP@.5': ap50[i],
|
||||||
|
'mAP@.5:.95': ap[i],
|
||||||
|
'precision': p[i],
|
||||||
|
'recall': r[i],
|
||||||
|
'f1': f1[i],
|
||||||
|
'true_positives': tp[i],
|
||||||
|
'false_positives': fp[i],
|
||||||
|
'support': nt[c]},
|
||||||
|
prefix=class_name)
|
||||||
|
|
||||||
|
if self.comet_log_confusion_matrix:
|
||||||
|
epoch = self.experiment.curr_epoch
|
||||||
|
class_names = list(self.class_names.values())
|
||||||
|
class_names.append('background')
|
||||||
|
num_classes = len(class_names)
|
||||||
|
|
||||||
|
self.experiment.log_confusion_matrix(
|
||||||
|
matrix=confusion_matrix.matrix,
|
||||||
|
max_categories=num_classes,
|
||||||
|
labels=class_names,
|
||||||
|
epoch=epoch,
|
||||||
|
column_label='Actual Category',
|
||||||
|
row_label='Predicted Category',
|
||||||
|
file_name=f'confusion-matrix-epoch-{epoch}.json',
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_fit_epoch_end(self, result, epoch):
|
||||||
|
self.log_metrics(result, epoch=epoch)
|
||||||
|
|
||||||
|
def on_model_save(self, last, epoch, final_epoch, best_fitness, fi):
|
||||||
|
if ((epoch + 1) % self.opt.save_period == 0 and not final_epoch) and self.opt.save_period != -1:
|
||||||
|
self.log_model(last.parent, self.opt, epoch, fi, best_model=best_fitness == fi)
|
||||||
|
|
||||||
|
def on_params_update(self, params):
|
||||||
|
self.log_parameters(params)
|
||||||
|
|
||||||
|
def finish_run(self):
|
||||||
|
self.experiment.end()
|
||||||
150
ros2_ws/src/yolov3_ros/utils/loggers/comet/comet_utils.py
Normal file
150
ros2_ws/src/yolov3_ros/utils/loggers/comet/comet_utils.py
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
try:
|
||||||
|
import comet_ml
|
||||||
|
except (ModuleNotFoundError, ImportError):
|
||||||
|
comet_ml = None
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
COMET_PREFIX = 'comet://'
|
||||||
|
COMET_MODEL_NAME = os.getenv('COMET_MODEL_NAME', 'yolov5')
|
||||||
|
COMET_DEFAULT_CHECKPOINT_FILENAME = os.getenv('COMET_DEFAULT_CHECKPOINT_FILENAME', 'last.pt')
|
||||||
|
|
||||||
|
|
||||||
|
def download_model_checkpoint(opt, experiment):
|
||||||
|
model_dir = f'{opt.project}/{experiment.name}'
|
||||||
|
os.makedirs(model_dir, exist_ok=True)
|
||||||
|
|
||||||
|
model_name = COMET_MODEL_NAME
|
||||||
|
model_asset_list = experiment.get_model_asset_list(model_name)
|
||||||
|
|
||||||
|
if len(model_asset_list) == 0:
|
||||||
|
logger.error(f'COMET ERROR: No checkpoints found for model name : {model_name}')
|
||||||
|
return
|
||||||
|
|
||||||
|
model_asset_list = sorted(
|
||||||
|
model_asset_list,
|
||||||
|
key=lambda x: x['step'],
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
logged_checkpoint_map = {asset['fileName']: asset['assetId'] for asset in model_asset_list}
|
||||||
|
|
||||||
|
resource_url = urlparse(opt.weights)
|
||||||
|
checkpoint_filename = resource_url.query
|
||||||
|
|
||||||
|
if checkpoint_filename:
|
||||||
|
asset_id = logged_checkpoint_map.get(checkpoint_filename)
|
||||||
|
else:
|
||||||
|
asset_id = logged_checkpoint_map.get(COMET_DEFAULT_CHECKPOINT_FILENAME)
|
||||||
|
checkpoint_filename = COMET_DEFAULT_CHECKPOINT_FILENAME
|
||||||
|
|
||||||
|
if asset_id is None:
|
||||||
|
logger.error(f'COMET ERROR: Checkpoint {checkpoint_filename} not found in the given Experiment')
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f'COMET INFO: Downloading checkpoint {checkpoint_filename}')
|
||||||
|
asset_filename = checkpoint_filename
|
||||||
|
|
||||||
|
model_binary = experiment.get_asset(asset_id, return_type='binary', stream=False)
|
||||||
|
model_download_path = f'{model_dir}/{asset_filename}'
|
||||||
|
with open(model_download_path, 'wb') as f:
|
||||||
|
f.write(model_binary)
|
||||||
|
|
||||||
|
opt.weights = model_download_path
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning('COMET WARNING: Unable to download checkpoint from Comet')
|
||||||
|
logger.exception(e)
|
||||||
|
|
||||||
|
|
||||||
|
def set_opt_parameters(opt, experiment):
|
||||||
|
"""Update the opts Namespace with parameters
|
||||||
|
from Comet's ExistingExperiment when resuming a run
|
||||||
|
|
||||||
|
Args:
|
||||||
|
opt (argparse.Namespace): Namespace of command line options
|
||||||
|
experiment (comet_ml.APIExperiment): Comet API Experiment object
|
||||||
|
"""
|
||||||
|
asset_list = experiment.get_asset_list()
|
||||||
|
resume_string = opt.resume
|
||||||
|
|
||||||
|
for asset in asset_list:
|
||||||
|
if asset['fileName'] == 'opt.yaml':
|
||||||
|
asset_id = asset['assetId']
|
||||||
|
asset_binary = experiment.get_asset(asset_id, return_type='binary', stream=False)
|
||||||
|
opt_dict = yaml.safe_load(asset_binary)
|
||||||
|
for key, value in opt_dict.items():
|
||||||
|
setattr(opt, key, value)
|
||||||
|
opt.resume = resume_string
|
||||||
|
|
||||||
|
# Save hyperparameters to YAML file
|
||||||
|
# Necessary to pass checks in training script
|
||||||
|
save_dir = f'{opt.project}/{experiment.name}'
|
||||||
|
os.makedirs(save_dir, exist_ok=True)
|
||||||
|
|
||||||
|
hyp_yaml_path = f'{save_dir}/hyp.yaml'
|
||||||
|
with open(hyp_yaml_path, 'w') as f:
|
||||||
|
yaml.dump(opt.hyp, f)
|
||||||
|
opt.hyp = hyp_yaml_path
|
||||||
|
|
||||||
|
|
||||||
|
def check_comet_weights(opt):
|
||||||
|
"""Downloads model weights from Comet and updates the
|
||||||
|
weights path to point to saved weights location
|
||||||
|
|
||||||
|
Args:
|
||||||
|
opt (argparse.Namespace): Command Line arguments passed
|
||||||
|
to YOLOv5 training script
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None/bool: Return True if weights are successfully downloaded
|
||||||
|
else return None
|
||||||
|
"""
|
||||||
|
if comet_ml is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(opt.weights, str):
|
||||||
|
if opt.weights.startswith(COMET_PREFIX):
|
||||||
|
api = comet_ml.API()
|
||||||
|
resource = urlparse(opt.weights)
|
||||||
|
experiment_path = f'{resource.netloc}{resource.path}'
|
||||||
|
experiment = api.get(experiment_path)
|
||||||
|
download_model_checkpoint(opt, experiment)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def check_comet_resume(opt):
|
||||||
|
"""Restores run parameters to its original state based on the model checkpoint
|
||||||
|
and logged Experiment parameters.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
opt (argparse.Namespace): Command Line arguments passed
|
||||||
|
to YOLOv5 training script
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None/bool: Return True if the run is restored successfully
|
||||||
|
else return None
|
||||||
|
"""
|
||||||
|
if comet_ml is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(opt.resume, str):
|
||||||
|
if opt.resume.startswith(COMET_PREFIX):
|
||||||
|
api = comet_ml.API()
|
||||||
|
resource = urlparse(opt.resume)
|
||||||
|
experiment_path = f'{resource.netloc}{resource.path}'
|
||||||
|
experiment = api.get(experiment_path)
|
||||||
|
set_opt_parameters(opt, experiment)
|
||||||
|
download_model_checkpoint(opt, experiment)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return None
|
||||||
118
ros2_ws/src/yolov3_ros/utils/loggers/comet/hpo.py
Normal file
118
ros2_ws/src/yolov3_ros/utils/loggers/comet/hpo.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import comet_ml
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
FILE = Path(__file__).resolve()
|
||||||
|
ROOT = FILE.parents[3] # YOLOv5 root directory
|
||||||
|
if str(ROOT) not in sys.path:
|
||||||
|
sys.path.append(str(ROOT)) # add ROOT to PATH
|
||||||
|
|
||||||
|
from train import train
|
||||||
|
from utils.callbacks import Callbacks
|
||||||
|
from utils.general import increment_path
|
||||||
|
from utils.torch_utils import select_device
|
||||||
|
|
||||||
|
# Project Configuration
|
||||||
|
config = comet_ml.config.get_config()
|
||||||
|
COMET_PROJECT_NAME = config.get_string(os.getenv('COMET_PROJECT_NAME'), 'comet.project_name', default='yolov5')
|
||||||
|
|
||||||
|
|
||||||
|
def get_args(known=False):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--weights', type=str, default=ROOT / 'yolov5s.pt', help='initial weights path')
|
||||||
|
parser.add_argument('--cfg', type=str, default='', help='model.yaml path')
|
||||||
|
parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='dataset.yaml path')
|
||||||
|
parser.add_argument('--hyp', type=str, default=ROOT / 'data/hyps/hyp.scratch-low.yaml', help='hyperparameters path')
|
||||||
|
parser.add_argument('--epochs', type=int, default=300, help='total training epochs')
|
||||||
|
parser.add_argument('--batch-size', type=int, default=16, help='total batch size for all GPUs, -1 for autobatch')
|
||||||
|
parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='train, val image size (pixels)')
|
||||||
|
parser.add_argument('--rect', action='store_true', help='rectangular training')
|
||||||
|
parser.add_argument('--resume', nargs='?', const=True, default=False, help='resume most recent training')
|
||||||
|
parser.add_argument('--nosave', action='store_true', help='only save final checkpoint')
|
||||||
|
parser.add_argument('--noval', action='store_true', help='only validate final epoch')
|
||||||
|
parser.add_argument('--noautoanchor', action='store_true', help='disable AutoAnchor')
|
||||||
|
parser.add_argument('--noplots', action='store_true', help='save no plot files')
|
||||||
|
parser.add_argument('--evolve', type=int, nargs='?', const=300, help='evolve hyperparameters for x generations')
|
||||||
|
parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')
|
||||||
|
parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"')
|
||||||
|
parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training')
|
||||||
|
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
|
||||||
|
parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%')
|
||||||
|
parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class')
|
||||||
|
parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='SGD', help='optimizer')
|
||||||
|
parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode')
|
||||||
|
parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)')
|
||||||
|
parser.add_argument('--project', default=ROOT / 'runs/train', help='save to project/name')
|
||||||
|
parser.add_argument('--name', default='exp', help='save to project/name')
|
||||||
|
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
|
||||||
|
parser.add_argument('--quad', action='store_true', help='quad dataloader')
|
||||||
|
parser.add_argument('--cos-lr', action='store_true', help='cosine LR scheduler')
|
||||||
|
parser.add_argument('--label-smoothing', type=float, default=0.0, help='Label smoothing epsilon')
|
||||||
|
parser.add_argument('--patience', type=int, default=100, help='EarlyStopping patience (epochs without improvement)')
|
||||||
|
parser.add_argument('--freeze', nargs='+', type=int, default=[0], help='Freeze layers: backbone=10, first3=0 1 2')
|
||||||
|
parser.add_argument('--save-period', type=int, default=-1, help='Save checkpoint every x epochs (disabled if < 1)')
|
||||||
|
parser.add_argument('--seed', type=int, default=0, help='Global training seed')
|
||||||
|
parser.add_argument('--local_rank', type=int, default=-1, help='Automatic DDP Multi-GPU argument, do not modify')
|
||||||
|
|
||||||
|
# Weights & Biases arguments
|
||||||
|
parser.add_argument('--entity', default=None, help='W&B: Entity')
|
||||||
|
parser.add_argument('--upload_dataset', nargs='?', const=True, default=False, help='W&B: Upload data, "val" option')
|
||||||
|
parser.add_argument('--bbox_interval', type=int, default=-1, help='W&B: Set bounding-box image logging interval')
|
||||||
|
parser.add_argument('--artifact_alias', type=str, default='latest', help='W&B: Version of dataset artifact to use')
|
||||||
|
|
||||||
|
# Comet Arguments
|
||||||
|
parser.add_argument('--comet_optimizer_config', type=str, help='Comet: Path to a Comet Optimizer Config File.')
|
||||||
|
parser.add_argument('--comet_optimizer_id', type=str, help='Comet: ID of the Comet Optimizer sweep.')
|
||||||
|
parser.add_argument('--comet_optimizer_objective', type=str, help="Comet: Set to 'minimize' or 'maximize'.")
|
||||||
|
parser.add_argument('--comet_optimizer_metric', type=str, help='Comet: Metric to Optimize.')
|
||||||
|
parser.add_argument('--comet_optimizer_workers',
|
||||||
|
type=int,
|
||||||
|
default=1,
|
||||||
|
help='Comet: Number of Parallel Workers to use with the Comet Optimizer.')
|
||||||
|
|
||||||
|
return parser.parse_known_args()[0] if known else parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def run(parameters, opt):
|
||||||
|
hyp_dict = {k: v for k, v in parameters.items() if k not in ['epochs', 'batch_size']}
|
||||||
|
|
||||||
|
opt.save_dir = str(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok or opt.evolve))
|
||||||
|
opt.batch_size = parameters.get('batch_size')
|
||||||
|
opt.epochs = parameters.get('epochs')
|
||||||
|
|
||||||
|
device = select_device(opt.device, batch_size=opt.batch_size)
|
||||||
|
train(hyp_dict, opt, device, callbacks=Callbacks())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
opt = get_args(known=True)
|
||||||
|
|
||||||
|
opt.weights = str(opt.weights)
|
||||||
|
opt.cfg = str(opt.cfg)
|
||||||
|
opt.data = str(opt.data)
|
||||||
|
opt.project = str(opt.project)
|
||||||
|
|
||||||
|
optimizer_id = os.getenv('COMET_OPTIMIZER_ID')
|
||||||
|
if optimizer_id is None:
|
||||||
|
with open(opt.comet_optimizer_config) as f:
|
||||||
|
optimizer_config = json.load(f)
|
||||||
|
optimizer = comet_ml.Optimizer(optimizer_config)
|
||||||
|
else:
|
||||||
|
optimizer = comet_ml.Optimizer(optimizer_id)
|
||||||
|
|
||||||
|
opt.comet_optimizer_id = optimizer.id
|
||||||
|
status = optimizer.status()
|
||||||
|
|
||||||
|
opt.comet_optimizer_objective = status['spec']['objective']
|
||||||
|
opt.comet_optimizer_metric = status['spec']['metric']
|
||||||
|
|
||||||
|
logger.info('COMET INFO: Starting Hyperparameter Sweep')
|
||||||
|
for parameter in optimizer.get_parameters():
|
||||||
|
run(parameter['parameters'], opt)
|
||||||
209
ros2_ws/src/yolov3_ros/utils/loggers/comet/optimizer_config.json
Normal file
209
ros2_ws/src/yolov3_ros/utils/loggers/comet/optimizer_config.json
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
{
|
||||||
|
"algorithm": "random",
|
||||||
|
"parameters": {
|
||||||
|
"anchor_t": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
2,
|
||||||
|
8
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"batch_size": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
16,
|
||||||
|
32,
|
||||||
|
64
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"box": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0.02,
|
||||||
|
0.2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cls": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0.2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cls_pw": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0.5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"copy_paste": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"degrees": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0,
|
||||||
|
45
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"epochs": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fl_gamma": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fliplr": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"flipud": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hsv_h": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hsv_s": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hsv_v": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"iou_t": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0.7
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lr0": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
1e-05,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lrf": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0.01,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mixup": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"momentum": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0.6
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mosaic": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"obj": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0.2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"obj_pw": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0.5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"optimizer": {
|
||||||
|
"type": "categorical",
|
||||||
|
"values": [
|
||||||
|
"SGD",
|
||||||
|
"Adam",
|
||||||
|
"AdamW"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"perspective": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"shear": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"translate": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"warmup_bias_lr": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0,
|
||||||
|
0.2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"warmup_epochs": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"warmup_momentum": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0,
|
||||||
|
0.95
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"weight_decay": {
|
||||||
|
"type": "discrete",
|
||||||
|
"values": [
|
||||||
|
0,
|
||||||
|
0.001
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"maxCombo": 0,
|
||||||
|
"metric": "metrics/mAP_0.5",
|
||||||
|
"objective": "maximize"
|
||||||
|
},
|
||||||
|
"trials": 1
|
||||||
|
}
|
||||||
147
ros2_ws/src/yolov3_ros/utils/loggers/wandb/README.md
Normal file
147
ros2_ws/src/yolov3_ros/utils/loggers/wandb/README.md
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
📚 This guide explains how to use **Weights & Biases** (W&B) with YOLOv3 🚀. UPDATED 29 September 2021.
|
||||||
|
* [About Weights & Biases](#about-weights-&-biases)
|
||||||
|
* [First-Time Setup](#first-time-setup)
|
||||||
|
* [Viewing runs](#viewing-runs)
|
||||||
|
* [Advanced Usage: Dataset Versioning and Evaluation](#advanced-usage)
|
||||||
|
* [Reports: Share your work with the world!](#reports)
|
||||||
|
|
||||||
|
## About Weights & Biases
|
||||||
|
Think of [W&B](https://wandb.ai/site?utm_campaign=repo_yolo_wandbtutorial) like GitHub for machine learning models. With a few lines of code, save everything you need to debug, compare and reproduce your models — architecture, hyperparameters, git commits, model weights, GPU usage, and even datasets and predictions.
|
||||||
|
|
||||||
|
Used by top researchers including teams at OpenAI, Lyft, Github, and MILA, W&B is part of the new standard of best practices for machine learning. How W&B can help you optimize your machine learning workflows:
|
||||||
|
|
||||||
|
* [Debug](https://wandb.ai/wandb/getting-started/reports/Visualize-Debug-Machine-Learning-Models--VmlldzoyNzY5MDk#Free-2) model performance in real time
|
||||||
|
* [GPU usage](https://wandb.ai/wandb/getting-started/reports/Visualize-Debug-Machine-Learning-Models--VmlldzoyNzY5MDk#System-4) visualized automatically
|
||||||
|
* [Custom charts](https://wandb.ai/wandb/customizable-charts/reports/Powerful-Custom-Charts-To-Debug-Model-Peformance--VmlldzoyNzY4ODI) for powerful, extensible visualization
|
||||||
|
* [Share insights](https://wandb.ai/wandb/getting-started/reports/Visualize-Debug-Machine-Learning-Models--VmlldzoyNzY5MDk#Share-8) interactively with collaborators
|
||||||
|
* [Optimize hyperparameters](https://docs.wandb.com/sweeps) efficiently
|
||||||
|
* [Track](https://docs.wandb.com/artifacts) datasets, pipelines, and production models
|
||||||
|
|
||||||
|
## First-Time Setup
|
||||||
|
<details open>
|
||||||
|
<summary> Toggle Details </summary>
|
||||||
|
When you first train, W&B will prompt you to create a new account and will generate an **API key** for you. If you are an existing user you can retrieve your key from https://wandb.ai/authorize. This key is used to tell W&B where to log your data. You only need to supply your key once, and then it is remembered on the same device.
|
||||||
|
|
||||||
|
W&B will create a cloud **project** (default is 'YOLOv3') for your training runs, and each new training run will be provided a unique run **name** within that project as project/name. You can also manually set your project and run name as:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ python train.py --project ... --name ...
|
||||||
|
```
|
||||||
|
|
||||||
|
YOLOv3 notebook example: <a href="https://colab.research.google.com/github/ultralytics/yolov3/blob/master/tutorial.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"></a> <a href="https://www.kaggle.com/ultralytics/yolov3"><img src="https://kaggle.com/static/images/open-in-kaggle.svg" alt="Open In Kaggle"></a>
|
||||||
|
<img width="960" alt="Screen Shot 2021-09-29 at 10 23 13 PM" src="https://user-images.githubusercontent.com/26833433/135392431-1ab7920a-c49d-450a-b0b0-0c86ec86100e.png">
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Viewing Runs
|
||||||
|
<details open>
|
||||||
|
<summary> Toggle Details </summary>
|
||||||
|
Run information streams from your environment to the W&B cloud console as you train. This allows you to monitor and even cancel runs in <b>realtime</b> . All important information is logged:
|
||||||
|
|
||||||
|
* Training & Validation losses
|
||||||
|
* Metrics: Precision, Recall, mAP@0.5, mAP@0.5:0.95
|
||||||
|
* Learning Rate over time
|
||||||
|
* A bounding box debugging panel, showing the training progress over time
|
||||||
|
* GPU: Type, **GPU Utilization**, power, temperature, **CUDA memory usage**
|
||||||
|
* System: Disk I/0, CPU utilization, RAM memory usage
|
||||||
|
* Your trained model as W&B Artifact
|
||||||
|
* Environment: OS and Python types, Git repository and state, **training command**
|
||||||
|
|
||||||
|
<p align="center"><img width="900" alt="Weights & Biases dashboard" src="https://user-images.githubusercontent.com/26833433/135390767-c28b050f-8455-4004-adb0-3b730386e2b2.png"></p>
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
You can leverage W&B artifacts and Tables integration to easily visualize and manage your datasets, models and training evaluations. Here are some quick examples to get you started.
|
||||||
|
<details open>
|
||||||
|
<h3>1. Visualize and Version Datasets</h3>
|
||||||
|
Log, visualize, dynamically query, and understand your data with <a href='https://docs.wandb.ai/guides/data-vis/tables'>W&B Tables</a>. You can use the following command to log your dataset as a W&B Table. This will generate a <code>{dataset}_wandb.yaml</code> file which can be used to train from dataset artifact.
|
||||||
|
<details>
|
||||||
|
<summary> <b>Usage</b> </summary>
|
||||||
|
<b>Code</b> <code> $ python utils/logger/wandb/log_dataset.py --project ... --name ... --data .. </code>
|
||||||
|
|
||||||
|

|
||||||
|
</details>
|
||||||
|
|
||||||
|
<h3> 2: Train and Log Evaluation simultaneousy </h3>
|
||||||
|
This is an extension of the previous section, but it'll also training after uploading the dataset. <b> This also evaluation Table</b>
|
||||||
|
Evaluation table compares your predictions and ground truths across the validation set for each epoch. It uses the references to the already uploaded datasets,
|
||||||
|
so no images will be uploaded from your system more than once.
|
||||||
|
<details>
|
||||||
|
<summary> <b>Usage</b> </summary>
|
||||||
|
<b>Code</b> <code> $ python utils/logger/wandb/log_dataset.py --data .. --upload_data </code>
|
||||||
|
|
||||||
|

|
||||||
|
</details>
|
||||||
|
|
||||||
|
<h3> 3: Train using dataset artifact </h3>
|
||||||
|
When you upload a dataset as described in the first section, you get a new config file with an added `_wandb` to its name. This file contains the information that
|
||||||
|
can be used to train a model directly from the dataset artifact. <b> This also logs evaluation </b>
|
||||||
|
<details>
|
||||||
|
<summary> <b>Usage</b> </summary>
|
||||||
|
<b>Code</b> <code> $ python utils/logger/wandb/log_dataset.py --data {data}_wandb.yaml </code>
|
||||||
|
|
||||||
|

|
||||||
|
</details>
|
||||||
|
|
||||||
|
<h3> 4: Save model checkpoints as artifacts </h3>
|
||||||
|
To enable saving and versioning checkpoints of your experiment, pass `--save_period n` with the base cammand, where `n` represents checkpoint interval.
|
||||||
|
You can also log both the dataset and model checkpoints simultaneously. If not passed, only the final model will be logged
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> <b>Usage</b> </summary>
|
||||||
|
<b>Code</b> <code> $ python train.py --save_period 1 </code>
|
||||||
|
|
||||||
|

|
||||||
|
</details>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<h3> 5: Resume runs from checkpoint artifacts. </h3>
|
||||||
|
Any run can be resumed using artifacts if the <code>--resume</code> argument starts with <code>wandb-artifact://</code> prefix followed by the run path, i.e, <code>wandb-artifact://username/project/runid </code>. This doesn't require the model checkpoint to be present on the local system.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> <b>Usage</b> </summary>
|
||||||
|
<b>Code</b> <code> $ python train.py --resume wandb-artifact://{run_path} </code>
|
||||||
|
|
||||||
|

|
||||||
|
</details>
|
||||||
|
|
||||||
|
<h3> 6: Resume runs from dataset artifact & checkpoint artifacts. </h3>
|
||||||
|
<b> Local dataset or model checkpoints are not required. This can be used to resume runs directly on a different device </b>
|
||||||
|
The syntax is same as the previous section, but you'll need to lof both the dataset and model checkpoints as artifacts, i.e, set bot <code>--upload_dataset</code> or
|
||||||
|
train from <code>_wandb.yaml</code> file and set <code>--save_period</code>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> <b>Usage</b> </summary>
|
||||||
|
<b>Code</b> <code> $ python train.py --resume wandb-artifact://{run_path} </code>
|
||||||
|
|
||||||
|

|
||||||
|
</details>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
<h3> Reports </h3>
|
||||||
|
W&B Reports can be created from your saved runs for sharing online. Once a report is created you will receive a link you can use to publically share your results. Here is an example report created from the COCO128 tutorial trainings of all YOLOv5 models ([link](https://wandb.ai/glenn-jocher/yolov5_tutorial/reports/YOLOv5-COCO128-Tutorial-Results--VmlldzozMDI5OTY)).
|
||||||
|
|
||||||
|
<img width="900" alt="Weights & Biases Reports" src="https://user-images.githubusercontent.com/26833433/135394029-a17eaf86-c6c1-4b1d-bb80-b90e83aaffa7.png">
|
||||||
|
|
||||||
|
|
||||||
|
## Environments
|
||||||
|
|
||||||
|
YOLOv3 may be run in any of the following up-to-date verified environments (with all dependencies including [CUDA](https://developer.nvidia.com/cuda)/[CUDNN](https://developer.nvidia.com/cudnn), [Python](https://www.python.org/) and [PyTorch](https://pytorch.org/) preinstalled):
|
||||||
|
|
||||||
|
- **Google Colab and Kaggle** notebooks with free GPU: <a href="https://colab.research.google.com/github/ultralytics/yolov3/blob/master/tutorial.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"></a> <a href="https://www.kaggle.com/ultralytics/yolov3"><img src="https://kaggle.com/static/images/open-in-kaggle.svg" alt="Open In Kaggle"></a>
|
||||||
|
- **Google Cloud** Deep Learning VM. See [GCP Quickstart Guide](https://github.com/ultralytics/yolov3/wiki/GCP-Quickstart)
|
||||||
|
- **Amazon** Deep Learning AMI. See [AWS Quickstart Guide](https://github.com/ultralytics/yolov3/wiki/AWS-Quickstart)
|
||||||
|
- **Docker Image**. See [Docker Quickstart Guide](https://github.com/ultralytics/yolov3/wiki/Docker-Quickstart) <a href="https://hub.docker.com/r/ultralytics/yolov3"><img src="https://img.shields.io/docker/pulls/ultralytics/yolov3?logo=docker" alt="Docker Pulls"></a>
|
||||||
|
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
If this badge is green, all [YOLOv3 GitHub Actions](https://github.com/ultralytics/yolov3/actions) Continuous Integration (CI) tests are currently passing. CI tests verify correct operation of YOLOv3 training ([train.py](https://github.com/ultralytics/yolov3/blob/master/train.py)), validation ([val.py](https://github.com/ultralytics/yolov3/blob/master/val.py)), inference ([detect.py](https://github.com/ultralytics/yolov3/blob/master/detect.py)) and export ([export.py](https://github.com/ultralytics/yolov3/blob/master/export.py)) on MacOS, Windows, and Ubuntu every 24 hours and on every commit.
|
||||||
27
ros2_ws/src/yolov3_ros/utils/loggers/wandb/log_dataset.py
Normal file
27
ros2_ws/src/yolov3_ros/utils/loggers/wandb/log_dataset.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import argparse
|
||||||
|
|
||||||
|
from wandb_utils import WandbLogger
|
||||||
|
|
||||||
|
from utils.general import LOGGER
|
||||||
|
|
||||||
|
WANDB_ARTIFACT_PREFIX = 'wandb-artifact://'
|
||||||
|
|
||||||
|
|
||||||
|
def create_dataset_artifact(opt):
|
||||||
|
logger = WandbLogger(opt, None, job_type='Dataset Creation') # TODO: return value unused
|
||||||
|
if not logger.wandb:
|
||||||
|
LOGGER.info("install wandb using `pip install wandb` to log the dataset")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--data', type=str, default='data/coco128.yaml', help='data.yaml path')
|
||||||
|
parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset')
|
||||||
|
parser.add_argument('--project', type=str, default='YOLOv3', help='name of W&B Project')
|
||||||
|
parser.add_argument('--entity', default=None, help='W&B entity')
|
||||||
|
parser.add_argument('--name', type=str, default='log dataset', help='name of W&B run')
|
||||||
|
|
||||||
|
opt = parser.parse_args()
|
||||||
|
opt.resume = False # Explicitly disallow resume check for dataset upload job
|
||||||
|
|
||||||
|
create_dataset_artifact(opt)
|
||||||
41
ros2_ws/src/yolov3_ros/utils/loggers/wandb/sweep.py
Normal file
41
ros2_ws/src/yolov3_ros/utils/loggers/wandb/sweep.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import wandb
|
||||||
|
|
||||||
|
FILE = Path(__file__).resolve()
|
||||||
|
ROOT = FILE.parents[3] # root directory
|
||||||
|
if str(ROOT) not in sys.path:
|
||||||
|
sys.path.append(str(ROOT)) # add ROOT to PATH
|
||||||
|
|
||||||
|
from train import parse_opt, train
|
||||||
|
from utils.callbacks import Callbacks
|
||||||
|
from utils.general import increment_path
|
||||||
|
from utils.torch_utils import select_device
|
||||||
|
|
||||||
|
|
||||||
|
def sweep():
|
||||||
|
wandb.init()
|
||||||
|
# Get hyp dict from sweep agent
|
||||||
|
hyp_dict = vars(wandb.config).get("_items")
|
||||||
|
|
||||||
|
# Workaround: get necessary opt args
|
||||||
|
opt = parse_opt(known=True)
|
||||||
|
opt.batch_size = hyp_dict.get("batch_size")
|
||||||
|
opt.save_dir = str(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok or opt.evolve))
|
||||||
|
opt.epochs = hyp_dict.get("epochs")
|
||||||
|
opt.nosave = True
|
||||||
|
opt.data = hyp_dict.get("data")
|
||||||
|
opt.weights = str(opt.weights)
|
||||||
|
opt.cfg = str(opt.cfg)
|
||||||
|
opt.data = str(opt.data)
|
||||||
|
opt.hyp = str(opt.hyp)
|
||||||
|
opt.project = str(opt.project)
|
||||||
|
device = select_device(opt.device, batch_size=opt.batch_size)
|
||||||
|
|
||||||
|
# train
|
||||||
|
train(hyp_dict, opt, device, callbacks=Callbacks())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sweep()
|
||||||
143
ros2_ws/src/yolov3_ros/utils/loggers/wandb/sweep.yaml
Normal file
143
ros2_ws/src/yolov3_ros/utils/loggers/wandb/sweep.yaml
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
# Hyperparameters for training
|
||||||
|
# To set range-
|
||||||
|
# Provide min and max values as:
|
||||||
|
# parameter:
|
||||||
|
#
|
||||||
|
# min: scalar
|
||||||
|
# max: scalar
|
||||||
|
# OR
|
||||||
|
#
|
||||||
|
# Set a specific list of search space-
|
||||||
|
# parameter:
|
||||||
|
# values: [scalar1, scalar2, scalar3...]
|
||||||
|
#
|
||||||
|
# You can use grid, bayesian and hyperopt search strategy
|
||||||
|
# For more info on configuring sweeps visit - https://docs.wandb.ai/guides/sweeps/configuration
|
||||||
|
|
||||||
|
program: utils/loggers/wandb/sweep.py
|
||||||
|
method: random
|
||||||
|
metric:
|
||||||
|
name: metrics/mAP_0.5
|
||||||
|
goal: maximize
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
# hyperparameters: set either min, max range or values list
|
||||||
|
data:
|
||||||
|
value: "data/coco128.yaml"
|
||||||
|
batch_size:
|
||||||
|
values: [64]
|
||||||
|
epochs:
|
||||||
|
values: [10]
|
||||||
|
|
||||||
|
lr0:
|
||||||
|
distribution: uniform
|
||||||
|
min: 1e-5
|
||||||
|
max: 1e-1
|
||||||
|
lrf:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.01
|
||||||
|
max: 1.0
|
||||||
|
momentum:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.6
|
||||||
|
max: 0.98
|
||||||
|
weight_decay:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 0.001
|
||||||
|
warmup_epochs:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 5.0
|
||||||
|
warmup_momentum:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 0.95
|
||||||
|
warmup_bias_lr:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 0.2
|
||||||
|
box:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.02
|
||||||
|
max: 0.2
|
||||||
|
cls:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.2
|
||||||
|
max: 4.0
|
||||||
|
cls_pw:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.5
|
||||||
|
max: 2.0
|
||||||
|
obj:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.2
|
||||||
|
max: 4.0
|
||||||
|
obj_pw:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.5
|
||||||
|
max: 2.0
|
||||||
|
iou_t:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.1
|
||||||
|
max: 0.7
|
||||||
|
anchor_t:
|
||||||
|
distribution: uniform
|
||||||
|
min: 2.0
|
||||||
|
max: 8.0
|
||||||
|
fl_gamma:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 0.1
|
||||||
|
hsv_h:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 0.1
|
||||||
|
hsv_s:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 0.9
|
||||||
|
hsv_v:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 0.9
|
||||||
|
degrees:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 45.0
|
||||||
|
translate:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 0.9
|
||||||
|
scale:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 0.9
|
||||||
|
shear:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 10.0
|
||||||
|
perspective:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 0.001
|
||||||
|
flipud:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 1.0
|
||||||
|
fliplr:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 1.0
|
||||||
|
mosaic:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 1.0
|
||||||
|
mixup:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 1.0
|
||||||
|
copy_paste:
|
||||||
|
distribution: uniform
|
||||||
|
min: 0.0
|
||||||
|
max: 1.0
|
||||||
532
ros2_ws/src/yolov3_ros/utils/loggers/wandb/wandb_utils.py
Normal file
532
ros2_ws/src/yolov3_ros/utils/loggers/wandb/wandb_utils.py
Normal file
@ -0,0 +1,532 @@
|
|||||||
|
"""Utilities and tools for tracking runs with Weights & Biases."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import pkg_resources as pkg
|
||||||
|
import yaml
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
FILE = Path(__file__).resolve()
|
||||||
|
ROOT = FILE.parents[3] # root directory
|
||||||
|
if str(ROOT) not in sys.path:
|
||||||
|
sys.path.append(str(ROOT)) # add ROOT to PATH
|
||||||
|
|
||||||
|
from utils.datasets import LoadImagesAndLabels, img2label_paths
|
||||||
|
from utils.general import LOGGER, check_dataset, check_file
|
||||||
|
|
||||||
|
try:
|
||||||
|
import wandb
|
||||||
|
|
||||||
|
assert hasattr(wandb, '__version__') # verify package import not local dir
|
||||||
|
except (ImportError, AssertionError):
|
||||||
|
wandb = None
|
||||||
|
|
||||||
|
RANK = int(os.getenv('RANK', -1))
|
||||||
|
WANDB_ARTIFACT_PREFIX = 'wandb-artifact://'
|
||||||
|
|
||||||
|
|
||||||
|
def remove_prefix(from_string, prefix=WANDB_ARTIFACT_PREFIX):
|
||||||
|
return from_string[len(prefix):]
|
||||||
|
|
||||||
|
|
||||||
|
def check_wandb_config_file(data_config_file):
|
||||||
|
wandb_config = '_wandb.'.join(data_config_file.rsplit('.', 1)) # updated data.yaml path
|
||||||
|
if Path(wandb_config).is_file():
|
||||||
|
return wandb_config
|
||||||
|
return data_config_file
|
||||||
|
|
||||||
|
|
||||||
|
def check_wandb_dataset(data_file):
|
||||||
|
is_trainset_wandb_artifact = False
|
||||||
|
is_valset_wandb_artifact = False
|
||||||
|
if check_file(data_file) and data_file.endswith('.yaml'):
|
||||||
|
with open(data_file, errors='ignore') as f:
|
||||||
|
data_dict = yaml.safe_load(f)
|
||||||
|
is_trainset_wandb_artifact = (isinstance(data_dict['train'], str) and
|
||||||
|
data_dict['train'].startswith(WANDB_ARTIFACT_PREFIX))
|
||||||
|
is_valset_wandb_artifact = (isinstance(data_dict['val'], str) and
|
||||||
|
data_dict['val'].startswith(WANDB_ARTIFACT_PREFIX))
|
||||||
|
if is_trainset_wandb_artifact or is_valset_wandb_artifact:
|
||||||
|
return data_dict
|
||||||
|
else:
|
||||||
|
return check_dataset(data_file)
|
||||||
|
|
||||||
|
|
||||||
|
def get_run_info(run_path):
|
||||||
|
run_path = Path(remove_prefix(run_path, WANDB_ARTIFACT_PREFIX))
|
||||||
|
run_id = run_path.stem
|
||||||
|
project = run_path.parent.stem
|
||||||
|
entity = run_path.parent.parent.stem
|
||||||
|
model_artifact_name = 'run_' + run_id + '_model'
|
||||||
|
return entity, project, run_id, model_artifact_name
|
||||||
|
|
||||||
|
|
||||||
|
def check_wandb_resume(opt):
|
||||||
|
process_wandb_config_ddp_mode(opt) if RANK not in [-1, 0] else None
|
||||||
|
if isinstance(opt.resume, str):
|
||||||
|
if opt.resume.startswith(WANDB_ARTIFACT_PREFIX):
|
||||||
|
if RANK not in [-1, 0]: # For resuming DDP runs
|
||||||
|
entity, project, run_id, model_artifact_name = get_run_info(opt.resume)
|
||||||
|
api = wandb.Api()
|
||||||
|
artifact = api.artifact(entity + '/' + project + '/' + model_artifact_name + ':latest')
|
||||||
|
modeldir = artifact.download()
|
||||||
|
opt.weights = str(Path(modeldir) / "last.pt")
|
||||||
|
return True
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def process_wandb_config_ddp_mode(opt):
|
||||||
|
with open(check_file(opt.data), errors='ignore') as f:
|
||||||
|
data_dict = yaml.safe_load(f) # data dict
|
||||||
|
train_dir, val_dir = None, None
|
||||||
|
if isinstance(data_dict['train'], str) and data_dict['train'].startswith(WANDB_ARTIFACT_PREFIX):
|
||||||
|
api = wandb.Api()
|
||||||
|
train_artifact = api.artifact(remove_prefix(data_dict['train']) + ':' + opt.artifact_alias)
|
||||||
|
train_dir = train_artifact.download()
|
||||||
|
train_path = Path(train_dir) / 'data/images/'
|
||||||
|
data_dict['train'] = str(train_path)
|
||||||
|
|
||||||
|
if isinstance(data_dict['val'], str) and data_dict['val'].startswith(WANDB_ARTIFACT_PREFIX):
|
||||||
|
api = wandb.Api()
|
||||||
|
val_artifact = api.artifact(remove_prefix(data_dict['val']) + ':' + opt.artifact_alias)
|
||||||
|
val_dir = val_artifact.download()
|
||||||
|
val_path = Path(val_dir) / 'data/images/'
|
||||||
|
data_dict['val'] = str(val_path)
|
||||||
|
if train_dir or val_dir:
|
||||||
|
ddp_data_path = str(Path(val_dir) / 'wandb_local_data.yaml')
|
||||||
|
with open(ddp_data_path, 'w') as f:
|
||||||
|
yaml.safe_dump(data_dict, f)
|
||||||
|
opt.data = ddp_data_path
|
||||||
|
|
||||||
|
|
||||||
|
class WandbLogger():
|
||||||
|
"""Log training runs, datasets, models, and predictions to Weights & Biases.
|
||||||
|
|
||||||
|
This logger sends information to W&B at wandb.ai. By default, this information
|
||||||
|
includes hyperparameters, system configuration and metrics, model metrics,
|
||||||
|
and basic data metrics and analyses.
|
||||||
|
|
||||||
|
By providing additional command line arguments to train.py, datasets,
|
||||||
|
models and predictions can also be logged.
|
||||||
|
|
||||||
|
For more on how this logger is used, see the Weights & Biases documentation:
|
||||||
|
https://docs.wandb.com/guides/integrations/yolov5
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, opt, run_id=None, job_type='Training'):
|
||||||
|
"""
|
||||||
|
- Initialize WandbLogger instance
|
||||||
|
- Upload dataset if opt.upload_dataset is True
|
||||||
|
- Setup trainig processes if job_type is 'Training'
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
opt (namespace) -- Commandline arguments for this run
|
||||||
|
run_id (str) -- Run ID of W&B run to be resumed
|
||||||
|
job_type (str) -- To set the job_type for this run
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Pre-training routine --
|
||||||
|
self.job_type = job_type
|
||||||
|
self.wandb, self.wandb_run = wandb, None if not wandb else wandb.run
|
||||||
|
self.val_artifact, self.train_artifact = None, None
|
||||||
|
self.train_artifact_path, self.val_artifact_path = None, None
|
||||||
|
self.result_artifact = None
|
||||||
|
self.val_table, self.result_table = None, None
|
||||||
|
self.bbox_media_panel_images = []
|
||||||
|
self.val_table_path_map = None
|
||||||
|
self.max_imgs_to_log = 16
|
||||||
|
self.wandb_artifact_data_dict = None
|
||||||
|
self.data_dict = None
|
||||||
|
# It's more elegant to stick to 1 wandb.init call,
|
||||||
|
# but useful config data is overwritten in the WandbLogger's wandb.init call
|
||||||
|
if isinstance(opt.resume, str): # checks resume from artifact
|
||||||
|
if opt.resume.startswith(WANDB_ARTIFACT_PREFIX):
|
||||||
|
entity, project, run_id, model_artifact_name = get_run_info(opt.resume)
|
||||||
|
model_artifact_name = WANDB_ARTIFACT_PREFIX + model_artifact_name
|
||||||
|
assert wandb, 'install wandb to resume wandb runs'
|
||||||
|
# Resume wandb-artifact:// runs here| workaround for not overwriting wandb.config
|
||||||
|
self.wandb_run = wandb.init(id=run_id,
|
||||||
|
project=project,
|
||||||
|
entity=entity,
|
||||||
|
resume='allow',
|
||||||
|
allow_val_change=True)
|
||||||
|
opt.resume = model_artifact_name
|
||||||
|
elif self.wandb:
|
||||||
|
self.wandb_run = wandb.init(config=opt,
|
||||||
|
resume="allow",
|
||||||
|
project='YOLOv3' if opt.project == 'runs/train' else Path(opt.project).stem,
|
||||||
|
entity=opt.entity,
|
||||||
|
name=opt.name if opt.name != 'exp' else None,
|
||||||
|
job_type=job_type,
|
||||||
|
id=run_id,
|
||||||
|
allow_val_change=True) if not wandb.run else wandb.run
|
||||||
|
if self.wandb_run:
|
||||||
|
if self.job_type == 'Training':
|
||||||
|
if opt.upload_dataset:
|
||||||
|
if not opt.resume:
|
||||||
|
self.wandb_artifact_data_dict = self.check_and_upload_dataset(opt)
|
||||||
|
|
||||||
|
if opt.resume:
|
||||||
|
# resume from artifact
|
||||||
|
if isinstance(opt.resume, str) and opt.resume.startswith(WANDB_ARTIFACT_PREFIX):
|
||||||
|
self.data_dict = dict(self.wandb_run.config.data_dict)
|
||||||
|
else: # local resume
|
||||||
|
self.data_dict = check_wandb_dataset(opt.data)
|
||||||
|
else:
|
||||||
|
self.data_dict = check_wandb_dataset(opt.data)
|
||||||
|
self.wandb_artifact_data_dict = self.wandb_artifact_data_dict or self.data_dict
|
||||||
|
|
||||||
|
# write data_dict to config. useful for resuming from artifacts. Do this only when not resuming.
|
||||||
|
self.wandb_run.config.update({'data_dict': self.wandb_artifact_data_dict},
|
||||||
|
allow_val_change=True)
|
||||||
|
self.setup_training(opt)
|
||||||
|
|
||||||
|
if self.job_type == 'Dataset Creation':
|
||||||
|
self.data_dict = self.check_and_upload_dataset(opt)
|
||||||
|
|
||||||
|
def check_and_upload_dataset(self, opt):
|
||||||
|
"""
|
||||||
|
Check if the dataset format is compatible and upload it as W&B artifact
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
opt (namespace)-- Commandline arguments for current run
|
||||||
|
|
||||||
|
returns:
|
||||||
|
Updated dataset info dictionary where local dataset paths are replaced by WAND_ARFACT_PREFIX links.
|
||||||
|
"""
|
||||||
|
assert wandb, 'Install wandb to upload dataset'
|
||||||
|
config_path = self.log_dataset_artifact(opt.data,
|
||||||
|
opt.single_cls,
|
||||||
|
'YOLOv3' if opt.project == 'runs/train' else Path(opt.project).stem)
|
||||||
|
LOGGER.info(f"Created dataset config file {config_path}")
|
||||||
|
with open(config_path, errors='ignore') as f:
|
||||||
|
wandb_data_dict = yaml.safe_load(f)
|
||||||
|
return wandb_data_dict
|
||||||
|
|
||||||
|
def setup_training(self, opt):
|
||||||
|
"""
|
||||||
|
Setup the necessary processes for training YOLO models:
|
||||||
|
- Attempt to download model checkpoint and dataset artifacts if opt.resume stats with WANDB_ARTIFACT_PREFIX
|
||||||
|
- Update data_dict, to contain info of previous run if resumed and the paths of dataset artifact if downloaded
|
||||||
|
- Setup log_dict, initialize bbox_interval
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
opt (namespace) -- commandline arguments for this run
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.log_dict, self.current_epoch = {}, 0
|
||||||
|
self.bbox_interval = opt.bbox_interval
|
||||||
|
if isinstance(opt.resume, str):
|
||||||
|
modeldir, _ = self.download_model_artifact(opt)
|
||||||
|
if modeldir:
|
||||||
|
self.weights = Path(modeldir) / "last.pt"
|
||||||
|
config = self.wandb_run.config
|
||||||
|
opt.weights, opt.save_period, opt.batch_size, opt.bbox_interval, opt.epochs, opt.hyp = str(
|
||||||
|
self.weights), config.save_period, config.batch_size, config.bbox_interval, config.epochs, \
|
||||||
|
config.hyp
|
||||||
|
data_dict = self.data_dict
|
||||||
|
if self.val_artifact is None: # If --upload_dataset is set, use the existing artifact, don't download
|
||||||
|
self.train_artifact_path, self.train_artifact = self.download_dataset_artifact(data_dict.get('train'),
|
||||||
|
opt.artifact_alias)
|
||||||
|
self.val_artifact_path, self.val_artifact = self.download_dataset_artifact(data_dict.get('val'),
|
||||||
|
opt.artifact_alias)
|
||||||
|
|
||||||
|
if self.train_artifact_path is not None:
|
||||||
|
train_path = Path(self.train_artifact_path) / 'data/images/'
|
||||||
|
data_dict['train'] = str(train_path)
|
||||||
|
if self.val_artifact_path is not None:
|
||||||
|
val_path = Path(self.val_artifact_path) / 'data/images/'
|
||||||
|
data_dict['val'] = str(val_path)
|
||||||
|
|
||||||
|
if self.val_artifact is not None:
|
||||||
|
self.result_artifact = wandb.Artifact("run_" + wandb.run.id + "_progress", "evaluation")
|
||||||
|
self.result_table = wandb.Table(["epoch", "id", "ground truth", "prediction", "avg_confidence"])
|
||||||
|
self.val_table = self.val_artifact.get("val")
|
||||||
|
if self.val_table_path_map is None:
|
||||||
|
self.map_val_table_path()
|
||||||
|
if opt.bbox_interval == -1:
|
||||||
|
self.bbox_interval = opt.bbox_interval = (opt.epochs // 10) if opt.epochs > 10 else 1
|
||||||
|
train_from_artifact = self.train_artifact_path is not None and self.val_artifact_path is not None
|
||||||
|
# Update the the data_dict to point to local artifacts dir
|
||||||
|
if train_from_artifact:
|
||||||
|
self.data_dict = data_dict
|
||||||
|
|
||||||
|
def download_dataset_artifact(self, path, alias):
|
||||||
|
"""
|
||||||
|
download the model checkpoint artifact if the path starts with WANDB_ARTIFACT_PREFIX
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
path -- path of the dataset to be used for training
|
||||||
|
alias (str)-- alias of the artifact to be download/used for training
|
||||||
|
|
||||||
|
returns:
|
||||||
|
(str, wandb.Artifact) -- path of the downladed dataset and it's corresponding artifact object if dataset
|
||||||
|
is found otherwise returns (None, None)
|
||||||
|
"""
|
||||||
|
if isinstance(path, str) and path.startswith(WANDB_ARTIFACT_PREFIX):
|
||||||
|
artifact_path = Path(remove_prefix(path, WANDB_ARTIFACT_PREFIX) + ":" + alias)
|
||||||
|
dataset_artifact = wandb.use_artifact(artifact_path.as_posix().replace("\\", "/"))
|
||||||
|
assert dataset_artifact is not None, "'Error: W&B dataset artifact doesn\'t exist'"
|
||||||
|
datadir = dataset_artifact.download()
|
||||||
|
return datadir, dataset_artifact
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def download_model_artifact(self, opt):
|
||||||
|
"""
|
||||||
|
download the model checkpoint artifact if the resume path starts with WANDB_ARTIFACT_PREFIX
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
opt (namespace) -- Commandline arguments for this run
|
||||||
|
"""
|
||||||
|
if opt.resume.startswith(WANDB_ARTIFACT_PREFIX):
|
||||||
|
model_artifact = wandb.use_artifact(remove_prefix(opt.resume, WANDB_ARTIFACT_PREFIX) + ":latest")
|
||||||
|
assert model_artifact is not None, 'Error: W&B model artifact doesn\'t exist'
|
||||||
|
modeldir = model_artifact.download()
|
||||||
|
epochs_trained = model_artifact.metadata.get('epochs_trained')
|
||||||
|
total_epochs = model_artifact.metadata.get('total_epochs')
|
||||||
|
is_finished = total_epochs is None
|
||||||
|
assert not is_finished, 'training is finished, can only resume incomplete runs.'
|
||||||
|
return modeldir, model_artifact
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def log_model(self, path, opt, epoch, fitness_score, best_model=False):
|
||||||
|
"""
|
||||||
|
Log the model checkpoint as W&B artifact
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
path (Path) -- Path of directory containing the checkpoints
|
||||||
|
opt (namespace) -- Command line arguments for this run
|
||||||
|
epoch (int) -- Current epoch number
|
||||||
|
fitness_score (float) -- fitness score for current epoch
|
||||||
|
best_model (boolean) -- Boolean representing if the current checkpoint is the best yet.
|
||||||
|
"""
|
||||||
|
model_artifact = wandb.Artifact('run_' + wandb.run.id + '_model', type='model', metadata={
|
||||||
|
'original_url': str(path),
|
||||||
|
'epochs_trained': epoch + 1,
|
||||||
|
'save period': opt.save_period,
|
||||||
|
'project': opt.project,
|
||||||
|
'total_epochs': opt.epochs,
|
||||||
|
'fitness_score': fitness_score
|
||||||
|
})
|
||||||
|
model_artifact.add_file(str(path / 'last.pt'), name='last.pt')
|
||||||
|
wandb.log_artifact(model_artifact,
|
||||||
|
aliases=['latest', 'last', 'epoch ' + str(self.current_epoch), 'best' if best_model else ''])
|
||||||
|
LOGGER.info(f"Saving model artifact on epoch {epoch + 1}")
|
||||||
|
|
||||||
|
def log_dataset_artifact(self, data_file, single_cls, project, overwrite_config=False):
|
||||||
|
"""
|
||||||
|
Log the dataset as W&B artifact and return the new data file with W&B links
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
data_file (str) -- the .yaml file with information about the dataset like - path, classes etc.
|
||||||
|
single_class (boolean) -- train multi-class data as single-class
|
||||||
|
project (str) -- project name. Used to construct the artifact path
|
||||||
|
overwrite_config (boolean) -- overwrites the data.yaml file if set to true otherwise creates a new
|
||||||
|
file with _wandb postfix. Eg -> data_wandb.yaml
|
||||||
|
|
||||||
|
returns:
|
||||||
|
the new .yaml file with artifact links. it can be used to start training directly from artifacts
|
||||||
|
"""
|
||||||
|
self.data_dict = check_dataset(data_file) # parse and check
|
||||||
|
data = dict(self.data_dict)
|
||||||
|
nc, names = (1, ['item']) if single_cls else (int(data['nc']), data['names'])
|
||||||
|
names = {k: v for k, v in enumerate(names)} # to index dictionary
|
||||||
|
self.train_artifact = self.create_dataset_table(LoadImagesAndLabels(
|
||||||
|
data['train'], rect=True, batch_size=1), names, name='train') if data.get('train') else None
|
||||||
|
self.val_artifact = self.create_dataset_table(LoadImagesAndLabels(
|
||||||
|
data['val'], rect=True, batch_size=1), names, name='val') if data.get('val') else None
|
||||||
|
if data.get('train'):
|
||||||
|
data['train'] = WANDB_ARTIFACT_PREFIX + str(Path(project) / 'train')
|
||||||
|
if data.get('val'):
|
||||||
|
data['val'] = WANDB_ARTIFACT_PREFIX + str(Path(project) / 'val')
|
||||||
|
path = Path(data_file).stem
|
||||||
|
path = (path if overwrite_config else path + '_wandb') + '.yaml' # updated data.yaml path
|
||||||
|
data.pop('download', None)
|
||||||
|
data.pop('path', None)
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
yaml.safe_dump(data, f)
|
||||||
|
|
||||||
|
if self.job_type == 'Training': # builds correct artifact pipeline graph
|
||||||
|
self.wandb_run.use_artifact(self.val_artifact)
|
||||||
|
self.wandb_run.use_artifact(self.train_artifact)
|
||||||
|
self.val_artifact.wait()
|
||||||
|
self.val_table = self.val_artifact.get('val')
|
||||||
|
self.map_val_table_path()
|
||||||
|
else:
|
||||||
|
self.wandb_run.log_artifact(self.train_artifact)
|
||||||
|
self.wandb_run.log_artifact(self.val_artifact)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def map_val_table_path(self):
|
||||||
|
"""
|
||||||
|
Map the validation dataset Table like name of file -> it's id in the W&B Table.
|
||||||
|
Useful for - referencing artifacts for evaluation.
|
||||||
|
"""
|
||||||
|
self.val_table_path_map = {}
|
||||||
|
LOGGER.info("Mapping dataset")
|
||||||
|
for i, data in enumerate(tqdm(self.val_table.data)):
|
||||||
|
self.val_table_path_map[data[3]] = data[0]
|
||||||
|
|
||||||
|
def create_dataset_table(self, dataset: LoadImagesAndLabels, class_to_id: Dict[int,str], name: str = 'dataset'):
|
||||||
|
"""
|
||||||
|
Create and return W&B artifact containing W&B Table of the dataset.
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
dataset -- instance of LoadImagesAndLabels class used to iterate over the data to build Table
|
||||||
|
class_to_id -- hash map that maps class ids to labels
|
||||||
|
name -- name of the artifact
|
||||||
|
|
||||||
|
returns:
|
||||||
|
dataset artifact to be logged or used
|
||||||
|
"""
|
||||||
|
# TODO: Explore multiprocessing to slpit this loop parallely| This is essential for speeding up the the logging
|
||||||
|
artifact = wandb.Artifact(name=name, type="dataset")
|
||||||
|
img_files = tqdm([dataset.path]) if isinstance(dataset.path, str) and Path(dataset.path).is_dir() else None
|
||||||
|
img_files = tqdm(dataset.img_files) if not img_files else img_files
|
||||||
|
for img_file in img_files:
|
||||||
|
if Path(img_file).is_dir():
|
||||||
|
artifact.add_dir(img_file, name='data/images')
|
||||||
|
labels_path = 'labels'.join(dataset.path.rsplit('images', 1))
|
||||||
|
artifact.add_dir(labels_path, name='data/labels')
|
||||||
|
else:
|
||||||
|
artifact.add_file(img_file, name='data/images/' + Path(img_file).name)
|
||||||
|
label_file = Path(img2label_paths([img_file])[0])
|
||||||
|
artifact.add_file(str(label_file),
|
||||||
|
name='data/labels/' + label_file.name) if label_file.exists() else None
|
||||||
|
table = wandb.Table(columns=["id", "train_image", "Classes", "name"])
|
||||||
|
class_set = wandb.Classes([{'id': id, 'name': name} for id, name in class_to_id.items()])
|
||||||
|
for si, (img, labels, paths, shapes) in enumerate(tqdm(dataset)):
|
||||||
|
box_data, img_classes = [], {}
|
||||||
|
for cls, *xywh in labels[:, 1:].tolist():
|
||||||
|
cls = int(cls)
|
||||||
|
box_data.append({"position": {"middle": [xywh[0], xywh[1]], "width": xywh[2], "height": xywh[3]},
|
||||||
|
"class_id": cls,
|
||||||
|
"box_caption": "%s" % (class_to_id[cls])})
|
||||||
|
img_classes[cls] = class_to_id[cls]
|
||||||
|
boxes = {"ground_truth": {"box_data": box_data, "class_labels": class_to_id}} # inference-space
|
||||||
|
table.add_data(si, wandb.Image(paths, classes=class_set, boxes=boxes), list(img_classes.values()),
|
||||||
|
Path(paths).name)
|
||||||
|
artifact.add(table, name)
|
||||||
|
return artifact
|
||||||
|
|
||||||
|
def log_training_progress(self, predn, path, names):
|
||||||
|
"""
|
||||||
|
Build evaluation Table. Uses reference from validation dataset table.
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
predn (list): list of predictions in the native space in the format - [xmin, ymin, xmax, ymax, confidence, class]
|
||||||
|
path (str): local path of the current evaluation image
|
||||||
|
names (dict(int, str)): hash map that maps class ids to labels
|
||||||
|
"""
|
||||||
|
class_set = wandb.Classes([{'id': id, 'name': name} for id, name in names.items()])
|
||||||
|
box_data = []
|
||||||
|
total_conf = 0
|
||||||
|
for *xyxy, conf, cls in predn.tolist():
|
||||||
|
if conf >= 0.25:
|
||||||
|
box_data.append(
|
||||||
|
{"position": {"minX": xyxy[0], "minY": xyxy[1], "maxX": xyxy[2], "maxY": xyxy[3]},
|
||||||
|
"class_id": int(cls),
|
||||||
|
"box_caption": f"{names[cls]} {conf:.3f}",
|
||||||
|
"scores": {"class_score": conf},
|
||||||
|
"domain": "pixel"})
|
||||||
|
total_conf += conf
|
||||||
|
boxes = {"predictions": {"box_data": box_data, "class_labels": names}} # inference-space
|
||||||
|
id = self.val_table_path_map[Path(path).name]
|
||||||
|
self.result_table.add_data(self.current_epoch,
|
||||||
|
id,
|
||||||
|
self.val_table.data[id][1],
|
||||||
|
wandb.Image(self.val_table.data[id][1], boxes=boxes, classes=class_set),
|
||||||
|
total_conf / max(1, len(box_data))
|
||||||
|
)
|
||||||
|
|
||||||
|
def val_one_image(self, pred, predn, path, names, im):
|
||||||
|
"""
|
||||||
|
Log validation data for one image. updates the result Table if validation dataset is uploaded and log bbox media panel
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
pred (list): list of scaled predictions in the format - [xmin, ymin, xmax, ymax, confidence, class]
|
||||||
|
predn (list): list of predictions in the native space - [xmin, ymin, xmax, ymax, confidence, class]
|
||||||
|
path (str): local path of the current evaluation image
|
||||||
|
"""
|
||||||
|
if self.val_table and self.result_table: # Log Table if Val dataset is uploaded as artifact
|
||||||
|
self.log_training_progress(predn, path, names)
|
||||||
|
|
||||||
|
if len(self.bbox_media_panel_images) < self.max_imgs_to_log and self.current_epoch > 0:
|
||||||
|
if self.current_epoch % self.bbox_interval == 0:
|
||||||
|
box_data = [{"position": {"minX": xyxy[0], "minY": xyxy[1], "maxX": xyxy[2], "maxY": xyxy[3]},
|
||||||
|
"class_id": int(cls),
|
||||||
|
"box_caption": f"{names[cls]} {conf:.3f}",
|
||||||
|
"scores": {"class_score": conf},
|
||||||
|
"domain": "pixel"} for *xyxy, conf, cls in pred.tolist()]
|
||||||
|
boxes = {"predictions": {"box_data": box_data, "class_labels": names}} # inference-space
|
||||||
|
self.bbox_media_panel_images.append(wandb.Image(im, boxes=boxes, caption=path.name))
|
||||||
|
|
||||||
|
def log(self, log_dict):
|
||||||
|
"""
|
||||||
|
save the metrics to the logging dictionary
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
log_dict (Dict) -- metrics/media to be logged in current step
|
||||||
|
"""
|
||||||
|
if self.wandb_run:
|
||||||
|
for key, value in log_dict.items():
|
||||||
|
self.log_dict[key] = value
|
||||||
|
|
||||||
|
def end_epoch(self, best_result=False):
|
||||||
|
"""
|
||||||
|
commit the log_dict, model artifacts and Tables to W&B and flush the log_dict.
|
||||||
|
|
||||||
|
arguments:
|
||||||
|
best_result (boolean): Boolean representing if the result of this evaluation is best or not
|
||||||
|
"""
|
||||||
|
if self.wandb_run:
|
||||||
|
with all_logging_disabled():
|
||||||
|
if self.bbox_media_panel_images:
|
||||||
|
self.log_dict["BoundingBoxDebugger"] = self.bbox_media_panel_images
|
||||||
|
try:
|
||||||
|
wandb.log(self.log_dict)
|
||||||
|
except BaseException as e:
|
||||||
|
LOGGER.info(f"An error occurred in wandb logger. The training will proceed without interruption. More info\n{e}")
|
||||||
|
self.wandb_run.finish()
|
||||||
|
self.wandb_run = None
|
||||||
|
|
||||||
|
self.log_dict = {}
|
||||||
|
self.bbox_media_panel_images = []
|
||||||
|
if self.result_artifact:
|
||||||
|
self.result_artifact.add(self.result_table, 'result')
|
||||||
|
wandb.log_artifact(self.result_artifact, aliases=['latest', 'last', 'epoch ' + str(self.current_epoch),
|
||||||
|
('best' if best_result else '')])
|
||||||
|
|
||||||
|
wandb.log({"evaluation": self.result_table})
|
||||||
|
self.result_table = wandb.Table(["epoch", "id", "ground truth", "prediction", "avg_confidence"])
|
||||||
|
self.result_artifact = wandb.Artifact("run_" + wandb.run.id + "_progress", "evaluation")
|
||||||
|
|
||||||
|
def finish_run(self):
|
||||||
|
"""
|
||||||
|
Log metrics if any and finish the current W&B run
|
||||||
|
"""
|
||||||
|
if self.wandb_run:
|
||||||
|
if self.log_dict:
|
||||||
|
with all_logging_disabled():
|
||||||
|
wandb.log(self.log_dict)
|
||||||
|
wandb.run.finish()
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def all_logging_disabled(highest_level=logging.CRITICAL):
|
||||||
|
""" source - https://gist.github.com/simon-weber/7853144
|
||||||
|
A context manager that will prevent any logging messages triggered during the body from being processed.
|
||||||
|
:param highest_level: the maximum logging level in use.
|
||||||
|
This would only need to be changed if a custom level greater than CRITICAL is defined.
|
||||||
|
"""
|
||||||
|
previous_level = logging.root.manager.disable
|
||||||
|
logging.disable(highest_level)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
logging.disable(previous_level)
|
||||||
234
ros2_ws/src/yolov3_ros/utils/loss.py
Normal file
234
ros2_ws/src/yolov3_ros/utils/loss.py
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Loss functions
|
||||||
|
"""
|
||||||
|
|
||||||
|
import torch
|
||||||
|
import torch.nn as nn
|
||||||
|
|
||||||
|
from utils.metrics import bbox_iou
|
||||||
|
from utils.torch_utils import de_parallel
|
||||||
|
|
||||||
|
|
||||||
|
def smooth_BCE(eps=0.1): # https://github.com/ultralytics/yolov3/issues/238#issuecomment-598028441
|
||||||
|
# return positive, negative label smoothing BCE targets
|
||||||
|
return 1.0 - 0.5 * eps, 0.5 * eps
|
||||||
|
|
||||||
|
|
||||||
|
class BCEBlurWithLogitsLoss(nn.Module):
|
||||||
|
# BCEwithLogitLoss() with reduced missing label effects.
|
||||||
|
def __init__(self, alpha=0.05):
|
||||||
|
super().__init__()
|
||||||
|
self.loss_fcn = nn.BCEWithLogitsLoss(reduction='none') # must be nn.BCEWithLogitsLoss()
|
||||||
|
self.alpha = alpha
|
||||||
|
|
||||||
|
def forward(self, pred, true):
|
||||||
|
loss = self.loss_fcn(pred, true)
|
||||||
|
pred = torch.sigmoid(pred) # prob from logits
|
||||||
|
dx = pred - true # reduce only missing label effects
|
||||||
|
# dx = (pred - true).abs() # reduce missing label and false label effects
|
||||||
|
alpha_factor = 1 - torch.exp((dx - 1) / (self.alpha + 1e-4))
|
||||||
|
loss *= alpha_factor
|
||||||
|
return loss.mean()
|
||||||
|
|
||||||
|
|
||||||
|
class FocalLoss(nn.Module):
|
||||||
|
# Wraps focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5)
|
||||||
|
def __init__(self, loss_fcn, gamma=1.5, alpha=0.25):
|
||||||
|
super().__init__()
|
||||||
|
self.loss_fcn = loss_fcn # must be nn.BCEWithLogitsLoss()
|
||||||
|
self.gamma = gamma
|
||||||
|
self.alpha = alpha
|
||||||
|
self.reduction = loss_fcn.reduction
|
||||||
|
self.loss_fcn.reduction = 'none' # required to apply FL to each element
|
||||||
|
|
||||||
|
def forward(self, pred, true):
|
||||||
|
loss = self.loss_fcn(pred, true)
|
||||||
|
# p_t = torch.exp(-loss)
|
||||||
|
# loss *= self.alpha * (1.000001 - p_t) ** self.gamma # non-zero power for gradient stability
|
||||||
|
|
||||||
|
# TF implementation https://github.com/tensorflow/addons/blob/v0.7.1/tensorflow_addons/losses/focal_loss.py
|
||||||
|
pred_prob = torch.sigmoid(pred) # prob from logits
|
||||||
|
p_t = true * pred_prob + (1 - true) * (1 - pred_prob)
|
||||||
|
alpha_factor = true * self.alpha + (1 - true) * (1 - self.alpha)
|
||||||
|
modulating_factor = (1.0 - p_t) ** self.gamma
|
||||||
|
loss *= alpha_factor * modulating_factor
|
||||||
|
|
||||||
|
if self.reduction == 'mean':
|
||||||
|
return loss.mean()
|
||||||
|
elif self.reduction == 'sum':
|
||||||
|
return loss.sum()
|
||||||
|
else: # 'none'
|
||||||
|
return loss
|
||||||
|
|
||||||
|
|
||||||
|
class QFocalLoss(nn.Module):
|
||||||
|
# Wraps Quality focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5)
|
||||||
|
def __init__(self, loss_fcn, gamma=1.5, alpha=0.25):
|
||||||
|
super().__init__()
|
||||||
|
self.loss_fcn = loss_fcn # must be nn.BCEWithLogitsLoss()
|
||||||
|
self.gamma = gamma
|
||||||
|
self.alpha = alpha
|
||||||
|
self.reduction = loss_fcn.reduction
|
||||||
|
self.loss_fcn.reduction = 'none' # required to apply FL to each element
|
||||||
|
|
||||||
|
def forward(self, pred, true):
|
||||||
|
loss = self.loss_fcn(pred, true)
|
||||||
|
|
||||||
|
pred_prob = torch.sigmoid(pred) # prob from logits
|
||||||
|
alpha_factor = true * self.alpha + (1 - true) * (1 - self.alpha)
|
||||||
|
modulating_factor = torch.abs(true - pred_prob) ** self.gamma
|
||||||
|
loss *= alpha_factor * modulating_factor
|
||||||
|
|
||||||
|
if self.reduction == 'mean':
|
||||||
|
return loss.mean()
|
||||||
|
elif self.reduction == 'sum':
|
||||||
|
return loss.sum()
|
||||||
|
else: # 'none'
|
||||||
|
return loss
|
||||||
|
|
||||||
|
|
||||||
|
class ComputeLoss:
|
||||||
|
sort_obj_iou = False
|
||||||
|
|
||||||
|
# Compute losses
|
||||||
|
def __init__(self, model, autobalance=False):
|
||||||
|
device = next(model.parameters()).device # get model device
|
||||||
|
h = model.hyp # hyperparameters
|
||||||
|
|
||||||
|
# Define criteria
|
||||||
|
BCEcls = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h['cls_pw']], device=device))
|
||||||
|
BCEobj = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h['obj_pw']], device=device))
|
||||||
|
|
||||||
|
# Class label smoothing https://arxiv.org/pdf/1902.04103.pdf eqn 3
|
||||||
|
self.cp, self.cn = smooth_BCE(eps=h.get('label_smoothing', 0.0)) # positive, negative BCE targets
|
||||||
|
|
||||||
|
# Focal loss
|
||||||
|
g = h['fl_gamma'] # focal loss gamma
|
||||||
|
if g > 0:
|
||||||
|
BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g)
|
||||||
|
|
||||||
|
m = de_parallel(model).model[-1] # Detect() module
|
||||||
|
self.balance = {3: [4.0, 1.0, 0.4]}.get(m.nl, [4.0, 1.0, 0.25, 0.06, 0.02]) # P3-P7
|
||||||
|
self.ssi = list(m.stride).index(16) if autobalance else 0 # stride 16 index
|
||||||
|
self.BCEcls, self.BCEobj, self.gr, self.hyp, self.autobalance = BCEcls, BCEobj, 1.0, h, autobalance
|
||||||
|
self.na = m.na # number of anchors
|
||||||
|
self.nc = m.nc # number of classes
|
||||||
|
self.nl = m.nl # number of layers
|
||||||
|
self.anchors = m.anchors
|
||||||
|
self.device = device
|
||||||
|
|
||||||
|
def __call__(self, p, targets): # predictions, targets
|
||||||
|
lcls = torch.zeros(1, device=self.device) # class loss
|
||||||
|
lbox = torch.zeros(1, device=self.device) # box loss
|
||||||
|
lobj = torch.zeros(1, device=self.device) # object loss
|
||||||
|
tcls, tbox, indices, anchors = self.build_targets(p, targets) # targets
|
||||||
|
|
||||||
|
# Losses
|
||||||
|
for i, pi in enumerate(p): # layer index, layer predictions
|
||||||
|
b, a, gj, gi = indices[i] # image, anchor, gridy, gridx
|
||||||
|
tobj = torch.zeros(pi.shape[:4], dtype=pi.dtype, device=self.device) # target obj
|
||||||
|
|
||||||
|
n = b.shape[0] # number of targets
|
||||||
|
if n:
|
||||||
|
# pxy, pwh, _, pcls = pi[b, a, gj, gi].tensor_split((2, 4, 5), dim=1) # faster, requires torch 1.8.0
|
||||||
|
pxy, pwh, _, pcls = pi[b, a, gj, gi].split((2, 2, 1, self.nc), 1) # target-subset of predictions
|
||||||
|
|
||||||
|
# Regression
|
||||||
|
pxy = pxy.sigmoid() * 2 - 0.5
|
||||||
|
pwh = (pwh.sigmoid() * 2) ** 2 * anchors[i]
|
||||||
|
pbox = torch.cat((pxy, pwh), 1) # predicted box
|
||||||
|
iou = bbox_iou(pbox, tbox[i], CIoU=True).squeeze() # iou(prediction, target)
|
||||||
|
lbox += (1.0 - iou).mean() # iou loss
|
||||||
|
|
||||||
|
# Objectness
|
||||||
|
iou = iou.detach().clamp(0).type(tobj.dtype)
|
||||||
|
if self.sort_obj_iou:
|
||||||
|
j = iou.argsort()
|
||||||
|
b, a, gj, gi, iou = b[j], a[j], gj[j], gi[j], iou[j]
|
||||||
|
if self.gr < 1:
|
||||||
|
iou = (1.0 - self.gr) + self.gr * iou
|
||||||
|
tobj[b, a, gj, gi] = iou # iou ratio
|
||||||
|
|
||||||
|
# Classification
|
||||||
|
if self.nc > 1: # cls loss (only if multiple classes)
|
||||||
|
t = torch.full_like(pcls, self.cn, device=self.device) # targets
|
||||||
|
t[range(n), tcls[i]] = self.cp
|
||||||
|
lcls += self.BCEcls(pcls, t) # BCE
|
||||||
|
|
||||||
|
# Append targets to text file
|
||||||
|
# with open('targets.txt', 'a') as file:
|
||||||
|
# [file.write('%11.5g ' * 4 % tuple(x) + '\n') for x in torch.cat((txy[i], twh[i]), 1)]
|
||||||
|
|
||||||
|
obji = self.BCEobj(pi[..., 4], tobj)
|
||||||
|
lobj += obji * self.balance[i] # obj loss
|
||||||
|
if self.autobalance:
|
||||||
|
self.balance[i] = self.balance[i] * 0.9999 + 0.0001 / obji.detach().item()
|
||||||
|
|
||||||
|
if self.autobalance:
|
||||||
|
self.balance = [x / self.balance[self.ssi] for x in self.balance]
|
||||||
|
lbox *= self.hyp['box']
|
||||||
|
lobj *= self.hyp['obj']
|
||||||
|
lcls *= self.hyp['cls']
|
||||||
|
bs = tobj.shape[0] # batch size
|
||||||
|
|
||||||
|
return (lbox + lobj + lcls) * bs, torch.cat((lbox, lobj, lcls)).detach()
|
||||||
|
|
||||||
|
def build_targets(self, p, targets):
|
||||||
|
# Build targets for compute_loss(), input targets(image,class,x,y,w,h)
|
||||||
|
na, nt = self.na, targets.shape[0] # number of anchors, targets
|
||||||
|
tcls, tbox, indices, anch = [], [], [], []
|
||||||
|
gain = torch.ones(7, device=self.device) # normalized to gridspace gain
|
||||||
|
ai = torch.arange(na, device=self.device).float().view(na, 1).repeat(1, nt) # same as .repeat_interleave(nt)
|
||||||
|
targets = torch.cat((targets.repeat(na, 1, 1), ai[..., None]), 2) # append anchor indices
|
||||||
|
|
||||||
|
g = 0.5 # bias
|
||||||
|
off = torch.tensor(
|
||||||
|
[
|
||||||
|
[0, 0],
|
||||||
|
[1, 0],
|
||||||
|
[0, 1],
|
||||||
|
[-1, 0],
|
||||||
|
[0, -1], # j,k,l,m
|
||||||
|
# [1, 1], [1, -1], [-1, 1], [-1, -1], # jk,jm,lk,lm
|
||||||
|
],
|
||||||
|
device=self.device).float() * g # offsets
|
||||||
|
|
||||||
|
for i in range(self.nl):
|
||||||
|
anchors, shape = self.anchors[i], p[i].shape
|
||||||
|
gain[2:6] = torch.tensor(shape)[[3, 2, 3, 2]] # xyxy gain
|
||||||
|
|
||||||
|
# Match targets to anchors
|
||||||
|
t = targets * gain # shape(3,n,7)
|
||||||
|
if nt:
|
||||||
|
# Matches
|
||||||
|
r = t[..., 4:6] / anchors[:, None] # wh ratio
|
||||||
|
j = torch.max(r, 1 / r).max(2)[0] < self.hyp['anchor_t'] # compare
|
||||||
|
# j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t'] # iou(3,n)=wh_iou(anchors(3,2), gwh(n,2))
|
||||||
|
t = t[j] # filter
|
||||||
|
|
||||||
|
# Offsets
|
||||||
|
gxy = t[:, 2:4] # grid xy
|
||||||
|
gxi = gain[[2, 3]] - gxy # inverse
|
||||||
|
j, k = ((gxy % 1 < g) & (gxy > 1)).T
|
||||||
|
l, m = ((gxi % 1 < g) & (gxi > 1)).T
|
||||||
|
j = torch.stack((torch.ones_like(j), j, k, l, m))
|
||||||
|
t = t.repeat((5, 1, 1))[j]
|
||||||
|
offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j]
|
||||||
|
else:
|
||||||
|
t = targets[0]
|
||||||
|
offsets = 0
|
||||||
|
|
||||||
|
# Define
|
||||||
|
bc, gxy, gwh, a = t.chunk(4, 1) # (image, class), grid xy, grid wh, anchors
|
||||||
|
a, (b, c) = a.long().view(-1), bc.long().T # anchors, image, class
|
||||||
|
gij = (gxy - offsets).long()
|
||||||
|
gi, gj = gij.T # grid indices
|
||||||
|
|
||||||
|
# Append
|
||||||
|
indices.append((b, a, gj.clamp_(0, shape[2] - 1), gi.clamp_(0, shape[3] - 1))) # image, anchor, grid
|
||||||
|
tbox.append(torch.cat((gxy - gij, gwh), 1)) # box
|
||||||
|
anch.append(anchors[a]) # anchors
|
||||||
|
tcls.append(c) # class
|
||||||
|
|
||||||
|
return tcls, tbox, indices, anch
|
||||||
331
ros2_ws/src/yolov3_ros/utils/metrics.py
Normal file
331
ros2_ws/src/yolov3_ros/utils/metrics.py
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Model validation metrics
|
||||||
|
"""
|
||||||
|
|
||||||
|
import math
|
||||||
|
import warnings
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
|
||||||
|
|
||||||
|
def fitness(x):
|
||||||
|
# Model fitness as a weighted combination of metrics
|
||||||
|
w = [0.0, 0.0, 0.1, 0.9] # weights for [P, R, mAP@0.5, mAP@0.5:0.95]
|
||||||
|
return (x[:, :4] * w).sum(1)
|
||||||
|
|
||||||
|
|
||||||
|
def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, save_dir='.', names=()):
|
||||||
|
""" Compute the average precision, given the recall and precision curves.
|
||||||
|
Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
|
||||||
|
# Arguments
|
||||||
|
tp: True positives (nparray, nx1 or nx10).
|
||||||
|
conf: Objectness value from 0-1 (nparray).
|
||||||
|
pred_cls: Predicted object classes (nparray).
|
||||||
|
target_cls: True object classes (nparray).
|
||||||
|
plot: Plot precision-recall curve at mAP@0.5
|
||||||
|
save_dir: Plot save directory
|
||||||
|
# Returns
|
||||||
|
The average precision as computed in py-faster-rcnn.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Sort by objectness
|
||||||
|
i = np.argsort(-conf)
|
||||||
|
tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]
|
||||||
|
|
||||||
|
# Find unique classes
|
||||||
|
unique_classes = np.unique(target_cls)
|
||||||
|
nc = unique_classes.shape[0] # number of classes, number of detections
|
||||||
|
|
||||||
|
# Create Precision-Recall curve and compute AP for each class
|
||||||
|
px, py = np.linspace(0, 1, 1000), [] # for plotting
|
||||||
|
ap, p, r = np.zeros((nc, tp.shape[1])), np.zeros((nc, 1000)), np.zeros((nc, 1000))
|
||||||
|
for ci, c in enumerate(unique_classes):
|
||||||
|
i = pred_cls == c
|
||||||
|
n_l = (target_cls == c).sum() # number of labels
|
||||||
|
n_p = i.sum() # number of predictions
|
||||||
|
|
||||||
|
if n_p == 0 or n_l == 0:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Accumulate FPs and TPs
|
||||||
|
fpc = (1 - tp[i]).cumsum(0)
|
||||||
|
tpc = tp[i].cumsum(0)
|
||||||
|
|
||||||
|
# Recall
|
||||||
|
recall = tpc / (n_l + 1e-16) # recall curve
|
||||||
|
r[ci] = np.interp(-px, -conf[i], recall[:, 0], left=0) # negative x, xp because xp decreases
|
||||||
|
|
||||||
|
# Precision
|
||||||
|
precision = tpc / (tpc + fpc) # precision curve
|
||||||
|
p[ci] = np.interp(-px, -conf[i], precision[:, 0], left=1) # p at pr_score
|
||||||
|
|
||||||
|
# AP from recall-precision curve
|
||||||
|
for j in range(tp.shape[1]):
|
||||||
|
ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j])
|
||||||
|
if plot and j == 0:
|
||||||
|
py.append(np.interp(px, mrec, mpre)) # precision at mAP@0.5
|
||||||
|
|
||||||
|
# Compute F1 (harmonic mean of precision and recall)
|
||||||
|
f1 = 2 * p * r / (p + r + 1e-16)
|
||||||
|
names = [v for k, v in names.items() if k in unique_classes] # list: only classes that have data
|
||||||
|
names = {i: v for i, v in enumerate(names)} # to dict
|
||||||
|
if plot:
|
||||||
|
plot_pr_curve(px, py, ap, Path(save_dir) / 'PR_curve.png', names)
|
||||||
|
plot_mc_curve(px, f1, Path(save_dir) / 'F1_curve.png', names, ylabel='F1')
|
||||||
|
plot_mc_curve(px, p, Path(save_dir) / 'P_curve.png', names, ylabel='Precision')
|
||||||
|
plot_mc_curve(px, r, Path(save_dir) / 'R_curve.png', names, ylabel='Recall')
|
||||||
|
|
||||||
|
i = f1.mean(0).argmax() # max F1 index
|
||||||
|
return p[:, i], r[:, i], ap, f1[:, i], unique_classes.astype('int32')
|
||||||
|
|
||||||
|
|
||||||
|
def compute_ap(recall, precision):
|
||||||
|
""" Compute the average precision, given the recall and precision curves
|
||||||
|
# Arguments
|
||||||
|
recall: The recall curve (list)
|
||||||
|
precision: The precision curve (list)
|
||||||
|
# Returns
|
||||||
|
Average precision, precision curve, recall curve
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Append sentinel values to beginning and end
|
||||||
|
mrec = np.concatenate(([0.0], recall, [1.0]))
|
||||||
|
mpre = np.concatenate(([1.0], precision, [0.0]))
|
||||||
|
|
||||||
|
# Compute the precision envelope
|
||||||
|
mpre = np.flip(np.maximum.accumulate(np.flip(mpre)))
|
||||||
|
|
||||||
|
# Integrate area under curve
|
||||||
|
method = 'interp' # methods: 'continuous', 'interp'
|
||||||
|
if method == 'interp':
|
||||||
|
x = np.linspace(0, 1, 101) # 101-point interp (COCO)
|
||||||
|
ap = np.trapz(np.interp(x, mrec, mpre), x) # integrate
|
||||||
|
else: # 'continuous'
|
||||||
|
i = np.where(mrec[1:] != mrec[:-1])[0] # points where x axis (recall) changes
|
||||||
|
ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) # area under curve
|
||||||
|
|
||||||
|
return ap, mpre, mrec
|
||||||
|
|
||||||
|
|
||||||
|
class ConfusionMatrix:
|
||||||
|
# Updated version of https://github.com/kaanakan/object_detection_confusion_matrix
|
||||||
|
def __init__(self, nc, conf=0.25, iou_thres=0.45):
|
||||||
|
self.matrix = np.zeros((nc + 1, nc + 1))
|
||||||
|
self.nc = nc # number of classes
|
||||||
|
self.conf = conf
|
||||||
|
self.iou_thres = iou_thres
|
||||||
|
|
||||||
|
def process_batch(self, detections, labels):
|
||||||
|
"""
|
||||||
|
Return intersection-over-union (Jaccard index) of boxes.
|
||||||
|
Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
|
||||||
|
Arguments:
|
||||||
|
detections (Array[N, 6]), x1, y1, x2, y2, conf, class
|
||||||
|
labels (Array[M, 5]), class, x1, y1, x2, y2
|
||||||
|
Returns:
|
||||||
|
None, updates confusion matrix accordingly
|
||||||
|
"""
|
||||||
|
detections = detections[detections[:, 4] > self.conf]
|
||||||
|
gt_classes = labels[:, 0].int()
|
||||||
|
detection_classes = detections[:, 5].int()
|
||||||
|
iou = box_iou(labels[:, 1:], detections[:, :4])
|
||||||
|
|
||||||
|
x = torch.where(iou > self.iou_thres)
|
||||||
|
if x[0].shape[0]:
|
||||||
|
matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy()
|
||||||
|
if x[0].shape[0] > 1:
|
||||||
|
matches = matches[matches[:, 2].argsort()[::-1]]
|
||||||
|
matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
|
||||||
|
matches = matches[matches[:, 2].argsort()[::-1]]
|
||||||
|
matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
|
||||||
|
else:
|
||||||
|
matches = np.zeros((0, 3))
|
||||||
|
|
||||||
|
n = matches.shape[0] > 0
|
||||||
|
m0, m1, _ = matches.transpose().astype(np.int16)
|
||||||
|
for i, gc in enumerate(gt_classes):
|
||||||
|
j = m0 == i
|
||||||
|
if n and sum(j) == 1:
|
||||||
|
self.matrix[detection_classes[m1[j]], gc] += 1 # correct
|
||||||
|
else:
|
||||||
|
self.matrix[self.nc, gc] += 1 # background FP
|
||||||
|
|
||||||
|
if n:
|
||||||
|
for i, dc in enumerate(detection_classes):
|
||||||
|
if not any(m1 == i):
|
||||||
|
self.matrix[dc, self.nc] += 1 # background FN
|
||||||
|
|
||||||
|
def matrix(self):
|
||||||
|
return self.matrix
|
||||||
|
|
||||||
|
def plot(self, normalize=True, save_dir='', names=()):
|
||||||
|
try:
|
||||||
|
import seaborn as sn
|
||||||
|
|
||||||
|
array = self.matrix / ((self.matrix.sum(0).reshape(1, -1) + 1E-6) if normalize else 1) # normalize columns
|
||||||
|
array[array < 0.005] = np.nan # don't annotate (would appear as 0.00)
|
||||||
|
|
||||||
|
fig = plt.figure(figsize=(12, 9), tight_layout=True)
|
||||||
|
sn.set(font_scale=1.0 if self.nc < 50 else 0.8) # for label size
|
||||||
|
labels = (0 < len(names) < 99) and len(names) == self.nc # apply names to ticklabels
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore') # suppress empty matrix RuntimeWarning: All-NaN slice encountered
|
||||||
|
sn.heatmap(array, annot=self.nc < 30, annot_kws={"size": 8}, cmap='Blues', fmt='.2f', square=True,
|
||||||
|
xticklabels=names + ['background FP'] if labels else "auto",
|
||||||
|
yticklabels=names + ['background FN'] if labels else "auto").set_facecolor((1, 1, 1))
|
||||||
|
fig.axes[0].set_xlabel('True')
|
||||||
|
fig.axes[0].set_ylabel('Predicted')
|
||||||
|
fig.savefig(Path(save_dir) / 'confusion_matrix.png', dpi=250)
|
||||||
|
plt.close()
|
||||||
|
except Exception as e:
|
||||||
|
print(f'WARNING: ConfusionMatrix plot failure: {e}')
|
||||||
|
|
||||||
|
def print(self):
|
||||||
|
for i in range(self.nc + 1):
|
||||||
|
print(' '.join(map(str, self.matrix[i])))
|
||||||
|
|
||||||
|
|
||||||
|
def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7):
|
||||||
|
# Returns Intersection over Union (IoU) of box1(1,4) to box2(n,4)
|
||||||
|
|
||||||
|
# Get the coordinates of bounding boxes
|
||||||
|
if xywh: # transform from xywh to xyxy
|
||||||
|
(x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, 1), box2.chunk(4, 1)
|
||||||
|
w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2
|
||||||
|
b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_
|
||||||
|
b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_
|
||||||
|
else: # x1, y1, x2, y2 = box1
|
||||||
|
b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, 1)
|
||||||
|
b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, 1)
|
||||||
|
w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps
|
||||||
|
w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps
|
||||||
|
|
||||||
|
# Intersection area
|
||||||
|
inter = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \
|
||||||
|
(torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0)
|
||||||
|
|
||||||
|
# Union Area
|
||||||
|
union = w1 * h1 + w2 * h2 - inter + eps
|
||||||
|
|
||||||
|
# IoU
|
||||||
|
iou = inter / union
|
||||||
|
if CIoU or DIoU or GIoU:
|
||||||
|
cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1) # convex (smallest enclosing box) width
|
||||||
|
ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1) # convex height
|
||||||
|
if CIoU or DIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
|
||||||
|
c2 = cw ** 2 + ch ** 2 + eps # convex diagonal squared
|
||||||
|
rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4 # center dist ** 2
|
||||||
|
if CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47
|
||||||
|
v = (4 / math.pi ** 2) * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2)
|
||||||
|
with torch.no_grad():
|
||||||
|
alpha = v / (v - iou + (1 + eps))
|
||||||
|
return iou - (rho2 / c2 + v * alpha) # CIoU
|
||||||
|
return iou - rho2 / c2 # DIoU
|
||||||
|
c_area = cw * ch + eps # convex area
|
||||||
|
return iou - (c_area - union) / c_area # GIoU https://arxiv.org/pdf/1902.09630.pdf
|
||||||
|
return iou # IoU
|
||||||
|
|
||||||
|
|
||||||
|
def box_iou(box1, box2):
|
||||||
|
# https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py
|
||||||
|
"""
|
||||||
|
Return intersection-over-union (Jaccard index) of boxes.
|
||||||
|
Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
|
||||||
|
Arguments:
|
||||||
|
box1 (Tensor[N, 4])
|
||||||
|
box2 (Tensor[M, 4])
|
||||||
|
Returns:
|
||||||
|
iou (Tensor[N, M]): the NxM matrix containing the pairwise
|
||||||
|
IoU values for every element in boxes1 and boxes2
|
||||||
|
"""
|
||||||
|
|
||||||
|
def box_area(box):
|
||||||
|
# box = 4xn
|
||||||
|
return (box[2] - box[0]) * (box[3] - box[1])
|
||||||
|
|
||||||
|
area1 = box_area(box1.T)
|
||||||
|
area2 = box_area(box2.T)
|
||||||
|
|
||||||
|
# inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2)
|
||||||
|
inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)
|
||||||
|
return inter / (area1[:, None] + area2 - inter) # iou = inter / (area1 + area2 - inter)
|
||||||
|
|
||||||
|
|
||||||
|
def bbox_ioa(box1, box2, eps=1E-7):
|
||||||
|
""" Returns the intersection over box2 area given box1, box2. Boxes are x1y1x2y2
|
||||||
|
box1: np.array of shape(4)
|
||||||
|
box2: np.array of shape(nx4)
|
||||||
|
returns: np.array of shape(n)
|
||||||
|
"""
|
||||||
|
|
||||||
|
box2 = box2.transpose()
|
||||||
|
|
||||||
|
# Get the coordinates of bounding boxes
|
||||||
|
b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]
|
||||||
|
b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]
|
||||||
|
|
||||||
|
# Intersection area
|
||||||
|
inter_area = (np.minimum(b1_x2, b2_x2) - np.maximum(b1_x1, b2_x1)).clip(0) * \
|
||||||
|
(np.minimum(b1_y2, b2_y2) - np.maximum(b1_y1, b2_y1)).clip(0)
|
||||||
|
|
||||||
|
# box2 area
|
||||||
|
box2_area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1) + eps
|
||||||
|
|
||||||
|
# Intersection over box2 area
|
||||||
|
return inter_area / box2_area
|
||||||
|
|
||||||
|
|
||||||
|
def wh_iou(wh1, wh2):
|
||||||
|
# Returns the nxm IoU matrix. wh1 is nx2, wh2 is mx2
|
||||||
|
wh1 = wh1[:, None] # [N,1,2]
|
||||||
|
wh2 = wh2[None] # [1,M,2]
|
||||||
|
inter = torch.min(wh1, wh2).prod(2) # [N,M]
|
||||||
|
return inter / (wh1.prod(2) + wh2.prod(2) - inter) # iou = inter / (area1 + area2 - inter)
|
||||||
|
|
||||||
|
|
||||||
|
# Plots ----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def plot_pr_curve(px, py, ap, save_dir='pr_curve.png', names=()):
|
||||||
|
# Precision-recall curve
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True)
|
||||||
|
py = np.stack(py, axis=1)
|
||||||
|
|
||||||
|
if 0 < len(names) < 21: # display per-class legend if < 21 classes
|
||||||
|
for i, y in enumerate(py.T):
|
||||||
|
ax.plot(px, y, linewidth=1, label=f'{names[i]} {ap[i, 0]:.3f}') # plot(recall, precision)
|
||||||
|
else:
|
||||||
|
ax.plot(px, py, linewidth=1, color='grey') # plot(recall, precision)
|
||||||
|
|
||||||
|
ax.plot(px, py.mean(1), linewidth=3, color='blue', label='all classes %.3f mAP@0.5' % ap[:, 0].mean())
|
||||||
|
ax.set_xlabel('Recall')
|
||||||
|
ax.set_ylabel('Precision')
|
||||||
|
ax.set_xlim(0, 1)
|
||||||
|
ax.set_ylim(0, 1)
|
||||||
|
plt.legend(bbox_to_anchor=(1.04, 1), loc="upper left")
|
||||||
|
fig.savefig(Path(save_dir), dpi=250)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def plot_mc_curve(px, py, save_dir='mc_curve.png', names=(), xlabel='Confidence', ylabel='Metric'):
|
||||||
|
# Metric-confidence curve
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True)
|
||||||
|
|
||||||
|
if 0 < len(names) < 21: # display per-class legend if < 21 classes
|
||||||
|
for i, y in enumerate(py):
|
||||||
|
ax.plot(px, y, linewidth=1, label=f'{names[i]}') # plot(confidence, metric)
|
||||||
|
else:
|
||||||
|
ax.plot(px, py.T, linewidth=1, color='grey') # plot(confidence, metric)
|
||||||
|
|
||||||
|
y = py.mean(0)
|
||||||
|
ax.plot(px, y, linewidth=3, color='blue', label=f'all classes {y.max():.2f} at {px[y.argmax()]:.3f}')
|
||||||
|
ax.set_xlabel(xlabel)
|
||||||
|
ax.set_ylabel(ylabel)
|
||||||
|
ax.set_xlim(0, 1)
|
||||||
|
ax.set_ylim(0, 1)
|
||||||
|
plt.legend(bbox_to_anchor=(1.04, 1), loc="upper left")
|
||||||
|
fig.savefig(Path(save_dir), dpi=250)
|
||||||
|
plt.close()
|
||||||
469
ros2_ws/src/yolov3_ros/utils/plots.py
Normal file
469
ros2_ws/src/yolov3_ros/utils/plots.py
Normal file
@ -0,0 +1,469 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Plotting utils
|
||||||
|
"""
|
||||||
|
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
from copy import copy
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import matplotlib
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
import seaborn as sn
|
||||||
|
import torch
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
from utils.general import (LOGGER, Timeout, check_requirements, clip_coords, increment_path, is_ascii, is_chinese,
|
||||||
|
try_except, user_config_dir, xywh2xyxy, xyxy2xywh)
|
||||||
|
from utils.metrics import fitness
|
||||||
|
|
||||||
|
# Settings
|
||||||
|
CONFIG_DIR = user_config_dir() # Ultralytics settings dir
|
||||||
|
RANK = int(os.getenv('RANK', -1))
|
||||||
|
matplotlib.rc('font', **{'size': 11})
|
||||||
|
matplotlib.use('Agg') # for writing to files only
|
||||||
|
|
||||||
|
|
||||||
|
class Colors:
|
||||||
|
# Ultralytics color palette https://ultralytics.com/
|
||||||
|
def __init__(self):
|
||||||
|
# hex = matplotlib.colors.TABLEAU_COLORS.values()
|
||||||
|
hex = ('FF3838', 'FF9D97', 'FF701F', 'FFB21D', 'CFD231', '48F90A', '92CC17', '3DDB86', '1A9334', '00D4BB',
|
||||||
|
'2C99A8', '00C2FF', '344593', '6473FF', '0018EC', '8438FF', '520085', 'CB38FF', 'FF95C8', 'FF37C7')
|
||||||
|
self.palette = [self.hex2rgb('#' + c) for c in hex]
|
||||||
|
self.n = len(self.palette)
|
||||||
|
|
||||||
|
def __call__(self, i, bgr=False):
|
||||||
|
c = self.palette[int(i) % self.n]
|
||||||
|
return (c[2], c[1], c[0]) if bgr else c
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def hex2rgb(h): # rgb order (PIL)
|
||||||
|
return tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4))
|
||||||
|
|
||||||
|
|
||||||
|
colors = Colors() # create instance for 'from utils.plots import colors'
|
||||||
|
|
||||||
|
|
||||||
|
def check_font(font='Arial.ttf', size=10):
|
||||||
|
# Return a PIL TrueType Font, downloading to CONFIG_DIR if necessary
|
||||||
|
font = Path(font)
|
||||||
|
font = font if font.exists() else (CONFIG_DIR / font.name)
|
||||||
|
try:
|
||||||
|
return ImageFont.truetype(str(font) if font.exists() else font.name, size)
|
||||||
|
except Exception as e: # download if missing
|
||||||
|
url = "https://ultralytics.com/assets/" + font.name
|
||||||
|
print(f'Downloading {url} to {font}...')
|
||||||
|
torch.hub.download_url_to_file(url, str(font), progress=False)
|
||||||
|
try:
|
||||||
|
return ImageFont.truetype(str(font), size)
|
||||||
|
except TypeError:
|
||||||
|
check_requirements('Pillow>=8.4.0') # known issue https://github.com/ultralytics/yolov5/issues/5374
|
||||||
|
|
||||||
|
|
||||||
|
class Annotator:
|
||||||
|
if RANK in (-1, 0):
|
||||||
|
check_font() # download TTF if necessary
|
||||||
|
|
||||||
|
# Annotator for train/val mosaics and jpgs and detect/hub inference annotations
|
||||||
|
def __init__(self, im, line_width=None, font_size=None, font='Arial.ttf', pil=False, example='abc'):
|
||||||
|
assert im.data.contiguous, 'Image not contiguous. Apply np.ascontiguousarray(im) to Annotator() input images.'
|
||||||
|
self.pil = pil or not is_ascii(example) or is_chinese(example)
|
||||||
|
if self.pil: # use PIL
|
||||||
|
self.im = im if isinstance(im, Image.Image) else Image.fromarray(im)
|
||||||
|
self.draw = ImageDraw.Draw(self.im)
|
||||||
|
self.font = check_font(font='Arial.Unicode.ttf' if is_chinese(example) else font,
|
||||||
|
size=font_size or max(round(sum(self.im.size) / 2 * 0.035), 12))
|
||||||
|
else: # use cv2
|
||||||
|
self.im = im
|
||||||
|
self.lw = line_width or max(round(sum(im.shape) / 2 * 0.003), 2) # line width
|
||||||
|
|
||||||
|
def box_label(self, box, label='', color=(128, 128, 128), txt_color=(255, 255, 255)):
|
||||||
|
# Add one xyxy box to image with label
|
||||||
|
if self.pil or not is_ascii(label):
|
||||||
|
self.draw.rectangle(box, width=self.lw, outline=color) # box
|
||||||
|
if label:
|
||||||
|
w, h = self.font.getsize(label) # text width, height
|
||||||
|
outside = box[1] - h >= 0 # label fits outside box
|
||||||
|
self.draw.rectangle([box[0],
|
||||||
|
box[1] - h if outside else box[1],
|
||||||
|
box[0] + w + 1,
|
||||||
|
box[1] + 1 if outside else box[1] + h + 1], fill=color)
|
||||||
|
# self.draw.text((box[0], box[1]), label, fill=txt_color, font=self.font, anchor='ls') # for PIL>8.0
|
||||||
|
self.draw.text((box[0], box[1] - h if outside else box[1]), label, fill=txt_color, font=self.font)
|
||||||
|
else: # cv2
|
||||||
|
p1, p2 = (int(box[0]), int(box[1])), (int(box[2]), int(box[3]))
|
||||||
|
cv2.rectangle(self.im, p1, p2, color, thickness=self.lw, lineType=cv2.LINE_AA)
|
||||||
|
if label:
|
||||||
|
tf = max(self.lw - 1, 1) # font thickness
|
||||||
|
w, h = cv2.getTextSize(label, 0, fontScale=self.lw / 3, thickness=tf)[0] # text width, height
|
||||||
|
outside = p1[1] - h - 3 >= 0 # label fits outside box
|
||||||
|
p2 = p1[0] + w, p1[1] - h - 3 if outside else p1[1] + h + 3
|
||||||
|
cv2.rectangle(self.im, p1, p2, color, -1, cv2.LINE_AA) # filled
|
||||||
|
cv2.putText(self.im, label, (p1[0], p1[1] - 2 if outside else p1[1] + h + 2), 0, self.lw / 3, txt_color,
|
||||||
|
thickness=tf, lineType=cv2.LINE_AA)
|
||||||
|
|
||||||
|
def rectangle(self, xy, fill=None, outline=None, width=1):
|
||||||
|
# Add rectangle to image (PIL-only)
|
||||||
|
self.draw.rectangle(xy, fill, outline, width)
|
||||||
|
|
||||||
|
def text(self, xy, text, txt_color=(255, 255, 255)):
|
||||||
|
# Add text to image (PIL-only)
|
||||||
|
w, h = self.font.getsize(text) # text width, height
|
||||||
|
self.draw.text((xy[0], xy[1] - h + 1), text, fill=txt_color, font=self.font)
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
# Return annotated image as array
|
||||||
|
return np.asarray(self.im)
|
||||||
|
|
||||||
|
|
||||||
|
def feature_visualization(x, module_type, stage, n=32, save_dir=Path('runs/detect/exp')):
|
||||||
|
"""
|
||||||
|
x: Features to be visualized
|
||||||
|
module_type: Module type
|
||||||
|
stage: Module stage within model
|
||||||
|
n: Maximum number of feature maps to plot
|
||||||
|
save_dir: Directory to save results
|
||||||
|
"""
|
||||||
|
if 'Detect' not in module_type:
|
||||||
|
batch, channels, height, width = x.shape # batch, channels, height, width
|
||||||
|
if height > 1 and width > 1:
|
||||||
|
f = f"stage{stage}_{module_type.split('.')[-1]}_features.png" # filename
|
||||||
|
|
||||||
|
blocks = torch.chunk(x[0].cpu(), channels, dim=0) # select batch index 0, block by channels
|
||||||
|
n = min(n, channels) # number of plots
|
||||||
|
fig, ax = plt.subplots(math.ceil(n / 8), 8, tight_layout=True) # 8 rows x n/8 cols
|
||||||
|
ax = ax.ravel()
|
||||||
|
plt.subplots_adjust(wspace=0.05, hspace=0.05)
|
||||||
|
for i in range(n):
|
||||||
|
ax[i].imshow(blocks[i].squeeze()) # cmap='gray'
|
||||||
|
ax[i].axis('off')
|
||||||
|
|
||||||
|
print(f'Saving {save_dir / f}... ({n}/{channels})')
|
||||||
|
plt.savefig(save_dir / f, dpi=300, bbox_inches='tight')
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def hist2d(x, y, n=100):
|
||||||
|
# 2d histogram used in labels.png and evolve.png
|
||||||
|
xedges, yedges = np.linspace(x.min(), x.max(), n), np.linspace(y.min(), y.max(), n)
|
||||||
|
hist, xedges, yedges = np.histogram2d(x, y, (xedges, yedges))
|
||||||
|
xidx = np.clip(np.digitize(x, xedges) - 1, 0, hist.shape[0] - 1)
|
||||||
|
yidx = np.clip(np.digitize(y, yedges) - 1, 0, hist.shape[1] - 1)
|
||||||
|
return np.log(hist[xidx, yidx])
|
||||||
|
|
||||||
|
|
||||||
|
def butter_lowpass_filtfilt(data, cutoff=1500, fs=50000, order=5):
|
||||||
|
from scipy.signal import butter, filtfilt
|
||||||
|
|
||||||
|
# https://stackoverflow.com/questions/28536191/how-to-filter-smooth-with-scipy-numpy
|
||||||
|
def butter_lowpass(cutoff, fs, order):
|
||||||
|
nyq = 0.5 * fs
|
||||||
|
normal_cutoff = cutoff / nyq
|
||||||
|
return butter(order, normal_cutoff, btype='low', analog=False)
|
||||||
|
|
||||||
|
b, a = butter_lowpass(cutoff, fs, order=order)
|
||||||
|
return filtfilt(b, a, data) # forward-backward filter
|
||||||
|
|
||||||
|
|
||||||
|
def output_to_target(output):
|
||||||
|
# Convert model output to target format [batch_id, class_id, x, y, w, h, conf]
|
||||||
|
targets = []
|
||||||
|
for i, o in enumerate(output):
|
||||||
|
for *box, conf, cls in o.cpu().numpy():
|
||||||
|
targets.append([i, cls, *list(*xyxy2xywh(np.array(box)[None])), conf])
|
||||||
|
return np.array(targets)
|
||||||
|
|
||||||
|
|
||||||
|
def plot_images(images, targets, paths=None, fname='images.jpg', names=None, max_size=1920, max_subplots=16):
|
||||||
|
# Plot image grid with labels
|
||||||
|
if isinstance(images, torch.Tensor):
|
||||||
|
images = images.cpu().float().numpy()
|
||||||
|
if isinstance(targets, torch.Tensor):
|
||||||
|
targets = targets.cpu().numpy()
|
||||||
|
if np.max(images[0]) <= 1:
|
||||||
|
images *= 255 # de-normalise (optional)
|
||||||
|
bs, _, h, w = images.shape # batch size, _, height, width
|
||||||
|
bs = min(bs, max_subplots) # limit plot images
|
||||||
|
ns = np.ceil(bs ** 0.5) # number of subplots (square)
|
||||||
|
|
||||||
|
# Build Image
|
||||||
|
mosaic = np.full((int(ns * h), int(ns * w), 3), 255, dtype=np.uint8) # init
|
||||||
|
for i, im in enumerate(images):
|
||||||
|
if i == max_subplots: # if last batch has fewer images than we expect
|
||||||
|
break
|
||||||
|
x, y = int(w * (i // ns)), int(h * (i % ns)) # block origin
|
||||||
|
im = im.transpose(1, 2, 0)
|
||||||
|
mosaic[y:y + h, x:x + w, :] = im
|
||||||
|
|
||||||
|
# Resize (optional)
|
||||||
|
scale = max_size / ns / max(h, w)
|
||||||
|
if scale < 1:
|
||||||
|
h = math.ceil(scale * h)
|
||||||
|
w = math.ceil(scale * w)
|
||||||
|
mosaic = cv2.resize(mosaic, tuple(int(x * ns) for x in (w, h)))
|
||||||
|
|
||||||
|
# Annotate
|
||||||
|
fs = int((h + w) * ns * 0.01) # font size
|
||||||
|
annotator = Annotator(mosaic, line_width=round(fs / 10), font_size=fs, pil=True)
|
||||||
|
for i in range(i + 1):
|
||||||
|
x, y = int(w * (i // ns)), int(h * (i % ns)) # block origin
|
||||||
|
annotator.rectangle([x, y, x + w, y + h], None, (255, 255, 255), width=2) # borders
|
||||||
|
if paths:
|
||||||
|
annotator.text((x + 5, y + 5 + h), text=Path(paths[i]).name[:40], txt_color=(220, 220, 220)) # filenames
|
||||||
|
if len(targets) > 0:
|
||||||
|
ti = targets[targets[:, 0] == i] # image targets
|
||||||
|
boxes = xywh2xyxy(ti[:, 2:6]).T
|
||||||
|
classes = ti[:, 1].astype('int')
|
||||||
|
labels = ti.shape[1] == 6 # labels if no conf column
|
||||||
|
conf = None if labels else ti[:, 6] # check for confidence presence (label vs pred)
|
||||||
|
|
||||||
|
if boxes.shape[1]:
|
||||||
|
if boxes.max() <= 1.01: # if normalized with tolerance 0.01
|
||||||
|
boxes[[0, 2]] *= w # scale to pixels
|
||||||
|
boxes[[1, 3]] *= h
|
||||||
|
elif scale < 1: # absolute coords need scale if image scales
|
||||||
|
boxes *= scale
|
||||||
|
boxes[[0, 2]] += x
|
||||||
|
boxes[[1, 3]] += y
|
||||||
|
for j, box in enumerate(boxes.T.tolist()):
|
||||||
|
cls = classes[j]
|
||||||
|
color = colors(cls)
|
||||||
|
cls = names[cls] if names else cls
|
||||||
|
if labels or conf[j] > 0.25: # 0.25 conf thresh
|
||||||
|
label = f'{cls}' if labels else f'{cls} {conf[j]:.1f}'
|
||||||
|
annotator.box_label(box, label, color=color)
|
||||||
|
annotator.im.save(fname) # save
|
||||||
|
|
||||||
|
|
||||||
|
def plot_lr_scheduler(optimizer, scheduler, epochs=300, save_dir=''):
|
||||||
|
# Plot LR simulating training for full epochs
|
||||||
|
optimizer, scheduler = copy(optimizer), copy(scheduler) # do not modify originals
|
||||||
|
y = []
|
||||||
|
for _ in range(epochs):
|
||||||
|
scheduler.step()
|
||||||
|
y.append(optimizer.param_groups[0]['lr'])
|
||||||
|
plt.plot(y, '.-', label='LR')
|
||||||
|
plt.xlabel('epoch')
|
||||||
|
plt.ylabel('LR')
|
||||||
|
plt.grid()
|
||||||
|
plt.xlim(0, epochs)
|
||||||
|
plt.ylim(0)
|
||||||
|
plt.savefig(Path(save_dir) / 'LR.png', dpi=200)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def plot_val_txt(): # from utils.plots import *; plot_val()
|
||||||
|
# Plot val.txt histograms
|
||||||
|
x = np.loadtxt('val.txt', dtype=np.float32)
|
||||||
|
box = xyxy2xywh(x[:, :4])
|
||||||
|
cx, cy = box[:, 0], box[:, 1]
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(6, 6), tight_layout=True)
|
||||||
|
ax.hist2d(cx, cy, bins=600, cmax=10, cmin=0)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
plt.savefig('hist2d.png', dpi=300)
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(1, 2, figsize=(12, 6), tight_layout=True)
|
||||||
|
ax[0].hist(cx, bins=600)
|
||||||
|
ax[1].hist(cy, bins=600)
|
||||||
|
plt.savefig('hist1d.png', dpi=200)
|
||||||
|
|
||||||
|
|
||||||
|
def plot_targets_txt(): # from utils.plots import *; plot_targets_txt()
|
||||||
|
# Plot targets.txt histograms
|
||||||
|
x = np.loadtxt('targets.txt', dtype=np.float32).T
|
||||||
|
s = ['x targets', 'y targets', 'width targets', 'height targets']
|
||||||
|
fig, ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True)
|
||||||
|
ax = ax.ravel()
|
||||||
|
for i in range(4):
|
||||||
|
ax[i].hist(x[i], bins=100, label=f'{x[i].mean():.3g} +/- {x[i].std():.3g}')
|
||||||
|
ax[i].legend()
|
||||||
|
ax[i].set_title(s[i])
|
||||||
|
plt.savefig('targets.jpg', dpi=200)
|
||||||
|
|
||||||
|
|
||||||
|
def plot_val_study(file='', dir='', x=None): # from utils.plots import *; plot_val_study()
|
||||||
|
# Plot file=study.txt generated by val.py (or plot all study*.txt in dir)
|
||||||
|
save_dir = Path(file).parent if file else Path(dir)
|
||||||
|
plot2 = False # plot additional results
|
||||||
|
if plot2:
|
||||||
|
ax = plt.subplots(2, 4, figsize=(10, 6), tight_layout=True)[1].ravel()
|
||||||
|
|
||||||
|
fig2, ax2 = plt.subplots(1, 1, figsize=(8, 4), tight_layout=True)
|
||||||
|
# for f in [save_dir / f'study_coco_{x}.txt' for x in ['yolov3', 'yolov3-spp', 'yolov3-tiny']]:
|
||||||
|
for f in sorted(save_dir.glob('study*.txt')):
|
||||||
|
y = np.loadtxt(f, dtype=np.float32, usecols=[0, 1, 2, 3, 7, 8, 9], ndmin=2).T
|
||||||
|
x = np.arange(y.shape[1]) if x is None else np.array(x)
|
||||||
|
if plot2:
|
||||||
|
s = ['P', 'R', 'mAP@.5', 'mAP@.5:.95', 't_preprocess (ms/img)', 't_inference (ms/img)', 't_NMS (ms/img)']
|
||||||
|
for i in range(7):
|
||||||
|
ax[i].plot(x, y[i], '.-', linewidth=2, markersize=8)
|
||||||
|
ax[i].set_title(s[i])
|
||||||
|
|
||||||
|
j = y[3].argmax() + 1
|
||||||
|
ax2.plot(y[5, 1:j], y[3, 1:j] * 1E2, '.-', linewidth=2, markersize=8,
|
||||||
|
label=f.stem.replace('study_coco_', '').replace('yolo', 'YOLO'))
|
||||||
|
|
||||||
|
ax2.plot(1E3 / np.array([209, 140, 97, 58, 35, 18]), [34.6, 40.5, 43.0, 47.5, 49.7, 51.5],
|
||||||
|
'k.-', linewidth=2, markersize=8, alpha=.25, label='EfficientDet')
|
||||||
|
|
||||||
|
ax2.grid(alpha=0.2)
|
||||||
|
ax2.set_yticks(np.arange(20, 60, 5))
|
||||||
|
ax2.set_xlim(0, 57)
|
||||||
|
ax2.set_ylim(25, 55)
|
||||||
|
ax2.set_xlabel('GPU Speed (ms/img)')
|
||||||
|
ax2.set_ylabel('COCO AP val')
|
||||||
|
ax2.legend(loc='lower right')
|
||||||
|
f = save_dir / 'study.png'
|
||||||
|
print(f'Saving {f}...')
|
||||||
|
plt.savefig(f, dpi=300)
|
||||||
|
|
||||||
|
|
||||||
|
@try_except # known issue https://github.com/ultralytics/yolov5/issues/5395
|
||||||
|
@Timeout(30) # known issue https://github.com/ultralytics/yolov5/issues/5611
|
||||||
|
def plot_labels(labels, names=(), save_dir=Path('')):
|
||||||
|
# plot dataset labels
|
||||||
|
LOGGER.info(f"Plotting labels to {save_dir / 'labels.jpg'}... ")
|
||||||
|
c, b = labels[:, 0], labels[:, 1:].transpose() # classes, boxes
|
||||||
|
nc = int(c.max() + 1) # number of classes
|
||||||
|
x = pd.DataFrame(b.transpose(), columns=['x', 'y', 'width', 'height'])
|
||||||
|
|
||||||
|
# seaborn correlogram
|
||||||
|
sn.pairplot(x, corner=True, diag_kind='auto', kind='hist', diag_kws=dict(bins=50), plot_kws=dict(pmax=0.9))
|
||||||
|
plt.savefig(save_dir / 'labels_correlogram.jpg', dpi=200)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
# matplotlib labels
|
||||||
|
matplotlib.use('svg') # faster
|
||||||
|
ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True)[1].ravel()
|
||||||
|
y = ax[0].hist(c, bins=np.linspace(0, nc, nc + 1) - 0.5, rwidth=0.8)
|
||||||
|
# [y[2].patches[i].set_color([x / 255 for x in colors(i)]) for i in range(nc)] # update colors bug #3195
|
||||||
|
ax[0].set_ylabel('instances')
|
||||||
|
if 0 < len(names) < 30:
|
||||||
|
ax[0].set_xticks(range(len(names)))
|
||||||
|
ax[0].set_xticklabels(names, rotation=90, fontsize=10)
|
||||||
|
else:
|
||||||
|
ax[0].set_xlabel('classes')
|
||||||
|
sn.histplot(x, x='x', y='y', ax=ax[2], bins=50, pmax=0.9)
|
||||||
|
sn.histplot(x, x='width', y='height', ax=ax[3], bins=50, pmax=0.9)
|
||||||
|
|
||||||
|
# rectangles
|
||||||
|
labels[:, 1:3] = 0.5 # center
|
||||||
|
labels[:, 1:] = xywh2xyxy(labels[:, 1:]) * 2000
|
||||||
|
img = Image.fromarray(np.ones((2000, 2000, 3), dtype=np.uint8) * 255)
|
||||||
|
for cls, *box in labels[:1000]:
|
||||||
|
ImageDraw.Draw(img).rectangle(box, width=1, outline=colors(cls)) # plot
|
||||||
|
ax[1].imshow(img)
|
||||||
|
ax[1].axis('off')
|
||||||
|
|
||||||
|
for a in [0, 1, 2, 3]:
|
||||||
|
for s in ['top', 'right', 'left', 'bottom']:
|
||||||
|
ax[a].spines[s].set_visible(False)
|
||||||
|
|
||||||
|
plt.savefig(save_dir / 'labels.jpg', dpi=200)
|
||||||
|
matplotlib.use('Agg')
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def plot_evolve(evolve_csv='path/to/evolve.csv'): # from utils.plots import *; plot_evolve()
|
||||||
|
# Plot evolve.csv hyp evolution results
|
||||||
|
evolve_csv = Path(evolve_csv)
|
||||||
|
data = pd.read_csv(evolve_csv)
|
||||||
|
keys = [x.strip() for x in data.columns]
|
||||||
|
x = data.values
|
||||||
|
f = fitness(x)
|
||||||
|
j = np.argmax(f) # max fitness index
|
||||||
|
plt.figure(figsize=(10, 12), tight_layout=True)
|
||||||
|
matplotlib.rc('font', **{'size': 8})
|
||||||
|
for i, k in enumerate(keys[7:]):
|
||||||
|
v = x[:, 7 + i]
|
||||||
|
mu = v[j] # best single result
|
||||||
|
plt.subplot(6, 5, i + 1)
|
||||||
|
plt.scatter(v, f, c=hist2d(v, f, 20), cmap='viridis', alpha=.8, edgecolors='none')
|
||||||
|
plt.plot(mu, f.max(), 'k+', markersize=15)
|
||||||
|
plt.title(f'{k} = {mu:.3g}', fontdict={'size': 9}) # limit to 40 characters
|
||||||
|
if i % 5 != 0:
|
||||||
|
plt.yticks([])
|
||||||
|
print(f'{k:>15}: {mu:.3g}')
|
||||||
|
f = evolve_csv.with_suffix('.png') # filename
|
||||||
|
plt.savefig(f, dpi=200)
|
||||||
|
plt.close()
|
||||||
|
print(f'Saved {f}')
|
||||||
|
|
||||||
|
|
||||||
|
def plot_results(file='path/to/results.csv', dir=''):
|
||||||
|
# Plot training results.csv. Usage: from utils.plots import *; plot_results('path/to/results.csv')
|
||||||
|
save_dir = Path(file).parent if file else Path(dir)
|
||||||
|
fig, ax = plt.subplots(2, 5, figsize=(12, 6), tight_layout=True)
|
||||||
|
ax = ax.ravel()
|
||||||
|
files = list(save_dir.glob('results*.csv'))
|
||||||
|
assert len(files), f'No results.csv files found in {save_dir.resolve()}, nothing to plot.'
|
||||||
|
for fi, f in enumerate(files):
|
||||||
|
try:
|
||||||
|
data = pd.read_csv(f)
|
||||||
|
s = [x.strip() for x in data.columns]
|
||||||
|
x = data.values[:, 0]
|
||||||
|
for i, j in enumerate([1, 2, 3, 4, 5, 8, 9, 10, 6, 7]):
|
||||||
|
y = data.values[:, j]
|
||||||
|
# y[y == 0] = np.nan # don't show zero values
|
||||||
|
ax[i].plot(x, y, marker='.', label=f.stem, linewidth=2, markersize=8)
|
||||||
|
ax[i].set_title(s[j], fontsize=12)
|
||||||
|
# if j in [8, 9, 10]: # share train and val loss y axes
|
||||||
|
# ax[i].get_shared_y_axes().join(ax[i], ax[i - 5])
|
||||||
|
except Exception as e:
|
||||||
|
print(f'Warning: Plotting error for {f}: {e}')
|
||||||
|
ax[1].legend()
|
||||||
|
fig.savefig(save_dir / 'results.png', dpi=200)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def profile_idetection(start=0, stop=0, labels=(), save_dir=''):
|
||||||
|
# Plot iDetection '*.txt' per-image logs. from utils.plots import *; profile_idetection()
|
||||||
|
ax = plt.subplots(2, 4, figsize=(12, 6), tight_layout=True)[1].ravel()
|
||||||
|
s = ['Images', 'Free Storage (GB)', 'RAM Usage (GB)', 'Battery', 'dt_raw (ms)', 'dt_smooth (ms)', 'real-world FPS']
|
||||||
|
files = list(Path(save_dir).glob('frames*.txt'))
|
||||||
|
for fi, f in enumerate(files):
|
||||||
|
try:
|
||||||
|
results = np.loadtxt(f, ndmin=2).T[:, 90:-30] # clip first and last rows
|
||||||
|
n = results.shape[1] # number of rows
|
||||||
|
x = np.arange(start, min(stop, n) if stop else n)
|
||||||
|
results = results[:, x]
|
||||||
|
t = (results[0] - results[0].min()) # set t0=0s
|
||||||
|
results[0] = x
|
||||||
|
for i, a in enumerate(ax):
|
||||||
|
if i < len(results):
|
||||||
|
label = labels[fi] if len(labels) else f.stem.replace('frames_', '')
|
||||||
|
a.plot(t, results[i], marker='.', label=label, linewidth=1, markersize=5)
|
||||||
|
a.set_title(s[i])
|
||||||
|
a.set_xlabel('time (s)')
|
||||||
|
# if fi == len(files) - 1:
|
||||||
|
# a.set_ylim(bottom=0)
|
||||||
|
for side in ['top', 'right']:
|
||||||
|
a.spines[side].set_visible(False)
|
||||||
|
else:
|
||||||
|
a.remove()
|
||||||
|
except Exception as e:
|
||||||
|
print(f'Warning: Plotting error for {f}; {e}')
|
||||||
|
ax[1].legend()
|
||||||
|
plt.savefig(Path(save_dir) / 'idetection_profile.png', dpi=200)
|
||||||
|
|
||||||
|
|
||||||
|
def save_one_box(xyxy, im, file='image.jpg', gain=1.02, pad=10, square=False, BGR=False, save=True):
|
||||||
|
# Save image crop as {file} with crop size multiple {gain} and {pad} pixels. Save and/or return crop
|
||||||
|
xyxy = torch.tensor(xyxy).view(-1, 4)
|
||||||
|
b = xyxy2xywh(xyxy) # boxes
|
||||||
|
if square:
|
||||||
|
b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1) # attempt rectangle to square
|
||||||
|
b[:, 2:] = b[:, 2:] * gain + pad # box wh * gain + pad
|
||||||
|
xyxy = xywh2xyxy(b).long()
|
||||||
|
clip_coords(xyxy, im.shape)
|
||||||
|
crop = im[int(xyxy[0, 1]):int(xyxy[0, 3]), int(xyxy[0, 0]):int(xyxy[0, 2]), ::(1 if BGR else -1)]
|
||||||
|
if save:
|
||||||
|
file.parent.mkdir(parents=True, exist_ok=True) # make directory
|
||||||
|
cv2.imwrite(str(increment_path(file).with_suffix('.jpg')), crop)
|
||||||
|
return crop
|
||||||
0
ros2_ws/src/yolov3_ros/utils/segment/__init__.py
Normal file
0
ros2_ws/src/yolov3_ros/utils/segment/__init__.py
Normal file
104
ros2_ws/src/yolov3_ros/utils/segment/augmentations.py
Normal file
104
ros2_ws/src/yolov3_ros/utils/segment/augmentations.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Image augmentation functions
|
||||||
|
"""
|
||||||
|
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from ..augmentations import box_candidates
|
||||||
|
from ..general import resample_segments, segment2box
|
||||||
|
|
||||||
|
|
||||||
|
def mixup(im, labels, segments, im2, labels2, segments2):
|
||||||
|
# Applies MixUp augmentation https://arxiv.org/pdf/1710.09412.pdf
|
||||||
|
r = np.random.beta(32.0, 32.0) # mixup ratio, alpha=beta=32.0
|
||||||
|
im = (im * r + im2 * (1 - r)).astype(np.uint8)
|
||||||
|
labels = np.concatenate((labels, labels2), 0)
|
||||||
|
segments = np.concatenate((segments, segments2), 0)
|
||||||
|
return im, labels, segments
|
||||||
|
|
||||||
|
|
||||||
|
def random_perspective(im,
|
||||||
|
targets=(),
|
||||||
|
segments=(),
|
||||||
|
degrees=10,
|
||||||
|
translate=.1,
|
||||||
|
scale=.1,
|
||||||
|
shear=10,
|
||||||
|
perspective=0.0,
|
||||||
|
border=(0, 0)):
|
||||||
|
# torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10))
|
||||||
|
# targets = [cls, xyxy]
|
||||||
|
|
||||||
|
height = im.shape[0] + border[0] * 2 # shape(h,w,c)
|
||||||
|
width = im.shape[1] + border[1] * 2
|
||||||
|
|
||||||
|
# Center
|
||||||
|
C = np.eye(3)
|
||||||
|
C[0, 2] = -im.shape[1] / 2 # x translation (pixels)
|
||||||
|
C[1, 2] = -im.shape[0] / 2 # y translation (pixels)
|
||||||
|
|
||||||
|
# Perspective
|
||||||
|
P = np.eye(3)
|
||||||
|
P[2, 0] = random.uniform(-perspective, perspective) # x perspective (about y)
|
||||||
|
P[2, 1] = random.uniform(-perspective, perspective) # y perspective (about x)
|
||||||
|
|
||||||
|
# Rotation and Scale
|
||||||
|
R = np.eye(3)
|
||||||
|
a = random.uniform(-degrees, degrees)
|
||||||
|
# a += random.choice([-180, -90, 0, 90]) # add 90deg rotations to small rotations
|
||||||
|
s = random.uniform(1 - scale, 1 + scale)
|
||||||
|
# s = 2 ** random.uniform(-scale, scale)
|
||||||
|
R[:2] = cv2.getRotationMatrix2D(angle=a, center=(0, 0), scale=s)
|
||||||
|
|
||||||
|
# Shear
|
||||||
|
S = np.eye(3)
|
||||||
|
S[0, 1] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # x shear (deg)
|
||||||
|
S[1, 0] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # y shear (deg)
|
||||||
|
|
||||||
|
# Translation
|
||||||
|
T = np.eye(3)
|
||||||
|
T[0, 2] = (random.uniform(0.5 - translate, 0.5 + translate) * width) # x translation (pixels)
|
||||||
|
T[1, 2] = (random.uniform(0.5 - translate, 0.5 + translate) * height) # y translation (pixels)
|
||||||
|
|
||||||
|
# Combined rotation matrix
|
||||||
|
M = T @ S @ R @ P @ C # order of operations (right to left) is IMPORTANT
|
||||||
|
if (border[0] != 0) or (border[1] != 0) or (M != np.eye(3)).any(): # image changed
|
||||||
|
if perspective:
|
||||||
|
im = cv2.warpPerspective(im, M, dsize=(width, height), borderValue=(114, 114, 114))
|
||||||
|
else: # affine
|
||||||
|
im = cv2.warpAffine(im, M[:2], dsize=(width, height), borderValue=(114, 114, 114))
|
||||||
|
|
||||||
|
# Visualize
|
||||||
|
# import matplotlib.pyplot as plt
|
||||||
|
# ax = plt.subplots(1, 2, figsize=(12, 6))[1].ravel()
|
||||||
|
# ax[0].imshow(im[:, :, ::-1]) # base
|
||||||
|
# ax[1].imshow(im2[:, :, ::-1]) # warped
|
||||||
|
|
||||||
|
# Transform label coordinates
|
||||||
|
n = len(targets)
|
||||||
|
new_segments = []
|
||||||
|
if n:
|
||||||
|
new = np.zeros((n, 4))
|
||||||
|
segments = resample_segments(segments) # upsample
|
||||||
|
for i, segment in enumerate(segments):
|
||||||
|
xy = np.ones((len(segment), 3))
|
||||||
|
xy[:, :2] = segment
|
||||||
|
xy = xy @ M.T # transform
|
||||||
|
xy = (xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2]) # perspective rescale or affine
|
||||||
|
|
||||||
|
# clip
|
||||||
|
new[i] = segment2box(xy, width, height)
|
||||||
|
new_segments.append(xy)
|
||||||
|
|
||||||
|
# filter candidates
|
||||||
|
i = box_candidates(box1=targets[:, 1:5].T * s, box2=new.T, area_thr=0.01)
|
||||||
|
targets = targets[i]
|
||||||
|
targets[:, 1:5] = new[i]
|
||||||
|
new_segments = np.array(new_segments)[i]
|
||||||
|
|
||||||
|
return im, targets, new_segments
|
||||||
332
ros2_ws/src/yolov3_ros/utils/segment/dataloaders.py
Normal file
332
ros2_ws/src/yolov3_ros/utils/segment/dataloaders.py
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
# YOLOv3 🚀 by Ultralytics, GPL-3.0 license
|
||||||
|
"""
|
||||||
|
Dataloaders
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
from torch.utils.data import DataLoader, distributed
|
||||||
|
|
||||||
|
from ..augmentations import augment_hsv, copy_paste, letterbox
|
||||||
|
from ..dataloaders import InfiniteDataLoader, LoadImagesAndLabels, seed_worker
|
||||||
|
from ..general import LOGGER, xyn2xy, xywhn2xyxy, xyxy2xywhn
|
||||||
|
from ..torch_utils import torch_distributed_zero_first
|
||||||
|
from .augmentations import mixup, random_perspective
|
||||||
|
|
||||||
|
RANK = int(os.getenv('RANK', -1))
|
||||||
|
|
||||||
|
|
||||||
|
def create_dataloader(path,
|
||||||
|
imgsz,
|
||||||
|
batch_size,
|
||||||
|
stride,
|
||||||
|
single_cls=False,
|
||||||
|
hyp=None,
|
||||||
|
augment=False,
|
||||||
|
cache=False,
|
||||||
|
pad=0.0,
|
||||||
|
rect=False,
|
||||||
|
rank=-1,
|
||||||
|
workers=8,
|
||||||
|
image_weights=False,
|
||||||
|
quad=False,
|
||||||
|
prefix='',
|
||||||
|
shuffle=False,
|
||||||
|
mask_downsample_ratio=1,
|
||||||
|
overlap_mask=False,
|
||||||
|
seed=0):
|
||||||
|
if rect and shuffle:
|
||||||
|
LOGGER.warning('WARNING ⚠️ --rect is incompatible with DataLoader shuffle, setting shuffle=False')
|
||||||
|
shuffle = False
|
||||||
|
with torch_distributed_zero_first(rank): # init dataset *.cache only once if DDP
|
||||||
|
dataset = LoadImagesAndLabelsAndMasks(
|
||||||
|
path,
|
||||||
|
imgsz,
|
||||||
|
batch_size,
|
||||||
|
augment=augment, # augmentation
|
||||||
|
hyp=hyp, # hyperparameters
|
||||||
|
rect=rect, # rectangular batches
|
||||||
|
cache_images=cache,
|
||||||
|
single_cls=single_cls,
|
||||||
|
stride=int(stride),
|
||||||
|
pad=pad,
|
||||||
|
image_weights=image_weights,
|
||||||
|
prefix=prefix,
|
||||||
|
downsample_ratio=mask_downsample_ratio,
|
||||||
|
overlap=overlap_mask)
|
||||||
|
|
||||||
|
batch_size = min(batch_size, len(dataset))
|
||||||
|
nd = torch.cuda.device_count() # number of CUDA devices
|
||||||
|
nw = min([os.cpu_count() // max(nd, 1), batch_size if batch_size > 1 else 0, workers]) # number of workers
|
||||||
|
sampler = None if rank == -1 else distributed.DistributedSampler(dataset, shuffle=shuffle)
|
||||||
|
loader = DataLoader if image_weights else InfiniteDataLoader # only DataLoader allows for attribute updates
|
||||||
|
generator = torch.Generator()
|
||||||
|
generator.manual_seed(6148914691236517205 + seed + RANK)
|
||||||
|
return loader(
|
||||||
|
dataset,
|
||||||
|
batch_size=batch_size,
|
||||||
|
shuffle=shuffle and sampler is None,
|
||||||
|
num_workers=nw,
|
||||||
|
sampler=sampler,
|
||||||
|
pin_memory=True,
|
||||||
|
collate_fn=LoadImagesAndLabelsAndMasks.collate_fn4 if quad else LoadImagesAndLabelsAndMasks.collate_fn,
|
||||||
|
worker_init_fn=seed_worker,
|
||||||
|
generator=generator,
|
||||||
|
), dataset
|
||||||
|
|
||||||
|
|
||||||
|
class LoadImagesAndLabelsAndMasks(LoadImagesAndLabels): # for training/testing
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
path,
|
||||||
|
img_size=640,
|
||||||
|
batch_size=16,
|
||||||
|
augment=False,
|
||||||
|
hyp=None,
|
||||||
|
rect=False,
|
||||||
|
image_weights=False,
|
||||||
|
cache_images=False,
|
||||||
|
single_cls=False,
|
||||||
|
stride=32,
|
||||||
|
pad=0,
|
||||||
|
min_items=0,
|
||||||
|
prefix='',
|
||||||
|
downsample_ratio=1,
|
||||||
|
overlap=False,
|
||||||
|
):
|
||||||
|
super().__init__(path, img_size, batch_size, augment, hyp, rect, image_weights, cache_images, single_cls,
|
||||||
|
stride, pad, min_items, prefix)
|
||||||
|
self.downsample_ratio = downsample_ratio
|
||||||
|
self.overlap = overlap
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
index = self.indices[index] # linear, shuffled, or image_weights
|
||||||
|
|
||||||
|
hyp = self.hyp
|
||||||
|
mosaic = self.mosaic and random.random() < hyp['mosaic']
|
||||||
|
masks = []
|
||||||
|
if mosaic:
|
||||||
|
# Load mosaic
|
||||||
|
img, labels, segments = self.load_mosaic(index)
|
||||||
|
shapes = None
|
||||||
|
|
||||||
|
# MixUp augmentation
|
||||||
|
if random.random() < hyp['mixup']:
|
||||||
|
img, labels, segments = mixup(img, labels, segments, *self.load_mosaic(random.randint(0, self.n - 1)))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Load image
|
||||||
|
img, (h0, w0), (h, w) = self.load_image(index)
|
||||||
|
|
||||||
|
# Letterbox
|
||||||
|
shape = self.batch_shapes[self.batch[index]] if self.rect else self.img_size # final letterboxed shape
|
||||||
|
img, ratio, pad = letterbox(img, shape, auto=False, scaleup=self.augment)
|
||||||
|
shapes = (h0, w0), ((h / h0, w / w0), pad) # for COCO mAP rescaling
|
||||||
|
|
||||||
|
labels = self.labels[index].copy()
|
||||||
|
# [array, array, ....], array.shape=(num_points, 2), xyxyxyxy
|
||||||
|
segments = self.segments[index].copy()
|
||||||
|
if len(segments):
|
||||||
|
for i_s in range(len(segments)):
|
||||||
|
segments[i_s] = xyn2xy(
|
||||||
|
segments[i_s],
|
||||||
|
ratio[0] * w,
|
||||||
|
ratio[1] * h,
|
||||||
|
padw=pad[0],
|
||||||
|
padh=pad[1],
|
||||||
|
)
|
||||||
|
if labels.size: # normalized xywh to pixel xyxy format
|
||||||
|
labels[:, 1:] = xywhn2xyxy(labels[:, 1:], ratio[0] * w, ratio[1] * h, padw=pad[0], padh=pad[1])
|
||||||
|
|
||||||
|
if self.augment:
|
||||||
|
img, labels, segments = random_perspective(img,
|
||||||
|
labels,
|
||||||
|
segments=segments,
|
||||||
|
degrees=hyp['degrees'],
|
||||||
|
translate=hyp['translate'],
|
||||||
|
scale=hyp['scale'],
|
||||||
|
shear=hyp['shear'],
|
||||||
|
perspective=hyp['perspective'])
|
||||||
|
|
||||||
|
nl = len(labels) # number of labels
|
||||||
|
if nl:
|
||||||
|
labels[:, 1:5] = xyxy2xywhn(labels[:, 1:5], w=img.shape[1], h=img.shape[0], clip=True, eps=1e-3)
|
||||||
|
if self.overlap:
|
||||||
|
masks, sorted_idx = polygons2masks_overlap(img.shape[:2],
|
||||||
|
segments,
|
||||||
|
downsample_ratio=self.downsample_ratio)
|
||||||
|
masks = masks[None] # (640, 640) -> (1, 640, 640)
|
||||||
|
labels = labels[sorted_idx]
|
||||||
|
else:
|
||||||
|
masks = polygons2masks(img.shape[:2], segments, color=1, downsample_ratio=self.downsample_ratio)
|
||||||
|
|
||||||
|
masks = (torch.from_numpy(masks) if len(masks) else torch.zeros(1 if self.overlap else nl, img.shape[0] //
|
||||||
|
self.downsample_ratio, img.shape[1] //
|
||||||
|
self.downsample_ratio))
|
||||||
|
# TODO: albumentations support
|
||||||
|
if self.augment:
|
||||||
|
# Albumentations
|
||||||
|
# there are some augmentation that won't change boxes and masks,
|
||||||
|
# so just be it for now.
|
||||||
|
img, labels = self.albumentations(img, labels)
|
||||||
|
nl = len(labels) # update after albumentations
|
||||||
|
|
||||||
|
# HSV color-space
|
||||||
|
augment_hsv(img, hgain=hyp['hsv_h'], sgain=hyp['hsv_s'], vgain=hyp['hsv_v'])
|
||||||
|
|
||||||
|
# Flip up-down
|
||||||
|
if random.random() < hyp['flipud']:
|
||||||
|
img = np.flipud(img)
|
||||||
|
if nl:
|
||||||
|
labels[:, 2] = 1 - labels[:, 2]
|
||||||
|
masks = torch.flip(masks, dims=[1])
|
||||||
|
|
||||||
|
# Flip left-right
|
||||||
|
if random.random() < hyp['fliplr']:
|
||||||
|
img = np.fliplr(img)
|
||||||
|
if nl:
|
||||||
|
labels[:, 1] = 1 - labels[:, 1]
|
||||||
|
masks = torch.flip(masks, dims=[2])
|
||||||
|
|
||||||
|
# Cutouts # labels = cutout(img, labels, p=0.5)
|
||||||
|
|
||||||
|
labels_out = torch.zeros((nl, 6))
|
||||||
|
if nl:
|
||||||
|
labels_out[:, 1:] = torch.from_numpy(labels)
|
||||||
|
|
||||||
|
# Convert
|
||||||
|
img = img.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB
|
||||||
|
img = np.ascontiguousarray(img)
|
||||||
|
|
||||||
|
return (torch.from_numpy(img), labels_out, self.im_files[index], shapes, masks)
|
||||||
|
|
||||||
|
def load_mosaic(self, index):
|
||||||
|
# YOLOv5 4-mosaic loader. Loads 1 image + 3 random images into a 4-image mosaic
|
||||||
|
labels4, segments4 = [], []
|
||||||
|
s = self.img_size
|
||||||
|
yc, xc = (int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border) # mosaic center x, y
|
||||||
|
|
||||||
|
# 3 additional image indices
|
||||||
|
indices = [index] + random.choices(self.indices, k=3) # 3 additional image indices
|
||||||
|
for i, index in enumerate(indices):
|
||||||
|
# Load image
|
||||||
|
img, _, (h, w) = self.load_image(index)
|
||||||
|
|
||||||
|
# place img in img4
|
||||||
|
if i == 0: # top left
|
||||||
|
img4 = np.full((s * 2, s * 2, img.shape[2]), 114, dtype=np.uint8) # base image with 4 tiles
|
||||||
|
x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, yc # xmin, ymin, xmax, ymax (large image)
|
||||||
|
x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h # xmin, ymin, xmax, ymax (small image)
|
||||||
|
elif i == 1: # top right
|
||||||
|
x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), yc
|
||||||
|
x1b, y1b, x2b, y2b = 0, h - (y2a - y1a), min(w, x2a - x1a), h
|
||||||
|
elif i == 2: # bottom left
|
||||||
|
x1a, y1a, x2a, y2a = max(xc - w, 0), yc, xc, min(s * 2, yc + h)
|
||||||
|
x1b, y1b, x2b, y2b = w - (x2a - x1a), 0, w, min(y2a - y1a, h)
|
||||||
|
elif i == 3: # bottom right
|
||||||
|
x1a, y1a, x2a, y2a = xc, yc, min(xc + w, s * 2), min(s * 2, yc + h)
|
||||||
|
x1b, y1b, x2b, y2b = 0, 0, min(w, x2a - x1a), min(y2a - y1a, h)
|
||||||
|
|
||||||
|
img4[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b] # img4[ymin:ymax, xmin:xmax]
|
||||||
|
padw = x1a - x1b
|
||||||
|
padh = y1a - y1b
|
||||||
|
|
||||||
|
labels, segments = self.labels[index].copy(), self.segments[index].copy()
|
||||||
|
|
||||||
|
if labels.size:
|
||||||
|
labels[:, 1:] = xywhn2xyxy(labels[:, 1:], w, h, padw, padh) # normalized xywh to pixel xyxy format
|
||||||
|
segments = [xyn2xy(x, w, h, padw, padh) for x in segments]
|
||||||
|
labels4.append(labels)
|
||||||
|
segments4.extend(segments)
|
||||||
|
|
||||||
|
# Concat/clip labels
|
||||||
|
labels4 = np.concatenate(labels4, 0)
|
||||||
|
for x in (labels4[:, 1:], *segments4):
|
||||||
|
np.clip(x, 0, 2 * s, out=x) # clip when using random_perspective()
|
||||||
|
# img4, labels4 = replicate(img4, labels4) # replicate
|
||||||
|
|
||||||
|
# Augment
|
||||||
|
img4, labels4, segments4 = copy_paste(img4, labels4, segments4, p=self.hyp['copy_paste'])
|
||||||
|
img4, labels4, segments4 = random_perspective(img4,
|
||||||
|
labels4,
|
||||||
|
segments4,
|
||||||
|
degrees=self.hyp['degrees'],
|
||||||
|
translate=self.hyp['translate'],
|
||||||
|
scale=self.hyp['scale'],
|
||||||
|
shear=self.hyp['shear'],
|
||||||
|
perspective=self.hyp['perspective'],
|
||||||
|
border=self.mosaic_border) # border to remove
|
||||||
|
return img4, labels4, segments4
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def collate_fn(batch):
|
||||||
|
img, label, path, shapes, masks = zip(*batch) # transposed
|
||||||
|
batched_masks = torch.cat(masks, 0)
|
||||||
|
for i, l in enumerate(label):
|
||||||
|
l[:, 0] = i # add target image index for build_targets()
|
||||||
|
return torch.stack(img, 0), torch.cat(label, 0), path, shapes, batched_masks
|
||||||
|
|
||||||
|
|
||||||
|
def polygon2mask(img_size, polygons, color=1, downsample_ratio=1):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
img_size (tuple): The image size.
|
||||||
|
polygons (np.ndarray): [N, M], N is the number of polygons,
|
||||||
|
M is the number of points(Be divided by 2).
|
||||||
|
"""
|
||||||
|
mask = np.zeros(img_size, dtype=np.uint8)
|
||||||
|
polygons = np.asarray(polygons)
|
||||||
|
polygons = polygons.astype(np.int32)
|
||||||
|
shape = polygons.shape
|
||||||
|
polygons = polygons.reshape(shape[0], -1, 2)
|
||||||
|
cv2.fillPoly(mask, polygons, color=color)
|
||||||
|
nh, nw = (img_size[0] // downsample_ratio, img_size[1] // downsample_ratio)
|
||||||
|
# NOTE: fillPoly firstly then resize is trying the keep the same way
|
||||||
|
# of loss calculation when mask-ratio=1.
|
||||||
|
mask = cv2.resize(mask, (nw, nh))
|
||||||
|
return mask
|
||||||
|
|
||||||
|
|
||||||
|
def polygons2masks(img_size, polygons, color, downsample_ratio=1):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
img_size (tuple): The image size.
|
||||||
|
polygons (list[np.ndarray]): each polygon is [N, M],
|
||||||
|
N is the number of polygons,
|
||||||
|
M is the number of points(Be divided by 2).
|
||||||
|
"""
|
||||||
|
masks = []
|
||||||
|
for si in range(len(polygons)):
|
||||||
|
mask = polygon2mask(img_size, [polygons[si].reshape(-1)], color, downsample_ratio)
|
||||||
|
masks.append(mask)
|
||||||
|
return np.array(masks)
|
||||||
|
|
||||||
|
|
||||||
|
def polygons2masks_overlap(img_size, segments, downsample_ratio=1):
|
||||||
|
"""Return a (640, 640) overlap mask."""
|
||||||
|
masks = np.zeros((img_size[0] // downsample_ratio, img_size[1] // downsample_ratio),
|
||||||
|
dtype=np.int32 if len(segments) > 255 else np.uint8)
|
||||||
|
areas = []
|
||||||
|
ms = []
|
||||||
|
for si in range(len(segments)):
|
||||||
|
mask = polygon2mask(
|
||||||
|
img_size,
|
||||||
|
[segments[si].reshape(-1)],
|
||||||
|
downsample_ratio=downsample_ratio,
|
||||||
|
color=1,
|
||||||
|
)
|
||||||
|
ms.append(mask)
|
||||||
|
areas.append(mask.sum())
|
||||||
|
areas = np.asarray(areas)
|
||||||
|
index = np.argsort(-areas)
|
||||||
|
ms = np.array(ms)[index]
|
||||||
|
for i in range(len(segments)):
|
||||||
|
mask = ms[i] * (i + 1)
|
||||||
|
masks = masks + mask
|
||||||
|
masks = np.clip(masks, a_min=0, a_max=i + 1)
|
||||||
|
return masks, index
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user