手把手教你实现YOLOv3 (一)

1. 引言

最近整理了YOLO系列相关论文阅读笔记,发现仅仅靠阅读论文还是有很多内容一知半解,吃得不是很透彻.
尽管网络上有很多博客都在讲解,但是很多实现细节细究起来还是有些困难.

俗话说的好:

Talk is cheap. Show me the code.

鉴于已在CV行业内卷四年,近期打算来写个教程和大家一起从零开始实现YOLOv3,顺便带大家一起入门目标检测的大坑…

闲话少说,我们直接开始吧…

2. YOLOV3算法思想

鉴于已在前篇详细介绍过YOLO系列的算法过程,这里我们仅简单对其核心思想进行回顾.

YOLO的作者将目标检测问题视为回归问题,首先将整副图划分成 S X S SXS SXS的网格,如果目标框的中心点落在这个网格中,那么这个网格就负责预测这个目标.

在这里插入图片描述
每一个网格都会预测bounding box,confidence以及class probability map:

  • bounding box包含四个值: x , y , w , h x,y,w,h x,y,w,h 其中 ( x , y ) (x,y) (x,y)代表预测框的中心点, ( w , h ) (w,h) (w,h)代表预测框的宽和高
  • confidence表示预测框包含目标的可能性,训练时的真值为预测框和真值框的IOU
  • class probability map表明这个目标所属类别的置信度

3. YOLOV3网络架构

YOLO(You Only Look Once)将整个图像输入到网络中,可以直接预测目标位置和对应的类别.这使得YOLO推理速度很快并且保持较高的精度.

3.1 Darknet53

YOLOv3采用了53层卷积层作为主干,又被叫做Darknet-53,网络结构如下所示:

请添加图片描述
观察上图,发现Darknet-53是由卷积层和残差层组成.同时需要注意的时,最后三层 Avgpool,Connected和softmax层是用来在ImageNet数据集上训练分类任务时使用的.当我们使用Darknet-53作为YOLOv3中提取图像特征的主干时,则不再使用最后三层.

接着我们来用代码实现DarkNet-53, 代码如下:

class Darknet53(nn.Module):
    def __init__(self):
        super(Darknet53, self).__init__()
        self.dark0 = BaseConv(3, 32, 3, 1)  
        self.dark1 = nn.Sequential(
            BaseConv(32, 64, 3, 2),  
            BasicBlock(64, 64, shortcut=True)  
        )
        self.dark2 = nn.Sequential(
            BaseConv(64, 128, 3, 2),
            BasicBlock(128, 128, True),
            BasicBlock(128, 128, True),
        )  
        self.dark3 = nn.Sequential(
            BaseConv(128, 256, 3, 2),
            BasicBlock(256, 256, True),  
            BasicBlock(256, 256, True),  
            BasicBlock(256, 256, True), 
            BasicBlock(256, 256, True),  
            BasicBlock(256, 256, True), 
            BasicBlock(256, 256, True),  
            BasicBlock(256, 256, True), 
            BasicBlock(256, 256, True), 
        )
        self.dark4 = nn.Sequential(
            BaseConv(256, 512, 3, 2), 
            BasicBlock(512, 512, True), 
            BasicBlock(512, 512, True),  
            BasicBlock(512, 512, True),  
            BasicBlock(512, 512, True),  
            BasicBlock(512, 512, True),  
            BasicBlock(512, 512, True),  
            BasicBlock(512, 512, True),  
            BasicBlock(512, 512, True), 
        )
        self.dark5 = nn.Sequential(
            BaseConv(512, 1024, 3, 2),  
            BasicBlock(1024, 1024, True),  
            BasicBlock(1024, 1024, True), 
            BasicBlock(1024, 1024, True),  
            BasicBlock(1024, 1024, True),  
        )
        
    def forward(self,x):
        out0 = self.dark0(x)
        out1 = self.dark1(out0)
        out2 = self.dark2(out1)
        out3 = self.dark3(out2)
        out4 = self.dark4(out3)
        out5 = self.dark5(out4)
        return out3,out4,out5

验证代码如下:

if __name__ == "__main__":
    x = torch.randn((1,3,416,416))
    model = Darknet53()
    out3,out4,out5 = model(x)
    print(out3.shape)
    print(out4.shape)
    print(out5.shape)

运行结果如下:

torch.Size([1, 256, 52, 52])
torch.Size([1, 512, 26, 26])
torch.Size([1, 1024, 13, 13])

符合预期,输出的三个特征图为采样8倍,16倍和32倍的结果.

3.2 YOLOv3网络结构

