VOC 格式 XML 标注文件转 YOLO 格式 TXT 文件,目标检测标注格式转换

目标检测实战:仪表盘指针识别

保姆级 YOLOv8/9/10 教程,附完整数据集与源码

在目标检测任务中,标注文件的格式往往需要与训练框架相匹配。YOLO 系列模型通常采用 TXT 格式的标注文件,而很多标注工具(如 LabelImg)默认生成的是 XML 格式标注。今天分享一段实用代码,可快速将 XML 标注文件转换为 YOLO 所需的 TXT 格式,同时自动整理图片和标注文件路径,方便后续模型训练。

代码功能概述

该代码主要实现以下功能:

  1. 解析 XML 格式的标注文件,提取目标类别、边界框坐标等信息

  2. 将 XML 中的边界框坐标(xmin, ymin, xmax, ymax)转换为 YOLO 格式的归一化坐标(中心 x, 中心 y, 宽,高)

  3. 为每张图片生成对应的 TXT 标注文件

  4. 自动复制图片到指定目录,与标注文件对应存放

完整代码

#xml文件转txt文件
import os
import random
import xml.etree.ElementTree as ET
import glob
import shutil

#img存放文件夹
ImgPath = 'datasets/images'
#xml存放文件夹
XmlPath = 'datasets/Annotations'
#处理后数据集存放位置
txtSavePath = 'datasets/labels'
fileWriteText = txtSavePath + '/' + ImgPath.split('/')[-1]+'_v8.txt'
xmls = glob.glob(os.path.join(XmlPath,'*.xml'))

txtpathdir = fileWriteText
savetxtpath = txtSavePath + '/labels'
txtpathdir = fileWriteText
saveimgpath = txtSavePath + '/images'

# 不存在,创建目录
if not os.path.exists(savetxtpath):
    os.makedirs(savetxtpath)
if not os.path.exists(saveimgpath):
    os.makedirs(saveimgpath)
list_file = open(fileWriteText,'w',encoding='utf-8')

#标签类别
classes = ['自定义'] #修改为自己的类别


def convert(size, box):
    dw = 1. / size[0]
    dh = 1. / size[1]
    x = (box[0] + box[2]) / 2.0
    y = (box[1] + box[3]) / 2.0
    w = min(size[0], box[2] - box[0])
    h = min(size[1], box[3] - box[1])
    x = x * dw
    w = w * dw
    y = y * dh
    h = h * dh
    return (x, y, w, h)


def convert_annotation(xml, list_file):
    in_file = open(os.path.join(xml), encoding='utf-8')
    tree = ET.parse(in_file)
    root = tree.getroot()

    xmlsize = root.find('size')
    w = int(xmlsize.find('width').text)
    h = int(xmlsize.find('height').text)

    for obj in root.iter('object'):
        difficult = 0
        if obj.find('difficult') != None:
            difficult = obj.find('difficult').text
        cls = obj.find('name').text
        if cls not in classes or int(difficult) == 1:
            print(cls + "----------------------------------" + '\n')
            continue
        cls_id = classes.index(cls)
        print(cls, cls_id)
        xmlbox = obj.find('bndbox')
        x0 = float(xmlbox.find('xmin').text)
        y0 = float(xmlbox.find('ymin').text)
        x1 = float(xmlbox.find('xmax').text)
        y1 = float(xmlbox.find('ymax').text)
        xmin = min(x0, x1)
        ymin = min(y0, y1)
        xmax = max(x0, x1)
        ymax = max(y0, y1)
        b = (float(xmin), float(ymin), float(xmax), float(ymax))
        bb = convert((w, h), b)
        list_file.write(" " + str(cls_id) + ',' + ','.join([str(a) for a in bb]))


for xml in xmls:
    img = xml.replace(XmlPath,ImgPath).replace('.xml','.jpg')
    if not os.path.exists(img):
        print(img,'is not exit')
        continue
    list_file.write(img)
    convert_annotation(xml,list_file)
    list_file.write('\n')
list_file.close()

file = open(txtpathdir,'r',encoding='utf-8')
lines = file.readlines()
for line in lines:
    line = line.split('\n')[0]
    imgdir = line.split(' ')[0]
    bboxinfo = line.split(' ')[1:]
    savetxtdir = os.path.join(savetxtpath,imgdir.split('\\')[-1].replace('.jpg','.txt'))
    file = open(savetxtdir,'w',encoding='utf-8')
    for i in range(len(bboxinfo)):
        info = bboxinfo[i].split(',')
        info1 = ' '.join(info)
        print(info1)
        file.write(info1+'\n')
    file.close()

file = open(txtpathdir,'r',encoding='utf-8')
lines = file.readlines()
for line in lines:
    line = line.split('\n')[0]
    imgdir = line.split(' ')[0]
    print('imgdir',imgdir)
    #print(imgdir.split('\\')[-1])
    saveimgdir = os.path.join(saveimgpath,imgdir.split('\\')[-1])
    print(saveimgdir)
    shutil.copy(imgdir,saveimgdir)
file.close()

核心代码解析

1. 环境与路径配置

首先导入必要的库,并设置文件路径(需根据自身数据集修改):

import os
import xml.etree.ElementTree as ET
import glob
import shutil

# 图片存放路径
ImgPath = 'datasets/images'
# XML标注文件存放路径
XmlPath = 'datasets/Annotations'
# 转换后数据集保存路径
txtSavePath = 'datasets/labels'

# 输出目录自动创建(确保路径存在)
savetxtpath = txtSavePath + '/labels'  # TXT标注文件保存路径
saveimgpath = txtSavePath + '/images'  # 图片复制目标路径
if not os.path.exists(savetxtpath):
    os.makedirs(savetxtpath)
if not os.path.exists(saveimgpath):
    os.makedirs(saveimgpath)

2. 类别设置

需根据自己的数据集类别修改classes列表,类别顺序决定了 YOLO 标注中的类别 ID:

# 标注类别(需替换为自己的目标类别)
classes = ['自定义']  # 例如:['person', 'car', 'dog']

3. 坐标转换函数(核心)

YOLO 格式要求边界框坐标以图片宽高为基准归一化,转换公式如下:

  • 中心 x = (xmin + xmax) / 2 / 图片宽度

  • 中心 y = (ymin + ymax) / 2 / 图片高度

  • 宽 = (xmax - xmin) / 图片宽度

  • 高 = (ymax - ymin) / 图片高度

def convert(size, box):
    dw = 1. / size[0]  # 1/图片宽度(归一化系数)
    dh = 1. / size[1]  # 1/图片高度(归一化系数)
    x = (box[0] + box[2]) / 2.0  # 中心x坐标
    y = (box[1] + box[3]) / 2.0  # 中心y坐标
    w = min(size[0], box[2] - box[0])  # 宽(避免超出图片范围)
    h = min(size[1], box[3] - box[1])  # 高(避免超出图片范围)
    x = x * dw  # 归一化
    w = w * dw
    y = y * dh
    h = h * dh
    return (x, y, w, h)

使用注意事项

  1. 路径修改:根据自己的数据集位置,修改ImgPathXmlPathtxtSavePath

  2. 类别修改:必须将classes列表替换为自己的目标类别(如['cat', 'dog']

  3. 图片格式:代码默认图片为.jpg 格式,若为.png 等其他格式,需修改replace('.xml', '.jpg')部分

  4. 异常处理:代码会自动跳过无对应图片的 XML 文件,以及不在类别列表中的目标

目标检测实战:仪表盘指针识别

保姆级 YOLOv8/9/10 教程,附完整数据集与源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

CV小涵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值