yolov4原理及对应代码详解【对比yolov3】

前言

该篇文章主要是自己学习yolov4时做的一个总结,让自己理解更清晰,先看yolov3会有更为深刻理解哟。

yolov4论文pdf下载链接:https://pan.baidu.com/s/1dJ4Sj833Xzl_5_SQzgMGqQ
提取码:w8lf
yolov3原理:https://blog.csdn.net/weixin_39615182/article/details/109752498

一、yolov4性能表现

在这里插入图片描述

在论文的第一页就有上面这张图,意思是研究者对比了 YOLOv4 和当前最优目标检测器,发现YoLov4在取得EfficientDet同等性能的情况下,速度是它的两倍。,并且AP与FPS这两项指数相比YOLOv3分别提高了10%与12%。

AP:average precision,平均精确率,即多类预测的时候每一类的precision取平均,反映了模型对某个类别识别的好坏。
FPS:每秒帧率(Frame Per Second,FPS),即每秒内可以处理的图片数量。
上面两个是评价模型性能的指标,准确率与检测速度

作者在摘要中写到:我们使用以下新功能:WRC,CSP,CmBN,SAT,Mish激活,Mosaic数据增强,CmBN,DropBlock正则化和CIoU丢失。我们一起来看看吧

二、yolov4结构

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

上图为论文原图,形象将模型分为四部分:input,Backbone、Neck、Head
结构讲解:
假设输入图像大小为416×416,可以看到主干网络Darknet53–>CSPDarknet53,激活函数由yolov3的Leak-Relu–>Mish,Mish激活函数公式与图像如下:
在这里插入图片描述
我们来分析一下网络结构:
1、主干网络Backbone
1.1、首先是输入层输入(416,416,3)的图,经过DarknetConv2D_BN_Mish(图中的Conv2d_BN_Mish)卷积块,卷积块由DarknetConv2D、BatchNormalization(BN)、Mish三部分组成,即该卷积结构包括l2正则化、批标准化、Mish激活函数这一系列操作。


#   单次卷积
@wraps(Conv2D)
def DarknetConv2D(*args, **kwargs):
    darknet_conv_kwargs = {'kernel_regularizer': l2(5e-4)}
    darknet_conv_kwargs['padding'] = 'valid' if kwargs.get('strides')==(2,2) else 'same'
    darknet_conv_kwargs.update(kwargs)
    return Conv2D(*args, **darknet_conv_kwargs)

#   卷积块 DarknetConv2D + BatchNormalization + Mish
def DarknetConv2D_BN_Mish(*args, **kwargs):
    no_bias_kwargs = {'use_bias': False}
    no_bias_kwargs.update(kwargs)
    return compose(
        DarknetConv2D(*args, **no_bias_kwargs),
        BatchNormalization(),
        Mish())

1.2、然后后面有5个CSPResblockBody,分别为×1,×2,×8,×8,×4,表示重复多少次resblock_body,可以看到该残差结构块中先有个ZeroPadding2D对二维矩阵的四周填充0,即零填充层,然后三个DarknetConv2D_BN_Mish卷积块,第二个DarknetConv2D_BN_Mish卷积块会生成一个大的残差边(后面会解释残差边),然后有个for循环对通道进行整合[compose]与特征提取,这个循环多少次就是图中×1,×2,×8等。,在后面进行一次1x1DarknetConv2D_BN_Mish卷积,再concatenate堆叠拼接,最后再对通道数进行整合并返回。可以看到,resblockbody中有大量的DarknetConv2D_BN_Mish卷积块进行卷积提取特征。

#   CSPdarknet的结构块
#   存在一个大残差边
def resblock_body(x, num_filters, num_blocks, all_narrow=True):
    # 进行长和宽的压缩
    preconv1 = ZeroPadding2D(((1,0),(1,0)))(x)
    preconv1 = DarknetConv2D_BN_Mish(num_filters, (3,3), strides=(2,2))(preconv1)

    # 生成一个大的残差边 
    shortconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(preconv1)

    # 主干部分的卷积
    mainconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(preconv1)
    # 1x1卷积对通道数进行整合->3x3卷积提取特征,使用残差结构
    for i in range(num_blocks):
        y = compose(
                DarknetConv2D_BN_Mish(num_filters//2, (1,1)),
                DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (3,3)))(mainconv)
        mainconv = Add()([mainconv,y])
    # 1x1卷积后和残差边堆叠
    postconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(mainconv)
    route = Concatenate()([postconv, shortconv])

    # 最后对通道数进行整合
    return DarknetConv2D_BN_Mish(num_filters, (1,1))(route)

CSPDarknet(yolov4)与Darknet(yolov3)的resbodyblock不一样地方就是使用了CSPnet结构在这里插入图片描述

CSPnet结构将原来的残差块的堆叠进行了一个拆分,拆成左右两部分:
主干部分还是进行残差快的堆叠(Part 2)
另一部分则像是一条长边一样,经过少量处理直接连接到最后。
因此可以认为CSP中存在一个大的残差边。
残差边的作用:残差结构可以不通过卷积与堆叠,直接从前面一个特征层映射到后面的特征层(跳跃连接),有助于训练,也有助于特征的提取。
主干网络代码如下:

#   CSPdarknet53 的主体部分
def darknet_body(x):
    x = DarknetConv2D_BN_Mish(32, (3,3))(x)
    x = resblock_body(x, 64, 1, False)
    x = resblock_body(x, 128, 2)
    x = resblock_body(x, 256, 8)
    feat1 = x
    x = resblock_body(x, 512, 8)
    feat2 = x
    x = resblock_body(x, 1024, 4)
    feat3 = x
    return feat1,feat2,feat3

darknet_body(x)中的x即为输入传递来的图像数组(416,416,3),然后图像先通过 DarknetConv2D_BN_Mish卷积块进行卷积,得到shape为(416,416,32),再进行5次resblockbody,每一次宽高都减半,深度翻倍,其中feat1,feat2,feat3是对当时的特征层做一个记录,因为后面还需用到,变化如下图。
在这里插入图片描述

2、特征金字塔部分SSP与PAN
相比YOLOv3,YOLOV4结合了两种改进:
1).使用了SPP结构。
2).使用了PANet结构。
:除去CSPDarknet53主干网络和Yolo Head的结构外,都是特征金字塔的结构。
2.1 SSP结构
首先SSP结构进行了三次DarknetConv2D_BN_Leaky卷积,你没看错,这里不是Mish,分别利用四个不同尺度的最大池化进行处理,最大池化的池化核大小分别为13x13、9x9、5x5、1x1(1x1–无处理),该结构能分离出最显著的上下文特征,是强有力的特征提取,池化后,再进行堆叠。如下图
在这里插入图片描述

	# 生成darknet53的主干模型
    feat1,feat2,feat3 = darknet_body(inputs)
    P5 = DarknetConv2D_BN_Leaky(512, (1,1))(feat3)
    P5 = DarknetConv2D_BN_Leaky(1024, (3,3))(P5)
    P5 = DarknetConv2D_BN_Leaky(512, (1,1))(P5)
    # 使用了SPP结构,即不同尺度的最大池化后堆叠。
    maxpool1 = MaxPooling2D(pool_size=(13,13), strides=(1,1), padding='same')(P5)
    maxpool2 = MaxPooling2D(pool_size=(9,9), strides=(1,1), padding='same')(P5)
    maxpool3 = MaxPooling2D(pool_size=(5,5), strides=(1,1), padding='same')(P5)
    P5 = Concatenate()([maxpool1, maxpool2, maxpool3, P5])

简单描述下代码,首先darknet_body(inputs)可以获得返回的三个参数feat1,feat2,feat3,其中feat3为主干网络经过5个网络输出的特征层,然后对它进行3次 DarknetConv2D_BN_Leaky卷积,再经过三个最大池化分离特征,一个1×1池化即不变,还是P5,最后用 Concatenate()将四个进行堆叠。

2.2PANet
PANet是2018年发表的一种实例分割算法,它可以反复提取特征。
在这里插入图片描述

在这里插入图片描述

整个过程大致来讲就是上采样、再堆叠卷积重复,之后再进行下采样、堆叠,如上图,上采样是放大,下采样是压缩。

