1. 程式人生 > >實現yolo3模型訓練自己的資料集總結

實現yolo3模型訓練自己的資料集總結

     經過兩天的努力,借鑑網上眾多部落格,在自己電腦上實現了使用yolo3模型訓練自己的資料集並進行測試圖片。本文主要是我根據下面參考文章一步步實施過程的總結,可能沒參考文章中那麼詳細,但是會包含一些參考文章中沒提及的容易掉坑的小細節,建議讀者結合參考文章一起看,一步步走即可。首先貼出本文主要參考的文章以及程式碼出處:

程式碼:https://github.com/qqwweee/keras-yolo3

參考文章:https://blog.csdn.net/patrick_Lxc/article/details/80615433

一.下載專案原始碼,進行快速測試

從上面程式碼連結處下載整個專案原始碼。下載好後,首先根據github中指引進行快速測試。

yolo web:https://pjreddie.com/darknet/yolo

對應操作如下(命令列操作):

1.  wget https://pjreddie.com/media/files/yolov3.weights                                  

註釋:這裡wget為linux命令,windows系統可以直接訪問後面連結來下載yolov3權重檔案,也可以訪問yolo web去下載。

2. python convert.py yolov3.cfg yolov3.weights model_data/yolo.h5

註釋:執行convert.py檔案,此為將darknet的yolo轉換為可以用於keras的h5檔案,生成的h5被儲存在model_data下。命令中的convert.py和yolov3.vfg克隆下來後已經有了,不需要單獨下載。

3.用已經被訓練好的yolo.h5進行圖片識別測試。執行:python yolo.py

執行後會讓你輸入一張圖片的路徑,因為我準備的圖片(網上隨便找的)放在yolo.py同級目錄,所以直接輸入圖片名稱,沒有加路徑。

過程和結果如下圖所示:

以上結果表明快速開始專案成功,接下來我們進行搭建自己的資料集,進行模型的訓練以及訓練後模型用於測試圖片識別。

二.準備自己的資料集

可以按照上面參考文章裡面做法下載VOC資料集,然後清空裡面內容,保留檔案目錄結構。也可以直接手動建立如下目錄結構:

這裡面用到的資料夾是Annotation、ImageSets和JPEGImages。注意:需要在VOC2007再建立一個上級目錄VOCdevkit。

     其中資料夾Annotation中主要存放xml檔案,每一個xml對應一張影象,並且每個xml中存放的是標記的各個目標的位置和類別資訊,命名通常與對應的原始影象一樣;而ImageSets我們只需要用到Main資料夾,這裡面存放的是一些文字檔案,通常為train.txt、test.txt等,該文字檔案裡面的內容是需要用來訓練或測試的影象的名字;JPEGImages資料夾中放我們已按統一規則命名好的原始影象。

      原始圖片就不解釋了,而與原始圖片一 一對應的xml檔案,可以使用LabelImg工具,具體使用方法百度即可。工具可以從參考的部落格中的附帶地址下載,也可以自己網上找,很容易。

     將自己的圖片以及xml按照要求放好後,在VOC2007的同級目錄下建立convert_to_txt.py檔案,拷貝下面的程式碼,然後執行該py檔案。該程式碼是讀取上面的xml檔案中圖片名稱,並儲存在ImageSets/Main目錄下的txt檔案中。注意:此處txt中僅有圖片名稱。

程式碼:

import os
import random

trainval_percent = 0.1
train_percent = 0.9
xmlfilepath = 'Annotations'
txtsavepath = 'ImageSets\Main'
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)

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

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

ftrainval.close()
ftrain.close()
fval.close()
ftest.close()

    然後,回到從github上下載的原始碼所在目錄,執行其中的voc_annotation.py,會在當前目錄生成新的三個txt檔案,手動去掉名稱中2007_部分。當然,可以自己進入voc_annotation.py修改程式碼,使生成的txt檔名中不包含2007_。

