DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析

文章目录

  • 一、前言——从YOLOv3到YOLOv5
  • 二、代码解析
    • 2.1 运行起来项目 detect.py
    • 2.2 网络结构 models/yolo.py
    • 2.3 配置文件 yolov5s.yaml
    • 2.4 网络子结构 models/common.py
      • 2.4.1 Conv与Focus
      • 2.4.2 Bottleneck与BottleneckCSP
    • 2.5 训练 train.py
    • 2.6 打包成jit
    • 2.7 打包成onnx

先放官网大图

YOLOv5官方Pytorch实现:https://github.com/ultralytics/yolov5
再放干货:YOLOv5的所有权重:
链接:https://pan.baidu.com/s/13ThlTOz-rmY7nhvgITgBYg
提取码:w8jq

一、前言——从YOLOv3到YOLOv5

YOLOv3
YOLO原项目darknet(官方)截止2020年5月31日,并没有更新添加这个"YOLOv5"的链接。最新的一次update还是上个月YOLOv4重磅出炉的那次,官方正式添加了YOLOv4项目链接。

"YOLOv5"的项目团队是Ultralytics LLC 公司,很多人应该没有听过这家公司。但提到他们公司的一个项目,很多人应该就知道了,因为不少同学用过。那就是基于PyTorch复现的YOLOv3,按目前github上star数来看,应该是基于PyTorch复现YOLOv3中的排名第一。Amusi 之前还分享过此项目。

附上Pytorch版的YOLOv3:https://github.com/ultralytics/yolov3

他们复现的YOLOv3版而且还有APP版本
YOLOv3 in PyTorch > ONNX > CoreML > iOS

其实这个公司团队在YOLOv3上花的功夫蛮多的,不仅有APP版,还对YOLOv3进行了改进,官方介绍的性能效果可以说相当炸裂!另外项目维护的也很牛逼,star数已达4.7 k,commits 都快逼近2500次!
DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析_第1张图片
可见Ultralytics LLC 公司在YOLO社区上的贡献还是有一定影响力的,这也是为什么他们刚推出"YOLOv5",就得到不少人的关注。

YOLOv5
据官方称:“YOLOv5” 实际上还处于开发的阶段,预计2020年第2季度/第3季度将全部开发完成。目前放出来的版本,是集成了YOLOv3-SPP和YOLOv4部分特性。

那么"YOLOv5"的性能有多强呢,Ultralytics LLC给出的数据如下:
这里说一下,YOLOv5-x的性能已经达到:47.2 AP / 63 FPS,但项目是在 image size = 736的情况下测得。但Ultralytics LLC并没有给出"YOLOv5"的算法介绍(论文、博客其实都没有看到),所以我们只能通过代码查看"YOLOv5"的特性。只能说现在版本的"YOLOv5"集成了YOLOv3-SPP和YOLOv4的部分特性等。
DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析_第2张图片
DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析_第3张图片

二、代码解析

代码目录
DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析_第4张图片

2.1 运行起来项目 detect.py

代码目录:
DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析_第5张图片
1)拿到yolov5的代码,我们首先要把项目运行起来。在这里我们需要下载得到权重,在这里我下载了yolov5s.pt文件,把其放到weights文件夹下;然后找到detect.py运行,inference文件下推理得到output文件夹:DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析_第6张图片
这样我们的第一步项目就跑通了,这是我们拿到源码首先要完成的任务。

2.2 网络结构 models/yolo.py

根据配置文件,models文件夹下选择yolov5s.yaml(根据选择的权重观察),在yolo.py文件里运行下面代码,使用netron可视化网络结构。(关于netron模型可视化,我们只需要在命令行安装pip install netron即可,然后进入netron,复制地址进入,打包我们需要的模型,即可使用可视化。这个工具有助于我们分析网络的整体架构)

model = Model(opt.cfg).to(device)
torch.save(model,"m.pt")

DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析_第7张图片
这个结构看起来比较简单,我们用torch.jit导出jit格式来看模型详细架构,运行以下代码:

	# Create model
    model = Model(opt.cfg).to(device)
    x = torch.randn(1,3,384,640)
    script_models = torch.jit.trace(model,x)
    script_models.save("m.jit")

导出m.jit格式后,将其重命名为m1.pt,再进行netron可视化分析,这里由于图片较长,我就展示一部分了。
DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析_第8张图片
DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析_第9张图片

2.3 配置文件 yolov5s.yaml

# parameters
nc: 80  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # layer channel multiple

# anchors
anchors:
  - [116,90, 156,198, 373,326]  # P5/32
  - [30,61, 62,45, 59,119]  # P4/16
  - [10,13, 16,30, 33,23]  # P3/8

