下面用到的 VOC 和 COCO 是我自己制作的小型数据集,有五张图,分为 car、person、motorcycle 和 dog 四个类,没有语义分割等内容。
代码中可能有许多不成熟的地方或者错误的地方,还请各位教正!
VOC转换成COCO
在转换之前,先来了解json格式中必要的元素:
- 重要工具:xml、json
- 参照 COCO 的格式,我先把图片名称、目标类别列出来,初始化 annotations,categories 等。
import xml.etree.ElementTree as ET
import json
sets = ['z_00001', 'z_00002', 'z_00003', 'z_00004', 'z_00005']
classes = ['car', 'person', 'motorcycle', 'dog']
images = []
annotations = []
categories = []
si = 1
oid = 1
- 用 for 循环遍历每一张 image 的对应 xml 文件,用 xml 中的工具读入文件、获取有用信息。
- 读取<size>部分,用.find().text获取<width>等的文本信息,将其变为int型后存入images中,同时每一张image有特定的 id
- 遍历<object>、获取并储存 annotation,过程与上一步相似。每一个目标有对应的 image_id 和 category_id,并且有自己的 id
for file in os.listdir(ann_filepath):
# file_name = 'D:/Datasets/My_datas/VOC/Annotations/%s.xml' % set
in_file = open(ann_filepath + '\\' + file, encoding='utf-8')
tree = ET.parse(in_file)
root = tree.getroot()
file_name = root.find('filename').text
# 输入img大小,输出image[]
for img_size in root.iter('size'):
width = int(float(img_size.find('width').text))
height = int(float(img_size.find('height').text))
depth = int(float(img_size.find('depth').text))
# 收集一张img内所有obj
image = {'file_name': file_name, 'width': width, 'height': height, 'depth': depth, 'id': si}
images.append(image)
# 得到关于obj位置,从而得到obj关键信息,输出annotations[]
for obj in root.iter('object'):
# difficult = 0
# if obj.find('difficult') != None:
# difficult = obj.find('difficult').text
# 得到obj类别
cls = obj.find('name').text
if cls not in COCO_NAMES:
print('not in')
continue
cls_id = COCO_NAMES.index(cls)
# 找到box位置,得到左下、右上角xy坐标
xmlbox = obj.find('bndbox')
bbox_ann = [int(float(xmlbox.find('xmin').text)), int(float(xmlbox.find('ymin').text)),
int(float(xmlbox.find('xmax').text)), int(float(xmlbox.find('ymax').text))]
bbox = [(bbox_ann[2] + bbox_ann[0]) / 2, (bbox_ann[3] + bbox_ann[1]) / 2, bbox_ann[2] - bbox_ann[0],
bbox_ann[3] - bbox_ann[1]]
print(bbox)
area = bbox[2] * bbox[3]
print(area)
iscrowd=0
annotation = {'image_id': si, 'category_id': cls_id + 1, 'id': oid,'area':area,
'iscrowd':iscrowd,'bbox': bbox} # category_id要加一,因为cls_id=0是背景
# print(annotation)
annotations.append(annotation)
oid += 1
si += 1
print('共%s张图' % (si - 1))
for cls in COCO_NAMES:
cls_id = COCO_NAMES.index(cls)
categorie = {'id': cls_id + 1, 'name': cls}
categories.append(categorie)
Annotation = {'images': images, 'annotations': annotations, 'categories': categories}
with open(save_path, 'w', encoding='utf-8') as f:
f.write(json.dumps(Annotation, indent=4, ensure_ascii=False))
# print(Annotation)
- 类别模块,cls_id要加一,因为cls_id=0是背景
for cls in classes:
cls_id = classes.index(cls)
categorie = {'id': cls_id + 1, 'name': cls}
categories.append(categorie)
- Annotation 收集所有模块,写入到文件中。
由于个人感觉原 xml 文件中并不是所有东西都有用,所以有’偷工减料’(annotations的area和 iscrowd不能‘偷工减料’,不然验证的时候可能会报错)。
Annotation = {'images': images, 'annotations': annotations, 'categories': categories}
with open('。/My_datas/COCO/ann.json', 'w', encoding='utf-8') as f:
f.write(json.dumps(Annotation, indent=4, ensure_ascii=False))
print(Annotation)
COCO转换成VOC
这里我用到了 pycocotools,这是一个读取 COCO 内部信息的工具:
- COCO() 的作用是读取 json 内部信息
- .imgs.values() 返回 images 模块信息
- .cats.values() 返回 images 中的每一个目标的分类
如下图 coco.py 的内部截图
- .imgToAnns.values() 返回 annotations 模块信息
输入如下:
from pycocotools.coco import COCO
import xml.dom
import xml
info = r'./My_datas/COCO/ann.json'
coco = COCO(info)
imgs = coco.imgs.values()
oi = coco.catToImgs # .values含第category_id类的图片的image_id
cats = coco.cats.values()
anns = coco.imgToAnns.values()
anns = sum(anns, [])
print('imgs.values():\n%s' % imgs, '\n', 'catToImgs:\n%s' % oi, '\n',
'cats.values():\n%s' % cats, '\n', 'annotations:\n%s' % anns)
输出如下图:
sum(anns, [ ]) 的作用是去掉 anns 中的 [ ] ,否则输出如下,在后面迭代时报错.
在进行COCO转换成VOC 格式之前,我想先将每张image中的目标进行分类并保存,即建立 ImageSets/Main 的 .txt 文件,因为在制作自己的小数据集时没有进行这样的分类操作,在这里可以顺便做一下。代码如下:
cat_name = []
for cat in cats:
n = cat['name']
cat_name.append(n)
print(n)
for i in oi:
ci = int(float(i)) - 1 # 为了避免类别对不上号,不直接用len(oi)
list_file = './My_datas/VOC/ImageSets/Main/%s.txt' % (cat_name[ci])
with open(list_file, 'w', encoding='utf-8') as f:
si = oi[i]
for img in imgs:
if img['id'] in si:
file_name = img['file_name'].strip('.jpg')
f.write(file_name)
f.write('\n')
打开./VOC/ImageSets/Main 可以看到每个类别的 txt :
接下来是COCO对于VOC 的转换,参考自:https://blog.csdn.net/weixin_43384257/article/details/103737588.
- 首先,封装创建节点的过程、添加一个子节点的过程
- <object>节点较为特殊,因此单独进行封装,方便下面操作
# 封装创建节点的过程
def createElementNode(doc, tag, attr): # 创建一个元素节点
element_node = doc.createElement(tag)
text_node = doc.createTextNode(attr) # 创建一个文本节点
element_node.appendChild(text_node) # 将文本节点作为元素节点的子节点
return element_node
# 封装添加一个子节点
def createChildNode(doc, tag, attr, parent_node):
child_node = createElementNode(doc, tag, attr)
parent_node.appendChild(child_node)
def createObjectNode(doc, ann):
object_node = doc.createElement('object')
for cat in cats:
if cat['id'] == ann['category_id']:
name = cat['name']
createChildNode(doc, 'name', name, object_node)
createChildNode(doc, 'difficult', '0', object_node)
bndbox_node = doc.createElement('bndbox')
createChildNode(doc, 'xmin', str(int(ann['bbox'][0])), bndbox_node)
createChildNode(doc, 'ymin', str(int(ann['bbox'][1])), bndbox_node)
createChildNode(doc, 'xmax', str(int(ann['bbox'][2])), bndbox_node)
createChildNode(doc, 'ymax', str(int(ann['bbox'][3])), bndbox_node)
object_node.appendChild(bndbox_node)
return object_node
注意:所有信息都要转换成 str 型
如下,处理 filename、img_size 等信息,当然,也有’偷工减料’。
for img in imgs:
my_dom = xml.dom.getDOMImplementation()
doc = my_dom.createDocument(None, 'annotation', None)
root_node = doc.documentElement
createChildNode(doc, 'filename', img['file_name'], root_node)
size_node = doc.createElement('size')
createChildNode(doc, 'width', str(img['width']), size_node)
createChildNode(doc, 'height', str(img['height']), size_node)
createChildNode(doc, 'depth', str(img['depth']), size_node)
root_node.appendChild(size_node)
createChildNode(doc, 'segmented', '0', root_node)
for ann in anns:
if ann['image_id'] == img['id']:
object_node = createObjectNode(doc, ann)
root_node.appendChild(object_node)
- 最后,写到 xml 中,
删除第一行信息<?xml version="1.0" encoding="utf-8"?>
new_file = open('./My_datas/COCO/ann_voc/%s.xml' % (img['file_name'].strip('.jpg')), 'w')
doc.writexml(new_file, addindent=' ' * 4, newl='\n', encoding='utf-8')
new_file.close()
# 删除第一行字
file = open('./My_datas/COCO/ann_voc/%s.xml' % (img['file_name'].strip('.jpg')), 'r+')
lines = file.readlines()
file.seek(0)
file.truncate()
for line in lines[1:]:
if line.split():
file.write(line.replace('<?xml version="1.0" encoding="utf-8"?>', ''))
file.close()