YOLOv5/v7/v8改进实验(二)之数据增强和格式转换篇

您所在的位置:网站首页 数据增强代码怎么做 YOLOv5/v7/v8改进实验(二)之数据增强和格式转换篇

YOLOv5/v7/v8改进实验(二)之数据增强和格式转换篇

2024-07-09 11:26| 来源: 网络整理| 查看: 265

在这里插入图片描述

🚀🚀 前言 🚀🚀

我们常常会遇到数据不足的情况。比如,你遇到的一个任务,目前只有小几百的数据,然而,你知道目前现在流行的最先进的神经网络都是成千上万的图片数据。你知道有人提及大的数据集是效果好的保证,那么这个时候就需要对自己的数据集进行数据增强。

🔥🔥 YOLO系列实验实战篇:

📖 YOLOv5/v7/v8改进实验(一)之数据准备篇 📖 YOLOv5/v7/v8改进实验(二)之数据增强篇 📖 YOLOv5/v7/v8改进实验(三)之训练技巧篇

更新中…

目录 一、数据增强1.1 常用方法1.2 代码实现 二、数据转换2.1 多线程处理2.2 VOC转YOLO实现代码 2.3 YOLO转VOC实现代码 2.4 YOLO转COCO实现代码

一、数据增强

数据增强(data augmentation)是在训练神经网络时对原始数据进行一系列变换操作以产生新的样本的技术。它在深度学习中具有重要的意义和重要性,主要体现在以下几个方面:

扩充数据集:数据增强通过对原始数据应用不同的变换操作,可以生成更多、更多样化的训练样本。这样可以有效扩充原始数据集,增加训练样本的多样性和数量,从而减轻模型的过拟合问题。

提高模型泛化能力:数据增强可以在不改变数据标签的情况下引入多样性,使得模型更加鲁棒,能够更好地适应不同的场景和数据变化。通过引入噪声、旋转、平移、缩放等变换,模型可以学习到更加鲁棒的特征表示,提高泛化能力。

减少过拟合风险:在深度学习中,当训练样本有限时,模型容易发生过拟合现象,即在训练集上表现良好但在测试集上表现较差。数据增强通过引入变换操作,增加了样本空间的多样性,可以使得模型更好地捕捉数据的统计规律,减少过拟合的风险。

对抗噪声和变化:在真实世界的应用中,数据常常面临各种噪声和变化,如光照变化、仿射变换、几何扭曲等。数据增强可以模拟这些变化,并使得模型在训练中更好地学习如何应对这些挑战,提高模型的鲁棒性和稳定性。

1.1 常用方法 比较常用的几何变换方法主要有:翻转,旋转,裁剪,缩放,平移,抖动。值得注意的是,在某些具体的任务中,当使用这些方法时需要主要标签数据的变化,如目标检测中若使用翻转,则需要将gt框进行相应的调整。比较常用的像素变换方法有:加椒盐噪声,高斯噪声,进行高斯模糊,调整HSV对比度,调节亮度,饱和度,直方图均衡化,调整白平衡等。还有以下数据增强方式: Mixup:将随机的两张样本按比例混合,分类的结果按比例分配。只适合分类任务。Cutout:随机的将样本中的部分区域cut掉,并且填充0像素值,分类的结果不变。Cutmix:将一部分区域cut掉但不填充0像素而是随机填充训练集中的其他数据的区域像素值,分类结果按一定的比例分配。Mosaic:将4张图片按一定比例组合成一张图片。 1.2 代码实现 点击下载网盘链接 二、数据转换

据集的分布会影响模型的性能和泛化能力。如果训练数据和实际应用场景的数据分布不一致,模型可能会出现过拟合或欠拟合的问题。了解数据分布可以帮助判断是否存在类别不平衡问题,有助于选择适当的算法和模型,以提高模型的泛化能力。

2.1 多线程处理

大量数据的处理需要消耗大量的时间,所以这里选择使用多线程转换速度,提高效率!具体代码如下:

from tqdm import tqdm from concurrent.futures import ThreadPoolExecutor NUM_THREADS = min(8, max(1, os.cpu_count() - 1)) def run(func, this_iter, desc="Processing"): with ThreadPoolExecutor(max_workers=NUM_THREADS, thread_name_prefix='MyThread') as executor: results = list( tqdm(executor.map(func, this_iter), total=len(this_iter), desc=desc) ) return results 2.2 VOC转YOLO

