1. 程式人生 > >【計算機視覺】【神經網路與深度學習】YOLO v2 detection訓練自己的資料

【計算機視覺】【神經網路與深度學習】YOLO v2 detection訓練自己的資料

轉自:http://blog.csdn.net/hysteric314/article/details/54097845

說明

這篇文章是訓練YOLO v2過程中的經驗總結,我使用YOLO v2訓練一組自己的資料,訓練後的model,在閾值為.25的情況下,Recall值是95.54%,Precision 是97.27%。 
需要注意的是,這一訓練過程可能只對我自己的訓練集有效,因為我是根據我這一訓練集的特徵來對YOLO程式碼進行修改,可能對你的資料集並不適用,所以僅供參考。

我的資料集

1,用於訓練的資料集一共1003張圖片和1003個與圖片對應的標記資訊(xml格式)。 
2,圖片的格式是 jpg,解析度都是384*288,圖片的命名從0000.jpg到1002.jpg,與VOC資料集的命名方式差不多。 
3,標記資訊的格式xml,命名從0000.xml到1002.xml,標記內容格式與VOC中標記資訊的格式類似,某一xml中具體內容如下:

<annotation>
    <folder>Image</folder>
    <filename>0000</filename>
    <source>
        <database>Pedestrian_ultrared</database>
    </source>
    <size>
        <width>384</width>
        <height>288</height>
    </size
>
<object> <name>n00000001</name> <bndbox> <xmin>232</xmin> <xmax>248</xmax> <ymin>161</ymin> <ymax>203</ymax> </bndbox> </object> </annotation>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

4,一共1003組資訊,選擇900組用於訓練(Train),103組用於驗證(Validation)。 
5,這個資料集只包含一個類別:人。當然也可以說是兩個類別:人、背景。

我要訓練的資料集的有以上這些特點,它資料的格式和VOC訓練資料集的格式很相似,而YOLO v2預設是能夠訓練VOC資料集的。為了讓YOLO v2能像訓練VOC資料集一樣訓練我自己的資料集,我對程式碼進行以下三個方面的修改: 
一:修改分類的個數:在程式碼中,預設VOC資料集是20類,而我要改成1類。 
二:準備txt文件:VOC訓練資料集中會自帶幾個txt文件,用來指明檔名或者路徑地址,而如果你使用你自己的資料可能就需要自己生成這些文件。 
三:修改程式碼中路徑資訊,把程式碼中VOC訓練資料集的路徑改成自己訓練資料集的路徑。

把20類改成1類

  1. cfg/voc.data檔案中:

    • classes 改成1。
    • names=data/pasacal.names。
    • pasacal.names這一個檔案要存在於darknet目錄下的data資料夾裡,沒有的話可以自己在那個目錄下建立一個pasacal.txt,加上內容之後,修改檔案字尾名變成pasacal.names即可,當然名字和路徑都可以自己定義。這個檔案中的行數要和類數一致,每一行都是一個類別的名字。比如我的這一檔案中就只有一行資料:“person”。這個檔案在測試你訓練的model的時候會用到,系統會在圖片上畫出bounding box,bounding box上面的文字,也就是這個框中物體的名字,應該就來自這個檔案。
  2. cfg/yolo_voc.cfg檔案中 :

    • 【region】層中 classes 改成1。
    • 【region】層上方第一個【convolution】層,其中的filters值要進行修改,改成(classes+ coords+ 1)* (NUM) ,我的情況中:(1+4+1)* 5=30,我把filters 的值改成了30。
  3. src/yolo.c 檔案中 :

    • 位置大約第14行左右改成:char *voc_names={“n00000001”},原來裡面有20類的名字,我改成了唯一1類的名字。
    • 位置大約第328行左右,修改draw_detection這個函式最後一個引數:20改成1。這個函式用於把系統檢測出的框給畫出來,並把畫完框的圖片傳回第一個引數im中,用於儲存和顯示。
    • 位置大約第361行左右,demo函式中,倒數第三個引數我把20改成了1,雖然不知道有沒有用,反正對結果沒什麼影響。
  4. src/yolo_kernels.cu 檔案中 :

    • 位置第62行,draw_detection這個函式最後一個引數20改成1。
  5. scripts/voc_label.py 檔案中 :

    • 位置第9行改成:classes=[“n00000001”],因為我只有一類。