有了上述主干网络DarkNet53的结构后,接下来我们来分析YOLOv3的整体结构,如下所示:

在这里插入图片描述
观察上图,我们可以知道:

  • YOLOv3在3个scale的特征图上分别预测不同大小的目标.即在8倍,16倍和32倍的特征图上进行预测.也就是说如果我们的输入为416X416,那么YOLOv3预测时采用的特征图的大小分别为52X52,32X32以及13X13
  • 对于第一个scale, YOLOV3将输入降采样为13X13,在82层进行预测,此时预测输出的3维Tensor的大小为13X13X255
  • 之后,YOLOv3从第79层获取特征图,接着应用一个卷积层进行通道压缩,然后将其上采样2倍,大小为26 x 26。然后,该特征图与第61层的特征图进行concat操作。最后,将concat后的特征图在经过几个卷积层进一步提取特征,直到在第94层作为第二个尺度检测的特征图。第二个sace预测输出的3维Tensor的大小为26 x 26 x 255。
  • 对于第三个尺度,重复上述操作.即第91层的特征图先接一个卷积层进行通道压缩,然后上采样2倍,大小为52X52,然后与第36层的特征图进行concat操作.接着是几层卷积操作,最终预测层在106层完成,产生的三维Tensor的大小为52X52X255.
  • 总之,YOLOv3在3种不同尺度的特征图上进行检测,因此如果我们输入416x416大小的图像,它将产生3种不同的输出形状张量,13 x 13 x 255、26 x 26 x 255和52 x 52 x 255。

上述结构,虽然便于理解,但是代码实现起来还是不太清晰,不过不用怕,我们还有更详细的网络结构图,如下所示:
在这里插入图片描述
在上图中,我们可以看到尺寸为416x416的输入图片在进入Darknet-53网络后得到3个分支。这些分支经历一系列卷积、上采样、合并和其他操作。最终获得三个不同尺寸的特征图,形状分别为[13,13,255]、[26,26,255]和[52,52,255].

我们用代码实现上述结构如下:

class YOLOV3(nn.Module):
    def __init__(self,num_calss):
        super(YOLOV3, self).__init__()
        self.darknet =  Darknet53()
        # branch1
        self.feature1 = YOLOBlock(1024,512)
        self.out_head1 = YOLO_HEAD(512, 1024, 3*(num_calss+1+4))
		# branch2
        self.cbl1 = self._make_cbl(512,256,1)
        self.upsample = nn.Upsample(scale_factor=2, mode='nearest')
        self.feature2  = YOLOBlock(768,256)
        self.out_head2 = YOLO_HEAD(256, 512, 3*(num_calss+1+4))
		# branch3
        self.cbl2 = self._make_cbl(256, 128, 1)
        self.feature3  = YOLOBlock(384, 128)
        self.out_head3 = YOLO_HEAD(128, 256, 3*(num_calss+1+4))
        self.num_class = num_calss
    
    def _make_cbl(self, _in, _out, ks):
        return BaseConv(_in, _out, ks, stride=1, activate="lrelu")

    def forward(self,x):
        # backbone
        out3,out4,out5 = self.darknet(x)
        # branch1 13X13X255
        feature1 = self.feature1(out5)
        out_large = self.out_head1(feature1)
        # branch2 26X26X255
        cb1 = self.cbl1(feature1)
        up1 = self.upsample(cb1)
        x1_in = torch.cat([up1, out4], 1)
        feature2 = self.feature2(x1_in)
        out_medium = self.out_head2(feature2)
        # branch3 52X52X255
        cb2 = self.cbl2(feature2)
        up2 = self.upsample(cb2)
        x2_in = torch.cat([up2, out3], 1)
        feature3 = self.feature3(x2_in)
        out_small = self.out_head3(feature3)
        
        return (out_large,out_medium,out_small)

测试代码如下:

if __name__ == "__main__":
    x = torch.randn((1,3,416,416))
    model = YOLOV3(80)
    cnt = 0
    out = model(x)
    out_large,out_medium,out_small= model(x)
    print(out_large.shape)
    print(out_medium.shape)
    print(out_small.shape)

运行结果如下:

torch.Size([1, 255, 13, 13])
torch.Size([1, 255, 26, 26])
torch.Size([1, 255, 52, 52])

3.3 Residual module

残差模块(Residual module)最显著的特点是使用了一种shortcut机制,以缓解因增加神经网络中的深度而导致的梯度消失的问题,从而使神经网络更易于训练。它主要使用identity mapping 在输入和输出之间建立连接.
在这里插入图片描述
实现细节如下:

