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=(',', ':')))
    
    
  • 相关阅读:
    yarn安装ant-报错
    Linux扩展分区记录
    转载--tomcat调优
    转发:tomcat的acess_log打印post请求参数,分析日志
    经纬度差和米单位的换算
    loadrunner 11 安装与使用
    前端知识图谱
    linux-nc命令介绍
    双网卡设置(转:https://www.cnblogs.com/visionfeng/p/5825078.html)
    网络设备介绍
  • 原文地址:https://www.cnblogs.com/muyisun/p/14557294.html
Copyright © 2011-2022 走看看