需要一个VOC格式的摩托车数据集,于是只好将从网上搜集到的一些PASCAL Annotation格式标注好的摩托车数据集转换成VOC格式的
代码如下:
# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
import os
import re
from shutil import copyfile
root_path="C:\\Users\\dell\\Downloads\\摩托车_my\\"
srcTxt_path="C:\\Users\\dell\\Downloads\\摩托车_my\\VOC2006\\test\\Annotations\\"
srcPic_path="C:\\Users\\dell\\Downloads\\摩托车_my\\VOC2006\\test\\PNGImages\\"
output_root_path="C:\\Users\\dell\\Downloads\\摩托车_my\\PASCAL2VOC_Output\\"
def prettyXml(element, indent, newline, level = 0): # elemnt为传进来的Elment类,参数indent用于缩进,newline用于换行
if element: # 判断element是否有子元素
if element.text == None or element.text.isspace(): # 如果element的text没有内容
element.text = newline + indent * (level + 1)
else:
element.text = newline + indent * (level + 1) + element.text.strip() + newline + indent * (level + 1)
#else: # 此处两行如果把注释去掉,Element的text也会另起一行
#element.text = newline + indent * (level + 1) + element.text.strip() + newline + indent * level
temp = list(element) # 将elemnt转成list
for subelement in temp:
if temp.index(subelement) < (len(temp) - 1): # 如果不是list的最后一个元素,说明下一个行是同级别元素的起始,缩进应一致
subelement.tail = newline + indent * (level + 1)
else: # 如果是list的最后一个元素, 说明下一行是母元素的结束,缩进应该少一个
subelement.tail = newline + indent * level
prettyXml(subelement, indent, newline, level = level + 1) # 对子元素进行递归操作
def PASCAL2VOC():
if not os.path.exists(output_root_path + srcTxt_path.replace(root_path, '')):
print("Create a xml dir: " + output_root_path + srcTxt_path.replace(root_path, ''))
os.makedirs(output_root_path + srcTxt_path.replace(root_path, ''))
if not os.path.exists(output_root_path + srcPic_path.replace(root_path, '')):
print("Create a pic dir: " + output_root_path + srcPic_path.replace(root_path, ''))
os.makedirs(output_root_path + srcPic_path.replace(root_path, ''))
for txt_filename in os.listdir(srcTxt_path):
if txt_filename.split(".")[-1]=="txt":
# 创建一个root element<annotation>
annotation = ET.Element("annotation")
# 直接通过SubElement类为<annotation>添加多个子元素<folder><filename><path><source><size><segmented><object>
folder = ET.SubElement(annotation, "folder")
folder.text = 'Tarin_picture'
filename = ET.SubElement(annotation, "filename")
path = ET.SubElement(annotation, "path")
source = ET.SubElement(annotation, "source")
database = ET.SubElement(source, "database")
size = ET.SubElement(annotation, "size")
width = ET.SubElement(size, "width")
height = ET.SubElement(size, "height")
depth = ET.SubElement(size, "depth")
segmented = ET.SubElement(annotation, "segmented")
segmented.text = "0"
with open(srcTxt_path + txt_filename, "r") as file:
for line in file.readlines():
picNameRegex = re.compile(r'Image filename : "(.*)"')
mo1 = picNameRegex.search(line)
if (mo1 != None):
database.text = mo1.group(1).split('/')[0]
filename.text = mo1.group(1).split('/')[-1]
path.text = srcPic_path + filename.text
filename.text = database.text + '_' + filename.text
picSizeRegex = re.compile(r'Image size (.*) : (.*) x (.*) x (.*)')
mo2 = picSizeRegex.search(line)
if (mo2 != None):
width.text = mo2.group(2)
height.text = mo2.group(3)
depth.text = mo2.group(4)
if database.text == 'Caltech' or 'VOC2005_1':
motorbikeRegex = re.compile(
r'Bounding box for object (\d)+ "(PASmotorbikeSide)"(.*) : (.*) - (.*)')
if database.text == 'VOC2005_2':
motorbikeRegex = re.compile(
r'Bounding box for object (\d)+ "(PASmotorbike|PASmotorbikeSide)"(.*) : (.*) - (.*)')
if database.text == 'VOC2006':
motorbikeRegex = re.compile(
r'Bounding box for object (\d)+ "(PASmotorbike|PASmotorbikeLeft|PASmotorbikeRight|PASmotorbikeDifficult|PASmotorbikeTrunc|PASmotorbikeTruncDifficult|PASmotorbikeRear|PASmotorbikeFrontal)"(.*) : (.*) - (.*)')
mo3 = motorbikeRegex.findall(line)
if (mo3 != None):
for obj_in_pic in mo3:
object = ET.SubElement(annotation, "object")
name = ET.SubElement(object, "name")
name.text = obj_in_pic[1]
pose = ET.SubElement(object, "pose")
pose.text = "Unspecified"
struncated = ET.SubElement(object, "struncated")
struncated.text = "0"
difficult = ET.SubElement(object, "difficult")
if ('Difficult' in obj_in_pic[1]):
difficult.text = "1"
else:
difficult.text = "0"
bndbox = ET.SubElement(object, "bndbox")
xmin = ET.SubElement(bndbox, "xmin")
xmin.text = obj_in_pic[3].split('(')[-1].split(',')[0]
ymin = ET.SubElement(bndbox, "ymin")
ymin.text = obj_in_pic[3].split(' ')[-1].split(')')[0]
xmax = ET.SubElement(bndbox, "xmax")
xmax.text = obj_in_pic[4].split('(')[-1].split(',')[0]
ymax = ET.SubElement(bndbox, "ymax")
ymax.text = obj_in_pic[4].split(' ')[-1].split(')')[0]
prettyXml(annotation, '\t', '\n')
annotationTree = ET.ElementTree(element=annotation)
annotationTree.write(output_root_path+ srcTxt_path.replace(root_path, '') + filename.text.split('.')[0]+ ".xml",encoding="utf-8")
copyfile(path.text, output_root_path + srcPic_path.replace(root_path, '') + filename.text)
if __name__ == "__main__":
PASCAL2VOC()
转换完成后发现标注框的坐标值有为负的情况,需要再写个脚本对新生成的数据集做一下筛选,挑出那些出现了负值的标注文件和对应的图像。
代码如下:
# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
import os
xml_path='E:\\***\\摩托车_my\\PASCAL2VOC_Output\\my_motorbike_data_set\\Annotations\\'
pic_path='E:\\***\\摩托车_my\\PASCAL2VOC_Output\\my_motorbike_data_set\\PNGImages\\'
dst_path=r'E:/***/摩托车_my/output_after_check_PASCAL2VOC_Output'
def shear_dile(src, dst):
if os.path.isdir(src):
if not os.listdir(src):
os.rmdir(src)
print('移除空目录: ' + src)
else:
for d in os.listdir(src):
shear_dile(os.path.join(src, d), dst)
if os.path.isfile(src):
print("文件剪切:", src)
fn = os.path.basename(src)
if not os.path.exists(dst + './' + fn):
#os.rename(src, dst + './' + fn)
os.rename(src, dst + fn)
def process_xml(src_path):
# 读取源xml文件
num=0
for xml_filename in os.listdir(src_path):
et = ET.parse(xml_path+xml_filename)
# 获取第一个标签为"filename"的“直接”subelement
filename = et.find("filename")
picname = filename.text
size = et.find("size")
width = size.find("width")
height = size.find("height")
depth = size.find("depth")
with open('数据集图像尺寸统计.txt', 'a') as file: # 设置文件对象
str = picname +" "+ width.text + " " +height.text +" "+ depth.text + "\n"
file.write(str)
for e in et.findall("object"):
name = e.find("name")
bndbox = e.find("bndbox")
xmin = bndbox.find("xmin")
ymin = bndbox.find("ymin")
xmax = bndbox.find("xmax")
ymax = bndbox.find("ymax")
if any([(int(xmin.text)<0),(int(ymin.text)<0), (int(xmax.text)<0),(int(ymax.text)<0)]):
num=num+1
with open('数据标注框坐标为负图像统计.txt', 'a') as f: # 设置文件对象
str1 = picname + " " + "宽:" + width.text + " 高:" + height.text + " 通道数:" + depth.text + "\n"
str2 = "name:" + name.text + " bndbox:" + xmin.text + " " + ymin.text + " " + xmax.text + " " + ymax.text + "\n"
f.write(str1+str2)
if num==0:
if not os.path.exists(dst_path +'/Annotations/'):
print("Create a xml dir: " + dst_path +'/Annotations/')
os.makedirs(dst_path +'/Annotations/')
if not os.path.exists(dst_path+'/JPEGImages/'):
print("Create a pic dir: " + dst_path+'/JPEGImages/')
os.makedirs(dst_path+'/JPEGImages/')
shear_dile(pic_path+picname ,dst_path+'/JPEGImages/')
shear_dile(xml_path+picname.split('.')[0]+'.xml',dst_path +'/Annotations/')
num=0
process_xml(xml_path)
运行得到
数据集图像尺寸统计.txt内容如下:
对图像尺寸分布进行统计,绘制散点图
代码如下:
# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import warnings
import numpy as np
warnings.filterwarnings("ignore")
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False #matplotlib画图中中文显示会有问题,需要这两行设置默认字体
file_path="数据集图像尺寸统计.txt"
def scatter_diagram(file_path):
with open('数据集图像尺寸统计.txt', 'r') as file:
x=[]
y=[]
i=0
for line in file.readlines():
width=line.split(" ")[1]
height=line.split(" ")[2]
x.append(int(width))
y.append(int(height))
i=i+1
plt.title('数据集图像尺寸统计')
plt.xlabel('width')
plt.ylabel('height')
colors = '#00FF00'
area = np.pi * 2 ** 2 # 点面积
plt.scatter(x, y, s=area, c=colors, alpha=0.4, label='width和height')
plt.legend(('real data', 'fitted line'))
plt.show()
#plt.savefig(r'E:\***\摩托车_my\数据集图像长宽分布图.png', dpi=300)
scatter_diagram(file_path)
【参考网址】:
【1】python xml格式美化 https://blog.csdn.net/udbipyga/article/details/77140494