class BasicBlock(nn.Module):
    def __init__(self,in_chl,out_chl,shortcut=True,activate='lrelu'):
        super(BasicBlock, self).__init__()
        down_chl  = in_chl // 2
        self.conv1 = BaseConv(in_chl,down_chl,ksize=1,stride=1,activate=activate)
        self.conv2 = BaseConv(down_chl,out_chl,ksize=3,stride=1,activate=activate)
        self.shortcut = shortcut and in_chl == out_chl

    def forward(self,x):
        conv1 = self.conv1(x)
        out = self.conv2(conv1)
        if self.shortcut:
           out = out + x
        return out

3.4 特征图分析

要详细了解YOLO的输出含义,首先需要了解什么是特征图。

特征图

在我们讨论CNN网络结构的时候,我们总经常使用的一个词汇叫做 feature map. 简单地说,输入图像与卷积核进行卷积操作后就可以获得图像特征。

一般来说,当输入图像经过CNN提取特征时,特征图的数量(卷积核的数量)将增加,同时空间信息将减少,当然提取到的特征也会越来越抽象.我们以著名的VGG16网络为例,其特征图尺度变化如下:

在这里插入图片描述

随着网络变深,特征图的空间尺寸越来越小,但通道数目越来越大。这是CNN的特性。

特征向量

当我们涉及到特征图时,我们经常会听到在人脸识别领域提到它。一般来说,此时的特征图的含义实际上是由最后一个全连接层提取到的特征向量。早在2006年,深度学习的创始人Hinton就在《Science》杂志上发表了一篇论文。文中首次使用神经网络从mnist手写字符数据集中提取特征向量(2D或3D向量)。

在这里插入图片描述
当CNN网络自下而上从输入图像中提取特征时,生成的特征图通常在空间尺寸上越来越小,在通道数目上越来越深:
在这里插入图片描述

上述特性与ROI到特征图的映射有关。如上图所示:将原始图像中的ROI映射到CNN网络空间后,特征图上的空间大小会变小,甚至是一个点,但该点的通道信息会非常丰富。该信息是CNN网络上映射的ROI区域中图像信息的特征表示。由于图像中的像素在空间上紧密相连,这导致了空间上的巨大冗余。因此,我们通常通过减少空间维度和增加通道维度来消除这种冗余,并尝试在最小维度中获得其最重要的特征:
在这里插入图片描述

举例,上图中输入图像左上角的红色ROI经过CNN映射,在特征图上仅获得一个点,但该点有85个通道线。因此,ROI的维度已经从原来的[32,32,3]变为当前的85维。

这实际上是YOLO网络对ROI执行特征提取后获得的85维特征向量。该特征向量的前四个维度表示候选框信息,中间维度表示判断对象是否存在的概率,接下来的80个维度表示80个类别的分类概率信息。


4 总结

本节主要实现了YOLO v3网络主干的讲解和代码实现,重点集中于Darknet-53主干和YOLO v3的检测头的实现.

嗯嗯,时间原因,今天就先到这里吧. 下篇计划讲解YOLOv3的预测部分.

在这里插入图片描述
关注公众号《AI算法之道》,获取更多AI算法资讯。

参考

  • 0
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
YOLOv5是目标检测算法中的一种,通过训练自己的数据集可以实现对自定义目标的检测。下面是YOLOv5入门实践的步骤。 第一步,准备数据集。首先收集一些与你想要检测的目标相关的图像,确保图像中的目标已标注好边界框坐标。将图像和对应的标注文件放在数据集文件夹中。 第二步,配置运行环境。需要在计算机上安装Python环境,并根据YOLOv5的要求安装相应的库和依赖。可以通过pip或conda进行安装。 第三步,调整配置文件。YOLOv5提供了一个默认的配置文件,可以根据自己的需求进行修改。主要需要调整的是类别数量和路径配置。 第四步,划分训练集和验证集。将数据集中的图像和标注文件划分为训练集和验证集,一般可以按照70%的比例划分。 第五步,训练模型。在终端中运行训练命令,指定相关参数,如模型类型、数据集路径、训练集和验证集路径等。训练时可以选择使用预训练权重或从头开始。 第六步,评估模型。训练完成后,可以通过评估命令对模型进行评估,得到关于模型性能的指标,如精确度、召回率等。 第七步,使用模型进行目标检测。训练完成的模型可以用于检测自定义数据集中的目标。可以在终端中运行检测命令,指定相关参数,如模型路径、检测图像路径等。 通过以上步骤,我们可以进行YOLOv5的入门实践,训练自己的数据集,并使用训练好的模型进行目标检测。随着更多的实践和调优,可以提高模型性能和检测效果。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赵卓不凡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值