Vannesa's Studio

利用Keras实现Yolov3

Word count: 2.9kReading time: 13 min
2020/09/21 Share

Yolov3-Keras代码学习

我们项目的基础代码是利用Keras实现的yolov3算法。

Keras

什么是Keras?

  • 官方给出的标题简洁明了,the Python deep learning API
  • Keras是一个基于Theano/TensorFlow的一个深度学习框架,是一个高模块化的神经网络库,支持GPU和CPU,纯Python编写的。
  • 利用这些深度学习框架,可以更好的帮助我们实现模型的代码。
  • 异构计算在人工智能领域起着很大的作用。我们应该知道GPU可以辅助CPU,提高性能。在计算时可能多次需要在GPU和CPU之间切换,有时候还需要手动求出一些表达式的导数,很麻烦,所以我们利用深度学习框架帮助我们完成一些事情。

为什么用Keras?

  • 高层神经网络API,支持快速实验,能够把你的idea迅速转换为结果
  • 简易和快速的原型设计(keras具有高度模块化,极简,和可扩充特性)
  • 支持CNN和RNN,或二者的结合
  • 无缝CPU和GPU切换

a)用户友好: Keras提供一致而简洁的API
b)模块性:网络层、损失函数、优化器、初始化策略、激活函数、正则化方法都是独立的模块,你可以使用它们来构建自己的模型
c)易扩展性:添加新模块超级容易,只需要仿照现有的模块编写新的类或函数即可
d)与Python协作:Keras没有单独的模型配置文件类型(作为对比,caffe有),模型由python代码描述,使其更紧凑和更易debug,并提供了扩展的便利性。

Keras的模块结构

使用Keras搭建一个神经网络

Yolov3-Keras

相关信息

性能情况

训练数据集 权值文件名称 测试数据集 输入图片大小 mAP 0.5:0.95 mAP 0.5
COCO-Train2017 yolo_weights.h5 COCO-Val2017 416x416 38.1 66.8
  • 权重文件保存的就是训练好的网络各层的权值,也就是通过训练集训练出来的。训练好之后,应用时只要加载权值就可以,不再需要训练集了。

所需环境

tensorflow-gpu==1.13.1
keras==2.1.5

  • 配置环境就不用说了吧

文件下载

训练所需的yolo_weights.h5:
百度链接: https://pan.baidu.com/s/1izPebZ6PVU25q1we1UgSGQ
提取码: tbj3

预测步骤

使用预训练权重

a、下载完库后解压,在百度网盘下载yolo_weights.h5,放入model_data,运行predict.py,输入

1
>img/street.jpg

可完成预测。
b、利用video.py可进行摄像头检测。

首先先进入你的虚拟环境中,然后运行python文件

缺少什么模块这种问题一般都是某个组件没有安装

图片检测:输入照片位置,使用相对路径;实时检测直接运行就可以。

使用自己训练的权重

a、按照训练步骤训练。
b、在yolo.py文件里面,在如下部分修改model_path和classes_path使其对应训练好的文件;model_path对应logs文件夹下面的权值文件,classes_path是model_path对应分的类

1
2
3
4
5
6
7
8
9
10
_defaults = {
"model_path": 'model_data/yolo_weights.h5',
"anchors_path": 'model_data/yolo_anchors.txt',
"classes_path": 'model_data/coco_classes.txt,
"score" : 0.5,
"iou" : 0.3,
# 显存比较小可以使用416x416
# 显存比较大可以使用608x608
"model_image_size" : (416, 416)
}

c、运行predict.py,输入

1
img/street.jpg

可完成预测。
d、利用video.py可进行摄像头检测。

训练步骤

1、本文使用VOC格式进行训练。
2、训练前将标签文件放在VOCdevkit文件夹下的VOC2007文件夹下的Annotation中。
3、训练前将图片文件放在VOCdevkit文件夹下的VOC2007文件夹下的JPEGImages中。
4、在训练前利用voc2yolo3.py文件生成对应的txt。
5、再运行根目录下的voc_annotation.py,运行前需要将classes改成你自己的classes。注意不要使用中文标签,文件夹中不要有空格!

