共计 6096 个字符,预计需要花费 16 分钟才能阅读完成。
1 网络结构
1.1、Backbone
(1)CSP 结构(Cross Stage Partial)
CSP 结构代码:common.pyc – BottleneckCSP
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(BottleneckCSP, self).__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1) #Conv = Conv2d + BatchNorm2d + LeakyReLU
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.LeakyReLU(0.1, inplace=True)
self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0)
for _ in range(n)]) #n 个 Bottleneck
def forward(self, x): #特征图分为两个部分
#第一部分:先后经过 Conv(C+B+L)和 n 个 Bottleneck 以及 Conv2d 得到 y 1
y1 = self.cv3(self.m(self.cv1(x)))
y2 = self.cv2(x) #第二部分:经过卷积 conv 得到 y2
#合并两个部分:cat y1 和 y2, 然后进行 BatchNorm2d 标准化 和 LeakyReLU 激活 再经过 Conv
return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1))))
(2)Focus
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(Focus, self).__init__()
self.conv = Conv(c1 * 4, c2, k, s, p, g, act)
def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2)
#::2 从 0 开始每两个取一个数, 1::2 从 1 开始每两个取一个数。第一维是 c,第二维是 w,第三维是 h
#x[..., ::2, ::2] 长宽从 0 开始相距 2 的位置也就是上图中的黄色 0,x[..., 1::2, ::2] 宽从 1 长从 0 开始相距 2 的位置也就是上图中的绿色 1
#因此 x[..., ::2, 1::2]为红色 2,x[..., 1::2, 1::2]为蓝色 3,#最后 cat 将这四个部分拼接到第一维 c,所以通道数 c 扩大四倍,w 和 h 各缩小两倍
return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))
(3)LeakyReLU
def leaky_relu(z, alpha=0.01):
return np.maximum(alpha*z, z)
SPP 的代码如下:common.py – class SPP
# Spatial pyramid pooling layer used in YOLOv3-SPP
def __init__(self, c1, c2, k=(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)
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))
1.2、Neck
Yolov5 的 Neck 部分采用了 PANet 结构,Neck 主要用于生成特征金字塔。特征金字塔会增强模型对于不同缩放尺度对象的检测,从而能够识别不同大小和尺度的同一个物体。
PANet 结构是在 FPN 的基础上引入了 Bottom-up path augmentation 结构。FPN 主要是通过融合高低层特征提升目标检测的效果,尤其可以提高小尺寸目标的检测效果。Bottom-up path augmentation 结构可以充分利用网络浅特征进行分割,网络浅层特征信息对于目标检测非常重要,因为目标检测是像素级别的分类浅层特征多是边缘形状等特征。PANet 在 FPN 的基础上加了一个自底向上方向的增强,使得顶层 feature map 也可以享受到底层带来的丰富的位置信息,从而提升了大物体的检测效果。如下图所示,(a)FPN backbone, (b)Bottom-up path augmentation。
1.3、Head
Head 进行最终检测部分,在 yolov5.yaml 的相关配置如下:
[-1, 1, nn.Conv2d, [na * (nc + 5), 1, 1]], # 26 (P5/32-large)
可以看到,YOLOv5 采用了与 YOLOv3 相同的 head 网络,都是 1*1 的卷积结构,并有三组 output,输出的特征图大小分辨为:
-
bs * 255 * 80 * 80; -
bs * 255 * 40 * 40; -
bs * 255 * 20 * 20;
- na(number of anchor) 为每组 anchor 的尺度数量(YOLOv5 中一共有 3 组 anchor,每组有 3 个尺度);
- nc 为 number of class(coco 的 class 为 80);
- 1 为前景背景的置信度 score;
-
4 为中心点坐标和宽高;
请在本站或者百度搜索:目标检测中的 Anchor 机制回顾
2 Loss Function
iou = inter / union # iou inter 是 A 和 B 的相交面积,union 是 A 和 B 的面积之和,计算 A 和 B 的 IOU
if GIoU or DIoU or CIoU: # 可以选择三种 IOU,yolov5 默认采用 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 GIoU: # Generalized IoU https://arxiv.org/pdf/1902.09630.pdf
c_area = cw * ch + 1e-16 # convex area 计算公式里的 C 就是能够包含 A 和 B 的最小矩形面积
return iou - (c_area - union) / c_area # GIoU
GIoU Loss 代码 : 在 compute_loss 函数
# Regression
pxy = ps[:, :2].sigmoid() * 2. - 0.5
#预测框的中心点
pwh = (ps[:, 2:4].sigmoid() * 2) ** 2 * anchors[i]
#预测框的
hw pbox = torch.cat((pxy, pwh), 1).to(device) # predicted box
iou = bbox_iou(pbox.T, tbox[i], x1y1x2y2=False, CIoU=True) # iou(prediction, target)
lbox += (1.0 - iou).mean() # iou loss
giou_loss = 1- giou
Classloss 和 Obj loss 代码:在 compute_loss 函数 obj loss 采用 BEC Logits loss,class loss 采用 BCEcls loss 二分类交叉熵损失。
# Objectness
tobj[b, a, gj, gi] = (1.0 - model.gr) + model.gr * iou.detach().clamp(0).type(tobj.dtype) # iou ratio
# Classification
if model.nc > 1: # cls loss (only if multiple classes)
t = torch.full_like(ps[:, 5:], cn, device=device) # targets
t[range(n), tcls[i]] = cp
lcls += BCEcls(ps[:, 5:], 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)]
lobj += BCEobj(pi[..., 4], tobj) * balance[i] # obj loss
3 Next
下一篇,《YOLOv5 从入门到部署》系列将会 介绍:实践部分——如何使用 YOLOv5 训练 自己的数据集。