一、写在前面
本篇博客将围绕着Github上星标最多(2k+)的基于Keras(后端为TensorFlow)的YOLOv3模型训练自己的数据,链接为qqwweee/keras-yolo3, 本篇博客仅介绍流程,代码和结构部分不做相应的解释,如果您对yolo的原理和结构比较感兴趣的话,建议可以搜索Yolov3,其它类似的文章可能对您有所帮助。如果您对Keras不是很了解的话,可以参考一下博主写的前面两篇以案例编写为介绍的Keras的相关代码以及原理,特别的该案例使用了多个回调函数来优化模型的训练结果,这一部分在上一篇确有所触及。如果您对TensorFlow有一定基础的话学Keras是很快的,当然先学Keras再学TensorFlow也是没有问题的,当然不仅如此,还有一些比较硬性的要求(可根据实际情况微调):
- 机器配置:i7 8700K+七彩虹1080Ti+16G内存+M2固态
- 机器环境:ubuntu16.04+anaconda(python 3.64)+pycharm+tensorflow-gpu+Keras
当然了,如果您的机器配置和博主的不一样也没有关系,和github上要求的Python 3.5.2 Keras 2.1.5 tensorflow 1.6.0
类似亦可,没有任何基础也没有关系,本篇尽量保证所描述的语言和操作的通俗以及易懂的语序。
二、前期准备
首先需要说明的是博主的用户名为dream,后面若有相同的,替换为自己的用户名即可。
- 下载与解压
下载项目至/home/dream/目录下,然后使用如下命令解压缩:
unzip keras-yolo3-master.zip
- 下载并转化模型权重文件
cd keras-yolo3-master
wget https://pjreddie.com/media/files/yolov3.weights
这里会在项目根目录下找到一个yolov3.weight的文件,即论文中给出的模型权重文件,因为作者是使用C和C++的darknet来完成的这个项目,所以这里我们需要将权重文件改写为Keras能读懂的模型权重文件,这里我们就会使用根目录下的convert.py
文件,来对我们刚下载的模型权重文件做转化,转化成h5为后缀的模型权重文件。这里只需使用python convert.py yolov3.cfg yolov3.weights model_data/yolo.h5
命令,运行之后我们会在model_data目录下看到有一个yolo.h5的文件,即Keras能读懂的模型权重文件。
- 简单测试
我们从某度上随便找一张图片,保存在/home/dream/keras-yolo3-master/timg.jpeg
,图片如下:
使用命令
python yolo_video.py --input=timg.jpeg
即可,当然博主在这里写的时候程序报错,这里仅需要在报错位置即yolo.py
文件中的image = Image.fromarray(frame)
部分加上异常处理部分即可。
try:
image = Image.fromarray(frame)
except AttributeError:
print(image)
break
如果您需要将检测到的图片保存的话,则需要在cv2.imshow("result", result)
下面加上一行cv2.imwrite("result.jpg",result)
即能把检测后的图片保存下来。
当然这里也会输出一些信息,以供查询,这里所输出的信息为:
(416, 416, 3)
Found 1 boxes for img
cat 0.96 (439, 18) (2560, 1389)
1.8417232729989337
三、模型使用自己的数据
- 图片准备
这里将自己的数据集放入到一个目录中去,博主这里仅使用少量数据做一个简单的手部目标检测,我们这里把要用到的图片保存在/home/dream/keras-yolo3-master/VOC/ConGD_phase_1/train_img/001/00001.M/
下。
- 标注anchor框(annotation)
这里使用一个通用且好用的图片标定工具:labelimg
。我们可以下载下来,然后解压到家目录下,然后按照github上面的提示来完成安装及使用。
unzip labelImg-master.zip
cd labelImg-master
sudo apt-get install pyqt5-dev-tools
sudo pip3 install -r requirements/requirements-linux-python3.txt
make qt5py3
#打开labelImg
python3 labelImg.py
我们首先可以把labelImg中预设的类别删除并加上自己要用的类别,以防后面标注的时候产生干扰,gedit /home/dream/labelImg-master/data/predefined_classes.txt
打开软件之后,左上角两个文件夹分别指定图片来源文件夹和PascalVOC(.xml为后缀名) 的保存文件夹,这里保存到了/home/dream/keras-yolo3-master/VOC/test
目录下,在软件界面下,我们可以使用快捷键W来画框,使用Ctrl+S保存anchor框,使用A和D来进行上下图片切换,软件界面如下图所示:
PascalVOC标注文件内容如下:
- 解析文档树并转化为yolov3的格式
这里需要用到voc_annotation.py文件,该文件的作用就是将PascalVOC文件转化为yolo3能识别的txt文本,不过在转化之前需要修改文档中的部分代码。这里具体修改的地方和修改的内容无非就是改改文件名,改改文件位置,这里仅贴一下修改之后的代码。值得一提的是其中train_list.txt文件存放的是数据集所有的图片名。
import xml.etree.ElementTree as ET
from os import getcwd
#sets=[('2007', 'train'), ('2007', 'val'), ('2007', 'test')]
classes = ["hand"]
def convert_annotation(image_id,list_file):
in_file = open('VOC/test/%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('VOC/train_list.txt').read().strip().split()
list_file = open('VOC/train_anno.txt', 'w')
for image_id in image_ids:
list_file.write('%s/VOC/ConGD_phase_1/train_img/001/00001.M/%s.jpg'%(wd, image_id))
convert_annotation( image_id, list_file)
list_file.write('\n')
list_file.close()
转化之后可以得到如下的txt文件。按照先后顺序分别为图片名 图片的坐标 图片的类别 。
- 模型训练
这里需要修改train.py
文件的部分内容,即将索引位置做对应的修改。另外这里我们设置的类别只有一个类别hand,所以需要将voc_classes.txt
文件中的内容修改为仅hand一项。并且因为显存较低,可以在unfreeze阶段将batch_size的值调为10。
annotation_path = 'VOC/train_anno.txt'
log_dir = 'logs/000/'
classes_path = 'model_data/voc_classes.txt'
anchors_path = 'model_data/yolo_anchors.txt'
然后运行python train.py
运行代码训练模型。我们可以看到这里触发了EarlyStopping回调函数,中止了整个程序的运行。
Train on 20 samples, val on 2 samples, with batch size 32.
Epoch 1/50
1/1 [==============================] - 7s 7s/step - loss: 10074.7832 - val_loss: 10445.6553
Epoch 2/50
1/1 [==============================] - 1s 1s/step - loss: 9290.3203 - val_loss: 9557.7178
Epoch 3/50
1/1 [==============================] - 1s 1s/step - loss: 8374.2451 - val_loss: 8615.5684
Epoch 4/50
1/1 [==============================] - 1s 1s/step - loss: 7543.6899 - val_loss: 7740.0029
。。。
Epoch 49/50
1/1 [==============================] - 2s 2s/step - loss: 303.3745 - val_loss: 334.6594
Epoch 50/50
1/1 [==============================] - 2s 2s/step - loss: 332.9825 - val_loss: 316.9244
Unfreeze all of the layers.
Train on 20 samples, val on 2 samples, with batch size 10.
Epoch 51/100
2/2 [==============================] - 14s 7s/step - loss: 275.3629 - val_loss: 259.4476
Epoch 52/100
2/2 [==============================] - 1s 646ms/step - loss: 168.4597 - val_loss: 232.0418
。。。
Epoch 95/100
2/2 [==============================] - 1s 660ms/step - loss: 40.1177 - val_loss: 43.1519
Epoch 96/100
2/2 [==============================] - 1s 668ms/step - loss: 41.1883 - val_loss: 43.2640
Epoch 97/100
2/2 [==============================] - 1s 658ms/step - loss: 39.0571 - val_loss: 42.9521
Epoch 00097: ReduceLROnPlateau reducing learning rate to 1.0000000116860975e-08.
Epoch 98/100
2/2 [==============================] - 1s 658ms/step - loss: 39.7956 - val_loss: 41.6073
Epoch 00098: early stopping
5、模型测试
这里测试依旧使用上面用过的方法,不过需要在yolo.py
文件中修改如下部分,索引到我们刚才训练的模型以及标签值。然后运行python yolo_video.py --input=10.jpg
,结果如下所示:
_defaults = {
"model_path": 'logs/000/trained_weights_final.h5',
"anchors_path": 'model_data/yolo_anchors.txt',
"classes_path": 'model_data/voc_classes.txt',
"score" : 0.3,
"iou" : 0.45,
"model_image_size" : (416, 416),
"gpu_num" : 1,
}
四、广而告之
当你在进行数据统计分析,模型建立遇到困难的时候,那么请点开这个链接吧或者保存下面图片,打开淘宝立即看见:
https://shop163287636.taobao.com/?spm=a230r.7195193.1997079397.2.b79b4e98VwGtpt
五、总结与分析
- 本例进使用少量数据且场景单一因而拟合效果比较优良,所以展示的效果比较好,后面会逐渐增大数据压力,标注更多的数据与类别,来训练更加复杂的场景与手部区域的数据集合,然后测试多场景下的手部区域识别的效果。
- 本例使用的方法算是较为简单的,因此很容易便会把整个流程走完,完成之后可以结合以前学习的知识和作者的论文研究整个网络的结构、运行以及优化原理。来学习yolo3更加细节的东西,用于以后做目标检测任务时的优化和处理。