Reputation: 2563
I have annotations in xml files such as this one, which follows the PASCAL VOC convention:
<annotation>
<folder>training</folder>
<filename>chanel1.jpg</filename>
<source>
<database>synthetic initialization</database>
<annotation>PASCAL VOC2007</annotation>
<image>synthetic</image>
<flickrid>none</flickrid>
</source>
<owner>
<flickrid>none</flickrid>
<name>none</name>
</owner>
<size>
<width>640</width>
<height>427</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>chanel</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>344</xmin>
<ymin>10</ymin>
<xmax>422</xmax>
<ymax>83</ymax>
</bndbox>
</object>
<object>
<name>chanel</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>355</xmin>
<ymin>165</ymin>
<xmax>443</xmax>
<ymax>206</ymax>
</bndbox>
</object>
</annotation>
What is the cleanest way of retrieving for example the fields filename
and bndbox
in Python?
I was trying to ElementTree, which seems to be the official Python solution, but I can't make it work.
My code so far:
from xml.etree import ElementTree as ET
tree = ET.parse("data/all/annotations/" + file)
fn = tree.find('filename').text
boxes = tree.findall('bndbox')
this produces
fn == 'chanel1.jpg'
boxes == []
So it succesfully extracts the filename
field, but not the bndbox
'es.
Upvotes: 19
Views: 18308
Reputation: 21
And another one option is to use pascal-voc
utility
from pascal import annotation_from_xml
ann = annotation_from_xml("path_to_xml")
Upvotes: 0
Reputation: 1719
My attempt at it, slightly more readable than the accepted answer, offering the option to convert to 0-based pixel coordinates, and pairing the name of the object rather than the name of the file with each box's coordinates.
Output example:
{'excavator': {'xmin': 0, 'ymin': 0, 'xmax': 1265, 'ymax': 587},
'dump_truck': {'xmin': 259, 'ymin': 159, 'xmax': 713, 'ymax': 405}}
import xml.etree.ElementTree as ET
def read_Pascal_VOC(xml_file,do_0_based):
# Pascal VOC is 1-based, but more recent formats like MS COCO are 0-based
# see, e.g., https://github.com/Ricardozzf/maskrcnn-benchmark/commit/da8f99927eb945d3e66985d5e070fb55db472de6
if do_0_based:
to_subtract = 1
else:
to_subtract = 0
tree = ET.parse(xml_file)
root = tree.getroot()
boxes = dict()
for box in root.iter('object'):
name = box.find('name').text
bb = box.find('bndbox')
# dict to remove any ambiguity ordering-wise
coords = dict(xmin = bb.find('xmin').text,
ymin = bb.find('ymin').text,
xmax = bb.find('xmax').text,
ymax = bb.find('ymax').text)
coords = {k:int(v)-to_subtract for k,v in coords.items()}
if name in boxes:
boxes[name] = boxes[name] + [coords]
else:
boxes[name] = [coords]
return boxes
Upvotes: 1
Reputation: 444
Another option is to use the standard xmldict
library to load the VOC XML in a python dict.
import xmltodict
with open('/path/to/voc.xml') as file:
file_data = file.read()
dict_data = xmltodict.parse(file_data)
print(dict_data)
Upvotes: 1
Reputation: 515
That's a quite easy solution for your problem:
This will return your box coordinates in a nested list [xmin, ymin, xmax, ymax] and the filename Once I struggled with bndbox tags which where mixed up (ymin, xmin,...) or any other strange combinations, so this code read the tags not only the position.
Finally I updated the code. Thanks to craq and Pritesh Gohil, you were absolutely right.
Hope it helps...
import xml.etree.ElementTree as ET
def read_content(xml_file: str):
tree = ET.parse(xml_file)
root = tree.getroot()
list_with_all_boxes = []
for boxes in root.iter('object'):
filename = root.find('filename').text
ymin, xmin, ymax, xmax = None, None, None, None
ymin = int(boxes.find("bndbox/ymin").text)
xmin = int(boxes.find("bndbox/xmin").text)
ymax = int(boxes.find("bndbox/ymax").text)
xmax = int(boxes.find("bndbox/xmax").text)
list_with_single_boxes = [xmin, ymin, xmax, ymax]
list_with_all_boxes.append(list_with_single_boxes)
return filename, list_with_all_boxes
name, boxes = read_content("file.xml")
Upvotes: 40