0%

xml格式转json

很多目标追踪给的数据集给的label都是xml格式的文件,labelme可视化工具需要的格式是json格式,所以需要对xml格式进行转换成json,师兄给了以前的代码,但是跑不通,下面便来具体解析一下这个代码并进行修改。

首先来看一下xml格式的label文件

fgg6f0.png

那么下面附一下读取xml文件的函数。通过ET。parse可以将xml格式文件解析为元素树(一个个节点接下去),然后通过tree.getroot()得到元素树的根节点,根节点中的元素就是最外层的几个参数,即source、research、size和objects,其实这些参数里面有存储了下一层节点参数,本质都是可迭代对象。可以来测试一下,通过list来转换一下可迭代对象:

1
2
3
for child_of_root in root:
print(child_of_root.tag)
print(list(child_of_root))

输出如下,可以看出根节点包含的的确是那四个,而那四个又是可迭代对象,而其中包含的参数,即是下一层的节点参数。其中child_of_root.tag可以获取当前的元素名称,其实tag类似于字典里的key,而text类似于字典里的value。

1
2
3
4
5
6
7
8
source
[<Element 'filename' at 0x000001CB382693B0>, <Element 'origin' at 0x000001CB392C7BD0>]
research
[<Element 'version' at 0x000001CB392C78B0>, <Element 'provider' at 0x000001CB392C71D0>, <Element 'author' at 0x000001CB392C7770>, <Element 'pluginname' at 0x000001CB392C7C20>, <Element 'pluginclass' at 0x000001CB392C7360>, <Element 'time' at 0x000001CB392C7DB0>]
size
[<Element 'width' at 0x000001CB392C7680>, <Element 'height' at 0x000001CB392C77C0>, <Element 'depth' at 0x000001CB392C7EF0>]
objects
[<Element 'object' at 0x000001CB392C7590>, <Element 'object' at 0x000001CB392C7720>]

而下述代码中,根节点中,if只判断了source和objects,没有使用size,那么后面转换成json的话,必然需要读取图片得到长宽和通道数,其实不太合适,可以自行加上几个if,通过不断的for产生可迭代对象,进行遍历元素树。我们仔细看一下objects元素,遍历后,只处理其中的object。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import os
import scipy.misc as misc
from xml.dom.minidom import Document
import numpy as np
import copy, cv2
# import imageio
import json
import glob
import xml.etree.cElementTree as ET
import re
from osgeo import gdal, gdalconst
def read_xml_gtbox_and_label(xml_path):
"""
:param xml_path: the path of voc xml
:return: a list contains gtboxes and labels, shape is [num_of_gtboxes, 9],
and has [x1, y1, x2, y2, x3, y3, x4, y4, label] in a per row
"""

tree = ET.parse(xml_path)
root = tree.getroot()
box_list = []
difficult_list = []
isObjNone=True
tmp_score_list=[]
for child_of_root in root:
# if child_of_root.tag == 'filename':
# assert child_of_root.text == xml_path.split('/')[-1].split('.')[0] \
# + FLAGS.img_format, 'xml_name and img_name cannot match'
if child_of_root.tag == 'source':
for child_item in child_of_root:
if child_item.tag == 'filename':
img_name = child_item.text

if child_of_root.tag == 'objects':
label = None
for child_item in child_of_root:
if child_item.tag == 'object':
for child_pic in child_item:
if child_pic.tag == 'possibleresult':
for child_name in child_pic:
if child_name.tag == 'name':
label = child_name.text
if child_name.tag == 'probability':
tmp_score=float(child_name.text)
tmp_score_list.append(tmp_score)
# difficult_tmp = int(not(child_name.text))
tmp_list=re.findall('[^.\d]',child_name.text)
if len(tmp_list)>0:
print(xml_path)
print(tmp_list)
if child_name.text.strip()=='':
print(xml_path)
print('empty')
difficult_tmp = 0#int(not(child_name.text))
difficult_list.append(difficult_tmp)
if child_pic.tag == 'points':
tmp_box = []
for node in child_pic:
tmp_list=re.findall('[^-,.\d]',node.text)
if len(tmp_list)>0:
print(xml_path)
print(tmp_list)
if node.text.strip()=='':
print(xml_path)
print('empty')
for node in child_pic[:4]:
tmp_box=tmp_box+node.text.split(',')
isObjNone=False
tmp_box.append(label)
box_list.append(tmp_box)
if isObjNone:
print(xml_path)
print('obj none!')