注意:一開始VOC2007,也可以叫VOC2008之類,這樣此處的txt就會成為2008_xxx.txt。此外,有一個很關鍵的地方需要注意,必須修改,不然此處生成的三個新的txt檔案中僅僅比前面Main下的txt中多了圖片路徑而已,並不包含框box的資訊,這樣的話在後面的訓練步驟,由於沒有框的資訊,僅僅是圖片路徑和名稱資訊,是訓練不好的,即使可以得到訓練後的h5檔案,但是當用這樣的h5檔案去執行類似前面所說的測試圖片識別,效果就是將整幅圖框住,而不是框住你所要識別的部分。

故所要做的是:在執行voc_annotation.py之前,開啟它,進行修改。將其中最上面的sets改為你自己的,比如2012改為我得2007,要和前面的目錄年份保持一致。還需要將最上面的classes中的內容,改為你自己xml檔案中object屬性中name屬性的值。你有哪些name值,就改為哪些,不然其中讀取xml框資訊的程式碼就不會執行。

上面是我的xml中一個object截圖,這裡的name實際上為你用LableIma工具畫框時候給那個框的命名值。

至此,自己資料集的準備工作就完成了。

三.修改一些檔案,然後執行訓練

首先是修改model_data下的檔案,放入你的類別,coco,voc這兩個檔案都需要修改。這裡的命名會成為最終檢測圖片時候框的框上的名稱。

其次是yolov3.cfg檔案

這一步事後我和同學討論了下,得出的結論是,從0開始訓練自己的模型,則不需要下面的修改步驟,而如果想用遷移學習思想,用已經預訓練好的權重接著訓練,則需要下面的修改步驟。

IDE裡直接開啟cfg檔案,ctrl+f搜 yolo, 總共會搜出3個含有yolo的地方。

每個地方都要改3處,filters:3*(5+len(classes));

                                    classes: len(classes) = 1,我只識別一種,所以為1

                                    random:原來是1,視訊記憶體小改為0   

如果要用預訓練的權重接著訓練,則需要執行以下程式碼:然後執行原train.py就可以了。原train.py中有載入預訓練權重的程式碼,並凍結部分層數,在此基礎上進行訓練。可以修改凍結層數。

python convert.py -w yolov3.cfg yolov3.weights model_data/yolo_weights.h5

這個在github和參考的文章中均提到。

如果不用預訓練的權重,上一步不用執行(執行也沒影響),但是下面的train.py需要修改,改為如下所示(程式碼出處為上文提到的參考部落格中):直接複製替換原train.py即可

"""
Retrain the YOLO model for your own dataset.
"""
import numpy as np
import keras.backend as K
from keras.layers import Input, Lambda
from keras.models import Model
from keras.callbacks import TensorBoard, ModelCheckpoint, EarlyStopping

from yolo3.model import preprocess_true_boxes, yolo_body, tiny_yolo_body, yolo_loss
from yolo3.utils import get_random_data


def _main():
    annotation_path = 'train.txt'
    log_dir = 'logs/000/'
    classes_path = 'model_data/voc_classes.txt'
    anchors_path = 'model_data/yolo_anchors.txt'
    class_names = get_classes(classes_path)
    anchors = get_anchors(anchors_path)
    input_shape = (416,416) # multiple of 32, hw
    model = create_model(input_shape, anchors, len(class_names) )
    train(model, annotation_path, input_shape, anchors, len(class_names), log_dir=log_dir)