3、输出结果yolo head
与yolov3中一样,内部是一个3×3卷积,一个是1×1卷积,进行通道调整
最后得到三个输出(52,52,75),(26,26,75),(13,13,75)
注:假设基于VOC数据集
在这里插入图片描述
在这里插入图片描述

	# 池化堆叠后的四次卷积,注意compose中还有一次
    P5 = DarknetConv2D_BN_Leaky(512, (1,1))(P5)
    P5 = DarknetConv2D_BN_Leaky(1024, (3,3))(P5)
    P5 = DarknetConv2D_BN_Leaky(512, (1,1))(P5)
	
	# 上采样
    P5_upsample = compose(DarknetConv2D_BN_Leaky(256, (1,1)), UpSampling2D(2))(P5)
    
    # 与左边主干网络的特征层feat2进行拼接
    P4 = DarknetConv2D_BN_Leaky(256, (1,1))(feat2)
    P4 = Concatenate()([P4, P5_upsample])
    # Conv2d×5
    P4 = make_five_convs(P4,256)
	
	# 上采样
    P4_upsample = compose(DarknetConv2D_BN_Leaky(128, (1,1)), UpSampling2D(2))(P4)
    
    # 与左边主干网络的特征层feat1进行拼接
    P3 = DarknetConv2D_BN_Leaky(128, (1,1))(feat1)
    P3 = Concatenate()([P3, P4_upsample])
    # Conv2d×5
    P3 = make_five_convs(P3,128)

    # 52x52的输出yolo head1,若是VOC数据集,则3×(20+5)=75,shape为(52,52,75)
    # conv2d 3*3
    P3_output = DarknetConv2D_BN_Leaky(256, (3,3))(P3)
    # con2d 1*1
    P3_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P3_output)

	# 注意一下,这里下采样有个零填充即可
    P3_downsample = ZeroPadding2D(((1,0),(1,0)))(P3)
    P3_downsample = DarknetConv2D_BN_Leaky(256, (3,3), strides=(2,2))(P3_downsample)
    P4 = Concatenate()([P3_downsample, P4])
    P4 = make_five_convs(P4,256)
    
    # 38x38的out
    P4_output = DarknetConv2D_BN_Leaky(512, (3,3))(P4)
    P4_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P4_output)
    

    P4_downsample = ZeroPadding2D(((1,0),(1,0)))(P4)
    P4_downsample = DarknetConv2D_BN_Leaky(512, (3,3), strides=(2,2))(P4_downsample)
    P5 = Concatenate()([P4_downsample, P5])
    P5 = make_five_convs(P5,512)
    
    # 19x19的out
    P5_output = DarknetConv2D_BN_Leaky(1024, (3,3))(P5)
    P5_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P5_output)
    return Model(inputs, [P5_output, P4_output, P3_output])

输出三个yolo head后还需对其进行解码,因为这个yolo head预测结果并不对应着最终的预测框在图片上的位置,还需要解码才可以完成。这部分在yolov3文章的第二、三步已经写了,可以直接看2、3步,链接如下:
yolov3原理详解
下面讲下实现预测结果解码得分排序、非极大抑制筛选两部分代码
解码是为了得到真实的边界框,但是不只一个,通过得分排序和非极大抑制筛选得到众多边框中最准确的一个,即最优的一个

#---------------------------------------------------#
#   将预测值的每个特征层调成真实值
#---------------------------------------------------#
def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False):
    num_anchors = len(anchors)
    # [1, 1, 1, num_anchors, 2]
    anchors_tensor = K.reshape(K.constant(anchors), [1, 1, 1, num_anchors, 2])

    # 获得x,y的网格
    # (13, 13, 1, 2)
    grid_shape = K.shape(feats)[1:3] # height, width
    grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]),
        [1, grid_shape[1], 1, 1])
    grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]),
        [grid_shape[0], 1, 1, 1])
    grid = K.concatenate([grid_x, grid_y])
    grid = K.cast(grid, K.dtype(feats))

    # (batch_size,19,19,3,85)
    feats = K.reshape(feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5])

    # 将预测值调成真实值
    # box_xy对应框的中心点
    # box_wh对应框的宽和高
    box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats))
    box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats))
    box_confidence = K.sigmoid(feats[..., 4:5])
    box_class_probs = K.sigmoid(feats[..., 5:])

    # 在计算loss的时候返回如下参数
    if calc_loss == True:
        return grid, feats, box_xy, box_wh
    return box_xy, box_wh, box_confidence, box_class_probs

#---------------------------------------------------#
#   对box进行调整,使其符合真实图片的样子
#---------------------------------------------------#
def yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape):
    box_yx = box_xy[..., ::-1]
    box_hw = box_wh[..., ::-1]
    
    input_shape = K.cast(input_shape, K.dtype(box_yx))
    image_shape = K.cast(image_shape, K.dtype(box_yx))

    new_shape = K.round(image_shape * K.min(input_shape/image_shape))
    offset = (input_shape-new_shape)/2./input_shape
    scale = input_shape/new_shape

    box_yx = (box_yx - offset) * scale
    box_hw *= scale

    box_mins = box_yx - (box_hw / 2.)
    box_maxes = box_yx + (box_hw / 2.)
    boxes =  K.concatenate([
        box_mins[..., 0:1],  # y_min
        box_mins[..., 1:2],  # x_min
        box_maxes[..., 0:1],  # y_max
        box_maxes[..., 1:2]  # x_max
    ])

    boxes *= K.concatenate([image_shape, image_shape])
    return boxes

