1. 程式人生 > 實用技巧 >tensorflow yolov3訓練自己的資料集,詳細教程

tensorflow yolov3訓練自己的資料集,詳細教程

這個教程是我在自己學習的過程中寫的,當作一個筆記,寫的比較詳細
在github上下載yolov3的tensorflow1.0版本:
https://github.com/YunYang1994/tensorflow-yolov3
在19年12月,發現網上訓練的教程大部分似乎已經過時了,因為作者對開原始碼進行了部分修改
其實在作者的readme上已經寫了訓練的方法,但是不是那麼詳細,於是記錄下
由於本人水平有限,如果文章有不當之處還望評論區指出

一,製作訓練集

1,打標籤
訓練集需要實驗labelimg工具進行製作,這裡的資料集格式採用的是voc格式:
labelimg下載地址
實驗labelimg打好標籤後會生成兩個資料夾:
Annotations —存放標記的圖片
JPEGImages —存放xml格式的標籤
這裡不做多餘的解釋
2,按照voc資料集格式建立資料夾

因為作者給的模型是訓練VOC資料集的模型,所以訓練我們自己的資料集的時候也需要改為VOC格式的,VOC格式解析:
第一步:首先了解VOC2007資料集的格式

  1. 1)JPEGImages資料夾

資料夾裡包含了訓練圖片和測試圖片,混放在一起

  1. 2)Annatations資料夾

資料夾存放的是xml格式的標籤檔案,每個xml檔案都對應於JPEGImages資料夾的一張圖片

  1. 3)ImageSets資料夾

Action存放的是人的動作,我們暫時不用

Layout存放的人體部位的資料。我們暫時不用

Main存放的是影象物體識別的資料,分為20類,當然我們自己製作就不一定了,如果你有精力,Main裡面有test.txt , train.txt, val.txt ,trainval.txt.這四個檔案我們後面會生成
train中存放的是訓練使用的資料,每一個class的train資料都有5717個。
val中存放的是驗證結果使用的資料,每一個class的val資料都有5823個。
trainval為訓練和驗證的圖片檔案的檔名列表 。
Segmentation存放的是可用於分割的資料
如果你下載了VOC2007資料集,那麼把它解壓,把各個資料夾裡面的東西刪除,保留資料夾名字。如果沒下載,那麼就仿照他的資料夾格式,按照這個目錄格式建立資料夾:

然後分別把標記的圖片放入JPEGImages資料夾,標籤xml檔案放入Annotations資料夾:


3,劃分訓練集和測試集
訓練時要有測試集和訓練集,通過劃分放在
VOCdevkit\VOC2008\ImageSets\Main
資料夾下,這裡可以使用一段python程式碼按照9:1進行隨機劃分:
在VOC2008資料夾建立split.py

import os
import random
import sys

if len(sys.argv) < 2:
    print("no directory specified, please input target directory")
    exit()

root_path = sys.argv[1]

xmlfilepath = root_path + '/Annotations'

txtsavepath = root_path + '/ImageSets/Main'

if not os.path.exists(root_path):
    print("cannot find such directory: " + root_path)
    exit()

if not os.path.exists(txtsavepath):
    os.makedirs(txtsavepath)

trainval_percent = 0.9
train_percent = 0.8
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)

print("train and val size:", tv)
print("train size:", tr)

ftrainval = open(txtsavepath + '/trainval.txt', 'w')
ftest = open(txtsavepath + '/test.txt', 'w')
ftrain = open(txtsavepath + '/train.txt', 'w')
fval = open(txtsavepath + '/val.txt', 'w')

for i in list:
    name = total_xml[i][:-4] + '\n'
    if i in trainval:
        ftrainval.write(name)
        if i in train:
            ftrain.write(name)
        else:
            fval.write(name)
    else:
        ftest.write(name)

ftrainval.close()
ftrain.close()
fval.close()
ftest.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

開啟控制檯,執行該python檔案,後跟Annotation的目錄即可進行劃分:

python .\split.py F:\dateset\hat_data\VOC\test\VOCdevkit\VOC2007
  • 1

在這裡插入圖片描述
train為訓練集,test為驗證集
4,根據劃分結果製作訓練集
上面程式碼已經實現了將資料集劃分為訓練集和驗證集,但是tensorflow yolov3作者寫的模型要求的資料集格式為:


所以我們需要寫一個小指令碼,根據train.txt 和test.txt將資料集進行更改