您的数据集存放格式可以如下所示:

- mydata - Annotations - images

运行脚本后会生成labels文件夹(用于存放txt文件)和classes.txt文件(记录种类)

- mydata - Annotations - images - labels - classes.txt 实现代码 import glob import os import re import xml.etree.ElementTree as ET from pathlib import Path import cv2 import numpy as np from tqdm import tqdm from concurrent.futures import ThreadPoolExecutor NUM_THREADS = min(8, max(1, os.cpu_count() - 1)) def run(func, this_iter, desc="Processing"): with ThreadPoolExecutor(max_workers=NUM_THREADS, thread_name_prefix='MyThread') as executor: results = list( tqdm(executor.map(func, this_iter), total=len(this_iter), desc=desc) ) return results # XML坐标格式转换成yolo坐标格式 def convert(size, box): dw = 1.0 / size[0] dh = 1.0 / 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 get_xml_classes(xml_path): f = open(xml_path) # xml文件路径 xml_text = f.read() root = ET.fromstring(xml_text) f.close() for obj in root.iter("object"): cls = obj.find("name").text if cls not in xml_classes: classes_file.write(cls + "\n") xml_classes.append(cls) # 标记文件格式转换 def convert_xml2yolo(img_path): img_path = Path(img_path) xml_name = re.sub(r"\.(jpg|png|jpeg)$", ".xml", img_path.name) txt_name = re.sub(r"\.(jpg|png|jpeg)$", ".txt", img_path.name) xml_path = Path(xml_target_path) / xml_name txt_path = Path(save_path) / txt_name if xml_path.exists(): out_file = open(txt_path, "w") # 转换后的txt文件存放路径 f = open(xml_path) # xml文件路径 xml_text = f.read() root = ET.fromstring(xml_text) f.close() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) if w == 0 or h == 0: # problem_xml.append(str(img_path.name)) img = cv2.imdecode(np.fromfile(img_path, dtype=np.uint8), 1) h, w, _ = img.shape for obj in root.iter("object"): cls = obj.find("name").text if cls not in xml_classes: print(cls) continue cls_id = xml_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), ) try: bbox = convert((w, h), b) except: print(img_path) out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bbox]) + "\n") else: print(f"{xml_path}不存在!") if __name__ == "__main__": xml_target_path = r"data\Annotations" # xml文件夹 save_path = r"data\labels" # 转换后的txt文件存放文件夹 images_path = r"data\images" # 图片文件夹 classes_file = open(Path(xml_target_path).parents[0] / "classes.txt", "w") # -------------------------------------------- # # 第一步 获得xml所有种类 # -------------------------------------------- # assert (Path(xml_target_path)).exists(), "Annotations文件夹不存在" xml_classes = [] xml_list = glob.glob(os.path.join(xml_target_path, "*.[x][m][l]*")) run(get_xml_classes, xml_list) print(Path(xml_target_path).parents[0]) print(xml_classes) # -------------------------------------------- # # 第二步 转换成YOLO txt # -------------------------------------------- # if not Path(save_path).exists(): Path(save_path).mkdir(parents=True) file_list = glob.glob(os.path.join(images_path, "*.[jp][pn][gg]*")) run(convert_xml2yolo, file_list) 2.3 YOLO转VOC

您的数据集存放格式可以如下所示:

- mydata - images - labels

运行脚本后会生成Annotations文件夹(用于存放xml文件)