1
classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]

6、此时会生成对应的2007_train.txt,每一行对应其图片位置及其真实框的位置
7、在训练前需要务必在model_data下新建一个txt文档,文档中输入需要分的类,在train.py中将classes_path指向该文件,示例如下:

1
classes_path = 'model_data/new_classes.txt'

model_data/new_classes.txt文件内容为:

1
2
3
cat
dog
...

8、运行train.py即可开始训练。

Yolov3-Keras代码解析

网络模型代码 (nets文件夹)

darknet53.py

  • 这个文件定义了骨干网络darknet的相关内容
  • DarknetConv2D函数定义了一个单次卷积操作的相关参数:如'kernel_regularizer': l2(5e-4)对于权值正则化的设定、对于卷积操作的判断(valid/same)
  • DarknetConv2D_BN_Leaky函数定义了本网络经常使用的最小单元DBL(也就是一个单次卷积操作+批标准化+leakyrelu激活函数),设定了相关的参数
  • resblock_body函数定义了一个残差块的结构是什么样的,每经过一次残差块操作,featuremap的scale缩小一倍
  • darknet_body函数定义了骨干网络的全貌,由最小的模块不断嵌套拼接而成的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
from functools import wraps
from keras.layers import Conv2D, Add, ZeroPadding2D, UpSampling2D, Concatenate, MaxPooling2D
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.normalization import BatchNormalization
from keras.regularizers import l2 #设计模型时需要使用Regularization(正则化)技巧来减少模型的过拟合效果
from utils.utils import compose


#--------------------------------------------------#
# DarknetConv2D单次卷积
#--------------------------------------------------#
@wraps(Conv2D)
def DarknetConv2D(*args, **kwargs):
darknet_conv_kwargs = {'kernel_regularizer': l2(5e-4)} # kernel_regularizer旧版本中成为weight_regularizer 对权值进行正则化,限制权值使其不至于过大 正则化系数为(0.0005)
# https://blog.csdn.net/xovee/article/details/92794763
darknet_conv_kwargs['padding'] = 'valid' if kwargs.get('strides')==(2,2) else 'same'
# 判断卷积类型:有步长数值的是valid卷积,没有的是same卷积
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)
return compose(
DarknetConv2D(*args, **no_bias_kwargs),
BatchNormalization(),
LeakyReLU(alpha=0.1))
# 批标准化,将分散的数据统一,让机器学习更容易学习到数据之中的规律
# 激活函数,alpha是参数,当输入数据为负值时,alpha的值会线性影响输出的值

#---------------------------------------------------#
# 残差块
# 每用一次残差块,feature map的scale缩小一倍
#---------------------------------------------------#
def resblock_body(x, num_filters, num_blocks):
# x:输入 num_filters:输出通道 num_blocks:重复块的次数
x = ZeroPadding2D(((1,0),(1,0)))(x)
x = DarknetConv2D_BN_Leaky(num_filters, (3,3), strides=(2,2))(x)#此处使用了stride=2降低分辨率(不用pooling)
#重复构建残差网络,这个算基本的block
for i in range(num_blocks):
y = DarknetConv2D_BN_Leaky(num_filters//2, (1,1))(x) #使用1x1的conv先将通道先降//2
y = DarknetConv2D_BN_Leaky(num_filters, (3,3))(y) #使用3x3将通道后拉升回去
x = Add()([x,y])
return x

#---------------------------------------------------#
# darknet53 的主体部分
# backbone(darknet_body)负责提取特征,输出feature map
#---------------------------------------------------#
def darknet_body(x):
x = DarknetConv2D_BN_Leaky(32, (3,3))(x)
x = resblock_body(x, 64, 1)
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
```

### yolo3.py

``` python
from functools import wraps
import numpy as np
import tensorflow as tf
from keras import backend as K
from keras.layers import Conv2D, Add, ZeroPadding2D, UpSampling2D, Concatenate, MaxPooling2D
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.normalization import BatchNormalization
from keras.models import Model
from keras.regularizers import l2
from nets.darknet53 import darknet_body
from utils.utils import compose


#--------------------------------------------------#
# 单次卷积
#--------------------------------------------------#
@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 + LeakyReLU
#---------------------------------------------------#
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))