準備txt文件

一共需要準備四個txt格式文件:train.txt與val.txt,infrared_train.txt與infrared_val.txt:

train.txt與val.txt 
在生成infrared_train.txt與infrared_val.txt這兩個檔案時,會分別用到這兩個文件。文件裡包含了用於訓練/驗證的圖片的名稱,裡面的資料組成很簡單,每行都是一個圖片的名稱,並不包含圖片的字尾(.jpg),比如文件中: 
第一行是: 0000 
第二行是: 0001 
…..

生成指令碼:creat_list.py:

#這個小指令碼是用來開啟圖片檔案所在資料夾,把前900個用於訓練的圖片的名稱儲存在tain.txt,後103個用於驗證的圖片儲存在val.txt
import os
from os import listdir, getcwd
from os.path import join
if __name__ == '__main__':
    source_folder='/home/yolo_v2_tinydarknet/darknet/infrared/image/dout/'#地址是所有圖片的儲存地點
    dest='/home/yolo_v2_tinydarknet/darknet/infrared/train.txt' #儲存train.txt的地址
    dest2='/home/yolo_v2_tinydarknet/darknet/infrared/val.txt'  #儲存val.txt的地址
    file_list=os.listdir(source_folder)       #賦值圖片所在資料夾的檔案列表
    train_file=open(dest,'a')                 #開啟檔案
    val_file=open(dest2,'a')                  #開啟檔案
    for file_obj in file_list:                #訪問檔案列表中的每一個檔案
        file_path=os.path.join(source_folder,file_obj) 
        #file_path儲存每一個檔案的完整路徑
        file_name,file_extend=os.path.splitext(file_obj)
        #file_name 儲存檔案的名字,file_extend儲存副檔名
        file_num=int(file_name) 
        #把每一個檔案命str轉換為 數字 int型 每一檔名字都是由四位數字組成的  如 0201 代表 201     高位補零  
        if(file_num<900):                     #保留900個檔案用於訓練
            #print file_num
            train_file.write(file_name+'\n')  #用於訓練前900個的圖片路徑儲存在train.txt裡面,結尾加回車換行
        else :
            val_file.write(file_name+'\n')    #其餘的檔案儲存在val.txt裡面
    train_file.close()#關閉檔案
    val_file.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

infrared_train.txt與infrared_val.txt 
這個文件用於告訴訓練系統哪些圖片是用來進行訓練,哪些是用於驗證的。 
文件裡包含了所有用於訓練/驗證的圖片的完整路徑,每一行都是一個圖片的完整路徑,例如 
第一行是: /home/yolo_v2_tinydarknet/darknet/infrared/image/dout/0000.jpg 
第二行是 :/home/yolo_v2_tinydarknet/darknet/infrared/image/dout/0001.jpg 
…..

生成指令碼:voc_label_change.py

