【yolo目标检测】(1) yolov3,网络结构Darknet53,特征提取

各位同学好,今天和大家分享一下使用Tensorflow2.0进行yolov3目标检测,如何构建Darknet53整体网络结构,如何使用特征金字塔强化特征提取。

1. 网络简介

yolov3借鉴了resnet的残差单元,在加深网络层数提高精度的同时大大降低了计算量。在yolov3中没有池化层和全连接层张量的尺度变换是通过改变卷积核的步长来实现的(也就是通过卷积实现下采样)。例如stride=(2,2),相当于将图像边长缩小一般,整个特征图缩小2^2倍。详细见:目标检测之YOLO v3(附代码详细解析) - 知乎,下图为yolov3的网络结构。


2. 卷积模块

卷积模块一般是指卷积层+批归一化+激活函数,在卷积层函数中,为了方便处理参数,设置*args用于接收卷积核个数和卷积核大小,**kwargs存放用于设置的权重正则化方法,在这里使用 keras.regularizers.l2正则化方法;当卷积层后面接批标准化BN层,就不需要使用偏置,会导致占用内存。

# 卷积
def Conv(x, *args, **kwargs):
    # 列表一个*号: *args代表卷积核数量filter和尺寸kernel_size,
    # 字典两个**号: **kwargs代表卷积部分其他参数,
    
    new_kwargs = {'kernel_regularizer': l2(5e-4), 'use_bias':False} #l2正则化,有BN层不需要偏置
    # new_kwargs是原来的字典参数,传进来新的参数,比如步长,需要更新字典参数
    # 如果步长为2,填充方式需要变成'valid',步长为1填充方式为'same'
    new_kwargs['padding'] = 'valid' if kwargs.get('strides') == (2,2) else 'same' #字典添加元素
    # .get()返回指定key的值
    
    # .update()把字典kwargs的键和值更新到new_kwargs中去
    new_kwargs.update(kwargs) # 把步长更新进去,确定填充方式
      
    # 卷积,使用更新后的参数
    x = layers.Conv2D(*args, **new_kwargs)(x)
    
    return x

# 卷积+BN+激活
def CBL(x, *args, **kwargs):
    # 卷积
    x = Conv(x, *args, **kwargs)
    # 批归一化
    x = layers.BatchNormalization()(x)
    # 激活函数
    x = LeakyReLU(alpha=0.1)(x)
    
    return x


3. 残差模块

残差模块最显著的特点是使用了 short cut 机制(有点类似于电路中的短路机制)来缓解在神经网络中增加深度带来的梯度消失问题,从而使得神经网络变得更容易优化。它通过恒等映射(identity mapping)的方法使得输入和输出之间建立了一条直接的关联通道,从而使得网络集中学习输入和输出之间的残差。

【yolo目标检测】(1) yolov3,网络结构Darknet53,特征提取_第1张图片

使用layers.ZeroPadding2D()方法用0填充图像,size为偶数的图像在使用步长为(2,2)的卷积可以得到偶数size的图像。例如,[416,416,3]的图像0填充后变成[417,417,3],经过卷积核size=(3,3),步长strides=(2,2)的卷积,图像size变化为(417-3+1)/2 = 208

# 0填充+卷积+BN+激活
def PCBL(x, num_filter):
    # 补0,将图片size补成奇数再降维得到偶数size
    x = layers.ZeroPadding2D(((1,0),(1,0)))(x)
    
    # 卷积层,下采样,步长为2
    # 传入的*args有卷积核个数和大小,*kwargs有步长
    x = CBL(x, num_filter, (3,3), strides=(2,2))
    
    return x


# 2次(卷积+BN+激活) + 残差结构,需要把最后的输出与输入相加
def CBLR(x, num_filter):
    # 卷积层
    y = CBL(x, num_filter, (1,1)) # 步长默认为1
    # 卷积层
    y = CBL(y, num_filter*2, (3,3))
    # 残差,add()相应元素相加,h,w,c都不变
    x = layers.Add()([x,y]) #残差块,将输入和输出相加
    
    return x

4. 网络组件

CBL5用于处理各尺度输出后的特征图,如最后一层大尺度目标检测的输出结果为[13,13,1024],经过五次卷积层进一步提取特征,从[13,13,1024]==>[13,13,512]==>[13,13,1024]==>[13,13,512]==>[13,13,1024]==>[13,13,512]

特征提取后的结果,一是用于大尺度目标检测;二是用于上采样函数CBLU,从[13,13,512]==>[26,26,256],长宽都变为原来的2倍,和中尺度输出的特征图像[26,26,512]叠加,即特征融合layers.Concatenate()图像size不变,只增加通道数,得到叠加后的特征图的shape为[26,26,768],也是分别用于中尺度目标检测,和上采样后与小尺度图像叠加。

# 处理输出结果的各尺度图像
def CBL5(x, num_filter):
    # 卷积+BN+激活
    x = CBL(x, num_filter, (1,1))
    x = CBL(x, num_filter*2, (3,3))
    x = CBL(x, num_filter, (1,1))
    x = CBL(x, num_filter*2, (3,3))
    x = CBL(x, num_filter, (1,1))
    
    return x

# 规整通道数得输出
def CBLC(x, num_filter, out_filter):
    # 上升通道数,提特征
    x = CBL(x, num_filter*2, (3,3))
    # 卷积降维,规整通道数,得输出结果的通道数,根据需求设置
    x = Conv(x, out_filter, (1,1))

    return x

