數據集准備:
使用的是網上公開的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,可以增加模型精度。
訓練中參數的意義
- 迭代次數
- 總體的Loss損失
- 平均的loss損失,期望該值越小越好,一般來說當這個數值低於0.060730 avg就可以終止訓練了。
- 當前的學習率,是在.cfg文件中設定的
- 當前批次訓練花費的總時長
- 這個數值是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,適合預測較大的物體。