- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
一、前言
1.本周任务介绍
将yolov5s网络模型中C3模块中的结构按照如下方式修改,并跑通YOLOv5。
2.common.py介绍
在YOLOv5中,common.py
文件是一个关键组件,它包含了实现YOLO算法中各个模块的代码。以下是对common.py
文件内容的详细解读:
- 文件位置和作用:
common.py
位于./models
目录下。该文件定义了YOLOv5网络结构中的多个基础模块,如果需要修改某一模块(例如C3模块),就需要修改这个文件中对应模块的定义。 - 导入的包和基本配置:文件开始部分导入了多个Python模块,如
math
,copy
,pathlib
,numpy
,pandas
,requests
,torch
,torch.nn
等,这些模块为后续定义的网络结构提供支持。 - 基本组件:
autopad
:这是一个用于计算卷积模块所需pad
值的函数。它会根据输入的卷积核大小自动计算所需的填充数。在YOLOv5中,有两种卷积:下采样卷积和保持特征尺寸不变的卷积。
- 其他关键组件:虽然具体的组件没有在引用的资源中详细列出,但根据YOLOv5的架构,
common.py
通常还会包含如Conv
,DWConv
,Bottleneck
,BottleneckCSP
,C3
,SPP
等关键组件的定义。这些组件在目标检测网络中发挥着重要作用,例如Conv
用于标准的卷积操作,Bottleneck
和C3
用于构建网络的深度可分离卷积块等。
3.yolov5结构介绍
有关yolov5的结构建议看这篇文章了解一下,这里不作赘述。
YOLOv5网络结构完全解读【源码+手绘网络结构+模块结构】
导入基本的包
import math # 数学函数模块
from copy import copy # 数据拷贝模块 分浅拷贝和深拷贝
from pathlib import Path # Path将str转换为Path对象 使字符串路径易于操作的模块
import numpy as np # numpy数组操作模块
import pandas as pd # panda数组操作模块
import requests # Python的HTTP客户端库
import torch # pytorch深度学习框架
import torch.nn as nn # 专门为神经网络设计的模块化接口
from PIL import Image # 图像基础操作模块
from torch.cuda import amp # 混合精度训练模块
from utils.datasets import letterbox
from utils.general import non_max_suppression, make_divisible, scale_coords, increment_path, xyxy2xywh, save_one_box
from utils.plots import colors, plot_one_box
from utils.torch_utils import time_synchronized
二、基本组件
1.autopad
在YOLOv5中,autopad
模块是一个辅助函数,其主要作用是根据卷积核的大小自动计算并应用适当的填充(padding)值。具体来说,autopad
的功能如下:
- 自动计算填充值:
autopad
函数会根据输入的卷积核大小(kernel size)自动计算所需的填充值。填充是在卷积操作中对输入特征图的边缘进行填充,以确保卷积后的特征图大小满足特定的设计要求。 - 保持特征图尺寸:在卷积神经网络中,有时需要保持特征图的尺寸不变。
autopad
通过计算合适的填充值,使得卷积操作后特征图的尺寸与输入特征图的尺寸相同。 - 支持不同类型的卷积:在YOLOv5中,通常有两种类型的卷积:
- 下采样卷积:这种卷积会减小特征图的尺寸,通常用于特征降维和提取更抽象的特征。
- 保持尺寸的卷积:这种卷积操作后,特征图的尺寸保持不变,常用于保持特征图的分辨率。
- 实现细节:
autopad
函数通常通过以下方式实现:- 如果卷积核大小是奇数,则使用相同的填充值在输入特征图的所有边界上进行填充。
- 如果卷积核大小是偶数,则可能需要不对称的填充,以确保输出特征图的尺寸正确。
def autopad(k, p=None):
"""用于Conv函数和Classify函数中
根据卷积核大小k自动计算卷积核padding数(0填充)
v5中只有两种卷积:
1、下采样卷积:conv3x3 s=2 p=k//2=1
2、feature size不变的卷积:conv1x1 s=1 p=k//2=1
:params k: 卷积核的kernel_size
:return p: 自动计算的需要pad值(0填充)
"""
if p is None:
p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # 自动计算pad数
return p
2.Conv
在YOLOv5中,conv
函数是一个构建块,用于创建卷积层,这是神经网络中用于特征提取和转换的核心组件。下面是YOLOv5中conv
函数的组成和作用:
组成
在YOLOv5的源代码中,conv
函数通常定义在models/common.py
文件中。以下是conv
函数的一般组成:
def conv(c1, c2, k=1, s=1, p=None, g=1, act=True): # c1: 输入通道数, c2: 输出通道数
# c1: 输入通道数, c2: 输出通道数, k: 卷积核尺寸, s: 步长, p: 填充, g: 分组数, act: 是否包含激活函数
return Conv(c1, c2, k, s, autopad(k, p), g, act)
Conv
类通常继承自torch.nn.Module
,并定义如下:
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(Conv, self).__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)))
作用
以下是conv
函数中各个组件的作用:
- nn.Conv2d:
- 这是PyTorch中的2D卷积层,用于执行图像上的卷积操作。
c1
是输入通道数,c2
是输出通道数。k
是卷积核的大小。s
是卷积的步长。p
是填充的大小,如果设置为None
,则会使用autopad
函数自动计算。g
是分组数,用于分组卷积,可以减少参数数量和计算量。
- autopad:
- 自动计算填充大小,确保卷积后特征图的尺寸保持不变(如果步长为1)。
autopad
函数根据卷积核的大小k
和提供的填充p
来计算填充。
- nn.BatchNorm2d:
- 批量归一化层,用于在训练期间对每个小批量数据进行归一化,加速训练并减少模型对初始权重的敏感性。
- nn.SiLU:
- SiLU激活函数(Sigmoid Linear Unit),是YOLOv5中常用的激活函数,它结合了Sigmoid函数和线性函数的特性。
- nn.Identity:
- 如果没有激活函数或者激活函数参数
act
是一个自定义的模块,则使用nn.Identity
,它不执行任何操作,直接返回输入。
- 如果没有激活函数或者激活函数参数
总结
conv
函数在YOLOv5中的作用是创建一个包含卷积、批量归一化和激活函数的复合层。这样的层是卷积神经网络(CNN)的基础,用于提取图像特征,并通过不同的卷积核大小、步长和填充来调整特征图的尺寸和深度。通过在卷积层中使用批量归一化和激活函数,可以增加模型的训练效率和性能。
Conv模块代码
class Conv(nn.Module):
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):
"""在Focus、Bottleneck、BottleneckCSP、C3、SPP、DWConv、TransformerBloc等模块中调用
Standard convolution conv+BN+act
:params c1: 输入的channel值
:params c2: 输出的channel值
:params k: 卷积的kernel_size
:params s: 卷积的stride
:params p: 卷积的padding 一般是None 可以通过autopad自行计算需要pad的padding数
:params g: 卷积的groups数 =1就是普通的卷积 >1就是深度可分离卷积
:params act: 激活函数类型 True就是SiLU()/Swish False就是不使用激活函数
类型是nn.Module就使用传进来的激活函数类型
"""
super(Conv, self).__init__()
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False) # conv
self.bn = nn.BatchNorm2d(c2) # bn
self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity()) # activation
def forward(self, x):
return self.act(self.bn(self.conv(x)))
def fuseforward(self, x):
"""用于Model类的fuse函数
融合conv+bn 加速推理 一般用于测试/验证阶段
"""
return self.act(self.conv(x))
注意
在YOLOv5的Conv
类中,fuseforward
函数的作用是在模型推理(测试或验证阶段)时加速卷积层的计算。以下是fuseforward
函数的具体作用和实现细节:
- 融合卷积和批量归一化:
- 在训练阶段,卷积层(
nn.Conv2d
)后面通常跟着批量归一化层(nn.BatchNorm2d
)。这是因为批量归一化有助于模型的训练稳定性和收敛速度。 - 在推理阶段,批量归一化的操作可以与卷积层的权重融合,减少计算量,从而加速推理过程。
- 在训练阶段,卷积层(
- 消除批量归一化的计算:
- 批量归一化在推理时需要计算均值和方差,并进行归一化和缩放。通过融合操作,这些计算可以在模型训练完成后预先计算并嵌入到卷积层的权重和偏置中,推理时不再需要这些计算。
实现细节
fuseforward
函数的实现如下:
def fuseforward(self, x):
"""用于Model类的fuse函数
融合conv+bn 加速推理 一般用于测试/验证阶段
"""
return self.act(self.conv(x))
在这个函数中,以下步骤被执行:
- 卷积操作 (
self.conv(x)
): 对输入x
执行卷积操作。由于在推理前已经将批量归一化的参数融合到了卷积层的权重和偏置中,这里直接进行卷积操作。 - 激活函数 (
self.act(...)
): 对卷积操作的结果应用激活函数。这里不包含批量归一化层,因为其影响已经通过权重融合到了卷积层中。
总结
fuseforward
函数通过融合卷积层和批量归一化层的权重,简化了推理过程中的计算流程,从而提高了模型的推理速度。这种优化通常在模型部署到生产环境或进行性能测试时使用。需要注意的是,在进行fuseforward
操作之前,需要确保模型已经完成了训练,并且已经将批量归一化的参数正确地融合到了卷积层中。
3.Focus
YOLOv5中的Focus层详解|CSDN创作打卡
可以看这篇文章,里面有focus模块的图片,或者看训练营自带的解释图
YOLOv5的Focus模块主要有以下作用:
- 特征提取和下采样:Focus模块通过切片操作将输入的高分辨率图像转换成多个低分辨率的特征图,然后拼接这些特征图。这一过程有效地提取了图像的特征,并且减少了图像的分辨率,从而降低了后续处理的计算量。
- 信息保留:在下采样的过程中,Focus模块能够较好地保留原始图像的信息。它通过隔列采样和拼接的方式,使得在减少分辨率的同时,不会丢失太多信息。
- 通道空间信息的集中:Focus模块将W、H平面上的信息转换到了通道空间,使得输入通道的数量扩充了4倍。例如,一个原始的RGB三通道图像(640×640×3)经过Focus模块处理后,会变成12个通道的特征图(320×320×12)。
- 减少计算量和参数量:通过下采样,Focus模块减少了特征图的尺寸,进而减少了后续卷积操作的计算量和网络的参数量,有助于提高网络的效率和速度。
- 提高特征表示能力:通过这种特殊的下采样方式,Focus模块有助于网络学习到更加丰富的特征表示,这对于目标检测任务来说非常重要。
总的来说,Focus模块在YOLOv5中起到了一个关键的作用,它不仅优化了数据处理流程,还提高了特征提取的效率和效果,这对于整个网络的性能有着直接的影响。
class Focus(nn.Module):
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):
"""在yolo.py的parse_model函数中被调用
理论:从高分辨率图像中,周期性的抽出像素点重构到低分辨率图像中,即将图像相邻的四个位置进行堆叠,
聚焦wh维度信息到c通道空,提高每个点感受野,并减少原始信息的丢失,该模块的设计主要是减少计算量加快速度。
Focus wh information into c-space 把宽度w和高度h的信息整合到c空间中
先做4个slice 再concat 最后再做Conv
slice后 (b,c1,w,h) -> 分成4个slice 每个slice(b,c1,w/2,h/2)
concat(dim=1)后 4个slice(b,c1,w/2,h/2)) -> (b,4c1,w/2,h/2)
conv后 (b,4c1,w/2,h/2) -> (b,c2,w/2,h/2)
:params c1: slice后的channel
:params c2: Focus最终输出的channel
:params k: 最后卷积的kernel
:params s: 最后卷积的stride
:params p: 最后卷积的padding
:params g: 最后卷积的分组情况 =1普通卷积 >1深度可分离卷积
:params act: bool激活函数类型 默认True:SiLU()/Swish False:不用激活函数
"""
super(Focus, self).__init__()
self.conv = Conv(c1 * 4, c2, k, s, p, g, act) # concat后的卷积(最后的卷积)
# self.contract = Contract(gain=2) # 也可以调用Contract函数实现slice操作
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))
4.Bottleneck
class Bottleneck(nn.Module):
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):
"""在BottleneckCSP和yolo.py的parse_model中调用
Standard bottleneck Conv+Conv+shortcut
:params c1: 第一个卷积的输入channel
:params c2: 第二个卷积的输出channel
:params shortcut: bool 是否有shortcut连接 默认是True
:params g: 卷积分组的个数 =1就是普通卷积 >1就是深度可分离卷积
:params e: expansion ratio e*c2就是第一个卷积的输出channel=第二个卷积的输入channel
"""
super(Bottleneck, self).__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1) # 1x1
self.cv2 = Conv(c_, c2, 3, 1, g=g) # 3x3
self.add = shortcut and c1 == c2 # shortcut=True and c1 == c2 才能做shortcut
def forward(self, x):
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
YOLOv5中的Bottleneck模块是基于YOLO系列检测网络中常用的一种设计,它是构建更复杂网络结构(如CSPNet中的BottleneckCSP块)的基础组件。以下是Bottleneck模块的结构、原理及其在YOLOv5中的作用。
Bottleneck模块的结构:
Bottleneck模块的核心思想是通过使用1x1卷积层(也称为瓶颈层)来减少和增加特征图的维度,从而在保持性能的同时减少计算量。一个典型的Bottleneck模块通常包含以下步骤:
- 1x1卷积层(降维):首先,通过一个1x1的卷积层来减少输入特征图的通道数(维度),这被称为降维。降维有助于减少后续3x3卷积层的计算量。
- 3x3卷积层(深度卷积):接着,使用一个3x3的卷积层(没有填充,或者步长为1或2,取决于是否需要下采样)来处理特征图。这个3x3卷积层是模块中的主要计算部分,用于提取特征。
- 1x1卷积层(升维):最后,通过另一个1x1的卷积层来增加特征图的通道数(维度),这被称为升维。升维确保了特征图的深度与模块输入的深度相同,以便进行后续的拼接或加和操作。
在某些实现中,Bottleneck模块可能还会包含一个激活函数(如SiLU),用于引入非线性,提高网络的表示能力。
Bottleneck模块的原理:
Bottleneck模块的原理基于以下两点:
- 维度缩减与扩展:通过在3x3卷积层之前和之后使用1x1卷积层,可以在不显著损失特征表示能力的情况下减少计算量。1x1卷积层是参数效率极高的层,因为它们仅涉及跨通道的线性变换。
- 深度卷积:3x3卷积层用于在空间维度上提取特征,而不会受到降维和升维层的影响。这种设计允许网络在较少的参数和计算下捕获丰富的特征。
Bottleneck模块在YOLOv5中的作用:
- 特征提取:Bottleneck模块是YOLOv5网络中用于提取特征的关键组件。通过使用3x3卷积层,模块能够捕捉到输入图像的空间特征。
- 减少计算量:通过在3x3卷积层前后使用1x1卷积层来减少特征图的维度,Bottleneck模块有效地减少了模型的计算负担,这对于实现实时检测非常重要。
- 参数效率:Bottleneck模块的设计有助于减少网络的参数数量,从而减少过拟合的风险,并提高模型的泛化能力。
- 构建更复杂的结构:Bottleneck模块是构建更复杂网络结构(如BottleneckCSP块)的基础。通过堆叠和组合多个Bottleneck模块,可以形成更强大的特征提取器。
在YOLOv5中,Bottleneck模块通常被组合成更复杂的结构,如BottleneckCSP块,它们在Backbone和Neck部分被广泛使用,以确保网络能够在不同尺度上有效地提取和融合特征,从而提高目标检测的性能。
5.BottleneckCSP
结构图如下
class BottleneckCSP(nn.Module):
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
"""在C3模块和yolo.py的parse_model模块调用
CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
:params c1: 整个BottleneckCSP的输入channel
:params c2: 整个BottleneckCSP的输出channel
:params n: 有n个Bottleneck
:params shortcut: bool Bottleneck中是否有shortcut,默认True
:params g: Bottleneck中的3x3卷积类型 =1普通卷积 >1深度可分离卷积
:params e: expansion ratio c2xe=中间其他所有层的卷积核个数/中间所有层的输入输出channel数
"""
# ch_in, ch_out, number, shortcut, groups, expansion
super(BottleneckCSP, self).__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) 2*c_
self.act = nn.LeakyReLU(0.1, inplace=True)
# 叠加n次Bottleneck
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))))
YOLOv5中的BottleneckCSP块是基于CSPNet(Cross Stage Partial networks)结构的一种改进,它结合了Bottleneck块和CSP思想。以下是BottleneckCSP块的结构、原理及其在YOLOv5中的作用。
BottleneckCSP块的结构:
- 输入特征图分割:BottleneckCSP块接收一个输入特征图,并将其分为两个部分。一部分直接传递到模块的末端,另一部分进入Bottleneck序列。
- Bottleneck序列:这个部分包含一系列的Bottleneck块。每个Bottleneck块通常包括以下操作:
- 1x1卷积层(降维)
- 3x3卷积层(深度卷积,可选的步长为2,用于下采样)
- 1x1卷积层(升维)
- 特征融合:经过Bottleneck序列处理后的特征图与最初直接传递的那部分特征图在深度方向上进行拼接(concatenate)。
- 深度卷积:拼接后的特征图可能还会通过一个额外的深度卷积层,用于进一步融合特征。
- 输出:最后,模块输出融合后的特征图,用于后续的网络层。
BottleneckCSP块的原理:
- CSP(Cross Stage Partial networks):CSPNet的核心思想是将输入特征图分为两部分,一部分直接连接到网络的后面,另一部分通过多个卷积层。这样做的好处是减少了计算量,并且有助于改善梯度的流动,从而有助于训练更深的网络。
- Bottleneck:Bottleneck块通过使用1x1卷积层来减少和增加特征图的维度,这样可以有效地减少参数数量和计算量,同时保持网络的性能。
BottleneckCSP块在YOLOv5中的作用:
- 减少计算量和参数数量:通过将特征图分为两部分,一部分直接连接,另一部分经过轻量级的Bottleneck块,减少了计算量和参数数量。
- 改善梯度流:CSP结构有助于改善梯度的流动,使得网络能够更好地训练,尤其是在训练深层网络时。
- 增强特征表示:BottleneckCSP块通过在特征图的不同部分上应用不同的操作,能够捕获更丰富的特征表示,这有助于提高检测的准确性。
- 维持性能:尽管BottleneckCSP块旨在提高效率,但它依然能够保持良好的性能,这对于实时目标检测任务至关重要。
在YOLOv5中,BottleneckCSP块通常用于Backbone和Neck部分,它们是整个网络结构中的关键组件,负责提取和融合不同层次的特征。通过这种方式,BottleneckCSP块有助于YOLOv5在维持高检测精度的同时,还能实现快速推理。
6.C3
class C3(nn.Module):
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
"""在C3TR模块和yolo.py的parse_model模块调用
CSP Bottleneck with 3 convolutions
:params c1: 整个BottleneckCSP的输入channel
:params c2: 整个BottleneckCSP的输出channel
:params n: 有n个Bottleneck
:params shortcut: bool Bottleneck中是否有shortcut,默认True
:params g: Bottleneck中的3x3卷积类型 =1普通卷积 >1深度可分离卷积
:params e: expansion ratio c2xe=中间其他所有层的卷积核个数/中间所有层的输入输出channel数
"""
super(C3, self).__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)])
# 实验性 CrossConv
# 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))
YOLOv5中的C3模块是一种基于Cross Stage Partial networks (CSPNet)思想的改进模块,它旨在提高网络的效率和性能。C3模块的结构可以概括如下:
C3模块结构:
- 输入特征图分割:C3模块接收一个输入特征图,并将其分为两个部分。一部分直接传递到模块的末端,另一部分进入一系列的Bottleneck结构。
- Bottleneck结构:进入Bottleneck结构的那部分特征图会经过多个Bottleneck块。Bottleneck块通常由一个1x1的卷积层(用于降维)、一个3x3的深度可分离卷积层(用于提取特征)和一个1x1的卷积层(用于升维)组成。
- 特征融合:经过Bottleneck块处理后的特征图与最初直接传递的那部分特征图在深度方向上进行拼接(concatenate)。
- 深度可分离卷积:拼接后的特征图可能还会通过一个额外的深度可分离卷积层,以进一步融合特征并减少计算量。
- 输出:最后,模块输出融合后的特征图,用于后续的网络层。
C3模块在YOLOv5中的作用:
- 提高计算效率:通过将特征图分割成两部分,C3模块可以在不牺牲太多性能的情况下减少计算量,因为一部分特征图直接传递,另一部分特征图通过轻量级的Bottleneck结构。
- 增强特征融合:C3模块中的特征融合步骤有助于网络捕获不同层次的特征,从而提高特征的丰富性和表示能力。
- 优化梯度流:C3模块的设计有助于改善梯度的流动,减少在反向传播过程中的梯度消失问题,这有助于网络的训练稳定性和收敛速度。
- 减少参数数量:由于使用了深度可分离卷积,C3模块可以减少模型的参数数量,使得网络更加轻量化。
- 保持性能:尽管C3模块旨在提高计算效率和减少参数数量,但它仍然能够保持较高的检测性能,这对于目标检测任务至关重要。
在YOLOv5中,C3模块通常被用在Neck部分,尤其是在Panet网络结构中,它们负责进一步处理Backbone提取的特征,并为最终的检测头(Head)准备高级特征图。
C3模块的设计是YOLOv5高效性和性能的关键因素之一,它使得YOLOv5在保持实时检测速度的同时,能够达到较高的检测精度。
7.SPP
class SPP(nn.Module):
def __init__(self, c1, c2, k=(5, 9, 13)):
"""在yolo.py的parse_model模块调用
空间金字塔池化 Spatial pyramid pooling layer used in YOLOv3-SPP
:params c1: SPP模块的输入channel
:params c2: SPP模块的输出channel
:params k: 保存着三个maxpool的卷积核大小 默认是(5, 9, 13)
"""
super(SPP, self).__init__()
c_ = c1 // 2 # hidden channels
self.cv1 = Conv(c1, c_, 1, 1) # 第一层卷积
self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1) # 最后一层卷积 +1是因为有len(k)+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)
return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))
YOLOv5中的SPP(Spatial Pyramid Pooling)模块是一种特殊的多尺度特征池化技术,它被设计用来增加网络的感受野,而不增加额外的参数和计算量。以下是SPP模块的结构、原理及其在YOLOv5中的作用。
SPP模块的结构:
SPP模块通常位于YOLOv5的Backbone和Neck之间,它的结构包含以下几个部分:
- 输入特征图:SPP模块接收来自Backbone的特征图。
- 并行池化层:SPP模块包含多个并行的池化层,这些池化层可以是不同尺寸的最大池化(Max Pooling)层,例如1x1、5x5、9x9和13x13。这些池化层具有相同的步长(通常是1),没有填充(padding)。
- 特征拼接:将经过不同尺寸池化层处理后的特征图在深度方向(通道维度)上进行拼接。
- 卷积层:在拼接后的特征图上应用一个或多个卷积层,用于进一步处理和整合多尺度特征。
SPP模块的原理:
SPP模块的原理基于以下几点:
- 多尺度特征融合:通过使用不同尺寸的池化层,SPP模块能够在不同的尺度上捕获上下文信息,这有助于检测不同尺寸的目标。
- 固定大小的输出:无论输入特征图的尺寸如何,SPP模块都能生成固定大小的输出,这为后续的网络层提供了稳定的输入。
- 增加感受野:通过使用大尺寸的池化核,SPP模块能够在不增加参数和计算量的情况下显著增加网络的感受野,这有助于网络捕捉更全局的上下文信息。
SPP模块在YOLOv5中的作用:
- 捕获多尺度上下文:SPP模块帮助网络捕获不同尺度的上下文信息,这对于检测不同尺寸的目标至关重要。
- 增强特征表示:通过融合不同尺度的特征,SPP模块增强了网络的特征表示能力,有助于提高检测精度。
- 固定输出尺寸:SPP模块为后续层提供了固定尺寸的输入,这简化了网络的设计,并使网络能够处理不同分辨率的输入图像。
为什么要有SPP模块:
- 多尺度检测:在目标检测任务中,目标可能出现在图像中的不同尺度上。SPP模块通过提供多尺度特征,使网络能够更有效地检测这些目标。
- 增加感受野:感受野是神经网络中一个重要的概念,它决定了网络能够捕获的输入图像区域的大小。SPP模块通过大尺寸池化层增加了感受野,使网络能够捕获更全局的信息。
- 减少计算量:与传统的卷积层相比,SPP模块通过池化操作而不是额外的卷积层来增加感受野,这样可以减少模型的参数数量和计算量。
- 提高检测性能:SPP模块已被证明能够提高目标检测网络的性能,尤其是在处理具有不同尺度目标的复杂场景时。
总的来说,SPP模块是YOLOv5网络中的一个关键组件,它通过简单的池化操作显著提高了网络的特征提取能力和检测性能。
8.Concat
class Concat(nn.Module):
def __init__(self, dimension=1):
"""在yolo.py的parse_model模块调用
Concatenate a list of tensors along dimension
:params dimension: 沿着哪个维度进行concat
"""
super(Concat, self).__init__()
self.d = dimension
def forward(self, x):
# x: a list of tensors
return torch.cat(x, self.d)
在YOLOv5中,Concat
函数是一个常用的操作,它用于将多个特征图的深度维度(channel dimension)合并在一起。以下是Concat
函数的结构、原理及其在YOLOv5中的作用。
Concat函数的结构:
Concat
函数通常接受多个张量(tensor)作为输入,这些张量在除了深度维度外的其他维度(如高度和宽度)上具有相同的尺寸。Concat
函数的结构非常简单,可以概括为:
- 输入张量:函数接收两个或多个特征图张量。
- 拼接操作:在深度维度上,将这些张量拼接在一起。
- 输出张量:生成一个新的张量,它在深度维度上具有所有输入张量深度的总和,而其他维度保持不变。
Concat函数的原理:
Concat
函数的工作原理是将多个特征图的通道组合在一起,从而创建一个更厚的特征图。这个过程可以看作是在深度方向上扩展了特征表示的容量,允许网络捕获更多种类的特征。
以下是Concat
操作的数学表示:
假设我们有两个张量 ( A ) 和 ( B ),它们的大小分别是 ( [C_1, H, W] ) 和 ( [C_2, H, W] ),其中 ( C ) 是通道数,( H ) 是高度,( W ) 是宽度。Concat
操作将这些张量在深度维度上合并,得到一个新的张量 ( C ),其大小为 ( [C_1 + C_2, H, W] )。
Concat函数在YOLOv5中的作用:
- 特征融合:在YOLOv5中,
Concat
函数用于将来自不同层的特征图合并,这有助于网络融合不同层次的特征信息,增强特征表示。 - 多尺度检测:YOLOv5采用了多尺度检测的策略,
Concat
函数在Neck部分将不同分辨率的特征图合并,这样可以在不同的尺度上检测目标。 - 增加特征丰富性:通过合并来自不同层的特征,
Concat
函数增加了特征图的深度,从而增加了特征的丰富性,有助于网络区分不同的目标和背景。 - 构建特征金字塔:在YOLOv5中,
Concat
函数与其它操作(如卷积层、上采样层)一起使用,构建了一个特征金字塔网络(FPN),该网络能够在多个尺度上检测目标。 - 减少信息损失:在特征传递过程中,通过
Concat
函数合并特征图可以减少信息损失,因为所有特征都被保留下来,而不是被筛选或丢弃。
总之,Concat
函数是YOLOv5网络设计中的一个关键组成部分,它通过简单的拼接操作实现了特征的丰富和融合,提高了网络在目标检测任务中的性能。
9.Contract、Expand
class Contract(nn.Module):
"""用在yolo.py的parse_model模块 用的不多
改变输入特征的shape 将w和h维度(缩小)的数据收缩到channel维度上(放大)
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):
N, C, H, W = x.size() # 1 64 80 80
s = self.gain # 2
x = x.view(N, C, H // s, s, W // s, s) # x(1,64,40,2,40,2)
# permute: 改变tensor的维度顺序
x = x.permute(0, 3, 5, 1, 2, 4).contiguous() # x(1,2,2,64,40,40)
# .view: 改变tensor的维度
return x.view(N, C * s * s, H // s, W // s) # x(1,256,40,40)
class Expand(nn.Module):
"""用在yolo.py的parse_model模块 用的不多
改变输入特征的shape 将channel维度(变小)的数据扩展到W和H维度(变大)
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):
N, C, H, W = x.size() # 1 64 80 80
s = self.gain # 2
x = x.view(N, 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(N, C // s ** 2, H * s, W * s) # x(1,16,160,160)
在YOLOv5中,Contract
和Expand
函数并不是YOLOv5官方代码库中的标准函数,它们可能是某些自定义版本的YOLOv5实现中的函数,或者是对某些操作的泛称。不过,基于通常的神经网络操作,我们可以推测Contract
和Expand
可能指的是特征图尺寸的缩小(下采样)和扩大(上采样)操作。下面我将基于这一假设来解释它们可能的结构、原理以及在YOLOv5中的作用。
Contract(下采样)函数的结构和原理:
结构:
- 输入张量:具有特定高度和宽度的特征图。
- 下采样操作:通常通过使用步长大于1的卷积、池化(如最大池化或平均池化)或者组合这些操作来实现。
- 输出张量:输出一个尺寸较小的特征图,高度和宽度是输入特征图的一半或更小。
原理:
- 通过减少特征图的分辨率,
Contract
操作可以减少计算量和存储需求。 - 它有助于捕捉输入图像中的高层次抽象特征。
在YOLOv5中的作用:
- 特征提取:在网络的早期阶段,
Contract
操作有助于捕捉图像中的基础特征。 - 建立特征金字塔:在特征金字塔网络(FPN)结构中,
Contract
操作用于创建不同尺度的特征图,以便在不同尺度上检测目标。
Expand(上采样)函数的结构和原理:
结构:
- 输入张量:一个较小的特征图。
- 上采样操作:可以通过插值(如最近邻插值、双线性插值)、反卷积(转置卷积)或PixelShuffle等操作来实现。
- 输出张量:输出一个尺寸较大的特征图,高度和宽度是输入特征图的两倍或更大。
原理:
- 上采样操作增加了特征图的分辨率,恢复了在
Contract
操作中丢失的细节信息。 - 它使得网络能够在高分辨率的特征图上进行更精细的预测。
在YOLOv5中的作用:
- 特征图恢复:在FPN中,
Expand
操作用于将较低分辨率的特征图上采样到较高的分辨率,以恢复细节信息。 - 多尺度预测:
Expand
操作与Concat
操作结合使用,可以在不同的尺度上进行目标检测,从而提高检测精度。
在YOLOv5中,Contract
和Expand
操作通常是成对出现的,它们共同构成了特征金字塔结构,允许网络在不同的尺度上进行有效的目标检测。这种结构使得YOLOv5能够在保持计算效率的同时,对不同尺寸的目标进行准确检测。
三、重要类
1.非极大值抑制(NMS)
class NMS(nn.Module):
"""在yolo.py中Model类的nms函数中使用
NMS非极大值抑制 Non-Maximum Suppression (NMS) module
给模型model封装nms 增加模型的扩展功能 但是我们一般不用 一般是在前向推理结束后再调用non_max_suppression函数
"""
conf = 0.25 # 置信度阈值 confidence threshold
iou = 0.45 # iou阈值 IoU threshold
classes = None # 是否nms后只保留特定的类别 (optional list) filter by class
max_det = 1000 # 每张图片的最大目标个数 maximum number of detections per image
def __init__(self):
super(NMS, self).__init__()
def forward(self, x):
"""
:params x[0]: [batch, num_anchors(3个yolo预测层), (x+y+w+h+1+num_classes)]
直接调用的是general.py中的non_max_suppression函数给model扩展nms功能
"""
return non_max_suppression(x[0], self.conf, iou_thres=self.iou, classes=self.classes, max_det=self.max_det)
非极大值抑制(Non-maximum Suppression, NMS)是一种在目标检测任务中常用的算法,用于筛选一组重叠的边界框,并只保留最有可能包含目标的那些边界框。以下是YOLOv5中NMS的结构、原理及其作用:
结构:
NMS通常遵循以下步骤:
- 排序:根据边界框的置信度(通常是分类得分与边界框的IOU乘积)对所有的边界框进行排序。
- 选择:选择置信度最高的边界框,将其作为保留的检测框。
- 抑制:对于剩下的边界框,计算它们与刚刚选择的边界框的交并比(Intersection over Union, IOU)。如果IOU超过了一个预定的阈值(例如0.5),则将这些边界框抑制(即移除)。
- 重复:重复步骤2和3,直到所有的边界框都被处理过。
原理:
NMS的原理基于一个假设:对于同一目标,模型可能会生成多个重叠的检测框,而实际上只需要一个最佳的边界框。NMS通过以下方式实现:
- 置信度:模型为每个边界框分配一个置信度分数,该分数表示边界框包含目标的概率。
- IOU:计算两个边界框之间的重叠程度。IOU是两个边界框交集的面积与并集的面积的比值。
- 抑制:如果一个边界框与已经选定的边界框高度重叠(即IOU超过阈值),则认为它是冗余的,可以被抑制。
在YOLOv5中的作用:
在YOLOv5中,NMS的作用是:
- 减少冗余检测:模型可能会对同一个目标产生多个检测框,NMS确保每个目标只保留一个最佳边界框。
- 提高检测精度:通过去除多余的检测框,NMS有助于提高检测的精确度,减少误检。
为什么要有NMS:
以下是为什么目标检测算法中需要NMS的原因:
- 避免重复检测:模型可能会对同一个目标产生多个重叠的检测框,NMS确保每个目标只被检测一次。
- 优化检测结果:NMS选择具有最高置信度的边界框,从而优化最终的检测结果。
- 满足实际应用需求:在许多实际应用中,每个目标只需要一个检测框,NMS有助于满足这一需求。
NMS是目标检测任务中的一个关键步骤,它有助于提高检测算法的实际应用价值。在YOLOv5中,NMS通常在网络的最后阶段应用,以确保最终的检测结果既准确又简洁。
2.AutoShape
class AutoShape(nn.Module):
"""在yolo.py中Model类的autoshape函数中使用
将model封装成包含前处理、推理、后处理的模块(预处理 + 推理 + nms) 也是一个扩展模型功能的模块
autoshape模块在train中不会被调用,当模型训练结束后,会通过这个模块对图片进行重塑,来方便模型的预测
自动调整shape,我们输入的图像可能不一样,可能来自cv2/np/PIL/torch 对输入进行预处理 调整其shape,
调整shape在datasets.py文件中,这个实在预测阶段使用的,model.eval(),模型就已经无法训练进入预测模式了
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 # 是否nms后只保留特定的类别 (optional list) filter by class
max_det = 1000 # maximum number of detections per image
def __init__(self, model):
super(AutoShape, self).__init__()
# 开启验证模式
self.model = model.eval()
def autoshape(self):
print('AutoShape already enabled, skipping... ') # model already converted to model.autoshape()
return self
@torch.no_grad()
def forward(self, imgs, size=640, augment=False, profile=False):
# 这里的imgs针对不同的方法读入,官方也给了具体的方法,size是图片的尺寸,就比如最上面图片里面的输入608*608*3
# Inference from various sources. For height=640, width=1280, RGB images example inputs are:
# filename: imgs = 'data/images/zidane.jpg'
# URI: = 'https://github.com/ultralytics/yolov5/releases/download/v1.0/zidane.jpg'
# OpenCV: = cv2.imread('image.jpg')[:,:,::-1] # HWC BGR to RGB x(640,1280,3)
# PIL: = Image.open('image.jpg') # 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_synchronized()]
p = next(self.model.parameters()) # for device and type
# 图片如果是tensor格式 说明是预处理过的, 直接正常进行前向推理即可 nms在推理结束进行(函数外写)
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
# 图片不是tensor格式 就先对图片进行预处理 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): # filename or uri
im, f = np.asarray(Image.open(requests.get(im, stream=True).raw if im.startswith('http') else im)), im
elif isinstance(im, Image.Image): # PIL Image
im, f = np.asarray(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 image
x = np.stack(x, 0) if n > 1 else x[0][None] # stack image
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_synchronized())
with amp.autocast(enabled=p.device.type != 'cpu'):
# 预处理结束再进行前向推理 Inference
y = self.model(x, augment, profile)[0] # forward 前向推理
t.append(time_synchronized())
# 前向推理结束后 进行后处理Post-process nms
y = non_max_suppression(y, self.conf, iou_thres=self.iou, classes=self.classes, max_det=self.max_det) # NMS
for i in range(n):
scale_coords(shape1, y[i][:, :4], shape0[i]) # 将nms后的预测结果映射回原图尺寸
t.append(time_synchronized())
return Detections(imgs, y, files, t, self.names, x.shape)
YOLOv5的AutoShape
是一个在模型推理阶段使用的功能,它用于处理不同尺寸的输入图像,并确保输出结果的正确性。以下是AutoShape
的结构、原理及其在YOLOv5中的作用:
结构:
AutoShape
通常包含以下几个步骤:
- 图像预处理:将输入图像调整到模型可以接受的尺寸。
- 模型推理:将预处理后的图像送入YOLOv5模型进行推理。
- 后处理:对模型的输出进行后处理,包括解码、缩放边界框坐标、应用NMS等。
- 输出调整:将后处理的结果调整回原始图像的尺寸。
原理:
AutoShape
的原理可以概括为以下几点:
- 尺寸调整:在模型推理之前,将输入图像尺寸调整到模型训练时使用的尺寸(例如,YOLOv5通常使用640x640的输入尺寸)。如果输入图像的宽高比与模型训练时的尺寸不匹配,
AutoShape
会进行填充或裁剪以适应模型要求。 - 坐标映射:模型推理后,输出的边界框坐标是在填充或裁剪后的图像上计算的。因此,需要将这些坐标映射回原始图像的尺寸。
- 后处理:在将边界框坐标映射回原始尺寸后,应用NMS等后处理步骤来过滤和优化检测结果。
在YOLOv5中的作用:
在YOLOv5中,AutoShape
的作用包括:
- 处理不同尺寸的输入:使得模型能够接受任意尺寸的输入图像,增加了模型的灵活性和适用性。
- 保持检测精度:通过适当的预处理和后处理,确保检测结果的准确性不受输入图像尺寸变化的影响。
- 简化推理流程:用户不需要手动处理图像尺寸和坐标映射,
AutoShape
自动处理这些细节,简化了模型推理的流程。
为什么要有AutoShape
:
以下是为什么YOLOv5需要AutoShape
的原因:
- 适应性:现实世界的应用中,图像的尺寸和比例是多种多样的。
AutoShape
使得YOLOv5能够适应这些变化,而无需用户进行复杂的图像预处理。 - 用户体验:自动处理图像尺寸和坐标映射可以提升用户体验,用户可以直接使用原始尺寸的图像进行检测,无需担心图像尺寸问题。
- 性能优化:通过自动化的处理流程,可以减少在图像预处理和后处理上可能出现的错误,从而优化模型的性能。
总之,AutoShape
是YOLOv5中一个重要的组件,它提高了模型在实际应用中的灵活性和易用性,确保了模型在不同尺寸输入图像上的检测性能。
3.Detections
这个实际上用的不多,只做了解
class Detections:
# YOLOv5 detections class for inference results
# YOLOv5推理结果检测类
def __init__(self, ims, pred, files, times=(0, 0, 0), names=None, shape=None):
"""Initializes the YOLOv5 Detections class with image info, predictions, filenames, timing and normalization."""
super().__init__()
d = pred[0].device # device
gn = [torch.tensor([*(im.shape[i] for i in [1, 0, 1, 0]), 1, 1], device=d) for im in ims] # normalizations
self.ims = ims # 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.times = times # profiling times
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(x.t / self.n * 1e3 for x in times) # timestamps (ms)
self.s = tuple(shape) # inference BCHW shape
def _run(self, pprint=False, show=False, save=False, crop=False, render=False, labels=True, save_dir=Path("")):
"""Executes model predictions, displaying and/or saving outputs with optional crops and labels."""
s, crops = "", []
for i, (im, pred) in enumerate(zip(self.ims, self.pred)):
s += f"\nimage {i + 1}/{len(self.pred)}: {im.shape[0]}x{im.shape[1]} " # string
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
s = s.rstrip(", ")
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 if labels else "", 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 show:
if is_jupyter():
from IPython.display import display
display(im)
else:
im.show(self.files[i])
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.ims[i] = np.asarray(im)
if pprint:
s = s.lstrip("\n")
return f"{s}\nSpeed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {self.s}" % self.t
if crop:
if save:
LOGGER.info(f"Saved results to {save_dir}\n")
return crops
@TryExcept("Showing images is not supported in this environment")
def show(self, labels=True):
"""
Displays detection results with optional labels.
Usage: show(labels=True)
"""
self._run(show=True, labels=labels) # show results
def save(self, labels=True, save_dir="runs/detect/exp", exist_ok=False):
"""
Saves detection results with optional labels to a specified directory.
Usage: save(labels=True, save_dir='runs/detect/exp', exist_ok=False)
"""
save_dir = increment_path(save_dir, exist_ok, mkdir=True) # increment save_dir
self._run(save=True, labels=labels, save_dir=save_dir) # save results
def crop(self, save=True, save_dir="runs/detect/exp", exist_ok=False):
"""
Crops detection results, optionally saves them to a directory.
Args: save (bool), save_dir (str), exist_ok (bool).
"""
save_dir = increment_path(save_dir, exist_ok, mkdir=True) if save else None
return self._run(crop=True, save=save, save_dir=save_dir) # crop results
def render(self, labels=True):
"""Renders detection results with optional labels on images; args: labels (bool) indicating label inclusion."""
self._run(render=True, labels=labels) # render results
return self.ims
def pandas(self):
"""
Returns detections as pandas DataFrames for various box formats (xyxy, xyxyn, xywh, xywhn).
Example: print(results.pandas().xyxy[0]).
"""
new = copy(self) # return copy
ca = "xmin", "ymin", "xmax", "ymax", "confidence", "class", "name" # xyxy columns
cb = "xcenter", "ycenter", "width", "height", "confidence", "class", "name" # xywh columns
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):
"""
Converts a Detections object into a list of individual detection results for iteration.
Example: for result in results.tolist():
"""
r = range(self.n) # iterable
return [
Detections(
[self.ims[i]],
[self.pred[i]],
[self.files[i]],
self.times,
self.names,
self.s,
)
for i in r
]
def print(self):
"""Logs the string representation of the current object's state via the LOGGER."""
LOGGER.info(self.__str__())
def __len__(self):
"""Returns the number of results stored, overrides the default len(results)."""
return self.n
def __str__(self):
"""Returns a string representation of the model's results, suitable for printing, overrides default
print(results).
"""
return self._run(pprint=True) # print results
def __repr__(self):
"""Returns a string representation of the YOLOv5 object, including its class and formatted results."""
return f"YOLOv5 {self.__class__} instance\n" + self.__str__()
在YOLOv5中,Detections
是一个用于处理模型输出的类,它包含了将模型的原始输出转换为可理解的检测结果所需的所有步骤。以下是Detections
的结构、原理及其在YOLOv5中的作用:
结构:
Detections
类通常包含以下方法和属性:
- 初始化方法:用于设置类的一些基本参数,如置信度阈值、NMS阈值等。
__call__
方法:这是类的核心方法,它接受模型的输出,并返回处理后的检测结果。- 坐标转换方法:用于将模型输出的坐标转换为原始图像的坐标。
- 非极大值抑制(NMS)方法:用于筛选最终的检测结果,去除重叠的边界框。
- 置信度过滤方法:用于过滤掉低于置信度阈值的检测结果。
原理:
Detections
的工作原理可以分为以下几个步骤:
- 解码:将模型的输出(通常是三个不同尺度的特征图的预测结果)解码成边界框的坐标、类别概率和置信度。
- 坐标转换:将解码后的边界框坐标从模型输入图像的坐标空间转换回原始图像的坐标空间。
- 置信度过滤:基于置信度阈值过滤掉一些边界框,只保留那些模型认为足够可信的预测。
- 非极大值抑制(NMS):对于每个类别,使用NMS来去除那些与其他高置信度边界框高度重叠的边界框。
- 结果排序:根据置信度对剩余的边界框进行排序。
在YOLOv5中的作用:
在YOLOv5中,Detections
起到以下作用:
- 转换模型输出:将模型的原始输出转换为人类可读的格式,包括边界框坐标、类别和置信度。
- 提高检测质量:通过置信度过滤和NMS,减少误检和重复检测,提高检测结果的准确性。
- 简化推理流程:将检测后处理步骤封装在一个类中,简化了推理代码的编写和模型的使用。
为什么要有Detections
:
以下是为什么YOLOv5需要Detections
的原因:
- 标准化处理流程:
Detections
为处理模型输出提供了一个标准化的流程,使得不同情况下都可以以相同的方式处理检测结果。 - 模块化设计:通过将后处理步骤封装在一个单独的类中,提高了代码的可维护性和可读性。
- 灵活性:
Detections
允许用户自定义处理参数,如置信度阈值和NMS阈值,以适应不同的应用需求。 - 性能优化:通过有效的后处理步骤,
Detections
有助于提高检测的性能,特别是在减少假阳性检测方面。
总之,Detections
是YOLOv5后处理阶段的核心部分,它确保了从模型输出到最终检测结果的有效转换,对于提高检测准确性和简化使用流程至关重要。
4.Classfy
class Classify(nn.Module):
def __init__(self, c1, c2, k=1, s=1, p=None, g=1):
"""
这是一个二级分类模块, 什么是二级分类模块? 比如做车牌的识别, 先识别出车牌, 如果想对车牌上的字进行识别, 就需要二级分类进一步检测.
如果对模型输出的分类再进行分类, 就可以用这个模块. 不过这里这个类写的比较简单, 若进行复杂的二级分类, 可以根据自己的实际任务可以改写, 这里代码不唯一.
Classification head, i.e. x(b,c1,20,20) to x(b,c2)
用于第二级分类 可以根据自己的任务自己改写,比较简单
比如车牌识别 检测到车牌之后还需要检测车牌在哪里,如果检测到侧拍后还想对车牌上的字再做识别的话就要进行二级分类
"""
# ch_in, ch_out, kernel, stride, padding, groups
super(Classify, self).__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
# 对z进行展平操作
return self.flat(self.conv(z)) # flatten to x(b,c2)
在YOLOv5中,Classify
不是一个单独的组件,而是模型的一部分,它通常指的是对边界框内的对象进行分类的层。YOLOv5模型的核心是它的检测头,其中包括分类和回归任务。下面我将解释YOLOv5中的分类部分的结构、原理及其作用。
结构:
YOLOv5的分类部分通常包含以下几个关键组件:
- 卷积层:在网络的最后几层,通常有一系列卷积层用于提取特征。
- 分类头:这些是网络的最后几层,它们负责输出每个边界框的类别概率。
- 激活函数:通常使用Sigmoid或Softmax激活函数来输出每个类别的概率。
具体来说,YOLOv5的分类头结构如下:
- 对于每个预测的边界框,网络输出一个向量,其长度等于数据集中的类别数。
- 如果使用Sigmoid激活函数,每个元素代表该类别存在的概率。
- 如果使用Softmax激活函数,整个向量代表一个多类别的概率分布。
原理:
分类部分的原理基于以下步骤:
- 特征提取:YOLOv5的骨干网络(如CSPDarknet53)提取输入图像的特征。
- 预测类别概率:在网络的检测头部分,对于每个预测的边界框,分类头输出一个类别概率向量。
- 激活函数:使用Sigmoid或Softmax激活函数将分类头输出的原始分数转换为概率。
在YOLOv5中的作用:
在YOLOv5中,分类部分的作用包括:
- 确定对象类别:为每个检测到的边界框分配一个或多个类别标签。
- 提供类别置信度:为每个类别分配一个概率值,表示模型对该类别预测的置信度。
为什么要有分类部分:
以下是为什么YOLOv5需要分类部分的原因:
- 多任务学习:YOLOv5是一个多任务模型,它不仅需要预测边界框的位置,还需要预测这些框内的对象类别。
- 细粒度识别:分类部分使得模型能够区分不同类别的对象,这对于细粒度的识别任务(如区分不同种类的动物或车辆)至关重要。
- 概率评估:通过输出类别概率,用户可以了解模型对每个类别的预测有多自信,这对于某些应用场景(如安全敏感的系统)非常有用。
总结来说,YOLOv5中的分类部分是模型不可或缺的一部分,它使得模型能够执行对象检测任务,并不仅仅定位对象,还能识别对象的具体类别。这是实现一个完整的目标检测系统的关键步骤。
三、修改C3模块
正如前文所说我们要将C3的Conv模块由三个改成两个
class C3(nn.Module):
# CSP Bottleneck with 3 convolutions
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
"""Initializes C3 module with options for channel count, bottleneck repetition, shortcut usage, group
convolutions, and 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) # optional act=FReLU(c2)
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
def forward(self, x):
"""Performs forward propagation using concatenated outputs from two convolutions and a Bottleneck sequence."""
return torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1)
这里修改了过后没有cv3了
在cmd中运行下面命令行(路径修改成自己的)
python train.py --img 900 --batch 2 --epoch 50 --data data/ab.yaml --./models/yolov5s.yaml --weights weights/yolov5s.pt --device '0'
四、总结
在本周学习过程中,我深入了解了YOLOv5算法中的common.py文件。这个文件是实现YOLOv5算法中各个模块的地方,如果我们需要修改某一模块(例如C3),那么就需要修改这个文件中对应模块的定义。文章详细介绍了common.py文件中的多个重要模块和函数,包括autopad、Conv、Focus、Bottleneck、BottleneckCSP、C3、SPP、Concat、Contract、Expand等。
通过本次学习,我对YOLOv5算法的实现有了更深入的理解,特别是对common.py文件中定义的模块和函数有了更清晰的认识。例如,文章详细介绍了C3模块的修改方案,我了解了如何修改C3模块的代码,以实现特定的结构要求。此外,文章还提供了如何运行YOLOv5训练过程的详细指导,包括修改C3模块后的训练步骤。给我提供了很多思路。