yolov3+tensorflow+keras实现吸烟的训练全流程及识别检测
弈休丶 2019-12-30 23:29:54 1591 收藏 19
分类专栏: 基于yolov3+tensorflow+keras实现吸烟的训练全流程
版权
一.前言
近期,在研究人工智能机器视觉领域,拜读了深度学习相关资料,在练手期间比较了各前沿的网络架构,个人认为基于darknet53网络结构的yolov3以及retinanet的faster rcnn最合适深度学习工程落地的技术选型。以下是整理的对yolov3的认知解析,同时有个基于人员吸烟检测识别的小工程练手,以望沟通学习交流。后期会继续更新对faster rcnn的认知解析。
二.yolov3理解
you only look once.采用的是多尺度预测,类似FPN;更好的基础分类网络(类ResNet)和分类器。yolov3使用逻辑回归预测每个边界框(bounding box)的对象分数。如果先前的边界框比之前的任何其他边界框重叠ground truth对象,则该值应该为1。如果以前的边界框不是最好的,但是确实将ground truth对象重叠了一定的阈值以上,我们会忽略这个预测。yolov3只为给每个groun truth对象分配一个边界框,如果之前的边界框未分配给grounding box对象,则不会对坐标或类别预测造成损失。yolo3在训练过程中,使用二元交叉熵损失来进行类别预测。yolo3创新地使用了金字塔网络,是端到端,输入图像,一次性输出每个栅格预测的一种或多种物体。每个格子可以预测B个bounding box,但是最终只选择IOU最高的bounding box作为物体检测输出,即每个格子最多只预测出一个问题。当物体占画面比例较小,如图像中包含牲畜或鸟群时,每个格子包含多个物体,但只能检测出其中一个。
1. 分类器:
YOLOv3不使用Softmax对每一个框进行分类,而使用多个logistic分类器,因为Softmax不适用于多标签分类,用独立的多个logistic分类器准确率也不会下降。分类损失采用binary cross-entropy loss.
2. 多尺度预测
每种尺度预测3个box,anchor的设计方式仍然适用聚类,得到9个聚类中心,将其按照大小均分给3种尺度。尺度1:在基础网络之后添加一些卷积层再输出box信息;尺度2:在尺度1中的倒数第二层卷积层上采样(×2)再与最后一个16×16大小的特征图相加,再次通过多个卷积后输出box信息。相比尺度1变大两倍;尺度3:与尺度2类似,使用了32×32大小的特征图。
3.基础网络
基础网络采用Darknet-53(53个卷积层),仿RestNet,与ResNet-10或ResNet-152正确率接近,采用2562563作为输入。基础网络如下:
4.YOLO3算法的基本思想
首先通过特征提取网络对输入图像提取特征,得到一定大小的特征图,比如13×13(相当于416416图片大小 ),然后将输入图像分成13 ×13个grid cells,接着如果GT中某个目标的中心坐标落在哪个grid cell中,那么就由该grid cell来预测该目标。每个grid cell都会预测3固定数量的边界框(YOLO v1中是2个,YOLO v2中是5个,YOLO v3中是3个,这几个边界框的初始大小是不同的)
预测得到的输出特征图有两个维度是提取到的特征的维度,比如13 × 13,还有一个维度(深度)是 B ×(5+C),注:YOLO v1中是(B×5+C),其中B表示每个grid cell预测的边界框的数量(比如YOLO v1中是2个,YOLO v2中是5个,YOLO v3中是3个); C表示边界框的类别数(没有背景类,所以对于VOC数据集是20),5表示4个坐标信息和一个目标性得分(objectness score)
5.类别预测
大多数分类器假设输出标签是互斥的。 如果输出是互斥的目标类别,则确实如此。 因此,YOLO应用softmax函数将得分转换为总和为1的概率。 而YOLOv3使用多标签分类。 例如,输出标签可以是“行人”和“儿童”,它们不是非排他性的。 (现在输出的总和可以大于1)
YOLOv3用多个独立的逻辑(logistic)分类器替换softmax函数,以计算输入属于特定标签的可能性。在计算分类损失时,YOLOv3对每个标签使用二元交叉熵损失。 这也避免使用softmax函数而降低了计算复杂度。
三.yolov3对比情况
算法很快,因为我们把目标检测问题看做一个回归问题,在测试时,我们在整个图片上运行我们的神经网络来进行目标检测
在检测过程中,因为是在整个图片上运行网络,和滑动窗口方法和区域提议方法不同,这些方法都是以图片的局部作为输入,即已经划分好的可能存在目标的区域;而yolo则是近以整张图片作为输入,此yolo在检测物体时能很好的利用上下文信息,从而不容易在背景上预测出错误的物体信息。
yolo可以学到物体的泛化特征,当yolo在自然图像上做训练,在艺术品上做测试时,YOLO表现的性能比DPM、R-CNN等物体检测系统要好,因为yolo可以学习到高度泛化的特征,从而迁移到其他领域。
在输入 320 × 320 的图片后,YOLOv3 能在 22 毫秒内完成处理,并取得 28.2 mAP 的检测精准度,它的精准度和 SSD 相当,但是速度要快 3 倍。当我们用老的 .5 IOU mAP 检测指标时,YOLOv3 的精准度也是相当好的。在 Titan X 环境下,YOLOv3 在 51 毫秒内实现了 57.9 AP50 的精准度,和 RetinaNet 在 198 毫秒内的 57.5 AP50 相当,但是 YOLOv3 速度要快 3.8 倍。
区域建议方法将分类器限制在特定区域。YOLO在预测边界框时访问整个图像,YOLO在背景区域显示的假阳性更少。
YOLO每个网格单元检测一个对象。它加强了预测的空间多样性。
Darknet53新网络比Darknet-19功能强大,也比ResNet-101或ResNet-152更有效,以下是一些ImageNet结果
就COCO的mAP指标而言,yolo3与ssd及faster rcnn比较如下
yolo3与Faster R-CNN、ResNet、SSD等训练算法比较,yolo3的速度与精确度远远大于他们,具体如下:
四.练手工程
1. 基本环境、工具准备:
win10(x64)
显卡gtx1060
python3.6
cuda_9.0.176_win10
cudnn-9.0-windows10-x64-v7
tensorflow-gpu1.7.0
pycharm
labelImg
voc2007数据集
2. 数据集说明:
PASCAL VOC challenge: voc挑战在2005年至2012年间展开,该数据集中有20个分类,该数据集包含11530张用于训练和验证的图像,以下是数据集中20个分类:人、鸟、猫、牛、狗、马、羊、飞机、自行车、船、巴士、汽车、摩托车、火车、瓶、椅子、餐桌、盆栽植物、沙发、电视/监视器,平均每个图像有2.4个目标。下载链接:http://host.robots.ox.ac.uk/pascal/VOC/voc2012/
ImageNet数据集:ImageNet拥有分类、定位和检测任务评估的数据。与分类数据相似,定位任务有1000个类别。正确率是根据Top5检测结果计算出来的,对200个检测问题有470000个图像,平均每个图像有1.1个目标。下载链接:http://image-net.org/download-images
COCO:MS coco的全称是Microsoft Common Objects in Context,起源于微软与2014年出资标注的Microsoft COCO数据集,与ImageNet竞赛一样,被视为是计算机视觉领域最受关注和权威的比赛之一。在ImageNet竞赛停办后,COCO竞赛就成为是当前目标识别、检测等领域的一个最权威、最重要的标杆,也是目前该领域在国际上唯一能汇集Google、微软、Facebook以及国内外众多顶尖院校和优秀创新企业共同参与的大赛。COO数据集包含20万个图像,80个类别中有超过50万个目标标注,他是广泛公开的目标检测数据库,平均每个图像的目标数为7.2下载链接:http://cocodataset.org/ 类别包含:person,bicycle,car,motorbike,aeroplane,bus,train,truck,boat,traffic light,fire hydrant,stop sign,parking meter,bench,bird,cat,dog,horse,sheep,cow,elephant,bear,zebra,giraffe,backpack,umbrella,handbag,tie,suitcase,frisbee,skis,snowboard,sports ball,kite,baseball bat,baseball glove,skateboard,surfboard,tennis racket,bottle,wine glass,cup,fork,knife,spoon,bowl,banana,apple,sandwich,orange,broccoli,carrot,hot dog,pizza,donut,cake,chair,sofa,pottedplant,bed,diningtable,toilet,tvmonitor,laptop,mouse,remote,keyboard,cell phone,microwave,oven,toaster,sink,refrigerator,book,clock,vase,scissors,teddy bear,hair drier,toothbrush
本文练手工具是基于VOC2007格式,通过爬虫工具建立的自己的数据集
3. 训练前准备:
建立VOC2007数据集文件夹(ImageSets下还需建立Main文件集),如下:
import os
from config import cfg
def mkdir(path):
# 去除首位空格
path = path.strip()
# 去除尾部 \ 符号
path = path.rstrip("\\")
# 判断路径是否存在
# 存在 True
# 不存在 False
isExists = os.path.exists(path)
# 判断结果
if not isExists:
# 如果不存在则创建目录
# 创建目录操作函数
os.makedirs(path)
print(path + ' 创建成功')
return True
else:
# 如果目录存在则不创建,并提示目录已存在
print(path + ' 目录已存在')
return False
if __name__=='__main__':
rootPath = cfg.ROOT.PATH
mkdir(os.path.join(rootPath, 'Annotations'))
mkdir(os.path.join(rootPath,'ImageSets'))
mkdir(os.path.join(rootPath,'JPEGImages'))
mkdir(os.path.join(rootPath,'ImageSets/Main'))
把所有的图片都复制到JPEGImages里面
利用LabelImg生成每张图片的配置文件
划分训练集、验证集、测试集:过去,人们运用机器学习传统的方法,一般将训练集和测试集划分为7:3,若有验证集,则划分为6:2:2这样划分确实很科学(万级别以下),但到了大数据时代,数据量徒增为百万级别,此时我们不需要那么多的验证集和训练集。这时只需要拉出来1W条当验证集,1W条来当测试集,就能很好的工作了,,甚至可以达到99.5:0.3:0.2。(训练集是用来训练模型的,通过尝试不同的方法和思路使用训练集来训练不同的模型,再通过验证集使用交叉验证来挑选最优的模型,通过不断的迭代来改善模型在验证集上的性能,最后再通过测试集来评估模型的性能。如果数据集划分的好,可以提高模型的应用速度。如果划分的不好则会大大影响模型的应用的部署,甚至可能会使得我们之后所做的工作功亏一篑。),这里因为数据集不多,我们采用9:1开展训练工作
在Imagesets根据标注结果xml生成yolo3所需的train.txt,val.txt,test.txt,trainval.txt 保存至ImageSets/Main
import os
import random
from config import cfg
# # trainval集占整个数据集的百分比,剩下的就是test集所占的百分比
trainval_percent = cfg.ROOT.TRAIN_VAL_PERCENT
# train集占trainval集的百分比, 剩下的就是val集所占的百分比
train_percent = cfg.ROOT.TRAIN_PERCENT
rootPath = cfg.ROOT.PATH
xmlfilepath = os.path.join(rootPath, 'Annotations')
txtsavepath = 'ImageSets'
total_xml = os.listdir(os.path.join(rootPath, xmlfilepath))
# 总数据集个数
num = len(total_xml)
list = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(list, tv)
train = random.sample(trainval, tr)
#用来训练和验证的图片文件的文件名列表
ftrainval = open(os.path.join(rootPath, 'ImageSets/Main/trainval.txt'), 'w')
#用来测试的图片文件的文件名列表
ftest = open(os.path.join(rootPath, 'ImageSets/Main/test.txt'), 'w')
#是用来训练的图片文件的文件名列表
ftrain = open(os.path.join(rootPath, 'ImageSets/Main/train.txt'), 'w')
#是用来验证的图片文件的文件名列表
fval = open(os.path.join(rootPath, 'ImageSets/Main/val.txt'), 'w')
for i in list:
name = total_xml[i][:-4] + '\n'
if i in trainval:
# 写到训练和验证集
ftrainval.write(name)
if i in train:
# 在训练集里的写到测试集里
ftest.write(name)
else:
# 不在训练集里,写到验证集
fval.write(name)
else:
# 写到训练集
ftrain.write(name)
ftrainval.close()
ftrain.close()
fval.close()
ftest.close()
修改yolov3源码的voc_annotation.py文件,执行生产新的三个txt文件:test.txt,train.txt,val.txt
使用k-means聚类算法生成对应自己样本的anchor box尺寸
修改参数文件yolo3.cfg,找到[yolo]节点,修改以下三个地方,共计三个节点: fiters: 3*(5+len(classes))、classes:len(classes)=1(因为只需要识别烟)、random:默认1,显卡内存小改为0
新增model_data下的文件,放入你的类别,coco、voc这两个文件都需要放入,修改类别名称为smoking
4. 修改代码,开始训练。
以下代码是基于原有权重的.weights文件继续训练,需要执行python convert.py -w yolov3.cfg model/yolov3.weights model/yolo_weights.h5 转成keras可用.h5权重文件,然后在源码的train.py训练。若需全新训练的代码,可在我的github下载train.py。(batch_size设为8,epoch设为5000,连续运行18个小时左右,loss将为0.01以下即可。)
import numpy as np
import keras.backend as K
from keras.layers import Input, Lambda
from keras.models import Model
from keras.optimizers import Adam
from keras.callbacks import TensorBoard, ModelCheckpoint, ReduceLROnPlateau, EarlyStopping
import os
import sys
sys.path.append('..')
from yolov3.model import preprocess_true_boxes, yolo_body, tiny_yolo_body, yolo_loss
from yolov3.utils import get_random_data
from config import cfg
def _main():
annotation_path = os.path.join(cfg.ROOT.PATH, 'train.txt')
log_dir = os.path.join(cfg.ROOT.PATH, 'logs/000/')
classes_path = os.path.join(cfg.ROOT.PATH, 'Model/voc_classes.txt')
anchors_path = os.path.join(cfg.ROOT.PATH, 'Model/yolo_anchors.txt')
class_names = get_classes(classes_path)
num_classes = len(class_names)
anchors = get_anchors(anchors_path)
input_shape = cfg.ROOT.INPUT_SHAPE # multiple of 32, hw
model = create_model(input_shape, anchors, num_classes,
freeze_body=2, weights_path=os.path.join(cfg.ROOT.PATH, cfg.ROOT.PRE_TRAIN_MODEL)) # make sure you know what you freeze
logging = TensorBoard(log_dir=log_dir)
checkpoint = ModelCheckpoint(log_dir + 'ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5',
monitor='val_loss', save_weights_only=True, save_best_only=True, period=3)
# reduce_lr:当评价指标不在提升时,减少学习率,每次减少10%,当验证损失值,持续3次未减少时,则终止训练。
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=3, verbose=1)
# early_stopping:当验证集损失值,连续增加小于0时,持续10个epoch,则终止训练。
# monitor:监控数据的类型,支持acc、val_acc、loss、val_loss等;
# min_delta:停止阈值,与mode参数配合,支持增加或下降;
# mode:min是最少,max是最多,auto是自动,与min_delta配合;
# patience:达到阈值之后,能够容忍的epoch数,避免停止在抖动中;
# verbose:日志的繁杂程度,值越大,输出的信息越多。
# min_delta和patience需要相互配合,避免模型停止在抖动的过程中。min_delta降低,patience减少;而min_delta增加,
# 则patience增加。
early_stopping = EarlyStopping(monitor='val_loss', min_delta=0, patience=10, verbose=1)
# 训练和验证的比例
val_split = 0.1
with open(annotation_path) as f:
lines = f.readlines()
np.random.seed(10101)
np.random.shuffle(lines)
np.random.seed(None)
num_val = int(len(lines)*val_split)
num_train = len(lines) - num_val
'''
把目标当成一个输入,构成多输入模型,把loss写成一个层,作为最后的输出,搭建模型的时候,
就只需要将模型的output定义为loss,而compile的时候,
直接将loss设置为y_pred(因为模型的输出就是loss,所以y_pred就是loss),
无视y_true,训练的时候,y_true随便扔一个符合形状的数组进去就行了。
'''
# Train with frozen layers first, to get a stable loss.
# Adjust num epochs to your dataset. This step is enough to obtain a not bad model.
if True:
model.compile(optimizer=Adam(lr=1e-3), loss={
# use custom yolo_loss Lambda layer.
'yolo_loss': lambda y_true, y_pred: y_pred})
batch_size = 16
print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
model.fit_generator(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
steps_per_epoch=max(1, num_train//batch_size),
validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),
validation_steps=max(1, num_val//batch_size),
epochs=50,
initial_epoch=0,
callbacks=[logging, checkpoint])
# 存储最终的参数,再训练过程中,通过回调存储
model.save_weights(log_dir + 'trained_weights_stage_1.h5')
# Unfreeze and continue training, to fine-tune.
# Train longer if the result is not good.
# 全部训练
if True:
for i in range(len(model.layers)):
model.layers[i].trainable = True
model.compile(optimizer=Adam(lr=1e-4), loss={'yolo_loss': lambda y_true, y_pred: y_pred}) # recompile to apply the change
print('Unfreeze all of the layers.')
batch_size = 32 # note that more GPU memory is required after unfreezing the body
print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
'''
在训练中,模型调用fit_generator方法,按批次创建数据,输入模型,进行训练。其中,数据生成器wrapper是data_generator_
wrapper,用于验证数据格式,最终调用data_generator
annotation_lines:标注数据的行,每行数据包含图片路径,和框的位置信息;
batch_size:批次数,每批生成的数据个数;
input_shape:图像输入尺寸,如(416, 416);
anchors:anchor box列表,9个宽高值;
num_classes:类别的数量;
'''
model.fit_generator(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
steps_per_epoch=max(1, num_train//batch_size),
validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),
validation_steps=max(1, num_val//batch_size),
epochs=100,
initial_epoch=50,
callbacks=[logging, checkpoint, reduce_lr, early_stopping])
model.save_weights(os.path.join(cfg.ROOT.PATH, cfg.ROOT.MODEL_RSLT_NAME))
# Further training if needed.
def get_classes(classes_path):
'''loads the classes'''
with open(classes_path) as f:
class_names = f.readlines()
class_names = [c.strip() for c in class_names]
return class_names
def get_anchors(anchors_path):
'''loads the anchors from a file'''
with open(anchors_path) as f:
anchors = f.readline()
anchors = [float(x) for x in anchors.split(',')]
return np.array(anchors).reshape(-1, 2)
def create_model(input_shape, anchors, num_classes, load_pretrained=True, freeze_body=2,
weights_path=os.path.join(cfg.ROOT.PATH, cfg.ROOT.PRE_TRAIN_MODEL)):
'''
create the training model
input_shape:输入图片的尺寸,默认是(416, 416);
anchors:默认的9种anchor box,结构是(9, 2);
num_classes:类别个数,在创建网络时,只需类别数即可。在网络中,类别值按0~n排列,同时,输入数据的类别也是用索引表示;
load_pretrained:是否使用预训练权重。预训练权重,既可以产生更好的效果,也可以加快模型的训练速度;
freeze_body:冻结模式,1或2。其中,1是冻结DarkNet53网络中的层,2是只保留最后3个1x1的卷积层,其余层全部冻结;
weights_path:预训练权重的读取路径;
'''
K.clear_session() # 清除session
h, w = input_shape # 尺寸
image_input = Input(shape=(w, h, 3)) # 图片输入格式
num_anchors = len(anchors) # anchor数量
# YOLO的三种尺度,每个尺度的anchor数,类别数+边框4个+置信度1
y_true = [Input(shape=(h//{0:32, 1:16, 2:8}[l], w//{0:32, 1:16, 2:8}[l], \
num_anchors//3, num_classes+5)) for l in range(3)]
model_body = yolo_body(image_input, num_anchors//3, num_classes)
print('Create YOLOv3 model with {} anchors and {} classes.'.format(num_anchors, num_classes))
# 加载预训练模型
if load_pretrained:
model_body.load_weights(weights_path, by_name=True, skip_mismatch=True)
print('Load weights {}.'.format(weights_path))
if freeze_body in [1, 2]:
# Freeze darknet53 body or freeze all but 3 output layers.
num = (185, len(model_body.layers)-3)[freeze_body-1]
for i in range(num): model_body.layers[i].trainable = False
print('Freeze the first {} layers of total {} layers.'.format(num, len(model_body.layers)))
# 构建 yolo_loss
# model_body: [(?, 13, 13, 18), (?, 26, 26, 18), (?, 52, 52, 18)]
# y_true: [(?, 13, 13, 18), (?, 26, 26, 18), (?, 52, 52, 18)]
model_loss = Lambda(yolo_loss, output_shape=(1,), name='yolo_loss',
arguments={'anchors': anchors, 'num_classes': num_classes, 'ignore_thresh': 0.5})(
[*model_body.output, *y_true])
model = Model([model_body.input, *y_true], model_loss)
return model
def data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes):
'''
data generator for fit_generator
annotation_lines: 所有的图片名称
batch_size:每批图片的大小
input_shape: 图片的输入尺寸
anchors: 大小
num_classes: 类别数
'''
n = len(annotation_lines)
i = 0
while True:
image_data = []
box_data = []
for b in range(batch_size):
if i==0:
# 随机排列图片顺序
np.random.shuffle(annotation_lines)
# image_data: (16, 416, 416, 3)
# box_data: (16, 20, 5) # 每个图片最多含有20个框
# 获取图片和盒子
image, box = get_random_data(annotation_lines[i], input_shape, random=True)
# 获取真实的数据根据输入的尺寸对原始数据进行缩放处理得到input_shape大小的数据图片,
# 随机进行图片的翻转,标记数据数据也根据比例改变
# 添加图片
image_data.append(image)
# 添加盒子
box_data.append(box)
i = (i+1) % n
image_data = np.array(image_data)
box_data = np.array(box_data)
# y_true是3个预测特征的列表
y_true = preprocess_true_boxes(box_data, input_shape, anchors, num_classes)
# y_true的第0和1位是中心点xy,范围是(0~13/26/52),第2和3位是宽高wh,范围是0~1,
# 第4位是置信度1或0,第5~n位是类别为1其余为0。
# [(16, 13, 13, 3, 6), (16, 26, 26, 3, 6), (16, 52, 52, 3, 6)]
yield [image_data, *y_true], np.zeros(batch_size)
def data_generator_wrapper(annotation_lines, batch_size, input_shape, anchors, num_classes):
"""
用于条件检查
"""
# 标注图片的行数
n = len(annotation_lines)
if n==0 or batch_size<=0: return None
return data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes)
if __name__ == '__main__':
_main()
5. 训练过程可视化分析:
训练过程可视化查看(命令行直接输入tensorboard --host=? --port=?–logdir=?,命令行会返回查看地址,在谷歌浏览器输入即可查看)
6. 模型效果测试:
import colorsys
import os
from timeit import default_timer as timer
import numpy as np
from keras import backend as K
from keras.models import load_model
from keras.layers import Input
from PIL import Image, ImageFont, ImageDraw
from yolov3.model import yolo_eval, yolo_body, tiny_yolo_body
from yolov3.utils import letterbox_image
import os
from keras.utils import multi_gpu_model
rootPath = "train/smoking"
class YOLO(object):
_defaults = {
"model_path": os.path.join(rootPath, 'Model/smoking.h5'),
"anchors_path": os.path.join(rootPath, 'Model/yolo_anchors.txt'),
"classes_path": os.path.join(rootPath, 'Model/coco_classes.txt'),
"score" : 0.3,
"iou" : 0.45,
"model_image_size" : (416, 416),
"gpu_num" : 1,
}
@classmethod
def get_defaults(cls, n):
if n in cls._defaults:
return cls._defaults[n]
else:
return "Unrecognized attribute name '" + n + "'"
def __init__(self, **kwargs):
self.__dict__.update(self._defaults) # set up default values
self.__dict__.update(kwargs) # and update with user overrides
self.class_names = self._get_class()
self.anchors = self._get_anchors()
self.sess = K.get_session()
self.boxes, self.scores, self.classes = self.generate()
def _get_class(self):
classes_path = os.path.expanduser(self.classes_path)
with open(classes_path) as f:
class_names = f.readlines()
class_names = [c.strip() for c in class_names]
return class_names
def _get_anchors(self):
anchors_path = os.path.expanduser(self.anchors_path)
with open(anchors_path) as f:
anchors = f.readline()
anchors = [float(x) for x in anchors.split(',')]
return np.array(anchors).reshape(-1, 2)
def generate(self):
model_path = os.path.expanduser(self.model_path)
assert model_path.endswith('.h5'), 'Keras model or weights must be a .h5 file.'
# Load model, or construct model and load weights.
num_anchors = len(self.anchors)
num_classes = len(self.class_names)
is_tiny_version = num_anchors==6 # default setting
try:
self.yolo_model = load_model(model_path, compile=False)
except:
self.yolo_model = tiny_yolo_body(Input(shape=(None,None,3)), num_anchors//2, num_classes) \
if is_tiny_version else yolo_body(Input(shape=(None,None,3)), num_anchors//3, num_classes)
self.yolo_model.load_weights(self.model_path) # make sure model, anchors and classes match
else:
assert self.yolo_model.layers[-1].output_shape[-1] == \
num_anchors/len(self.yolo_model.output) * (num_classes + 5), \
'Mismatch between model and given anchor and class sizes'
print('{} model, anchors, and classes loaded.'.format(model_path))
# Generate colors for drawing bounding boxes.
hsv_tuples = [(x / len(self.class_names), 1., 1.)
for x in range(len(self.class_names))]
self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
self.colors = list(
map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)),
self.colors))
np.random.seed(10101) # Fixed seed for consistent colors across runs.
np.random.shuffle(self.colors) # Shuffle colors to decorrelate adjacent classes.
np.random.seed(None) # Reset seed to default.
# Generate output tensor targets for filtered bounding boxes.
self.input_image_shape = K.placeholder(shape=(2, ))
if self.gpu_num>=2:
self.yolo_model = multi_gpu_model(self.yolo_model, gpus=self.gpu_num)
boxes, scores, classes = yolo_eval(self.yolo_model.output, self.anchors,
len(self.class_names), self.input_image_shape,
score_threshold=self.score, iou_threshold=self.iou)
return boxes, scores, classes
def detect_image(self, image):
start = timer()
if self.model_image_size != (None, None):
assert self.model_image_size[0]%32 == 0, 'Multiples of 32 required'
assert self.model_image_size[1]%32 == 0, 'Multiples of 32 required'
boxed_image = letterbox_image(image, tuple(reversed(self.model_image_size)))
else:
new_image_size = (image.width - (image.width % 32),
image.height - (image.height % 32))
boxed_image = letterbox_image(image, new_image_size)
image_data = np.array(boxed_image, dtype='float32')
print(image_data.shape)
image_data /= 255.
image_data = np.expand_dims(image_data, 0) # Add batch dimension.
out_boxes, out_scores, out_classes = self.sess.run(
[self.boxes, self.scores, self.classes],
feed_dict={
self.yolo_model.input: image_data,
self.input_image_shape: [image.size[1], image.size[0]],
K.learning_phase(): 0
})
print('Found {} boxes for {}'.format(len(out_boxes), 'img'))
font = ImageFont.truetype(font='../resources/font/FiraMono-Medium.otf',
size=np.floor(3e-2 * image.size[1] + 0.5).astype('int32'))
thickness = (image.size[0] + image.size[1]) // 300
for i, c in reversed(list(enumerate(out_classes))):
predicted_class = self.class_names[c]
box = out_boxes[i]
score = out_scores[i]
label = '{} {:.2f}'.format(predicted_class, score)
draw = ImageDraw.Draw(image)
label_size = draw.textsize(label, font)
top, left, bottom, right = box
top = max(0, np.floor(top + 0.5).astype('int32'))
left = max(0, np.floor(left + 0.5).astype('int32'))
bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))
right = min(image.size[0], np.floor(right + 0.5).astype('int32'))
print(label, (left, top), (right, bottom))
if top - label_size[1] >= 0:
text_origin = np.array([left, top - label_size[1]])
else:
text_origin = np.array([left, top + 1])
# My kingdom for a good redistributable image drawing library.
for i in range(thickness):
draw.rectangle(
[left + i, top + i, right - i, bottom - i],
outline=self.colors[c])
draw.rectangle(
[tuple(text_origin), tuple(text_origin + label_size)],
fill=self.colors[c])
draw.text(text_origin, label, fill=(0, 0, 0), font=font)
del draw
end = timer()
print(end - start)
return image
def close_session(self):
self.sess.close()
def detect_video(yolo, video_path, output_path=""):
import cv2
vid = cv2.VideoCapture(video_path)
if not vid.isOpened():
raise IOError("Couldn't open webcam or video")
video_FourCC = int(vid.get(cv2.CAP_PROP_FOURCC))
video_fps = vid.get(cv2.CAP_PROP_FPS)
video_size = (int(vid.get(cv2.CAP_PROP_FRAME_WIDTH)),
int(vid.get(cv2.CAP_PROP_FRAME_HEIGHT)))
isOutput = True if output_path != "" else False
if isOutput:
print("!!! TYPE:", type(output_path), type(video_FourCC), type(video_fps), type(video_size))
out = cv2.VideoWriter(output_path, video_FourCC, video_fps, video_size)
accum_time = 0
curr_fps = 0
fps = "FPS: ??"
prev_time = timer()
while True:
return_value, frame = vid.read()
image = Image.fromarray(frame)
image = yolo.detect_image(image)
result = np.asarray(image)
curr_time = timer()
exec_time = curr_time - prev_time
prev_time = curr_time
accum_time = accum_time + exec_time
curr_fps = curr_fps + 1
if accum_time > 1:
accum_time = accum_time - 1
fps = "FPS: " + str(curr_fps)
curr_fps = 0
cv2.putText(result, text=fps, org=(3, 15), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
fontScale=0.50, color=(255, 0, 0), thickness=2)
cv2.namedWindow("result", cv2.WINDOW_NORMAL)
cv2.imshow("result", result)
if isOutput:
out.write(result)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
yolo.close_session()
def detect_img(yolo):
#img = input('Input image filename:')
img = 'testImg/7_17.jpg'
try:
image = Image.open(img)
except:
print('Open Error! Try again!')
else:
r_image = yolo.detect_image(image)
r_image.show()
yolo.close_session()
if __name__ == '__main__':
#detect_img(YOLO())
detect_video(YOLO(), 'smoking_detect.mp4')
五.如何提高检测识别效果及效率
在.cfg文件中设置flag random=1, 它将通过不同分辨率来训练yolo以提高精度
分类识别用不同的识别检测引擎(如:猫狗分两个模型)
提高.cfg文件中网络的分辨率(例如h,w=608,或者任意32的倍数),这样可以提高精度
确保数据集中每个类都带有标签,并且确保标签正确
优化标注内容(避免漏标、错标),去除数据集中重复的图片或者多余的标注文件
丰富数据集(达到10W+)
完善网络模型结构
优化特征提取、比对算法,优化边界识别算法
识别图片预处理:去噪、加强、抖动、杂线干
针对不同应用场景,训练不同场景应用的模型,以降低识别速度换取提高识别率
训练过程加大learning_rate、识别过程略调scores、iou
对于要检测的每一个对象,训练数据集中必须至少有一个类似的对象,其形状、对象侧面、相对大小、旋转角度、倾斜、照明等条件大致相同
数据集中应包括对象的不同缩放、旋转、照明、不同的面、不同的背景的图像,最好为每个类提供2000个不同的图像,并且训练(2000*类的数量)的迭代次数加多
对于目标物体较多的图像,在.cfg文件中最后一个[yolo]层和[region]层加入max=200参数或者更高的值。(yolov3可以检测到的对象的全局最大数目是0.0615234375 (widthheight),其中width和height是.cfg文件中[net]部分的参数)
训练小物体时(图像调整到416x416后物体小于16x16),将[route]参数替换为layers=-1,11,将[upsample]参数改为stride=4
对于都包含小对象和大对象可以使用以下的修改模型:Full-model: 5 yolo layers: https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/yolov3_5l.cfg Tiny-model: 3 yolo layers: https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/yolov3-tiny_3l.cfg Spatial-full-model: 3 yolo layers: https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/yolov3-spp.cfg
如果要训练区分左右的对象(例如左手右手,道路上左转右转标志等),需要禁用数据翻转增强,在.cfg文件中17行左右的位置加入flip=0
一般规律——训练数据集应该包含一组您想要检测的对象的相对大小。简单来说,就是需要检测的对象在图像中的百分比是多少,那么训练的图像中也应该包含这个百分比的对象。例如:如果训练的时候目标在图像中都占80-90%,那检测的时候很可能就检测不出目标占0-10%情况。
同一个对象在不同的光照条件、侧面、尺寸、倾斜或旋转30度的情况下,对于神经网络的内部来说,都是不同的对象。因此,如果想检测更多的不同对象,就应该选择更复杂的神经网络。
在.cfg中重新计算锚(anchors)的width和height:在.cfg文件中的3个[yolo]层中的每个层中设置相同的9个锚,但是应该为每个[yolo]层更改锚点masks=的索引,以便[yolo]第一层的锚点大于60x60,第二层大于30x30,第三层剩余。此外,还应在每个[yolo]层之前更改过滤器filters=(classes + 5) * 。如果许多计算出的锚找不到适当的层,那么只需尝试使用所有的默认锚。这里提高了网络的分辨率,但是可以不需要重新训练(即使之前使用416*416分辨率训练的)。但是为了提高精度,还是建议使用更高的分辨率来重新训练。注意:如果出现了Out of memory的错误,建议提高.cfg文件中的subdivisions=16参数,改成32或者64等
在检测时,减小 yolo. py 文件参数中的 score 和 iou,如果这时候能出现框了,但是会发现效果不是很好,或者说置信度特别低,说明检测是成功的,但是训练做得比较差;如果还是不能出现框,考虑有可能检测方法错了(路径设置等),或者是训练过程错误(类名称和索引没配置好等)。
增加业务流程控制,对识别结果进行业务处理,对识别过程进行时序分析、控制。
六.loss收敛情况解析
训练过程loss异常说明(train loss:使用训练集的样本来计算的损失,val loss使用验证集的样本来计算的损失, 过拟合:为了得到一致假设而使假设变得更严格)
train lossval loss说明
下降下降网络在不断学习收敛
下降不变网络过拟合
不变下降数据集有问题
不变不变此次条件下学习达到瓶颈
上升上升网络结构或设计参数有问题,无法收敛
若一开始loss就无穷变大,可能是训练集和验证集的数据不匹配,或者归一化量纲不对或者参数设置错误。
train loss 不断下降,test loss不断下降,说明网络仍在学习。
train loss 不断下降,test loss趋于不变,说明网络过拟合。
train loss 趋于不变,test loss不断下降,说明数据集100%有问题。
train loss 趋于不变,test loss趋于不变,说明学习遇到瓶颈,需要减小学习率或批量数目。
train loss 不断上升,test loss不断上升,说明网络结构设计不当,训练超参数设置不当,数据集经过清洗等问题。
loss值不下降问题:模型结构和特征工程存在问题;权重初始化方案有问题;正则化过度;选择合适的激活函数、损失函数;选择合适的优化器和学习速率;训练时间不足;模型训练遇到瓶颈;batch size太大或者太小,数据导入不对,比如维度错了;数据集未打乱;数据集有问题;未进行归一化;特征工程中对数据特征的选取有问题。
LOSS持续不降,第一步先减小数据量,比方说只在单张图片上跑,使用小epochsize,观察每次LOSS下降情况,此时如果LOSS还是不下降说明网络没有学习能力,应该调整模型,一般是先把网络规模缩小,因为任何一个网络都会有学习能力,然而此时你的网络没有学习能力,则一定是你的模型有地方出错,而神经网络又是个黑盒,你只能摘除一部分网络,以排除“坏的”部分。此时网络的规模小了,又在一个相对较小的数据集上跑,必然会有个很好的学习能力。此时可以不断增加网络部件,来提高学习能力。
从训练开始就一直震荡或者发散:图片质量极差,人眼几乎无法识别其中想要识别的特征,对于网络来说相当于输入的一直都是噪音数据,比如通过resize的时候,图片的长宽比改变特别大,使图片丧失对应特征,或者tfrecord中图片大小是(m,n),但是读取的时候,按照(n,m)读取,所以loss一直震荡无法收敛;大部分标签都是对应错误的标签;leaning rate 设置过大。
训练开始会有所下降,然后出现发散:数据标签中有错误,甚至所有标签都有一定的错误,比如生成的标签文件格式和读取标签时设置的文件格式不一样,导致读取的标签是乱码;或者为标签中存在的空格未分配对应的编码,导致读取的空格为乱码(在OCR问题中);learning rate 设置过大。
训练开始会有所下降,然后出现震荡:loss函数中正则化系数设置有问题,或者loss函数本身有问题。比如,在序列化问题中的label_smoothing设置过大,比如设置为0.9,一般设置为0.1即可(OCR问题中);数据标签中有错误,甚至所有标签都有一定的错误。
在自己训练新网络时,可以从0.1开始尝试,如果loss不下降的意思,那就降低,除以10,用0.01尝试,一般来说0.01会收敛,不行的话就用0.001. 学习率设置过大,很容易震荡。不过刚刚开始不建议把学习率设置过小,尤其是在训练的开始阶段。在开始阶段我们不能把学习率设置的太低否则loss不会收敛。我的做法是逐渐尝试,从0.1,0.08,0.06,0.05 …逐渐减小直到正常为止。
检查lable是否有错,有的时候图像类别的label设置成1,2,3正确设置应该为0,1,2。
训练神经网络的时候,如果不收敛你可以改变一下图像的大小,很有可能事半功万倍。
七.下载
yolov3开源代码:https://github.com/qqwweee/keras-yolo3
本文练手工程GitHub:https://github.com/swliu2016/ai-core/tree/train-core-yolov3
CSDN下载地址:https://download.csdn.net/download/liuwuw/12066793
————————————————
版权声明:本文为CSDN博主「弈休丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/liuwuw/java/article/details/103630996