目录
- 参考链接
- 0、配置环境
- 1、下载预训练模型——推荐
- 2、准备数据集——非常关键
* - 2-1、det文件夹下
– - 2-2、seg文件夹下
– - 3、配置文件参数修改
* - 3-1、models/segheads.yaml
- 3-2、data/voc.yaml
- 3-3、models/yolov5s.yaml
- 3-4、trainds.py
- 4、遇到的问题
* - 4-1、运行trainds.py
- 4-2、运行detectds.py
参考链接
- 🍅yolov5ds:Yolov5同时进行目标检测和分割分割(yolov5ds作者的博客介绍)
- github地址:👉yolov5ds
- 训练yolov5ds案例:用YOLOv5ds训练自己的数据集——同时检测和分割
- 对上训练yolov5ds案例的补充:用YOLOv5ds训练自己的数据集,注意点!
- yolov5ds-断点训练、继续训练(yolov5同样使用)
以下步骤是参考:用YOLOv5ds训练自己的数据集——同时检测和分割
0、配置环境
不在赘述,跟YOLOv5差不多
1、下载预训练模型——推荐
在yolov5ds-main根目录新建weights文件夹
下载yolov5预训练模型Releases · ultralytics/yolov5 · GitHub放到weights文件夹中
我下载的是 yolov5s.pt
,下面均以 yolov5s.pt
为例
2、准备数据集——非常关键
在yolov5ds-main根目录新建 paper_data
文件夹
paper_data
文件夹下新建 det
和 seg
两个文件夹
det
文件夹存放检测数据集
seg
文件夹存放分割数据集
🍀对于数据集,我的整体步骤是:
(1)运行:paper_data/det/json2txt.py,生成json对应的txt文件
(2)运行:paper_data/det/split.py,以9:1划分train、val(没有划分test),存放到paper_data/det/ImageSet/Main
(3)运行:paper_data/det/voc_labels.py,通过(2)划分的数据集将文件绝对路径存放到相应txt中,存放到paper_data/det
(4)运行:paper_data/seg/getmask.py,将raw_data中所有的.png图片复制到paper_data/seg/labels中
(5)运行:paper_data/seg/segsplit.py,按照train.txt和val.txt划分到paper_data/seg/images或labels下的train、val文件夹下
❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀
关于以下我制作数据集的代码,我都分享出来了,有需要的好兄弟自取:👉百度网盘:paper_data
2-1、det文件夹下
images
文件夹下存放 .jpg
图像(或者.png格式)
labels
文件夹下存放 .txt
标签文件
Annotations
文件夹下存放 xml
标签文件(这个在训练过程其实用不到,只是如果标签文件是xml格式的话,就暂存在这个文件夹,然后会通过 3.voc_labels.py
转换成txt格式且划分到labels中的train或val子文件下)
注:xml、txt文件应和对应图像名称相同
再注:
- 因为,我的数据集格式只有实例分割数据集:jpg图像+json格式标签,所以这里还多了json2txt.py文件
- 有xml检测标签的,就直接执行2、3步
- *直接有txt,也只执行2、3步,但是第3步中要注释掉调用
convert_annotation(image_id)
函数
1. json2txt.py
(1)paper_data/det/ json2txt.py
,生成json对应的txt文件
import json
import os
import os.path
import re
def getclass(rootdir):
classes = []
for file in os.listdir(rootdir):
load_f = open(os.path.join(rootdir, file), 'r')
load_dict = json.load(load_f)
objects = load_dict['shapes']
for i in range(0, len(objects)):
label = objects[i]['label']
if label not in classes:
classes.append(label)
return classes
def image_id(rootdir):
a = []
for root, dirnames, filenames in os.walk(rootdir):
for filename in filenames:
filename = filename.strip('.json')
a.append(filename)
return a
def position(pos):
x = []
y = []
nums = len(pos)
for i in range(nums):
x.append(pos[i][0])
y.append(pos[i][1])
x_max = max(x)
x_min = min(x)
y_max = max(y)
y_min = min(y)
b = (float(x_min), float(x_max), float(y_min), float(y_max))
return b
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(rootdir, out_file, cls_list, image_id):
load_f = open(rootdir + '/{0}.json'.format(image_id), 'r')
load_dict = json.load(load_f)
out_file = open(out_file + '/{0}.txt'.format(image_id), 'w')
w = load_dict['imageWidth']
h = load_dict['imageHeight']
objects = load_dict['shapes']
nums = len(objects)
for i in range(0, nums):
print(image_id + '第{}个'.format(i))
pos = objects[i]['points']
box = position(pos)
bb = convert([w, h], box)
cls = objects[i]['label']
if re.match('Support', cls) is not None:
cls_id = 0
elif re.match('Bolt_hole', cls) is not None:
cls_id = 1
elif re.match('Grouting_hole', cls) is not None:
cls_id = 2
elif re.match('Cable', cls) is not None:
cls_id = 3
elif re.match('Pipe', cls) is not None:
cls_id = 4
elif re.match('Signal_light', cls) is not None:
cls_id = 6
elif re.match('Sign', cls) is not None:
cls_id = 5
elif re.match('Railway', cls) is not None:
cls_id = 7
elif re.match('PJB', cls) is not None:
cls_id = 8
elif re.match('Instrument_box', cls) is not None:
cls_id = 9
elif re.match('Crack', cls) is not None:
cls_id = 10
elif re.match('Falling_block', cls) is not None:
cls_id = 11
else:
cls_id = -1
out_file.write(str(cls_id) + " " + " ".join([str(round(a, 3)) for a in bb]) + '\n')
if __name__ == '__main__':
rootdir = '../../raw_data/labels-json'
cls_list = getclass(rootdir)
print(cls_list)
out_file = 'labels'
if not os.path.exists(out_file):
os.makedirs(out_file)
ids = image_id(rootdir)
for id in ids:
convert_annotation(rootdir, out_file, cls_list, id)
print(id + '.json' + '已转换')
print('over!')
2. split.py
det
文件夹下新建一个 split.py
文件,使用下面代码生成 ImageSets
,里面有一个 Main
文件夹, Main
文件夹里包括 test.txt
、 train.txt
、 trainval.txt
、 val.txt
四个文本文档
trainval.txt
包含你数据集里所有图像名称train.txt
为数据集的训练集,为总数据集的90%val.txt
为数据集的验证集,为总数据集的10%test.txt
文件里是空的不用担心,因为没有划分测试集
import os
import random
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--xml_path', default='./images', type=str,
help='input xml label path')
parser.add_argument('--txt_path', default='./ImageSets/Main', type=str,
help='output txt label path')
opt = parser.parse_args()
trainval_percent = 1.0
train_percent = 0.9
xmlfilepath = opt.xml_path
txtsavepath = opt.txt_path
total_xml = os.listdir(xmlfilepath)
if not os.path.exists(txtsavepath):
os.makedirs(txtsavepath)
num = len(total_xml)
list_index = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
random.seed(0)
trainval = random.sample(list_index, tv)
train = random.sample(trainval, tr)
file_trainval = open(txtsavepath + '/trainval.txt', 'w')
file_test = open(txtsavepath + '/test.txt', 'w')
file_train = open(txtsavepath + '/train.txt', 'w')
file_val = open(txtsavepath + '/val.txt', 'w')
for i in list_index:
name = total_xml[i][:-4] + '\n'
if i in trainval:
file_trainval.write(name)
if i in train:
file_train.write(name)
else:
file_val.write(name)
else:
file_test.write(name)
file_trainval.close()
file_train.close()
file_val.close()
file_test.close()
3. voc_labels.py
更改记录:
- 添加了
abs()
函数,以保证转换为txt时都是正数,不然可能运行代码时加载数据会出错 out_dir
如果不存在则创建
import xml.etree.ElementTree as ET
import os
from os import getcwd
sets = ['train', 'val', 'test']
classes = ['Bolt_hole', 'Grouting_hole', 'Crack']
abs_path = os.getcwd()
print(abs_path)
def convert(size, box):
dw = 1. / (size[0])
dh = 1. / (size[1])
x = (box[0] + box[1]) / 2.0
y = (box[2] + box[3]) / 2.0
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(image_id):
in_file = open(os.path.join(r'E:\A_new_dataset\A_tunnel_crack\labels_xml', f'{image_id}.xml'), encoding='UTF-8')
out_dir = os.path.join(abs_path, 'labels')
if not os.path.exists(out_dir):
os.makedirs(out_dir)
out_file = open(os.path.join(out_dir, f'{image_id}.txt'), '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'):
cls = obj.find('name').text
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))
b1, b2, b3, b4 = b
if b2 > w:
b2 = w
if b4 > h:
b4 = h
b = (b1, b2, b3, b4)
bb = convert((w, h), b)
out_file.write(str(cls_id) + " " + " ".join([str(abs(a)) for a in bb]) + '\n')
wd = getcwd()
for image_set in sets:
if not os.path.exists('./labels'):
os.makedirs('./labels')
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(abs_path + '/images/%s.png\n' % (image_id))
convert_annotation(image_id)
list_file.close()
运行之后会在det目录下生成train.txt、test.txt、val.txt三个文件,对应的图像名称前加入了绝对路径
运行完以上代码后,det文件夹结构为:
2-2、seg文件夹下
images
存放图像
labels
存放分割图像,类似下面这种图
(经过下面的 segsplit.py
代码后,会在images、labels里面分别创建train、val两个子文件夹,这里的train和val里的图要与上述提到的 det/Main/train.txt
和 val.txt
里分好的相对应)
注:labels图像应和images对应图像名称相同,格式为 png
这种图是用labelme标注后,使用labelme里自带的一个程序生成
通过网上批量转换labelme生成的json代码,生成了如下的文件夹,然后下面的 getmask.py
就是实现将这些文件夹中的label.png全部提出来放到一起
; 1. getmask.py
"""
将所有json转成的label.png拷贝到专门的文件夹
"""
import shutil
import os
json_dir = '../../raw_data/labels-json-labelmeout'
mask_dir = 'labels'
if not os.path.exists(mask_dir):
os.makedirs(mask_dir)
jsonlist = os.listdir(json_dir)
for x in jsonlist:
file_new_path = shutil.copy(os.path.join(json_dir, x, 'label.png'), os.path.join(mask_dir, x[:-5] + '.png'))
print(x + '.png' + ' done.')
2. segsplit.py
import os
import shutil
"""
实现:
1. 将train.txt 和 val.txt中指定的原始图像【复制】到seg/images/train子文件或者val子文件
2. 将train.txt 和 val.txt中指定的原始图像对应的标签png图像,移动到seg/labels/train子文件夹或val子文件夹
"""
def openreadtxt(file_name):
data = []
f = open(file_name, 'r')
for row in f.readlines():
tmp_list = row.split('\n')[0]
data.append(tmp_list)
f.close()
return data
if __name__ == '__main__':
labels_dir = r'E:\A_new_dataset\A_tunnel_crack\masks'
images_train_dir = './images/train'
images_val_dir = './images.val'
labels_train_dir = './labels/train'
labels_val_dir = './labels/val'
os.makedirs(images_train_dir)
os.makedirs(images_val_dir)
os.makedirs(labels_train_dir)
os.makedirs(labels_val_dir)
train_list = openreadtxt('../det/train.txt')
val_list = openreadtxt('../det/val.txt')
print(train_list, len(train_list))
print(val_list, len(val_list))
for file_path in train_list:
file = file_path.split('/')[-1]
filename, _ = os.path.splitext(file)
shutil.copy(file_path, os.path.join(images_train_dir, file))
labels_name = filename + '.png'
shutil.copy(os.path.join(labels_dir, labels_name), os.path.join(labels_train_dir, labels_name))
for file_path in val_list:
file = file_path.split('/')[-1]
filename, _ = os.path.splitext(file)
shutil.copy(file_path, os.path.join(images_val_dir, file))
labels_name = filename + '.png'
shutil.copy(os.path.join(labels_dir, labels_name), os.path.join(labels_val_dir, labels_name))
经过以上两个代码之后的 paper_data/seg 文件夹结构:
到此,检测、分割的数据集都做好了,paper_data数据集总结构为:
3、配置文件参数修改
3-1、models/segheads.yaml
- segnc:改为自己的分割类别数 + 1(这里一定要+1)
3-2、data/voc.yaml
- train:改为自己det文件夹下train.txt路径
- val:改为自己det文件夹下val.txt路径
- road_seg_train:改为自己seg文件夹下images/train文件夹路径
- road_seg_val:改为自己seg文件夹下images/val文件夹路径
- nc:改为自己的检测类别数
- segnc:改为自己的分割类别数(这里一定不要+1 !!!)
3-3、models/yolov5s.yaml
nc:改为自己的检测类别数
3-4、trainds.py
parse_opt函数下修改对应 default
里面的内容
关于resume断点训练、继续训练的方法:yolov5ds-断点训练、继续训练(yolov5同样适用)
; 4、遇到的问题
4-1、运行trainds.py
- 出现报错:
RuntimeError: weight tensor should be defined either for all or no class at ...
原因:这个文件中计算分割损失时,没有初始化weight,所以无法设置weight(可能是我自己的torch.nn有问题?)
解决方案:在 trainds.py 中用 Ctrl+F 搜索定位到 SegLoss = nn.CrossEntropyLoss
,然后去除掉里面的weight参数,改为以下:
SegLoss = nn.CrossEntropyLoss(ignore_index=255)
4-2、运行detectds.py
- 出现报错:
RuntimeError: Input type (torch.cuda.HalfTensor) and weight type (torch.HalfTensor)
原因:输入放在的gpu上,权重却没有放在gpu上,导致数据类型不一致
解决方案:在 detectds.py 中用 Ctrl+F 搜索定位到 model = ckpts['model']
位置,然后在下方加上一行代码:
model = model.cuda()
IndexError: index 1 is out of bounds for axis 0 with size 1
Original: https://blog.csdn.net/LWD19981223/article/details/125921793
Author: 孟孟单单
Title: yolov5ds训练步骤
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/628962/
转载文章受原作者版权保护。转载请注明原作者出处!