人工智能学习74-Yolo主干网络—快手视频
人工智能学习75-Yolo主干网络—快手视频
YoloV3是主干网络DarkNet53,主要是使用了残差块Residual block,残差块中包含3个卷积层,YoloV3主干网络使用5个残差块,故此称为DarkNet53网络。
通过不同多尺寸特征预测,YoloV3提取3个特征图,如果分类物体为20类,三个特征图分别为(13,13,75),(26,26,75),(52,52,75)。如果分类物体为80类,三个特征图分别为(13,13,255),(26,26,255),(52,52,255)。如下图:
Darknet类
from functools import wraps
#存在语法错误,使用下面一行代替 by chenqx at 2023/9/19
from keras.initializers import random_normal
#from keras.initializers.initializers_v2 import RandomNormal as random_normal
from keras.layers import (Add, BatchNormalization, Conv2D, LeakyReLU,ZeroPadding2D)
from keras.regularizers import l2
from utils import compose
#---------------------------------------------------#
# 单次卷积
# DarknetConv2D
# random_normal生成随机数,stddev=0.02方差为0.02
#---------------------------------------------------#
@wraps(Conv2D)
def DarknetConv2D(*args, **kwargs):
#构造参数darknet_conv_kwargs
darknet_conv_kwargs = {'kernel_initializer' : random_normal(stddev=0.02)}
#含义:如果kwargs.get('strides')==(2,2)时padding=valid,否则padding=same
#padding=valid p=0
#padding=same strides=1,p=0;strides=3,p=1;strides=5,p=2;依次类推
darknet_conv_kwargs['padding'] = 'valid' if kwargs.get('strides')==(2,2) else 'same'
#更新参数darknet_conv_kwargs
darknet_conv_kwargs.update(kwargs)
#构造二维卷积网络层
return Conv2D(*args, **darknet_conv_kwargs)
#---------------------------------------------------#
# 卷积块 -> 卷积 + 标准化 + 激活函数
# DarknetConv2D + BatchNormalization + LeakyReLU
#---------------------------------------------------#
def DarknetConv2D_BN_Leaky(*args, **kwargs):
#是否使用偏置项
no_bias_kwargs = {'use_bias': False}
no_bias_kwargs.update(kwargs)
#依次调用DarknetConv2D(*args, **no_bias_kwargs)->BatchNormalization()->LeakyReLU(alpha=0.1)
return compose(
DarknetConv2D(*args, **no_bias_kwargs), #进行二维卷积Conv2D
BatchNormalization(), #批量标准化
LeakyReLU(alpha=0.1)) #执行LeakyReLU操作
#---------------------------------------------------------------------#
# 残差结构
# 首先利用ZeroPadding2D和一个步长为2x2的卷积块进行高和宽的压缩
# num_filters输出张量通道数量
# num_blocks残差块数量,对num_blocks进行循环,循环内部是残差结构。
#---------------------------------------------------------------------#
def resblock_body(x, num_filters, num_blocks):
#代表高与宽使用不同填报项 `((top_pad, bottom_pad), (left_pad, right_pad))`
#top_pad=1,bottom_pad=0,left_pad=1,right_pad=0
x = ZeroPadding2D(((1,0),(1,0)))(x)
#(3,3)是卷积核大小,strides=(2,2)是高与宽的步长
x = DarknetConv2D_BN_Leaky(num_filters, (3,3), strides=(2,2))(x)
for i in range(num_blocks): #循环遍历每个残差结构模块
y = DarknetConv2D_BN_Leaky(num_filters//2, (1,1))(x) #卷积核大小(1,1)
y = DarknetConv2D_BN_Leaky(num_filters, (3,3))(y) #卷积核大小(3,3)
x = Add()([x,y]) #残差网络,输出与输入合并
return x
#---------------------------------------------------#
# darknet53 的主体部分
# 输入为一张416x416x3的图片
# 输出为三个有效特征层
#---------------------------------------------------#
def darknet_body(x):
# 计算输出数据大小 shape = (输入大小 - 卷积核大小 + padding)/步长 + 1
# 416,416,3 -> 416,416,32 计算输出大小 (416-3 + 2)/1 + 1 = 416 核大小为(3,3),步长为strides=(1,1)
x = DarknetConv2D_BN_Leaky(32, (3,3))(x) #输出(416,416,32)
# 416,416,32 -> 208,208,64 计算输出大小 (416-3 + 2)/2 + 1 = 208 核大小为(3,3),步长为strides=(2,2)
x = resblock_body(x, 64, 1) #1个残差结构,输出(208,208,64)
# 208,208,64 -> 104,104,128 计算输出大小 (208-3 + 2)/2 + 1 = 104 核大小为(3,3),步长为strides=(2,2)
x = resblock_body(x, 128, 2) #2个残差结构,输出通道数(104,104,128)
# 104,104,128 -> 52,52,256 计算输出大小 (104-3 + 2)/2 + 1 = 52 核大小为(3,3),步长为strides=(2,2)
x = resblock_body(x, 256, 8) #8个残差结构,输出通道数(52,52,256)
feat1 = x #feat1将图像划分为(52,52)网格大小
# 52,52,256 -> 26,26,512 计算输出大小 (52-3 + 2)/2 + 1 = 26 核大小为(3,3),步长为strides=(2,2)
x = resblock_body(x, 512, 8) #8个残差结构,输出通道数(26,26,512)
feat2 = x #feat2将图像划分为(26,26)网格大小
# 26,26,512 -> 13,13,1024 计算输出大小 (26-3 + 2)/2 + 1 = 13 核大小为(3,3),步长为strides=(2,2)
x = resblock_body(x, 1024, 4) #4个残差结构,输出通道数(13,13,1024)
feat3 = x #feat3将图像划分为(13,13)网格大小
return feat1, feat2, feat3 #输出尺寸分别是(52,52,256) (26,26,512) (13,13,1024)
代码解释部分
方法DarknetConv2D_BN_Leaky第24行
padding='valid’是卷积神经网络中不进行边缘填充的操作模式,其特点是允许输出特征图的尺寸自然缩小,适用于无需保持输入输出尺寸一致的场景。
当使用padding='same’时,卷积操作的输出尺寸会通过计算来确保与输入尺寸相同。具体来说,系统会自动计算需要在输入特征图的边缘添加多少零填充,使得卷积操作后的输出尺寸与输入尺寸一致。
当调用DarknetConv2D时,根据卷积核尺寸自行决定padding的大小,
#padding=valid p=0
#padding=same strides=1,p=0;strides=3,p=1;strides=5,p=2;依次类推
方法DarknetConv2D_BN_Leaky第39行
方法compose是utils.py中定义的,是将各层依次连接在一起对数据进行处理。
例程:
def func1(x):
return x+1
def func2(x):
return x*2
from utils import compose
func3 = compose(func1, func2)
print(func3(2))
输出6
方法resblock_body第59行
体现了残差结构,x是为参与卷积之前的数据,y是参与卷积之后的数据,x=Add([x,y])是将卷积前后数据融合,避免特征数据损失。
方法darknet_body第85行
体现Yolo主干网络输出三个不同尺寸特征图。