目标检测标注文件转换,xml,json,txt相互转换

目标检测标注文件转换,xml,json,txt相互转换。

    • 1. json转xml
    • 2. xml转txt
    • 3. YOLO(txt)格式转VOC(xml)格式
    • 4. voc(xml)格式转coco(json)格式

闲来无事,把自己平时的一些小工具整理一下,

1. json转xml

json是coco等数据集的标注格式,要对其转换,就要先读取json中针对每张图片的数据,然后根据每张图片生成一个XML文件。

import os
import json
from lxml import etree as ET
from xml.dom import minidom


def edit_xml(objects, id, dir):
    save_xml_path = os.path.join(dir, "%s.xml" % id)  #加入xml,dir为加入路径

    root = ET.Element("annotation")
    # root.set("version", "1.0")
    folder = ET.SubElement(root, "folder")
    folder.text = "none"
    filename = ET.SubElement(root, "filename")
    filename.text = "none"
    source = ET.SubElement(root, "source")
    source.text = "201908"
    owner = ET.SubElement(root, "owner")
    owner.text = "YZN"
    size = ET.SubElement(root, "size")
    width = ET.SubElement(size, "width")
    width.text = str(2048)
    height = ET.SubElement(size, "height")
    height.text = str(2048)
    depth = ET.SubElement(size, "depth")
    depth.text = "3"
    segmented = ET.SubElement(root, "segmented")
    segmented.text = "0"
    for obj in objects:  #
        object = ET.SubElement(root, "object")
        name = ET.SubElement(object, "name")  # number
        name.text = obj["category"]
        # meaning = ET.SubElement(object, "meaning")  # name
        # meaning.text = inf_value[0]
        pose = ET.SubElement(object, "pose")
        pose.text = "Unspecified"
        truncated = ET.SubElement(object, "truncated")
        truncated.text = "0"
        difficult = ET.SubElement(object, "difficult")
        difficult.text = "0"
        bndbox = ET.SubElement(object, "bndbox")
        xmin = ET.SubElement(bndbox, "xmin")
        xmin.text = str(int(obj["bbox"]["xmin"]))
        ymin = ET.SubElement(bndbox, "ymin")
        ymin.text = str(int(obj["bbox"]["ymin"]))
        xmax = ET.SubElement(bndbox, "xmax")
        xmax.text = str(int(obj["bbox"]["xmax"]))
        ymax = ET.SubElement(bndbox, "ymax")
        ymax.text = str(int(obj["bbox"]["ymax"]))
    tree = ET.ElementTree(root)
    tree.write(save_xml_path, encoding="UTF-8", xml_declaration=True)
    root = ET.parse(save_xml_path)
    file_lines = minidom.parseString(ET.tostring(root, encoding="Utf-8")).toprettyxml(
        indent="\t")
    file_line = open(save_xml_path, "w", encoding="utf-8")
    file_line.write(file_lines)
    file_line.close()


def getDirId(dir):  # get the  id list  of id.png
    names = os.listdir(dir)
    ids = []
    for name in names:
        # path = os.path.join(dir, name)
        # img  = cv2.imread(path)
        # w, h, c = img.shape
        # if name.endswith(".jpg") or name.endswith(".png"):
        # ids["%s" % name.split(".")[0]] = [w, h, c]
        ids.append(name.split(".")[0])
    return ids


filedir = "/media/TT100K/data/data/annotations.json"#json文件的文件路径
annos = json.loads(open(filedir).read())

trainIds = getDirId("/media/TT100K/data/data/train/")#用于训练的图片的路径,用于获取图片ID
testIds = getDirId("/media/TT100K/data/data/test/")

ids = annos["imgs"].keys()  # 读取json文件中的所有图片id

for id in ids:
    #  json 中的ID图片,若有待检测目标,且该id图片在 train文件夹中,则生成此图片的xml文件,加入train文件中
    if len(annos["imgs"][id]["objects"]) > 0 and (id in trainIds) :
        objects = annos["imgs"][id]["objects"]
        edit_xml(objects, id, dir="/media/TT100K/data/data/xmllabel/train")

    elif len(annos["imgs"][id]["objects"]) > 0 and (id in testIds):#同上,将xml文件加入测试文件夹
        objects = annos["imgs"][id]["objects"]
        edit_xml(objects, id, dir="/media/TT100K/data/data/xmllabel/test")

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法1 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

2. xml转txt

xml是voc数据集的标注格式,txt是YOLO和大部分数据集的标注格式,在yolov3的github中提供了转换代码,代码如下:

import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join

sets=['train', 'test']

classes = ["police"]#这里输入你的数据集类别


def convert(size, box):#读取xml文件中的数据,xywh
    dw = 1./size[0]
    dh = 1./size[1]
    x = (box[0] + box[1])/2.0
    y = (box[2] + box[3])/2.0
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x*dw
    w = w*dw
    y = y*dh
    h = h*dh
    return (x,y,w,h)