#此指令碼修改自voc_label.py。修改的原因是:我的訓練集跟voc有些不同。
#由於資料集中包括用於訓練的資料和用於驗證的資料,所以此指令碼可能需要分別對這兩種資料各執行一次,對兩種資料只需要簡單地註釋掉相應語句即可
#這個指令碼需要train.txt ,這個檔案是我用指令碼creat_list.py生成的,儲存了用於訓練的圖片的名字id,儲存了用於訓練的圖片的名字
#這個指令碼需要val.txt檔案,這個檔案是我用指令碼creat_list.py生成的,儲存了用於驗證的圖片的名字id,儲存了用於驗證的圖片的名字
#這個指令碼還需要xml格式的標籤檔案,我的訓練集xml檔案的格式與voc2007的類似,xml檔案的名稱與對應的用於訓練的圖片的名稱相同
#這個指令碼會生成 indrared_train.txt檔案 ,用於儲存每一用於訓練的圖片的完整的路徑,隨後會被voc.data yolo.c使用
#這個指令碼會生成 indrared_val.txt檔案 ,用於儲存每一用於驗證的圖片的完整的路徑,隨後會被voc.data yolo.c使用
#這個指令碼還會生成 txt格式的yolo可識別的標籤檔案,轉換自每一個用於訓練或驗證的圖片對應的xml檔案,txt格式的檔名稱與對應的xml檔名相同,但是內容不同,副檔名不同
#這個指令碼 需要與圖片對應的xml檔案所在的地址,需要,轉換後生成的txt的完整儲存路徑
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
#sets=[('2012', 'train'), ('2012', 'val'), ('2007', 'train'), ('2007', 'val'), ('2007', 'test')] #按照自己的檔案格式改的,不需要判斷是那個voc資料包
classes = ["n00000001"]#因為我的資料集只有一個類別
def convert(size, box):#voc_label.py 自帶的函式,沒有修改
    dw = 1./size[0]
    dh = 1./size[1]
    x = (box[0] + box[1])/2.0
    y = (box[2] + box[3])/2.0
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x*dw
    w = w*dw
    y = y*dh
    h = h*dh
    return (x,y,w,h)
def convert_annotation(image_id):
    #in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id))
    in_file = open('/home/yolo_v2_tinydarknet/darknet/infrared/labels/dout_original/%s.xml'%(image_id))#與圖片對應的xml檔案所在的地址
    out_file = open('/home/yolo_v2_tinydarknet/darknet/infrared/labels/%s.txt'%(image_id),'w') #與此xml對應的轉換後的txt,這個txt的儲存完整路徑
    tree=ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')  #訪問size標籤的資料
    w = int(size.find('width').text)#讀取size標籤中寬度的資料
    h = int(size.find('height').text)#讀取size標籤中高度的資料

    for obj in root.iter('object'):
       # difficult = obj.find('difficult').text   #由於自己的檔案裡面沒有diffcult這一個標籤,所以就遮蔽之
        cls = obj.find('name').text
        if cls not in classes :#or int(difficult) == 1:
            continue
        cls_id = classes.index(cls)
        xmlbox = obj.find('bndbox')   #訪問boundbox標籤的資料並進行處理,都按yolo自帶的程式碼來,沒有改動
        b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text))
        bb = convert((w,h), b)
        out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')

#image_ids = open('VOCdevkit/VOC%s/ImageSets/Main/%s.txt'%(year, image_set)).read().strip().split()  #之前程式碼是按照sets裡面的字元來訪問儲存有圖片名字的train或者val的txt檔案
image_ids = open('/home/yolo_v2_tinydarknet/darknet/infrared/train.txt').read().strip().split()  #如果是訓練集資料開啟這一行,註釋下一行
#image_ids = open('/home/yolo_v2_tinydarknet/darknet/infrared/val.txt').read().strip().split()  #如果是驗證資料集資料開啟這一行,註釋上一行
#list_file = open('%s_%s.txt'%(year, image_set), 'w')
list_file = open('infrared_train.txt', 'w')     #把結果寫入到indrared_train.txt檔案中,如果是訓練集資料開啟這一行,註釋下一行
#list_file = open('infrared_val.txt', 'w')     #把結果寫入到indrared_train.txt檔案中,如果是驗證資料集資料開啟這一行,註釋上一行
for image_id in image_ids:
    #list_file.write('%s/VOCdevkit/VOC%s/JPEGImages/%s.jpg\n'%(wd, year, image_id))
    list_file.write('/home/yolo_v2_tinydarknet/darknet/infrared/image/dout/%s.jpg\n'%(image_id))  #把每一用於訓練或驗證的圖片的完整的路徑寫入到infrared_train.txt中  這個檔案會被voc.data yolo.c呼叫
    convert_annotation(image_id)   #把圖片的名稱id傳給函式,用於把此圖片對應的xml中的資料轉換成yolo要求的txt格式
