目标检测之yolo源码解析
关于yolo目标检测的原理请参考前面一篇文章:目标检测之YOLO算法详解
在讲解源码之前,我们需要做一些准备工作:
- 下载源码,本文所使用的yolo源码来源于网址:https://github.com/hizhangp/yolo_tensorflow
- 下载训练所使用的数据集,我们仍然使用以VOC 2012数据集为例,下载地址为:http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar。
- yolo源码所在目录下,创建一个目录data,然后在data里面创建一个pascal_voc目录,用来保存与VOC 2012数据集相关的数据,我们把下载好的数据集解压到该目录下,如下图所示,其中VOCdevkit为数据集解压得到的文件,剩余三个文件夹我们先不用理会,后面会详细介绍
- 下载与训练模型,即YOLO_small文件,我们把下载好之后的文件解压放在weights文件夹下面。下载链接:https://drive.google.com/file/d/0B5aC8pI-akZUNVFZMmhmcVRpbTA/view?usp=sharing,需要FQ才能下载,可以使用自-由-门FQ软件。
-
根据自己的需求修改配置文件yolo/config.py。
- 运行train.py文件,开始训练。
- 运行test.py文件,开始测试。
二 yolo代码文件结构
如果你按照上面我说的步骤配置好文件之后,源代码结构就会如下图所示:
我们来粗略的介绍一下每个文件的功能:
- data文件夹,上面已经说过了,存放数据集以及训练时生成的模型,缓存文件。
- test文件夹,用来存放测试时用到的图片。
- utils文件夹,包含两个文件一个是pascal_voc.py,主要用来获取训练集图片文件,以及生成对应的标签文件,为yolo网络训练做准备。另一个文件是timer.py用来计时。
- yolo文件夹,也包含两个文件,config.py包含yolo网络的配置参数,yolo_net.py文件包含yolo网络的结构。
- train.py文件用来训练yolo网络。
- test.py文件用来测试yolo网络。
三 config.py文件讲解
我们先从配置文件说起,代码如下:
# -*- coding: utf-8 -*-
"""
Created on Tue Jun 12 12:08:15 2018
@author: lenovo
"""
'''
配置参数
'''
import os
#
# 数据集路径,和模型检查点文件路径
#
DATA_PATH = 'data' #所有数据所在的根目录
PASCAL_PATH = os.path.join(DATA_PATH, 'pascal_voc') #VOC2012数据集所在的目录
CACHE_PATH = os.path.join(PASCAL_PATH, 'cache') #保存生成的数据集标签缓冲文件所在文件夹
OUTPUT_DIR = os.path.join(PASCAL_PATH, 'output') #保存生成的网络模型和日志文件所在的文件夹
WEIGHTS_DIR = os.path.join(PASCAL_PATH, 'weights') #检查点文件所在的目录
#WEIGHTS_FILE = None
WEIGHTS_FILE = os.path.join(WEIGHTS_DIR, 'YOLO_small.ckpt')
#VOC 2012数据集类别名
CLASSES = ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus',
'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse',
'motorbike', 'person', 'pottedplant', 'sheep', 'sofa',
'train', 'tvmonitor']
#使用水平镜像,扩大一倍数据集
FLIPPED = True
'''
网络模型参数
'''
#图片大小
IMAGE_SIZE = 448
#单元格大小S 一共有CELL_SIZExCELL_SIZE个单元格
CELL_SIZE = 7
#每个单元格边界框的个数B
BOXES_PER_CELL = 2
#泄露修正线性激活函数 系数
ALPHA = 0.1
#控制台输出信息
DISP_CONSOLE = False
#损失函数 的权重设置
OBJECT_SCALE = 1.0 #有目标时,置信度权重
NOOBJECT_SCALE = 1.0 #没有目标时,置信度权重
CLASS_SCALE = 2.0 #类别权重
COORD_SCALE = 5.0 #边界框权重
'''
训练参数设置
'''
GPU = ''
#学习率
LEARNING_RATE = 0.0001
#退化学习率衰减步数
DECAY_STEPS = 30000
#衰减率
DECAY_RATE = 0.1
STAIRCASE = True
#批量大小
BATCH_SIZE = 45
#最大迭代次数
MAX_ITER = 15000
#日志文件保存间隔步
SUMMARY_ITER = 10
#模型保存间隔步
SAVE_ITER = 500
'''
测试时的相关参数
'''
#格子有目标的置信度阈值
THRESHOLD = 0.2
#非极大值抑制 IoU阈值
IOU_THRESHOLD = 0.5
各个参数我已经在上面注释了,下面就不在重复了。下面我们来介绍yolo网络的构建。
四 yolo.py文件讲解
yolo网络的建立是通过yolo文件夹中的yolo_net.py文件的代码实现的,yolo_net.py文件定义了YOLONet类,该类包含了网路初始化(__init__()),建立网络(build_networks)和loss函数(loss_layer())等方法。
# -*- coding: utf-8 -*-
"""
Created on Tue Jun 12 12:08:15 2018
@author: lenovo
"""
'''
定义YOLO网络模型
https://blog.youkuaiyun.com/qq_34784753/article/details/78803423
https://blog.youkuaiyun.com/qq1483661204/article/details/79681926
'''
import numpy as np
import tensorflow as tf
import yolo.config as cfg
import logging
import sys
slim = tf.contrib.slim
#配置logging
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
level=logging.INFO,
stream=sys.stdout)
class YOLONet(object):
1、网络参数初始化
网络的所有初始化参数包含于__init__()方法中。
def __init__(self, is_training=True):
'''
构造函数
利用 cfg 文件对网络参数进行初始化,同时定义网络的输入和输出 size 等信息,
其中 offset 的作用应该是一个定长的偏移
boundery1和boundery2 作用是在输出中确定每种信息的长度(如类别,置信度等)。
其中 boundery1 指的是对于所有的 cell 的类别的预测的张量维度,所以是 self.cell_size * self.cell_size * self.num_class
boundery2 指的是在类别之后每个cell 所对应的 bounding boxes 的数量的总和,所以是self.boundary1 + self.cell_size * self.cell_size * self.boxes_per_cell
args:
is_training:训练?
'''
#VOC 2012数据集类别名
self.classes = cfg.CLASSES
#类别个数C 20
self.num_class = len(self.classes)
#网络输入图像大小448, 448 x 448
self.image_size = cfg.IMAGE_SIZE
#单元格大小S=7 将图像分为SxS的格子
self.cell_size = cfg.CELL_SIZE
#每个网格边界框的个数B=2
self.boxes_per_cell = cfg.BOXES_PER_CELL
#网络输出的大小 S*S*(B*5 + C) = 1470
self.output_size = (self.cell_size * self.cell_size) *\
(self.num_class + self.boxes_per_cell * 5)
#图片的缩放比例 64
self.scale = 1.0 * self.image_size / self.cell_size
'''#将网络输出分离为类别和置信度以及边界框的大小,输出维度为7*7*20 + 7*7*2 + 7*7*2*4=1470'''
#7*7*20
self.boundary1 = self.cell_size * self.cell_size * self.num_class
#7*7*20+7*7*2
self.boundary2 = self.boundary1 +\
self.cell_size * self.cell_size * self.boxes_per_cell
#代价函数 权重
self.object_scale = cfg.OBJECT_SCALE #1
self.noobject_scale = cfg.NOOBJECT_SCALE #1
self.class_scale = cfg.CLASS_SCALE #2.0
self.coord_scale = cfg.COORD_SCALE #5.0
#学习率0.0001
self.learning_rate = cfg.LEARNING_RATE
#批大小 45
self.batch_size = cfg.BATCH_SIZE
#泄露修正线性激活函数 系数0.1
self.alpha = cfg.ALPHA
#偏置 形状[7,7,2]
self.offset = np.transpose(np.reshape(np.array(
[np.arange(self.cell_size)] * self.cell_size * self.boxes_per_cell),
(self.boxes_per_cell, self.cell_size, self.cell_size)), (1, 2, 0))
#输入图片占位符 [NONE,image_size,image_size,3]
self.images = tf.placeholder(
tf.float32, [None, self.image_size, self.image_size, 3],
name='images')
#构建网络 获取YOLO网络的输出(不经过激活函数的输出) 形状[None,1470]
self.logits = self.build_network(
self.images, num_outputs=self.output_size, alpha=self.alpha,
is_training=is_training)
if is_training:
#设置标签占位符 [None,S,S,5+C] 即[None,7,7,25]
self.labels = tf.placeholder(
tf.float32,
[None, self.cell_size, self.cell_size, 5 + self.num_class])
#设置损失函数
self.loss_layer(self.logits, self.labels)
#加入权重正则化之后的损失函数
self.total_loss = tf.losses.get_total_loss()
#将损失以标量形式显示,该变量命名为total_loss
tf.summary.scalar('total_loss', self.total_loss)
2、构建网络
网络的建立是通过build_network()函数实现的,网络由卷积层,池化层和全连接层组成,网络的输入维度是[None,448,448,3],输出维度为[None,1470]。
def build_network(self,
images,
num_outputs,
alpha,
keep_prob=0.5,
is_training=True,
scope='yolo'):
'''
构建YOLO网络
args:
images:输入图片占位符 [None,image_size,image_size,3] 这里是[None,448,448,3]
num_outputs:标量,网络输出节点数 1470
alpha:泄露修正线性激活函数 系数0.1
keep_prob:弃权 保留率
is_training:训练?
scope:命名空间名
return:
返回网络最后一层,激活函数处理之前的值 形状[None,1470]
'''
#定义变量命名空间
with tf.variable_scope(scope):
#定义共享参数 使用l2正则化
with slim.arg_scope(
[slim.conv2d, slim.fully_connected],
activation_fn=leaky_relu(alpha),
weights_regularizer=slim.l2_regularizer(0.0005),
weights_initializer=tf.truncated_normal_initializer(0.0, 0.01)
):
logging.info('image shape{0}'.format(images.shape))
#pad_1 填充 454x454x3
net = tf.pad(
images, np.array([[0, 0], [3, 3], [3, 3], [0, 0]]),
name='pad_1')
logging.info('Layer pad_1 {0}'.format(net.shape))
#卷积层conv_2 s=2 (n-f+1)/s向上取整 224x224x64
net = slim.conv2d(
net, 64, 7, 2, padding='VALID', scope='conv_2')
logging.info('Layer conv_2 {0}'.format(net.shape))
#池化层pool_3 112x112x64
net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_3')
logging.info('Layer pool_3 {0}'.format(net.shape))
#卷积层conv_4、3x3x192 s=1 n/s向上取整 112x112x192
net = slim.conv2d(net, 192, 3, scope='conv_4')
logging.info('Layer conv_4 {0}'.format(net.shape))
#池化层pool_5 56x56x192
net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_5')
logging.info('Layer pool_5 {0}'.format(net.shape))
#卷积层conv_6、1x1x128 s=1 n/s向上取整 56x56x128
net = slim.conv2d(net, 128, 1, scope='conv_6')
logging.info('Layer conv_6 {0}'.format(net.shape))
#卷积层conv_7、3x3x256 s=1 n/s向上取整 56x56x256
net = slim.conv2d(net, 256, 3, scope='conv_7')
logging.info('Layer conv_7 {0}'.format(net.shape))
#卷积层conv_8、1x1x256 s=1 n/s向上取整 56x56x256
net = slim.conv2d(net, 256, 1, scope='conv_8')
logging.info('Layer conv_8 {0}'.format(net.shape))
#卷积层conv_9、3x3x512 s=1 n/s向上取整 56x56x512
net = slim.conv2d(net, 512, 3, scope='conv_9')
logging.info('Layer conv_9 {0}'.format(net.shape))
#池化层pool_10 28x28x512
net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_10')
logging.info('Layer pool_10 {0}'.format(net.shape))
#卷积层conv_11、1x1x256 s=1 n/s向上取整 28x28x256
net = slim.conv2d(net, 256, 1, scope='conv_11')
logging.info('Layer conv_11 {0}'.format(net.shape))
#卷积层conv_12、3x3x512 s=1 n/s向上取整 28x28x512
net = slim.conv2d(net, 512, 3, scope='conv_12')
logging.info('Layer conv_12 {0}'.format(net.shape))
#卷积层conv_13、1x1x256 s=1 n/s向上取整 28x28x256
net = slim.conv2d(net, 256, 1, scope='conv_13')
logging.info('Layer conv_13 {0}'.format(net.shape))
#卷积层conv_14、3x3x512 s=1 n/s向上取整 28x28x512
net = slim.conv2d(net, 512, 3, scope='conv_14')
logging.info('Layer conv_14 {0}'.format(net.shape))
#卷积层conv_15、1x1x256 s=1 n/s向上取整 28x28x256
net = slim.conv2d(net, 256, 1, scope='conv_15')
logging.info('Layer conv_15 {0}'.format(net.shape))
#卷积层conv_16、3x3x512 s=1 n/s向上取整 28x28x512
net = slim.conv2d(net, 512, 3, scope='conv_16')
logging.info('Layer