目标检测 YOLO 篇(2)
我们先来计划一下分享内容,接下来我们分享就进入了边读源码边分析过程,大概就是这个计划,今天我们来分享网络模型,先说原理然后通过代码把网络结构实现
- 网络模型
- 数据采集
- 损失函数
- 训练模型
- 预测
YOLOv3
我们对图片分别按 、 和 进行分割得到图片网格,这样做好处是我们使用不同尺寸网格来实现对图像不同大小物体都可以进行目标检测,不会丢掉小的物体,例如 的网格能够识别更多物体,而且物体的体积可以更小。所以我们用 、 和 网格分别识别大、中和小物体。 这样做避免小物体在多次卷积之后物体信息丢失。每个网格点负责其右下角物体检测。
如果物体的中心点落在某一个网格中,那么该网格就具有该物体位置和种类信息。 这句话很重要,希望大家能够理解。
YOLOv3 实现
我们重点就是分享 YOLOv3 是如何实现目标检测,我们根据 YOLOv3 模型结构图,一步一步来分析 YOLOv3 的目标检测算法。这是一个非常优秀的目标检测算法。为什么说优秀呢,一方面是因为 YOLOv3 表现优秀,而且算法结构还不是那么复杂。
只有大家将这张图吃透,随后才能够轻松地用代码来将其一步一步实现,不然就是只能大概读懂源码,至于每一块为什么要这样做,还是 consufing。
主干网络
左边的部分是 YOLOv3 算法的主干特征提取网络,其功能就是提取特征。
- 输入(Input) 是 的图片
- 接下就是一系列特征提取过程,也就是一系列卷积的过程。在卷积过程图片高和宽不断地被压缩,而通道不断扩张,这也就是下采样的过程。通过卷积我们就得到反映的图片一系列特征层。
预测网络
- 我们截取最后 3 个特征层,分别是
-
: 进行 5 次卷积,完成 5 次卷积后分两个方向进行前进,一个是现实为黄色块也就是对图片进行分类预测和回归预测。其实就是进行两次卷积得到 ,我们输出进行分解为 。然后我们来一个一个看这些数字都代表什么, 表示将图片划分为 个网格,然后每一个网格上存在 3 个先验框,这些先验框就是我们预先标记在图片上,然后判断这些先验框是否包含物体,如果包含物体我们就判断这些物体种类。还需要对先验框的中心进行调整将其调整到正确位置上。那么 20,1 和 4 有分别代表什么 1 代表先验框置信度也就是表示先验框中是否有物体,4 表示先验框的位置,20 是因为我们使用 VOC 数据集有 20 种类的数据集,这个可以根据自己的数据集而定。
然后这个特征层还有一个去处就是对特征层进行进行上采样(也就是增加图片尺寸缩减通道数的过程)然后和 进行堆叠,所谓堆叠就是将他们通道维度上进行合并。这样就是将再次利用用于在 网格上提取特征过程。 -
和 堆叠后也需要进行 5 次卷积。得到 过程和上面类似,这里不做过多的赘述了。然后同样这个特征也会通过一次上采样后和
-
: 进行 5 次卷积,完成 5 次卷积后分两个方向进行前进,一个是现实为黄色块也就是对图片进行分类预测和回归预测。其实就是进行两次卷积得到 ,我们输出进行分解为 。然后我们来一个一个看这些数字都代表什么, 表示将图片划分为 个网格,然后每一个网格上存在 3 个先验框,这些先验框就是我们预先标记在图片上,然后判断这些先验框是否包含物体,如果包含物体我们就判断这些物体种类。还需要对先验框的中心进行调整将其调整到正确位置上。那么 20,1 和 4 有分别代表什么 1 代表先验框置信度也就是表示先验框中是否有物体,4 表示先验框的位置,20 是因为我们使用 VOC 数据集有 20 种类的数据集,这个可以根据自己的数据集而定。
实现主体网络
from keras.models import Model
from keras.layers import Input
model_input = Input(shape(416,416,3))
model = Modle(model_input,model_output)
- 输入是 的图片
- 输出两个表示不同大小(维度)的数组
model_input = Input(shape(416,416,3))
model_output = body(model_input)
model = Modle(model_input,model_output)
接下来工作就是搭建 yolov3 的主题网络,也称称为 backbone 网络,用于提取不同尺寸大小的特征图,Darknet53.要写好代码我们就需要熟悉 yolo 网络结构,然后按照结构图一层一层的实现
def body(inputs,num_anchors,num_classes):
我们都已经知道主体网络用于提取特征,body 方法用于构建主体网络,接收 3 个参数分别
- inputs: 输入,通常是一张 的图片
- num_anchors: 每一个网格生成的多少个预选框
- num_classes: 表示分类种类数量
return [y1,y2,y3]
那么输出就是 3 种不同尺寸的
我们对照图来一层一层写代码来实现设计图中的层或块,其实我们实现深度学习算法通常做法是打开一个设计好网络结构图,然后对照图一行一行地实现。图中的乘 1 ,乘 2 表示该块重复次数,
块名称 | 数量 | filters | size | output |
---|---|---|---|---|
CBL | 1 | 32 | ||
PCBL | 1 | 32 | ||
CBLR | 1 | 32 | ||
1 | 64 | |||
PCBL | 1 | 128 | ||
CBLR | 1 | 64 | ||
8 | 256 | |||
PCBL | 1 | 256 | ||
CBLR | 1 | 128 | ||
8 | 256 | |||
PCBL | 1 | 512 | ||
CBLR | 1 | 256 | ||
8 | 512 | |||
PCBL | 1 | 1024 | ||
CBLR | 1 | 512 | ||
4 | 1024 |
def body(inputs,num_anchors,num_classes):
out = []
x = CBL(inputs,32,(3,3))
n = [1,2,8,8,4]
for i in range(5):
x = PCBL(x,2**(6+i))
for _ in range(n[i]):
x = CBLR(x,2**(5+i))
if i in [2,3,4]:
out.append(x)
通过 for 循环我们将重复部分提炼出来, 然后我们图中每一个重复块前还有一个
在 CBL 中使用到的卷积 conv
定义卷积 conv 方法
卷积用到的参数有字典参数和列表参数
- 列表用一个星号
- 字典用两个星号
def conv(x):
def conv(*args,**kwargs):
new_kwargs = {'kernel_regularizer':l2(5e-4)}
new_kwargs['padding'] = 'valid' if kwargs.get('strides') == (2,2) else 'same'
new_kwargs.update(kwargs)
return Conv2D(*args,**new_kwargs)
定义 CBL 层(块)
CBL 是我们网络结构的基础,也是出现最多结构,其结构包括以下部分
- Conv 表示卷积层
- BN 表示归一化
- LeakyRelu 激活函数
def CBL(x,*args,**kwargs):
new_kwargs ={ 'use_bias': False}
new_kwargs.update(kwargs)
x = conv(*args,**new_kwargs)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.1)(x)
这里用到了 L2 正则,L2 正则是控制参数
我们通过卷积的步长(stride)来决定卷积,其实这些都卷积的基础知识。我们
引入依赖
from keras.regularizers import l2
from keras.layers import Conv2D
定义 PCBL 块
用于在完成每一个块对卷积结果进行一次下采样,功能等于一个池化层将通过 stride=(2,2) 将图片长宽缩小为原来的
def PCBL(x,num_filters):
x = ZeroPadding2D(((1,0),(1,0)))(x)
x = CBL(x,num_filters,(3,3),strides=(2,2))
return x
CBLR 块
CBLR 就是我们在图中重复结构,显示一个 卷积,用于对通道数减半的卷积,接下拉就是 卷积层同时将通道数翻倍。随后一个将输入和输出在通道上进行叠加通道数翻倍。
def CBLR(x,num_filters):
y = CBL(x,num_filters,(1,1))
y = CBL(y,num_filters*2,(3,3))
x = Add()([x,y])
return x
检测网络
接下来将主干网络提取特征通过处理输出我们之前提到的 3 中不同尺寸的矩阵
x1 = CBL5(out[2],512)
y1 = CBLC(x1,512,num_anchors*(num_classes+5))
x = CBLU(x1,256)
x = Concatenate()([x,out[1]])
x2 = CBL5(x,256)
y2 = CBLC(x2, 256, num_anchors * (num_classes + 5))
x = CBLU(x2,128)
x = Concatenate()([x, out[0]])
x3 = CBL5(x,128)
y3 = CBLC(x3, 128, num_anchors * (num_classes + 5))
return [y1,y2,y3]
CBL5 层
这个解构就是
def CBL5(x,num_filters):
x = CBL(x, num_filters, (1,1))
x = CBL(x, num_filters*2, (3,3))
x = CBL(x, num_filters, (1,1))
x = CBL(x, num_filters*2, (3,3))
x = CBL(x, num_filters, (1,1))
return x
CBLC
def CBLC(x,num_filters,out_filters):
x = CBL(x, num_filters * 2, (3, 3))
x = conv(out_filters,(1,1))(x)
return x
CBLU 层
在 CBL 层上添加一个上采样层,经过 CBLU 后宽度和长度翻倍便于参加网格较大尺寸的层输出。例如 会经过 CBLU 扩大为。
def CBLU(x, num_filters):
x = CBL(x, num_filters, (1, 1))
x = UpSampling2D(2)(x)
return x
共有 0 条评论