return img_name, box_list, difficult_list, tmp_score_list

我们将objects一块拿出来,见下述的#1的代码,for遍历,如果遍历的元素是object的,那么继续遍历处理,然后我们希望处理的是object中的possibleresult:这里面是包含所属类别和置信度(分类)和points:里面包含了追踪目标的四个角点(其实都是有五队,形成来一个收尾连接的闭合空间,我们只需要四对即四队角点即可)。

先看处理possibleresult的,里面会保存元素name中的参数,即label的类别;然后判断力probability,这个是置信度,child_name.text得到分数后,将分数加入列表中,不过后面对child_name.text进行了一个正则表达式匹配(和深度学习中的正则化区别,深度学习中的正则化是为了加入一些约束防止过拟合),re.findall(‘[^.\d]‘,child_name.text)是前面第一个元素进行对后面的字符串进行匹配,[]代表系列组合,比如[0-99],就会匹配0、1、2。。。99的元素。而^可以理解为取反,[abc]是匹配a、b和c的字符,即[^abc ]即匹配除了abc的所有字符,这里的\d代表所有数字,^\d代表匹配除了所有数字以外的字符,“.”的意思是匹配除了换行的所有字符串,但是这里前面加了^则不是这个含义了,这里的含义就是简单的浮点数的点点,^.是不匹配浮点数的点点的意思。这个正则表达式是用于检测置信度是否是浮点数的,如果有其他字符,则会检测出来。

正则表达式参考链接如下:

Python 正则表达式

re-正则表达式操作

下一个if child_name.text.strip()==’’是用于判断字符串(置信度也是以字符串形式存储的)是否为空,strip函数是用来移除字符串头尾指定的字符(默认为空格)或字符序列,需要注意的是,只移除开头和结尾的指定字符。这里就是默认移除空格,然后看其是否为空。

probability判断完后,开始检测points,就是坐标,依旧是正则表达式,不匹配负号、逗号、点和数字,如果还有其他字符,则认为有问题。第二个for用来遍历取出所有的坐标,通过遍历前四组坐标(第五组和第一组重合了),然后用split进行分割,分割的界限以”,”进行,然后把结果加入list列表,list列表的加法是拼接的意思,乘法是重复拼接的意思,区别于np的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#1        
if child_of_root.tag == 'objects':
label = None
for child_item in child_of_root:
if child_item.tag == 'object':
for child_pic in child_item:
if child_pic.tag == 'possibleresult':
for child_name in child_pic:
if child_name.tag == 'name':
label = child_name.text
if child_name.tag == 'probability':
tmp_score=float(child_name.text)
tmp_score_list.append(tmp_score)
# difficult_tmp = int(not(child_name.text))
tmp_list=re.findall('[^.\d]',child_name.text)
if len(tmp_list)>0:
print(xml_path)
print(tmp_list)
if child_name.text.strip()=='':
print(xml_path)
print('empty')
difficult_tmp = 0#int(not(child_name.text))
difficult_list.append(difficult_tmp)
if child_pic.tag == 'points':
tmp_box = []
for node in child_pic:
tmp_list=re.findall('[^-,.\d]',node.text)
if len(tmp_list)>0:
print(xml_path)
print(tmp_list)
if node.text.strip()=='':
print(xml_path)
print('empty')
for node in child_pic[:4]:
tmp_box=tmp_box+node.text.split(',')
isObjNone=False
tmp_box.append(label)
box_list.append(tmp_box)

