先放官网大图
YOLOv5官方Pytorch实现:https://github.com/ultralytics/yolov5
再放干货:YOLOv5的所有权重:
链接:https://pan.baidu.com/s/13ThlTOz-rmY7nhvgITgBYg
提取码:w8jq
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次!
可见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的部分特性等。
代码目录:
1)拿到yolov5的代码,我们首先要把项目运行起来。在这里我们需要下载得到权重,在这里我下载了yolov5s.pt文件,把其放到weights文件夹下;然后找到detect.py运行,inference文件下推理得到output文件夹:
这样我们的第一步项目就跑通了,这是我们拿到源码首先要完成的任务。
根据配置文件,models文件夹下选择yolov5s.yaml(根据选择的权重观察),在yolo.py文件里运行下面代码,使用netron可视化网络结构。(关于netron模型可视化,我们只需要在命令行安装pip install netron即可,然后进入netron,复制地址进入,打包我们需要的模型,即可使用可视化。这个工具有助于我们分析网络的整体架构)
model = Model(opt.cfg).to(device)
torch.save(model,"m.pt")
这个结构看起来比较简单,我们用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可视化分析,这里由于图片较长,我就展示一部分了。
# 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。
这部分我们需要根据 yolov5s.yaml 配置文件查看主干网backbone和侦测网head查看不同的子结构。
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个通道,通道多少对计算量影响不大,但图像缩小,大大减少了计算量。可以当成下图理解:
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完整核心理论详解。
下图为YOLOv4类似的子结构图:
模型下载的是模型和权重都在一起的,我们想把里面的模型单独提出来不方便,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)
,我把权重放出来供大家下载,链接: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()
打包成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()
可以查阅官网,看他怎么打包:进入pytorch官网:https://pytorch.org/,点击Tutorials,点击下图红色链接:找到相关打包代码
打包成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()