Yolov5 创建自己的训练集
数据集的目录结构
首先我们需要了解训练的目录结构
跑通了官方训练集的时候我们会发现在yolov5的目录的同级目录有datasets文件夹
里面有coco128官方数据集
我们可以知道
|datasets
| |----coco128
| | |-----images
| | |-----labels
images
在images的文件夹中我们会发现
|images
| |-----train2017
在train2017中存放的是所需要的照片 .jpg
labels
|labels
|-----train2017
在train2017中我们会发现是对应的标记
列如这个解释
45 0.479492 0.688771 0.955609 0.5955
45 0.736516 0.247188 0.498875 0.476417
50 0.637063 0.732938 0.494125 0.510583
45 0.339438 0.418896 0.678875 0.7815
49 0.646836 0.132552 0.118047 0.096937
49 0.773148 0.129802 0.090734 0.097229
49 0.668297 0.226906 0.131281 0.146896
49 0.642859 0.079219 0.148063 0.148062
归于第50类,然后后面的是整合后的坐标
引用数据集的目录结构
| yolov5
| |-----data
| | |-----coco.yaml
| | |-----coco128.yaml
我们可以看到coco.yaml 和 coco128.yaml 的文件都是作为引索训练的文件配置文件
就用coco.yaml解释一下
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
# COCO 2017 dataset http://cocodataset.org
# Example usage: python train.py --data coco.yaml
# parent
# ├── yolov5
# └── datasets
# └── coco ← downloads here
# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: ../datasets/coco # dataset root dir
train: train2017.txt # train images (relative to 'path') 118287 images
val: val2017.txt # train images (relative to 'path') 5000 images
test: test-dev2017.txt # 20288 of 40670 images, submit to https://competitions.codalab.org/competitions/20794
# Classes
nc: 16 # number of classes
names: ['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light',
'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard',
'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear',
'hair drier', 'toothbrush'] # class names
这里我粗略的删除了东西因为不需要,这里重要的是
train:
test:
val :
后面的填充内容都是txt文件,冒号后面则是需要相应txt的路径
所以我们在这个目录下创建我们自己的person.yaml
内容
train: /yolov5上一级目录名称/yolov5/face_dataset/train.txt
val: /yolov5上一级目录名称/yolov5/face_dataset/val.txt
#或者用绝对路径也可以
#这里是我们自己的训练的地方
# number of classes
nc: 16
# class names
names: []
nc 是你所需要训练的种类的类别多少
names是你所训练的类别的名字全部加入到这里
例如
names:["wuhu","aoligei"]
就先这样创建这来后面再创建face_dataset文件夹
创建生成txt文档的目录结构
首先来到yolov5的目录下 创建一个文件夹(这里名为)face_dataset
|yolov5| |-----face_dataset
进去后我们从上面数据集的目录结构中了解到,我们需要创建出lable 已经image
进去face_dataset目录下创建两个文件夹分别为images 和 Annotations
|yolov5| |-----face_dataset| | |-----images| | |-----Annotations
images中存放所有的照片
Annotations存放我们的标记文件.xml文件
首先我们需要做的是训练集和测试集分离就需要我们写一个py文件
存放目录:
|yolov5| |-----face_dataset| | |-----images| | |-----Annotations| | |-----split_train_val.py
split_train_val.py的代码:
import osimport randomtrainval_percent = 1.0train_percent = 0.9#xml的文件路径xmlfilepath = 'Annotations'total_xml = os.listdir(xmlfilepath)num = len(total_xml)list = range(num)tv = int(num * trainval_percent)tr = int(num * train_percent)trainval = random.sample(list, tv)train = random.sample(trainval, tr)# ImageSets目录不存在,就创建if not os.path.exists('ImageSets/'): os.makedirs('ImageSets/')# ImageSets/Main目录不存在,就创建if not os.path.exists('ImageSets/Main/'): os.makedirs('ImageSets/Main/')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 = 'yolov5/face_dataset/images/' + total_xml[i][:-4] + '.jpg' + '\n' if i in trainval: ftrainval.write(name) if i in train: ftrain.write(name) else: fval.write(name) else: ftest.write(name)ftrainval.close()ftrain.close()fval.close()ftest.close()
随后运行split_train_val.py就会发现目录结构有了改变
|yolov5| |-----face_dataset| | |-----images| | |-----Annotations| | |-----split_train_val.py| | |-----ImageSets| | | |-----Main| | | | |-----test.txt| | | | |-----train.txt| | | | |-----trainval.txt| | | | |-----val.txt
我们打开train.txt 我们发现我们弄的是训练集90%的照片的引索
val.txt是我们10%的测试集的照片的引索
然后我们需要生成lables即可大功告成
回到目录
|yolov5| |-----face_dataset| | |-----images| | |-----Annotations| | |-----split_train_val.py| | |-----ImageSets
创建生成labes的py文件 voc_label.py
voc_label.py代码
import xml.etree.ElementTree as ETimport pickleimport osfrom os import listdir, getcwdfrom os.path import joinimage_sets = ['train', 'val', 'test']#这里classes和上面一样存放你所有的类名比如#classes=["wuhu","aoligei"]classes = []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 print(x) print(h) print(y) print(w) isValid = True if x == 0 or y == 0 or w == 0 or h == 0: print(f'x :{x} y:{y} w:{w} h:{h}') isValid = False if box[0] == 0 or box[1] == 0 or box[2] == 0 or box[3] == 0: print(f'box[0] :{box[0]} box[1]:{box[1]} box[2]:{box[2]} box[3]:{box[3]}') isValid = False return (x, y, w, h), isValiddef convert_annotation(image_id): #我们需要引用xml中的标记进行显示对生成labes in_file = open('Annotations/%s.xml' % (image_id.split("/").pop().split(".")[0])) print(in_file) out_file = open('labels/%s.txt' % (image_id.split("/").pop().split(".")[0]), 'w') print(out_file) tree = ET.parse(in_file) root = tree.getroot() size = root.find('size') w = int(size.find('width').text) print(w) h = int(size.find('height').text) print(h) # print(root['object']) # return objList = root.findall('object') # for obj in objList: for obj in root.iter('object'): difficult = obj.find('difficult').text cls = obj.find('name').text print('-------') # 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, isValid = convert((w, h), b) if isValid == False: # out_file.close() print(image_id) return False out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n') print("文件内容:") print(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n') out_file.flush() out_file.close() return Trueif not os.path.exists('labels/'): os.makedirs('labels/')for image_set in image_sets: # strip() 移除字符串的首尾字符,默认为空格 # split() 字符串分割,默认为所有空字符,包含空格、换行、制表符 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: isValid = convert_annotation(image_id) if isValid == True: list_file.write('%s\n' % (image_id)) else: print(f'存在无效值:{image_id}') list_file.close()
在运行split_train_val.py的基础上才能运行voc_label.py
运行了voc_label.py后目录结构会发生改变
|yolov5| |-----face_dataset| | |-----images| | |-----Annotations| | |-----split_train_val.py| | |-----ImageSets| | |-----labels| | |-----text.txt| | |-----train.txt| | |-----train.cache| | |-----train.cache.npy| | |-----val.txt| | |-----val.cache| | |-----val.cache.npy
|lables| |-----照片名字.txt
我们所需要用到的就是在face_dataset的目录下的train.txt和text.txt和val.txt
所以person.yaml中我们设置的引索路径就是这里的
train: /yolov5_gpu/yolov5/face_dataset/train.txtval: /yolov5_gpu/yolov5/face_dataset/val.txt
准备训练前需要更改的地方
|yolov5| |-----models| | |-----yolov5l.yaml| | |-----yolov5m.yaml| | |-----yolov5m.yaml| | |-----yolov5s.yaml| | |-----yolov5x.yaml
我们需要更改看我们用的是哪个的网络训练,比如说(官方默认的是yolov5s.yaml)我们点击进入对应的yaml中会看到
nc: 16 # number of classesdepth_multiple: 0.33 # model depth multiplewidth_multiple: 0.50 # layer channel multiple
我们只需要更改nc即可
nc:是我们所存有几种类
例如这里就是有16个类
|yolov5| |-----train.py
在train.py中我们会发现
def parse_opt(known=False): parser = argparse.ArgumentParser() parser.add_argument('--weights', type=str, default=ROOT / 'weights/yolov5s.pt', help='initial weights path') parser.add_argument('--cfg', type=str, default='', help='models/yolov5s.yaml') parser.add_argument('--data', type=str, default=ROOT / 'data/person.yaml', help='dataset.yaml path') parser.add_argument('--hyp', type=str, default=ROOT / 'data/hyps/hyp.scratch.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=200) parser.add_argument('--batch-size', type=int, default=1, help='total batch size for all GPUs') parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=240, help='train, val image size (pixels)') parser.add_argument('--rect', action='store_true', help='rectangular training') parser.add_argument('--resume', nargs='?', const=True, default=False, help='resume most recent training') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--noval', action='store_true', help='only validate final epoch') parser.add_argument('--noautoanchor', action='store_true', help='disable autoanchor check') parser.add_argument('--evolve', type=int, nargs='?', const=300, help='evolve hyperparameters for x generations') parser.add_argument('--bucket', type=str, default='', help='gsutil bucket') parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"') parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%') parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class') parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer') parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode') parser.add_argument('--workers', type=int, default=8, help='maximum number of dataloader workers') parser.add_argument('--project', default=ROOT / 'runs/train', help='save to project/name') parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') parser.add_argument('--quad', action='store_true', help='quad dat aloader') parser.add_argument('--linear-lr', action='store_true', help='linear LR') parser.add_argument('--label-smoothing', type=float, default=0.0, help='Label smoothing epsilon') parser.add_argument('--patience', type=int, default=100, help='EarlyStopping patience (epochs without improvement)') parser.add_argument('--freeze', type=int, default=0, help='Number of layers to freeze. backbone=10, all=24') parser.add_argument('--save-period', type=int, default=-1, help='Save checkpoint every x epochs (disabled if < 1)') parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') # Weights & Biases arguments parser.add_argument('--entity', default=None, help='W&B: Entity') parser.add_argument('--upload_dataset', action='store_true', help='W&B: Upload dataset as artifact table') parser.add_argument('--bbox_interval', type=int, default=-1, help='W&B: Set bounding-box image logging interval') parser.add_argument('--artifact_alias', type=str, default='latest', help='W&B: Version of dataset artifact to use') opt = parser.parse_known_args()[0] if known else parser.parse_args() return opt
我们一般需要更改的是:
- –weights:权值文件
- –cfg:所用的网络模型就是models中的yolov5s.yaml(这里是例子)
- –data:我们存放在data/person.yaml
- –hyp:这个是有关学习率和图像增强相关设置这个可以查阅一下(一般先不怎么改)
- –epochs:训练的次数
- –batch-size:一次给多少张图片
有些地方不是很对,但就这样了摆烂了