原文鏈接:實現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中僅有圖片名稱。
代碼:
1 import os 2 import random 3 4 trainval_percent = 0.1 5 train_percent = 0.9 6 xmlfilepath = 'Annotations' 7 txtsavepath = 'ImageSets\Main' 8 total_xml = os.listdir(xmlfilepath) 9 10 num = len(total_xml) 11 list = range(num) 12 tv = int(num * trainval_percent) 13 tr = int(tv * train_percent) 14 trainval = random.sample(list, tv) 15 train = random.sample(trainval, tr) 16 17 ftrainval = open('ImageSets/Main/trainval.txt', 'w') 18 ftest = open('ImageSets/Main/test.txt', 'w') 19 ftrain = open('ImageSets/Main/train.txt', 'w') 20 fval = open('ImageSets/Main/val.txt', 'w') 21 22 for i in list: 23 name = total_xml[i][:-4] + '\n' 24 if i in trainval: 25 ftrainval.write(name) 26 if i in train: 27 ftest.write(name) 28 else: 29 fval.write(name) 30 else: 31 ftrain.write(name) 32 33 ftrainval.close() 34 ftrain.close() 35 fval.close() 36 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開始訓練自己的模型,則不需要下面的修改步驟,而如果想用遷移學習思想,用已經預訓練好的權重接着訓練,則需要下面的修改步驟。
DE里直接打開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即可
1 """ 2 Retrain the YOLO model for your own dataset. 3 """ 4 import numpy as np 5 import keras.backend as K 6 from keras.layers import Input, Lambda 7 from keras.models import Model 8 from keras.callbacks import TensorBoard, ModelCheckpoint, EarlyStopping 9 10 from yolo3.model import preprocess_true_boxes, yolo_body, tiny_yolo_body, yolo_loss 11 from yolo3.utils import get_random_data 12 13 14 def _main(): 15 annotation_path = 'train.txt' 16 log_dir = 'logs/000/' 17 classes_path = 'model_data/voc_classes.txt' 18 anchors_path = 'model_data/yolo_anchors.txt' 19 class_names = get_classes(classes_path) 20 anchors = get_anchors(anchors_path) 21 input_shape = (416,416) # multiple of 32, hw 22 model = create_model(input_shape, anchors, len(class_names) ) 23 train(model, annotation_path, input_shape, anchors, len(class_names), log_dir=log_dir) 24 25 def train(model, annotation_path, input_shape, anchors, num_classes, log_dir='logs/'): 26 model.compile(optimizer='adam', loss={ 27 'yolo_loss': lambda y_true, y_pred: y_pred}) 28 logging = TensorBoard(log_dir=log_dir) 29 checkpoint = ModelCheckpoint(log_dir + "ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5", 30 monitor='val_loss', save_weights_only=True, save_best_only=True, period=1) 31 batch_size = 10 32 val_split = 0.1 33 with open(annotation_path) as f: 34 lines = f.readlines() 35 np.random.shuffle(lines) 36 num_val = int(len(lines)*val_split) 37 num_train = len(lines) - num_val 38 print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size)) 39 40 model.fit_generator(data_generator_wrap(lines[:num_train], batch_size, input_shape, anchors, num_classes), 41 steps_per_epoch=max(1, num_train//batch_size), 42 validation_data=data_generator_wrap(lines[num_train:], batch_size, input_shape, anchors, num_classes), 43 validation_steps=max(1, num_val//batch_size), 44 epochs=500, 45 initial_epoch=0) 46 model.save_weights(log_dir + 'trained_weights.h5') 47 48 def get_classes(classes_path): 49 with open(classes_path) as f: 50 class_names = f.readlines() 51 class_names = [c.strip() for c in class_names] 52 return class_names 53 54 def get_anchors(anchors_path): 55 with open(anchors_path) as f: 56 anchors = f.readline() 57 anchors = [float(x) for x in anchors.split(',')] 58 return np.array(anchors).reshape(-1, 2) 59 60 def create_model(input_shape, anchors, num_classes, load_pretrained=False, freeze_body=False, 61 weights_path='model_data/yolo_weights.h5'): 62 K.clear_session() # get a new session 63 image_input = Input(shape=(None, None, 3)) 64 h, w = input_shape 65 num_anchors = len(anchors) 66 y_true = [Input(shape=(h//{0:32, 1:16, 2:8}[l], w//{0:32, 1:16, 2:8}[l], \ 67 num_anchors//3, num_classes+5)) for l in range(3)] 68 69 model_body = yolo_body(image_input, num_anchors//3, num_classes) 70 print('Create YOLOv3 model with {} anchors and {} classes.'.format(num_anchors, num_classes)) 71 72 if load_pretrained: 73 model_body.load_weights(weights_path, by_name=True, skip_mismatch=True) 74 print('Load weights {}.'.format(weights_path)) 75 if freeze_body: 76 # Do not freeze 3 output layers. 77 num = len(model_body.layers)-3 78 for i in range(num): model_body.layers[i].trainable = False 79 print('Freeze the first {} layers of total {} layers.'.format(num, len(model_body.layers))) 80 81 model_loss = Lambda(yolo_loss, output_shape=(1,), name='yolo_loss', 82 arguments={'anchors': anchors, 'num_classes': num_classes, 'ignore_thresh': 0.5})( 83 [*model_body.output, *y_true]) 84 model = Model([model_body.input, *y_true], model_loss) 85 return model 86 def data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes): 87 n = len(annotation_lines) 88 np.random.shuffle(annotation_lines) 89 i = 0 90 while True: 91 image_data = [] 92 box_data = [] 93 for b in range(batch_size): 94 i %= n 95 image, box = get_random_data(annotation_lines[i], input_shape, random=True) 96 image_data.append(image) 97 box_data.append(box) 98 i += 1 99 image_data = np.array(image_data) 100 box_data = np.array(box_data) 101 y_true = preprocess_true_boxes(box_data, input_shape, anchors, num_classes) 102 yield [image_data, *y_true], np.zeros(batch_size) 103 104 def data_generator_wrap(annotation_lines, batch_size, input_shape, anchors, num_classes): 105 n = len(annotation_lines) 106 if n==0 or batch_size<=0: return None 107 return data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes) 108 109 if __name__ == '__main__': 110 _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后,訓練后的模型進行測試,速度就會很快。