import os
from shutil import copyfile
#根據tarin.txt和test.txt將資料集分為標準資料集
train_text_path = 'F:/dateset/hat_data/VOCdevkit/VOC2007/ImageSets/Main/train.txt'
test_text_path = 'F:/dateset/hat_data/VOCdevkit/VOC2007/ImageSets/Main/test.txt'
#圖片存放地址
image_path = 'F:/dateset/hat_data/VOCdevkit/VOC2007/JPEGImages'
#xml檔案存放地址
xml_path = 'F:/dateset/hat_data/VOCdevkit/VOC2007/Annotations'

#輸出的目錄
outdir = 'F:/dateset/hat_data'
#建立各級資料夾
test_xml_out = os.path.join(outdir,'VOC/test/VOCdevkit/VOC2007/Annotations')
os.makedirs(test_xml_out)
os.makedirs(os.path.join(outdir,'VOC/test/VOCdevkit/VOC2007/ImageSets/Layout'))
os.makedirs(os.path.join(outdir,'VOC/test/VOCdevkit/VOC2007/ImageSets/Main'))
os.makedirs(os.path.join(outdir,'VOC/test/VOCdevkit/VOC2007/ImageSets/Segmentation'))
test_img_out = os.path.join(outdir,'VOC/test/VOCdevkit/VOC2007/JPEGImages')
os.makedirs(test_img_out)
os.makedirs(os.path.join(outdir,'VOC/test/VOCdevkit/VOC2007/SegmentationClass'))
os.makedirs(os.path.join(outdir,'VOC/test/VOCdevkit/VOC2007/SegmentationObject'))
train_xml_out = os.path.join(outdir,'VOC/train/VOCdevkit/VOC2007/Annotations')
os.makedirs(train_xml_out)
os.makedirs(os.path.join(outdir,'VOC/train/VOCdevkit/VOC2007/ImageSets/Layout'))
os.makedirs(os.path.join(outdir,'VOC/train/VOCdevkit/VOC2007/ImageSets/Main'))
os.makedirs(os.path.join(outdir,'VOC/train/VOCdevkit/VOC2007/ImageSets/Segmentation'))
train_img_out = os.path.join(outdir,'VOC/train/VOCdevkit/VOC2007/JPEGImages')
os.makedirs(train_img_out)
os.makedirs(os.path.join(outdir,'VOC/train/VOCdevkit/VOC2007/SegmentationClass'))
os.makedirs(os.path.join(outdir,'VOC/train/VOCdevkit/VOC2007/SegmentationObject'))



with open(train_text_path) as f:
    lines = f.readlines()
    for i in lines:
        img_save_path = os.path.join(train_img_out,i.rstrip('\n')+'.jpg')
        xml_save_path = os.path.join(train_xml_out, i.rstrip('\n') + '.xml')
        copyfile(os.path.join(image_path,i.rstrip('\n')+'.jpg'),img_save_path)
        copyfile(os.path.join(xml_path, i.rstrip('\n') + '.xml'), xml_save_path)
        print(i)
with open(test_text_path) as f:
    lines = f.readlines()
    for i in lines:
        img_save_path = os.path.join(test_img_out, i.rstrip('\n') + '.jpg')
        xml_save_path = os.path.join(test_xml_out, i.rstrip('\n') + '.xml')
        copyfile(os.path.join(image_path, i.rstrip('\n') + '.jpg'), img_save_path)
        copyfile(os.path.join(xml_path, i.rstrip('\n') + '.xml'), xml_save_path)
        print(i)
  • 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

根據這個小指令碼就可進行劃分為規定的格式,如果資料集的量比較大,可能有點慢,當然你也可以進行手動劃分,省略前面幾步操作,只要最終目錄結構滿足作者的格式就行

5,分別在test和train下面執行split.py指令碼

 python .\split.py F:\dateset\hat_data\VOC\train\VOCdevkit\VOC2007
  • 1
 python .\split.py F:\dateset\hat_data\VOC\test\VOCdevkit\VOC2007
  • 1

二,製作標準資料集txt檔案

作者在github上宣告,訓練需要兩個檔案,如下所示:

dataset.txt:

xxx/xxx.jpg 18.19,6.32,424.13,421.83,20 323.86,2.65,640.0,421.94,20 
xxx/xxx.jpg 48,240,195,371,11 8,12,352,498,14
 image_path x_min, y_min, x_max, y_max, class_id  x_min, y_min ,..., class_id 
 make sure that x_max < width and y_max < height
  • 1
  • 2
  • 3
  • 4

class.names:

person
bicycle
car
...
toothbrush
  • 1
  • 2
  • 3
  • 4
  • 5

1,生成dataset.txt
其實前幾步都是為這兩步做準備,通過目錄下的 scripts/voc_annotation.py就可以生成dataset.txt檔案,但是需要改一些程式碼引數:
只需更改 classes為自己的類別

classes = ['hat','person']
  • 1


