YOLO-Keras 跑通记录

本帖根据GITHUB上的开源项目开展https://github.com/qqwweee/keras-yolo3,主要用YOLO来跑自己的数据,其中遇到不少坑,避免之后盲目测试代码,所以记录下来以便参考。本帖围绕踩过的坑,以及代码跑通的整体流程、以及代码相应修改,最后将整理想法,包括移植开发板,以及将YOLO转成可商用的开发软件。
项目需求:目标检测。使用YOLO一般都是用来目标检测,当然身边也有朋友用来目标检测后做二次开发的,包括二维码定位识别。YOLO凭借快速识别以及识别准确性的优势,缩短了开发周期。

YOLO介绍

YOLO是You Only Look Once: Unified, Real-Time Object Detection文章中的一种深度学习方法,取名就是每个首字母拼写。对于现在的深度学习目标检测算法分两种:one-stage、two-stage。two-stage以RCNN为主,准确率虽然高,但是用来开发会有一定的难度。one-stage的YOLO直接对输入图像应用算法并输出类别和相应的定位,所以实时场景中,推荐YOLO来实现检测。

1.代码运行环境

win7
keras
tensorflow-gpu
pycharm

2. weight文件转化成keras可用的.h5文件

如项目提示,需要从YOLO的网站下载yolov3.weight文件,然后执行语句转化为h5文件。此时应注意,该模型文件仅仅只能识别YOLO论文中所提到的目标。要想实现自己项目的目标识别,这里使用的是迁移学习的思想,在这个已经训练好的模型基础之上,再进行二次训练。

2.准备数据集

数据集要准备的有检测图片、labelImg工具。如何在windows下运行labelImg:安装anacoda,打开anacoda prompt。



然后输入

pip install labelImg

这基本是安装此工具最方便的方法了,如果pip install太慢,可以使用清华镜像,具体方法可以百度“pip太慢”。安装后,输入

labelImg

即可进入工具界面



可以选择打开数据集的文件夹。然后对每张图可以进行标记。



保存的文件为XML文件,也就是对应的图片的标签。

3.训练数据集(重点部分)

第一步,需要在开源的工程文件夹中,创建一个文件夹VOCdevkit,然后再根据图中继续创建文件夹


第二步,在Annotations文件夹中放的是labelImg生成的XML标签文件

第三步,在JPEGImages放的是与Annotations文件夹对应的图片。而ImageSets文件夹不需要手动操作,将Main文件夹创建好后我们可以在VOC2019文件夹下创建creattxt.py文件,代码如下:

import os
import random
 
trainval_percent = 0.2
train_percent = 0.8
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)
 
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()

执行后,Main文件夹中会多出两个txt文件,里面记录的分别是训练图片名称和检验图片名称,不带文件名后缀。
第四步,改写项目中voc_annotation.py、model_data文件下的voc_classes.txt以及coco_classes.txt内容。
首先voc_annotation.py中,更改部分在代码中做了注释

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

sets=[('2019', 'train'), ('2019', 'val')]  #需要更改部分,根据文件年份更改

classes = ["aeroplane", "bicycle"] #更改为自己所需要的分类,此处以这两个分类为例


def convert_annotation(year, image_id, list_file):
    in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id),encoding='utf-8')
    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('VOCdevkit/VOC%s/ImageSets/Main/%s.txt'%(year, image_set)).read().strip().split()
    list_file = open('%s_%s.txt'%(year, image_set), 'w')
    for image_id in image_ids:
        list_file.write('%s/VOCdevkit/VOC%s/JPEGImages/%s.jpg'%(wd, year, image_id))
        convert_annotation(year, image_id, list_file)
        list_file.write('\n')
    list_file.close()

代码中注意 encoding='utf-8'这个地方,原本项目中没有这个语句,但是由于我执行时有报错,尝试加入该语句,可以成功运行。

voc_classes.txt以及coco_classes.txt内容保持一致,如下:



然后运行voc_annotation.py可以获得两个txt文件如下



此时就可以运行项目中的trian.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 = '2019_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)-7
            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()

跑完之后会在log/000文件夹下形成新的模型文件也就是可以用来运行自己的训练数据的神经网络模型已经构建完成,打开yolo.py,把自己训练好的模型文件加载进去。


4.目标检测

可以输入一张图片和一段视频或者直接使用网络摄像头,都可以进行检测。
检测图片在terminal下输入

python yolo_video.py --image

检测视频输入

python yolo_video.py --input 'path' --output 'path'

path就是视频的绝对文件地址。
网络摄像头要先把yolo.py文件中video_path 直接替换成0



然后在terminal下输入

python yolo_video.py --input --output

就能检测目标物体。

5.关于目标检测软件开发和移植嵌入式平台的小想法

a、在训练好自己的模型,把模型文件保存下来,如果进行软件开发,在C的opencv库的帮助下可以实现,但是训练模型当然还是要在python环境下训练好。具体方案可以参考大神博客:https://blog.csdn.net/haoqimao_hard/article/details/82081285
b、树莓派下进行快速检测
具体方案参考:https://blog.csdn.net/wjbwjbwjbwjb/article/details/77688625https://www.jianshu.com/p/3e9862a55e43
初步想法,亲测后会继续跟帖。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • 一瞥(You Only Look Once, YOLO),是检测Pascal VOC2012数据集内对象/目标的系...
    Aspirinrin阅读 52,335评论 28 64
  • 源码地址 https://github.com/qqwweee/keras-yolo3 春节期间仔细看了看yol...
    yanghedada阅读 17,621评论 16 17
  • 改变命运的关键在于自己,外在因素或许只能做到刺激作用,改变从自我做起,没有神会来搭救你,也没有大师点播你。
    Alan520阅读 178评论 0 0
  • 今天是周六,起床写了两篇简书,原谅我的后知后觉,你妈妈今天加班,她和我说我便用心记着,她不和我说,我感觉主...
    曼本竹心llm不良帅阅读 334评论 2 1
  • 我最大的遗憾,是你的遗憾与我有关。 后来的我们,还记得最初的模样吗? 那个穿着校服扎着马尾的姑娘,那个带着帽子抱着...
    安念儿_deb8阅读 290评论 0 0