zoukankan      html  css  js  c++  java
  • 目标检测数据集xml格式和json的转换标本

    COCO数据集的格式

    annotation.json文件内容,主要有四个部分需要关注。

    {
        "categories": [   // 类别组,是一个列表,长度等于类别数。列表的每个元素存放的是数据集的类别,每个类别又是一个字典格式
            {
                "supercategory": "父类",  // 该项表示类别的父类 如果不是特殊情况,通常可以与name相同
                "id": 1,             // 该项表示类别的序号
                "name": "类别1的名字"  // 该项表示类别的名字 
            },
              {
                "supercategory": "父类",  
                "id": 2,             
                "name": "类别2"  
            }
        ],
        
          "images":[   // 图像组,也是一个字典组成的列表。 长度是数据集中的图像数量
        {
                "file_name": "img_0175153.jpg",  // 该项表示图像文件名
                "width": 4096,  // 图像宽高
                "id": 1,         // 图像的id序号,这个序号是人为规定的,比如,有n张图,id从1变化到n
                "height": 3000
            },
            {
                "file_name": "img_0172993.jpg",
                "width": 4096,
                "id": 2,
                "height": 3000
            }
        ],
    
     "annotations": [  // 标注部分 也是字典构成的列表,它的长度不等于图像文件名,而是 每一个图像的每一个目标 作为列表元素,因此,该列表的长度是 数据集中全部目标的数量
            {
                "area": 2993,  // 该目标区域的面积
                "iscrowd": 0,  // 是否分块,如果是1,表示一组对象。即该目标被遮挡导致有多个小块
                "image_id": 1,  // 图片的序号id
                "bbox": [  // 该区域的mask外接矩形
                    2500,
                    1268,
                    41,
                    73
                ],
                "segmentation":[[x1,y1,x2,y2,x3,y3] ],  // 掩膜轮廓区域坐标 每相邻xy表示一个点
                "category_id": 12,  // 目标的类别序号
                "id": 1  // 目标的序号id,即在annotation类别中的序号。
            },
    }
    

    coco的json标签转xml文件

    从coco转换到xml格式,只需要从json文件中提取到图像的信息,包括文件名/路径、宽高和标注信息,标注信息只需要类别id和区域的bbox。

    具体代码如下,pretty_xml是用于缩进xml文件,进行美化(忘了是在哪个博客看到的了)。write函数用于写入一个xml文件,函数接受一个字典输入info,其key是图像名,value是一个列表,每个元素是图像中的一个目标,以x y w h label的顺序表示,或者x1 y1 x2 y2 label的顺序,shape参数是该图像的hwc,如果没有传入,会试图通过读取文件名来获取该信息。

    写入xml的内容都必须是字符串,不能是整数或者浮点数,因此write函数中写入一些数字类型的数据,如坐标、宽高等要转成str。

    import time
    import cv2
    import numpy as np
    from PIL import Image
    import json
    import xml.etree.ElementTree as ET 
    import random
    
    def pretty_xml(element, indent, newline, level=0):  # elemnt为传进来的Elment类,参数indent用于缩进,newline用于换行
        if element:  # 判断element是否有子元素    
            if (element.text is 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)  # 将element转成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
            pretty_xml(subelement, indent, newline, level=level + 1)  # 对子元素进行递归操作
    
    
    
    # 送入的是dict {图像名字 :框信息} 框信息是[[x1,y1,w,h,label], [x1,y1,w,h,label],[]]
    def write(infos,shape=None):
    
        root = ET.Element('annotation',{"verified":"no"})  # 根节点
    
        folder = ET.SubElement(root, 'folder')
        folder.text = '1'
        img_name = list(infos.keys())[0].strip()  # 图像信息
    
        filename = img_name.split("/")[-1]
        file_name = ET.SubElement(root, 'filename')
        file_name.text = filename  # 文件名这个tag
    
        path = ET.SubElement(root, 'path')  # 路径的tag
        path.text = img_name 
    
        source = ET.SubElement(root, 'source')
        database = ET.SubElement(source, 'database')
        database.text = "Unknown"
    
        if shape:
            h,w,c = shape[0], shape[1],shape[2]
        else:
            img = cv2.imread(img_name)
            h,w,c = img.shape 
            
        size = ET.SubElement(root, 'size')
        width = ET.SubElement(size, 'width')
        width.text = str(w)
        height = ET.SubElement(size, 'height')
        height.text = str(h)
        depth = ET.SubElement(size, 'depth')
        depth.text = str(c)
    
        segmented = ET.SubElement(root, 'segmented')
        segmented.text = str(0)
    
        for key in infos.keys():
            bboxes = infos[key]
            for bbox in bboxes:
                object_tag = ET.SubElement(root, 'object')
                # bbox = ['911', '258', '153', '326', 'class1']
                name = ET.SubElement(object_tag, 'name')
                name.text = bbox[-1]  #框名字
                
                pose = ET.SubElement(object_tag, 'pose')
                pose.text = "Unspecified"
                trunc = ET.SubElement(object_tag,'truncated')
                trunc.text = '0'
                diff = ET.SubElement(object_tag,'difficult')
                diff.text = '0'
                # 下面是写入坐标框
                bndbox = ET.SubElement(object_tag,'bndbox')
                #left_x:  911   top_y:  258     153   height:  326
                bbox[2] = str(int(bbox[2]))
                bbox[3] = str(int(bbox[3]))
    
                xmin = ET.SubElement(bndbox,'xmin')
                xmin.text = bbox[0]
    
                ymin = ET.SubElement(bndbox,'ymin')
                ymin.text = bbox[1]
    
                xmax = ET.SubElement(bndbox,'xmax')   
                # 如果是 x y w h
                # xmax.text = str(int(bbox[0]) + int(bbox[2]) )
                # 如果是 x1 y1 x2 y2
                xmax.text = str(int(bbox[2]) )
    
                ymax = ET.SubElement(bndbox,'ymax')
    
                # ymax.text = str(int(bbox[1]) + int(bbox[3]) )
                ymax.text = str(int(bbox[3]))
    
        pretty_xml(root, '	', '
    ')  # 执行美化方法
        #ET.dump(root)
        tree = ET.ElementTree(root)
        xml_name = filename.replace("jpg","xml")  # 要保存的xml文件名字 和图像文件名相同,仅后缀不同
        tree.write(f"F:/xjzh/cocoxml/{xml_name}", encoding="utf-8",xml_declaration=False)
    
    
        
    
    # 打开json标签文件
    with open(r'F:xjzh图片annotations.json','r') as f:
        data_an = {}  # 外围大字典
        json_dicts = json.loads(f.read())  # 
    
    
    cate = json_dicts['categories']
    cls_name = [0]*len(cate)
    for cate_dict in cate:
        cls_name[cate_dict['id']] = cate_dict['name'] # 各个类别对应的名字 按顺序放在cls_name中,如id为0的类别名就是cls_name[0]
    
    # 类别 按照id顺序来的
    #  ['背景', '瓶盖破损', '瓶盖变形', '瓶盖坏边', '瓶盖打旋', '瓶盖断点', '标贴歪斜', '标贴起皱', '标贴气泡', '喷码正常', '喷码异常', '酒液杂质', '瓶身破损', '瓶身气泡']
    
    images_info = json_dicts["images"]  # 图像信息
    
    image_nums = len(images_info)  # 图像数量
    print(image_nums)
    
    file_list = []  # 存放图像基本信息 依次按顺序读入,这样图像image_id是k的图像信息就是file_list[k-1]  k从1开始
    
    annot_list = [[] for _ in range(image_nums)] # 存放每个图像对应的框,每个图像的全部目标是一个元素,因此初始化为一个长度和图像数量相同的列表,列表的每个元素是一个空列表,后面用于增加目标框信息
    
    for index in range(image_nums):
        # 第 index张图像的信息 包括文件名 宽高
        file_name = images_info[index]["file_name"]  # id从1变换到2668  索引从0到2667
        width = images_info[index]["width"]
        height = images_info[index]['height']
        # print([file_name,width,height],'-------')
        file_list.append([file_name,width,height])  # file_list列表保存
    
    
    annot = json_dicts["annotations"]
    # 根据目标所属的图像id,放到列表对应的位置。从而使一张图片的多个目标聚合在一起
    for index in range(len(annot)):  # 获取标签信息
        # 第index个目标的框可能是x y w h或者x1 y1 x2 y2的形式。按照实际情况修改
        annot[index]['bbox'][2] = annot[index]['bbox'][2] + annot[index]['bbox'][0]  # 这是x y w h的形式 转成x1 y1 x2 y2的形式。或者不转,和write函数保持一致就可以
        annot[index]['bbox'][3] = annot[index]['bbox'][3] + annot[index]['bbox'][1]
        
        # 这个目标的类别id是annot[index]["category_id"] 它的名字是cls[索引]  注意json文件中类别id可能是从1开始,而cls_name的索引是0开始 注意保持对应
        annot[index]['bbox'].append(cls_name[ annot[index]["category_id"] ])  # 在框信息后面 加入 label
        # print(annot[index]['bbox'],annot[index]['image_id']-1)
        # -1是因为该目标对应的图像id是从1开始,因此要放在列表的第 id -1的位置
        annot_list[annot[index]['image_id']-1].append(annot[index]['bbox'])   # bbox是 x1 y1 w h 
    
    
    # 对每一张图 写入xml文件
    for index in range(image_nums):
        print(index)
        box = annot_list[index]
        print(box)
        # info的key是文件名 value是坐标列表
        write({file_list[index][0]: box},shape=[str(file_list[index][1]),str(file_list[index][0]),'3'])  # shape =hwc
    

    xml或者yolo标签转labelme的json格式

    目标检测任务,而不是分割任务格式。将xml或者yolo格式的框标注文件转成json格式,格式和labelme标注结果格式相同,而不是coco数据集的json格式。

    转换步骤: 从xml文件或者txt文件获取框的坐标,按照labelme的json坐标顺序组织,依次对照json的各个key填充相应内容即可。

    labelme标注的json文件内容如下。

    labelme 标签的json文件格式

    "imagePath":"1.jpg" 图像路径

    "fillColor": [255,0,0,128], # 填充颜色,RGB和透明度

    "imageData":"/9j/啥啥啥" base64编码

    "imageHeight": 768, 图像高度

    "flags": {},

    "version": "3.16.7",

    "imageWidth": 1366, 图像宽度

    "lineColor": [0,255,0,128], # 线条颜色

    "shapes": [ # shapes是列表,每一个元素是字典。

    # 每个字典格式如下

    {

    ​ "fill_color": null,

    ​ "line_color": null,

    ​ "shape_type": "polygon", # 多边形 或者 长方形等

    ​ "points": [ # 列表点 xy

    ​ [

    ​ 248.8771929824561, 273.2631578947368

    ​ ],

    ​ ],

    ​ "flags": {},

    ​ "label": "person" # 标签

    },

    import cv2
    import  xml.etree.ElementTree as ET 
    import numpy as np
    import os
    import json
    import shutil
    import base64
    '''
    该脚本实现将xml类型标签(或者yolo格式标签)转为json格式标签
    需要的数据:原始图像 原始xml标签(原始txt标签)
    
    '''
    
    # 解析数据集,输入单张图片路径,图片路径不能出现中文,因为是cv2读取的。和对应xml文件的路径
    # 返回图片 该图所有的目标框[[x1,y1,x2,y2],....]  每个框的类别[label1, label2, label3,.....]  注意是label而不是索引
    def parse_img_label(img_path, xml_path):  # 绝对路径
        img = cv2.imread(img_path)
        tree = ET.parse(xml_path) 
        root = tree.getroot()
        objs = root.findall('object')
        bboxes = []  # 坐标框
        h ,w = img.shape[0], img.shape[1]
        #gt_labels = []  # 标签名
        for obj in objs: # 遍历所有的目标
            label = obj[0].text  # <name>这个tag的值,即标签
            label = label.strip(' ')
            box = [int(obj[4][i].text) for i in range(4)]
            box.append(label)  # box的元素 x1 y1 x2 y2 类别
            bboxes.append(box)
        return img, bboxes
    
    # 该函数用于将yolo的标签转回xml需要的标签。。即将归一化后的坐标转为原始的像素坐标
    def convert_yolo_xml(box,img):  # 
        x,y,w,h = box[0], box[1], box[2], box[3]
        # 求出原始的x1 x2 y1 y2
        x2 = (2*x + w)*img.shape[1] /2
        x1 = x2 - w*img.shape[1]
    
        y2 = (2*y+h)*img.shape[0] /2
        y1 = y2 - h* img.shape[0]
        new_box = [x1,y1, x2, y2]
        new_box = list(map(int,new_box))
        return new_box
    
    # 该函数用于解析yolo格式的数据集,即txt格式的标注 返回图像 边框坐标 真实标签名(不是索引,因此需要预先定义标签)
    def parse_img_txt(img_path, txt_path):
        name_label = ['class0','class1','class2']  # 需要自己预先定义,它的顺序要和实际yolo格式的标签中0 1 2 3的标签对应 yolo标签的类别是索引 而不是名字
        img = cv2.imread(img_path)
        f = open(txt_path)
        bboxes = []
        for line in f.readlines():
            line = line.split(" ")
            if len(line) == 5:
                obj_label = name_label[int(line[0])] # 将类别索引转成其名字
                x = float(line[1])
                y = float(line[2])
                w = float(line[3])
                h = float(line[4])
                box = convert_yolo_xml([x,y,w,h], img)
                box.append(obj_label)
                bboxes.append(box)
        return img, bboxes
    
    
    
    # 制作labelme格式的标签
    # 参数说明 img_name: 图像文件名称 
    # txt_name: 标签文件的绝对路径,注意是绝对路径
    # prefix: 图像文件的上级目录名。即形如/home/xjzh/data/ 而img_name是其下的文件名,如00001.jpg
    # prefix+img_name即为图像的绝对路径。不该路径能出现中文,否则cv2读取会有问题
    # 
    def get_json(img_name, txt_name, prefix, yolo=False):
        # 图片名 标签名 前缀
        label_dict = {}  # json字典,依次填充它的value 
        label_dict["imagePath"] = prefix + img_name  # 图片路径
        label_dict["fillColor"] = [255,0,0,128]  # 目标区域的填充颜色 RGBA
        label_dict["lineColor"] = [0,255,0,128]  # 线条颜色
        label_dict["flag"] = {}
        label_dict["version"] = "3.16.7"  # 版本号,随便
        with open(prefix + img_name,"rb") as f:
            img_data = f.read()
            base64_data = base64.b64encode(img_data)
            base64_str = str(base64_data, 'utf-8')
            label_dict["imageData"] = base64_str  # labelme的json文件存放了图像的base64编码。这样如果图像路径有问题仍然能够打开文件
    
        img, gt_box = parse_img_label(prefix + img_name, txt_name) if not yolo else parse_img_txt(prefix + img_name, txt_name)  # 读取真实数据
        
        label_dict["imageHeight"] = img.shape[0]  # 高度
        label_dict["imageWidth"] = img.shape[1]
    
        shape_list = [] # 存放标注信息的列表,它是 shapes这个键的值。里面是一个列表,每个元素又是一个字典,字典内容是该标注的类型 颜色 坐标点等等
        #label_dict["shapes"] = [] # 列表,每个元素是字典。
        # box的元素 x1 y1 x2 y2 类别
        for box in gt_box:
            shape_dict = {}  # 表示一个目标的字典
            shape_dict["shape_type"] = "rectangle"  # 因为xml或yolo格式标签是矩形框标注,因此是rectangle
            shape_dict["fill_color"] = None  #该类型的填充颜色 
            shape_dict["line_color"] = None  # 线条颜色 可以设置,或者根据标签名自己预先设定labe_color_dict
            shape_dict["flags"] = {}
            shape_dict["label"] = box[-1] # 标签名  
            shape_dict["points"] = [[box[0],box[1]], [box[2], box[3]]] 
            # 通常contours是长度为1的列表,如果有分块,可能就有多个  # [[x1,y1], [x2,y2]...]的列表
            shape_list.append(shape_dict)
        
        label_dict["shapes"] = shape_list  #
        return label_dict
    
    imgs_path = "/home/xjzh/fgd/JPEGImages/"  # 图像路径
    xmls_path ="/home/xjzh/fgd/Annotations/" # xml文件路径
    
    img_path = os.listdir(imgs_path)
    out_json = '/home/xjzh/DATA/JSON_data/'  # 保存的json文件路径
    
    for nums, path in enumerate(img_path):
        if nums %200==0:
            print(f"processed {nums} images")
        xml_path = xmls_path + path.replace('jpg','xml')  # xml文件的绝对路径
        label_dict = get_json(path, xml_path,prefix=imgs_path)  # 
        with open(out_json + path.replace("jpg","json"),'w') as f: # 写入一个json文件
            f.write(json.dumps(label_dict, ensure_ascii=False, indent=4, separators=(',', ':')))
    
    
  • 相关阅读:
    2021.1.28 个人rating赛补题报告
    2021.1.23 个人rating赛补题报告
    2021.1.23 个人rating赛补题报告
    2020.12.14 个人训练赛补题报告
    2020.11.28 2020团体程序设计天梯赛补题报告
    2020.12.3 Codeforces Beta Round #73(Div2)补题报告
    Xhorse VVDI Prog V5.0.6 is Ready for BCM2 Adapter
    Program 2021 Ford Bronco All Keys Lost using VVDI Key Tool Plus
    Xhorse VVDI Prog V5.0.4 Software Update in July 2021
    How to use Xhorse VVDI2 to Exchange BMW FEM/BDC Module?
  • 原文地址:https://www.cnblogs.com/muyisun/p/14557294.html
Copyright © 2011-2022 走看看