windows系統下使用yolo3訓練自己的數據集並進行物體檢測測試

windows系統下使用yolo3訓練自己的數據集並進行物體檢測測試

用yolo3對自己的數據集進行訓練,查閱一些相關文章,發現都是使用老版本的文章,一些細節方面有很多改動,我在文章中會列出我遇到的一些新問題和一些老問題,在寫訓練步驟的同時多爲大家列出一些常見問題。新手入門大神們多多指點。

如果是linux環境下可以訪問參考文章
模型代碼下載地址:
https://github.com/qqwweee/keras-yolo3
參考文章:
1.https://blog.csdn.net/m0_37857151/article/details/81330699
2.https://blog.csdn.net/patrick_Lxc/article/details/80615433

一、下載項目源碼,進行快速測試

進入模型代碼網址,下載源碼目錄到本地,根據github作者的指導完成快速測試,由於現在新代碼跟老本版不一樣了,相關命令有些許的改動。
這是原文中的指導步驟
1.步驟一讓去yolo官網下載一個權重文件:https://pjreddie.com/media/files/yolov3.weights,文件200多M,如果沒有VPN下載速度慢的話可以從這裏下載:https://pan.baidu.com/s/15NsB5hbwa_N-eJ6-sNwgLw 提取碼:kj44 。
2.下載好weight權重文件後放入在github上下載的keras-yolo3-master文件夾下,執行convert.py文件,用於將權重文件轉爲 .h5格式的文件,生成的h5將被保存在model_data目錄下。在當前目錄下打開終端並輸入命令:

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

3.用生成好的.h5文件進行目標檢測。
a:如果對圖片進行測試,輸入命令,運行後會讓你在終端上輸入圖片路徑:

python yolo_video.py --image

在這裏插入圖片描述
b:如果要對視頻進行測試,直接輸入命令:

python3 yolo_video.py --input=原視頻地址 --output=新生成地址

在這裏插入圖片描述
+++++++++++++++++++++++++++++++++++++++++++++++++分割線
貼個效果圖:
在這裏插入圖片描述

二、用自己的數據集訓練自已的模型

文章開頭列出的參考文章是使用了VOC2007數據集的格式,新手不用在意是否使用什麼VOC格式,之所以用這個本質是爲減少對源代碼的修改量,就算自己創建幾個文件夾也無所謂,無非就是在代碼中修改目錄罷了。

我們創建的文件夾,第一Annotations是儲存標註後的xml文件,第二ImageSets/Main是存儲生成所需文本文件,第三JPEGImages是存儲圖片,第四個model_data儲存我們的classes和最終生成的.h5文件,第五個yolo3是在githup下載模型中的文件夾,沒有改動,直接複製過來就好,後面其它的文件也是模型中複製過來或者後文中貼出的代碼。

注:爲了少改代碼大家可以參考我的VOC2007目錄,跟原版本格式有刪減,剔除了沒用的部分:
在這裏插入圖片描述
第一步:創建JPEGImages文件夾,將你要訓練的圖片放到JPEGImages中。

第二步:闖將Annotations文件夾,將你標註所生成的xml文件放到Annotations文件夾中。如果不知道怎麼標註的童鞋們點擊藍色字體:講述如何標註的文章

第三步:創建ImageSets/Main文件夾,這個文件夾用來儲存我們的對數據集進行 訓練和驗證拆分後的文本。

第四步:對我們的數據集進行分類,分成train.txt和val.txt。
複製以下代碼至根目錄VOC2007下,命名convert_to_txt.py

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()

此代碼在上述參考文章中都有,該目錄的朋友注意下目錄地址,按本文創建的目錄不需要刪減。
運行後在ImageSets/Main目錄下生成4個txt文本:
在這裏插入圖片描述
第五步:將生成的txt文本重新整理生成我們yolo3模型需要的txt格式,就是把圖片的文件名和xml文件中box的座標融合在一起。運行voc_annotation.py文件,注意這裏面是要進行修改的:

import xml.etree.ElementTree as ET
from os import getcwd

sets=[('2007', 'train'), ('2007', 'val'), ('2007', 'test')]

classes = [‘car’,'person','bus']


def convert_annotation(year, image_id, list_file):
    in_file = open('Annotations/%s.xml'%( image_id))
    tree=ET.parse(in_file)
    root = tree.getroot()

    for obj in root.iter('object'):
        difficult = obj.find('difficult').text
        cls = obj.find('name').text
        if cls not in classes or int(difficult)==1:
            continue
        cls_id = classes.index(cls)
        xmlbox = obj.find('bndbox')
        b = (int(xmlbox.find('xmin').text), int(xmlbox.find('ymin').text), int(xmlbox.find('xmax').text), int(xmlbox.find('ymax').text))
        list_file.write(" " + ",".join([str(a) for a in b]) + ',' + str(cls_id))

wd = getcwd()

for year, image_set in sets:
    image_ids = open('ImageSets/Main/%s.txt'%(image_set)).read().strip().split()
    list_file = open('%s.txt'%(image_set), 'w')
    for image_id in image_ids:
        list_file.write('JPEGImages/%s.jpg'%(image_id))
        convert_annotation(year, image_id, list_file)
        list_file.write('\n')
    list_file.close()