# YOLOv5 backbone
backbone:
  # [from, number, module, args]
  [[-1, 1, Focus, [64, 3]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, BottleneckCSP, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 9, BottleneckCSP, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, BottleneckCSP, [512]],
   [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
   [-1, 1, SPP, [1024, [5, 9, 13]]],
  ]

# YOLOv5 head
head:
  [[-1, 3, BottleneckCSP, [1024, False]],  # 9

   [-1, 1, Conv, [512, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, Concat, [1]],  # cat backbone P4
   [-1, 3, BottleneckCSP, [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, BottleneckCSP, [256, False]],
   [-1, 1, nn.Conv2d, [na * (nc + 5), 1, 1]],  # 18 (P3/8-small)

   [-2, 1, Conv, [256, 3, 2]],
   [[-1, 14], 1, Concat, [1]],  # cat head P4
   [-1, 3, BottleneckCSP, [512, False]],
   [-1, 1, nn.Conv2d, [na * (nc + 5), 1, 1]],  # 22 (P4/16-medium)

   [-2, 1, Conv, [512, 3, 2]],
   [[-1, 10], 1, Concat, [1]],  # cat head P5
   [-1, 3, BottleneckCSP, [1024, False]],
   [-1, 1, nn.Conv2d, [na * (nc + 5), 1, 1]],  # 26 (P5/32-large)

   [[], 1, Detect, [nc, anchors]],  # Detect(P5, P4, P3)
  ]

解析:[-1, 1, Focus, [64, 3]] , [[-1, 6], 1, Concat, [1]]

① -1代表动态计算上一层的通道数(-2代表计算上两层的通道数),设计的原因是一层一层下来的,但存在残差路由结构;[-1,6]代表把上一层与第六层cat起来。

② [64,3]:网络第一层输出是32个通道(把模型打印出来可以看到),但这里是64,这就与采样率有关:64乘以width_multiple=32,与网络第一层输出一致。
3代表这一层复制3次,3乘以depth_multiple等于1,即1层。最少也要有1层。

width_multiple: 0.50这个参数与网络设计有关,现在设计网络一般都不设计一个网络,如yolov3-tiny,yolov3-darknet53,yolov3-spp,但都是单独设计,不太好;如果我们设计几种网络,一般设计常规网络(不大不小),进行训练,效果不错我们再进行缩放,包含深度缩放depth_multiple宽度缩放width_multiple(通道数),这样的网络被证明效果是不错的,所以可以得到n个网络,减轻了设计负担。

缩放规则r^2βw<2,r(分辨率)β(深度即层数)w(通道数),希望网络的这些参数变大1倍,但计算量小于2。

2.4 网络子结构 models/common.py

这部分我们需要根据 yolov5s.yaml 配置文件查看主干网backbone和侦测网head查看不同的子结构。

2.4.1 Conv与Focus

class Conv(nn.Module):# 自定义卷积块:卷积_BN_激活。类比yolov4里的CBL结构
    # Standard convolution
    def __init__(self, c1, c2, k=1, s=1, g=1, act=True):  # ch_in, ch_out, kernel, stride, groups
        super(Conv, self).__init__()
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  # padding
        self.conv = nn.Conv2d(c1, c2, k, s, p, groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = nn.LeakyReLU(0.1, inplace=True) if act 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 Focus(nn.Module):# Focus模块:将W、H信息集中到通道空间
    # Focus wh information into c-space
    def __init__(self, c1, c2, k=1):
        super(Focus, self).__init__()
        self.conv = Conv(c1 * 4, c2, k, 1)

    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))

Focus模块输入通道扩充了4倍作用是可以使信息不丢失的情况下提高计算力。具体操作为把一张图片每隔一个像素拿到一个值,类似于邻近下采样,这样我们就拿到了4张图,4张图片互补,长的差不多,但信息没有丢失,拼接起来相当于RGB模式下变为12个通道,通道多少对计算量影响不大,但图像缩小,大大减少了计算量。可以当成下图理解:
DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析_第10张图片

2.4.2 Bottleneck与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)
        self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False)
        self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False)
        self.cv4 = Conv(c2, 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)])

    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))))

在这里我们可以画出其网络结构图如下图所示,其实我们还可以类比YOLOv4的子结构,我们发现其实他们大概类似,只不过YOLOv4第一个是CBM结构其他的CBM结构换成了卷积,这也解释了我在前一篇博文提到的CBM结构换卷积会有更好的效果,有兴趣可以跳到这篇博文DeepLearing—CV系列(十三)——YOLOv4完整核心理论详解。
DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析_第11张图片
下图为YOLOv4类似的子结构图:
在这里插入图片描述

2.5 训练 train.py

模型下载的是模型和权重都在一起的,我们想把里面的模型单独提出来不方便,yolov5提供另外一种方式:
当有hubconf.py这个文件,我们就可以用pytorch默认的模型,通过这个方式去加载一些默认模型,如果有这个文件,说明已经上传到pytorch上去了,形成了pytorch默认的一个模型。新建test01.py,将以下代码复制进去:

import torch
model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True, channels=3, classes=80)

