可视化
python models/export.py --weights ./weights/yolov5s.pt --img 640 --batch 1
使用onnx文件可视化
网络配置文件
depth_multiple: 0.33 # 模型网络深度参数(如果已经是1了就不做操作,只对非1的乘这个系数)
width_multiple: 0.50 # 模型channel参数
9就是堆叠BottleneckCSP的次数(网络深度)
512就是网络channel参数
- from 当前层的输入(-1表示由上一层的输出结果做为当前层的输入)
- number 当前层堆叠几次
- module 当前层的名字
- args 是channel数和卷积核的大小
网络结构解读
关于骨干网络前两个结构块,见我的这篇博文:
netron中的参数
实际上BottleneckCSP深度为1;channer为64
看onnx结构图中,只要把
看成卷积+激活函数的组合操作,就很好的可以看懂onnx图了。
代码解读
首先读取网络结构配置文件yaml.
然后开始依据配置文件生成网络结构模型。
(使用函数parse_model来解析的)
parse_model解析网络架构的代码如下:
其输出参数如下:
(就是读取的yaml文件里面的内容)
anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple']
将d中的数据读取出来:
gd是模型深度系数(堆叠次数)
gw是模型通道channer的系数
nc是类别个数
anchor是先验框的系数:
na是anchor个数
no是:(类别数+5) × anchor个数
其中:
d['backbone'] + d['head']
的数据如下(就是从yaml的数据中一模一样读取过来的)
遍历backbone和head
for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): # from, number, module, args
以第一次遍历为例:
f、n、m、args、和完全对应。
对应的Focus代码:
再回来,
对堆叠次数乘以系数gd,但最少也得1次
当前是Focus,在定义的模块里面。。
再继续:
ch就是输入的channel
f就是yaml文件里面写再第一位的那个-1
得到:
c1是输入channel,c2是输出channel
再将channel维度乘以系数
args的参数:输入channel,输出channel,卷积核大小
3,32,3
将Fcous模块添加进去:
Focus(
(conv): Conv(
(conv): Conv2d(12, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(act): Hardswish()
)
)
将Fcous添加并计算参数
和控制台打印数据有关的参数:
m_.i, m_.f, m_.type, m_.np = i, f, t, np # attach index, 'from' index, type, number params
logger.info('%3s%18s%3s%10.0f %-40s%-30s' % (i, f, n, np, t, args)) # print
将当前层添加到layer里面
由开始第二层的遍历。。。。。
其中2是步长,如果是1特征图大小不变,是2的话,特征图减半:
。。。。。。
最后的结果:
初始化先验框和参数权重
前向传播
特征提取网络
定位到forward_once函数:
其中self.model
就是之前定义的网络结构
对self.model进学校遍历:
第一个m是:
Focus(
(conv): Conv(
(conv): Conv2d(12, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(act): Hardswish()
)
)
图像经过Focus的处理…
跳转到Focus:
- 先分块,再拼接,最后卷积
- 间隔来完成分块
- channel由3变为12(3x4)
- 为了加速
x(b,c,w,h) -> y(b,4c,w/2,h/2)
其卷积为:
Conv(
(conv): Conv2d(12, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(act): Hardswish()
)
再回来。。。
到了第二个模块的遍历。。
Conv(
(conv): Conv2d(32, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(act): Hardswish()
)
到三个模块的遍历
BottleneckCSP(
(cv1): Conv(
(conv): Conv2d(64, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(act): Hardswish()
)
(cv2): Conv2d(64, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
(cv3): Conv2d(32, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
(cv4): Conv(
(conv): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(act): Hardswish()
)
(bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(act): LeakyReLU(negative_slope=0.1, inplace=True)
(m): Sequential(
(0): Bottleneck(
(cv1): Conv(
(conv): Conv2d(32, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(act): Hardswish()
)
(cv2): Conv(
(conv): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(act): Hardswish()
)
)
)
)
跳转到BottleneckCSP函数
y1是主分支的路径
y2是捷径分支的路径
最后return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1))))
就是concat拼接合并后的操作
- 结合了resnet模块
- 提升map
- 叠加个数=配置文件中的个数x深度系数
然后,到spp结构了:
1024为输出特征图channel
5,9,13为不同kernel核的池化
4. 第四个分支直接短路,然后拼接
5. 先卷积(cv1),再三池化一短路,最后卷积(cv2)
6. 第一个cv1卷积为了降维(之后拼接就升回来了)
7.
ModuleList(
(0): MaxPool2d(kernel_size=5, stride=1, padding=2, dilation=1, ceil_mode=False)
(1): MaxPool2d(kernel_size=9, stride=1, padding=4, dilation=1, ceil_mode=False)
(2): MaxPool2d(kernel_size=13, stride=1, padding=6, dilation=1, ceil_mode=False)
)
不同的操作,最后得到的图中图大小是一样的,以便完成拼接操作
输出特征图计算公司如下:
例如(300-5+2x2)/1 +1 =300
(300-9+2x4)/1 +1 = 300特征图不会变化。。
channel输入是256,输出是256x4=1024,卷积再降维到512…、、
带上卷积操作:
整体的结构为:
(卷积后的那一些乘除加是hardswish激活函数的计算过程)
特征融合网络
深层网络的特征图感受野大-检测大目标
浅层网络的特征图感受野小-检测小目标
第一个模块中的卷积模块如下:
(i=11的那一层卷积操作)(第11层)
上采用模块(第12层)
特征拼接模块(第13层)
看到,yaml文件中的:
[[-1, 6], 1, Concat, [1]], # cat backbone P4
6,表示和第六层的拼接,其特征层维度一样。
torch.Size([1, 256, 8, 8])concat torch.Size([1, 256, 8, 8])
开始第二个卷积模块:
这里的拼接是和第四层进行concat的
上采样输出的结果就是16层的特征图
和第四层的拼接就是17层的输出维度
层数: 4 特征图大小: torch.Size([1, 128, 16, 16])
层数: 16 特征图大小: torch.Size([1, 128, 16, 16])
特征图拼接后:
层数: 17 特征图大小: torch.Size([1, 256, 16, 16])
开始第三个卷积模块
其concat是拼接第14个层结构。
第四个卷积块的concat
拼接前是22层输出的大小
拼接后是23层输出的大小
检测头
na是anchor的个数
nc是类别个数
5=4+1(位置预测+置信度)
具体的大图见pdf.
Detect(
(m): ModuleList(
(0): Conv2d(128, 21, kernel_size=(1, 1), stride=(1, 1))
(1): Conv2d(256, 21, kernel_size=(1, 1), stride=(1, 1))
(2): Conv2d(512, 21, kernel_size=(1, 1), stride=(1, 1))
)
)
将17层、20层、23层的进行检测头操作(1x1卷积)。
输出的通道数channel: 21 = 3x(2+5)