# 上采样用于下一个尺度--图像的size变大
def CBLU(x, num_filter):
    # 1*1卷积传递图像信息
    x = CBL(x, num_filter, (1,1))
    # 上采样,宽高size各变成原来的2倍
    x = layers.UpSampling2D(2)(x)
    
    return x

5. 网络主体

darknet53网络结构如下:

使用特征金字塔FPN的思想,采用多尺度来对不同size的目标进行检测,这也对应了网络结构图的三个预测分支(输出)y1,y2,y3(每一个特征图会分配3个先验框,一共9个先验框)。y1,y2和y3的深度都是255,边长的规律是13:26:52,越小的特征图代表下采样的倍数高,而感受野也大,所以13×13的y1特征图对应检测大物体,26×26的y2特征图对应检测中物体,52×52的y3特征图对应检测小物体。

def body(inputs, num_anchors, num_classes): # 预选框数目、类别数目
    
    # 存放最后三尺度的输出结果
    out = []
    
    # CBL层,卷积+BN+激活,给定卷积核32个,size(3,3)
    x = CBL(inputs, 32, (3,3)) 
    
    # CBLR在两两循环下的自身的循环次数
    n = [1, 2, 8, 8, 4]
    
    #(1)网络构建
    # PCBL+CBLR出现连着出现五次
    for i in range(5):
        # PCBL==> zeropadding + conv + BN + 激活
        x = PCBL(x, 2**(6+i)) #卷积核个数为2^(6+i)
        
        # CBLR自身也有一个循环
        for _ in range(n[i]):
            # 残差结构
            x = CBLR(x, 2**(5+i))
        # darknet53构建完成
        
        #(2)输出最后三尺度的图像
        if i in [2,3,4]:
            out.append(x)
    
    #(3)处理每个尺度的图像
    # ==1== 大尺度13*13*1024
    x1 = CBL5(out[2], 512) #[13,13,512]
    
    # 普通卷积得输出结果,将通道数变成输出结果的分类个数
    y1 = CBLC(x1, 512, num_anchors*(num_classes + 5)) # 输出结果,和预选框的数目和识别物体的种类有关
    # 5代表box的坐标(x,y), 宽w, 高h, 置信度c
     
    # 上采样用于中尺度目标检测
    x = CBLU(x1, 256) #[26,26,256]
    
    # 大尺度上采样的返回结果与中尺度图像拼接
    # Concatenate()拼接,h,w不变,通道数增加
    x = layers.Concatenate()([x, out[1]]) #out[1]代表中尺度的图像
    # [26,26,256] + [26,26,512] = [26,26,768]

    # ==2== 中尺度26*26*768
    x2 = CBL5(x, 256) #[26,26,256]
    
    # 卷积得输出结果
    y2 = CBLC(x2, 256, num_anchors*(num_classes + 5)) #[26,26,N]
    
    # 中尺度上采样
    x = CBLU(x2, 128) #[52,52,128]
    # 与小尺度拼接
    x = layers.Concatenate()([x, out[0]])
    # [52,52,128] + [52,52,256] = [52,52,384]
    
    # ==3== 小尺度52*52*384
    x3 = CBL5(x, 128)  # [52,52,128]
    
    y3 = CBLC(x3, 128, num_anchors*(num_classes + 5))  #[52,52,N]
    
    
    # 返回三个尺度信息
    return [y1, y2, y3]

6. 查看网络结构

num_anchors = 3  # 预选框数目
num_classes = 5  # 识别种类,可改

# 输入层
input_tensor = keras.Input(shape=(416,416,3))
# 输出层,预选框数目和,分类数目
output_tensor = body(input_tensor, num_anchors, num_classes)
# 构建网络
model = Model(input_tensor, output_tensor)
# input_tensor是416*416*3的图片,output_tensor是大中小三个尺度的数组

# 查看网络结构
model.summary()
---------------------------------------------------------------
省略 N 层
---------------------------------------------------------------   
 Normalization)                                                                                   
                                                                                                  
 batch_normalization_143 (Batch  (None, 52, 52, 256)  1024       ['conv2d_148[0][0]']             
 Normalization)                                                                                   
                                                                                                  
 leaky_re_lu_129 (LeakyReLU)    (None, 13, 13, 1024  0           ['batch_normalization_129[0][0]']
                                )                                                                 
                                                                                                  
 leaky_re_lu_136 (LeakyReLU)    (None, 26, 26, 512)  0           ['batch_normalization_136[0][0]']
                                                                                                  
 leaky_re_lu_143 (LeakyReLU)    (None, 52, 52, 256)  0           ['batch_normalization_143[0][0]']
                                                                                                  
 conv2d_133 (Conv2D)            (None, 13, 13, 30)   30720       ['leaky_re_lu_129[0][0]']        
                                                                                                  
 conv2d_141 (Conv2D)            (None, 26, 26, 30)   15360       ['leaky_re_lu_136[0][0]']        
                                                                                                  
 conv2d_149 (Conv2D)            (None, 52, 52, 30)   7680        ['leaky_re_lu_143[0][0]']        
                                                                                                  
==================================================================================================
Total params: 61,597,792
Trainable params: 61,545,184
Non-trainable params: 52,608
__________________________________________________________________________________________________

你可能感兴趣的:(yolo目标检测,目标检测,深度学习,神经网络,tensorflow,python)