下面看一下把xml转换成json格式的函数#2,这个函数中调用了上述描述的函数。代码如下:

这里需要讲一下这个python中的路径问题,python中用的是\作为路径文件夹相隔符,然而window中常常用\(ubuntu是/),而\这个字符是转义字符,用来作为程序中的文件路径肯定是不合适的,所以一般都是用/,所以在路径使用之前,先进行检测替换一下,用/替换\,即用到了replace函数,似乎应该用replace(“\\”,”/“),但是\\是转义字符的意思,所以需要使用\\\,这样才表示window中路径斜杠。

save_path赋值时候,前面加了r,对于这些路径加上r有啥含义呢?如果我们的路径直接从windows拷贝过来的,格式都是类似于C:\Users\这样的,直接作为字符串赋值,\会作为转义字符直接被忽略掉,要么变成\\\\这样,人为修改太麻烦,加上一个r后,强制把后面的这字符串强制存为字符串,即\直接就是真的字符\了,而不是转移字符。

下述函数中用到了os.path.split,其作用是,如果你传入一个文件路径,它会返回元组,包括两个参数,第一个是路径(精确到文件夹),第二个是文件名,比如os.path.split(“C:\\Users\\21311\\machine_learning\\tensorflow\\xingtubei\\1.tif”),返回(‘C:\\Users\\21311\\machine_learning\\tensorflow\\xingtubei’, ‘1.tif’)。然后下面调用了上面分析的解析xml的函数,由于解析xml的时候,没有解析size,所以不知道长宽和深度,所以这里用gdal库读入了图片,获取了width和length,这里显然是更浪费内存的,其实直接解析xml就可以得到了。所谓的json格式可以就看做为一个字典,字典里面可以嵌套字典,key值需要按照规定的来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#2
def convert_pascal_to_tfrecord(image_path, xml_path):
#葛改save_path=r'/emwuser/gzl/gf2021/data/plane_2020/train_split_test/train/json_c'
save_path=r'/emwuser/gzl/gf2021/data/FAIR1M/train/part1/json_c'#json的保存地址#r其实是为了强制认为后面的都是字符串,不考虑转义字符,无r则会考虑转义字符,这里加不加其实无所谓,但是C:\data这种的就得加上r,或者直接C:\\data
for count, xml in enumerate(glob.glob(xml_path + '/*.xml')):#进行索引,*代表任意匹配,得到可迭代对象
# to avoid path error in different development platform
xml = xml.replace('\\', '/')#用/替换\\,即后面的替换前面的
(_, xml_name) = os.path.split(xml)#分割。返回路径和文件名。若本身就是一个文件夹路径,则返回路径和空

# img_name = xml.split('/')[-1].split('.')[0] + FLAGS.img_format
# img_path = image_path + '/' + img_name

# if not os.path.exists(img_path):
# print('{} is not exist!'.format(img_path))
# continue

img_name, box , difficult_list, tmp_score_list = read_xml_gtbox_and_label(xml)
img_name=xml_name.replace('.xml', '.tif')
img_path = image_path + '/' + img_name

if not os.path.exists(img_path):
print('{} is not exist!'.format(img_path))
continue

json_str={}
shapes=[]

json_str['version']="4.5.6"
json_str['flags']={}

# json_str['image_name']=img_name
json_str['imagePath']=os.path.join('..','images_c',img_name)
# img_data = cv2.imread(json_str['imagePath'])
json_str['imageData']= None
dataset = gdal.Open(os.path.join(image_path, img_name))
json_str['imageHeight']=dataset.RasterYSize#img_data.shape[0]
json_str['imageWidth']=dataset.RasterXSize#img_data.shape[1]
del dataset
# json_str['image_depth']=3
for i in range(len(box)):
shape={}
shape['label']=box[i][-1]
shape['shape_type']="polygon"
shape['points']=[[box[i][0],box[i][1]],[box[i][2],box[i][3]],[box[i][4],box[i][5]],[box[i][6],box[i][7]]]
shape['points']=np.array(shape['points'],dtype=np.float)
shape['points']=shape['points'].tolist()