def convert_annotation(image_id):
    in_file = open('police_labels/%s.xml'%(image_id))#这里是读取xml的文件夹
    out_file = open('labels/%s.txt'%(image_id), 'w')#存入txt文件的文件夹
    tree=ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)

    for obj in root.iter('object'):
        difficult = obj.find('difficult').text
        cls = obj.find('name').text
        if cls not in classes or int(difficult) == 1:
            continue
        cls_id = classes.index(cls)
        xmlbox = obj.find('bndbox')
        b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text))
        bb = convert((w,h), b)
        out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')

wd = getcwd()

for image_set in sets:
    # if not os.path.exists('labels/'):
    #     os.makedirs('labels/')
    image_ids = open('ImageSets/Main/%s.txt'%(image_set)).read().strip().split()#读取train.txt或者test.txt从而找到每个xml文件的文件名,这里的train.txt中仅包含文件名,不包好路径。
    list_file = open('%s.txt'%(image_set), 'w')
    for image_id in image_ids:
        list_file.write('/root/object-detection/yolov5-master/data/police_obj/images/%s.jpg\n'%(image_id))#从写train.txt或者test.txt文件,把图片文件的绝对路径写入,方便读取图片
        convert_annotation(image_id)
    list_file.close()

暂时没有用到json直接转换txt文件的,有需要的看官可以先转xml,再转txt。

3. YOLO(txt)格式转VOC(xml)格式

为了形成闭环,需要把txt格式转换为coco格式,但是由于YOLO(txt)格式没有标签名,这里先将coco(txt)转换为VOC(xml)格式。

import cv2
import os

labels = ['A', 'B', 'C']  # 数据集类别名
xml_head = '''
    VOC2007
    
    {}.
    
        The VOC2007 Database
        PASCAL VOC2007
        flickr
        325991873
    
    
        null
        null
        
    
        {}
        {}
        {}
    
    0
    '''
xml_obj = '''
            
        {}
        Rear
        
        0
        
        0
        
        
            {}
            {}
            {}
            {}
        
    
    '''

xml_end = '''
'''
cnt = 0
with open('train.txt', 'r') as train_list: # 训练数据train.txt或test.txt,其中包含图片路径
    for lst in train_list.readlines():
        lst = lst.strip()
        jpg = lst  # image path
        txt1 = lst.replace('images', 'labels')
        txt = lst.replace('.jpg', '.txt')  # yolo label txt path
        xml_path1 = jpg.replace('images', 'xmllabels')
        xml_path = jpg.replace('.jpg', '.xml') 
        # xml保存路径,此时images,labels,xmllabels必须在一个文件目录下,images存放图片,labels存放txt文件。

        obj = ''

        img = cv2.imread(jpg)
        img_h, img_w = img.shape[0], img.shape[1]
        head = xml_head.format(str(jpg), str(img_w), str(img_h))
        with open(txt, 'r') as f:
            for line in f.readlines():
                yolo_datas = line.strip().split(' ')
                label = int(float(yolo_datas[0].strip()))
                center_x = round(float(str(yolo_datas[1]).strip()) * img_w)
                center_y = round(float(str(yolo_datas[2]).strip()) * img_h)
                bbox_width = round(float(str(yolo_datas[3]).strip()) * img_w)
                bbox_height = round(float(str(yolo_datas[4]).strip()) * img_h)

                xmin = str(int(center_x - bbox_width / 2))
                ymin = str(int(center_y - bbox_height / 2))
                xmax = str(int(center_x + bbox_width / 2))
                ymax = str(int(center_y + bbox_height / 2))

                obj += xml_obj.format(labels[label], xmin, ymin, xmax, ymax)
        with open(xml_path, 'w') as f_xml:
            f_xml.write(head + obj + xml_end)
        cnt += 1
        print(cnt)

4. voc(xml)格式转coco(json)格式

此时需要将xml文件和图片文件皆转入当前目录的annotations文件夹中,若没有这个文件夹,就创造一个。

import os
import glob
import json
import shutil
import numpy as np
import xml.etree.ElementTree as ET
 
 
 
path2 = "."#当前该文件路径
 
 
START_BOUNDING_BOX_ID = 1
 
 
def get(root, name):
    return root.findall(name)
 
 
def get_and_check(root, name, length):
    vars = root.findall(name)
    if len(vars) == 0:
        raise NotImplementedError('Can not find %s in %s.'%(name, root.tag))
    if length > 0 and len(vars) != length:
        raise NotImplementedError('The size of %s is supposed to be %d, but is %d.'%(name, length, len(vars)))
    if length == 1:
        vars = vars[0]
    return vars
 
 