#---------------------------------------------------#
# 特征层->最后的输出
#---------------------------------------------------#
def make_last_layers(x, num_filters, out_filters):
# 五次卷积
x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x)
x = DarknetConv2D_BN_Leaky(num_filters*2, (3,3))(x)
x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x)
x = DarknetConv2D_BN_Leaky(num_filters*2, (3,3))(x)
x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x)

# 将最后的通道数调整为outfilter
y = DarknetConv2D_BN_Leaky(num_filters*2, (3,3))(x)
y = DarknetConv2D(out_filters, (1,1))(y)

return x, y

#---------------------------------------------------#
# yolo网络全貌
#---------------------------------------------------#
def yolo_body(inputs, num_anchors, num_classes):
# 输入数据, 多少个anchor, 分类类别

# 生成darknet53的主干模型 (backbone)
feat1,feat2,feat3 = darknet_body(inputs)
darknet = Model(inputs, feat3)

# 第一个特征层
# y1=(batch_size,13,13,3,85)
x, y1 = make_last_layers(darknet.output, 512, num_anchors*(num_classes+5))

x = compose(
DarknetConv2D_BN_Leaky(256, (1,1)),
UpSampling2D(2))(x) ##UpSampling2D,对卷积结果进行上采样从而将特征图放大
x = Concatenate()([x,feat2])
# 第二个特征层
# y2=(batch_size,26,26,3,85)
x, y2 = make_last_layers(x, 256, num_anchors*(num_classes+5))

x = compose(
DarknetConv2D_BN_Leaky(128, (1,1)),
UpSampling2D(2))(x)。
x = Concatenate()([x,feat1])
# 第三个特征层
# y3=(batch_size,52,52,3,85)
x, y3 = make_last_layers(x, 128, num_anchors*(num_classes+5))

return Model(inputs, [y1,y2,y3])

#---------------------------------------------------#
# 将预测值的每个特征层调成真实值
#---------------------------------------------------#
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,13,13,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,13,13,3,2; -1,13,13,3,2; -1,13,13,3,1; -1,13,13,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_

Reference

  1. github yolov3-keras实现代码 https://github.com/bubbliiiing/yolo3-keras
  2. Keras基础篇 https://www.cnblogs.com/lc1217/p/7132364.html
  3. 序贯模型 https://blog.csdn.net/zjw642337320/article/details/81204560
  4. yolo系列之yolo v3【深度解析】https://blog.csdn.net/leviopku/article/details/82660381
  5. YOLOv3原理代码赏析https://zhuanlan.zhihu.com/p/58856405
  6. keras-yolov3代码解读https://blog.csdn.net/qq_25800609/article/details/87880651
  7. KERAS-YOLOV3的代码走读https://blog.csdn.net/yangchengtest/article/details/80664415
CATALOG
  1. 1. Yolov3-Keras代码学习
  2. 2. Keras
    1. 2.1. 什么是Keras?
    2. 2.2. 为什么用Keras?
    3. 2.3. Keras的模块结构
    4. 2.4. 使用Keras搭建一个神经网络
  3. 3. Yolov3-Keras
    1. 3.1. 相关信息
      1. 3.1.1. 性能情况
      2. 3.1.2. 所需环境
      3. 3.1.3. 文件下载
    2. 3.2. 预测步骤
      1. 3.2.1. 使用预训练权重
      2. 3.2.2. 使用自己训练的权重
      3. 3.2.3. 训练步骤
  4. 4. Yolov3-Keras代码解析
    1. 4.1. 网络模型代码 (nets文件夹)
      1. 4.1.1. darknet53.py
  5. 5. Reference