default 更改為你自己的看註釋

    #default 更改為你自己資料集VOC的目錄
    parser.add_argument("--data_path", default="F:/dateset/hat_data/VOC")
    #default 更改為voc_train.txt的存放的位置
    parser.add_argument("--train_annotation", default="../data/dataset/voc_train.txt")
    parser.add_argument("--test_annotation",  default="../data/dataset/voc_test.txt")
    flags = parser.parse_args()

    if os.path.exists(flags.train_annotation):os.remove(flags.train_annotation)
    if os.path.exists(flags.test_annotation):os.remove(flags.test_annotation)
    
    #更改訓練集和測試集的相對路徑
    num1 = convert_voc_annotation(os.path.join(flags.data_path, 'train/VOCdevkit/VOC2007'), 'trainval', flags.train_annotation, False)
    num3 = convert_voc_annotation(os.path.join(flags.data_path, 'test/VOCdevkit/VOC2007'),  'trainval', flags.test_annotation, False)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

其他預設即可:
更改後的程式碼:

import os
import argparse
import xml.etree.ElementTree as ET

def convert_voc_annotation(data_path, data_type, anno_path, use_difficult_bbox=True):

    #更改為你自己的類別
    classes = ['hat','person']
    img_inds_file = os.path.join(data_path, 'ImageSets', 'Main', data_type + '.txt')
    with open(img_inds_file, 'r') as f:
        txt = f.readlines()
        image_inds = [line.strip() for line in txt]

    with open(anno_path, 'a') as f:
        for image_ind in image_inds:
            image_path = os.path.join(data_path, 'JPEGImages', image_ind + '.jpg')
            annotation = image_path
            label_path = os.path.join(data_path, 'Annotations', image_ind + '.xml')
            root = ET.parse(label_path).getroot()
            objects = root.findall('object')
            for obj in objects:
                difficult = obj.find('difficult').text.strip()
                if (not use_difficult_bbox) and(int(difficult) == 1):
                    continue
                bbox = obj.find('bndbox')
                class_ind = classes.index(obj.find('name').text.lower().strip())
                xmin = bbox.find('xmin').text.strip()
                xmax = bbox.find('xmax').text.strip()
                ymin = bbox.find('ymin').text.strip()
                ymax = bbox.find('ymax').text.strip()
                annotation += ' ' + ','.join([xmin, ymin, xmax, ymax, str(class_ind)])
            print(annotation)
            f.write(annotation + "\n")
    return len(image_inds)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    #default 更改為你自己資料集VOC的目錄
    parser.add_argument("--data_path", default="F:/dateset/hat_data/VOC")
    #default 更改為voc_train.txt的存放的位置
    parser.add_argument("--train_annotation", default="../data/dataset/voc_train.txt")
    parser.add_argument("--test_annotation",  default="../data/dataset/voc_test.txt")
    flags = parser.parse_args()

    if os.path.exists(flags.train_annotation):os.remove(flags.train_annotation)
    if os.path.exists(flags.test_annotation):os.remove(flags.test_annotation)

    #更改訓練集和測試集的相對路徑
    num1 = convert_voc_annotation(os.path.join(flags.data_path, 'train/VOCdevkit/VOC2007'), 'trainval', flags.train_annotation, False)
    num3 = convert_voc_annotation(os.path.join(flags.data_path, 'test/VOCdevkit/VOC2007'),  'trainval', flags.test_annotation, False)
    print('=> The number of image for train is: %d\tThe number of image for test is:%d' %(num1 , num3))

  • 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

更改後執行該python檔案:


這就生成了dataset.txt
2,更改voc.names的類別

修改為自己的類別:

3,修改配置檔案
編輯您的檔案./core/config.py以進行一些必要的配置

_C.YOLO.CLASSES                = "./data/classes/voc.names"
__C.TRAIN.ANNOT_PATH            = "./data/dataset/voc_train.txt"
__C.TEST.ANNOT_PATH             = "./data/dataset/voc_test.txt"
  • 1
  • 2
  • 3

到這裡已經配置完成,已經可以訓練了

三,訓練資料

1)從頭開始訓練:

$ python train.py
$ tensorboard --logdir ./data
  • 1
  • 2


(2)從COCO配置訓練(推薦):

$ cd checkpoint
$ wget https://github.com/YunYang1994/tensorflow-yolov3/releases/download/v1.0/yolov3_coco.tar.gz
$ tar -xvf yolov3_coco.tar.gz
$ cd ..
$ python convert_weight.py --train_from_coco
$ python train.py
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.2評估VOC資料集

$ python evaluate.py
$ cd mAP
$ python main.py -na
原文:https://blog.csdn.net/qq_38441692/article/details/103652760