def convert(xml_list, json_file):
    json_dict = {
     "images": [], "type": "instances", "annotations": [], "categories": []}
    categories = pre_define_categories.copy()
    bnd_id = START_BOUNDING_BOX_ID
    all_categories = {
     }
    for index, line in enumerate(xml_list):
        # print("Processing %s"%(line))
        xml_f = line
        tree = ET.parse(xml_f)
        root = tree.getroot()
        
        filename = os.path.basename(xml_f)[:-4] + ".jpg"
        image_id = 1 + index
        size = get_and_check(root, 'size', 1)
        width = int(get_and_check(size, 'width', 1).text)
        height = int(get_and_check(size, 'height', 1).text)
        image = {
     'file_name': filename, 'height': height, 'width': width, 'id':image_id}
        json_dict['images'].append(image)
        ## Cruuently we do not support segmentation
        #  segmented = get_and_check(root, 'segmented', 1).text
        #  assert segmented == '0'
        for obj in get(root, 'object'):
            category = get_and_check(obj, 'name', 1).text
            if category in all_categories:
                all_categories[category] += 1
            else:
                all_categories[category] = 1
            if category not in categories:
                if only_care_pre_define_categories:
                    continue
                new_id = len(categories) + 1
                print("[warning] category '{}' not in 'pre_define_categories'({}), create new id: {} automatically".format(category, pre_define_categories, new_id))
                categories[category] = new_id
            category_id = categories[category]
            bndbox = get_and_check(obj, 'bndbox', 1)
            xmin = int(float(get_and_check(bndbox, 'xmin', 1).text))
            ymin = int(float(get_and_check(bndbox, 'ymin', 1).text))
            xmax = int(float(get_and_check(bndbox, 'xmax', 1).text))
            ymax = int(float(get_and_check(bndbox, 'ymax', 1).text))
            assert(xmax > xmin), "xmax <= xmin, {}".format(line)
            assert(ymax > ymin), "ymax <= ymin, {}".format(line)
            o_width = abs(xmax - xmin)
            o_height = abs(ymax - ymin)
            ann = {
     'area': o_width*o_height, 'iscrowd': 0, 'image_id':
                   image_id, 'bbox':[xmin, ymin, o_width, o_height],
                   'category_id': category_id, 'id': bnd_id, 'ignore': 0,
                   'segmentation': []}
            json_dict['annotations'].append(ann)#将生成的json文件加入annotations文件夹
            bnd_id = bnd_id + 1
 
    for cate, cid in categories.items():
        cat = {
     'supercategory': 'none', 'id': cid, 'name': cate}
        json_dict['categories'].append(cat)
    json_fp = open(json_file, 'w')
    json_str = json.dumps(json_dict)
    json_fp.write(json_str)
    json_fp.close()
    print("------------create {} done--------------".format(json_file))
    print("find {} categories: {} -->>> your pre_define_categories {}: {}".format(len(all_categories), all_categories.keys(), len(pre_define_categories), pre_define_categories.keys()))
    print("category: id --> {}".format(categories))
    print(categories.keys())
    print(categories.values())
 
 
if __name__ == '__main__':
    classes = ['person']
    pre_define_categories = {
     }
    for i, cls in enumerate(classes):
        pre_define_categories[cls] = i + 1
    # pre_define_categories = {'a1': 1, 'a3': 2, 'a6': 3, 'a9': 4, "a10": 5}
    only_care_pre_define_categories = True
    # only_care_pre_define_categories = False
 
    train_ratio = 1#控制train和val的比例,train_ratio=1是全部生成为train数据
    save_json_train = 'instances_train2014.json'#生成训练集json文件名
    save_json_val = 'instances_val2014.json'
    xml_dir = "Annotations"存放xml文件的文件夹
 
    xml_list = glob.glob(xml_dir + "/*.xml")
    xml_list = np.sort(xml_list)
    np.random.seed(100)
    np.random.shuffle(xml_list)
 
    train_num = int(len(xml_list)*train_ratio)
    xml_list_train = xml_list[:train_num]
    xml_list_val = xml_list[train_num:]
 
    convert(xml_list_train, save_json_train)
    convert(xml_list_val, save_json_val)
 
    if os.path.exists(path2 + "/annotations"):
        shutil.rmtree(path2 + "/annotations")
    os.makedirs(path2 + "/annotations")
    if os.path.exists(path2 + "/images/train2014"):
        shutil.rmtree(path2 + "/images/train2014")
    os.makedirs(path2 + "/images/train2014")
    if os.path.exists(path2 + "/images/val2014"):
        shutil.rmtree(path2 +"/images/val2014")
    os.makedirs(path2 + "/images/val2014")
 
    f1 = open("train.txt", "w")
    for xml in xml_list_train:
        img = xml[:-4] + ".jpg"#根据xml文件路径获取图片路径,此时图片和xml文件都在annotations文件夹中
        f1.write(os.path.basename(xml)[:-4] + "\n")
        shutil.copyfile(img, path2 + "/images/train2014/" + os.path.basename(img))#将用于训练的图片存入训练集
 
    f2 = open("test.txt", "w")
    for xml in xml_list_val:
        img = xml[:-4] + ".jpg"
        f2.write(os.path.basename(xml)[:-4] + "\n") 
        shutil.copyfile(img, path2 + "/images/val2014/" + os.path.basename(img))#将用于测试的图片存入测试集
    f1.close()
    f2.close()
    print("-------------------------------")
    print("train number:", len(xml_list_train))
    print("val number:", len(xml_list_val))

  1. mermaid语法说明 ↩︎

你可能感兴趣的:(目标检测常规操作,python)