list_file.close() #關閉檔案
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

修改路徑

  1. cfg/voc.data檔案中:

    • train = /home/yolo_v2_tinydarknet/darknet/infrared/infrared_train.txt //infrared_train.txt的完整路徑
    • valid = /home/yolo_v2_tinydarknet/darknet/infrared/infrared_val.txt //infrared_val.txt的完整路徑
    • backup = /home/yolo_v2_tinydarknet/darknet/backup/ /* 這個路徑是YOLO用於備份的,在訓練過程中YOLO會不斷地對產生的weights檔案進行備份,darknet目錄下就自帶一個backup資料夾,這個路徑指向那裡。*/
  2. src/yolo.c 檔案中:

    • train_yolo函式中:

      char *train_images =" /home/yolo_v2_tinydarknet/darknet/infrared/infrared_train.txt";//infrared_train.txt的完整路徑
      char *backup_directory = "/home/yolo_v2_tinydarknet/darknet/backup/";//可以修改為自己的路徑
      • 1
      • 2
      • 1
      • 2
    • validate_yolo函式中:

      char *base = "/home/yolo_v2_tinydarknet/darknet/results/comp4_det_test_";//可以修改自己的路徑,好像是用於儲存測試結果
      list *plist=get_paths("/home/yolo_v2_tinydarknet/darknet/infrared/infrared_val.txt");//infrared_val.txt的完整路徑
      • 1
      • 2
      • 1
      • 2
    • validate_yolo_recall函式中:

      char *base = "/home/yolo_v2_tinydarknet/darknet/results/comp4_det_test_";//可以修改自己的路徑
      list *plist = get_paths("/home/yolo_v2_tinydarknet/darknet/infrared/infrared_val.txt");//infrared_val.txt的完整路徑
      • 1
      • 2
      • 1
      • 2
  3. src/detector.c 檔案中: 
    • 位置第375行改成:list *plist = get_paths(“/home/yolo_v2_tinydarknet/darknet/infrared/infrared_val.txt”);//改成infrared_val.txt的完整路徑
    • 需要注意的是,這個檔案裡的validate_detector_recall函式是用來計算輸出recall值的,後面會說。

對了,可能你注意到,輸入到系統的infrared_train.txt或者infrared_val.txt都只是圖片的完整路徑,你也知道,要進行訓練的話,除了需要圖片還需要標記資訊,然而標記資訊僅僅用我的指令碼voc_label_change.py從xml轉換成了YOLO可識別的txt格式,但是它們的完整路徑並沒有輸入進系統,那麼系統該怎麼找到它們呢?

因為在訓練集中,一個圖片檔案和這一圖片檔案對應標記檔案,他們倆除了字尾名之外其餘的名稱是一樣的,所以src/yolo.c中有以下語句:

          find_replace(path, "dout", "labels", labelpath);
          find_replace(labelpath, "JPEGImages", "labels", labelpath);
          find_replace(labelpath, ".jpg", ".txt", labelpath);
          find_replace(labelpath, ".JPEG", ".txt", labelpath);
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

函式會找到路徑中的圖片字尾名.jpg,自動替換成.txt。比如: 
/home/yolo_v2_tinydarknet/darknet/infrared/image/dout/0000.jpg 
自動替換後變成了 
/home/yolo_v2_tinydarknet/darknet/infrared/image/dout/0000.txt 
所以在使用voc_label_new.py轉換生成txt格式的標記資訊之後,只需要把這些txt格式的標記檔案複製到圖片所在的目錄下即可。系統根據替換後的路徑地址來讀取對應標記檔案。

開始訓練!

