pascal VOC数据的标注格式为边框的(左上x,左上y,右下x,右下y)
的坐标值,并且没有归一化,而yolo数据标注格式为(分类的id, 中心点坐标x,中心点坐标y,框的宽,框的高)
,yolo的数据标注格式是进行了归一化的。
pascal VOC数据标注的存储文件为XML,接下来简单地介绍一下XML文件。
XML文件的一个节点的基本结构为
VOC数据标注文件
以下就是一个标准的VOC标注文件。
<annotation>
<folder>VOC2007</folder>
<filename>000002.jpg</filename>
<source>
<database>The VOC2007 Database</database>
<annotation>PASCAL VOC2007</annotation>
<image>flickr</image>
<flickrid>329145082</flickrid>
</source>
<owner>
<flickrid>hiromori2</flickrid>
<name>Hiroyuki Mori</name>
</owner>
<size>
<width>335</width>
<height>500</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>train</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>139</xmin>
<ymin>200</ymin>
<xmax>207</xmax>
<ymax>301</ymax>
</bndbox>
</object>
</annotation>
使用python的xml.etree.ElementTree
模块可以很方便地构建和解析xml文件,以下代码全部使用该库进行构建。
# 指定根节点tag是 'data'
root = ET.Element('data')
# 指定data的子节点是country,并且attrib是'name' = Liechtenstein'
country = ET.SubElement(root,'country', {'name':'Liechtenstein'})
# 指定country的子节点是rank, rank.text = 1
rank = ET.SubElement(country,'rank')
rank.text = '1'
year = ET.SubElement(country,'year')
year.text = '2008'
# 构建tree
tree=ET.ElementTree(root)
# 写入tree
tree.write("demo.xml")
我们写入了一个基本的文件,文件名为demo.xml
,打开该文件我们可以查看我们写入的xml结构
<data><country name="Liechtenstein"><rank>1</rank><year>2008</year></country></data>
可以看出,这样生成的xml文件并不美观,和VOC数据标注格式相比,可读性非常差。因此我们需要美化xml文件。
使用以下脚本进行xml文件美化
def prettyXml(tree,indent='\t',newline='\n',level=0):
"""
Pretty Xml File before writing
:param tree: 创建好的父节点,未初始化的tree
:param indent: 缩进“\t”
:param newline: 换行“\n”
:param level: 用于递归操作的变量,实现不同级别节点增加不同数量的缩进“\t”
:return: 无
"""
treeList=list(tree)
for subElement in treeList:
if treeList.index(subElement) < (len(treeList) - 1):
subElement.tail=newline+indent*(level+1)
else:
subElement.tail=newline+indent*level
prettyXml(subElement,indent,newline,level+1)
# 指定根节点tag是 'data'
root = ET.Element('data')
# 指定data的子节点是country,并且attrib是'name' = Liechtenstein'
country = ET.SubElement(root,'country', {'name':'Liechtenstein'})
# 指定country的子节点是rank, rank.text = 1
rank = ET.SubElement(country,'rank')
rank.text = '1'
year = ET.SubElement(country,'year')
year.text = '2008'
# 构建tree
prettyXml(root)
tree=ET.ElementTree(root)
# 写入tree
tree.write("demo2.xml")
美化后的demo2.xml
文件如下
<data><country name="Liechtenstein"><rank>1</rank>
<year>2008</year>
</country>
</data>
'''
Description:
coding: utf-8
language: python3.8, tensorflow2.6
Author: JiaHao Shum
Date: 2022-03-20 16:09:52
'''
import numpy as np
from PIL import Image, ImageDraw
import os
import xml.etree.ElementTree as ET
from tqdm import tqdm
# 图片中包含的分类,需要和yolo中的class_id对应起来
classes = ['face', 'face with mask']
# yolo格式标注文件存放路径
yolo_labels_dir = './Labels'
# 图片存放路径
images_dir = './JPEGImages'
# voc格式标注文件存放路径
pascal_labels_dir = './XMLs'
def prettyXml(tree,indent='\t',newline='\n',level=0):
"""
Pretty Xml File before writing
:param tree: 创建好的父节点,未初始化的tree
:param indent: 缩进“\t”
:param newline: 换行“\n”
:param level: 用于递归操作的变量,实现不同级别节点增加不同数量的缩进“\t”
:return: 无
"""
treeList=list(tree)
for subElement in treeList:
if treeList.index(subElement) < (len(treeList) - 1):
subElement.tail=newline+indent*(level+1)
else:
subElement.tail=newline+indent*level
prettyXml(subElement,indent,newline,level+1)
def convert_one_label(yolo_filename, images_dir, xml_save_path):
"""
Func:
将一张图片的yolo格式的标签转换成xml格式
Args:
yolo_filename: yolo格式标签的完整路径
images_dir: images的存放地址
xml_save_path: xml格式标签的存放地址
"""
filename = os.path.split(yolo_filename)[-1]
image_filename = os.path.join(images_dir, str(filename[:-4]) + '.jpg')
image = Image.open(image_filename)
iw, ih = image.size
"""获得一张图片的yolo box"""
if not os.path.exists(xml_save_path):
os.makedirs(xml_save_path)
bndbox = open(yolo_filename).readlines()
bndbox = [x.strip() for x in bndbox]
boxes_org = np.array([x.split() for x in bndbox], dtype='float32')
boxes = np.zeros_like(boxes_org, dtype='int')
boxes[:, -1] = boxes_org[:, 0].astype('int')
boxes[:, 0] = (boxes_org[:, 1] - boxes_org[:, 3] / 2) * iw
boxes[:, 1] = (boxes_org[:, 2] - boxes_org[:, 4] / 2) * ih
boxes[:, 2] = (boxes_org[:, 1] + boxes_org[:, 3] / 2) * iw
boxes[:, 3] = (boxes_org[:, 2] + boxes_org[:, 4] / 2) * ih
"""写入一张图片的xml"""
xml_filename = os.path.join(xml_save_path, str(filename[:-4])+'.xml')
root = ET.Element('annotation')
root.text = '\n\t'
for box in boxes:
obj = ET.SubElement(root, 'object')
name = ET.SubElement(obj, 'name')
name.text = classes[box[-1]]
bndbox = ET.SubElement(obj, 'bndbox')
xmin = ET.SubElement(bndbox, 'xmin')
ymin = ET.SubElement(bndbox, 'ymin')
xmax = ET.SubElement(bndbox, 'xmax')
ymax = ET.SubElement(bndbox, 'ymax')
xmin.text, = str(box[0]),
ymin.text, = str(box[1]),
xmax.text, = str(box[2]),
ymax.text, = str(box[3]),
prettyXml(root)
tree = ET.ElementTree(root)
tree.write(xml_filename)
if __name__ == '__main__':
filenames = os.listdir(yolo_labels_dir)
filenames = [str(x) for x in filenames if x.endswith('.txt')]
for filename in tqdm(filenames):
yolo_filename = os.path.join(yolo_labels_dir, filename)
convert_one_label(yolo_filename, images_dir, pascal_labels_dir)
print('-'*50)
print('Convertion Finish!')
print('-'*50)
http://www.4k8k.xyz/article/weixin_42997255/100090114
https://docs.python.org/zh-cn/3.9/library/xml.etree.elementtree.html?highlight=subelement#xml.etree.ElementTree.SubElement
https://blog.csdn.net/weixin_42782150/article/details/106219001
https://blog.csdn.net/jq_98/article/details/123316087
https://blog.csdn.net/qq_39507748/article/details/110819929