yolov8等目标检测数据集YOLO格式转VOC格式,txt文件转xml文件,格式转换

引言

随着深度学习的快速发展,目标检测任务在计算机视觉领域中占据了重要地位。无论是自动驾驶、安防监控,还是农业监测等领域,目标检测技术都得到了广泛应用。在目标检测任务中,数据集的质量直接影响模型的训练效果和检测精度,标注格式也多种多样。当前,YOLO(You Only Look Once)系列模型因其实时性和高效性在目标检测中非常受欢迎,而 VOC(PASCAL Visual Object Classes)数据集格式则广泛应用于多个经典目标检测框架,如 Faster R-CNN 和 SSD(Single Shot MultiBox Detector)。这两种格式在标注文件的结构和坐标表示上存在显著差异,因此进行数据集格式转换显得尤为重要。

在实际项目中,不同的目标检测框架往往使用不同的数据集标注格式。YOLO 模型采用简单的 .txt 文件进行标注,每一行包含一个物体的类别和位置信息,并且这些坐标是归一化的相对坐标。而 VOC 格式则使用 .xml 文件来描述图片中的物体信息,文件结构更复杂,包含图片的尺寸、物体的类别和精确的绝对坐标信息。

同时,针对数据集图片数量众多,也可采取先部分标注训练,再将训练好的模型检测未标注的图片,同时保存标注文件,这时保存的标注文件大多数是YOLO格式的,如果需要更直观的标注以及后续对数据集进行增强等则需要将YOLO格式转为VOC格式,即.txt转为.xml文件。

目前网络上关于此类数据转换可用的代码较少,因此给出一个单文件稳定可用的版本。

准备工作

两个文件夹,分别是只存放数据集图片的文件夹和只存放标注文件的文本文件夹

python的numpy库及opencv库,运行如下代码

pip install numpy opencv-python

思路介绍

YOLO格式的标注文件是归一化之后的数据,每一行有5个数值,从左到右分别是类别,中心坐标x,中心坐标y,宽度w,高度h,这些数据经过计算后即可得到xmin,ymin,xmax,ymax的值,将这些信息相应的填入xml文件中即可。

xmin = float(box[1] - 0.5 * box[3]) * w
ymin = float(box[2] - 0.5 * box[4]) * h
xmax = float(xmin + box[3] * w)
ymax = float(ymin + box[4] * h)

还有一个关键部分是xml文件的构建,如下代码构建,变量确认后替换。

out0 = '''<annotation>
    <folder>%(folder)s</folder>
    <filename>%(name)s</filename>
    <path>%(path)s</path>
    <source>
        <database>None</database>
    </source>
    <size>
        <width>%(width)d</width>
        <height>%(height)d</height>
        <depth>3</depth>
    </size>
    <segmented>0</segmented>
'''
out1 = '''    <object>
        <name>%(class)s</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>%(xmin)d</xmin>
            <ymin>%(ymin)d</ymin>
            <xmax>%(xmax)d</xmax>
            <ymax>%(ymax)d</ymax>
        </bndbox>
    </object>
'''

out2 = '''</annotation>
'''

完整代码

完整代码如下,使用时需要替换文件夹路径并将类别按照顺序替换

# 作者:CSDN-笑脸惹桃花 https://blog.csdn.net/qq_67105081?type=blog
# github:peng-xiaobai https://github.com/peng-xiaobai/Dataset-Conversion
import os
import cv2
import numpy as np

#xml文件格式
out0 = '''<annotation>
    <folder>%(folder)s</folder>
    <filename>%(name)s</filename>
    <path>%(path)s</path>
    <source>
        <database>None</database>
    </source>
    <size>
        <width>%(width)d</width>
        <height>%(height)d</height>
        <depth>3</depth>
    </size>
    <segmented>0</segmented>
'''
out1 = '''    <object>
        <name>%(class)s</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>%(xmin)d</xmin>
            <ymin>%(ymin)d</ymin>
            <xmax>%(xmax)d</xmax>
            <ymax>%(ymax)d</ymax>
        </bndbox>
    </object>
'''

out2 = '''</annotation>
'''

def upp2low(directory):
    converted_count = 0
    # 检查目录是否存在
    if not os.path.exists(directory):
        raise FileNotFoundError(f"Directory {directory} does not exist.")
    # 遍历文件夹中的所有文件
    for filename in os.listdir(directory):
        file_path = os.path.join(directory, filename)
        # 仅处理文件
        if os.path.isfile(file_path):
            # 拆分文件名和后缀
            name, extension = os.path.splitext(filename)
            # 检查后缀是否为大写
            if extension.isupper():
                new_filename = name + extension.lower()
                new_file_path = os.path.join(directory, new_filename)
                # 重命名文件
                os.rename(file_path, new_file_path)
                converted_count += 1
                print(f"Renamed: {filename} -> {new_filename}")
    print(f"All file suffixes in the folder are lowercase, and a total of {converted_count} files have been processed")
    return converted_count

def yolo2voc(dir1,dir2,dir3,Class):
    file = os.listdir(dir1)

    source = {}
    label = {}
    for img in file:
        print(img)
        if img.endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tif')):
            img1 = os.path.join(dir1, img)
            image = cv2.imread(img1)  # 路径不能有中文
            h, w, _ = image.shape
            name, extension = os.path.splitext(img)
            name1 = name + '.xml'
            name2 = name + '.txt'
            fxml = os.path.join(dir2, name1)
            txt = os.path.join(dir3, name2)
            if not os.path.exists(txt):
                print(f"{name2}未找到,已跳过")
                continue

            fxml = open(fxml, 'w')
            source['name'] = img
            source['path'] = img1
            source['folder'] = os.path.basename(dir1)
            source['width'] = w
            source['height'] = h

            fxml.write(out0 % source)
            lines = np.loadtxt(txt)
            flag = 0
            for box in lines:
                if box.shape != (5,):
                    box = lines
                    flag = 1
                '''把txt上的第一列(类别)转成xml上的类别'''
                box_index = int(box[0])
                label['class'] = Class[box_index] # 类别索引从1开始
                '''把txt上的数字(归一化)转成xml上框的坐标'''
                xmin = float(box[1] - 0.5 * box[3]) * w
                ymin = float(box[2] - 0.5 * box[4]) * h
                xmax = float(xmin + box[3] * w)
                ymax = float(ymin + box[4] * h)
                label['xmin'] = xmin
                label['ymin'] = ymin
                label['xmax'] = xmax
                label['ymax'] = ymax

                keys = ['xmin', 'ymin', 'xmax', 'ymax']
                limits = [w, h, w, h]
                for i, key in enumerate(keys):
                    if label[key] >= limits[i]:
                        label[key] = limits[i]
                    elif label[key] < 0:
                        label[key] = 0

                fxml.write(out1 % label)
                if flag == 1:
                    break
            fxml.write(out2)


if __name__ == '__main__':
    l = ["hat","nohat"] #所有类别
    file_dir1 = r' ' #图像文件夹
    file_dir2 = r' '  #xml存放文件夹
    file_dir3 = r' '  #txt存放文件夹
    if not os.path.exists(file_dir2):
        os.makedirs(file_dir2)
    upp2low(file_dir1)
    yolo2voc(file_dir1,file_dir2,file_dir3,l)
    print('转换已结束')

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笑脸惹桃花

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值