通過複雜地修改和準備,終於可以開始訓練了 
我是按照YOLO的官方指南來的,首先下載一個預訓練的model(當然你也可以自己生成),放到darkent/目錄下。 
下載地址 (76 MB):http://pjreddie.com/media/files/darknet19_448.conv.23 
然後執行指令:./darknet detector train cfg/voc.data cfg/yolo_voc.cfg darknet19_448.conv.23 
就可以開始訓練了,系統預設會迭代45000次,我花了一週時間才訓練完。 
當然迭代次數是可以修改的,應該是在cfg/yolo_voc.cfg修改max_batches的值就行。

評估效能

經過漫長的訓練過程,model終於訓練好了,為了評估效能,可以使用以下指令 
./darknet detector recall cfg/voc.data cfg/yolo_voc.cfg backup/yolo_voc_final.weights

需要注意的是,在使用這個指令之前,我先修改一下src/detector.c 這一函式

  • 位置第375行改成:list *plist = get_paths(“/home/yolo_v2_tinydarknet/darknet/infrared/infrared_val.txt”);//改成infrared_val.txt的完整路徑
  • 執行上面的指令會呼叫validate_detector_recall函式,這個函式中有個引數thresh(閾值),預設的值是.001,這個預設值設的很小,會讓系統識別出更多的框來,導致proposals值激增,還會讓recall值變高,達到98.5%。最終我改成了 .25。
  • 上面的函式只會顯示出recall值,沒有precision值,precision的值計算方法是:識別為正確的個數/畫了多少個框,所以我修改了程式碼。我把第447行顯示結果的程式碼修改為 :
fprintf(stderr, "ID:%5d Correct:%5d Total:%5d\tRPs/Img: %.2f\tIOU: %.2f%%\tRecall:%.2f%%\t", i, correct, total, (float)proposals/(i+1), avg_iou*100/total, 100.*correct/total);
fprintf(stderr, "proposals:%5d\tPrecision:%.2f%%\n",proposals,100.*correct/(float)proposals); 
  • 1
  • 2
  • 1
  • 2

我執行後顯示的結果是: 
….. 
ID: 101 Correct: 106 Total: 111 RPs/Img: 1.07 IOU: 82.00% Recall:95.50% proposals: 109 Precision:97.25% 
ID: 102 Correct: 107 Total: 112 RPs/Img: 1.07 IOU: 82.11% Recall:95.54% proposals: 110 Precision:97.27%

結果中的引數,我的理解是:

Correct :可以理解為正確地畫了多少個框,遍歷每張圖片的Ground Truth,網路會預測出很多的框,對每一Groud Truth框與所有預測出的框計算IoU,在所有IoU中找一個最大值,如果最大值超過一個預設的閾值,則correct加一。

Total:一共有多少個Groud Truth框。

Rps/img:p 代表proposals, r 代表region。 意思就是平均下來每個圖片會有預測出多少個框。預測框的決定條件是,預測某一類的概率大於閾值。在validation_yolo_recall函式中,預設的這一個閾值是0.001,這一閾值設定的比較低,這就會導致會預測出很多個框,但是這樣做是可以提升recall的值,一般yolo用於畫框的預設值是.25,使用這個閾值會讓畫出來的框比較準確。而validation_yolo_recall使用的閾值改成。25的時候,Rps/img 值會降低,recall的值會降低,所以validation_yolo_recall預設使用一個較低的閾值,有可能作者的目的就是為了提高recall值,想在某種程度上體現網路的識別精度比較高。

要說的

1,這是我的經驗總結,可能對你並沒有意義,僅供參考。 
2,如果你發現了錯誤,歡迎留言指正,多謝! 
3,接下來一段時間可能要比較忙了,馬上就要中期了,不過如果有新進展的話還會繼續更新部落格。 
4,具體的YOLOv2的安裝,教程很多,在這就不贅述了,可以參考他們的官網:http://pjreddie.com/darknet/yolo/ 
5,如果對YOLO感興趣,可以跳牆去訪問這個論壇,YOLO作者會親自答疑:https://groups.google.com/forum/#!forum/darknet