VOC.yaml
ultralytics\cfg\datasets\VOC.yaml
目录
VOC.yaml
1.YAML文件内容
2.所需的库和模块
3.def convert_label(path, lb_path, year, image_id):
4.Download
5.Convert
1.YAML文件内容
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license# PASCAL VOC dataset http://host.robots.ox.ac.uk/pascal/VOC by University of Oxford
# Documentation: # Documentation: https://docs.ultralytics.com/datasets/detect/voc/
# Example usage: yolo train data=VOC.yaml
# parent
# ├── ultralytics
# └── datasets
# └── VOC ← downloads here (2.8 GB)# 示例用法: yolo train data=VOC.yaml# 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, ..] 训练/验证/测试集为 1)目录:path/to/imgs,2)文件:path/to/imgs.txt,或 3)列表:[path/to/imgs1, path/to/imgs2, ..]
path: ../datasets/VOC
train: # train images (relative to 'path') 16551 images 训练图像(相对于“path”) 16551 幅图像。- images/train2012- images/train2007- images/val2012- images/val2007
val: # val images (relative to 'path') 4952 images val 图像(相对于“path”) 4952 幅图像。- images/test2007
test: # test images (optional) 测试图像(可选)。- images/test2007# Classes
names:0: aeroplane1: bicycle2: bird3: boat4: bottle5: bus6: car7: cat8: chair9: cow10: diningtable11: dog12: horse13: motorbike14: person15: pottedplant16: sheep17: sofa18: train19: tvmonitor# Download script/URL (optional) 下载脚本/URL(可选) ---------------------------------------------------------------------------------------
download: |import xml.etree.ElementTree as ETfrom tqdm import tqdmfrom ultralytics.utils.downloads import downloadfrom pathlib import Pathdef convert_label(path, lb_path, year, image_id):def convert_box(size, box):dw, dh = 1. / size[0], 1. / size[1]x, y, w, h = (box[0] + box[1]) / 2.0 - 1, (box[2] + box[3]) / 2.0 - 1, box[1] - box[0], box[3] - box[2]return x * dw, y * dh, w * dw, h * dhin_file = open(path / f'VOC{year}/Annotations/{image_id}.xml')out_file = open(lb_path, '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)names = list(yaml['names'].values()) # names listfor obj in root.iter('object'):cls = obj.find('name').textif cls in names and int(obj.find('difficult').text) != 1:xmlbox = obj.find('bndbox')bb = convert_box((w, h), [float(xmlbox.find(x).text) for x in ('xmin', 'xmax', 'ymin', 'ymax')])cls_id = names.index(cls) # class idout_file.write(" ".join(str(a) for a in (cls_id, *bb)) + '\n')# Downloaddir = Path(yaml['path']) # dataset root dirurl = 'https://github.com/ultralytics/assets/releases/download/v0.0.0/'urls = [f'{url}VOCtrainval_06-Nov-2007.zip', # 446MB, 5012 imagesf'{url}VOCtest_06-Nov-2007.zip', # 438MB, 4953 imagesf'{url}VOCtrainval_11-May-2012.zip'] # 1.95GB, 17126 imagesdownload(urls, dir=dir / 'images', curl=True, threads=3, exist_ok=True) # download and unzip over existing paths (required)# Convertpath = dir / 'images/VOCdevkit'for year, image_set in ('2012', 'train'), ('2012', 'val'), ('2007', 'train'), ('2007', 'val'), ('2007', 'test'):imgs_path = dir / 'images' / f'{image_set}{year}'lbs_path = dir / 'labels' / f'{image_set}{year}'imgs_path.mkdir(exist_ok=True, parents=True)lbs_path.mkdir(exist_ok=True, parents=True)with open(path / f'VOC{year}/ImageSets/Main/{image_set}.txt') as f:image_ids = f.read().strip().split()for id in tqdm(image_ids, desc=f'{image_set}{year}'):f = path / f'VOC{year}/JPEGImages/{id}.jpg' # old img pathlb_path = (lbs_path / f.name).with_suffix('.txt') # new label pathf.rename(imgs_path / f.name) # move imageconvert_label(path, lb_path, year, id) # convert labels to YOLO format
2.所需的库和模块
# Example usage: yolo train data=VOC.yamlimport xml.etree.ElementTree as ETfrom tqdm import tqdm
from ultralytics.utils.downloads import download
from pathlib import Path
3.def convert_label(path, lb_path, year, image_id):
# 这段代码定义了一个名为 convert_label 的函数,用于将 VOC 数据集中的标注文件从 XML 格式转换为 YOLO 格式的标注文件。
# 定义了一个函数 convert_label ,它接受四个参数。
# path :数据集的根目录路径。
# lb_path :转换后的标注文件保存路径。
# year :VOC 数据集的年份(如 2007 或 2012)。
# image_id :图像的 ID,用于定位对应的 XML 标注文件。
def convert_label(path, lb_path, year, image_id):# 在函数内部定义了一个嵌套函数 convert_box ,用于将 VOC 标注格式的边界框( xmin , xmax , ymin , ymax )转换为 YOLO 格式( x_center , y_center , width , height )。def convert_box(size, box):# 计算 宽度和高度的 归一化因子 dw 和 dh ,分别为图像宽度和高度的倒数。dw, dh = 1. / size[0], 1. / size[1]# 将 VOC 标注格式的边界框转换为 YOLO 格式。# x 和 y 分别是边界框中心点的坐标,计算公式为 (xmin + xmax) / 2 和 (ymin + ymax) / 2 ,减去 1 是为了将坐标从 1 开始的索引转换为从 0 开始的索引。# w 和 h 分别是边界框的宽度和高度,计算公式为 xmax - xmin 和 ymax - ymin 。x, y, w, h = (box[0] + box[1]) / 2.0 - 1, (box[2] + box[3]) / 2.0 - 1, box[1] - box[0], box[3] - box[2]# 返回 归一化后的边界框坐标 ,通过乘以 dw 和 dh ,将坐标值归一化到 [0, 1] 范围内。return x * dw, y * dh, w * dw, h * dh# 打开 VOC 数据集的 XML 标注文件,文件路径通过组合 path 、 year 和 image_id 构造。in_file = open(path / f'VOC{year}/Annotations/{image_id}.xml')# 打开 目标标注文件路径 lb_path ,以写入模式打开,用于保存转换后的 YOLO 格式标注。out_file = open(lb_path, 'w')# ET.parse(source, parser=None)# ET.parse() 是 Python 中 xml.etree.ElementTree 模块提供的一个函数,用于解析 XML 文件并将其加载到内存中,以便进行进一步的处理。 ElementTree 是 Python 标准库中用于解析和操作 XML 数据的模块。# 参数 :# source :类型 :可以是文件名(字符串)、文件对象或类似文件的对象。 说明 :指定要解析的 XML 文件的路径或文件对象。# parser (可选) : 类型 : xml.etree.ElementTree.XMLParser 的实例。 说明 :用于解析 XML 的解析器对象。如果不提供,默认使用 XMLParser 。# 返回值 :# 返回一个 xml.etree.ElementTree.ElementTree 对象,表示解析后的 XML 文档的树形结构。# 详细说明 :# 解析 XML 文件 :# ET.parse(source) 会读取指定的 XML 文件,并将其解析为一个树形结构。# 返回的 ElementTree 对象可以用来访问和操作 XML 文档。# 获取根节点 :# 使用 tree.getroot() 方法可以获取 XML 文档的根节点( <root> )。# 遍历 XML 文档 :# 使用 findall() 方法可以查找所有匹配的子节点。# 使用 find() 方法可以查找第一个匹配的子节点。# 使用 .text 属性可以获取节点的文本内容。# 使用自定义解析器 :# 如果需要自定义解析行为,可以传递一个 XMLParser 实例作为 parser 参数。例如 :# parser = ET.XMLParser(encoding='utf-8')# tree = ET.parse('example.xml', parser=parser)# 总结 : ET.parse() 是一个用于解析 XML 文件的函数,它将 XML 文件加载为一个树形结构,返回一个 ElementTree 对象。通过这个对象,可以方便地访问和操作 XML 文档的各个节点。# 使用 xml.etree.ElementTree 模块解析 XML 文件,将其加载到内存中。tree = ET.parse(in_file)# 获取 XML 文件的根节点,用于 后续遍历 。root = tree.getroot()# 找到 XML 文件中 <size> 标签,该标签 包含图像的宽度和高度信息 。size = root.find('size')# 从 <size> 标签中提取图像的 宽度 和 高度 ,并将其转换为整数。w = int(size.find('width').text)h = int(size.find('height').text)# 从 yaml 文件中提取 类别名称列表 。 yaml 文件中包含类别名称的映射, names 是一个列表,存储了 所有可能的类别名称 。names = list(yaml['names'].values()) # names list# 遍历 XML 文件中所有的 <object> 标签,每个 <object> 标签代表一个 标注对象 。for obj in root.iter('object'):# 提取当前对象的 类别名称 ,存储在 <name> 标签中。cls = obj.find('name').text# 检查 类别名称 是否在 names 列表中,并且对象的难度标记( difficult )不为 1。如果类别名称不存在或难度为 1,则跳过该对象。if cls in names and int(obj.find('difficult').text) != 1:# 提取当前对象的 边界框信息 ,存储在 <bndbox> 标签中。xmlbox = obj.find('bndbox')# 调用 convert_box 函数,将 VOC 格式的边界框转换为 YOLO 格式。 xmin , xmax , ymin , ymax 是从 <bndbox> 标签中提取的边界框坐标。bb = convert_box((w, h), [float(xmlbox.find(x).text) for x in ('xmin', 'xmax', 'ymin', 'ymax')])# 获取 当前类别的索引 cls_id ,即类别名称在 names 列表中的位置。cls_id = names.index(cls) # class id# 将 类别 ID 和 归一化后的边界框坐标 写入 目标标注文件 ,格式为 class_id x_center y_center width height ,每行代表一个标注对象。out_file.write(" ".join(str(a) for a in (cls_id, *bb)) + '\n')
# 这段代码的主要功能是将 VOC 数据集的 XML 标注文件转换为 YOLO 格式的标注文件。它通过解析 XML 文件,提取图像尺寸、对象类别和边界框信息,并将这些信息转换为 YOLO 所需的格式。代码中使用了嵌套函数 convert_box 来完成边界框格式的转换,并通过条件判断过滤掉了难度为 1 的标注对象。最终,转换后的标注信息被写入指定的文件中,以便用于 YOLO 模型的训练或推理。
4.Download
# 这段代码的作用是下载并解压 VOC 数据集的压缩文件到指定的目录。
# Download
# 从 yaml 配置中获取 path 键对应的值,并将其作为 数据集的根目录路径 。 使用 Path (来自 pathlib 模块)来处理路径, Path 提供了更方便的路径操作方法。
dir = Path(yaml['path']) # dataset root dir
# 定义一个基础 URL,用于构建完整的下载链接。这里指向的是一个托管在 GitHub 的资源仓库,可能是用于存储数据集文件的仓库。
url = 'https://github.com/ultralytics/assets/releases/download/v0.0.0/'
# 定义一个列表 urls ,包含三个 VOC 数据集的下载链接。
# VOCtrainval_06-Nov-2007.zip :包含 VOC 2007 的训练和验证集,大小约为 446MB,包含 5012 张图像。
# VOCtest_06-Nov-2007.zip :包含 VOC 2007 的测试集,大小约为 438MB,包含 4953 张图像。
# VOCtrainval_11-May-2012.zip :包含 VOC 2012 的训练和验证集,大小约为 1.95GB,包含 17126 张图像。
urls = [f'{url}VOCtrainval_06-Nov-2007.zip', # 446MB, 5012 imagesf'{url}VOCtest_06-Nov-2007.zip', # 438MB, 4953 imagesf'{url}VOCtrainval_11-May-2012.zip'] # 1.95GB, 17126 images
# 调用 download 函数,用于 下载和解压数据集文件 。
# urls :包含下载链接的列表。
# dir=dir / 'images' :指定下载和解压的目标目录,路径为 yaml['path'] 下的 images 文件夹。
# curl=True :使用 curl 命令进行下载( curl 是一个常用的命令行工具,用于从网络下载文件)。
# threads=3 :同时使用 3 个线程进行下载,提高下载效率。
# exist_ok=True :如果目标路径已经存在,允许覆盖现有文件。这对于重新下载或更新数据集很有用。
# def download(url, dir=Path.cwd(), unzip=True, delete=False, curl=False, threads=1, retry=3, exist_ok=False): -> 用于从指定的 URL 下载文件,并支持多种功能,包括多线程下载、自动解压、删除源文件、使用 curl 下载等。
download(urls, dir=dir / 'images', curl=True, threads=3, exist_ok=True) # download and unzip over existing paths (required)
# 这段代码的作用是下载 VOC 数据集的三个压缩文件,并将它们解压到指定的目录。具体步骤如下。从 yaml 配置中获取数据集的根目录路径。构建包含数据集压缩文件下载链接的列表。调用 download 函数,使用 curl 和多线程下载并解压文件到目标目录。如果目标路径已存在,允许覆盖现有文件。
5.Convert
# 这段代码的作用是将 VOC 数据集的图像和标注文件从原始格式转换为 YOLO 格式,并将它们重新组织到指定的目录结构中。
# Convert
# 定义 path ,指向 VOC 数据集的根目录,通常是 VOCdevkit 文件夹。
path = dir / 'images/VOCdevkit'
# 遍历 VOC 数据集的 不同年份 和 数据集类型 (训练集、验证集、测试集)。这里涉及 VOC 2007 和 VOC 2012 的 train 、 val 和 VOC 2007 的 test 。
for year, image_set in ('2012', 'train'), ('2012', 'val'), ('2007', 'train'), ('2007', 'val'), ('2007', 'test'):# 定义 目标图像路径 ,格式为 dir/images/{image_set}{year} ,例如 dir/images/train2012 。imgs_path = dir / 'images' / f'{image_set}{year}'# 定义 目标标注路径 ,格式为 dir/labels/{image_set}{year} ,例如 dir/labels/train2012 。lbs_path = dir / 'labels' / f'{image_set}{year}'# 创建目标图像路径的目录,如果目录已存在则忽略( exist_ok=True ),同时创建所有必要的父目录( parents=True )。imgs_path.mkdir(exist_ok=True, parents=True)# 创建目标标注路径的目录,逻辑同上。lbs_path.mkdir(exist_ok=True, parents=True)# 打开 VOC 数据集的图像列表文件,路径为 VOCdevkit/VOC{year}/ImageSets/Main/{image_set}.txt 。这个文件包含当前数据集的图像 ID 列表。with open(path / f'VOC{year}/ImageSets/Main/{image_set}.txt') as f:# 读取文件内容,去除首尾空白字符后按空格分割,得到图像 ID 列表。image_ids = f.read().strip().split()# 遍历图像 ID 列表,使用 tqdm 显示进度条,进度条的描述为 {image_set}{year} 。for id in tqdm(image_ids, desc=f'{image_set}{year}'):# 构造 原始图像路径 ,格式为 VOCdevkit/VOC{year}/JPEGImages/{id}.jpg 。f = path / f'VOC{year}/JPEGImages/{id}.jpg' # old img path# 构造 目标标注路径 ,将图像文件名( f.name )替换为 .txt 后缀,用于保存 YOLO 格式的标注文件。lb_path = (lbs_path / f.name).with_suffix('.txt') # new label path# Path.rename(target)# 在 Python 的 pathlib 模块中, Path.rename() 是一个方法,用于将文件或目录从一个路径重命名为另一个路径。它是 pathlib.Path 类的一部分,提供了面向对象的方式来操作文件系统路径。# 参数 :# source :调用 rename() 方法的 Path 对象,表示要被重命名的文件或目录的原始路径。# target :目标路径,表示重命名后的文件或目录的路径。# 功能 :# rename() 方法将文件或目录从 source 路径移动到 target 路径。如果目标路径已经存在 :# 如果目标路径是一个文件,它会被覆盖。# 如果目标路径是一个目录,行为取决于操作系统。在某些系统中,会抛出错误;在其他系统中,可能会将文件移动到该目录下。# 返回值 :# rename() 方法返回一个新的 Path 对象,表示目标路径。# 注意事项 :# 覆盖文件 :如果目标路径已经存在, rename() 会覆盖目标文件。# 跨文件系统移动 :在某些操作系统中,如果 source 和 target 不在同一个文件系统上,可能会抛出错误。在这种情况下,可以先复制文件,然后删除原始文件。# 权限问题 :如果目标路径的目录不可写, rename() 会抛出 PermissionError 。# 将原始图像移动到目标图像路径。 rename 方法会将文件从原始路径移动到新路径。f.rename(imgs_path / f.name) # move image# 调用 convert_label 函数,将 VOC 格式的标注文件转换为 YOLO 格式,并保存到目标标注路径。convert_label(path, lb_path, year, id) # convert labels to YOLO format
# 这段代码的主要功能是将 VOC 数据集的图像和标注文件从原始格式转换为 YOLO 格式,并重新组织到指定的目录结构中。具体步骤如下。遍历 VOC 数据集的不同年份和数据集类型。创建目标图像和标注文件的目录。读取每个数据集的图像 ID 列表。遍历图像 ID,将原始图像移动到目标目录。调用 convert_label 函数,将 VOC 格式的标注文件转换为 YOLO 格式,并保存到目标路径。通过这种方式,代码实现了 VOC 数据集到 YOLO 格式的转换,同时将文件组织到适合 YOLO 训练的目录结构中。