这样就去github上下载了,yolov5完整代码下载位置在:C盘\用户\admin.cache\torch\hub\ultralytics_yolov5_master。运行后会报错,因为我们缺少权重文件(注意这里的权重文件名也是yolov5s.pt,但与我们刚才拿到的不一样,这个只包含权重,刚才的是模型和权重都在一块),因为普通用户无法使用google(原链接https://drive.google.com/drive/folders/1Drs_Aiu7xx6S-ix95f9kNsA6ueKRpN2J)
DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析_第12张图片
,我把权重放出来供大家下载,链接:https://pan.baidu.com/s/17wYKcivyyYsVj4adHaZVgw
提取码:0f7v

下载后把权重yolov5s.pt放到根目录(与test01.py在同一级目录即可),再运行一下就不报错了。

但不方便用,下载后我们可以导出模型装载到我们自己的模型上去:

from models import  yolo
import torch
model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True, channels=3, classes=80)
model1 = yolo.Model("models/yolov5s.yaml")
model1.load_state_dict(model.state_dict())
torch.save(model1.state_dict(),"my_yolov5s.pt")

这样根目录下就有我们自己的权重“my_yolov5s.pt”,接下来就是使用了:

model = yolo.Model("models/yolov5s.yaml")
model.load_state_dict(torch.load("my_yolov5s.pt"))
model.eval()
from PIL import Image,ImageDraw
from torchvision import transforms
from utils import utils
import numpy as np
img = Image.open(r"inference/images/zidane.jpg")
w,h = img.size
print(w,h)
tf = transforms.Compose([
    transforms.Resize((512,640)),
    transforms.ToTensor()
])
img_tensor = tf(img)
pred = model(img_tensor[None])[0]
det = utils.non_max_suppression(pred, 0.5,0.5)
img = img.resize((640,512))
Imgdraw = ImageDraw.Draw(img)
for box in det[0]:
    b = box.cpu().detach().long().numpy()
    print(b)
    Imgdraw.rectangle((b[0],b[1],b[2],b[3]))

img.show()

显示后的图片如下(我做了缩放):
DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析_第13张图片

2.6 打包成jit

打包成jit

from models import  yolo
import torch
from PIL import Image,ImageDraw
from torchvision import transforms
from utils import utils
import torch.jit

model = yolo.Model("models/yolov5s.yaml")
model.load_state_dict(torch.load("my_yolov5s.pt"))
model.eval()
img = Image.open(r"inference/images/zidane.jpg")
tf = transforms.Compose([
    transforms.Resize((512,640)),
    transforms.ToTensor()
])
img_tensor = tf(img)
pred = model(img_tensor[None])[0]
script_model = torch.jit.trace(model,img_tensor[None])
script_model.save("my_yolov5s.jit")

使用:

model = torch.jit.load("my_yolov5s.jit")
img = Image.open(r"inference/images/zidane.jpg")

tf = transforms.Compose([
    transforms.Resize((512,640)),
    transforms.ToTensor()
])
img_tensor = tf(img)
pred = model(img_tensor[None])[0]
det = utils.non_max_suppression(pred, 0.5,0.5)

img = img.resize((640,512))
Imgdraw = ImageDraw.Draw(img)
for box in det[0]:
    b = box.cpu().detach().long().numpy()
    print(b)
    Imgdraw.rectangle((b[0],b[1],b[2],b[3]))

img.show()

2.7 打包成onnx

可以查阅官网,看他怎么打包:进入pytorch官网:https://pytorch.org/,点击Tutorials,点击下图红色链接:找到相关打包代码
DeepLearing—CV系列(十四)——YOLOv5理论详解+Pytorch源码解析_第14张图片
打包成onnx:

from models import  yolo
import torch
from PIL import Image,ImageDraw
from torchvision import transforms
from utils import utils

model = yolo.Model("models/yolov5s.yaml")
model.load_state_dict(torch.load("my_yolov5s.pt"))
model.eval()

img = Image.open(r"inference/images/zidane.jpg")
tf = transforms.Compose([
    transforms.Resize((512,640)),
    transforms.ToTensor()
])
img_tensor = tf(img)
pred = model(img_tensor[None])[0]
model.model[-1].export = True
torch.onnx.export(model, img_tensor[None], "my_yolov5s.onnx", verbose=True, opset_version=11, input_names=['images'],
                      output_names=['output1','output2','output3'])  # output_names=['classes', 'boxes']

同样我们可以使用netron可视化分析网络结构(安装使用在上面有介绍),这里图示过大,我就不展示了。

使用onnx:
先安装onnxruntime:打开控制台:pip install onnxruntime

import onnxruntime

ort_session = onnxruntime.InferenceSession("my_yolov5s.onnx")
print("Exported model has been tested with ONNXRuntime, and the result looks good!")

img = Image.open(r"inference/images/zidane.jpg")
tf = transforms.Compose([
    transforms.Resize((512,640)),
    transforms.ToTensor()
])
img_tensor = tf(img)
ort_inputs = {ort_session.get_inputs()[0].name: img_tensor[None].numpy()}
pred = torch.tensor(ort_session.run(None, ort_inputs)[0])
det = utils.non_max_suppression(pred, 0.5,0.5)
img = img.resize((640,512))
Imgdraw = ImageDraw.Draw(img)

for box in det[0]:
    b = box.cpu().detach().long().numpy()
    print(b)
    Imgdraw.rectangle((b[0],b[1],b[2],b[3]))

img.show()

你可能感兴趣的:(深度学习,AI,yolo)