def train(model, annotation_path, input_shape, anchors, num_classes, log_dir='logs/'):
    model.compile(optimizer='adam', loss={
        'yolo_loss': lambda y_true, y_pred: y_pred})
    logging = TensorBoard(log_dir=log_dir)
    checkpoint = ModelCheckpoint(log_dir + "ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5",
        monitor='val_loss', save_weights_only=True, save_best_only=True, period=1)
    batch_size = 10
    val_split = 0.1
    with open(annotation_path) as f:
        lines = f.readlines()
    np.random.shuffle(lines)
    num_val = int(len(lines)*val_split)
    num_train = len(lines) - num_val
    print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))

    model.fit_generator(data_generator_wrap(lines[:num_train], batch_size, input_shape, anchors, num_classes),
            steps_per_epoch=max(1, num_train//batch_size),
            validation_data=data_generator_wrap(lines[num_train:], batch_size, input_shape, anchors, num_classes),
            validation_steps=max(1, num_val//batch_size),
            epochs=500,
            initial_epoch=0)
    model.save_weights(log_dir + 'trained_weights.h5')

def get_classes(classes_path):
    with open(classes_path) as f:
        class_names = f.readlines()
    class_names = [c.strip() for c in class_names]
    return class_names

def get_anchors(anchors_path):
    with open(anchors_path) as f:
        anchors = f.readline()
    anchors = [float(x) for x in anchors.split(',')]
    return np.array(anchors).reshape(-1, 2)

def create_model(input_shape, anchors, num_classes, load_pretrained=False, freeze_body=False,
            weights_path='model_data/yolo_weights.h5'):
    K.clear_session() # get a new session
    image_input = Input(shape=(None, None, 3))
    h, w = input_shape
    num_anchors = len(anchors)
    y_true = [Input(shape=(h//{0:32, 1:16, 2:8}[l], w//{0:32, 1:16, 2:8}[l], \
        num_anchors//3, num_classes+5)) for l in range(3)]

    model_body = yolo_body(image_input, num_anchors//3, num_classes)
    print('Create YOLOv3 model with {} anchors and {} classes.'.format(num_anchors, num_classes))

    if load_pretrained:
        model_body.load_weights(weights_path, by_name=True, skip_mismatch=True)
        print('Load weights {}.'.format(weights_path))
        if freeze_body:
            # Do not freeze 3 output layers.
            num = len(model_body.layers)-3
            for i in range(num): model_body.layers[i].trainable = False
            print('Freeze the first {} layers of total {} layers.'.format(num, len(model_body.layers)))

    model_loss = Lambda(yolo_loss, output_shape=(1,), name='yolo_loss',
        arguments={'anchors': anchors, 'num_classes': num_classes, 'ignore_thresh': 0.5})(
        [*model_body.output, *y_true])
    model = Model([model_body.input, *y_true], model_loss)
    return model
def data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes):
    n = len(annotation_lines)
    np.random.shuffle(annotation_lines)
    i = 0
    while True:
        image_data = []
        box_data = []
        for b in range(batch_size):
            i %= n
            image, box = get_random_data(annotation_lines[i], input_shape, random=True)
            image_data.append(image)
            box_data.append(box)
            i += 1
        image_data = np.array(image_data)
        box_data = np.array(box_data)
        y_true = preprocess_true_boxes(box_data, input_shape, anchors, num_classes)
        yield [image_data, *y_true], np.zeros(batch_size)

def data_generator_wrap(annotation_lines, batch_size, input_shape, anchors, num_classes):
    n = len(annotation_lines)
    if n==0 or batch_size<=0: return None
    return data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes)

if __name__ == '__main__':
    _main()    

    我是在cpu版本tensorflow上跑的,故特別慢,有運算資源的就不說了,如果資源有限,建議少弄些圖片和xml檔案,這樣最後的txt檔案中資料就少,跑起來輕鬆點。其次可以修改train.py中的迭代次數epochs的值,該值原作者設定的為500;也可以修改batch_size = 10的大小。

注意:我第一次訓練時候,確實在目錄下自動生成了logs資料夾,並在其中生成000資料夾,然後裡面放的是自己訓練好的h5檔案。但是後來我除錯程式碼,刪除該目錄,再次訓練時,報如下錯誤:

 

此時只需要手動建立logs資料夾和其內的000資料夾即可。嫌名字不好,可以自己修改train.py檔案,改裡面的儲存目錄。

下面為成功測試截圖:

四.用自己訓練的h5檔案進行測試

先修改yolo.py檔案中的模型路徑,如下所示,改為自己訓練後生成的h5檔案路徑。

然後執行,測試過程和前面所講一樣,因為我得沒怎麼訓練,就不貼出很挫的測試效果圖了。在最初沒有框資訊的txt檔案訓練後,執行測試很慢,因為訓練時候根本不知道要框什麼,改為正常txt後,訓練後的模型進行測試,速度就會很快。