用yolo3对自己的数据集进行训练,查阅一些相关文章,发现都是使用老版本的文章,一些细节方面有很多改动,我在文章中会列出我遇到的一些新问题和一些老问题,在写训练步骤的同时多为大家列出一些常见问题。新手入门大神们多多指点。
如果是linux环境下可以访问参考文章
模型代码下载地址:
https://github.com/qqwweee/keras-yolo3
参考文章:
1.https://blog.csdn.net/m0_37857151/article/details/81330699
2.https://blog.csdn.net/patrick_Lxc/article/details/80615433
进入模型代码网址,下载源码目录到本地,根据github作者的指导完成快速测试,由于现在新代码跟老本版不一样了,相关命令有些许的改动。
1.步骤一让去yolo官网下载一个权重文件:https://pjreddie.com/media/files/yolov3.weights,文件200多M,如果没有VPN下载速度慢的话可以从这里下载:https://pan.baidu.com/s/15NsB5hbwa_N-eJ6-sNwgLw 提取码:kj44 。
2.下载好weight权重文件后放入在github上下载的keras-yolo3-master文件夹下,执行convert.py文件,用于将权重文件转为 .h5格式的文件,生成的h5将被保存在model_data目录下。在当前目录下打开终端并输入命令:
python convert.py yolov3.cfg yolov3.weights model_data/yolo.h5
3.用生成好的.h5文件进行目标检测。
a:如果对图片进行测试,输入命令,运行后会让你在终端上输入图片路径:
python yolo_video.py --image
python3 yolo_video.py --input=原视频地址 --output=新生成地址
+++++++++++++++++++++++++++++++++++++++++++++++++分割线
贴个效果图:
文章开头列出的参考文章是使用了VOC2007数据集的格式,新手不用在意是否使用什么VOC格式,之所以用这个本质是为减少对源代码的修改量,就算自己创建几个文件夹也无所谓,无非就是在代码中修改目录罢了。
我们创建的文件夹,第一Annotations是储存标注后的xml文件,第二ImageSets/Main是存储生成所需文本文件,第三JPEGImages是存储图片,第四个model_data储存我们的classes和最终生成的.h5文件,第五个yolo3是在githup下载模型中的文件夹,没有改动,直接复制过来就好,后面其它的文件也是模型中复制过来或者后文中贴出的代码。
注:为了少改代码大家可以参考我的VOC2007目录,跟原版本格式有删减,剔除了没用的部分:
第一步:创建JPEGImages文件夹,将你要训练的图片放到JPEGImages中。
第二步:闯将Annotations文件夹,将你标注所生成的xml文件放到Annotations文件夹中。如果不知道怎么标注的童鞋们点击蓝色字体:讲述如何标注的文章
第三步:创建ImageSets/Main文件夹,这个文件夹用来储存我们的对数据集进行 训练和验证拆分后的文本。
第四步:对我们的数据集进行分类,分成train.txt和val.txt。
复制以下代码至根目录VOC2007下,命名convert_to_txt.py
import os
import random
trainval_percent = 0.1
train_percent = 0.9
xmlfilepath = 'Annotations'
txtsavepath = 'ImageSets\Main'
total_xml = os.listdir(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('ImageSets/Main/trainval.txt', 'w')
ftest = open('ImageSets/Main/test.txt', 'w')
ftrain = open('ImageSets/Main/train.txt', 'w')
fval = open('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()
此代码在上述参考文章中都有,该目录的朋友注意下目录地址,按本文创建的目录不需要删减。
运行后在ImageSets/Main目录下生成4个txt文本:
第五步:将生成的txt文本重新整理生成我们yolo3模型需要的txt格式,就是把图片的文件名和xml文件中box的坐标融合在一起。运行voc_annotation.py文件,注意这里面是要进行修改的:
import xml.etree.ElementTree as ET
from os import getcwd
sets=[('2007', 'train'), ('2007', 'val'), ('2007', 'test')]
classes = [‘car’,'person','bus']
def convert_annotation(year, image_id, list_file):
in_file = open('Annotations/%s.xml'%( image_id))
tree=ET.parse(in_file)
root = tree.getroot()
for obj in root.iter('object'):
difficult = obj.find('difficult').text
cls = obj.find('name').text
if cls not in classes or int(difficult)==1:
continue
cls_id = classes.index(cls)
xmlbox = obj.find('bndbox')
b = (int(xmlbox.find('xmin').text), int(xmlbox.find('ymin').text), int(xmlbox.find('xmax').text), int(xmlbox.find('ymax').text))
list_file.write(" " + ",".join([str(a) for a in b]) + ',' + str(cls_id))
wd = getcwd()
for year, image_set in sets:
image_ids = open('ImageSets/Main/%s.txt'%(image_set)).read().strip().split()
list_file = open('%s.txt'%(image_set), 'w')
for image_id in image_ids:
list_file.write('JPEGImages/%s.jpg'%(image_id))
convert_annotation(year, image_id, list_file)
list_file.write('\n')
list_file.close()
注意:
1.第6行的 classes 改成你们自己的类
2.第10,27,30行的目录是否是自己的目录
3.我与原文的目录稍加不同,如果完全按照本文目录生成的直接复制上面代码
4.新生成的txt内容是这个样子的!!!!!!!
第六步:创建model_data文件夹,并生成voc_classes.txt,里面填写的是你的classes。
第七步:修改train.py文件。为什么修改呢?在参考文章中说到:因为原作者的代码中会加载预先对coco数据集已经训练完成的yolo3权重文件,我们不需要预加载他以前训练过的权重,我们要训练自己的模型,所以我们剔除掉原文中没用的东西。下面贴出修改后的train.py,我也是在参考文章中直接复制的,谢谢这些大哥们的代码。
"""
Retrain the YOLO model for your own dataset.
"""
import numpy as np
import keras.backend as K
from keras.layers import Input, Lambda
from keras.models import Model
from keras.callbacks import TensorBoard, ModelCheckpoint, EarlyStopping
from yolo3.model import preprocess_true_boxes, yolo_body, tiny_yolo_body, yolo_loss
from yolo3.utils import get_random_data
def _main():
annotation_path = 'train.txt'
log_dir = 'logs/000/'
classes_path = 'model_data/voc_classes.txt'
anchors_path = 'model_data/yolo_anchors.txt'
class_names = get_classes(classes_path)
anchors = get_anchors(anchors_path)
input_shape = (416, 416) # multiple of 32, hw
model = create_model(input_shape, anchors, len(class_names))
train(model, annotation_path, input_shape, anchors, len(class_names), log_dir=log_dir)
def train(model, annotation_path, input_shape, anchors, num_classes, log_dir='logs/'):
model.compile(optimizer='adam', loss={
'yolo_loss': lambda y_true, y_pred: y_pred})
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=1)
batch_size = 10
val_split = 0.1
with open(annotation_path) as f:
lines = f.readlines()
np.random.shuffle(lines)
num_val = int(len(lines) * val_split)
num_train = len(lines) - num_val
print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
model.fit_generator(data_generator_wrap(lines[:num_train], batch_size, input_shape, anchors, num_classes),
steps_per_epoch=max(1, num_train // batch_size),
validation_data=data_generator_wrap(lines[num_train:], batch_size, input_shape, anchors,
num_classes),
validation_steps=max(1, num_val // batch_size),
epochs=100,
initial_epoch=0)
model.save_weights(log_dir + 'trained_weights.h5')
def get_classes(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(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 create_model(input_shape, anchors, num_classes, load_pretrained=False, freeze_body=False,
weights_path='model_data/yolo_weights.h5'):
K.clear_session() # get a new session
image_input = Input(shape=(None, None, 3))
h, w = input_shape
num_anchors = len(anchors)
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:
# Do not freeze 3 output layers.
num = len(model_body.layers) - 3
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)))
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):
n = len(annotation_lines)
np.random.shuffle(annotation_lines)
i = 0
while True:
image_data = []
box_data = []
for b in range(batch_size):
i %= n
image, box = get_random_data(annotation_lines[i], input_shape, random=True)
image_data.append(image)
box_data.append(box)
i += 1
image_data = np.array(image_data)
box_data = np.array(box_data)
y_true = preprocess_true_boxes(box_data, input_shape, anchors, num_classes)
yield [image_data, *y_true], np.zeros(batch_size)
def data_generator_wrap(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()
直接运行train.py就可以了,至此训练步骤结束。其中有一些运行问题我将在文章末尾写出来,大家可以先看看在进行训练,避免做无用功!!!
我是用1080ti 11G显存跑的,其中bitch_size=10 epochs=80,训练了6个小时,loss训练到30。参考文章中说loss训练到10就可以了,我实在懒得等的就先凑活用了。我先说下步骤。
第一步:可以根据第一章的内容,快速测试!!!!!!
第二步:用原文中的方式得一张一张图片输入路径太慢了,我对代码进行了修改,批量对图片进行测试。
->先创建一个pic文件,里面储存待测试图片。在生成一个res文件,储存测试结果。
->将新建yolo_image.py文件,直接复制下面代码:
import time
import argparse
from yolo import YOLO, detect_video
from PIL import Image
import os
def detect_img(yolo):
filename = os.listdir('pic')
for i in filename:
img = f"pic/{i}"
try:
image = Image.open(img)
except:
print('Open Error! Try again!')
continue
else:
r_image = yolo.detect_image(image)
r_image.save(f'res/{i}')
yolo.close_session()
if __name__ == '__main__':
print("=========================开始预测===============================")
start = time.time()
parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS)
'''
Command line options
'''
parser.add_argument(
'--model', type=str,
help='path to model weight file, default ' + YOLO.get_defaults("model_path")
)
parser.add_argument(
'--anchors', type=str,
help='path to anchor definitions, default ' + YOLO.get_defaults("anchors_path")
)
parser.add_argument(
'--classes', type=str,
help='path to class definitions, default ' + YOLO.get_defaults("classes_path")
)
parser.add_argument(
'--gpu_num', type=int,
help='Number of GPU to use, default ' + str(YOLO.get_defaults("gpu_num"))
)
parser.add_argument(
'--image', default=False, action="store_true",
help='Image detection mode, will ignore all positional arguments'
)
'''
Command line positional arguments -- for video detection mode
'''
parser.add_argument(
"--input", nargs='?', type=str,required=False,default='./path2your_video',
help = "Video input path"
)
parser.add_argument(
"--output", nargs='?', type=str, default="",
help = "[Optional] Video output path"
)
FLAGS = parser.parse_args()
detect_img(YOLO(**vars(FLAGS)))
end = time.time()
t = end-start
print('用时:',int(t),'s')
直接运行 yolo_image.py,结果直接在res文件中查看即可。
++++++++++++++++++++++++++++++++++++++++++++分割线
贴测试效果图:
2600张数据集,训练时间6小时,loss=30,训练时间太短效果一般,识别效果时有时无。
1.在训练自己的数据集之前,执行train.py之前,先在目录下创建logs/000文件夹,否则训练几个小时后,在完成的那一刻报错!!!!太坑了这个问题!
OSError: Unable to open file
2.我分别用1080ti 11g和2080 8g显卡跑,1080ti可以直接跑,但是2080跑回报显存不足的错误,这个错误我们先从模型本身入手,先将train.py中bitch_size值改小,最好先改成1,然后我在运行,依然会报显存不足。
tensorflow.python.framework.errors_impl.InternalError: Blas SGEMM launch failed : m=43264, n=32, k=64
[[{{node conv2d_3/convolution}}]]
[[{{node loss/add_74}}]]
然后我觉得8G显存足够了,用faster-rcnn和ssd都没问题,我在想会不会是因为显存一下被占用满导致显卡运行有问题,然后我在yolo3文件夹中的model.py中添加了限制gpu使用的代码:
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
config.gpu_options.per_process_gpu_memory_fraction = 0.8
set_session(tf.Session(config=config))
此时bitch_size=1,完美执行。然后又一点点试,bitch_size设置为5都没有问题。
3.以上是两个让我比较深刻的问题,还有一些问题暂且想不起来,如果有人提问我将问题写在这里。或者去参考文章下看看提问兴许对你们有帮助。