#---------------------------------------------------#
#   获取每个box和它的得分
#---------------------------------------------------#
def yolo_boxes_and_scores(feats, anchors, num_classes, input_shape, image_shape):
    # 将预测值调成真实值
    # box_xy对应框的中心点
    # box_wh对应框的宽和高
    # -1,19,19,3,2; -1,19,19,3,2; -1,19,19,3,1; -1,19,19,3,80
    box_xy, box_wh, box_confidence, box_class_probs = yolo_head(feats, anchors, num_classes, input_shape)
    # 将box_xy、和box_wh调节成y_min,y_max,xmin,xmax
    boxes = yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape)
    # 获得得分和box
    boxes = K.reshape(boxes, [-1, 4])
    box_scores = box_confidence * box_class_probs
    box_scores = K.reshape(box_scores, [-1, num_classes])
    return boxes, box_scores

#---------------------------------------------------#
#   图片预测
#---------------------------------------------------#
def yolo_eval(yolo_outputs,
              anchors,
              num_classes,
              image_shape,
              max_boxes=20,
              score_threshold=.6,
              iou_threshold=.5):
    # 获得特征层的数量
    num_layers = len(yolo_outputs)
    # 特征层1对应的anchor是678
    # 特征层2对应的anchor是345
    # 特征层3对应的anchor是012
    anchor_mask = [[6,7,8], [3,4,5], [0,1,2]]
    
    input_shape = K.shape(yolo_outputs[0])[1:3] * 32
    boxes = []
    box_scores = []
    # 对每个特征层进行处理
    for l in range(num_layers):
        _boxes, _box_scores = yolo_boxes_and_scores(yolo_outputs[l], anchors[anchor_mask[l]], num_classes, input_shape, image_shape)
        boxes.append(_boxes)
        box_scores.append(_box_scores)
    # 将每个特征层的结果进行堆叠
    boxes = K.concatenate(boxes, axis=0)
    box_scores = K.concatenate(box_scores, axis=0)

    mask = box_scores >= score_threshold
    max_boxes_tensor = K.constant(max_boxes, dtype='int32')
    boxes_ = []
    scores_ = []
    classes_ = []
    for c in range(num_classes):
        # 取出所有box_scores >= score_threshold的框,和成绩
        class_boxes = tf.boolean_mask(boxes, mask[:, c])
        class_box_scores = tf.boolean_mask(box_scores[:, c], mask[:, c])

        # 非极大抑制,去掉box重合程度高的那一些,获取局部最大值
        nms_index = tf.image.non_max_suppression(
            class_boxes, class_box_scores, max_boxes_tensor, iou_threshold=iou_threshold)

        # 获取非极大抑制后的结果
        # 下列三个分别是
        # 框的位置,得分与种类
        class_boxes = K.gather(class_boxes, nms_index)
        class_box_scores = K.gather(class_box_scores, nms_index)
        classes = K.ones_like(class_box_scores, 'int32') * c
        boxes_.append(class_boxes)
        scores_.append(class_box_scores)
        classes_.append(classes)
    boxes_ = K.concatenate(boxes_, axis=0)
    scores_ = K.concatenate(scores_, axis=0)
    classes_ = K.concatenate(classes_, axis=0)

    return boxes_, scores_, classes_

三、yolov4新增训练技巧

