【项目实战】yolov3-tiny人脸数据模型训练


数据集准备:

  使用的是网上公开的widerface数据集(从事图像标注的人都是专业的呀(http://mmlab.ie.cuhk.edu.hk/projects/WIDERFace/index.html)

  需要下载前四个文件,包括训练集、验证集、测试集和人脸标注的txt文件(并没有原始的xml文件)。

  训练集、验证集、测试集的数据如下图所示:

  其中每一个数据集中都包含60种不同场景下人的图像。(该数据集包含不同类型的场景,场景比较丰富)如下图所示:

  标注文件(txt/mat)

  标注文件中包含上述6个(3类)文件,分别适用于不同的环境。其中包含的标注信息如下图所示:

  其格式和linux环境下需要的txt文件有所不同。

  因此,参考博客https://blog.csdn.net/u010397980/article/details/86764637将给定的标注文件重新转化成xml文件。之后按照linux环境下数据集和文件格式的要求(如下图所示),将不同场景下的图像、xml文件放在一个文件夹下。

  下述是上面介绍的将给定的标注文件txt转化为xml文件的代码,在使用过程中,只需要更改标注文件的路径txt_label和标注文件对应图像的路径img_path

  需要注意的是,通过下述代码生成的xml文件与图片一起分布在不同的场景当中,后续使用的时候需要提取。

 1 # -*- coding: utf-8 -*-
 2 # @Time    : 2019/9/25 20:11
 3 # @Author  : smw
 4 # @Email   : monologuesmw@163.com
 5 # @File    : txt2xml.py
 6 # @Software: PyCharm
 7 # 将txt生成xml文件
 8 import os
 9 import cv2
10 
11 from xml_writer import PascalVocWriter
12 
13 # txt_label = "wider_face_val_bbx_gt.txt"
14 # img_path = "WIDER_val/images"
15 
16 txt_label = "wider_face_train_bbx_gt.txt"   # 修改train/val自带的标注txt文件的名称
17 img_path = "WIDER_train/WIDER_train/images"  # 修改对应的train/val图片的地址
18 
19 
20 def write_xml(img, res):
21 # 
22     img_dir = os.path.join(img_path, img)
23     print(img_dir)
24     img = cv2.imread(img_dir)
25     shape = img.shape
26     img_h, img_w = shape[0], shape[1]
27     writer = PascalVocWriter("./", img_dir, (img_h, img_w, 3), localImgPath="./", usrname="wider_face")
28     for r in res:
29         r = r.strip().split(" ")[:4]
30         print(r)
31         x_min, y_min = int(r[0]), int(r[1])
32         x_max, y_max = x_min + int(r[2]), y_min + int(r[3])
33         writer.verified = True
34         writer.addBndBox(x_min, y_min, x_max, y_max, 'face', 0)
35         writer.save(targetFile=img_dir[:-4] + '.xml')
36 
37 
38 with open(txt_label, "r") as f:   
39     line_list = f.readlines()  # 打开并读取标注文件
40     for index, line in enumerate(line_list):
41         line = line.strip()
42         if line[-4:] == ".jpg":
43             print("----------------------")
44             # print index, line
45             label_number = int(line_list[index + 1].strip())
46             print("label number:", label_number)
47             # print line_list[index: index + 2 + label_number]
48             write_xml(line_list[index].strip(), line_list[index + 2: index + 2 + label_number])  # 写xml

Main文件夹下txt的生成

  由于linux环境下的test.py会对所有的数据集重新进行训练集和验证集的划分,生成Main文件夹中的train.txt,val.txt(其中记录的是参与训练和验证数据的名称(不带后缀))。而本数据集已经划分好了训练集和验证集,如果将所有的数据重新放置在一起重新划分,未免有些傻用test.py的感觉。而且可能会丢失掉之前数据集划分的依据。因此,我们将训练数据集的xml文件放在一起验证数据集的xml文件放置在一起,在windows10下对test.py文件进行改写,生成指定xml文件对应的txt(生成txt的过程只需要xml文件,不需要图像数据文件。因此,训练、验证数据是直接放在一起的)。然后,便可以将生成的txt文件移植到linux环境下的Main文件夹下

  整个过程的流程大致可以分为以下两步:

  1. 将不同场景文件夹下的数据放置在一个文件夹中。(训练、验证的图像数据集直接放在一起)

  2. 将不同场景文件夹下,生成的xml文件分别放置在train_xml和val_xml两个文件夹下(用于后续生成Linux下对应的txt文件) ---- 上述两部均同时人工进行

 数据整理 (两个人,大概耗时1.5小时)

A. 图像数据集包含所有的训练、验证数据集

  将给定数据集的 WIDER_train、WIDER_val中不同场景下的图像数据均放置在同一个文件夹xJPEG下。由下图所示,训练、验证数据共包含16106份。

B. xml数据集

  将给定数据集的WIDER_train、WIDER_val中,生成的xml文件放置到train_xml和val_xml文件夹中。分为训练集的xml 测试集的xml 用于生成指定的txt(train.txt 和 val.txt)。

  

生成指定xml文件的txt [Main中的txt] (test.py的改写)

  执行下述代码将会生成上述的train.txt val.txt,然后移植到linux环境下的Main文件夹中

 1 # 根据指定训练集和测试集的xml文件生成txt
 2 import os
 3 import random
 4 train_xmlfilepath = 'xml/train_xml'
 5 val_xmlfilepath = 'xml/val_xml'     # 获取训练、验证xml文件夹的路径
 6 txtsavepath = 'xml'
 7 total_train_xml = os.listdir(train_xmlfilepath)
 8 total_val_xml = os.listdir(val_xmlfilepath)  # 获取文件夹下所有文件的列表
 9 
10 num_train = len(total_train_xml)
11 list_train = range(num_train)
12 num_val = len(total_val_xml)
13 list_val = range(num_val)
14 
15 ftrain = open('xml/train.txt', 'w')
16 fval = open('xml/val.txt', 'w')   # 打开两个待生成的txt文件
17 
18 # train 的txt的书写
19 for i in list_train:
20     name = total_train_xml[i][:-4] + '\n'
21     ftrain.write(name)
22 ftrain.close()
23 
24 # val 的txt的书写
25 for j in list_val:
26     name = total_val_xml[j][:-4] + '\n'
27     fval.write(name)
28 fval.close()

  生成txt文件后,就可以将train文件夹和val文件夹下的xml文件合并在一起移植linux下的Annotations文件夹下。 【合并xml,移植linux Annotations文件夹下】  

Linux下lables文件夹下txt生成

voc_labels.py修改

  在linux下,对voc_labels.py文件中的下述部分进行修改。

  sets中更改文件夹设定的年份,以及Main文件夹中包含的txt数据集类型。修改类别名称。

 part 2.2 在linux终端上执行

  在darknet文件夹下打开终端,执行下述指令。便会在labels文件夹下生成每一张图像的

python --version 
python voc_labels.py

  至此,数据集的准备便已完成

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 

  前期的准备工作和yolo v3中是一样的。

  需要修改的配置文件有voc.data、voc.names、 yolov3-tiny.cfg文件

  • voc.names中修改训练数据集中包含有哪些类标签,一行一类标签;这里改为了face
  • voc.data中修改类别的数量;voc_labels.py生成的txt的路径,包括训练和验证两部分;其余两项一般不需要修改,默认的就可以。详情如下:
1 classes = 1
2 train = /home/vtstar/yolo/darknet/2019_train.txt
3 valid = /home/vtstar/yolo/darknet/2019_val.txt
4 names = data/voc.names
5 backup = backup
  • yolov3-tiny.cfg中需要修改内容如下:
    • [net]下 batch 和subdivisions参数(根据PC的性能)
    • [convolutional]([yolo]层的前一层),fitters的数量,根据类别的数量进行修改(5+class_num)*3
    • [yolo] anchors修改为K-Means生成的锚框(采用默认的也可以);classes数量  

  Makefile文件除了修改GPU,CUDNN,OPENCV的配置参数外,还需要根据电脑的配置,修改一些关于nvcc的参数。

  下载yolo v3-tiny的权重测试:

wget https://pjreddie.com/media/files/yolov3_tiny.weights

  下载卷积层的权重

./darknet partial cfg/yolov3-tiny.cfg yolov3-tiny.weights yolov3-tiny.conv.13 13

  模型训练

1 ./darknet detector train cfg/voc.data cfg/yolov3-tiny.cfg yolov3-tiny.conv.15 -gpus 0,1

  gpus后的0,1,2,...根据服务器中GPU的数量选择。  

初始锚框的生成:(win10) 用于修改yolov3-tiny中的锚框信息

  使用keras版本的K-Means生成人脸检测的锚框宽高:

6,8, 10,13, 15,19, 24,30, 42,54, 106,140

  重复了十几次,选择了精度最高的一组(72.98%)

 

  初始锚框生成的步骤

  使用Keras版本的darknet源码中的voc_annotation.py文件放置到下载的数据集中,修改一些文件的路径:

1 in_file = open('xml/train_xml/%s.xml' % image_id)
2 ...
3 image_ids = open('xml/%s.txt' % image_set).read().strip().split()
4 list_file = open('%s.txt' % image_set, 'w')
5 ...
6 list_file.write('%s/xJPEG/%s.jpg'%(wd, image_id))

  voc_annotation.py 代码如下所示,可对应上述需要改动的内容。该程序的执行实际上不需要将xml文件、图像文件放置在标准的文件夹下。

  下述代码主要的作用是将图像的路径+名称.jpg 和 xml文件中标注框的信息写到txt中。需要的文件:

  1. 没有后缀的图像名称,也就是上述给Main文件夹中放置的txt

  2. 不需要图像文件,但需要图像的路径,用于写到txt中,第三行的2007没有用,trian将作为生成的txt的名称。【标注文件中图片的路径不能作为写到txt的路径,标注文中总如果包含中文路径,则in_file=open()中需要添加encoding= "utf-8" , 否则会报错,默认gbk】

 1 import xml.etree.ElementTree as ET
 2 from os import getcwd
 3 sets = [('2007', 'train')]
 4 
 5 classes = ["face"]   # 不需要使用
 6 def convert_annotation(year, image_id, list_file):   # 将xml转化为txt
 7     in_file = open('xml/train_xml/%s.xml' % image_id)  # xml的地址,需要图像名称作为xml名称(二者名称是一致的)
 8     tree=ET.parse(in_file)  # 此处,如果在标注时,存储地址包含中文路径,会报错,需要将上一行代码添加encoding="utf-8"
 9     root = tree.getroot()
10 
11     for obj in root.iter('object'):
12         difficult = obj.find('difficult').text
13         cls = obj.find('name').text
14         if cls not in classes or int(difficult)==1:
15             continue
16         cls_id = classes.index(cls)
17         xmlbox = obj.find('bndbox')
18         b = (int(xmlbox.find('xmin').text), int(xmlbox.find('ymin').text), int(xmlbox.find('xmax').text), int(xmlbox.find('ymax').text))
19         list_file.write(" " + ",".join([str(a) for a in b]) + ',' + str(cls_id))
20 
21 wd = getcwd()
22 
23 for year, image_set in sets:
24     image_ids = open('xml/%s.txt' % image_set).read().strip().split()   # 只包含图像名称的txt, 也是给Main文件夹中的txt
25     list_file = open('%s.txt' % image_set, 'w')  # 要生成txt文件的路径,名称这里使用了第3行代码中的train
26     for image_id in image_ids:
27         list_file.write('%s/xJPEG/%s.jpg'%(wd, image_id))  # 修改图像所在的路径,当然实际上不需要图像,只是把路径当做字符串写到txt中
28         convert_annotation(year, image_id, list_file)
29         list_file.write('\n')
30     list_file.close()

  通过文件,便可以生成python中的train.txt的模式(即路径+框信息),如下图所示:

  然后将生成train.txt放置到K-Means.py文件夹中,修改K-Means中的一些路径,如下图所示:

  在这里,我们将train.txt的名称修改为train_2019.txt,避免和之前文件的重合。由于在yolo v3-tiny中进行训练,网络中只包含两个尺度的输出,相应锚框的数量为6,所以在此处聚类个数修改为6.  

  至此训练前修改完成,可以在终端下通过命令进行训练

 

  对于缺陷检测、海底物体识别、无人机识别、字符识别等都可以通过这样的方式进行操作。

  -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

参数调优

  更深度了解配置文件的参数:

  subdivision可以减轻显卡的压力

    训练过程中出现nan的现象:

  • 如果运行中出现了nan, 是正常现象
  • 但如果全部都是nan的话,这就是训练过程出了问题

  一般而言,出现nan 是梯度爆炸、分母为0、log取到0或负值的情况。

  

  改小batch/增大subdivisions,或者关闭多尺度训练random = 0. random设置为1,会增加检测的精度

  Makefile中arch架构与自己显卡架构不匹配

    

  多GPU训练有可能会出现训练开始一段时间后 loss = nan、 IOU=nan,其他参数全为0的情况

  确保cfg文件是train模式时,batch、subdivisions不全为1  

  增加input图像的尺寸,例如在.cfg文件中设置(height=608,width=608)以上(看之前uav的配置文件 确实有608的设置),可以增加检测的精度

  random=1,可以增加模型精度。

    训练中参数的意义

  1. 迭代次数
  2. 总体的Loss损失
  3. 平均的loss损失,期望该值越小越好,一般来说当这个数值低于0.060730 avg就可以终止训练了。
  4. 当前的学习率,是在.cfg文件中设定的
  5. 当前批次训练花费的总时长
  6. 这个数值是9798*64的大小,表示到目前为止,参与训练的图片的总量

  (1) Region Avg IOU:平均的IOU,代表着预测的Bounding Box和Ground truth的交集与并集之比,(batch/subdivision)期望该值趋近于1。

  (2) Class:是标注物体的概率,期望该值趋近于1。

  (3) Obj:期望该值趋近于1。

  (4) No Obj:期望该值越来越小,但不为0。

  (5) AvgRecall:期望该值趋近于1,召回率比较高说明效果较好

  包含.5R和.75R,分别表示IOU取0.5和0.75时模型的召回率;

  (6) count表示输出有多少个目标总和

  这里的region 16和23 表示yolo v3-tiny 中两个尺度的输出,16卷积层为最大尺度的输出,使用较大的mask,适合预测较小的物体;23卷积层为最小尺度的输出,使用较小的mask,适合预测较大的物体。

  


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM