yolov2 - tiny模型训练识别

兴趣尝试,训练一下自己的数据集做图像识别人脸口罩。

darknet网络下载

下载地址:https://pjreddie.com/darknet/yolov2/
直接按照步骤,里面有不同yolo版本的基本配置文件,区别是使用什么样的预训练模型就用什么样的weight文件,下载好后直接放在darknet目录下。

尝试运行:

cd darknet

./darknet detector test cfg/voc.data cfg/yolov2-tiny-voc.cfg yolov2-tiny-voc.weights data/dog.jpg

测试结果:
yolov2 - tiny模型训练识别_第1张图片

yolov2 - tiny模型训练识别_第2张图片

bicycle居然没识别出来。确实精度差点。

数据集处理

一、转换数据集格式

我是直接从网上下载的数据集,但jpg和xml是一一对应放在同一个文件夹下,而一般训练大多是按照VOC格式存储,所以要先转换数据集格式。

VOC数据集格式如下:

VOCdevkit
——VOC2020        #文件夹的年份可以自己取
————Annotations  #放入所有的xml文件
————ImageSets    
——————Main       #放入train.txt,val.txt,test.txt,trainval.txt文件(不一定全要建)
————JPEGImages   #放入所有的图片文件
 
Main中的文件分别表示test.txt是测试集,train.txt是训练集,val.txt是验证集,trainval.txt是训练和验证集
二、数据增强:

数据增强

参考链接
博主用的os.walk(),获取路径,但是读取的文件路径是乱序(害苦我了),之后稍微改了一下。
在网上搜索发现,获取文件路径有两种方法:

  • os.listdir(path)只能获取当前目录下的所有文件或者文件夹的名称,而不能获取文件夹的绝对路径
  • os.walk(path) 返回包含(root,dirs,files)三种信息的生成器。
    os.walk(path)获得的并不是路径,所以需要将获得的三种信息进行链接才能得到路径
    root 所指的是当前正在遍历的这个文件夹的本身的地址
    dirs 是一个 list ,内容是该文件夹中所有的目录的名字(不包括子目录)
    files 同样是 list , 内容是该文件夹中所有的文件(不包括子目录)

这里采用的是os.listdir(),再sort(),将文件排序后再依次读取并且重命名为000123.xml/.jpg格式的文件。

#author:gr
# -*- coding:utf-8 -*-

import os
import shutil

path = '/Users/apple/train'   #数据集文件夹路径,下面包含每个类,改成你自己的
new_img_path = '/Users/apple/VOCdevkit/VOC2020/JPEGImages/'   #新的图片路径,改成你自己的
new_ann_path = '/Users/apple/VOCdevkit/VOC2020/Annotations/'    #新的xml路径,搞成你自己的
count_img = 1   #每提取一张图片,count+1,也能够按顺序给图片重命名
count_ann = 1   #每提取一个xml,count+1也能够按顺序给文件重命名
path_list = os.listdir(path) #获取文件名+后缀
path_list.sort()#我对数据集没有顺序要求,只需jpg和xml一一对应,所以最简单的排序
#print(path_list)
root = path
for files in path_list:
    '''循环path文件下每个每个文件夹,每个图片,按照以.jpg结尾和.xml结尾区分'''
    #print(files)
    if files[-3:] == 'jpg':
        file_path = root + '/' + files
        shutil.copy(file_path, os.path.join(new_img_path, str(count_img).zfill(6)+'.jpg'))
        count_img = count_img + 1
    elif files[-3:] == 'xml':
        file_path = root + '/' + files
        shutil.copy(file_path, os.path.join(new_ann_path, str(count_ann).zfill(6)+'.xml'))
        count_ann = count_ann + 1
print(count_img)
print(count_ann)

二、接着生成tain.txt

再将生成之后的文件VOCdevkit放到/darknet/scripts/目录下

#author:gr
# -*- coding:utf-8 -*-

# 生成main.txt
import os
def main(src, dest):
    count = 0
    out_file = open(dest,'w')  #生成了在指定目录下的txt文件
    path_list = os.listdir(src)
    path_list.sort()
    with open(dest, 'w') as f:
        for name in path_list:
            base_name = os.path.basename(name)
            file_name = base_name.split('.')[0]
            f.write('%s\n' % file_name)
            count = count + 1
    print(count)

if __name__ == '__main__':
    TrainDir = '/Users/apple/VOCdevkit/VOC2020/JPEGImages/'  #图片文件所在目录
    target = '/Users/apple/VOCdevkit/VOC2020/ImageSets/Main/train.txt'
    main(TrainDir, target)

三、修改voc_label.py.直接在vocdevkit文件里自动创建label文件夹包含每张照片对应的txt文件。
按照如下格式:

<object-class> <x> <y> <width> <height>

分别代表物体类别,
中心归一化横坐标(x)
中心归一化纵坐标(y)
归一化宽度(w)
归一化高度(h)

计算公式参考:
dw = 1 / width
dh = 1/ height
x = ( xmin + xmax ) / 2 * dw
y = ( ymin + yman ) / 2 * dh
w = ( xmax - xmin ) / 2 * dw
h = ( ymax - ymin ) / 2 * dh

实现代码:

import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join

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

#classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]

sets = [('2020','train')]	#训练的集合

classes = ["face_mask","face"] #标注的类别


def convert(size, box):
    dw = 1./(size[0])
    dh = 1./(size[1])
    x = (box[0] + box[1])/2.0 - 1
    y = (box[2] + box[3])/2.0 - 1
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x*dw
    w = w*dw
    y = y*dh
    h = h*dh
    return (x,y,w,h)

def convert_annotation(year, image_id):
    in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id))
    out_file = open('VOCdevkit/VOC%s/labels/%s.txt'%(year, image_id), 'w')
    tree=ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)

    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 = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text))
        bb = convert((w,h), b)
        out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')

wd = getcwd()

for year, image_set in sets:
    if not os.path.exists('VOCdevkit/VOC%s/labels/'%(year)):
        os.makedirs('VOCdevkit/VOC%s/labels/'%(year))
    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\n'%(wd, year, image_id))
        convert_annotation(year, image_id)
    list_file.close()

#os.system("cat 2007_train.txt 2007_val.txt 2012_train.txt 2012_val.txt > train.txt")
#os.system("cat 2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt > train.all.txt")
os.system("cat 2020_train.txt 2020_val.txt  > train.txt")  


最后生成了label文件夹包含所有的图片对应的txt文件,生成2020_train.txt,2020_val.txt之类的文本文件列出了该年份的图像文件和图像集。 train.txt需要一个文本文件,其中包含要训练的所有图像。

不过我的数据集有一点不规范没有< size >标签,弄了好久。不过一般按照正常情况标注的应该不会发生,如果有出入相应修改代码就行了。这里就不详述啦

修改相应配置文件
四、修改cfg文件和names文件

在cfg文件夹下找到voc.data文件

  1 classes= 2	#classes训练类别数
  2 train  = <path-to-voc>/train.txt #训练集
  3 valid  = <path-to-voc>2020_test.txt#测试集
  4 names = data/voc.names
  5 backup = backup

< path-to-voc>就是你放数据集的位置

修改data文件夹下voc.name文件,换成你训练的类别:

mask
face
五、下载预训练模型
wget https://pjreddie.com/media/files/darknet19_448.conv.23
六、修改cfg/yolo-tiny-voc.cfg

1.修改了batch和subdivison

[net]
# Testing
#batch=1
#subdivisions=1
# Training
batch=64
subdivisions=8
width=416
height=416
channels=3
momentum=0.9
decay=0.0005
angle=0
saturation = 1.5
exposure = 1.5
hue=.1

2.修改最后一层的卷积核和类别

filter =5*(5+2)

[convolutional]
size=1
stride=1
pad=1
filters=35//
activation=linear
[region]
anchors = 1.08,1.19,  3.42,4.41,  6.63,11.38,  9.42,5.11,  16.62,10.52
bias_match=1
classes=2//
coords=4
num=5
softmax=1
jitter=.2
rescore=1

object_scale=5
noobject_scale=1
class_scale=1
coord_scale=1

absolute=1
thresh = .6
random=1
五、Makefile文件
GPU=1
CUDNN=1
OPENCV=1
OPENMP=0
DEBUG=0
cd darkent

make

数据集终于弄好了,下一步准备上服务器了,嘿嘿嘿。

训练

cd darknet目录下执行

./darknet detector train cfg/voc.data cfg/yolov2-tiny-voc.cfg darknet19_448.conv.23

测试

cd darknet

./darknet detector test cfg/voc.data cfg/yolov2-tiny-voc.cfg backup/yolov2-tiny-voc_final.weights 

输入图片名进行测试

Enter Image Path: data/1.jpg
data/1.jpg: Predicted in 1.202904 seconds.
face: 56%
mask: 91%

(一般放在darknet/data下)

./darknet detector test cfg/voc.data cfg/yolov2-tiny-voc.cfg backup/yolov2-tiny-voc_final.weights data/1.jpg

点开prediction.jpg,效果还行,就不放出来了。

计算map值

由于yolov2版本比较低,无法直接计算出map,所以先生成检测结果文件(以下保存在mask.txt中)

./darknet detector valid cfg/voc.data cfg/yolov2-tiny-voc.cfg backup/yolov2-tiny-voc_final.weights -out mask.txt -gpu 0 -thresh .5

其他

训练暂停:ctrl+z
恢复训练:fg
训练终止:ctrl+c

训练中遇到的一些问题

一、Couldn’t open file

Couldn't open file: /darknet/scripts/2020_train.txt

solution:

修改cfg/voc.data,要用绝对路径不要用相对路径

train = /home/你的用户名/darknet/scripts/2020_train.txt
valid = /home/你的用户名/darknet/scripts/2020_val.txt

二、Cannot load image

Cannot load image "/darknet/scripts/VOCdevkit/VOC2020/JPEGImages/002846.jpg" 
STB Reason: can't fopen

solution:

重要的事情说三遍
绝对路径!绝对路径!绝对路径!

ps:这个如果用之前的文件生成的文件的确是绝对路径,由于我个人原因修改了一下路径,变成了相对路径,所以我的问题不一定都会遇到。

其他学习

服务器上传,下载文件参考:
https://blog.csdn.net/resilient/article/details/85334594

上传文件:scp -P(大写) 端口号。。。。。

你可能感兴趣的:(那些事,图像识别)