- mydata - Annotations - images - labels 实现代码 import glob from pathlib import Path from xml.dom.minidom import Document import os import cv2 from tqdm import tqdm from concurrent.futures import ThreadPoolExecutor NUM_THREADS = min(8, max(1, os.cpu_count() - 1)) def run(func, this_iter, desc="Processing"): with ThreadPoolExecutor(max_workers=NUM_THREADS, thread_name_prefix='MyThread') as executor: results = list( tqdm(executor.map(func, this_iter), total=len(this_iter), desc=desc) ) return results def makexml(file_name): try: name = Path(file_name).name xmlBuilder = Document() annotation = xmlBuilder.createElement("annotation") # 创建annotation标签 xmlBuilder.appendChild(annotation) txtFile = open(txtPath + name) txtList = txtFile.readlines() img = cv2.imread(picPath + name[0:-4] + ".jpg") Pheight, Pwidth, Pdepth = img.shape folder = xmlBuilder.createElement("folder") # folder标签 foldercontent = xmlBuilder.createTextNode(folder_name) folder.appendChild(foldercontent) annotation.appendChild(folder) # folder标签结束 filename = xmlBuilder.createElement("filename") # filename标签 filenamecontent = xmlBuilder.createTextNode(name[0:-4] + ".jpg") filename.appendChild(filenamecontent) annotation.appendChild(filename) # filename标签结束 size = xmlBuilder.createElement("size") # size标签 width = xmlBuilder.createElement("width") # size子标签width widthcontent = xmlBuilder.createTextNode(str(Pwidth)) width.appendChild(widthcontent) size.appendChild(width) # size子标签width结束 height = xmlBuilder.createElement("height") # size子标签height heightcontent = xmlBuilder.createTextNode(str(Pheight)) height.appendChild(heightcontent) size.appendChild(height) # size子标签height结束 depth = xmlBuilder.createElement("depth") # size子标签depth depthcontent = xmlBuilder.createTextNode(str(Pdepth)) depth.appendChild(depthcontent) size.appendChild(depth) # size子标签depth结束 annotation.appendChild(size) # size标签结束 for j in txtList: oneline = j.strip().split(" ") object = xmlBuilder.createElement("object") # object 标签 picname = xmlBuilder.createElement("name") # name标签 namecontent = xmlBuilder.createTextNode(dic[oneline[0]]) picname.appendChild(namecontent) object.appendChild(picname) # name标签结束 pose = xmlBuilder.createElement("pose") # pose标签 posecontent = xmlBuilder.createTextNode("Unspecified") pose.appendChild(posecontent) object.appendChild(pose) # pose标签结束 truncated = xmlBuilder.createElement("truncated") # truncated标签 truncatedContent = xmlBuilder.createTextNode("0") truncated.appendChild(truncatedContent) object.appendChild(truncated) # truncated标签结束 difficult = xmlBuilder.createElement("difficult") # difficult标签 difficultcontent = xmlBuilder.createTextNode("0") difficult.appendChild(difficultcontent) object.appendChild(difficult) # difficult标签结束 bndbox = xmlBuilder.createElement("bndbox") # bndbox标签 xmin = xmlBuilder.createElement("xmin") # xmin标签 mathData = int(((float(oneline[1])) * Pwidth + 1) - (float(oneline[3])) * 0.5 * Pwidth) xminContent = xmlBuilder.createTextNode(str(mathData)) xmin.appendChild(xminContent) bndbox.appendChild(xmin) # xmin标签结束 ymin = xmlBuilder.createElement("ymin") # ymin标签 mathData = int(((float(oneline[2])) * Pheight + 1) - (float(oneline[4])) * 0.5 * Pheight) yminContent = xmlBuilder.createTextNode(str(mathData)) ymin.appendChild(yminContent) bndbox.appendChild(ymin) # ymin标签结束 xmax = xmlBuilder.createElement("xmax") # xmax标签 mathData = int(((float(oneline[1])) * Pwidth + 1) + (float(oneline[3])) * 0.5 * Pwidth) xmaxContent = xmlBuilder.createTextNode(str(mathData)) xmax.appendChild(xmaxContent) bndbox.appendChild(xmax) # xmax标签结束 ymax = xmlBuilder.createElement("ymax") # ymax标签 mathData = int(((float(oneline[2])) * Pheight + 1) + (float(oneline[4])) * 0.5 * Pheight) ymaxContent = xmlBuilder.createTextNode(str(mathData)) ymax.appendChild(ymaxContent) bndbox.appendChild(ymax) # ymax标签结束 object.appendChild(bndbox) # bndbox标签结束 annotation.appendChild(object) # object标签结束 f = open(xmlPath + name[0:-4] + ".xml", 'w') xmlBuilder.writexml(f, indent='\t', newl='\n', addindent='\t', encoding='utf-8') f.close() except Exception as e: print(e) def main(txtPath): # txt所在文件夹路径,xml文件保存路径,图片所在文件夹路径 """此函数用于将yolo格式txt标注文件转换为voc格式xml标注文件 """ # files = os.listdir(txtPath) files = glob.glob(os.path.join(txtPath, '*.[t][x][t]*')) run(makexml, files) if __name__ == "__main__": dic = { '0': "Dead tree", # 创建字典用来对类型进行转换 '1': "Sick tree", # 此处的字典要与自己的classes.txt文件中的类对应,且顺序要一致 } folder_name = "JPEGImages" # # folder标签,可更改 picPath = r"data/images/" # 图片所在文件夹路径,后面的/一定要带上 txtPath = r"data/labels/" # txt所在文件夹路径,后面的/一定要带上 xmlPath = r"data/Annotations/" # xml文件保存路径,后面的/一定要带上 assert (Path(picPath)).exists() or (Path(txtPath)).exists(), f"{picPath}或{txtPath}文件夹不存在" if not Path(xmlPath).exists(): Path(xmlPath).mkdir(parents=True) main(txtPath) 2.4 YOLO转COCO

