精华内容
下载资源
问答
  • 睿智的目标检测30——Pytorch搭建YoloV4目标检测平台

    万次阅读 多人点赞 2020-05-24 14:14:13
    睿智的目标检测30——Pytorch搭建YoloV4目标检测平台学习前言什么是YOLOV4代码下载YOLOV4改进的部分(不完全)YOLOV4结构解析1、主干特征提取网络Backbone2、特征金字塔3、YoloHead利用获得到的特征进行预测4、预测...

    学习前言

    也做了一下pytorch版本的。
    在这里插入图片描述

    什么是YOLOV4

    在这里插入图片描述
    YOLOV4是YOLOV3的改进版,在YOLOV3的基础上结合了非常多的小Tricks。
    尽管没有目标检测上革命性的改变,但是YOLOV4依然很好的结合了速度与精度。
    根据上图也可以看出来,YOLOV4在YOLOV3的基础上,在FPS不下降的情况下,mAP达到了44,提高非常明显。

    YOLOV4整体上的检测思路和YOLOV3相比相差并不大,都是使用三个特征层进行分类与回归预测。

    请注意!

    强烈建议在学习YOLOV4之前学习YOLOV3,因为YOLOV4确实可以看作是YOLOV3结合一系列改进的版本!

    强烈建议在学习YOLOV4之前学习YOLOV3,因为YOLOV4确实可以看作是YOLOV3结合一系列改进的版本!

    强烈建议在学习YOLOV4之前学习YOLOV3,因为YOLOV4确实可以看作是YOLOV3结合一系列改进的版本!

    (重要的事情说三遍!)

    YOLOV3可参考该博客:
    https://blog.csdn.net/weixin_44791964/article/details/105310627

    代码下载

    https://github.com/bubbliiiing/yolov4-pytorch
    喜欢的可以给个star噢!

    YOLOV4改进的部分(不完全)

    1、主干特征提取网络:DarkNet53 => CSPDarkNet53

    2、特征金字塔:SPP,PAN

    3、分类回归层:YOLOv3(未改变)

    4、训练用到的小技巧:Mosaic数据增强、Label Smoothing平滑、CIOU、学习率余弦退火衰减

    5、激活函数:使用Mish激活函数

    以上并非全部的改进部分,还存在一些其它的改进,由于YOLOV4使用的改进实在太多了,很难完全实现与列出来,这里只列出来了一些我比较感兴趣,而且非常有效的改进。

    还有一个重要的事情:
    论文中提到的SAM,作者自己的源码也没有使用。

    还有其它很多的tricks,不是所有的tricks都有提升,我也没法实现全部的tricks

    整篇BLOG会结合YOLOV3与YOLOV4的差别进行解析

    YOLOV4结构解析

    为方便理解,本文将所有通道数都放到了最后一维度。
    为方便理解,本文将所有通道数都放到了最后一维度。
    为方便理解,本文将所有通道数都放到了最后一维度。

    1、主干特征提取网络Backbone

    当输入是416x416时,特征结构如下:
    在这里插入图片描述
    当输入是608x608时,特征结构如下:
    在这里插入图片描述
    主干特征提取网络Backbone的改进点有两个:
    a).主干特征提取网络:DarkNet53 => CSPDarkNet53
    b).激活函数:使用Mish激活函数

    如果大家对YOLOV3比较熟悉的话,应该知道Darknet53的结构,其由一系列残差网络结构构成。在Darknet53中,其存在resblock_body模块,其由一次下采样多次残差结构的堆叠构成,Darknet53便是由resblock_body模块组合而成

    而在YOLOV4中,其对该部分进行了一定的修改。
    1、其一是将DarknetConv2D的激活函数由LeakyReLU修改成了Mish,卷积块由DarknetConv2D_BN_Leaky变成了DarknetConv2D_BN_Mish
    Mish函数的公式与图像如下:
    Mish=x×tanh(ln(1+ex)) Mish=x \times tanh(ln(1+e^x))
    在这里插入图片描述
    2、其二是将resblock_body的结构进行修改,使用了CSPnet结构。此时YOLOV4当中的Darknet53被修改成了CSPDarknet53
    在这里插入图片描述
    CSPnet结构并不算复杂,就是将原来的残差块的堆叠进行了一个拆分,拆成左右两部分:
    主干部分继续进行原来的残差块的堆叠
    另一部分则像一个残差边一样,经过少量处理直接连接到最后。
    因此可以认为CSP中存在一个大的残差边。

    #---------------------------------------------------#
    #   CSPdarknet的结构块
    #   存在一个大残差边
    #   这个大残差边绕过了很多的残差结构
    #---------------------------------------------------#
    class Resblock_body(nn.Module):
        def __init__(self, in_channels, out_channels, num_blocks, first):
            super(Resblock_body, self).__init__()
    
            self.downsample_conv = BasicConv(in_channels, out_channels, 3, stride=2)
    
            if first:
                self.split_conv0 = BasicConv(out_channels, out_channels, 1)
                self.split_conv1 = BasicConv(out_channels, out_channels, 1)  
                self.blocks_conv = nn.Sequential(
                    Resblock(channels=out_channels, hidden_channels=out_channels//2),
                    BasicConv(out_channels, out_channels, 1)
                )
                self.concat_conv = BasicConv(out_channels*2, out_channels, 1)
            else:
                self.split_conv0 = BasicConv(out_channels, out_channels//2, 1)
                self.split_conv1 = BasicConv(out_channels, out_channels//2, 1)
    
                self.blocks_conv = nn.Sequential(
                    *[Resblock(out_channels//2) for _ in range(num_blocks)],
                    BasicConv(out_channels//2, out_channels//2, 1)
                )
                self.concat_conv = BasicConv(out_channels, out_channels, 1)
    
        def forward(self, x):
            x = self.downsample_conv(x)
    
            x0 = self.split_conv0(x)
    
            x1 = self.split_conv1(x)
            x1 = self.blocks_conv(x1)
    
            x = torch.cat([x1, x0], dim=1)
            x = self.concat_conv(x)
    
            return x
    

    全部实现代码为:

    import torch
    import torch.nn.functional as F
    import torch.nn as nn
    import math
    from collections import OrderedDict
    
    #-------------------------------------------------#
    #   MISH激活函数
    #-------------------------------------------------#
    class Mish(nn.Module):
        def __init__(self):
            super(Mish, self).__init__()
    
        def forward(self, x):
            return x * torch.tanh(F.softplus(x))
    
    #-------------------------------------------------#
    #   卷积块
    #   CONV+BATCHNORM+MISH
    #-------------------------------------------------#
    class BasicConv(nn.Module):
        def __init__(self, in_channels, out_channels, kernel_size, stride=1):
            super(BasicConv, self).__init__()
    
            self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride, kernel_size//2, bias=False)
            self.bn = nn.BatchNorm2d(out_channels)
            self.activation = Mish()
    
        def forward(self, x):
            x = self.conv(x)
            x = self.bn(x)
            x = self.activation(x)
            return x
    
    #---------------------------------------------------#
    #   CSPdarknet的结构块的组成部分
    #   内部堆叠的残差块
    #---------------------------------------------------#
    class Resblock(nn.Module):
        def __init__(self, channels, hidden_channels=None, residual_activation=nn.Identity()):
            super(Resblock, self).__init__()
    
            if hidden_channels is None:
                hidden_channels = channels
    
            self.block = nn.Sequential(
                BasicConv(channels, hidden_channels, 1),
                BasicConv(hidden_channels, channels, 3)
            )
    
        def forward(self, x):
            return x+self.block(x)
    
    #---------------------------------------------------#
    #   CSPdarknet的结构块
    #   存在一个大残差边
    #   这个大残差边绕过了很多的残差结构
    #---------------------------------------------------#
    class Resblock_body(nn.Module):
        def __init__(self, in_channels, out_channels, num_blocks, first):
            super(Resblock_body, self).__init__()
    
            self.downsample_conv = BasicConv(in_channels, out_channels, 3, stride=2)
    
            if first:
                self.split_conv0 = BasicConv(out_channels, out_channels, 1)
                self.split_conv1 = BasicConv(out_channels, out_channels, 1)  
                self.blocks_conv = nn.Sequential(
                    Resblock(channels=out_channels, hidden_channels=out_channels//2),
                    BasicConv(out_channels, out_channels, 1)
                )
                self.concat_conv = BasicConv(out_channels*2, out_channels, 1)
            else:
                self.split_conv0 = BasicConv(out_channels, out_channels//2, 1)
                self.split_conv1 = BasicConv(out_channels, out_channels//2, 1)
    
                self.blocks_conv = nn.Sequential(
                    *[Resblock(out_channels//2) for _ in range(num_blocks)],
                    BasicConv(out_channels//2, out_channels//2, 1)
                )
                self.concat_conv = BasicConv(out_channels, out_channels, 1)
    
        def forward(self, x):
            x = self.downsample_conv(x)
    
            x0 = self.split_conv0(x)
    
            x1 = self.split_conv1(x)
            x1 = self.blocks_conv(x1)
    
            x = torch.cat([x1, x0], dim=1)
            x = self.concat_conv(x)
    
            return x
    
    class CSPDarkNet(nn.Module):
        def __init__(self, layers):
            super(CSPDarkNet, self).__init__()
            self.inplanes = 32
            self.conv1 = BasicConv(3, self.inplanes, kernel_size=3, stride=1)
            self.feature_channels = [64, 128, 256, 512, 1024]
    
            self.stages = nn.ModuleList([
                Resblock_body(self.inplanes, self.feature_channels[0], layers[0], first=True),
                Resblock_body(self.feature_channels[0], self.feature_channels[1], layers[1], first=False),
                Resblock_body(self.feature_channels[1], self.feature_channels[2], layers[2], first=False),
                Resblock_body(self.feature_channels[2], self.feature_channels[3], layers[3], first=False),
                Resblock_body(self.feature_channels[3], self.feature_channels[4], layers[4], first=False)
            ])
    
            self.num_features = 1
            # 进行权值初始化
            for m in self.modules():
                if isinstance(m, nn.Conv2d):
                    n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                    m.weight.data.normal_(0, math.sqrt(2. / n))
                elif isinstance(m, nn.BatchNorm2d):
                    m.weight.data.fill_(1)
                    m.bias.data.zero_()
    
    
        def forward(self, x):
            x = self.conv1(x)
    
            x = self.stages[0](x)
            x = self.stages[1](x)
            out3 = self.stages[2](x)
            out4 = self.stages[3](out3)
            out5 = self.stages[4](out4)
    
            return out3, out4, out5
    
    def darknet53(pretrained, **kwargs):
        model = CSPDarkNet([1, 2, 8, 8, 4])
        if pretrained:
            if isinstance(pretrained, str):
                model.load_state_dict(torch.load(pretrained))
            else:
                raise Exception("darknet request a pretrained path. got [{}]".format(pretrained))
        return model
    

    2、特征金字塔

    当输入是416x416时,特征结构如下:
    在这里插入图片描述
    当输入是608x608时,特征结构如下:
    在这里插入图片描述
    在特征金字塔部分,YOLOV4结合了两种改进:
    a).使用了SPP结构。
    b).使用了PANet结构。

    如上图所示,除去CSPDarknet53和Yolo Head的结构外,都是特征金字塔的结构。
    1、SPP结构参杂在对CSPdarknet53的最后一个特征层的卷积里,在对CSPdarknet53的最后一个特征层进行三次DarknetConv2D_BN_Leaky卷积后分别利用四个不同尺度的最大池化进行处理,最大池化的池化核大小分别为13x13、9x9、5x5、1x1(1x1即无处理)

    #---------------------------------------------------#
    #   SPP结构,利用不同大小的池化核进行池化
    #   池化后堆叠
    #---------------------------------------------------#
    class SpatialPyramidPooling(nn.Module):
        def __init__(self, pool_sizes=[5, 9, 13]):
            super(SpatialPyramidPooling, self).__init__()
    
            self.maxpools = nn.ModuleList([nn.MaxPool2d(pool_size, 1, pool_size//2) for pool_size in pool_sizes])
    
        def forward(self, x):
            features = [maxpool(x) for maxpool in self.maxpools[::-1]]
            features = torch.cat(features + [x], dim=1)
    
            return features
    

    其可以它能够极大地增加感受野,分离出最显著的上下文特征
    在这里插入图片描述
    2、PANet是2018的一种实例分割算法,其具体结构由反复提升特征的意思。
    在这里插入图片描述
    上图为原始的PANet的结构,可以看出来其具有一个非常重要的特点就是特征的反复提取
    在(a)里面是传统的特征金字塔结构,在完成特征金字塔从下到上的特征提取后,还需要实现(b)中从上到下的特征提取。

    而在YOLOV4当中,其主要是在三个有效特征层上使用了PANet结构。
    在这里插入图片描述
    实现代码如下:

    #---------------------------------------------------#
    #   yolo_body
    #---------------------------------------------------#
    class YoloBody(nn.Module):
        def __init__(self, config):
            super(YoloBody, self).__init__()
            self.config = config
            #  backbone
            self.backbone = darknet53(None)
    
            self.conv1 = make_three_conv([512,1024],1024)
            self.SPP = SpatialPyramidPooling()
            self.conv2 = make_three_conv([512,1024],2048)
    
            self.upsample1 = Upsample(512,256)
            self.conv_for_P4 = conv2d(512,256,1)
            self.make_five_conv1 = make_five_conv([256, 512],512)
    
            self.upsample2 = Upsample(256,128)
            self.conv_for_P3 = conv2d(256,128,1)
            self.make_five_conv2 = make_five_conv([128, 256],256)
            # 3*(5+num_classes)=3*(5+20)=3*(4+1+20)=75
            final_out_filter2 = len(config["yolo"]["anchors"][2]) * (5 + config["yolo"]["classes"])
            self.yolo_head3 = yolo_head([256, final_out_filter2],128)
    
            self.down_sample1 = conv2d(128,256,3,stride=2)
            self.make_five_conv3 = make_five_conv([256, 512],512)
            # 3*(5+num_classes)=3*(5+20)=3*(4+1+20)=75
            final_out_filter1 = len(config["yolo"]["anchors"][1]) * (5 + config["yolo"]["classes"])
            self.yolo_head2 = yolo_head([512, final_out_filter1],256)
    
    
            self.down_sample2 = conv2d(256,512,3,stride=2)
            self.make_five_conv4 = make_five_conv([512, 1024],1024)
            # 3*(5+num_classes)=3*(5+20)=3*(4+1+20)=75
            final_out_filter0 = len(config["yolo"]["anchors"][0]) * (5 + config["yolo"]["classes"])
            self.yolo_head1 = yolo_head([1024, final_out_filter0],512)
    
    
        def forward(self, x):
            #  backbone
            x2, x1, x0 = self.backbone(x)
    
            P5 = self.conv1(x0)
            P5 = self.SPP(P5)
            P5 = self.conv2(P5)
    
            P5_upsample = self.upsample1(P5)
            P4 = self.conv_for_P4(x1)
            P4 = torch.cat([P4,P5_upsample],axis=1)
            P4 = self.make_five_conv1(P4)
    
            P4_upsample = self.upsample2(P4)
            P3 = self.conv_for_P3(x2)
            P3 = torch.cat([P3,P4_upsample],axis=1)
            P3 = self.make_five_conv2(P3)
    
            P3_downsample = self.down_sample1(P3)
            P4 = torch.cat([P3_downsample,P4],axis=1)
            P4 = self.make_five_conv3(P4)
    
            P4_downsample = self.down_sample2(P4)
            P5 = torch.cat([P4_downsample,P5],axis=1)
            P5 = self.make_five_conv4(P5)
    
            out2 = self.yolo_head3(P3)
            out1 = self.yolo_head2(P4)
            out0 = self.yolo_head1(P5)
    
            return out0, out1, out2
    

    3、YoloHead利用获得到的特征进行预测

    当输入是416x416时,特征结构如下:
    在这里插入图片描述
    当输入是608x608时,特征结构如下:
    在这里插入图片描述
    1、在特征利用部分,YoloV4提取多特征层进行目标检测,一共提取三个特征层,分别位于中间层,中下层,底层,三个特征层的shape分别为(76,76,256)、(38,38,512)、(19,19,1024)。

    2、输出层的shape分别为(19,19,75),(38,38,75),(76,76,75),最后一个维度为75是因为该图是基于voc数据集的,它的类为20种,YoloV4只有针对每一个特征层存在3个先验框,所以最后维度为3x25;
    如果使用的是coco训练集,类则为80种,最后的维度应该为255 = 3x85
    ,三个特征层的shape为(19,19,255),(38,38,255),(76,76,255)

    实现代码如下:

    #---------------------------------------------------#
    #   最后获得yolov4的输出
    #---------------------------------------------------#
    def yolo_head(filters_list, in_filters):
        m = nn.Sequential(
            conv2d(in_filters, filters_list[0], 3),
            nn.Conv2d(filters_list[0], filters_list[1], 1),
        )
        return m
    
    #---------------------------------------------------#
    #   yolo_body
    #---------------------------------------------------#
    class YoloBody(nn.Module):
        def __init__(self, config):
            super(YoloBody, self).__init__()
            self.config = config
            #  backbone
            self.backbone = darknet53(None)
    
            self.conv1 = make_three_conv([512,1024],1024)
            self.SPP = SpatialPyramidPooling()
            self.conv2 = make_three_conv([512,1024],2048)
    
            self.upsample1 = Upsample(512,256)
            self.conv_for_P4 = conv2d(512,256,1)
            self.make_five_conv1 = make_five_conv([256, 512],512)
    
            self.upsample2 = Upsample(256,128)
            self.conv_for_P3 = conv2d(256,128,1)
            self.make_five_conv2 = make_five_conv([128, 256],256)
            # 3*(5+num_classes)=3*(5+20)=3*(4+1+20)=75
            final_out_filter2 = len(config["yolo"]["anchors"][2]) * (5 + config["yolo"]["classes"])
            self.yolo_head3 = yolo_head([256, final_out_filter2],128)
    
            self.down_sample1 = conv2d(128,256,3,stride=2)
            self.make_five_conv3 = make_five_conv([256, 512],512)
            # 3*(5+num_classes)=3*(5+20)=3*(4+1+20)=75
            final_out_filter1 = len(config["yolo"]["anchors"][1]) * (5 + config["yolo"]["classes"])
            self.yolo_head2 = yolo_head([512, final_out_filter1],256)
    
    
            self.down_sample2 = conv2d(256,512,3,stride=2)
            self.make_five_conv4 = make_five_conv([512, 1024],1024)
            # 3*(5+num_classes)=3*(5+20)=3*(4+1+20)=75
            final_out_filter0 = len(config["yolo"]["anchors"][0]) * (5 + config["yolo"]["classes"])
            self.yolo_head1 = yolo_head([1024, final_out_filter0],512)
    
    
        def forward(self, x):
            #  backbone
            x2, x1, x0 = self.backbone(x)
    
            P5 = self.conv1(x0)
            P5 = self.SPP(P5)
            P5 = self.conv2(P5)
    
            P5_upsample = self.upsample1(P5)
            P4 = self.conv_for_P4(x1)
            P4 = torch.cat([P4,P5_upsample],axis=1)
            P4 = self.make_five_conv1(P4)
    
            P4_upsample = self.upsample2(P4)
            P3 = self.conv_for_P3(x2)
            P3 = torch.cat([P3,P4_upsample],axis=1)
            P3 = self.make_five_conv2(P3)
    
            P3_downsample = self.down_sample1(P3)
            P4 = torch.cat([P3_downsample,P4],axis=1)
            P4 = self.make_five_conv3(P4)
    
            P4_downsample = self.down_sample2(P4)
            P5 = torch.cat([P4_downsample,P5],axis=1)
            P5 = self.make_five_conv4(P5)
    
            out2 = self.yolo_head3(P3)
            out1 = self.yolo_head2(P4)
            out0 = self.yolo_head1(P5)
    
            return out0, out1, out2
    

    4、预测结果的解码

    由第二步我们可以获得三个特征层的预测结果,shape分别为(N,19,19,255),(N,38,38,255),(N,76,76,255)的数据,对应每个图分为19x19、38x38、76x76的网格上3个预测框的位置。

    但是这个预测结果并不对应着最终的预测框在图片上的位置,还需要解码才可以完成。

    此处要讲一下yolo3的预测原理,yolo3的3个特征层分别将整幅图分为19x19、38x38、76x76的网格,每个网络点负责一个区域的检测。

    我们知道特征层的预测结果对应着三个预测框的位置,我们先将其reshape一下,其结果为(N,19,19,3,85),(N,38,38,3,85),(N,76,76,3,85)。

    最后一个维度中的85包含了4+1+80,分别代表x_offset、y_offset、h和w、置信度、分类结果。

    yolo3的解码过程就是将每个网格点加上它对应的x_offset和y_offset,加完后的结果就是预测框的中心,然后再利用 先验框和h、w结合 计算出预测框的长和宽。这样就能得到整个预测框的位置了。

    在这里插入图片描述
    当然得到最终的预测结构后还要进行得分排序与非极大抑制筛选
    这一部分基本上是所有目标检测通用的部分。不过该项目的处理方式与其它项目不同。其对于每一个类进行判别。
    1、取出每一类得分大于self.obj_threshold的框和得分。
    2、利用框的位置和得分进行非极大抑制。

    实现代码如下,当调用yolo_eval时,就会对每个特征层进行解码:

    class DecodeBox(nn.Module):
        def __init__(self, anchors, num_classes, img_size):
            super(DecodeBox, self).__init__()
            self.anchors = anchors
            self.num_anchors = len(anchors)
            self.num_classes = num_classes
            self.bbox_attrs = 5 + num_classes
            self.img_size = img_size
    
        def forward(self, input):
            # input为bs,3*(1+4+num_classes),13,13
    
            # 一共多少张图片
            batch_size = input.size(0)
            # 13,13
            input_height = input.size(2)
            input_width = input.size(3)
    
            # 计算步长
            # 每一个特征点对应原来的图片上多少个像素点
            # 如果特征层为13x13的话,一个特征点就对应原来的图片上的32个像素点
            # 416/13 = 32
            stride_h = self.img_size[1] / input_height
            stride_w = self.img_size[0] / input_width
    
            # 把先验框的尺寸调整成特征层大小的形式
            # 计算出先验框在特征层上对应的宽高
            scaled_anchors = [(anchor_width / stride_w, anchor_height / stride_h) for anchor_width, anchor_height in self.anchors]
    
            # bs,3*(5+num_classes),13,13 -> bs,3,13,13,(5+num_classes)
            prediction = input.view(batch_size, self.num_anchors,
                                    self.bbox_attrs, input_height, input_width).permute(0, 1, 3, 4, 2).contiguous()
    
            # 先验框的中心位置的调整参数
            x = torch.sigmoid(prediction[..., 0])  
            y = torch.sigmoid(prediction[..., 1])
            # 先验框的宽高调整参数
            w = prediction[..., 2]  # Width
            h = prediction[..., 3]  # Height
    
            # 获得置信度,是否有物体
            conf = torch.sigmoid(prediction[..., 4])
            # 种类置信度
            pred_cls = torch.sigmoid(prediction[..., 5:])  # Cls pred.
    
            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,13,13
            grid_x = torch.linspace(0, input_width - 1, input_width).repeat(input_width, 1).repeat(
                batch_size * self.num_anchors, 1, 1).view(x.shape).type(FloatTensor)
            grid_y = torch.linspace(0, input_height - 1, input_height).repeat(input_height, 1).t().repeat(
                batch_size * self.num_anchors, 1, 1).view(y.shape).type(FloatTensor)
    
            # 生成先验框的宽高
            anchor_w = FloatTensor(scaled_anchors).index_select(1, LongTensor([0]))
            anchor_h = FloatTensor(scaled_anchors).index_select(1, LongTensor([1]))
            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)
            
            # 计算调整后的先验框中心与宽高
            pred_boxes = FloatTensor(prediction[..., :4].shape)
            pred_boxes[..., 0] = x.data + grid_x
            pred_boxes[..., 1] = y.data + grid_y
            pred_boxes[..., 2] = torch.exp(w.data) * anchor_w
            pred_boxes[..., 3] = torch.exp(h.data) * anchor_h
    
            # 用于将输出调整为相对于416x416的大小
            _scale = torch.Tensor([stride_w, stride_h] * 2).type(FloatTensor)
            output = torch.cat((pred_boxes.view(batch_size, -1, 4) * _scale,
                                conf.view(batch_size, -1, 1), pred_cls.view(batch_size, -1, self.num_classes)), -1)
            return output.data
    

    5、在原图上进行绘制

    通过第四步,我们可以获得预测框在原图上的位置,而且这些预测框都是经过筛选的。这些筛选后的框可以直接绘制在图片上,就可以获得结果了。

    YOLOV4的训练

    1、YOLOV4的改进训练技巧

    a)、Mosaic数据增强

    Yolov4的mosaic数据增强参考了CutMix数据增强方式,理论上具有一定的相似性!
    CutMix数据增强方式利用两张图片进行拼接。
    在这里插入图片描述
    但是mosaic利用了四张图片,根据论文所说其拥有一个巨大的优点是丰富检测物体的背景!且在BN计算的时候一下子会计算四张图片的数据!
    就像下图这样:
    在这里插入图片描述
    实现思路如下:
    1、每次读取四张图片。

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    2、分别对四张图片进行翻转、缩放、色域变化等,并且按照四个方向位置摆好。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    3、进行图片的组合和框的组合
    在这里插入图片描述

    def rand(a=0, b=1):
        return np.random.rand()*(b-a) + a
    
    def merge_bboxes(bboxes, cutx, cuty):
    
        merge_bbox = []
        for i in range(len(bboxes)):
            for box in bboxes[i]:
                tmp_box = []
                x1,y1,x2,y2 = box[0], box[1], box[2], box[3]
    
                if i == 0:
                    if y1 > cuty or x1 > cutx:
                        continue
                    if y2 >= cuty and y1 <= cuty:
                        y2 = cuty
                        if y2-y1 < 5:
                            continue
                    if x2 >= cutx and x1 <= cutx:
                        x2 = cutx
                        if x2-x1 < 5:
                            continue
                    
                if i == 1:
                    if y2 < cuty or x1 > cutx:
                        continue
    
                    if y2 >= cuty and y1 <= cuty:
                        y1 = cuty
                        if y2-y1 < 5:
                            continue
                    
                    if x2 >= cutx and x1 <= cutx:
                        x2 = cutx
                        if x2-x1 < 5:
                            continue
    
                if i == 2:
                    if y2 < cuty or x2 < cutx:
                        continue
    
                    if y2 >= cuty and y1 <= cuty:
                        y1 = cuty
                        if y2-y1 < 5:
                            continue
    
                    if x2 >= cutx and x1 <= cutx:
                        x1 = cutx
                        if x2-x1 < 5:
                            continue
    
                if i == 3:
                    if y1 > cuty or x2 < cutx:
                        continue
    
                    if y2 >= cuty and y1 <= cuty:
                        y2 = cuty
                        if y2-y1 < 5:
                            continue
    
                    if x2 >= cutx and x1 <= cutx:
                        x1 = cutx
                        if x2-x1 < 5:
                            continue
    
                tmp_box.append(x1)
                tmp_box.append(y1)
                tmp_box.append(x2)
                tmp_box.append(y2)
                tmp_box.append(box[-1])
                merge_bbox.append(tmp_box)
        return merge_bbox
    
    def get_random_data(annotation_line, input_shape, random=True, hue=.1, sat=1.5, val=1.5, proc_img=True):
        '''random preprocessing for real-time data augmentation'''
        h, w = input_shape
        min_offset_x = 0.4
        min_offset_y = 0.4
        scale_low = 1-min(min_offset_x,min_offset_y)
        scale_high = scale_low+0.2
    
        image_datas = [] 
        box_datas = []
        index = 0
    
        place_x = [0,0,int(w*min_offset_x),int(w*min_offset_x)]
        place_y = [0,int(h*min_offset_y),int(w*min_offset_y),0]
        for line in annotation_line:
            # 每一行进行分割
            line_content = line.split()
            # 打开图片
            image = Image.open(line_content[0])
            image = image.convert("RGB") 
            # 图片的大小
            iw, ih = image.size
            # 保存框的位置
            box = np.array([np.array(list(map(int,box.split(',')))) for box in line_content[1:]])
            
            # image.save(str(index)+".jpg")
            # 是否翻转图片
            flip = rand()<.5
            if flip and len(box)>0:
                image = image.transpose(Image.FLIP_LEFT_RIGHT)
                box[:, [0,2]] = iw - box[:, [2,0]]
    
            # 对输入进来的图片进行缩放
            new_ar = w/h
            scale = rand(scale_low, scale_high)
            if new_ar < 1:
                nh = int(scale*h)
                nw = int(nh*new_ar)
            else:
                nw = int(scale*w)
                nh = int(nw/new_ar)
            image = image.resize((nw,nh), Image.BICUBIC)
    
            # 进行色域变换
            hue = rand(-hue, hue)
            sat = rand(1, sat) if rand()<.5 else 1/rand(1, sat)
            val = rand(1, val) if rand()<.5 else 1/rand(1, val)
            x = rgb_to_hsv(np.array(image)/255.)
            x[..., 0] += hue
            x[..., 0][x[..., 0]>1] -= 1
            x[..., 0][x[..., 0]<0] += 1
            x[..., 1] *= sat
            x[..., 2] *= val
            x[x>1] = 1
            x[x<0] = 0
            image = hsv_to_rgb(x)
    
            image = Image.fromarray((image*255).astype(np.uint8))
            # 将图片进行放置,分别对应四张分割图片的位置
            dx = place_x[index]
            dy = place_y[index]
            new_image = Image.new('RGB', (w,h), (128,128,128))
            new_image.paste(image, (dx, dy))
            image_data = np.array(new_image)/255
    
            # Image.fromarray((image_data*255).astype(np.uint8)).save(str(index)+"distort.jpg")
            
            index = index + 1
            box_data = []
            # 对box进行重新处理
            if len(box)>0:
                np.random.shuffle(box)
                box[:, [0,2]] = box[:, [0,2]]*nw/iw + dx
                box[:, [1,3]] = box[:, [1,3]]*nh/ih + dy
                box[:, 0:2][box[:, 0:2]<0] = 0
                box[:, 2][box[:, 2]>w] = w
                box[:, 3][box[:, 3]>h] = h
                box_w = box[:, 2] - box[:, 0]
                box_h = box[:, 3] - box[:, 1]
                box = box[np.logical_and(box_w>1, box_h>1)]
                box_data = np.zeros((len(box),5))
                box_data[:len(box)] = box
            
            image_datas.append(image_data)
            box_datas.append(box_data)
    
            img = Image.fromarray((image_data*255).astype(np.uint8))
            for j in range(len(box_data)):
                thickness = 3
                left, top, right, bottom  = box_data[j][0:4]
                draw = ImageDraw.Draw(img)
                for i in range(thickness):
                    draw.rectangle([left + i, top + i, right - i, bottom - i],outline=(255,255,255))
            img.show()
    
        
        # 将图片分割,放在一起
        cutx = np.random.randint(int(w*min_offset_x), int(w*(1 - min_offset_x)))
        cuty = np.random.randint(int(h*min_offset_y), int(h*(1 - min_offset_y)))
    
        new_image = np.zeros([h,w,3])
        new_image[:cuty, :cutx, :] = image_datas[0][:cuty, :cutx, :]
        new_image[cuty:, :cutx, :] = image_datas[1][cuty:, :cutx, :]
        new_image[cuty:, cutx:, :] = image_datas[2][cuty:, cutx:, :]
        new_image[:cuty, cutx:, :] = image_datas[3][:cuty, cutx:, :]
    
        # 对框进行进一步的处理
        new_boxes = merge_bboxes(box_datas, cutx, cuty)
    
        return new_image, new_boxes
    

    b)、Label Smoothing平滑

    标签平滑的思想很简单,具体公式如下:

    new_onehot_labels = onehot_labels * (1 - label_smoothing) + label_smoothing / num_classes
    

    当label_smoothing的值为0.01得时候,公式变成如下所示:

    new_onehot_labels = y * (1 - 0.01) + 0.01 / num_classes
    

    其实Label Smoothing平滑就是将标签进行一个平滑,原始的标签是0、1,在平滑后变成0.005(如果是二分类)、0.995,也就是说对分类准确做了一点惩罚,让模型不可以分类的太准确,太准确容易过拟合。

    实现代码如下:

    #---------------------------------------------------#
    #   平滑标签
    #---------------------------------------------------#
    def smooth_labels(y_true, label_smoothing,num_classes):
        return y_true * (1.0 - label_smoothing) + label_smoothing / num_classes
    

    c)、CIOU

    IoU是比值的概念,对目标物体的scale是不敏感的。然而常用的BBox的回归损失优化和IoU优化不是完全等价的,寻常的IoU无法直接优化没有重叠的部分。

    于是有人提出直接使用IOU作为回归优化loss,CIOU是其中非常优秀的一种想法。

    CIOU将目标与anchor之间的距离,重叠率、尺度以及惩罚项都考虑进去,使得目标框回归变得更加稳定,不会像IoU和GIoU一样出现训练过程中发散等问题。而惩罚因子把预测框长宽比拟合目标框的长宽比考虑进去。

    在这里插入图片描述
    CIOU公式如下
    CIOU=IOUρ2(b,bgt)c2αv CIOU = IOU - \frac{\rho^2(b,b^{gt})}{c^2} - \alpha v
    其中,ρ2(b,bgt)\rho^2(b,b^{gt})分别代表了预测框和真实框的中心点的欧式距离。 c代表的是能够同时包含预测框和真实框的最小闭包区域的对角线距离。

    α\alphavv的公式如下
    α=v1IOU+v \alpha = \frac{v}{1-IOU+v}
    v=4π2(arctanwgthgtarctanwh)2 v = \frac{4}{\pi ^2}(arctan\frac{w^{gt}}{h^{gt}}-arctan\frac{w}{h})^2
    把1-CIOU就可以得到相应的LOSS了。
    LOSSCIOU=1IOU+ρ2(b,bgt)c2+αv LOSS_{CIOU} = 1 - IOU + \frac{\rho^2(b,b^{gt})}{c^2} + \alpha v

    def box_ciou(b1, b2):
        """
        输入为:
        ----------
        b1: tensor, shape=(batch, feat_w, feat_h, anchor_num, 4), xywh
        b2: tensor, shape=(batch, feat_w, feat_h, anchor_num, 4), xywh
    
        返回为:
        -------
        ciou: tensor, shape=(batch, feat_w, feat_h, anchor_num, 1)
        """
        # 求出预测框左上角右下角
        b1_xy = b1[..., :2]
        b1_wh = b1[..., 2:4]
        b1_wh_half = b1_wh/2.
        b1_mins = b1_xy - b1_wh_half
        b1_maxes = b1_xy + b1_wh_half
        # 求出真实框左上角右下角
        b2_xy = b2[..., :2]
        b2_wh = b2[..., 2:4]
        b2_wh_half = b2_wh/2.
        b2_mins = b2_xy - b2_wh_half
        b2_maxes = b2_xy + b2_wh_half
    
        # 求真实框和预测框所有的iou
        intersect_mins = torch.max(b1_mins, b2_mins)
        intersect_maxes = torch.min(b1_maxes, b2_maxes)
        intersect_wh = torch.max(intersect_maxes - intersect_mins, torch.zeros_like(intersect_maxes))
        intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
        b1_area = b1_wh[..., 0] * b1_wh[..., 1]
        b2_area = b2_wh[..., 0] * b2_wh[..., 1]
        union_area = b1_area + b2_area - intersect_area
        iou = intersect_area / (union_area + 1e-6)
    
        # 计算中心的差距
        center_distance = torch.sum(torch.pow((b1_xy - b2_xy), 2), axis=-1)
        # 找到包裹两个框的最小框的左上角和右下角
        enclose_mins = torch.min(b1_mins, b2_mins)
        enclose_maxes = torch.max(b1_maxes, b2_maxes)
        enclose_wh = torch.max(enclose_maxes - enclose_mins, torch.zeros_like(intersect_maxes))
        # 计算对角线距离
        enclose_diagonal = torch.sum(torch.pow(enclose_wh,2), axis=-1)
        ciou = iou - 1.0 * (center_distance) / (enclose_diagonal + 1e-7)
        
        v = (4 / (math.pi ** 2)) * torch.pow((torch.atan(b1_wh[..., 0]/b1_wh[..., 1]) - torch.atan(b2_wh[..., 0]/b2_wh[..., 1])), 2)
        alpha = v / (1.0 - iou + v)
        ciou = ciou - alpha * v
        return ciou
    

    d)、学习率余弦退火衰减

    余弦退火衰减法,学习率会先上升再下降,这是退火优化法的思想。(关于什么是退火算法可以百度。)

    上升的时候使用线性上升,下降的时候模拟cos函数下降。执行多次。

    效果如图所示:
    在这里插入图片描述
    pytorch有直接实现的函数,可直接调用。

    lr_scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=5, eta_min=1e-5)
    

    2、loss组成

    a)、计算loss所需参数

    在计算loss的时候,实际上是y_pre和y_true之间的对比:
    y_pre就是一幅图像经过网络之后的输出,内部含有三个特征层的内容;其需要解码才能够在图上作画
    y_true就是一个真实图像中,它的每个真实框对应的(19,19)、(38,38)、(76,76)网格上的偏移位置、长宽与种类。其仍需要编码才能与y_pred的结构一致
    实际上y_pre和y_true内容的shape都是
    (batch_size,19,19,3,85)
    (batch_size,38,38,3,85)
    (batch_size,76,76,3,85)

    b)、y_pre是什么

    网络最后输出的内容就是三个特征层每个网格点对应的预测框及其种类,即三个特征层分别对应着图片被分为不同size的网格后,每个网格点上三个先验框对应的位置、置信度及其种类。
    对于输出的y1、y2、y3而言,[…, : 2]指的是相对于每个网格点的偏移量,[…, 2: 4]指的是宽和高,[…, 4: 5]指的是该框的置信度,[…, 5: ]指的是每个种类的预测概率。
    现在的y_pre还是没有解码的,解码了之后才是真实图像上的情况。

    c)、y_true是什么。

    y_true就是一个真实图像中,它的每个真实框对应的(19,19)、(38,38)、(76,76)网格上的偏移位置、长宽与种类。其仍需要编码才能与y_pred的结构一致

    d)、loss的计算过程

    在得到了y_pre和y_true后怎么对比呢?不是简单的减一下!

    loss值需要对三个特征层进行处理,这里以最小的特征层为例。
    1、利用y_true取出该特征层中真实存在目标的点的位置(m,19,19,3,1)及其对应的种类(m,19,19,3,80)。
    2、将prediction的预测值输出进行处理,得到reshape后的预测值y_pre,shape为(m,19,19,3,85)。还有解码后的xy,wh。
    3、对于每一幅图,计算其中所有真实框与预测框的IOU,如果某些预测框和真实框的重合程度大于0.5,则忽略。
    4、计算ciou作为回归的loss,这里只计算正样本的回归loss。
    5、计算置信度的loss,其有两部分构成,第一部分是实际上存在目标的,预测结果中置信度的值与1对比;第二部分是实际上不存在目标的,在第四步中得到其最大IOU的值与0对比。
    6、计算预测种类的loss,其计算的是实际上存在目标的,预测类与真实类的差距。

    其实际上计算的总的loss是三个loss的和,这三个loss分别是:

    • 实际存在的框,CIOU LOSS
    • 实际存在的框,预测结果中置信度的值与1对比;实际不存在的框,预测结果中置信度的值与0对比,该部分要去除被忽略的不包含目标的框
    • 实际存在的框,种类预测结果与实际结果的对比

    其实际代码如下:

    
    #---------------------------------------------------#
    #   平滑标签
    #---------------------------------------------------#
    def smooth_labels(y_true, label_smoothing,num_classes):
        return y_true * (1.0 - label_smoothing) + label_smoothing / num_classes
    
    def box_ciou(b1, b2):
        """
        输入为:
        ----------
        b1: tensor, shape=(batch, feat_w, feat_h, anchor_num, 4), xywh
        b2: tensor, shape=(batch, feat_w, feat_h, anchor_num, 4), xywh
    
        返回为:
        -------
        ciou: tensor, shape=(batch, feat_w, feat_h, anchor_num, 1)
        """
        # 求出预测框左上角右下角
        b1_xy = b1[..., :2]
        b1_wh = b1[..., 2:4]
        b1_wh_half = b1_wh/2.
        b1_mins = b1_xy - b1_wh_half
        b1_maxes = b1_xy + b1_wh_half
        # 求出真实框左上角右下角
        b2_xy = b2[..., :2]
        b2_wh = b2[..., 2:4]
        b2_wh_half = b2_wh/2.
        b2_mins = b2_xy - b2_wh_half
        b2_maxes = b2_xy + b2_wh_half
    
        # 求真实框和预测框所有的iou
        intersect_mins = torch.max(b1_mins, b2_mins)
        intersect_maxes = torch.min(b1_maxes, b2_maxes)
        intersect_wh = torch.max(intersect_maxes - intersect_mins, torch.zeros_like(intersect_maxes))
        intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
        b1_area = b1_wh[..., 0] * b1_wh[..., 1]
        b2_area = b2_wh[..., 0] * b2_wh[..., 1]
        union_area = b1_area + b2_area - intersect_area
        iou = intersect_area / (union_area + 1e-6)
    
        # 计算中心的差距
        center_distance = torch.sum(torch.pow((b1_xy - b2_xy), 2), axis=-1)
        # 找到包裹两个框的最小框的左上角和右下角
        enclose_mins = torch.min(b1_mins, b2_mins)
        enclose_maxes = torch.max(b1_maxes, b2_maxes)
        enclose_wh = torch.max(enclose_maxes - enclose_mins, torch.zeros_like(intersect_maxes))
        # 计算对角线距离
        enclose_diagonal = torch.sum(torch.pow(enclose_wh,2), axis=-1)
        ciou = iou - 1.0 * (center_distance) / (enclose_diagonal + 1e-7)
        
        v = (4 / (math.pi ** 2)) * torch.pow((torch.atan(b1_wh[..., 0]/b1_wh[..., 1]) - torch.atan(b2_wh[..., 0]/b2_wh[..., 1])), 2)
        alpha = v / (1.0 - iou + v)
        ciou = ciou - alpha * v
        return ciou
    
    def clip_by_tensor(t,t_min,t_max):
        t=t.float()
        result = (t >= t_min).float() * t + (t < t_min).float() * t_min
        result = (result <= t_max).float() * result + (result > t_max).float() * t_max
        return result
    
    def MSELoss(pred,target):
        return (pred-target)**2
    
    def BCELoss(pred,target):
        epsilon = 1e-7
        pred = clip_by_tensor(pred, epsilon, 1.0 - epsilon)
        output = -target * torch.log(pred) - (1.0 - target) * torch.log(1.0 - pred)
        return output
    
    class YOLOLoss(nn.Module):
        def __init__(self, anchors, num_classes, img_size, label_smooth=0, cuda=True):
            super(YOLOLoss, self).__init__()
            self.anchors = anchors
            self.num_anchors = len(anchors)
            self.num_classes = num_classes
            self.bbox_attrs = 5 + num_classes
            self.img_size = img_size
            self.label_smooth = label_smooth
    
            self.ignore_threshold = 0.5
            self.lambda_conf = 1.0
            self.lambda_cls = 1.0
            self.lambda_loc = 1.0
            self.cuda = cuda
    
        def forward(self, input, targets=None):
            # input为bs,3*(5+num_classes),13,13
            
            # 一共多少张图片
            bs = input.size(0)
            # 特征层的高
            in_h = input.size(2)
            # 特征层的宽
            in_w = input.size(3)
    
            # 计算步长
            # 每一个特征点对应原来的图片上多少个像素点
            # 如果特征层为13x13的话,一个特征点就对应原来的图片上的32个像素点
            stride_h = self.img_size[1] / in_h
            stride_w = self.img_size[0] / in_w
    
            # 把先验框的尺寸调整成特征层大小的形式
            # 计算出先验框在特征层上对应的宽高
            scaled_anchors = [(a_w / stride_w, a_h / stride_h) for a_w, a_h in self.anchors]
            # bs,3*(5+num_classes),13,13 -> bs,3,13,13,(5+num_classes)
            prediction = input.view(bs, int(self.num_anchors/3),
                                    self.bbox_attrs, in_h, in_w).permute(0, 1, 3, 4, 2).contiguous()
            
            # 对prediction预测进行调整
            conf = torch.sigmoid(prediction[..., 4])  # Conf
            pred_cls = torch.sigmoid(prediction[..., 5:])  # Cls pred.
    
            # 找到哪些先验框内部包含物体
            mask, noobj_mask, t_box, tconf, tcls, box_loss_scale_x, box_loss_scale_y = self.get_target(targets, scaled_anchors,in_w, in_h,self.ignore_threshold)
    
            noobj_mask, pred_boxes_for_ciou = self.get_ignore(prediction, targets, scaled_anchors, in_w, in_h, noobj_mask)
    
            if self.cuda:
                mask, noobj_mask = mask.cuda(), noobj_mask.cuda()
                box_loss_scale_x, box_loss_scale_y= box_loss_scale_x.cuda(), box_loss_scale_y.cuda()
                tconf, tcls = tconf.cuda(), tcls.cuda()
                pred_boxes_for_ciou = pred_boxes_for_ciou.cuda()
                t_box = t_box.cuda()
    
            box_loss_scale = 2-box_loss_scale_x*box_loss_scale_y
            #  losses.
            ciou = (1 - box_ciou( pred_boxes_for_ciou[mask.bool()], t_box[mask.bool()]))* box_loss_scale[mask.bool()]
    
            loss_loc = torch.sum(ciou / bs)
            loss_conf = torch.sum(BCELoss(conf, mask) * mask / bs) + \
                        torch.sum(BCELoss(conf, mask) * noobj_mask / bs)
                        
            # print(smooth_labels(tcls[mask == 1],self.label_smooth,self.num_classes))
            loss_cls = torch.sum(BCELoss(pred_cls[mask == 1], smooth_labels(tcls[mask == 1],self.label_smooth,self.num_classes))/bs)
            # print(loss_loc,loss_conf,loss_cls)
            loss = loss_conf * self.lambda_conf + loss_cls * self.lambda_cls + loss_loc * self.lambda_loc
            return loss, loss_conf.item(), loss_cls.item(), loss_loc.item()
    
        def get_target(self, target, anchors, in_w, in_h, ignore_threshold):
            # 计算一共有多少张图片
            bs = len(target)
            # 获得先验框
            anchor_index = [[0,1,2],[3,4,5],[6,7,8]][[13,26,52].index(in_w)]
            subtract_index = [0,3,6][[13,26,52].index(in_w)]
            # 创建全是0或者全是1的阵列
            mask = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            noobj_mask = torch.ones(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
    
            tx = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            ty = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            tw = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            th = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            t_box = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, 4, requires_grad=False)
            tconf = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            tcls = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, self.num_classes, requires_grad=False)
    
            box_loss_scale_x = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            box_loss_scale_y = torch.zeros(bs, int(self.num_anchors/3), in_h, in_w, requires_grad=False)
            for b in range(bs):
                for t in range(target[b].shape[0]):
                    # 计算出在特征层上的点位
                    gx = target[b][t, 0] * in_w
                    gy = target[b][t, 1] * in_h
                    
                    gw = target[b][t, 2] * in_w
                    gh = target[b][t, 3] * in_h
    
                    # 计算出属于哪个网格
                    gi = int(gx)
                    gj = int(gy)
    
                    # 计算真实框的位置
                    gt_box = torch.FloatTensor(np.array([0, 0, gw, gh])).unsqueeze(0)
                    
                    # 计算出所有先验框的位置
                    anchor_shapes = torch.FloatTensor(np.concatenate((np.zeros((self.num_anchors, 2)),
                                                                      np.array(anchors)), 1))
                    # 计算重合程度
                    anch_ious = bbox_iou(gt_box, anchor_shapes)
                   
                    # Find the best matching anchor box
                    best_n = np.argmax(anch_ious)
                    if best_n not in anchor_index:
                        continue
                    # Masks
                    if (gj < in_h) and (gi < in_w):
                        best_n = best_n - subtract_index
                        # 判定哪些先验框内部真实的存在物体
                        noobj_mask[b, best_n, gj, gi] = 0
                        mask[b, best_n, gj, gi] = 1
                        # 计算先验框中心调整参数
                        tx[b, best_n, gj, gi] = gx
                        ty[b, best_n, gj, gi] = gy
                        # 计算先验框宽高调整参数
                        tw[b, best_n, gj, gi] = gw
                        th[b, best_n, gj, gi] = gh
                        # 用于获得xywh的比例
                        box_loss_scale_x[b, best_n, gj, gi] = target[b][t, 2]
                        box_loss_scale_y[b, best_n, gj, gi] = target[b][t, 3]
                        # 物体置信度
                        tconf[b, best_n, gj, gi] = 1
                        # 种类
                        tcls[b, best_n, gj, gi, int(target[b][t, 4])] = 1
                    else:
                        print('Step {0} out of bound'.format(b))
                        print('gj: {0}, height: {1} | gi: {2}, width: {3}'.format(gj, in_h, gi, in_w))
                        continue
            t_box[...,0] = tx
            t_box[...,1] = ty
            t_box[...,2] = tw
            t_box[...,3] = th
            return mask, noobj_mask, t_box, tconf, tcls, box_loss_scale_x, box_loss_scale_y
    
        def get_ignore(self,prediction,target,scaled_anchors,in_w, in_h,noobj_mask):
            bs = len(target)
            anchor_index = [[0,1,2],[3,4,5],[6,7,8]][[13,26,52].index(in_w)]
            scaled_anchors = np.array(scaled_anchors)[anchor_index]
            # 先验框的中心位置的调整参数
            x = torch.sigmoid(prediction[..., 0])  
            y = torch.sigmoid(prediction[..., 1])
            # 先验框的宽高调整参数
            w = prediction[..., 2]  # Width
            h = prediction[..., 3]  # Height
    
            FloatTensor = torch.cuda.FloatTensor if x.is_cuda else torch.FloatTensor
            LongTensor = torch.cuda.LongTensor if x.is_cuda else torch.LongTensor
    
            # 生成网格,先验框中心,网格左上角
            grid_x = torch.linspace(0, in_w - 1, in_w).repeat(in_w, 1).repeat(
                int(bs*self.num_anchors/3), 1, 1).view(x.shape).type(FloatTensor)
            grid_y = torch.linspace(0, in_h - 1, in_h).repeat(in_h, 1).t().repeat(
                int(bs*self.num_anchors/3), 1, 1).view(y.shape).type(FloatTensor)
    
            # 生成先验框的宽高
            anchor_w = FloatTensor(scaled_anchors).index_select(1, LongTensor([0]))
            anchor_h = FloatTensor(scaled_anchors).index_select(1, LongTensor([1]))
            
            anchor_w = anchor_w.repeat(bs, 1).repeat(1, 1, in_h * in_w).view(w.shape)
            anchor_h = anchor_h.repeat(bs, 1).repeat(1, 1, in_h * in_w).view(h.shape)
            
            # 计算调整后的先验框中心与宽高
            pred_boxes = FloatTensor(prediction[..., :4].shape)
            pred_boxes[..., 0] = x + grid_x
            pred_boxes[..., 1] = y + grid_y
            pred_boxes[..., 2] = torch.exp(w) * anchor_w
            pred_boxes[..., 3] = torch.exp(h) * anchor_h
            for i in range(bs):
                pred_boxes_for_ignore = pred_boxes[i]
                pred_boxes_for_ignore = pred_boxes_for_ignore.view(-1, 4)
    
                for t in range(target[i].shape[0]):
                    gx = target[i][t, 0] * in_w
                    gy = target[i][t, 1] * in_h
                    gw = target[i][t, 2] * in_w
                    gh = target[i][t, 3] * in_h
                    gt_box = torch.FloatTensor(np.array([gx, gy, gw, gh])).unsqueeze(0).type(FloatTensor)
    
                    anch_ious = bbox_iou(gt_box, pred_boxes_for_ignore, x1y1x2y2=False)
                    anch_ious = anch_ious.view(pred_boxes[i].size()[:3])
                    noobj_mask[i][anch_ious>self.ignore_threshold] = 0
            return noobj_mask, pred_boxes
    

    训练自己的YOLOV4模型

    yolo4整体的文件夹构架如下:
    在这里插入图片描述
    本文使用VOC格式进行训练。
    训练前将标签文件放在VOCdevkit文件夹下的VOC2007文件夹下的Annotation中。
    在这里插入图片描述
    训练前将图片文件放在VOCdevkit文件夹下的VOC2007文件夹下的JPEGImages中。
    在这里插入图片描述
    在训练前利用voc2yolo3.py文件生成对应的txt。
    在这里插入图片描述
    再运行根目录下的voc_annotation.py,运行前需要将classes改成你自己的classes。

    classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]
    

    在这里插入图片描述
    就会生成对应的2007_train.txt,每一行对应其图片位置及其真实框的位置。
    在这里插入图片描述
    在训练前需要修改model_data里面的voc_classes.txt文件,需要将classes改成你自己的classes。
    在这里插入图片描述
    运行train.py即可开始训练。
    在这里插入图片描述

    展开全文
  • 因为工作原因,项目中经常遇到目标检测的任务,因此对目标检测算法会经常使用和关注,比如Yolov3、Yolov4算法。 当然,实际项目中很多的第一步,也都是先进行目标检测任务,比如人脸识别、多目标追踪、REID、客流...

    因为工作原因,项目中经常遇到目标检测的任务,因此对目标检测算法会经常使用和关注,比如Yolov3Yolov4算法Yolov5算法

    当然,实际项目中很多的第一步,也都是先进行目标检测任务,比如人脸识别多目标追踪REID客流统计等项目。因此目标检测是计算机视觉项目中非常重要的一部分。

    从2018年Yolov3年提出的两年后,在原作者声名放弃更新Yolo算法后,俄罗斯的Alexey大神扛起了Yolov4的大旗,然后不久又出现了Yolov5。

    因为项目中Yolov3、Yolov4、Yolov5使用很多,所以大白将项目中,需要了解的Yolov3、Yolov4、Yolov5系列相关知识点以及相关代码进行完整的汇总,希望和大家共同学习探讨。

    同时,大白每周会整理几十个人工智能公众号的精华文章,并系统的分类,让大家对于人工智能行业每周的内容动态可以一目了然,点击查看

    版权申明:本文包含图片,都为大白使用PPT所绘制的,如需网络结构高清图模型权重,可点击查看下载

    更新提醒(2021.05.20):Yolov3&Yolov4的相关视频,已经更新上传,可点击查看

    求职跳槽福利:为了便于大家求职、跳槽的准备,大白将45家大厂3500篇面经,按照知识框架,整理成700多页的《人工智能算法岗江湖武林秘籍》,限时开放下载,点击查看下载


    1 论文汇总

    Yolov3论文名:《Yolov3: An Incremental Improvement
    Yolov3论文地址:https://arxiv.org/pdf/1804.02767.pdf

    Yolov4论文名:《Yolov4: Optimal Speed and Accuracy of Object Detection
    Yolov4论文地址:https://arxiv.org/pdf/2004.10934.pdf


    2 Yolov3核心基础内容

    2.1 网络结构可视化

    Yolov3是目标检测Yolo系列非常非常经典的算法。
    不过很多同学拿到Yolov3或者Yolov4cfg文件时,并不知道如何直观的可视化查看网络结构。如果纯粹看cfg里面的内容,肯定会一脸懵逼
    其实可以很方便的用netron查看Yolov3的网络结构图,一目了然。
    这里不多说,如果需要安装,可以移步大白的另一篇文章:《网络可视化工具netron详细安装流程》
    如果不想安装,也可以直接点击此链接,查看Yolov3可视化流程图

    2.2 网络结构图

    在这里插入图片描述
    绘制网络结构图受到Yolov3另一位作者文章的启发,包括下面Yolov4的结构图,从总体框架上先了解了Yolov3的流程,再针对去学习每一小块的知识点,会事半功倍。
    上图三个蓝色方框内表示Yolov3三个基本组件:
    (1)CBL:Yolov3网络结构中的最小组件,由Conv+Bn+Leaky_relu激活函数三者组成。
    (2)Res unit:借鉴Resnet网络中的残差结构,让网络可以构建的更深。
    (3)ResX:由一个CBL和X个残差组件构成,是Yolov3中的大组件。每个Res模块前面的CBL都起到下采样的作用,因此经过5次Res模块后,得到的特征图是608->304->152->76->38->19大小。

    其他基础操作:
    (1)Concat:张量拼接,会扩充两个张量的维度,例如26×26×25626×26×512两个张量拼接,结果是26×26×768。Concat和cfg文件中的route功能一样。
    (2)Add:张量相加,张量直接相加,不会扩充维度,例如104×104×128104×104×128相加,结果还是104×104×128。add和cfg文件中的shortcut功能一样。

    Backbone中卷积层的数量:
    每个ResX中包含1+2×X个卷积层,因此整个主干网络Backbone中一共包含1+(1+2×1)+(1+2×2)+(1+2×8)+(1+2×8)+(1+2×4)=52,再加上一个FC全连接层,即可以组成一个Darknet53分类网络。不过在目标检测Yolov3中,去掉FC层,不过为了方便称呼,仍然把Yolov3的主干网络叫做Darknet53结构

    2.3 核心基础内容

    Yolov32018年发明提出的,这成为了目标检测one-stage中非常经典的算法,包含Darknet-53网络结构anchor锚框FPN等非常优秀的结构。
    本文主要目的在于描述Yolov4Yolov3算法的不同及创新之处,对Yolov3的基础不过多描述。

    这里大白也准备了Yolov3算法非常浅显易懂的基础视频课程,让小白也能简单清楚的了解Yolov3的整个过程及各个算法细节。

    Yolov3及Yolov4深入浅出系列视频点击查看。

    在准备课程过程中,大白搜集查看了网络上几乎所有的Yolov3资料,在此整理几个非常不错的文章及视频,大家也可以点击查看,学习相关知识。
    (1)视频:吴恩达目标检测Yolo入门讲解
    https://www.bilibili.com/video/BV1N4411J7Y6?from=search&seid=18074481568368507115
    (2)文章:Yolo系列之Yolov3【深度解析】
    https://blog.csdn.net/leviopku/article/details/82660381
    (3)文章:一文看懂Yolov3
    https://blog.csdn.net/litt1e/article/details/88907542
    相信大家看完,对于Yolov3的基础知识点会有一定的了解。


    3 Yolov3相关代码

    3.1 python代码

    代码地址:https://github.com/ultralytics/Yolov3

    3.2 C++代码

    这里推荐Yolov4作者darknetAB代码,代码和原始作者代码相比,进行了很多的优化,如需要运行Yolov3网络,加载cfg时,使用Yolov3.cfg即可
    代码地址:https://github.com/AlexeyAB/darknet

    3.3 python版本的Tensorrt代码

    除了算法研究外,实际项目中还需要将算法落地部署到工程上使用,比如GPU服务器使用时还需要对模型进行tensorrt加速
    (1)Tensort中的加速案例
    强烈推荐tensort软件中,自带的Yolov3加速案例,路径位于tensorrt解压文件夹的TensortX/samples/python/Yolov3_onnx
    针对案例中的代码,如果有不明白的,也可参照下方文章上的详细说明:
    代码讲解文章:https://www.cnblogs.com/shouhuxianjian/p/10550262.html
    (2)Github上的tensorrt加速
    除了Tensorrt软件中的代码, Github上也有其他作者的开源代码
    代码地址:https://github.com/lewes6369/TensorRT-Yolov3

    3.4 C++版本的Tensorrt代码

    项目的工程部署上,如果使用C++版本进行Tensorrt加速,一方面可以参照Alexey的Github代码,另一方面也可以参照下面其他作者的开源代码
    代码地址:https://github.com/wang-xinyu/tensorrtx/tree/master/Yolov3


    4 YoloV4核心基础内容

    4.1 网络结构可视化

    Yolov4的网络结构也可以使用netron工具查看,大白也是对照其展示的可视化流程图绘制的下方网络结构图。
    netron可视化显示Yolov4网络结构可以参照大白的另一篇文章:《netron可视化网络结构详细安装流程
    如果不想安装,也可以直接点击此链接,查看Yolov4可视化流程图。

    4.2 网络结构图

    在这里插入图片描述
    Yolov4的结构图和Yolov3相比,因为多了CSP结构PAN结构,如果单纯看可视化流程图,会觉得很绕,但是在绘制出上面的图形后,会觉得豁然开朗,其实整体架构和Yolov3是相同的,不过使用各种新的算法思想对各个子结构都进行了改进。
    先整理下Yolov4的五个基本组件:
    1. CBM:Yolov4网络结构中的最小组件,由Conv+Bn+Mish激活函数三者组成。
    2. CBL:Conv+Bn+Leaky_relu激活函数三者组成。
    3. Res unit:借鉴Resnet网络中的残差结构,让网络可以构建的更深。
    4. CSPX:借鉴CSPNet网络结构,由卷积层和X个Res unint模块Concat组成。
    5. SPP:采用1×1,5×5,9×9,13×13的最大池化的方式,进行多尺度融合。

    其他基础操作:
    1. Concat:张量拼接,维度会扩充,和Yolov3中的解释一样,对应于cfg文件中的route操作。
    2. Add:张量相加,不会扩充维度,对应于cfg文件中的shortcut操作。

    Backbone中卷积层的数量:
    和Yolov3一样,再来数一下Backbone里面的卷积层数量。
    每个CSPX中包含5+2×X个卷积层,因此整个主干网络Backbone中一共包含1+(5+2×1)+(5+2×2)+(5+2×8)+(5+2×8)+(5+2×4)=72。
    这里大白有些疑惑,按照Yolov3设计的传统,72个卷积层,加上最后的一个全连接层,主干网络的名字不应该叫CSPDarknet73吗????

    4.3 核心基础内容

    Yolov4本质上和Yolov3相差不大,可能有些人会觉得失望。
    但我觉得算法创新分为三种方式
    (1)第一种:面目一新的创新,比如Yolov1Faster-RCNNCenternet等,开创出新的算法领域,不过这种也是最难的。
    (2)第二种:守正出奇的创新,比如将图像金字塔改进为特征金字塔。
    (3)第三种:各种先进算法集成的创新,比如不同领域发表的最新论文的tricks,集成到自己的算法中,却发现有出乎意料的改进。
    Yolov4既有第二种也有第三种创新,组合尝试了大量深度学习领域最新论文的20多项研究成果,而且不得不佩服的是作者AlexeyGithub代码库维护的频繁程度。
    目前Yolov4代码的Star数量已经1万左右,据我所了解,目前超过这个数量的,目标检测领域只有FacebookDetectron(v1-v2)、和Yolo(v1-v3)官方代码库(已停止更新)。
    所以
    Yolov4
    中的各种创新方式,大白觉得还是很值得仔细研究的。

    为了便于分析,将Yolov4的整体结构拆分成四大板块:
    在这里插入图片描述
    大白主要从以上4个部分YoloV4的创新之处进行讲解,让大家一目了然。

    (1)输入端:这里指的创新主要是训练时对输入端的改进,主要包括Mosaic数据增强、cmBN、SAT自对抗训练。
    (2)BackBone主干网络:将各种新的方式结合起来,包括:CSPDarknet53Mish激活函数Dropblock
    (3)Neck:目标检测网络在BackBone和最后的输出层之间往往会插入一些层,比如Yolov4中的SPP模块FPN+PAN结构
    (4)Prediction:输出层的锚框机制和Yolov3相同,主要改进的是训练时的损失函数CIOU_Loss,以及预测框筛选的nms变为DIOU_nms
    总体来说,Yolov4Yolov3的各个部分都进行了改进优化,下面丢上作者的算法对比图。
    在这里插入图片描述
    仅对比Yolov3Yolov4,在COCO数据集上,同样的FPS等于83左右时,Yolov4的AP是43,而Yolov3是33,直接上涨了10个百分点
    不得不服,当然可能针对具体不同的数据集效果也不一样,但总体来说,改进效果是很优秀的,下面大白对Yolov4的各个创新点继续进行深挖。

    4.3.1 输入端创新

    考虑到很多同学GPU显卡数量并不是很多,Yolov4对训练时的输入端进行改进,使得训练在单张GPU上也能有不错的成绩。比如数据增强MosaiccmBNSAT自对抗训练。
    但感觉cmBN和SAT影响并不是很大,所以这里主要讲解Mosaic数据增强

    (1)Mosaic数据增强
    Yolov4中使用的Mosaic是参考2019年底提出的CutMix数据增强的方式,但CutMix只使用了两张图片进行拼接,而Mosaic数据增强则采用了4张图片,随机缩放随机裁剪随机排布的方式进行拼接。
    在这里插入图片描述
    这里首先要了解为什么要进行Mosaic数据增强呢?
    在平时项目训练时,小目标的AP一般比中目标和大目标低很多。而Coco数据集中也包含大量的小目标,但比较麻烦的是小目标的分布并不均匀
    首先看下小、中、大目标的定义:
    2019年发布的论文《Augmentation for small object detection》对此进行了区分

    在这里插入图片描述
    可以看到小目标的定义是目标框的长宽0×0~32×32之间的物体。

    在这里插入图片描述
    但在整体的数据集中,小、中、大目标的占比并不均衡。
    如上表所示,Coco数据集中小目标占比达到41.4%,数量比中目标和大目标都要多。
    但在所有的训练集图片中,只有52.3%的图片有小目标,而中目标和大目标的分布相对来说更加均匀一些。
    针对这种状况,Yolov4的作者采用了
    Mosaic数据增强
    的方式。
    主要有几个优点:
    a. 丰富数据集:随机使用4张图片,随机缩放,再随机分布进行拼接,大大丰富了检测数据集,特别是随机缩放增加了很多小目标,让网络的鲁棒性更好。
    b. 减少GPU:可能会有人说,随机缩放,普通的数据增强也可以做,但作者考虑到很多人可能只有一个GPU。
    因此Mosaic增强训练时,可以直接计算4张图片的数据,使得Mini-batch大小并不需要很大,一个GPU就可以达到比较好的效果。
    此外,发现另一研究者的训练方式也值得借鉴,采用的数据增强和Mosaic比较类似,也是使用4张图片(不是随机分布),但训练计算loss时,采用“缺啥补啥”的思路:
    如果上一个iteration中,小物体产生的loss不足(比如小于某一个阈值),则下一个iteration就用拼接图;否则就用正常图片训练,也很有意思。
    参考链接:https://www.zhihu.com/question/390191723?rf=390194081

    4.3.2 BackBone创新

    (1)CSPDarknet53
    CSPDarknet53是在Yolov3主干网络Darknet53的基础上,借鉴2019年CSPNet的经验,产生的Backbone结构,其中包含了5个CSP模块

    在这里插入图片描述
    这里因为CSP模块比较长,不放到本处,大家也可以点击Yolov4的netron网络结构图,对比查看,一目了然。
    每个CSP模块前面的卷积核的大小都是3×3步长为2,因此可以起到下采样的作用。
    因为Backbone有5个CSP模块,输入图像是608*608,所以特征图变化的规律是:608->304->152->76->38->19
    经过5次CSP模块后得到19*19大小的特征图。
    而且作者只在Backbone中采用了Mish激活函数,网络后面仍然采用Leaky_relu激活函数
    我们再看看下作者为啥要参考2019年的CSPNet,采用CSP模块?
    CSPNet论文地址:https://arxiv.org/pdf/1911.11929.pdf
    CSPNet全称是Cross Stage Paritial Network,主要从网络结构设计的角度解决推理中从计算量很大的问题。
    CSPNet的作者认为推理计算过高的问题是由于网络优化中的梯度信息重复导致的。
    因此采用CSP模块先将基础层的特征映射划分为两部分,然后通过跨阶段层次结构将它们合并,在减少了计算量的同时可以保证准确率。
    因此Yolov4在主干网络Backbone采用CSPDarknet53网络结构,主要有三个方面的优点:
    优点一:增强CNN的学习能力,使得在轻量化的同时保持准确性。
    优点二:降低计算瓶颈
    优点三:降低内存成本

    (2)Mish激活函数
    Mish激活函数是2019年下半年提出的激活函数
    论文地址:https://arxiv.org/abs/1908.08681
    leaky_relu激活函数的图形对比如下:
    在这里插入图片描述
    Yolov4的Backbone中都使用了Mish激活函数,而后面的网络则还是使用Leaky_relu函数。
    在这里插入图片描述
    Yolov4作者实验测试时,使用CSPDarknet53网络ImageNet数据集上做图像分类任务,发现使用了Mish激活函数TOP-1TOP-5的精度比没有使用时都略高一些。
    因此在设计Yolov4目标检测任务时,主干网络Backbone还是使用Mish激活函数

    (3)Dropblock
    Yolov4中使用的Dropblock,其实和常见网络中的Dropout功能类似,也是缓解过拟合的一种正则化方式。
    Dropblock2018年提出,论文地址:https://arxiv.org/pdf/1810.12890.pdf

    传统的Dropout很简单,一句话就可以说的清:随机删除减少神经元的数量,使网络变得更简单。
    在这里插入图片描述
    DropblockDropout相似,比如下图:

    在这里插入图片描述中间Dropout的方式会随机的删减丢弃一些信息,但Dropblock的研究者认为,卷积层对于这种随机丢弃并不敏感.
    因为卷积层通常是三层连用:卷积+激活+池化层,池化层本身就是对相邻单元起作用。
    而且即使随机丢弃,卷积层仍然可以从相邻的激活单元学习到相同的信息。因此,在全连接层上效果很好的Dropout在卷积层上效果并不好
    所以右图Dropblock的研究者则干脆整个局部区域进行删减丢弃。
    这种方式其实是借鉴2017年Cutout数据增强的方式,cutout是将输入图像的部分区域清零,而Dropblock则是将Cutout应用到每一个特征图。而且并不是用固定的归零比率,而是在训练时以一个小的比率开始,随着训练过程线性的增加这个比率
    在这里插入图片描述
    Dropblock的研究者与Cutout数据增强对训练效果进行对比验证时,发现有几个优点:
    优点一:Dropblock的效果优于Cutout
    优点二:cutout只能作用于输入层,而Dropblock则是将Cutout应用到网络中的每一个特征图上
    优点三:Dropblock可以定制各种组合,在训练的不同阶段可以修改删减的概率,从空间层面和时间层面,和cutout相比都有更精细的改进。
    Yolov4中直接采用了更优的Dropblock,对网络的正则化过程进行了全面的升级改进。

    4.3.3 Neck创新

    在目标检测领域,为了更好的提取融合特征,通常在Backbone输出层,会插入一些层,这个部分称为Neck。相当于目标检测网络的颈部,也是非常关键的。
    Yolov4Neck结构主要采用了SPP模块FPN+PAN的方式。

    (1)SPP模块
    SPP模块,其实在Yolov3中已经存在了,在Yolov4C++代码文件夹中有一个Yolov3_spp版本,但有的同学估计从来没有使用过,在Yolov4中,SPP模块仍然是在Backbone主干网络之后:

    在这里插入图片描述
    作者在SPP模块中,使用k={1×1,5×5,9×9,13×13}的最大池化的方式,再将不同尺度的特征图进行Concat操作
    注意:这里最大池化采用padding操作,移动的步长为1,比如13×13的输入特征图,使用5×5大小的池化核池化,padding=2,因此池化后的特征图仍然是13×13大小。

    在这里插入图片描述
    在2019年的《DC-SPP-Yolo》文章:https://arxiv.org/ftp/arxiv/papers/1903/1903.08589.pdf
    也对Yolo目标检测的SPP模块进行了对比测试。
    和Yolov4作者的研究相同,采用SPP模块的方式,比单纯的使用k×k最大池化的方式,更有效的增加主干特征的接收范围,显著的分离了最重要的上下文特征
    Yolov4的作者在使用608×608大小的图像进行测试时发现,在COCO目标检测任务中,以0.5%的额外计算代价将AP50增加了2.7%,因此Yolov4中也采用了SPP模块

    (2)FPN+PAN
    PAN结构比较有意思,看了网上Yolov4关于这个部分的讲解,大多都是讲的比较笼统的,而PAN是借鉴**2018年图像分割领域PANet的创新点,有些同学可能不是很清楚。
    下面大白将这个部分拆解开来,看下
    Yolov3Yolov4**中是如何设计的。

    Yolov3各个网络结构
    在这里插入图片描述
    可以看到经过几次下采样,三个紫色箭头指向的地方,输出分别是76×7638×3819×19
    以及最后的Prediction中用于预测的三个特征图①19×19×255②38×38×255③76×76×255 [注:255表示80类别 (1+4+80)×3=255]
    我们将Neck部分用立体图画出来,更直观的看下两部分之间是如何通过FPN结构融合的。

    在这里插入图片描述
    如图所示,FPN是自顶向下的,将高层的特征信息通过上采样的方式进行传递融合,得到进行预测的特征图。

    Yolov4各个网络结构
    而Yolov4中Neck这部分除了使用FPN外,还在此基础上使用了PAN结构

    在这里插入图片描述
    前面CSPDarknet53中讲到,每个CSP模块前面的卷积核都是3×3大小,相当于下采样操作。
    因此可以看到三个紫色箭头处的特征图是76×7638×3819×19
    以及最后Prediction中用于预测的三个特征图:①76×76×255②38×38×255③19×19×255
    我们也看下Neck部分的立体图像,看下两部分是如何通过FPN+PAN结构进行融合的。
    在这里插入图片描述
    和Yolov3的FPN层不同,Yolov4在FPN层的后面还添加了一个自底向上的特征金字塔
    其中包含两个PAN结构
    这样结合操作,FPN层自顶向下传达强语义特征,而特征金字塔则自底向上传达强定位特征,两两联手,从不同的主干层对不同的检测层进行特征聚合,这样的操作确实很皮。
    FPN+PAN借鉴的是18年CVPR的PANet,当时主要应用于图像分割领域,但Alexey将其拆分应用到Yolov4中,进一步提高特征提取的能力。

    不过这里需要注意几点:
    注意一:
    Yolov3的FPN层输出的三个大小不一的特征图①②③直接进行预测
    但Yolov4的FPN层,只使用最后的一个76×76特征图①,而经过两次PAN结构,输出预测的特征图②和③。
    这里的不同也体现在cfg文件中,这一点有很多同学之前不太明白。
    比如Yolov3.cfg中输入时608×608,最后的三个Yolo层中,
    第一个Yolo层是最小的特征图19×19,mask=6,7,8,对应最大的anchor box
    第二个Yolo层是中等的特征图38×38,mask=3,4,5,对应中等的anchor box
    第三个Yolo层是最大的特征图76×76,mask=0,1,2,对应最小的anchor box
    而Yolov4.cfg则恰恰相反
    第一个Yolo层是最大的特征图76×76mask=0,1,2,对应最小的anchor box
    第二个Yolo层是中等的特征图38×38mask=3,4,5,对应中等的anchor box
    第三个Yolo层是最小的特征图19×19mask=6,7,8,对应最大的anchor box
    注意二:
    原本的PANet网络PAN结构中,两个特征图结合是采用shortcut操作,而Yolov4中则采用concatroute)操作,特征图融合后的尺寸发生了变化。
    在这里插入图片描述
    这里也可以对应Yolov4的netron网络图查看,很有意思。

    4.3.4 Prediction创新

    (1)CIOU_loss
    目标检测任务的损失函数一般由Classificition Loss(分类损失函数)和Bounding Box Regeression Loss(回归损失函数)两部分构成。
    Bounding Box Regeression的Loss近些年的发展过程是:Smooth L1 Loss-> IoU Loss(2016)-> GIoU Loss(2019)-> DIoU Loss(2020)->CIoU Loss(2020)
    我们从最常用的IOU_Loss开始,进行对比拆解分析,看下Yolov4为啥要选择CIOU_Loss

    a. IOU_loss

    在这里插入图片描述
    可以看到IOU的loss其实很简单,主要是交集/并集,但其实也存在两个问题。
    在这里插入图片描述
    问题1:即状态1的情况,当预测框和目标框不相交时,IOU=0,无法反应两个框距离的远近,此时损失函数不可导,IOU_Loss无法优化两个框不相交的情况。
    问题2:即状态2和状态3的情况,当两个预测框大小相同,两个IOU也相同,IOU_Loss无法区分两者相交情况的不同。
    因此2019年出现了GIOU_Loss来进行改进。

    b. GIOU_loss
    在这里插入图片描述
    可以看到右图GIOU_Loss中,增加了相交尺度的衡量方式,缓解了单纯IOU_Loss时的尴尬。
    但为什么仅仅说缓解呢?因为还存在一种不足
    在这里插入图片描述
    问题:状态1、2、3都是预测框在目标框内部且预测框大小一致的情况,这时预测框和目标框的差集都是相同的,因此这三种状态的GIOU值也都是相同的,这时GIOU退化成了IOU,无法区分相对位置关系。
    基于这个问题,2020年的AAAI又提出了DIOU_Loss

    c. DIOU_loss

    好的目标框回归函数应该考虑三个重要几何因素:重叠面积中心点距离长宽比
    针对IOU和GIOU存在的问题,作者从两个方面进行考虑
    一:如何最小化预测框和目标框之间的归一化距离?
    二:如何在预测框和目标框重叠时,回归的更准确?
    针对第一个问题,提出了DIOU_LossDistance_IOU_Loss
    在这里插入图片描述
    DIOU_Loss考虑了重叠面积中心点距离,当目标框包裹预测框的时候,直接度量2个框的距离,因此DIOU_Loss收敛的更快。
    但就像前面好的目标框回归函数所说的,这时并没有考虑到长宽比
    在这里插入图片描述
    问题:比如上面三种状态,目标框包裹预测框,本来DIOU_Loss可以起作用。
    但预测框的中心点的位置都是一样的,因此按照DIOU_Loss的计算公式,三者的值都是相同的。
    针对这个问题,又提出了CIOU_Loss,不对不说,科学总是在解决问题中,不断进步!!

    d. CIOU_loss

    CIOU_LossDIOU_Loss前面的公式都是一样的,不过在此基础上还增加了一个影响因子,将预测框和目标框的长宽比都考虑了进去。
    在这里插入图片描述
    其中v是衡量长宽比一致性的参数,我们也可以定义为:
    在这里插入图片描述
    这样CIOU_Loss就将目标框回归函数应该考虑三个重要几何因素:重叠面积、中心点距离,长宽比全都考虑进去了。

    再来综合的看下各个Loss函数不同点
    IOU_Loss:主要考虑检测框和目标框重叠面积
    GIOU_Loss:在IOU的基础上,解决边界框不重合时的问题。
    DIOU_Loss:在IOU和GIOU的基础上,考虑边界框中心点距离的信息。
    CIOU_Loss:在DIOU的基础上,考虑边界框宽高比的尺度信息。
    Yolov4中采用了CIOU_Loss的回归方式,使得预测框回归的速度和精度更高一些。

    (2)DIOU_nms

    nms主要用于预测框的筛选,常用的目标检测算法中,一般采用普通的nms的方式,Yolov4则借鉴上面D/CIOU loss的论文:https://arxiv.org/pdf/1911.08287.pdf
    将其中计算IOU的部分替换成DIOU的方式:
    再来看下实际的案例:
    在这里插入图片描述
    在上图重叠的摩托车检测中,中间的摩托车因为考虑边界框中心点的位置信息,也可以回归出来。
    因此在重叠目标的检测中,DIOU_nms的效果优于传统的nms

    注意:
    有读者会有疑问,这里为什么不用CIOU_nms,而用DIOU_nms?
    :因为前面讲到的CIOU_loss,实在DIOU_loss的基础上,添加的影响因子,包含groundtruth标注框的信息,在训练时用于回归。
    但在测试过程中,并没有groundtruth的信息,不用考虑影响因子,因此直接用DIOU_nms即可。

    总体来说,YOLOv4的论文称的上良心之作,将近几年关于深度学习领域最新研究的tricks移植到Yolov4中做验证测试,将Yolov3的精度提高了不少。
    虽然没有全新的创新,但很多改进之处都值得借鉴,借用Yolov4作者的总结。

    Yolov4 主要带来了 3 点新贡献:
    (1)提出了一种高效而强大的目标检测模型,使用 1080Ti 或 2080Ti 就能训练出超快、准确的目标检测器。
    (2)在检测器训练过程中,验证了最先进的一些研究成果对目标检测器的影响。
    (3)改进了 SOTA 方法,使其更有效、更适合单 GPU 训练。


    5 YoloV4相关代码

    5.1 python代码

    代码地址:https://github.com/Tianxiaomo/pytorch-Yolov4
    作者的训练和测试推理代码都已经完成

    5.2 C++代码

    Yolov4作者Alexey的代码,俄罗斯的大神,应该是个独立研究员,更新算法的频繁程度令人佩服。
    Yolov3作者Joseph Redmon宣布停止更新Yolo算法之后,Alexey凭借对于Yolov3算法的不断探索研究,赢得了Yolov3作者的认可,发布了Yolov4
    代码地址:https://github.com/AlexeyAB/darknet

    5.3 python版本的Tensorrt代码

    目前测试有效的有tensorflow版本:weights->pb->trt
    代码地址:https://github.com/hunglc007/tensorflow-Yolov4-tflite

    5.4 C++版本的Tensorrtrt代码

    代码地址:https://github.com/wang-xinyu/tensorrtx/tree/master/Yolov4
    作者自定义了Mish激活函数Plugin层,Tensorrt加速后速度还是挺快的。

    6 Yolov5核心基础知识完整讲解

    因为Yolov5的核心基础知识点也很多,可以查看大白的另一篇文章《深入浅出Yolov5核心基础知识完整讲解》


    7 相关数据集下载

    实际项目中,目标检测算法应该的非常多非常多,比如人脸识别,比如疫情期间的口罩人脸识别,比如车流统计人流统计等等。
    因此大白也会将不错的值得一试的数据集汇总到此处,方便需要的同学进行下载。

    (1)口罩遮挡人脸数据集

    数据集详情:由武汉大学多媒体研究中心发起,目前是全球最大的口罩遮挡人脸数据集。
    分为真实口罩人脸和模拟口罩人脸两部分,真实口罩人脸包含525人的5000张口罩人脸9万张正常人脸。模拟口罩人脸包含1万个人共50万张模拟人脸数据集。
    应用项目:人脸检测、人脸识别
    数据集地址https://github.com/X-zhangyang/Real-World-Masked-Face-Dataset

    (2)Wider Face人脸数据集
    数据集详情:香港中文大学发起的,包含3万张图片共40万张人脸。
    应用项目:人脸检测
    数据集地址http://shuoyang1213.me/WIDERFACE/WiderFace_Results.html

    (3)Wider Person拥挤场景行人数据集
    数据集详情:多种场景比较拥挤场景的行人检测数据集,包含13382张图片,共计40万个不同遮挡程度的人体。
    应用项目:人体检测
    数据集地址http://www.cbsr.ia.ac.cn/users/sfzhang/WiderPerson/

    因为工作原因,会搜集大量的各类公开应用场景数据集,如果有同学需要其他场景或者其他项目的,也可以留言,或者发送邮件到jiangdabai@126.com,也会将对应的数据集更新到此处。


    8 不断更新ing

    在深度学习的图像领域,肯定会涉及目标检测,而在目标检测中,Yolov3和Yolov4是非常经典,必须要学习的算法。
    有些同学,特别新接触的同学,刚学习时会觉得Yolo算法很繁琐
    而且我发现,网上很多的教程其实讲的还是比较笼统,并不适合小白学习
    所以大白也在耗尽洪荒之力,在准备Yolov3和Yolov4及相关的基础入门视频,让大家看完就能明白整体的流程各种算法细节,大家可以先收藏,后期制作好后会更新到此处。
    希望和大家一起努力,在人工智能深度学习领域一起进步,一起提升,一起变强!

    在这里插入图片描述

    展开全文
  • 深入浅出Yolo系列之Yolov5核心基础知识完整讲解

    万次阅读 多人点赞 2020-08-10 15:21:33
    Yolov4的相关基础知识做了比较系统的梳理,但Yolov4后不久,又出现了Yolov5,虽然作者没有放上和Yolov4的直接测试对比,但在COCO数据集的测试效果还是很可观的。 很多人考虑到Yolov5的创新性不足,对算法是否能够...

    大白在之前写过《深入浅出Yolo系列之Yolov3&Yolov4核心基础知识完整讲解》点击即可查看

    Yolov4的相关基础知识做了比较系统的梳理,但Yolov4后不久,又出现了Yolov5,虽然作者没有放上和Yolov4的直接测试对比,但在COCO数据集的测试效果还是很可观的。

    很多人考虑到Yolov5创新性不足,对算法是否能够进化,称得上Yolov5而议论纷纷。

    但既然称之为Yolov5,也有很多非常不错的地方值得我们学习。不过因为Yolov5的网络结构和Yolov3、Yolov4相比,不好可视化,导致很多同学看Yolov5看的云里雾里。

    因此本文,大白主要对Yolov5四种网络结构的各个细节做一个深入浅出的分析总结,和大家一些探讨学习。

    同时,大白每周会整理几十个人工智能公众号的精华文章,并系统的分类,让大家对于人工智能行业每周的内容动态可以一目了然,点击查看

    版权申明:本文包含图片,都为大白使用PPT所绘制的,如需网络结构高清图模型权重,可点击查看下载

    PS:原创不易,编辑上传文字2小时,为了便于大家查看,排版8小时,欢迎大家点赞收藏。

    更新提醒(2021.05.20):Yolov3&Yolov4的相关视频,已经更新上传,可点击查看

    求职跳槽福利:为了便于大家求职、跳槽的准备,大白将45家大厂3500篇面经,按照知识框架,整理成700多页的《人工智能算法岗江湖武林秘籍》,限时开放下载,点击查看下载


    1 Yolov5四种网络模型

    Yolov5官方代码中,给出的目标检测网络中一共有4个版本,分别是Yolov5s、Yolov5m、Yolov5l、Yolov5x四个模型。
    学习一个新的算法,最好在脑海中对算法网络的整体架构有一个清晰的理解。
    比较尴尬的是,Yolov5代码中给出的网络文件是yaml格式,和原本Yolov3、Yolov4中的cfg不同。
    因此无法用netron工具直接可视化的查看网络结构,造成有的同学不知道如何去学习这样的网络。
    比如下载了Yolov5的四个pt格式的权重模型:

    在这里插入图片描述
    大白在《深入浅出Yolo系列之Yolov3&Yolov4核心基础知识完整讲解》中讲到,可以使用netron工具打开查看网络模型。
    但因为netron对pt格式的文件兼容性并不好,直接使用netron工具打开,会发现,根本无法显示全部网络。
    因此可以采用pt->onnx->netron的折中方式,先使用Yolov5代码中models/export.py脚本将pt文件转换为onnx格式,再用netron工具打开,这样就可以看全网络的整体架构了。

    在这里插入图片描述
    如果有同学对netron工具还不是很熟悉,这里还是放上安装netron工具的详解,如果需要安装,可以移步大白的另一篇文章:《网络可视化工具netron详细安装流程》
    如需下载Yolov5整体的4个网络pt文件及onnx文件,也可点击链接查看下载,便于直观的学习。

    1.1 Yolov5网络结构图

    安装好netron工具,就可以可视化的打开Yolov5的网络结构,这里大白也和之前讲解Yolov3&Yolov4同样的方式,绘制了Yolov5s整体的网络结构图,配合netron的可视化网络结构查看,脑海中的架构会更加清晰。

    在这里插入图片描述

    本文也会以Yolov5s的网络结构为主线,讲解与其他三个模型(Yolov5m、Yolov5l、Yolov5x)的不同点,让大家对于Yolov5有一个深入浅出的了解

    1.2 网络结构可视化

    四种模型的pt文件转换成对应的onnx文件后,即可使用netron工具查看。
    但是,有些同学可能不方便,使用脚本转换查看。
    因此,大白也上传了每个网络结构图的图片,也可以直接点击查看。虽然没有netron工具更直观,但是也可以学习了解

    1.2.1 Yolov5s网络结构

    Yolov5s网络是Yolov5系列中深度最小,特征图的宽度最小的网络。
    后面的3种都是在此基础上不断加深,不断加宽。
    上图绘制出的网络结构图也是Yolov5s的结构,大家也可直接点击查看,Yolov5s的网络结构可视化的图片。

    1.2.2 Yolov5m网络结构

    此处也放上netron打开的Yolov5m网络结构可视图,点击即可查看,后面第二版块会详细说明不同模型的不同点。

    1.2.3 Yolov5l网络结构

    此处也放上netronx打开的Yolov5l网络结构可视图,点击即可查看

    1.2.4 Yolov5x网络结构

    此处也放上netronx打开的Yolov5x网络结构可视图,点击即可查看

    2 核心基础内容

    2.1 Yolov3&Yolov4网络结构图

    2.1.1 Yolov3网络结构图

    Yolov3网络结构是比较经典的one-stage结构,分为输入端BackboneNeckPrediction四个部分。
    大白在之前的《深入浅出Yolo系列之Yolov3&Yolov4核心基础完整讲解》中讲了很多,这里不多说,还是放上绘制的Yolov3网络结构图

    在这里插入图片描述

    2.1.2 Yolov4网络结构图

    Yolov4Yolov3的基础上进行了很多的创新。
    比如输入端采用mosaic数据增强,
    Backbone上采用了CSPDarknet53、Mish激活函数、Dropblock等方式,
    Neck中采用了SPP、FPN+PAN的结构,
    输出端则采用CIOU_Loss、DIOU_nms操作。
    因此Yolov4Yolov3的各个部分都进行了很多的整合创新,
    关于Yolov4如果有不清楚的可以参照大白之前写的《深入浅出Yolo系列之Yolov3&Yolov4核心基础知识完整讲解》,写的比较详细。

    在这里插入图片描述

    2.2 Yolov5核心基础内容

    Yolov5的结构和Yolov4很相似,但也有一些不同,大白还是按照从整体细节的方式,对每个板块进行讲解。
    在这里插入图片描述

    上图即Yolov5的网络结构图,可以看出,还是分为输入端、Backbone、Neck、Prediction四个部分。
    大家可能对Yolov3比较熟悉,因此大白列举它和Yolov3的一些主要的不同点,并和Yolov4进行比较。
    (1)输入端Mosaic数据增强、自适应锚框计算
    (2)BackboneFocus结构,CSP结构
    (3)NeckFPN+PAN结构
    (4)PredictionGIOU_Loss

    下面丢上Yolov5作者的算法性能测试图:
    在这里插入图片描述

    Yolov5作者也是在COCO数据集上进行的测试。
    大白在之前的文章讲过,COCO数据集的小目标占比
    因此最终的四种网络结构,性能上来说各有千秋。
    Yolov5s网络最小,速度最少,AP精度也最低。
    但如果检测的以大目标为主,追求速度,倒也是个不错的选择。
    其他的三种网络,在此基础上,不断加深加宽网络AP精度也不断提升,但速度的消耗也在不断增加

    2.2.1 输入端

    (1)Mosaic数据增强
    Yolov5的输入端采用了和Yolov4一样的Mosaic数据增强的方式。
    Mosaic数据增强提出的作者也是来自Yolov5团队的成员,不过,随机缩放随机裁剪随机排布的方式进行拼接,对于小目标的检测效果还是很不错的。
    在这里插入图片描述

    Mosaic数据增强的内容在之前《深入浅出Yolo系列之Yolov3&Yolov4核心基础知识完整讲解》文章中写的很详细,详情可以查看之前的内容。

    (2)自适应锚框计算
    Yolo算法中,针对不同的数据集,都会有初始设定长宽的锚框。
    在网络训练中,网络在初始锚框的基础上输出预测框,进而和真实框groundtruth进行比对,计算两者差距,再反向更新,迭代网络参数。
    因此初始锚框也是比较重要的一部分,比如Yolov5Coco数据集上初始设定的锚框:
    在这里插入图片描述

    Yolov3、Yolov4中,训练不同的数据集时,计算初始锚框的值是通过单独的程序运行的。
    Yolov5中将此功能嵌入到代码中,每次训练时,自适应的计算不同训练集中的最佳锚框值
    当然,如果觉得计算的锚框效果不是很好,也可以在代码中将自动计算锚框功能关闭
    在这里插入图片描述

    控制的代码即train.py中上面一行代码,设置成False,每次训练时,不会自动计算。

    (3)自适应图片缩放
    在常用的目标检测算法中,不同的图片长宽都不相同,因此常用的方式是将原始图片统一缩放到一个标准尺寸,再送入检测网络中。
    比如Yolo算法中常用416×416,608×608等尺寸,比如对下面800*600的图像进行变换。
    在这里插入图片描述

    Yolov5代码中对此进行了改进,也是Yolov5推理速度能够很快的一个不错的trick
    作者认为,在项目实际使用时,很多图片的长宽比不同。
    因此缩放填充后,两端的黑边大小都不同,而如果填充的比较多,则存在信息冗余,影响推理速度。
    因此在Yolov5代码datasets.py的letterbox函数中进行了修改,对原始图像自适应的添加最少的黑边

    在这里插入图片描述

    图像高度上两端的黑边变少了,在推理时,计算量也会减少,即目标检测速度会得到提升。
    这种方式在之前github上Yolov3中也进行了讨论:https://github.com/ultralytics/yolov3/issues/232
    在讨论中,通过这种简单的改进,推理速度得到了37%的提升,可以说效果很明显。
    但是有的同学可能会有大大的问号??如何进行计算的呢?
    大白按照Yolov5中的思路详细的讲解一下,在datasets.py的letterbox函数中也有详细的代码。

    第一步:计算缩放比例

    在这里插入图片描述

    原始缩放尺寸是416×416,都除以原始图像的尺寸后,可以得到0.52,和0.69两个缩放系数,选择小的缩放系数0.52

    第二步:计算缩放后的尺寸

    在这里插入图片描述
    原始图片的长宽都乘以最小的缩放系数0.52,宽变成了416,而高变成了312

    第三步:计算黑边填充数值

    在这里插入图片描述

    416-312=104,得到原本需要填充的高度。再采用numpy中np.mod取余数的方式,得到8个像素,再除以2,即得到图片高度两端需要填充的数值。
    此外,需要注意的是:
    a.这里大白填充的是黑色,即(0,0,0),而Yolov5中填充的是灰色,即(114,114,114),都是一样的效果。
    b.训练时没有采用缩减黑边的方式,还是采用传统填充的方式,即缩放到416×416大小。
    只是在测试,使用模型推理时,才采用缩减黑边的方式,提高目标检测,推理的速度。
    c.为什么np.mod函数的后面用32?因为Yolov5的网络经过5次下采样,而2的5次方,等于32。所以至少要去掉32的倍数,再进行取余。

    2.2.2 Backbone

    (1)Focus结构
    在这里插入图片描述

    Focus结构,Yolov3&Yolov4中并没有这个结构,其中比较关键是切片操作。
    比如右图的切片示意图,4×4×3的图像切片后变成2×2×12的特征图。
    Yolov5s的结构为例,原始608×608×3的图像输入Focus结构,采用切片操作,先变成304×304×12的特征图,再经过一次32个卷积核的卷积操作,最终变成304×304×32的特征图。
    需要注意的是:Yolov5s的Focus结构最后使用了32个卷积核,而其他三种结构,使用的数量有所增加,先注意下,后面会讲解到四种结构的不同点。

    (2)CSP结构
    Yolov4网络结构中,借鉴了CSPNet的设计思路,在主干网络中设计了CSP结构。

    在这里插入图片描述

    Yolov5Yolov4不同点在于,Yolov4中只有主干网络使用了CSP结构,Yolov5中设计了两种CSP结构,Yolov5s网络为例,以CSP1_X结构应用于Backbone主干网络,另一种CSP2_X结构则应用于Neck中

    在这里插入图片描述

    这里关于CSPNet的内容,也可以查看大白之前的《深入浅出Yolo系列之Yolov3&Yolov4核心基础知识完整讲解》

    2.2.3 Neck

    Yolov5现在的NeckYolov4中一样,都采用FPN+PAN的结构,但在Yolov5刚出来时,只使用了FPN结构,后面才增加了PAN结构,此外网络中其他部分也进行了调整。
    因此,大白在Yolov5刚提出时,画的很多结构图又都重新进行了调整
    在这里插入图片描述

    这里关于FPN+PAN的结构,大白在《深入浅出Yolo之Yolov3&Yolov4核心基础知识完整讲解》中,讲的很多,大家应该都有理解。
    但如上面CSPNet中讲到,Yolov5Yolov4的不同点在于,Yolov4Neck中,采用的都是普通的卷积操作。
    Yolov5Neck结构中,采用借鉴CSPNet设计的CSP2结构加强网络特征融合的能力

    在这里插入图片描述

    2.2.4 输出端

    (1)Bounding box损失函数
    《深入浅出Yolo系列之Yolov3&Yolov4核心基础完整讲解》中,大白详细的讲解了IOU_Loss,以及进化版的GIOU_LossDIOU_Loss,以及CIOU_Loss
    Yolov5中采用其中的GIOU_LossBounding box损失函数
    在这里插入图片描述

    Yolov4中采用CIOU_Loss作为目标Bounding box的损失函数。

    在这里插入图片描述

    (2)nms非极大值抑制
    在目标检测的后处理过程中,针对很多目标框的筛选,通常需要nms操作
    Yolov4DIOU_Loss的基础上采用DIOU_nms的方式,而Yolov5中仍然采用加权nms的方式。
    可以看出,采用DIOU_nms,下方中间箭头的黄色部分,原本被遮挡的摩托车也可以检出。

    在这里插入图片描述

    大白在项目中,也采用了DIOU_nms的方式,在同样的参数情况下,将nmsIOU修改成DIOU_nms。对于一些遮挡重叠的目标,确实会有一些改进
    比如下面黄色箭头部分,原本两个人重叠的部分,在参数和普通的IOU_nms一致的情况下,修改成DIOU_nms,可以将两个目标检出
    虽然大多数状态下效果差不多,但在不增加计算成本的情况下,有稍微的改进也是好的。

    在这里插入图片描述

    2.3 Yolov5四种网络结构的不同点

    Yolov5代码中的四种网络,和之前的Yolov3Yolov4中的cfg文件不同,都是以yaml的形式来呈现。
    而且四个文件的内容基本上都是一样的,只有最上方的depth_multiplewidth_multiple两个参数不同,很多同学看的一脸懵逼,不知道只通过两个参数是如何控制四种结构的

    2.3.1 四种结构的参数

    大白先取出Yolov5代码中,每个网络结构的两个参数:

    (1)Yolov5s.yaml
    在这里插入图片描述

    (2)Yolov5m.yaml
    在这里插入图片描述

    (3)Yolov5l.yaml
    在这里插入图片描述

    (4)Yolov5x.yaml
    在这里插入图片描述

    四种结构就是通过上面的两个参数,来进行控制网络的深度和宽度。其中depth_multiple控制网络的深度width_multiple控制网络的宽度

    2.3.2 Yolov5网络结构

    四种结构的yaml文件中,下方的网络架构代码都是一样的。
    为了便于讲解,大白将其中的Backbon部分提取出来,讲解如何控制网络的宽度和深度yaml文件中的Head部分也是同样的原理。

    在这里插入图片描述

    在对网络结构进行解析时,yolo.py中下方的这一行代码将四种结构的depth_multiplewidth_multiple提取出,赋值给gdgw。后面主要对这gdgw这两个参数进行讲解。
    在这里插入图片描述

    下面再细致的剖析下,看是如何控制每种结构,深度和宽度的

    2.3.3 Yolov5四种网络的深度

    在这里插入图片描述

    (1)不同网络的深度
    在上图中,大白画了两种CSP结构CSP1CSP2,其中CSP1结构主要应用于Backbone中,CSP2结构主要应用于Neck中。
    需要注意的是,四种网络结构中每个CSP结构的深度都是不同的
    a. Yolov5s为例,第一个CSP1中,使用了1个残差组件,因此是CSP1_1
    而在Yolov5m中,则增加了网络的深度,在第一个CSP1中,使用了2个残差组件,因此是CSP1_2
    Yolov5l中,同样的位置,则使用了3个残差组件Yolov5x中,使用了4个残差组件。
    其余的第二个CSP1和第三个CSP1也是同样的原理。
    b. 在第二种CSP2结构中也是同样的方式,以第一个CSP2结构为例。
    Yolov5s组件中使用了2×X=2×1=2个卷积,因为X=1,所以使用了1组卷积,因此是CSP2_1
    Yolov5m中使用了2组,Yolov5l中使用了3组Yolov5x中使用了4组。其他的四个CSP2结构,也是同理。
    Yolov5中,网络的不断加深,也在不断增加网络特征提取特征融合的能力。
    (2)控制深度的代码
    控制四种网络结构的核心代码是yolo.py中下面的代码,存在两个变量,ngd
    我们再将ngd带入计算,看每种网络的变化结果。

    在这里插入图片描述

    (3)验证控制深度的有效性
    我们选择最小的yolov5s.yaml和中间的yolov5l.yaml两个网络结构,将gd(height_multiple)系数带入,看是否正确。

    在这里插入图片描述

    a. yolov5s.yaml
    其中height_multiple=0.33,即gd=0.33,而n则由上面红色框中的信息获得。
    以上面网络框图中的第一个CSP1为例,即上面的第一个红色框。n等于第二个数值3
    gd=0.33,带入(2)中的计算代码,结果n=1。因此第一个CSP1结构内只有1个残差组件,即CSP1_1
    第二个CSP1结构中,n等于第二个数值9,而gd=0.33,带入(2)中计算,结果n=3,因此第二个CSP1结构中有3个残差组件,即CSP1_3
    第三个CSP1结构也是同理,这里不多说。
    b. yolov5l.xml
    其中height_multiple=1,即gd=1
    和上面的计算方式相同,第一个CSP1结构中,n=3,带入代码中,结果n=3,因此为CSP1_3
    下面第二个CSP1结构和第三个CSP1结构都是同样的原理。

    2.3.4 Yolov5四种网络的宽度

    在这里插入图片描述
    (1)不同网络的宽度
    上图表格中所示,四种Yolov5结构不同阶段的卷积核的数量都是不一样的。
    因此也直接影响卷积后特征图第三维度,即厚度,大白这里表示为网络的宽度
    a. Yolov5s结构为例,第一个Focus结构中,最后卷积操作时,卷积核的数量是32个,因此经过Focus结构,特征图的大小变成304×304×32
    Yolov5mFocus结构中的卷积操作使用了48个卷积核,因此Focus结构后的特征图变成304×304×48Yolov5lYolov5x也是同样的原理。
    b. 第二个卷积操作时,Yolov5s使用了64个卷积核,因此得到的特征图是152×152×64。而Yolov5m使用96个特征图,因此得到的特征图是152×152×96Yolov5lYolov5x也是同理。
    c. 后面三个卷积下采样操作也是同样的原理,这样大白不过多讲解。
    四种不同结构的卷积核的数量不同,这也直接影响网络中比如CSP1结构CSP2等结构,以及各个普通卷积,卷积操作时的卷积核数量也同步在调整,影响整体网络的计算量
    大家最好可以将结构图和前面第一部分四个网络的特征图链接,对应查看,思路会更加清晰
    当然卷积核的数量越多,特征图的厚度,即宽度越宽,网络提取特征的学习能力也越强
    (2)控制宽度的代码
    Yolov5的代码中,控制宽度的核心代码是yolo.py文件里面的这一行:
    在这里插入图片描述

    它所调用的子函数make_divisible的功能是:
    在这里插入图片描述

    (3)验证控制宽度的有效性
    我们还是选择最小的Yolov5s和中间的Yolov5l两个网络结构,将width_multiple系数带入,看是否正确。
    在这里插入图片描述

    a. yolov5s.yaml
    其中width_multiple=0.5,即gw=0.5

    在这里插入图片描述

    第一个卷积下采样为例,即Focus结构中下面的卷积操作。
    按照上面Backbone的信息,我们知道Focus中,标准的c2=64,而gw=0.5,代入(2)中的计算公式,最后的结果=32。即Yolov5sFocus结构中,卷积下采样操作的卷积核数量为32个
    再计算后面的第二个卷积下采样操作,标准c2的值=128gw=0.5,代入(2)中公式,最后的结果=64,也是正确的。
    b. yolov5l.yaml
    其中width_multiple=1,即gw=1,而标准的c2=64,代入上面(2)的计算公式中,可以得到Yolov5lFocus结构中,卷积下采样操作的卷积核的数量为64个,而第二个卷积下采样的卷积核数量是128个
    另外的三个卷积下采样操作,以及Yolov5mYolov5x结构也是同样的计算方式,大白这里不过多解释。

    3 Yolov5相关论文及代码

    3.1 代码

    Yolov5的作者并没有发表论文,因此只能从代码角度进行分析。
    Yolov5代码:https://github.com/ultralytics/yolov5
    大家可以根据网页的说明,下载训练,及测试,流程还是比较简单的。

    3.2 相关论文

    另外一篇论文,PP-Yolo,在Yolov3的原理上,采用了很多的tricks调参方式,也挺有意思。
    感兴趣的话可以参照另一个博主的文章:点击查看

    4 小目标分割检测

    目标检测发展很快,但对于小目标的检测还是有一定的瓶颈,特别是大分辨率图像小目标检测。比如7920×2160,甚至16000×16000像素的图像。

    在这里插入图片描述

    图像的分辨率很大,但又有很多小的目标需要检测。但是如果直接输入检测网络,比如Yolov3检出效果并不好
    主要原因是:
    (1)小目标尺寸
    以网络的输入608×608为例,Yolov3Yolov4Yolov5中下采样都使用了5次,因此最后的特征图大小是19×1938×3876×76
    三个特征图中,最大的76×76负责检测小目标,而对应到608×608上,每格特征图的感受野608/76=8×8大小。

    在这里插入图片描述

    再将608×608对应到7680×2160上,以最长边7680为例,7680/608×8=101
    即如果原始图像中目标的宽或高小于101像素,网络很难学习到目标的特征信息。
    (PS:这里忽略多尺度训练的因素及增加网络检测分支的情况)
    (2)高分辨率
    而在很多遥感图像中,长宽比的分辨率比7680×2160更大,比如上面的16000×16000
    如果采用直接输入原图的方式,很多小目标都无法检测出。
    (3)显卡爆炸
    很多图像分辨率很大,如果简单的进行下采样,下采样的倍数太大,容易丢失数据信息。
    但是倍数太小,网络前向传播需要在内存中保存大量的特征图,极大耗尽GPU资源,很容易发生显卡爆炸显存爆炸,无法正常的训练及推理。
    因此可以借鉴2018年YOLT算法的方式,改变一下思维,对大分辨率图片先进行分割,变成一张张小图,再进行检测
    需要注意的是:
    为了避免两张小图之间,一些目标正好被分割截断,所以两个小图之间设置overlap重叠区域,比如分割的小图是960×960像素大小,则overlap可以设置为960×20%=192像素

    在这里插入图片描述

    每个小图检测完成后,再将所有的框放到大图上,对大图整体做一次nms操作,将重叠区域的很多重复框去除。
    这样操作,可以将很多小目标检出,比如16000×16000像素的遥感图像。

    注意:这里关于小图检测后,放到大图上的方法,发现评论中,很多的同学可能想的过于复杂了,采用的方式,其实按照在大图上裁剪的位置,直接回归到大图即可。

    此外,国内还有一个10亿像素图像目标检测的比赛,也是用的这样的方式,大白将其中一个讲解不错的视频,也放到这个,大家可以点击查看
    在这里插入图片描述

    无人机视角下,也有很多小的目标。大白也进行了测试,效果还是不错的。
    比如下图是将原始大图->416×416大小,直接使用目标检测网络输出的效果:

    在这里插入图片描述

    可以看到中间黄色框区域,很多汽车检测漏掉。
    再使用分割的方式,将大图先分割成小图,再对每个小图检测,可以看出中间区域很多的汽车都被检测出来:

    在这里插入图片描述

    不过这样的方式有中间黄色框区域优点也有中间黄色框区域缺点:
    优点
    (1) 准确性
    分割后的小图,再输入目标检测网络中,对于最小目标像素的下限大大降低
    比如分割成608×608大小,送入输入图像大小608×608的网络中,按照上面的计算方式,原始图片上,长宽大于8个像素的小目标都可以学习到特征。
    (2)检测方式
    在大分辨率图像,比如遥感图像,或者无人机图像,如果无需考虑实时性的检测,且对小目标检测也有需求的项目,可以尝试此种方式。
    缺点:
    (1) 增加计算量
    比如原本7680×2160的图像,如果使用直接大图检测的方式,一次即可检测完。
    但采用分割的方式,切分成N张608×608大小的图像,再进行N次检测,会大大增加检测时间。
    借鉴Yolov5的四种网络方式,我们可以采用尽量轻的网络,比如Yolov5s网络结构或者更轻的网络。
    当然Yolov4Yolov5的网络各有优势,我们也可以借鉴Yolov5的设计方式,对Yolov4进行轻量化改造,或者进行剪枝

    5 后语

    综合而言,在实际测试中,Yolov4的准确性有不错的优势,但Yolov5的多种网络结构使用起来更加灵活,我们可以根据不同的项目需求,取长补短,发挥不同检测网络的优势。
    希望在人工智能的道路上,和大家共同进步

    在这里插入图片描述

    展开全文
  • YOLOV5测试及训练自己的数据集

    万次阅读 多人点赞 2020-06-17 17:33:30
    YOLOV5项目复现一、YOLOv5 实现检测1.1 下载源码1.2 下载官方模型(.pt文件)1.3 配置虚拟环境1.4 进行测试二、YOLOV5 实现训练 首先说一下软硬件配置这一块:win10 + pycharm + i7-9700kf + rtx2070Super + cuda10.2 ...

    首先说一下软硬件配置这一块:win10 + i7-9700kf + rtx2070Super + cuda10.2 + anaconda

    官方模型
    Yolov5检测流程:
    yolov5源码
    .pt
    自己训练的模型
    实现检测

    一、YOLOv5 实现检测

    1.1 下载源码

    进入官方地址,进行源码下载   https://github.com/ultralytics/yolov5[大概4M左右]

    1.2 下载官方模型(.pt文件)

    文中作者是把模型都放到了谷歌网盘里了,如果没有梯子,访问会很慢–>>作者给的模型地址
    如果你实在是下载不下来,并且如果你也还有积分的话–>>CSDN下载模型【可怜可怜孩子吧】
    再如果你没有积分,好吧,好吧,那就,那就,那就留邮箱吧,但别忘了给卑微的我点个赞呦、、、额额额额
    2020.10.21更: 想要模型文件的直接私信我,我一开始设置的5积分,但是积分自己涨的太多了,花那么多积分下载不值当
    2021.04.03更:yolov5官方pt模型文件【点我,我是模型地址】如果链接失效,请及时评论区给我反馈,我及时更新

    1.3 配置虚拟环境

    虚拟环境的优点不再阐述
    创建虚拟环境:conda create -n yolov5 python==3.7在yolov5中尽量用python3.7
    进  入  环  境 :conda activate yolov5
    再安装所需库:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt(使用清华镜像源)
    在pip install的时候,可能会出现read timeout的情况,你需要更换镜像源,或者多执行几次pip install,如果还有其他报错,请留言评论区,我会及时回复,因为我在安装的时候也报了一些错,但是都没有记录下来
    在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

    1.4 进行测试

    进入到yolov5根目录下,我这里是用的powershell,你也可以在控制台,都是一样的。在这里插入图片描述
    运行测试文件:   python detect.py --source 0 【0:是指定的本机摄像头】PS:我特么的竟然一次运行成功,多少是挺失望

    ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓雷霆嘎巴↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
    ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ZBC↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

    在这里插入图片描述

    在这里插入图片描述在这里插入图片描述

    二、YOLOV5 实现训练

    2.1 首先是准备数据集

    ★    数据集的准备工作,我以前的博客有细写过,—>>传送门
    ★    数据集准备好后,一定先确保label和JPEGImages这两个文件夹在同一目录里
    在这里插入图片描述

    2.2 文件修改

    2.2.1 修改数据集方面的yaml文件

    作者是把以前用的.data、.names文件合并到了data/coco.yaml中,打开coco.yaml进行修改

    # COCO 2017 dataset http://cocodataset.org
    # Download command: bash yolov5/data/get_coco2017.sh
    # Train command: python train.py --data ./data/coco.yaml
    # Dataset should be placed next to yolov5 folder:
    #   /parent_folder
    #     /coco
    #     /yolov5
    
    
    # 这些是生成的图片的路径文件,这里是我自己的路径,需要修改成你自己的路径,绝对路径也ok
    train: ../coco/2007_train.txt  # 118k images
    val: ../coco/2007_val.txt  # 5k images
    test: ../coco/2007_test.txt  # 20k images for submission to https://competitions.codalab.org/competitions/20794
    
    # 你数据集的类别数
    nc: 1
    
    # 类别的名称
    names: ['cell phone']
    
    # Print classes
    # with open('data/coco.yaml') as f:
    #   d = yaml.load(f, Loader=yaml.FullLoader)  # dict
    #   for i, x in enumerate(d['names']):
    #     print(i, x)
    

    2.2.2 修改网络参数方面的yaml文件

    这个相当于以前版本的.cfg文件,在models/yolov3-spp.yaml【当然,你想用哪个模型就去修改对应的yaml文件】

    # parameters
    nc: 1  # 数据集类别数
    depth_multiple: 1.0  # expand model depth
    width_multiple: 1.0  # expand layer channels
    
    # anchors【你也可以使用k-means去产出你自己数据集的anchors】
    anchors:
      - [10,13, 16,30, 33,23]  # P3/8
      - [30,61, 62,45, 59,119]  # P4/16
      - [116,90, 156,198, 373,326]  # P5/32
    
    # darknet53 backbone
    backbone:
      # [from, number, module, args]
      [[-1, 1, Conv, [32, 3, 1]],  # 0
       [-1, 1, Conv, [64, 3, 2]],  # 1-P1/2
       [-1, 1, Bottleneck, [64]],
       [-1, 1, Conv, [128, 3, 2]],  # 3-P2/4
       [-1, 2, Bottleneck, [128]],
       [-1, 1, Conv, [256, 3, 2]],  # 5-P3/8
       [-1, 8, Bottleneck, [256]],
       [-1, 1, Conv, [512, 3, 2]],  # 7-P4/16
       [-1, 8, Bottleneck, [512]],
       [-1, 1, Conv, [1024, 3, 2]], # 9-P5/32
       [-1, 4, Bottleneck, [1024]],  # 10
      ]
    
    # yolov3-spp head
    # na = len(anchors[0])
    head:
      [[-1, 1, Bottleneck, [1024, False]],  # 11
       [-1, 1, SPP, [512, [5, 9, 13]]],
       [-1, 1, Conv, [1024, 3, 1]],
       [-1, 1, Conv, [512, 1, 1]],
       [-1, 1, Conv, [1024, 3, 1]],
       [-1, 1, nn.Conv2d, [na * (nc + 5), 1, 1]],  # 16 (P5/32-large)
    
       [-3, 1, Conv, [256, 1, 1]],
       [-1, 1, nn.Upsample, [None, 2, 'nearest']],
       [[-1, 8], 1, Concat, [1]],  # cat backbone P4
       [-1, 1, Bottleneck, [512, False]],
       [-1, 1, Bottleneck, [512, False]],
       [-1, 1, Conv, [256, 1, 1]],
       [-1, 1, Conv, [512, 3, 1]],
       [-1, 1, nn.Conv2d, [na * (nc + 5), 1, 1]],  # 24 (P4/16-medium)
    
       [-3, 1, Conv, [128, 1, 1]],
       [-1, 1, nn.Upsample, [None, 2, 'nearest']],
       [[-1, 6], 1, Concat, [1]],  # cat backbone P3
       [-1, 1, Bottleneck, [256, False]],
       [-1, 2, Bottleneck, [256, False]],
       [-1, 1, nn.Conv2d, [na * (nc + 5), 1, 1]],  # 30 (P3/8-small)
    
       [[], 1, Detect, [nc, anchors]],   # Detect(P3, P4, P5)
      ]
    
    

    2.2.3 修改train.py中的一些参数

    train.py在根目录里,修改一些主要的参数,奥利给
    在这里插入图片描述

    parser.add_argument('--epochs', type=int, default=200)  # 训练的epoch
    parser.add_argument('--batch-size', type=int, default=16)  # batch_size 显卡垃圾的话,就调小点
    parser.add_argument('--cfg', type=str, default='models/yolov5s.yaml', help='*.cfg path')
    parser.add_argument('--data', type=str, default='data/coco.yaml', help='*.data path')
    parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='train,test sizes')
    

    2.3开始训练

    直接 python train.py 就Ok了

    成功训练如图所示
    在这里插入图片描述
    ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓无情哈拉少↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

    在这里插入图片描述
    在这里插入图片描述

    2.4 ?

    都已经在训练了,你接下来还有最重要的一步,就是看个日本特产电影啥的,或者是吃个瓜啥的,拉个屎啥的,反正我是去拉屎了🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵在这里插入图片描述
    等它训练完就没问题了,但是还是要时不时看一眼,具体看什么,我也不知道呀,反正是看就完事儿了🐷🐷🐷🐷🐷🐷🐷🐷🐷🐷🐷🐷🐷🐷🐷🐷

    都训练完了,测试的话,就不用再说的吧,阿sir,

    三、个人对于yolov5的看法

    先说一个情况吧,我在复现yolov4时,使用1080p的摄像头进行测试的时候,检测的帧率只有1.7fps(在我的rtx2070s显卡上),不管我如何调整cfg文件里的宽高,基本都无济于事,然后我用480p的摄像头才可以达到20fps,不要搞我啊,阿sir,现在摄像头基本都是在1080p检测的啊,480p怎么能满足!!!!!我不知道为什么图像在相同的cfg参数下,分辨率对检测速度影响会这么大。但是,啊,但是,我在用yolov5的时候,用1080P就可以达到实时,最主要的是yolov5的模型非常小,比yolo的前几个系列小了大概4倍,非常适合做嵌入。对于yolov5,虽然是作者自封的,但是非常达到我心里的预期!!!,不吹不黑,yolov5是我遇到最牛啤的目标检测算法,你说呢,你是不是也这么感觉的呢

    在这里插入图片描述在这里插入图片描述在这里插入图片描述

    展开全文
  • Yolov3&Yolov4&Yolov5模型权重及网络结构图资源下载

    万次阅读 多人点赞 2020-08-09 10:27:27
    Yolov4的《深入浅出Yolo系列之Yolov3&Yolov4核心基础完整讲解》 (2)Yolov5的《深入浅出Yolo系列之Yolov5核心基础完整讲解》 其中提到很多网络模型的权重,由于不是国内资源,很多同学反馈不好下载。 而且文章...
  • YOLO系列可以说是单机目标检测框架中的潮流前线了,由于YOLOv5是在PyTorch中实现的,它受益于成熟的PyTorch生态系统,支持更简单,部署更容易,相对于YOLOv4,YOLOv5具有以下优点: 速度更快。在YOLOv5 Colab ...
  • YOLOv5学习总结(持续更新)

    万次阅读 多人点赞 2020-09-12 06:11:36
    YOLOv5学习总结训练效果(持续更新)前言网络结构可视化yolov3yolov4yolov5核心基础内容Mosaic数据增强自适应锚框计算自适应图片缩放 首先感谢江大白大佬的研究与分享,贴上链接 深入浅出Yolo系列之Yolov5核心基础...
  • yolov3-tiny预训练 yolov3预训练 yolov4 预训练yolov4-tiny预训练模型,全部打包在压缩文件
  • 项目代码yolov5,官网,项目开源的时间:20200601 自定义数据集: #1 安装环境依赖 ##1.1 克隆项目 git clone https://github.com/ultralytics/yolov5 # clone repo 如果下载比较慢,建议使用下面的镜像下载: ...
  • yolov3,yolov4,yolov5网络架构图 Visio 2013绘制 vsdx文件 可编辑
  • YoLov3训练自己的数据集(小白手册)

    万次阅读 多人点赞 2018-08-02 11:00:12
    工具:labelimg、MobaXterm 1.标注自己的数据集。用labelimg进行标注,保存后会生成与所标注图片文件名相同...2.下载yolov3项目工程。按照YoLo官网下载 git clone https://github.com/pjreddie/darknet cd darkn...
  • 目标检测:YOLOv3: 训练自己的数据

    万次阅读 多人点赞 2018-03-26 10:59:02
    update:20200424 YOLOV4出来啦!!!快去把玩一下!!! https://github.com/AlexeyAB/darknet  ------------------------------ 本文仅供学习交流,如有错误,望交流指教 ------------------------------ windows...
  • yolov5s.pt yolov5x.pt yolov5m.pt yolov5l.pt
  • YOLOv5训练自己的数据集(超详细完整版)

    万次阅读 多人点赞 2020-10-16 11:38:18
    一.Requirements 本教程所用环境:代码版本V3.0,源码下载地址:...1.在yolov5目录下创建paper_data文件夹(名字可以自定义),目录结构如下,将之前labelImg标注好的xml文件和图片放到对应目录下 paper_
  • 该存储库使用简化和最少的代码来重现 yolov3 / yolov4 检测网络和暗网分类网络。 亮点如下: 1、支持原版暗网模式; 2、支持“* .cfg”、“* .weights”模型的训练、推理、导入导出; 3、支持最新的yolov3、yolov4...
  • YoloVx(yolov5 / yolov4 / yolov3 / yolo_tiny) 张量流 安装NVIDIA驱动程序 安装CUDA10.1和cudnn7.5 安装Anaconda3,下载 安装tensorflow,例如“ sudo pip install tensorflow> = 1.15或tensorflow> 2.0”等。 ...
  • YoloV5实战:手把手教物体检测——YoloV5

    万次阅读 多人点赞 2020-10-24 07:35:53
    目录 摘要 训练 1、下载代码 2、配置环境 ...YOLOV5严格意义上说并不是YOLO...YOLOv5并不是一个单独的模型,而是一个模型家族,包括了YOLOv5s、YOLOv5m、YOLOv5l、YOLOv5x、YOLOv5x+TTA,这点有点儿像EfficientDe..
  • YOLOv4-pytorch 训练自己的数据集环境新特性安装依赖库准备工作下载预训练模型训练测试 注:版权所有,转载请注明出处。 环境 Nvida GeForce RTX 2070 CUDA10.0 CUDNN7.0 Windows10 Python 3.6 代码地址: 新特性 ...
  • yolov3、yolov4与yolov5性能对比

    千次阅读 2021-02-08 15:13:51
    下面将对yolov3、yolov4与yolov5从性能方面进行对比 1、yolov5四种模型之间的对比 Yolov5s网络最小,速度最少,AP精度也最低。但如果检测的以大目标为主,追求速度,倒也是个不错的选择。 其他的三种网络,在此基础...
  • 【深入YoloV5(开源)】基于YoloV5的模型优化技术与使用OpenVINO推理实现 前言 CV调包侠自己的深度学习交流群中一位兄弟在看我以前的github和博客:https://blog.csdn.net/qq_46098574/article/details/107334954 中...
  • yolov5s.pt yolov5weight文件
  • yolov5和yolov4、yolov3对比

    千次阅读 2020-12-01 20:55:21
    yolov5和yolov4、yolov3对比性能曲线模型结构iou-Loss 性能曲线 MSCOCO数据集的测试结果: 模型结构 yolov5模型大小: iou-Loss 回归框loss的发展路线:Smooth L1 Loss—— IoU Loss(2016)——GIoU Loss...
  • yolov4.conv.137这个文件,作者给的下载链接,是外网,所以翻墙去下载了一下,速度还不错,这玩意没必要还收费,毕竟模型都是作者的,直接免费分享了: 链接:https://pan.baidu.com/s/1XrcPHdp2_4c-dKge2Guw4w 提取...
  • YOLOv4:kaggle的YOLOv4
  • yolov3&yolov4&yolov5比较

    千次阅读 2021-01-28 21:48:41
    简单总结了yolov3、v4、v5区别。 yolov3 yolov4 yolov5 输入端 数据增强 Mosaic Mosaic 锚框 anchor anchor 自适应anchor 主干网络(backbone) 网络 ...
  • yolov5x.pt yolov5权重文件
  • opencvYolo opencv调用yolov3或者yolov4模型文件,包括使用CPU、GPU的方法。 详情前往: 运行效果: 1. 2. 模型文件 1.YoloV3: 2.YoloV4:
  • import { yolov3, yolov3Tiny } from 'tfjs-yolov3' async function start () { const yolo = await yolov3Tiny() // pre-load model (35M) // or // const yolo = await yolov3() // pre-load mode
  • YOLOv1、YOLOv2和YOLOv3对比

    千次阅读 2020-06-06 10:04:22
    YOLOv1、YOLOv2和YOLOv3对比R-CNN系列YOLOv1结构目标输出网络训练YOLOv1的局限性和R-CNN系列的对比YOLOv2结构目标输出网络训练关于YOLO9000YOLOv3结构目标输出网络训练YOLOv3系统做过的不成功的尝试未来 YOLO深度...
  • 史上最详细yolov5环境配置搭建+配置所需文件

    万次阅读 多人点赞 2020-07-31 00:21:49
    yolov5的配置过程总体来说还算简单,但是网上大部分博客都没有仔细介绍具体步骤,本文将从最细节的层面记录yolov5环境配置的全过程 使用到的工具有 1.anaconda,pycharm 2.cuda10.2+cudnn-10.2-windows10-x64-v7.6....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 22,499
精华内容 8,999
关键字:

yolov