Tensorflow2.0—YOLO V4-tiny网络原理及代码解析(一)- 特征提取网络

代码链接:yolov4-tiny-tf2-master
(密码:yum7)
先来看看YOLO V4-tiny的特征提取网络是长什么样子的。个人认为,大体的框架与YOLO V3的相似,只不过在里面加了3个tricks,让网络更加容易训练
在这里插入图片描述

图片转载:https://blog.csdn.net/weixin_44791964/article/details/107302710

一般来说,YOLO V4-tiny与YOLO V4差别在于tiny版本最后只有两个yolo head。同时,一般的input的大小为(416,416,3)和(608,608,3)两种,这里我就用的是416。


从图中,可以看到backbone一共有两个大模块:DarknetConv2D_BN_Leaky,resblock_body

1. DarknetConv2D_BN_Leaky模块

在这里插入图片描述
该模块,其实就是三个基本操作的堆叠:DarknetConv2D,BN,Leaky。代码为:

def DarknetConv2D_BN_Leaky(*args, **kwargs):
    no_bias_kwargs = {'use_bias': False}
    no_bias_kwargs.update(kwargs)

    return compose( 
        DarknetConv2D(*args, **no_bias_kwargs),
        BatchNormalization(),
        LeakyReLU(alpha=0.1))

其中,对DarknetConv2D单独进行了封装:

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

在该代码中,这里使用的是参数字典的方式进行传入,还是很有新意的。

2.resblock_body模块

该模块主要是用了两个点:yolov3中的残差网络和CSP。
在这里插入图片描述
在图中,可以看到:
在yolov3中,只是进行了单纯的残差融合。
在yolov4中,首先将base layer按照channels维度进行split成了两个部分,然后一个part保留,另外一个part进行残差网络,最后将part2的最终结果与part1进行再一次的融合。

def resblock_body(x, num_filters):
    # 利用一个3x3卷积进行特征整合
    x = DarknetConv2D_BN_Leaky(num_filters, (3,3))(x)  # 104,104,64
    # 引出一个大的残差边route
    route = x
    # 对特征层的通道进行分割,取第二部分作为主干部分。
    x = Lambda(route_group,arguments={'groups':2, 'group_id':1})(x)  # 104,104,32
    # 对主干部分进行3x3卷积
    x = DarknetConv2D_BN_Leaky(int(num_filters/2), (3,3))(x)
    # 引出一个小的残差边route_1
    route_1 = x
    # 对第主干部分进行3x3卷积
    x = DarknetConv2D_BN_Leaky(int(num_filters/2), (3,3))(x)
    # 主干部分与残差部分进行相接
    x = Concatenate()([x, route_1])  # 104,104,64

    # 对相接后的结果进行1x1卷积
    x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x)
    feat = x
    x = Concatenate()([route, x])  # 104,104,128

    # 利用最大池化进行高和宽的压缩
    x = MaxPooling2D(pool_size=[2,2],)(x)  # 52,52,128

    return x, feat

这是我按照代码,画的示意图:
在这里插入图片描述
说白了,其实内部就是进行两次残差特征融合。


下面是整体的网络框架代码:

def darknet_body(x):
    # 首先利用两次步长为2x2的3x3卷积进行高和宽的压缩
    # 416,416,3 -> 208,208,32 -> 104,104,64
    x = ZeroPadding2D(((1,0),(1,0)))(x)
    x = DarknetConv2D_BN_Leaky(32, (3,3), strides=(2,2))(x)
    x = ZeroPadding2D(((1,0),(1,0)))(x)
    x = DarknetConv2D_BN_Leaky(64, (3,3), strides=(2,2))(x)
    
    # 104,104,64 -> 52,52,128
    x, _ = resblock_body(x,num_filters = 64)
    # 52,52,128 -> 26,26,256
    x, _ = resblock_body(x,num_filters = 128)
    # 26,26,256 -> x为13,13,512
    #           -> feat1为26,26,256
    x, feat1 = resblock_body(x,num_filters = 256)
    # 13,13,512 -> 13,13,512
    x = DarknetConv2D_BN_Leaky(512, (3,3))(x)

    feat2 = x
    return feat1, feat2

这里,我有一个疑问:在图中明明是在第二个resblock_body之后取x和经过三次resblock_body并进行DarknetConv2D_BN_Leaky得到的x,但是在代码中却表现为:取第三个resblock_bodymok之后的feat和经过三次resblock_body并进行DarknetConv2D_BN_Leaky得到的x???????


最终,得到的feat1, feat2为:

feat1:Tensor("leaky_re_lu_13/LeakyRelu:0", shape=(None, 26, 26, 256), dtype=float32) 
feat2:Tensor("leaky_re_lu_14/LeakyRelu:0", shape=(None, 13, 13, 512), dtype=float32)

然后,进行FPN(特征金字塔),代码对应于图中已经分别对应标注了:
在这里插入图片描述
最终,得到了两个yolo head:P4_output和P5_output,大小分别为(26,26,255)和(13,13,255)。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