一、数据集转化 - VOC 转 Yolo
目录结构
- dataset
- 2023
- Annotations
- Imagesets
- images
- labels
- train.txt
- test.txt
- val.txt
- 生成
Imagesets
目录下的文件内容
import os
import random
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--xml_path', default='D:\datasets\2023\Annotations', type=str, help='input xml label path')
parser.add_argument('--txt_path', default='D:\datasets\2023\Imagesets', type=str, help='output txt label path')
opt = parser.parse_args()
trainval_percent = 0.9
train_percent = 0.7 #这里的train_percent 是指占trainval_percent中的
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)
trainval = random.sample(list_index, tv)
train = random.sample(trainval, tr)
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:
if i in train:
file_train.write(name)
else:
file_val.write(name)
else:
file_test.write(name)
file_train.close()
file_val.close()
file_test.close()
- 转成
yolo
格式
#该脚本文件需要修改第10行(classes)即可
# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
from tqdm import tqdm
import os
from os import getcwd
sets = ['train', 'test','val']
#这里使用要改成自己的类别
classes = ['red', 'green', 'yellow', 'off']
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
x = round(x,6)
w = round(w,6)
y = round(y,6)
h = round(h,6)
return x, y, w, h
#后面只用修改各个文件夹的位置
def convert_annotation(image_id):
#try:
in_file = open('D:/datasets/2023/Annotations/%s.xml' % (image_id), encoding='utf-8')
out_file = open('D:/datasets/2023/labels/%s.txt' % (image_id), 'w', encoding='utf-8')
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))
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(a) for a in bb]) + '\n')
#except Exception as e:
#print(e, image_id)
#这一步生成的txt文件写在data.yaml文件里
wd = getcwd()
for image_set in sets:
if not os.path.exists('D:/datasets/2023/labels/'):
os.makedirs('D:/datasets/2023/labels/')
image_ids = open('D:/datasets/2023/Imagesets/%s.txt' %
(image_set)).read().strip().split()
list_file = open('D:/datasets/2023/%s.txt' % (image_set), 'w')
for image_id in tqdm(image_ids):
list_file.write('D:/datasets/2023/images/%s.png\n' % (image_id))
convert_annotation(image_id)
list_file.close()
数据增强 https://aistudio.baidu.com/aistudio/projectdetail/4334420
import os
import cv2
import albumentations as A
import xml.etree.ElementTree as ET
import matplotlib.pyplot as plt
# 定义类
class VOCAug(object):
def __init__(self,
pre_image_path=None,
pre_xml_path=None,
aug_image_save_path=None,
aug_xml_save_path=None,
start_aug_id=None,
labels=None,
max_len=4,
is_show=False):
"""
:param pre_image_path:
:param pre_xml_path:
:param aug_image_save_path:
:param aug_xml_save_path:
:param start_aug_id:
:param labels: 标签列表, 展示增强后的图片用
:param max_len:
:param is_show:
"""
self.pre_image_path = pre_image_path
self.pre_xml_path = pre_xml_path
self.aug_image_save_path = aug_image_save_path
self.aug_xml_save_path = aug_xml_save_path
self.start_aug_id = start_aug_id
self.labels = labels
self.max_len = max_len
self.is_show = is_show
print(self.labels)
assert self.labels is not None, "labels is None!!!"
# 数据增强选项
self.aug = A.Compose([
A.RandomBrightnessContrast(brightness_limit=0.3, contrast_limit=0.3, p=1),
A.GaussianBlur(p=0.7),
A.GaussNoise(p=0.7),
A.CLAHE(clip_limit=2.0, tile_grid_size=(4, 4), p=0.5), # 直方图均衡
A.Equalize(p=0.5), # 均衡图像直方图
A.OneOf([
# A.RGBShift(r_shift_limit=50, g_shift_limit=50, b_shift_limit=50, p=0.5),
# A.ChannelShuffle(p=0.3), # 随机排列通道
# A.ColorJitter(p=0.3), # 随机改变图像的亮度、对比度、饱和度、色调
# A.ChannelDropout(p=0.3), # 随机丢弃通道
], p=0.),
# A.Downscale(p=0.1), # 随机缩小和放大来降低图像质量
A.Emboss(p=0.2), # 压印输入图像并将结果与原始图像叠加
],
# voc: [xmin, ymin, xmax, ymax] # 经过归一化
# min_area: 表示bbox占据的像素总个数, 当数据增强后, 若bbox小于这个值则从返回的bbox列表删除该bbox.
# min_visibility: 值域为[0,1], 如果增强后的bbox面积和增强前的bbox面积比值小于该值, 则删除该bbox
A.BboxParams(format='pascal_voc', min_area=0., min_visibility=0., label_fields=['category_id'])
)
print('--------------*--------------')
print("labels: ", self.labels)
if self.start_aug_id is None:
self.start_aug_id = len(os.listdir(self.pre_xml_path))
print("the start_aug_id is not set, default: len(images)", self.start_aug_id)
print('--------------*--------------')
def get_xml_data(self, xml_filename):
with open(os.path.join(self.pre_xml_path, xml_filename), 'r') as f:
tree = ET.parse(f)
root = tree.getroot()
image_name = tree.find('filename').text
size = root.find('size')
w = int(size.find('width').text)
h = int(size.find('height').text)
bboxes = []
cls_id_list = []
for obj in root.iter('object'):
# difficult = obj.find('difficult').text
difficult = obj.find('difficult').text
cls_name = obj.find('name').text # label
if cls_name not in LABELS or int(difficult) == 1:
continue
xml_box = obj.find('bndbox')
xmin = int(xml_box.find('xmin').text)
ymin = int(xml_box.find('ymin').text)
xmax = int(xml_box.find('xmax').text)
ymax = int(xml_box.find('ymax').text)
# 标注越界修正
if xmax > w:
xmax = w
if ymax > h:
ymax = h
bbox = [xmin, ymin, xmax, ymax]
bboxes.append(bbox)
cls_id_list.append(self.labels.index(cls_name))
# 读取图片
image = cv2.imread(os.path.join(self.pre_image_path, image_name))
return bboxes, cls_id_list, image, image_name
def aug_image(self):
xml_list = os.listdir(self.pre_xml_path)
cnt = self.start_aug_id
for xml in xml_list:
# AI Studio下会存在.ipynb_checkpoints文件, 为了不报错, 根据文件后缀过滤
file_suffix = xml.split('.')[-1]
if file_suffix not in ['xml']:
continue
bboxes, cls_id_list, image, image_name = self.get_xml_data(xml)
anno_dict = {'image': image, 'bboxes': bboxes, 'category_id': cls_id_list}
# 获得增强后的数据 {"image", "bboxes", "category_id"}
augmented = self.aug(**anno_dict)
# 保存增强后的数据
flag = self.save_aug_data(augmented, image_name, cnt)
if flag:
cnt += 1
else:
continue
def save_aug_data(self, augmented, image_name, cnt):
aug_image = augmented['image']
aug_bboxes = augmented['bboxes']
aug_category_id = augmented['category_id']
# print(aug_bboxes)
# print(aug_category_id)
name = '0' * self.max_len
# 获取图片的后缀名
image_suffix = image_name.split(".")[-1]
# 未增强对应的xml文件名
pre_xml_name = image_name.replace(image_suffix, 'xml')
# 获取新的增强图像的文件名
cnt_str = str(cnt)
length = len(cnt_str)
new_image_name = name[:-length] + cnt_str + "." + image_suffix
# 获取新的增强xml文本的文件名
new_xml_name = new_image_name.replace(image_suffix, 'xml')
# 获取增强后的图片新的宽和高
new_image_height, new_image_width = aug_image.shape[:2]
# 深拷贝图片
aug_image_copy = aug_image.copy()
# 在对应的原始xml上进行修改, 获得增强后的xml文本
with open(os.path.join(self.pre_xml_path, pre_xml_name), 'r') as pre_xml:
aug_tree = ET.parse(pre_xml)
# 修改image_filename值
root = aug_tree.getroot()
aug_tree.find('filename').text = new_image_name
# 修改变换后的图片大小
size = root.find('size')
size.find('width').text = str(new_image_width)
size.find('height').text = str(new_image_height)
# 修改每一个标注框
for index, obj in enumerate(root.iter('object')):
obj.find('name').text = self.labels[aug_category_id[index]]
xmin, ymin, xmax, ymax = aug_bboxes[index]
xml_box = obj.find('bndbox')
xml_box.find('xmin').text = str(int(xmin))
xml_box.find('ymin').text = str(int(ymin))
xml_box.find('xmax').text = str(int(xmax))
xml_box.find('ymax').text = str(int(ymax))
if self.is_show:
tl = 2
text = f"{LABELS[aug_category_id[index]]}"
t_size = cv2.getTextSize(text, 0, fontScale=tl / 3, thickness=tl)[0]
cv2.rectangle(aug_image_copy, (int(xmin), int(ymin) - 3),
(int(xmin) + t_size[0], int(ymin) - t_size[1] - 3),
(0, 0, 255), -1, cv2.LINE_AA) # filled
cv2.putText(aug_image_copy, text, (int(xmin), int(ymin) - 2), 0, tl / 3, (255, 255, 255), tl,
cv2.LINE_AA)
cv2.rectangle(aug_image_copy, (int(xmin), int(ymin)), (int(xmax), int(ymax)), (255, 255, 0), 2)
if self.is_show:
cv2.imshow('aug_image_show', aug_image_copy)
# 按下s键保存增强,否则取消保存此次增强
key = cv2.waitKey(0)
if key & 0xff == ord('s'):
pass
else:
return False
# 保存增强后的图片
cv2.imwrite(os.path.join(self.aug_image_save_path, new_image_name), aug_image)
# 保存增强后的xml文件
tree = ET.ElementTree(root)
tree.write(os.path.join(self.aug_xml_save_path, new_xml_name))
return True
# 原始的xml路径和图片路径
PRE_IMAGE_PATH = 'D:\dev\python\datasets\lightv2\images'
PRE_XML_PATH = 'D:\dev\python\datasets\lightv2\Annotations'
# 增强后保存的xml路径和图片路径
AUG_SAVE_IMAGE_PATH ='D:\dev\python\datasets\lightv2\images'
AUG_SAVE_XML_PATH = 'D:\dev\python\datasets\lightv2\Annotations'
# 标签列表
LABELS = ['red', 'green', 'yellow','off']
aug = VOCAug(
pre_image_path=PRE_IMAGE_PATH,
pre_xml_path=PRE_XML_PATH,
aug_image_save_path=AUG_SAVE_IMAGE_PATH,
aug_xml_save_path=AUG_SAVE_XML_PATH,
start_aug_id=None,
labels=LABELS,
is_show=False,
)
aug.aug_image()
# cv2.destroyAllWindows()
# original_image1 = cv2.imread('/home/aistudio/work/TestImage/VOC/images/0000.jpg')
# transformed_image1 = cv2.imread('/home/aistudio/work/TestImage/VOC/images/0003.jpg')
# original_image2 = cv2.imread('/home/aistudio/work/TestImage/VOC/images/0001.jpg')
# transformed_image2 = cv2.imread('/home/aistudio/work/TestImage/VOC/images/0004.jpg')
#
# original_image1 = cv2.cvtColor(original_image1, cv2.COLOR_BGR2RGB)
# transformed_image1 = cv2.cvtColor(transformed_image1, cv2.COLOR_BGR2RGB)
# original_image2 = cv2.cvtColor(original_image2, cv2.COLOR_BGR2RGB)
# transformed_image2 = cv2.cvtColor(transformed_image2, cv2.COLOR_BGR2RGB)
#
# plt.subplot(2, 2, 1), plt.title("original image"), plt.axis('off')
# plt.imshow(original_image1)
# plt.subplot(2, 2, 2), plt.title("transformed image"), plt.axis('off')
# plt.imshow(transformed_image1)
# plt.subplot(2, 2, 3), plt.title("original image"), plt.axis('off')
# plt.imshow(original_image2)
# plt.subplot(2, 2, 4), plt.title("transformed image"), plt.axis('off')
# plt.imshow(transformed_image2)
二、运行过程异常
OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
os.environ["PYTHONUNBUFFERED"]=1
三、超参数设置
hyp.scratch-v1.yaml 和 hyp.scratch-low.yaml 的主要差异:
| 超参数 | hyp.scratch-v1.yaml | hyp.scratch-low.yaml |
| :-: | :-: | :-: |
| lr0 | 0.01 | 0.01 |
| lrf | 0.2 | 0.1 |
| momentum | 0.937 | 0.937|
| weight_decay | 0.0005 | 0.0005|
| warmup_epochs | 3.0 | 2.0 |
| warmup_momentum | 0.8 | 0.8|
| warmup_bias_lr | 0.1 | 0.1 |
| box | 0.05 | 0.05|
| cls | 0.5 | 0.2 |
| cls_pw | 1.0 | 1.0|
| obj | 1.0 | 1.0 |
| obj_pw | 1.0 | 1.0|
| iou_t | 0.2 | 0.2|
| anchor_t | 4.0 | 4.0 |
| fl_gamma| 0.0 | 0.0|
| hsv_h | 0.015 | 0.015|
| hsv_s | 0.7 | 0.7|
| hsv_v | 0.4 | 0.4 |
| degrees | 0.0 | 0.0|
| translate | 0.1 | 0.1|
| scale | 0.5 | 0.5|
| shear | 0.0 | 0.0|
| perspective | 0.0 | 0.0|
| flipud | 0.5 | 0.5|
| fliplr | 0.5 | 0.5|
| mosaic | 1.0 | 0.5|
| mixup | 0.0 | 0.0| 从表中可以清晰地看出,两者的主要差异在于:- lrf:学习率衰减因子,hyp.scratch-low.yaml更低
- cls:分类损失权重,hyp.scratch-low.yaml更低
- mosaic:图像mosaic数据增强概率,hyp.scratch-low.yaml更低其他超参数两者基本一致。所以,总体来说,hyp.scratch-low.yaml采取了相对更为稳定的超参数设置,这可能会使模型训练更加平稳。而hyp.scratch-v1.yaml的设置则更激进一些,训练效果上可能会更高但也更不稳定。