为了使设计的检测器更适合于单 GPU 的训练,我们进行了如下其他设计和改进:
3.1 Mosaic数据增强
论文原话:Mosaic表示一种新的数据增强方法,该方法混合了4个训练图像。 因此,混合了4个不同的上下文,而CutMix仅混合了2个输入图像。 这样可以检测正常上下文之外的对象,增强模型的鲁棒性。。 此外,批量归一化从每层上的4张不同图像计算激活统计信息。 这大大减少了对大批量生产的需求。在这里插入图片描述
这个Mosaic数据增强就是一次读取4张不同的图片,然后进行缩放,调整大小等操作,然后拼接成一幅图。这样能一次训练四个图像,增加了图片内容的丰富性,即丰富检测物体的背景。BN(标准化)计算的时候一下子会计算四张图片的数据
3.2 自对抗训练SAT
SAT是一种新的数据增强技术,该技术分前后两个阶段进行:
第一阶段,神经网络改变原始图像而不是网络权值。通过这种方式,神经网络对自身执行一种对抗性攻击,改变原始图像,从而造成图像上没有目标的假象。
第二阶段,训练神经网络对修改后的图像进行正常的目标检测。
yolov4论文讲解

  • 6
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
需要学习Windows系统YOLOv4的同学请前往《Windows版YOLOv4目标检测实战:原理与源码解析》,课程链接 https://edu.csdn.net/course/detail/29865【为什么要学习这门课】 Linux创始人Linus Torvalds有一句名言:Talk is cheap. Show me the code. 冗谈不够,放码过来!  代码阅读是从基础到提高的必由之路。尤其对深度学习,许多框架隐藏了神经网络底层的实现,只能在上层调包使用,对其内部原理很难认识清晰,不利于进一步优化和创新。YOLOv4是最近推出的基于深度学习的端到端实时目标检测方法。YOLOv4的实现darknet是使用C语言开发的轻型开源深度学习框架,依赖少,可移植性好,可以作为很好的代码阅读案例,让我们深入探究其实现原理。【课程内容与收获】 本课程将解析YOLOv4的实现原理和源码,具体内容包括:- YOLOv4目标检测原理- 神经网络及darknet的C语言实现,尤其是反向传播的梯度求解和误差计算- 代码阅读工具及方法- 深度学习计算的利器:BLAS和GEMM- GPU的CUDA编程方法及在darknet的应用- YOLOv4的程序流程- YOLOv4各层及关键技术的源码解析本课程将提供注释后的darknet的源码程序文件。【相关课程】 除本课程《YOLOv4目标检测:原理与源码解析》外,本人推出了有关YOLOv4目标检测的系列课程,包括:《YOLOv4目标检测实战:训练自己的数据集》《YOLOv4-tiny目标检测实战:训练自己的数据集》《YOLOv4目标检测实战:人脸口罩佩戴检测》《YOLOv4目标检测实战:中国交通标志识别》建议先学习一门YOLOv4实战课程,对YOLOv4的使用方法了解以后再学习本课程。【YOLOv4网络模型架构图】 下图由白勇老师绘制  
Yolov5 是一种基于深度学习的目标检测算法,它是由Ultralytics公司开发的一种轻量级目标检测模型,并且在实时性能和准确性方面表现出色。下面是 Yolov5 工程代码的一些详解: 1. 数据准备:在使用 Yolov5 进行目标检测之前,需要准备好训练和测试数据。数据需要按照一定的格式进行组织,通常是将图片和对应的标注信息放在同一个文件夹中,并生成对应的标签文件。 2. 模型定义:Yolov5 的模型定义主要包括网络结构、损失函数和评估指标等。在 Yolov5 的代码中,网络结构使用了一种叫做CSPDarknet53的骨干网络,并在其上加上了多个特征金字塔层(PANet),用于提取不同尺度的特征。损失函数方面,Yolov5 使用了一种称为YOLOv5 Loss的多任务损失函数,该损失函数包括了目标分类损失、边界框回归损失和目标置信度损失。 3. 数据加载与增强:在 Yolov5 中,数据加载与增强是通过 PyTorch 的数据加载器(DataLoader)和数据增强库(Albumentations)来完成的。数据加载器用于从硬盘上加载数据,并进行一定的预处理操作,如缩放、裁剪等。数据增强库则用于对加载的数据进行一系列的增强操作,如随机翻转、旋转、亮度调整等。 4. 训练过程:Yolov5 的训练过程主要包括模型的初始化、数据加载与增强、前向传播、损失计算、反向传播和参数更新等。训练过程中,模型会根据给定的训练数据进行一定的迭代训练,以优化模型参数,使其能够更好地适应目标检测任务。 5. 推理过程:Yolov5 的推理过程主要是将训练好的模型应用到新的测试数据上,以实现目标检测的功能。推理过程中,模型会将输入图像传入网络中进行前向传播,然后根据网络输出的结果进行后处理,如非极大值抑制(NMS)等,最终得到目标检测结果。 这些是 Yolov5 工程代码的一些主要部分,通过对这些部分的理解和实践,可以更深入地了解 Yolov5 的工作原理和使用方法。当然,这只是一个简单的概述,具体的代码实现还需要参考 Yolov5 的官方代码和文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小样x

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

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

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

打赏作者

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

抵扣说明:

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

余额充值