# labelme 特有
shape['group_id']= None
shape['flags']={}

# 图像标识平台 特有
shape['score']=tmp_score_list[i]
shape['difficult']=0#difficult_list[i]#20200711
shape['truncated']=0#20200711
shape['status']="add"

shapes.append(shape)
json_str['shapes']=shapes


with open(os.path.join(save_path, xml_name.replace('xml', 'json')), 'w') as fw:
json.dump(json_str, fw)

# view_bar('Conversion progress', count + 1,
# len(glob.glob(xml_path + '/*.xml')))

print('\nConversion is complete!')

下面来看一下主函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
if __name__ == "__main__":
'''
raw_label_dir=r"/emwuser/gzl/gf2021/data/plane_2020/train_split_test/train/label_xml_c"
raw_images_dir=r'/emwuser/gzl/gf2021/data/plane_2020/train_split_test/train/images_c'
'''
raw_label_dir=r"/emwuser/gzl/gf2021/data/FAIR1M/train/part1/labelXmls_c"
raw_images_dir=r'/emwuser/gzl/gf2021/data/FAIR1M/train/part1/images_c'

# raw_label_dir=r"/emwusr/jhc/data/plane/data/train/label_xml"
# raw_images_dir=r'/emwusr/jhc/data/plane/data/train/images'

#save_path=r'/emwusr/nuoyizhou/pickplanedataset/competition/data/train/json'
convert_pascal_to_tfrecord(raw_images_dir,raw_label_dir)

其实可以发现其报错了:

1
2
3
4
5
6
Traceback (most recent call last):
File "/emwuser/gzl/gf2021/code/R3Det_Tensorflow_gf2021/tools_gf_2021/xml2json_zry.py", line 161, in <module>
convert_pascal_to_tfrecord(raw_images_dir,raw_label_dir)
File "/emwuser/gzl/gf2021/code/R3Det_Tensorflow_gf2021/tools_gf_2021/xml2json_zry.py", line 130, in convert_pascal_to_tfrecord
shape['score']=tmp_score_list[i]
IndexError: list index out of range

即tmp_score_list超界了,可以看一下该文最上面的xml文件,可以发现里面是没有置信度的参数,而解析xml文件的时候,如果有则写入list中,如果没有,则list就不写入,这显然是不合理的,没有的话我们应该进行填充默认值,作为给的训练集数据,那么置信度可以默认为1。我们可以在convert_pascal_to_tfrecord中进行判断一下,加入如下语句

1
2
3
4
if not tmp_score_list:
print("tmp_score_list是空的")
tmp_score_list=[0]*len(box)
print(tmp_score_list)

其中两个print可以屏蔽掉,因为循环的时候打印太多也没有意义。其实更好的应该是在read_xml_gtbox_and_label函数中判断probability那加入else,给予默认值。

填充好tmp_score_list后,就可以得到json格式的文件,我们可以用labelme进行打开,但是打开还是报错的:

1
a byte-like object is required,not “Nonetype”

其实我们可以随便打开一张tif文件,然后进行标注,保存json格式的label,把json单独拿出,labelme打开json后,图像会自动显示,这个图像是json中路径寻找到的?你即使给它一个错误路径图片也可以正常显示,所以是json中存储了图片信息,在key为ImageData的value,然而我们生成json的时候,是用null填充的,所以是打不开的,不过这只是影响我们可视化而已,不影响我们图像处理,如果需要可视化的话,我们可以打开图片,随便标注一下,存下json,把这个json中的ImageData的value复制到我们函数产生的json即可。