您的数据集存放格式可以如下所示:

- mydata - test - images - labels - classes.txt

classes.txt存放目标类别信息,注意顺序要对应。运行脚本后会在当前根目录下生成instances_val2017.json文件夹

实现代码 ''' Date: 2023-10-18 10:41:52 LastEditors: xujiayue LastEditTime: 2023-10-18 10:46:18 ''' import os import cv2 import json from tqdm import tqdm # from sklearn.model_selection import train_test_split import argparse parser = argparse.ArgumentParser() parser.add_argument('--root_dir', default=r'F:\ObjectDetection\Datasets\Experiment Datasets\archive721', type=str, help="root path of images and labels, include ./images and ./labels and classes.txt") parser.add_argument('--save_path', type=str, default='instances_val2017.json', help="if not split the dataset, give a path to a json file") arg = parser.parse_args() def yolo2coco(arg): root_path = arg.root_dir print("Loading data from ", root_path) assert os.path.exists(root_path) originLabelsDir = os.path.join(root_path, 'test/labels') originImagesDir = os.path.join(root_path, 'test/images') with open(os.path.join(root_path, 'classes.txt')) as f: classes = list(map(lambda x: x.strip(), f.readlines())) # images dir name indexes = os.listdir(originImagesDir) dataset = {'categories': [], 'annotations': [], 'images': []} for i, cls in enumerate(classes, 0): dataset['categories'].append({'id': i, 'name': cls, 'supercategory': 'mark'}) # 标注的id ann_id_cnt = 0 for k, index in enumerate(tqdm(indexes)): # 支持 png jpg 格式的图片。 txtFile = index.replace('images', 'txt').replace('.jpg', '.txt').replace('.png', '.txt') # 读取图像的宽和高 im = cv2.imread(os.path.join(originImagesDir, index)) height, width, _ = im.shape # 添加图像的信息 if not os.path.exists(os.path.join(originLabelsDir, txtFile)): # 如没标签,跳过,只保留图片信息。 continue dataset['images'].append({'file_name': index, 'id': int(index[:-4]) if index[:-4].isnumeric() else index[:-4], 'width': width, 'height': height}) with open(os.path.join(originLabelsDir, txtFile), 'r') as fr: labelList = fr.readlines() for label in labelList: label = label.strip().split() x = float(label[1]) y = float(label[2]) w = float(label[3]) h = float(label[4]) # convert x,y,w,h to x1,y1,x2,y2 H, W, _ = im.shape x1 = (x - w / 2) * W y1 = (y - h / 2) * H x2 = (x + w / 2) * W y2 = (y + h / 2) * H # 标签序号从0开始计算, coco2017数据集标号混乱,不管它了。 cls_id = int(label[0]) width = max(0, x2 - x1) height = max(0, y2 - y1) dataset['annotations'].append({ 'area': width * height, 'bbox': [x1, y1, width, height], 'category_id': cls_id, 'id': ann_id_cnt, 'image_id': int(index[:-4]) if index[:-4].isnumeric() else index[:-4], 'iscrowd': 0, # mask, 矩形是从左上角点按顺时针的四个顶点 'segmentation': [[x1, y1, x2, y1, x2, y2, x1, y2]] }) ann_id_cnt += 1 # 保存结果 with open(arg.save_path, 'w') as f: json.dump(dataset, f) print('Save annotation to {}'.format(arg.save_path)) if __name__ == "__main__": yolo2coco(arg)

在这里插入图片描述



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3