注意:
1.第6行的 classes 改成你們自己的類

2.第10,27,30行的目錄是否是自己的目錄
3.我與原文的目錄稍加不同,如果完全按照本文目錄生成的直接複製上面代碼
4.新生成的txt內容是這個樣子的!!!!!!!
在這裏插入圖片描述

第六步:創建model_data文件夾,並生成voc_classes.txt,裏面填寫的是你的classes。
在這裏插入圖片描述

第七步:修改train.py文件。爲什麼修改呢?在參考文章中說到:因爲原作者的代碼中會加載預先對coco數據集已經訓練完成的yolo3權重文件,我們不需要預加載他以前訓練過的權重,我們要訓練自己的模型,所以我們剔除掉原文中沒用的東西。下面貼出修改後的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=100,
                        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()

直接運行train.py就可以了,至此訓練步驟結束。其中有一些運行問題我將在文章末尾寫出來,大家可以先看看在進行訓練,避免做無用功!!!

三、使用生成好的模型文件來預測

我是用1080ti 11G顯存跑的,其中bitch_size=10 epochs=80,訓練了6個小時,loss訓練到30。參考文章中說loss訓練到10就可以了,我實在懶得等的就先湊活用了。我先說下步驟。

第一步:可以根據第一章的內容,快速測試!!!!!!

第二步:用原文中的方式得一張一張圖片輸入路徑太慢了,我對代碼進行了修改,批量對圖片進行測試。
->先創建一個pic文件,裏面儲存待測試圖片。在生成一個res文件,儲存測試結果。
在這裏插入圖片描述
->將新建yolo_image.py文件,直接複製下面代碼:

import time
import argparse
from yolo import YOLO, detect_video
from PIL import Image
import os

def detect_img(yolo):
    filename = os.listdir('pic')
    for i in filename:
        img = f"pic/{i}"
        try:
            image = Image.open(img)
        except:
            print('Open Error! Try again!')
            continue
        else:
            r_image = yolo.detect_image(image)
            r_image.save(f'res/{i}')
    yolo.close_session()



if __name__ == '__main__':
    print("=========================開始預測===============================")
    start = time.time()
    parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS)
    '''
    Command line options
    '''
    parser.add_argument(
        '--model', type=str,
        help='path to model weight file, default ' + YOLO.get_defaults("model_path")
    )

    parser.add_argument(
        '--anchors', type=str,
        help='path to anchor definitions, default ' + YOLO.get_defaults("anchors_path")
    )

    parser.add_argument(
        '--classes', type=str,
        help='path to class definitions, default ' + YOLO.get_defaults("classes_path")
    )

    parser.add_argument(
        '--gpu_num', type=int,
        help='Number of GPU to use, default ' + str(YOLO.get_defaults("gpu_num"))
    )

    parser.add_argument(
        '--image', default=False, action="store_true",
        help='Image detection mode, will ignore all positional arguments'
    )
    '''
    Command line positional arguments -- for video detection mode
    '''
    parser.add_argument(
        "--input", nargs='?', type=str,required=False,default='./path2your_video',
        help = "Video input path"
    )

    parser.add_argument(
        "--output", nargs='?', type=str, default="",
        help = "[Optional] Video output path"
    )

    FLAGS = parser.parse_args()
    detect_img(YOLO(**vars(FLAGS)))
    end =  time.time()
    t = end-start
    print('用時:',int(t),'s')

直接運行 yolo_image.py,結果直接在res文件中查看即可。
++++++++++++++++++++++++++++++++++++++++++++分割線
貼測試效果圖:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
2600張數據集,訓練時間6小時,loss=30,訓練時間太短效果一般,識別效果時有時無。

四、訓練過程中的問題及注意事項

1.在訓練自己的數據集之前,執行train.py之前,先在目錄下創建logs/000文件夾,否則訓練幾個小時後,在完成的那一刻報錯!!!!太坑了這個問題!
OSError: Unable to open file

2.我分別用1080ti 11g和2080 8g顯卡跑,1080ti可以直接跑,但是2080跑回報顯存不足的錯誤,這個錯誤我們先從模型本身入手,先將train.py中bitch_size值改小,最好先改成1,然後我在運行,依然會報顯存不足。
tensorflow.python.framework.errors_impl.InternalError: Blas SGEMM launch failed : m=43264, n=32, k=64
[[{{node conv2d_3/convolution}}]]
[[{{node loss/add_74}}]]

然後我覺得8G顯存足夠了,用faster-rcnn和ssd都沒問題,我在想會不會是因爲顯存一下被佔用滿導致顯卡運行有問題,然後我在yolo3文件夾中的model.py中添加了限制gpu使用的代碼:
在這裏插入圖片描述

os.environ["CUDA_VISIBLE_DEVICES"] = "0"
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
config.gpu_options.per_process_gpu_memory_fraction = 0.8
set_session(tf.Session(config=config))

此時bitch_size=1,完美執行。然後又一點點試,bitch_size設置爲5都沒有問題。
3.以上是兩個讓我比較深刻的問題,還有一些問題暫且想不起來,如果有人提問我將問題寫在這裏。或者去參考文章下看看提問興許對你們有幫助。

發佈了1 篇原創文章 · 獲贊 2 · 訪問量 329
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章