文章目录我之前学过yoloV1到yoloV3,但对于图像检测这些明显还不够,所以把yoloV5提上日程,以下是我学习yoloV5的笔记,主要参考此链接。
注:此篇博客非100%原创,主要是学习博客,如侵就删。
- 一、网络结构
- 1、主干网络(backbone)
- 1.1 BottleNeck
- 1.2 CSPnet
- 1.3 Focus结构
- 1.4 Silu激活函数
- 1.5 SPP结构
- 1.6 整个主干(backbone)实现代码
- 2、FPN(特征金字塔)
- 3、利用Yolo Head获取预测结果
- 二、预测结果的解码
- 1、预测框和先验框(anchor)的解析
YoloV5的网络结构:
一、网络结构 1、主干网络(backbone)
下面介绍主干网络用到的网络结构
1.1 BottleNeck作用:
1、结合不同层次的信息,使网络做的更深;
2、残差网络的特点是容易优化,并且能够通过增加相当的深度来提高准确率;
3、其内部的残差块使用了跳跃连接,缓解了在深度神经网络中增加深度带来的梯度消失问题。
import torch
import torch.nn as nn
class Bottleneck(nn.Module):
def __init__(self, c1, c2, e, shortcut=True):
super(Bottleneck, self).__init__()
self.c_ = int(c1 * e) # hidden channels
self.conv1 = nn.Conv2d(c1, self.c_, 1, 1)
self.conv2 = nn.Conv2d(self.c_, c2, 3, 1, 1)
self.add = shortcut and c1 == c2
def forward(self, x):
return x + self.conv2(self.conv1(x)) if self.add else self.conv2(self.conv1(x))
if __name__ == '__main__':
x = torch.randn(2, 3, 3, 3)
print(x.shape)
out = Bottleneck(3, 3, 0.5)(x)
print(out.shape)
输出:
torch.Size([2, 3, 3, 3]) torch.Size([2, 3, 3, 3])1.2 CSPnet
作用:
1、
过程:
1、输入的feature map先做1×1卷积,然后再进行Bottleneck,得到f1;
2、输入的feature map只做1×1卷积,得到f2;
3、对f1和f2进行堆叠,再进行1×1卷积得到f3;
class CspNet(nn.Module):
def __init__(self, c1, c2, e, n=1):
super(CspNet, self).__init__()
c_ = int(c1 * e)
self.conv1 = nn.Conv2d(c1, c_, 1, 1)
self.conv2 = nn.Conv2d(c1, c_, 1, 1)
self.conv3 = nn.Conv2d(2 * c_, c2, 1, 1)
self.m = nn.Sequential(*[Bottleneck(c_, c_, 0.5) for _ in range(n)])
def forward(self, x):
return self.conv3(torch.cat((self.m(self.conv1(x)), self.conv2(x)), dim=1))
if __name__ == '__main__':
x = torch.randn(2, 5, 3, 3)
print(x.shape)
out = CspNet(5, 5, 0.5)(x)
print(out.shape)
输出:
torch.Size([2, 5, 3, 3]) torch.Size([2, 5, 3, 3])1.3 Focus结构
定义:
使用了Focus网络结构,这个网络结构是在YoloV5里面使用到比较有趣的网络结构,具体操作是在一张图片中每隔一个像素拿到一个值,这个时候获得了四个独立的特征层,然后将四个独立的特征层进行堆叠,此时宽高信息就集中到了通道信息,输入通道扩充了四倍。拼接起来的特征层相对于原先的三通道变成了十二个通道,下图很好的展示了Focus结构,一看就能明白。
import torch
import torch.nn as nn
class Focus(nn.Module):
def __init__(self, c1, c2):
super(Focus, self).__init__()
self.conv1 = nn.Conv2d(c1 * 4, c2, 1, 1)
def forward(self, x):
return self.conv1(
torch.cat((x[..., ::2, ::2], x[..., ::2, 1::2], x[..., 1::2, ::2], x[..., 1::2, 1::2]), dim=1))
if __name__ == '__main__':
x = torch.randn(2, 3, 4, 4)
print(x.shape)
out = Focus(3, 3)(x)
print(out.shape)
输出:
torch.Size([2, 3, 4, 4]) torch.Size([2, 3, 2, 2])1.4 Silu激活函数
silu激活函数结合了relu和sigmoid函数,具备无上界有下界、平滑、非单调的特性。SiLU在深层模型上的效果优于 ReLU。可以看做是平滑的ReLU激活函数。
import matplotlib.pyplot as pl
import torch
import torch.nn as nn
import numpy as np
class SiLU(nn.Module):
@staticmethod
def forward(x):
return x * torch.sigmoid(x)
# x=torch.randn(2,3,3,3)
x = np.linspace(-10, 10, 100)
out = SiLU.forward(torch.from_numpy(x))
print(out.shape) # torch.Size([100])
fig = pl.figure()
pl.plot(x, out)
pl.show()
输出:
定义:
使用不同大小的池化核对feature map分别进行池化,然后进行堆叠之后再卷积;
作用:
通过不同大小的池化核进行池化,会提高网络的感受野。在YoloV4中,SPP是用在FPN里面的,在YoloV5中,SPP模块被用在了主干特征提取网络中。
import torch
import torch.nn as nn
class SPP(nn.Module):
def __init__(self, c1, c2, k=[5, 7, 13]):
super(SPP, self).__init__()
c_ = int(c1 // 2) # hidden channel
self.conv1 = nn.Conv2d(c1, c_, 1, 1)
self.conv2 = nn.Conv2d(c_ * (len(k) + 1), c2, 1, 1)
self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=_, stride=1, padding=_ // 2) for _ in k])
def forward(self, x):
x = self.conv1(x)
return self.conv2(torch.cat([x] + [m(x) for m in self.m], dim=1))
if __name__ == '__main__':
x = torch.randn(2, 3, 26, 26)
out = SPP(3, 3)(x)
print(out.shape)
输出:
torch.Size([2, 3, 26, 26])1.6 整个主干(backbone)实现代码
import torch
import torch.nn as nn
class SiLU(nn.Module):
@staticmethod
def forward(x):
return x * torch.sigmoid(x)
def autopad(k, p=None):
if p is None:
p = k // 2 if isinstance(k, int) else [x // 2 for x in k]
return p
class Focus(nn.Module):
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):
return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))
class Conv(nn.Module):
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):
super(Conv, self).__init__()
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
self.bn = nn.BatchNorm2d(c2, eps=0.001, momentum=0.03)
self.act = 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 fuseforward(self, x):
return self.act(self.conv(x))
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(Bottleneck, self).__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 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(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)])
# 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 SPP(nn.Module):
# 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))
class CSPDarknet(nn.Module):
def __init__(self, base_channels, base_depth):
super().__init__()
# -----------------------------------------------#
# 输入图片是640, 640, 3
# 初始的基本通道是64
# -----------------------------------------------#
# -----------------------------------------------#
# 利用focus网络结构进行特征提取
# 640, 640, 3 -> 320, 320, 12 -> 320, 320, 64
# -----------------------------------------------#
self.stem = Focus(3, base_channels, k=3)
# -----------------------------------------------#
# 完成卷积之后,320, 320, 64 -> 160, 160, 128
# 完成CSPlayer之后,160, 160, 128 -> 160, 160, 128
# -----------------------------------------------#
self.dark2 = nn.Sequential(
Conv(base_channels, base_channels * 2, 3, 2),
C3(base_channels * 2, base_channels * 2, base_depth),
)
# -----------------------------------------------#
# 完成卷积之后,160, 160, 128 -> 80, 80, 256
# 完成CSPlayer之后,80, 80, 256 -> 80, 80, 256
# -----------------------------------------------#
self.dark3 = nn.Sequential(
Conv(base_channels * 2, base_channels * 4, 3, 2),
C3(base_channels * 4, base_channels * 4, base_depth * 3),
)
# -----------------------------------------------#
# 完成卷积之后,80, 80, 256 -> 40, 40, 512
# 完成CSPlayer之后,40, 40, 512 -> 40, 40, 512
# -----------------------------------------------#
self.dark4 = nn.Sequential(
Conv(base_channels * 4, base_channels * 8, 3, 2),
C3(base_channels * 8, base_channels * 8, base_depth * 3),
)
# -----------------------------------------------#
# 完成卷积之后,40, 40, 512 -> 20, 20, 1024
# 完成SPP之后,20, 20, 1024 -> 20, 20, 1024
# 完成CSPlayer之后,20, 20, 1024 -> 20, 20, 1024
# -----------------------------------------------#
self.dark5 = nn.Sequential(
Conv(base_channels * 8, base_channels * 16, 3, 2),
SPP(base_channels * 16, base_channels * 16),
C3(base_channels * 16, base_channels * 16, base_depth, shortcut=False),
)
def forward(self, x):
x = self.stem(x)
x = self.dark2(x)
# -----------------------------------------------#
# dark3的输出为80, 80, 256,是一个有效特征层
# -----------------------------------------------#
x = self.dark3(x)
feat1 = x
# -----------------------------------------------#
# dark4的输出为40, 40, 512,是一个有效特征层
# -----------------------------------------------#
x = self.dark4(x)
feat2 = x
# -----------------------------------------------#
# dark5的输出为20, 20, 1024,是一个有效特征层
# -----------------------------------------------#
x = self.dark5(x)
feat3 = x
return feat1, feat2, feat3
if __name__ == '__main__':
x = torch.randn(2, 3, 640, 640)
out = CSPDarknet(64, 3)(x)
for item in out:
print(item.shape)
输出:
torch.Size([2, 256, 80, 80]) torch.Size([2, 512, 40, 40]) torch.Size([2, 1024, 20, 20])2、FPN(特征金字塔)
作用:进行加强特征提取
过程:
1、在backbone提取到3个有效特征层,当输入为[2, 3, 640, 640],则3个有效特征层分别为:[2, 256,80, 80],[2, 512, 40, 40],[2, 1024, 20, 20],然后利用这3个有效特征层进行FPN的构建;
2、feat3=(20,20,1024)的特征层进行1次1X1卷积调整通道后获得P5,P5进行上采样UmSampling2d后与feat2=(40,40,512)特征层进行结合,然后使用CSPLayer进行特征提取获得P5_upsample,此时获得的特征层为(40,40,512)。
3、P5_upsample=(40,40,512)的特征层进行1次1X1卷积调整通道后获得P4,P4进行上采样UmSampling2d后与feat1=(80,80,256)特征层进行结合,然后使用CSPLayer进行特征提取P3_out,此时获得的特征层为(80,80,256)。
4、P3_out=(80,80,256)的特征层进行一次3x3卷积进行下采样,下采样后与P4堆叠,然后使用CSPLayer进行特征提取P4_out,此时获得的特征层为(40,40,512)。
5、P4_out=(40,40,512)的特征层进行一次3x3卷积进行下采样,下采样后与P5堆叠,然后使用CSPLayer进行特征提取P5_out,此时获得的特征层为(20,20,1024)。
代码:
假设类别数:80
import torch
import torch.nn as nn
from nets.CSPdarknet import CSPDarknet, C3, Conv
# ---------------------------------------------------#
# yolo_body
# ---------------------------------------------------#
class YoloBody(nn.Module):
def __init__(self, anchors_mask, num_classes, phi):
super(YoloBody, self).__init__()
depth_dict = {'s': 0.33, 'm': 0.67, 'l': 1.00, 'x': 1.33, }
width_dict = {'s': 0.50, 'm': 0.75, 'l': 1.00, 'x': 1.25, }
dep_mul, wid_mul = depth_dict[phi], width_dict[phi]
base_channels = int(wid_mul * 64) # 64
base_depth = max(round(dep_mul * 3), 1) # 3
# -----------------------------------------------#
# 输入图片是640, 640, 3
# 初始的基本通道是64
# -----------------------------------------------#
# ---------------------------------------------------#
# 生成CSPdarknet53的主干模型
# 获得三个有效特征层,他们的shape分别是:
# 80,80,256
# 40,40,512
# 20,20,1024
# ---------------------------------------------------#
self.backbone = CSPDarknet(base_channels, base_depth)
self.upsample = nn.Upsample(scale_factor=2, mode="nearest")
self.conv_for_feat3 = Conv(base_channels * 16, base_channels * 8, 1, 1)
self.conv3_for_upsample1 = C3(base_channels * 16, base_channels * 8, base_depth, shortcut=False)
self.conv_for_feat2 = Conv(base_channels * 8, base_channels * 4, 1, 1)
self.conv3_for_upsample2 = C3(base_channels * 8, base_channels * 4, base_depth, shortcut=False)
self.down_sample1 = Conv(base_channels * 4, base_channels * 4, 3, 2)
self.conv3_for_downsample1 = C3(base_channels * 8, base_channels * 8, base_depth, shortcut=False)
self.down_sample2 = Conv(base_channels * 8, base_channels * 8, 3, 2)
self.conv3_for_downsample2 = C3(base_channels * 16, base_channels * 16, base_depth, shortcut=False)
self.yolo_head_P3 = nn.Conv2d(base_channels * 4, len(anchors_mask[2]) * (5 + num_classes), 1)
self.yolo_head_P4 = nn.Conv2d(base_channels * 8, len(anchors_mask[1]) * (5 + num_classes), 1)
self.yolo_head_P5 = nn.Conv2d(base_channels * 16, len(anchors_mask[0]) * (5 + num_classes), 1)
def forward(self, x):
# backbone
feat1, feat2, feat3 = self.backbone(x)
P5 = self.conv_for_feat3(feat3)
P5_upsample = self.upsample(P5)
P4 = torch.cat([P5_upsample, feat2], 1)
P4 = self.conv3_for_upsample1(P4)
P4 = self.conv_for_feat2(P4)
P4_upsample = self.upsample(P4)
P3 = torch.cat([P4_upsample, feat1], 1)
P3 = self.conv3_for_upsample2(P3)
P3_downsample = self.down_sample1(P3)
P4 = torch.cat([P3_downsample, P4], 1)
P4 = self.conv3_for_downsample1(P4)
P4_downsample = self.down_sample2(P4)
P5 = torch.cat([P4_downsample, P5], 1)
P5 = self.conv3_for_downsample2(P5)
# ---------------------------------------------------#
# 第三个特征层
# y3=(batch_size,75,80,80)
# ---------------------------------------------------#
out2 = self.yolo_head_P3(P3)
# ---------------------------------------------------#
# 第二个特征层
# y2=(batch_size,75,40,40)
# ---------------------------------------------------#
out1 = self.yolo_head_P4(P4)
# ---------------------------------------------------#
# 第一个特征层
# y1=(batch_size,75,20,20)
# ---------------------------------------------------#
out0 = self.yolo_head_P5(P5)
return out0, out1, out2
if __name__ == '__main__':
x = torch.randn(2, 3, 640, 640)
out = YoloBody([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 80, "l")(x)
for item in out:
print(item.shape)
输出:
torch.Size([2, 255, 20, 20]) torch.Size([2, 255, 40, 40]) torch.Size([2, 255, 80, 80])3、利用Yolo Head获取预测结果
下面复制于原链接:
利用FPN特征金字塔,我们可以获得三个加强特征,这三个加强特征的shape分别为(20,20,1024)、(40,40,512)、(80,80,256),然后我们利用这三个shape的特征层传入Yolo Head获得预测结果。
对于每一个特征层,我们可以获得利用一个卷积调整通道数,最终的通道数和需要区分的种类个数相关,在YoloV5里,每一个特征层上每一个特征点存在3个先验框。
如果使用的是voc训练集,类则为20种,最后的维度应该为75 = 3x25,三个特征层的shape为(20,20,75),(40,40,75),(80,80,75)。
最后的75可以拆分成3个25,对应3个先验框的25个参数,25可以拆分成4+1+20。
前4个参数用于判断每一个特征点的回归参数,回归参数调整后可以获得预测框;
第5个参数用于判断每一个特征点是否包含物体;
最后20个参数用于判断每一个特征点所包含的物体种类。
如果使用的是coco训练集,类则为80种,最后的维度应该为255 = 3x85,三个特征层的shape为(20,20,255),(40,40,255),(80,80,255)
最后的255可以拆分成3个85,对应3个先验框的85个参数,85可以拆分成4+1+80。
前4个参数用于判断每一个特征点的回归参数,回归参数调整后可以获得预测框;
第5个参数用于判断每一个特征点是否包含物体;
最后80个参数用于判断每一个特征点所包含的物体种类。
代码同上。
二、预测结果的解码 1、预测框和先验框(anchor)的解析由第二步我们可以获得三个特征层的预测结果,shape分别为(N,20,20,255),(N,40,40,255),(N,80,80,255)的数据。
但是这个预测结果并不对应着最终的预测框在图片上的位置,还需要解码才可以完成。在YoloV5里,每一个特征层上每一个特征点存在3个先验框。
每个特征层最后的255可以拆分成3个85,对应3个先验框的85个参数,我们先将其reshape一下,其结果为(N,20,20,3,85),(N,40.40,3,85),(N,80,80,3,85)。
其中的85可以拆分成4+1+80。
前4个参数用于判断每一个特征点的回归参数,回归参数调整后可以获得预测框;
第5个参数用于判断每一个特征点是否包含物体;
最后80个参数用于判断每一个特征点所包含的物体种类。
以(N,20,20,3,85)这个特征层为例,该特征层相当于将图像划分成20x20个特征点,如果某个特征点落在物体的对应框内,就用于预测该物体。
如图所示,蓝色的点为20x20的特征点,此时我们对左图黑色点的三个先验框进行解码操作演示:
1、进行中心预测点的计算,利用Regression预测结果前两个序号的内容对特征点的三个先验框中心坐标进行偏移,偏移后是右图红色的三个点;
2、进行预测框宽高的计算,利用Regression预测结果后两个序号的内容求指数后获得预测框的宽高;
3、此时获得的预测框就可以绘制在图片上了。
代码:
下面的代码我费了好长时间才看懂,这个链接有助于看懂下面的代码
关于调整anchor_w 和anchor_h的size,请先看一下这段代码,很详细,应该能看懂
import numpy as np
import torch
anchors = np.array([[116, 90], [156, 198], [373, 326], [30, 61], [62, 45], [59, 119], [10, 13], [16, 30], [33, 23]])
print(anchors.shape)
anchors_mask = [[6, 7, 8], [3, 4, 5], [0, 1, 2]]
scaled_anchors = [(anchor_width / 32, anchor_height / 32) for anchor_width, anchor_height in
anchors[anchors_mask[2]]]
print(scaled_anchors)
print(torch.FloatTensor(scaled_anchors))
print(torch.FloatTensor(scaled_anchors).shape)
# 输出:
(9, 2)
[(3.625, 2.8125), (4.875, 6.1875), (11.65625, 10.1875)]
tensor([[ 3.6250, 2.8125],
[ 4.8750, 6.1875],
[11.6562, 10.1875]])
torch.Size([3, 2])
------------------------------------
batch_size=2
input_height=input_width=20
anchor_w = torch.FloatTensor(scaled_anchors).index_select(1, torch.LongTensor([0])) # 取出anchor的width
anchor_h = torch.FloatTensor(scaled_anchors).index_select(1, torch.LongTensor([1])) # 取出anchor的high
print(anchor_w)
print(anchor_h)
# 输出:
tensor([[ 3.6250],
[ 4.8750],
[11.6562]])
tensor([[ 2.8125],
[ 6.1875],
[10.1875]])
-------------------------------------
anchor_w = anchor_w.repeat(batch_size, 1)
print(anchor_w)
print(anchor_w.shape)
# 输出:
tensor([[ 3.6250],
[ 4.8750],
[11.6562],
[ 3.6250],
[ 4.8750],
[11.6562]])
torch.Size([6, 1])
----------------------------------------
anchor_w=anchor_w.repeat(1, 1, input_height * input_width)
print(anchor_w)
print(anchor_w.shape)
# 输出:
tensor([[[ 3.6250, 3.6250, 3.6250, ..., 3.6250, 3.6250, 3.6250],
[ 4.8750, 4.8750, 4.8750, ..., 4.8750, 4.8750, 4.8750],
[11.6562, 11.6562, 11.6562, ..., 11.6562, 11.6562, 11.6562],
[ 3.6250, 3.6250, 3.6250, ..., 3.6250, 3.6250, 3.6250],
[ 4.8750, 4.8750, 4.8750, ..., 4.8750, 4.8750, 4.8750],
[11.6562, 11.6562, 11.6562, ..., 11.6562, 11.6562, 11.6562]]])
torch.Size([1, 6, 400])
---------------------------------------------
anchor_w=anchor_w.view([2,3,20,20])
print(anchor_w)
print(anchor_w.shape)
# 输出:
tensor([[[[ 3.6250, 3.6250, 3.6250, ..., 3.6250, 3.6250, 3.6250],
[ 3.6250, 3.6250, 3.6250, ..., 3.6250, 3.6250, 3.6250],
[ 3.6250, 3.6250, 3.6250, ..., 3.6250, 3.6250, 3.6250],
...,
[ 3.6250, 3.6250, 3.6250, ..., 3.6250, 3.6250, 3.6250],
[ 3.6250, 3.6250, 3.6250, ..., 3.6250, 3.6250, 3.6250],
[ 3.6250, 3.6250, 3.6250, ..., 3.6250, 3.6250, 3.6250]],
[[ 4.8750, 4.8750, 4.8750, ..., 4.8750, 4.8750, 4.8750],
[ 4.8750, 4.8750, 4.8750, ..., 4.8750, 4.8750, 4.8750],
[ 4.8750, 4.8750, 4.8750, ..., 4.8750, 4.8750, 4.8750],
...,
[ 4.8750, 4.8750, 4.8750, ..., 4.8750, 4.8750, 4.8750],
[ 4.8750, 4.8750, 4.8750, ..., 4.8750, 4.8750, 4.8750],
[ 4.8750, 4.8750, 4.8750, ..., 4.8750, 4.8750, 4.8750]],
[[11.6562, 11.6562, 11.6562, ..., 11.6562, 11.6562, 11.6562],
[11.6562, 11.6562, 11.6562, ..., 11.6562, 11.6562, 11.6562],
[11.6562, 11.6562, 11.6562, ..., 11.6562, 11.6562, 11.6562],
...,
[11.6562, 11.6562, 11.6562, ..., 11.6562, 11.6562, 11.6562],
[11.6562, 11.6562, 11.6562, ..., 11.6562, 11.6562, 11.6562],
[11.6562, 11.6562, 11.6562, ..., 11.6562, 11.6562, 11.6562]]],
[[[ 3.6250, 3.6250, 3.6250, ..., 3.6250, 3.6250, 3.6250],
[ 3.6250, 3.6250, 3.6250, ..., 3.6250, 3.6250, 3.6250],
[ 3.6250, 3.6250, 3.6250, ..., 3.6250, 3.6250, 3.6250],
...,
[ 3.6250, 3.6250, 3.6250, ..., 3.6250, 3.6250, 3.6250],
[ 3.6250, 3.6250, 3.6250, ..., 3.6250, 3.6250, 3.6250],
[ 3.6250, 3.6250, 3.6250, ..., 3.6250, 3.6250, 3.6250]],
[[ 4.8750, 4.8750, 4.8750, ..., 4.8750, 4.8750, 4.8750],
[ 4.8750, 4.8750, 4.8750, ..., 4.8750, 4.8750, 4.8750],
[ 4.8750, 4.8750, 4.8750, ..., 4.8750, 4.8750, 4.8750],
...,
[ 4.8750, 4.8750, 4.8750, ..., 4.8750, 4.8750, 4.8750],
[ 4.8750, 4.8750, 4.8750, ..., 4.8750, 4.8750, 4.8750],
[ 4.8750, 4.8750, 4.8750, ..., 4.8750, 4.8750, 4.8750]],
[[11.6562, 11.6562, 11.6562, ..., 11.6562, 11.6562, 11.6562],
[11.6562, 11.6562, 11.6562, ..., 11.6562, 11.6562, 11.6562],
[11.6562, 11.6562, 11.6562, ..., 11.6562, 11.6562, 11.6562],
...,
[11.6562, 11.6562, 11.6562, ..., 11.6562, 11.6562, 11.6562],
[11.6562, 11.6562, 11.6562, ..., 11.6562, 11.6562, 11.6562],
[11.6562, 11.6562, 11.6562, ..., 11.6562, 11.6562, 11.6562]]]])
torch.Size([2, 3, 20, 20])
# ---------------------------------------------------#
# 将预测值的每个特征层调成真实值
# ---------------------------------------------------#
def get_anchors_and_decode(input, input_shape, anchors, anchors_mask, num_classes):
# -----------------------------------------------#
# input batch_size, 3 * (4 + 1 + num_classes), 20, 20
# -----------------------------------------------#
batch_size = input.size(0)
input_height = input.size(2)
input_width = input.size(3)
# -----------------------------------------------#
# 输入为640x640时 input_shape = [640, 640] input_height = 20, input_width = 20
# 640 / 20 = 32
# stride_h = stride_w = 32
# -----------------------------------------------#
stride_h = input_shape[0] / input_height
stride_w = input_shape[1] / input_width
# -------------------------------------------------#
# 此时获得的scaled_anchors大小是相对于特征层的
# anchor_width, anchor_height / stride_h, stride_w
# -------------------------------------------------#
scaled_anchors = [(anchor_width / stride_w, anchor_height / stride_h) for anchor_width, anchor_height in
anchors[anchors_mask[2]]]
# -----------------------------------------------#
# batch_size, 3 * (4 + 1 + num_classes), 20, 20 =>
# batch_size, 3, 5 + num_classes, 20, 20 =>
# batch_size, 3, 20, 20, 4 + 1 + num_classes
# -----------------------------------------------#
prediction = input.view(batch_size, len(anchors_mask[2]),
num_classes + 5, input_height, input_width).permute(0, 1, 3, 4, 2).contiguous()
# -----------------------------------------------#
# 先验框的中心位置的调整参数
# -----------------------------------------------#
x = torch.sigmoid(prediction[..., 0])
y = torch.sigmoid(prediction[..., 1])
# -----------------------------------------------#
# 先验框的宽高调整参数
# -----------------------------------------------#
w = torch.sigmoid(prediction[..., 2])
h = torch.sigmoid(prediction[..., 3])
# -----------------------------------------------#
# 获得置信度,是否有物体 0 - 1
# -----------------------------------------------#
conf = torch.sigmoid(prediction[..., 4])
# -----------------------------------------------#
# 种类置信度 0 - 1
# -----------------------------------------------#
pred_cls = torch.sigmoid(prediction[..., 5:]) # shape:torch.Size([2, 3, 20, 20, 80])
FloatTensor = torch.cuda.FloatTensor if x.is_cuda else torch.FloatTensor
LongTensor = torch.cuda.LongTensor if x.is_cuda else torch.LongTensor
# ----------------------------------------------------------#
# 生成网格,先验框中心,网格左上角
# batch_size,3,20,20
# range(20)
# [
# [0, 1, 2, 3 ……, 19],
# [0, 1, 2, 3 ……, 19],
# …… (20次)
# [0, 1, 2, 3 ……, 19]
# ] * (batch_size * 3)
# [batch_size, 3, 20, 20]
#
# [
# [0, 0, 0, 0 ……, 0],
# [1, 1, 1, 1 ……, 1],
# …… (20次)
# [19, 19, 19, 19 ……, 19]
# ]* (batch_size * 3)
# [batch_size, 3, 20, 20]
# ----------------------------------------------------------#
grid_x = torch.linspace(0, input_width - 1, input_width).repeat(input_height, 1).repeat(
batch_size * len(anchors_mask[2]), 1, 1).view(x.shape).type(FloatTensor)
grid_y = torch.linspace(0, input_height - 1, input_height).repeat(input_width, 1).t().repeat(
batch_size * len(anchors_mask[2]), 1, 1).view(y.shape).type(FloatTensor)
# ----------------------------------------------------------#
# 按照网格格式生成先验框的宽高
# 3 => batch_size, 3, 20 * 20 => batch_size, 3, 20, 20
# 3 => batch_size, 3, 20 * 20 => batch_size, 3, 20, 20
# ----------------------------------------------------------#
# 1:按列索引
anchor_w = FloatTensor(scaled_anchors).index_select(1, LongTensor([0])) # 取出anchor的width
anchor_h = FloatTensor(scaled_anchors).index_select(1, LongTensor([1])) # 取出anchor的high
anchor_w = anchor_w.repeat(batch_size, 1).repeat(1, 1, input_height * input_width).view(w.shape)
anchor_h = anchor_h.repeat(batch_size, 1).repeat(1, 1, input_height * input_width).view(h.shape)
# ----------------------------------------------------------#
# 利用预测结果对先验框进行调整
# 首先调整先验框的中心,从先验框中心向右下角偏移
# 再调整先验框的宽高。
# x 0 ~ 1 => 0 ~ 2 => -0.5 ~ 1.5 + grid_x
# y 0 ~ 1 => 0 ~ 2 => -0.5 ~ 1.5 + grid_y
# w 0 ~ 1 => 0 ~ 2 => 0 ~ 4 * anchor_w
# h 0 ~ 1 => 0 ~ 2 => 0 ~ 4 * anchor_h
# ----------------------------------------------------------#
pred_boxes = FloatTensor(prediction[..., :4].shape)
pred_boxes[..., 0] = x.data * 2. - 0.5 + grid_x
pred_boxes[..., 1] = y.data * 2. - 0.5 + grid_y
pred_boxes[..., 2] = (w.data * 2) ** 2 * anchor_w
pred_boxes[..., 3] = (h.data * 2) ** 2 * anchor_h



