已实现数据集转换
一、CoCo1.1 CoCo2VOC1.2 CoCo2YOLO 二、VOC2.1 VOC2CoCo2.2 VOC2YOLO 三、YOLO3.1 YOLO2CoCo3.2 YOLO2VOC 四、TT100K4.1 TT100K2YOLO
一、CoCo
1.1 CoCo2VOC
from pycocotools.coco import COCOimport osfrom lxml import etree, objectifyimport shutilfrom tqdm import tqdmimport sysimport argparse# 将类别名字和id建立索引def catid2name(coco): classes = dict() for cat in coco.dataset['categories']: classes[cat['id']] = cat['name'] return classes# 将标签信息写入xmldef save_anno_to_xml(filename, size, objs, save_path): E = objectify.ElementMaker(annotate=False) anno_tree = E.annotation( E.folder("DATA"), E.filename(filename), E.source( E.database("The VOC Database"), E.annotation("PASCAL VOC"), E.image("flickr") ), E.size( E.width(size['width']), E.height(size['height']), E.depth(size['depth']) ), E.segmented(0) ) for obj in objs: E2 = objectify.ElementMaker(annotate=False) anno_tree2 = E2.object( E.name(obj[0]), E.pose("Unspecified"), E.truncated(0), E.difficult(0), E.bndbox( E.xmin(obj[1]), E.ymin(obj[2]), E.xmax(obj[3]), E.ymax(obj[4]) ) ) anno_tree.append(anno_tree2) anno_path = os.path.join(save_path, filename[:-3] + "xml") etree.ElementTree(anno_tree).write(anno_path, pretty_print=True)# 利用cocoAPI从json中加载信息def load_coco(anno_file, xml_save_path): if os.path.exists(xml_save_path): shutil.rmtree(xml_save_path) os.makedirs(xml_save_path) coco = COCO(anno_file) classes = catid2name(coco) imgIds = coco.getImgIds() classesIds = coco.getCatIds() for imgId in tqdm(imgIds): size = {} img = coco.loadImgs(imgId)[0] filename = img['file_name'] width = img['width'] height = img['height'] size['width'] = width size['height'] = height size['depth'] = 3 annIds = coco.getAnnIds(imgIds=img['id'], iscrowd=None) anns = coco.loadAnns(annIds) objs = [] for ann in anns: object_name = classes[ann['category_id']] # bbox:[x,y,w,h] bbox = list(map(int, ann['bbox'])) xmin = bbox[0] ymin = bbox[1] xmax = bbox[0] + bbox[2] ymax = bbox[1] + bbox[3] obj = [object_name, xmin, ymin, xmax, ymax] objs.append(obj) save_anno_to_xml(filename, size, objs, xml_save_path)def parseJsonFile(data_dir, xmls_save_path): assert os.path.exists(data_dir), "data dir:{} does not exits".format(data_dir) if os.path.isdir(data_dir): data_types = ['train2017', 'val2017'] for data_type in data_types: ann_file = 'instances_{}.json'.format(data_type) xmls_save_path = os.path.join(xmls_save_path, data_type) load_coco(ann_file, xmls_save_path) elif os.path.isfile(data_dir): anno_file = data_dir load_coco(anno_file, xmls_save_path)if __name__ == '__main__': """ 脚本说明: 该脚本用于将coco格式的json文件转换为voc格式的xml文件 参数说明: data_dir:json文件的路径 xml_save_path:xml输出路径 """ parser = argparse.ArgumentParser() parser.add_argument('-d', '--data-dir', type=str, default='./data/labels/coco/train.json', help='json path') parser.add_argument('-s', '--save-path', type=str, default='./data/convert/voc', help='xml save path') opt = parser.parse_args() print(opt) if len(sys.argv) > 1: parseJsonFile(opt.data_dir, opt.save_path) else: data_dir = './data/labels/coco/train.json' xml_save_path = './data/convert/voc' parseJsonFile(data_dir=data_dir, xmls_save_path=xml_save_path)
1.2 CoCo2YOLO
from pycocotools.coco import COCOimport osimport shutilfrom tqdm import tqdmimport sysimport argparseimages_nums = 0category_nums = 0bbox_nums = 0# 将类别名字和id建立索引def catid2name(coco): classes = dict() for cat in coco.dataset['categories']: classes[cat['id']] = cat['name'] return classes# 将[xmin,ymin,xmax,ymax]转换为yolo格式[x_center, y_center, w, h](做归一化)def xyxy2xywhn(object, width, height): cat_id = object[0] xn = object[1] / width yn = object[2] / height wn = object[3] / width hn = object[4] / height out = "{} {:.5f} {:.5f} {:.5f} {:.5f}".format(cat_id, xn, yn, wn, hn) return outdef save_anno_to_txt(images_info, save_path): filename = images_info['filename'] txt_name = filename[:-3] + "txt" with open(os.path.join(save_path, txt_name), "w") as f: for obj in images_info['objects']: line = xyxy2xywhn(obj, images_info['width'], images_info['height']) f.write("{}\n".format(line))# 利用cocoAPI从json中加载信息def load_coco(anno_file, xml_save_path): if os.path.exists(xml_save_path): shutil.rmtree(xml_save_path) os.makedirs(xml_save_path) coco = COCO(anno_file) classes = catid2name(coco) imgIds = coco.getImgIds() classesIds = coco.getCatIds() with open(os.path.join(xml_save_path, "classes.txt"), 'w') as f: for id in classesIds: f.write("{}\n".format(classes[id])) for imgId in tqdm(imgIds): info = {} img = coco.loadImgs(imgId)[0] filename = img['file_name'] width = img['width'] height = img['height'] info['filename'] = filename info['width'] = width info['height'] = height annIds = coco.getAnnIds(imgIds=img['id'], iscrowd=None) anns = coco.loadAnns(annIds) objs = [] for ann in anns: object_name = classes[ann['category_id']] # bbox:[x,y,w,h] bbox = list(map(float, ann['bbox'])) xc = bbox[0] + bbox[2] / 2. yc = bbox[1] + bbox[3] / 2. w = bbox[2] h = bbox[3] obj = [ann['category_id'], xc, yc, w, h] objs.append(obj) info['objects'] = objs save_anno_to_txt(info, xml_save_path)def parseJsonFile(json_path, txt_save_path): assert os.path.exists(json_path), "json path:{} does not exists".format(json_path) if os.path.exists(txt_save_path): shutil.rmtree(txt_save_path) os.makedirs(txt_save_path) assert json_path.endswith('json'), "json file:{} It is not json file!".format(json_path) load_coco(json_path, txt_save_path)if __name__ == '__main__': """ 脚本说明: 该脚本用于将coco格式的json文件转换为yolo格式的txt文件 参数说明: json_path:json文件的路径 txt_save_path:txt保存的路径 """ parser = argparse.ArgumentParser() parser.add_argument('-jp', '--json-path', type=str, default='./data/labels/coco/train.json', help='json path') parser.add_argument('-s', '--save-path', type=str, default='./data/convert/yolo', help='txt save path') opt = parser.parse_args() if len(sys.argv) > 1: print(opt) parseJsonFile(opt.json_path, opt.save_path) # print("image nums: {}".format(images_nums)) # print("category nums: {}".format(category_nums)) # print("bbox nums: {}".format(bbox_nums)) else: json_path = './data/labels/coco/train.json' # r'D:\practice\compete\goodsDec\data\train\train.json' txt_save_path = './data/convert/yolo' parseJsonFile(json_path, txt_save_path) # print("image nums: {}".format(images_nums)) # print("category nums: {}".format(category_nums)) # print("bbox nums: {}".format(bbox_nums))
二、VOC
2.1 VOC2CoCo
import xml.etree.ElementTree as ETimport osimport jsonfrom datetime import datetimeimport sysimport argparsecoco = dict()coco['images'] = []coco['type'] = 'instances'coco['annotations'] = []coco['categories'] = []category_set = dict()image_set = set()category_item_id = -1image_id = 000000annotation_id = 0def addCatItem(name): global category_item_id category_item = dict() category_item['supercategory'] = 'none' category_item_id += 1 category_item['id'] = category_item_id category_item['name'] = name coco['categories'].append(category_item) category_set[name] = category_item_id return category_item_iddef addImgItem(file_name, size): global image_id if file_name is None: raise Exception('Could not find filename tag in xml file.') if size['width'] is None: raise Exception('Could not find width tag in xml file.') if size['height'] is None: raise Exception('Could not find height tag in xml file.') image_id += 1 image_item = dict() image_item['id'] = image_id image_item['file_name'] = file_name image_item['width'] = size['width'] image_item['height'] = size['height'] image_item['license'] = None image_item['flickr_url'] = None image_item['coco_url'] = None image_item['date_captured'] = str(datetime.today()) coco['images'].append(image_item) image_set.add(file_name) return image_iddef addAnnoItem(object_name, image_id, category_id, bbox): global annotation_id annotation_item = dict() annotation_item['segmentation'] = [] seg = [] # bbox[] is x,y,w,h # left_top seg.append(bbox[0]) seg.append(bbox[1]) # left_bottom seg.append(bbox[0]) seg.append(bbox[1] + bbox[3]) # right_bottom seg.append(bbox[0] + bbox[2]) seg.append(bbox[1] + bbox[3]) # right_top seg.append(bbox[0] + bbox[2]) seg.append(bbox[1]) annotation_item['segmentation'].append(seg) annotation_item['area'] = bbox[2] * bbox[3] annotation_item['iscrowd'] = 0 annotation_item['ignore'] = 0 annotation_item['image_id'] = image_id annotation_item['bbox'] = bbox annotation_item['category_id'] = category_id annotation_id += 1 annotation_item['id'] = annotation_id coco['annotations'].append(annotation_item)def read_image_ids(image_sets_file): ids = [] with open(image_sets_file, 'r') as f: for line in f.readlines(): ids.append(line.strip()) return idsdef parseXmlFilse(data_dir, json_save_path, split='train'): assert os.path.exists(data_dir), "data path:{} does not exist".format(data_dir) labelfile = split + ".txt" image_sets_file = os.path.join(data_dir, "ImageSets", "Main", labelfile) xml_files_list = [] if os.path.isfile(image_sets_file): ids = read_image_ids(image_sets_file) xml_files_list = [os.path.join(data_dir, "Annotations", f"{i}.xml") for i in ids] elif os.path.isdir(data_dir): # 修改此处xml的路径即可 # xml_dir = os.path.join(data_dir,"labels/voc") xml_dir = data_dir xml_list = os.listdir(xml_dir) xml_files_list = [os.path.join(xml_dir, i) for i in xml_list] for xml_file in xml_files_list: if not xml_file.endswith('.xml'): continue tree = ET.parse(xml_file) root = tree.getroot() # 初始化 size = dict() size['width'] = None size['height'] = None if root.tag != 'annotation': raise Exception('pascal voc xml root element should be annotation, rather than {}'.format(root.tag)) # 提取图片名字 file_name = root.findtext('filename') assert file_name is not None, "filename is not in the file" # 提取图片 size {width,height,depth} size_info = root.findall('size') assert size_info is not None, "size is not in the file" for subelem in size_info[0]: size[subelem.tag] = int(subelem.text) if file_name is not None and size['width'] is not None and file_name not in image_set: # 添加coco['image'],返回当前图片ID current_image_id = addImgItem(file_name, size) print('add image with name: {}\tand\tsize: {}'.format(file_name, size)) elif file_name in image_set: raise Exception('file_name duplicated') else: raise Exception("file name:{}\t size:{}".format(file_name, size)) # 提取一张图片内所有目标object标注信息 object_info = root.findall('object') if len(object_info) == 0: continue # 遍历每个目标的标注信息 for object in object_info: # 提取目标名字 object_name = object.findtext('name') if object_name not in category_set: # 创建类别索引 current_category_id = addCatItem(object_name) else: current_category_id = category_set[object_name] # 初始化标签列表 bndbox = dict() bndbox['xmin'] = None bndbox['xmax'] = None bndbox['ymin'] = None bndbox['ymax'] = None # 提取box:[xmin,ymin,xmax,ymax] bndbox_info = object.findall('bndbox') for box in bndbox_info[0]: bndbox[box.tag] = int(box.text) if bndbox['xmin'] is not None: if object_name is None: raise Exception('xml structure broken at bndbox tag') if current_image_id is None: raise Exception('xml structure broken at bndbox tag') if current_category_id is None: raise Exception('xml structure broken at bndbox tag') bbox = [] # x bbox.append(bndbox['xmin']) # y bbox.append(bndbox['ymin']) # w bbox.append(bndbox['xmax'] - bndbox['xmin']) # h bbox.append(bndbox['ymax'] - bndbox['ymin']) print('add annotation with object_name:{}\timage_id:{}\tcat_id:{}\tbbox:{}'.format(object_name, current_image_id, current_category_id, bbox)) addAnnoItem(object_name, current_image_id, current_category_id, bbox) json_parent_dir = os.path.dirname(json_save_path) if not os.path.exists(json_parent_dir): os.makedirs(json_parent_dir) json.dump(coco, open(json_save_path, 'w')) print("class nums:{}".format(len(coco['categories']))) print("image nums:{}".format(len(coco['images']))) print("bbox nums:{}".format(len(coco['annotations'])))if __name__ == '__main__': """ 脚本说明: 本脚本用于将VOC格式的标注文件.xml转换为coco格式的标注文件.json 参数说明: voc_data_dir:两种格式 1.voc2012文件夹的路径,会自动找到voc2012/imageSets/Main/xx.txt 2.xml标签文件存放的文件夹 json_save_path:json文件输出的文件夹 split:主要用于voc2012查找xx.txt,如train.txt.如果用格式2,则不会用到该参数 """ voc_data_dir = 'D:/jinxData/voctest/Annotations' json_save_path = 'D:/jinxData/voc/voc2coco/train.json' split = 'train' parseXmlFilse(data_dir=voc_data_dir, json_save_path=json_save_path, split=split)
将annotations目录下的所有xml标注文件按coco格式写入了json文件中。
2.2 VOC2YOLO
import osimport jsonimport shutilfrom lxml import etreefrom tqdm import tqdmcategory_set = set()image_set = set()bbox_nums = 0class VOC2YOLO: def __init__(self): self.original_datasets = 'voc' self.to_datasets = 'yolo' def parse_xml_to_dict(self, xml): """ 将xml文件解析成字典形式,参考tensorflow的recursive_parse_xml_to_dict Args: xml: xml tree obtained by parsing XML file contents using lxml.etree Returns: Python dictionary holding XML contents. """ if len(xml) == 0: # 遍历到底层,直接返回tag对应的信息 return {xml.tag: xml.text} result = {} for child in xml: child_result = self.parse_xml_to_dict(child) # 递归遍历标签信息 if child.tag != 'object': result[child.tag] = child_result[child.tag] else: if child.tag not in result: # 因为object可能有多个,所以需要放入列表里 result[child.tag] = [] result[child.tag].append(child_result[child.tag]) return {xml.tag: result} def write_classIndices(self, category_set): class_indices = dict((k, v) for v, k in enumerate(category_set)) json_str = json.dumps(dict((val, key) for key, val in class_indices.items()), indent=4) with open('class_indices.json', 'w') as json_file: json_file.write(json_str) def xyxy2xywhn(self, bbox, size): bbox = list(map(float, bbox)) size = list(map(float, size)) xc = (bbox[0] + (bbox[2] - bbox[0]) / 2.) / size[0] yc = (bbox[1] + (bbox[3] - bbox[1]) / 2.) / size[1] wn = (bbox[2] - bbox[0]) / size[0] hn = (bbox[3] - bbox[1]) / size[1] return (xc, yc, wn, hn) def parser_info(self, info: dict, only_cat=True, class_indices=None): filename = info['annotation']['filename'] image_set.add(filename) objects = [] width = int(info['annotation']['size']['width']) height = int(info['annotation']['size']['height']) for obj in info['annotation']['object']: obj_name = obj['name'] category_set.add(obj_name) if only_cat: continue xmin = round(float(obj['bndbox']['xmin'])) ymin = round(float(obj['bndbox']['ymin'])) xmax = round(float(obj['bndbox']['xmax'])) ymax = round(float(obj['bndbox']['ymax'])) bbox = self.xyxy2xywhn((xmin, ymin, xmax, ymax), (width, height)) if class_indices is not None: obj_category = class_indices[obj_name] object = [obj_category, bbox] objects.append(object) return filename, objects def parseXmlFilse(self, voc_dir, save_dir): assert os.path.exists(voc_dir), "ERROR {} does not exists".format(voc_dir) if os.path.exists(save_dir): shutil.rmtree(save_dir) os.makedirs(save_dir) xml_files = [os.path.join(voc_dir, i) for i in os.listdir(voc_dir) if os.path.splitext(i)[-1] == '.xml'] for xml_file in xml_files: with open(xml_file) as fid: xml_str = fid.read() xml = etree.fromstring(xml_str) info_dict = self.parse_xml_to_dict(xml) self.parser_info(info_dict, only_cat=True) with open(save_dir + "/classes.txt", 'w') as classes_file: for cat in sorted(category_set): classes_file.write("{}\n".format(cat)) class_indices = dict((v, k) for k, v in enumerate(sorted(category_set))) xml_files = tqdm(xml_files) for xml_file in xml_files: with open(xml_file) as fid: xml_str = fid.read() xml = etree.fromstring(xml_str) info_dict = self.parse_xml_to_dict(xml) filename, objects = self.parser_info(info_dict, only_cat=False, class_indices=class_indices) if len(objects) != 0: global bbox_nums bbox_nums += len(objects) with open(save_dir + "/" + filename.split(".")[0] + ".txt", 'w') as f: for obj in objects: f.write( "{} {:.5f} {:.5f} {:.5f} {:.5f}\n".format(obj[0], obj[1][0], obj[1][1], obj[1][2], obj[1][3]))if __name__ == '__main__': voc2yolo = VOC2YOLO() voc_dir = 'D:/jinxData/voctest/Annotations' save_dir = 'D:/jinxData/voctest/convert' voc2yolo.parseXmlFilse(voc_dir, save_dir) print("image nums: {}".format(len(image_set))) print("category nums: {}".format(len(category_set))) print("bbox nums: {}".format(bbox_nums))
此处得到的是全部的标签信息,可根据如下代码进行train、val和test的比例划分:
import osimport randomdef voc_proportion_divide(xmlfilepath, txtsavepath, trainval_percent, train_percent): ''' vod数据集比例自定义划分 Args: xmlfilepath: xml文件的地址, xml一般存放在Annotations下,如'D:\jinx\Annatations' txtsavepath:地址选择自己数据下的ImageSets/Main,如'D:\jinx\ImageSets\Main' trainval_percent: 训练和验证集比例 train_percent: 训练集比例(如trainval_percent=0.8,train_percent=0.7表示0.7train、 0.1val、0.2test) ''' total_xml = os.listdir(xmlfilepath) if not os.path.exists(txtsavepath): os.makedirs(txtsavepath) num = len(total_xml) list_index = range(num) tv = int(num * trainval_percent) tr = int(tv * train_percent) trainval = random.sample(list_index, tv) train = random.sample(trainval, tr) file_trainval = open(txtsavepath + '/trainval.txt', 'w') file_test = open(txtsavepath + '/test.txt', 'w') file_train = open(txtsavepath + '/train.txt', 'w') file_val = open(txtsavepath + '/val.txt', 'w') for i in list_index: name = total_xml[i][:-4] + '\n' if i in trainval: file_trainval.write(name) if i in train: file_train.write(name) else: file_val.write(name) else: file_test.write(name) file_trainval.close() file_train.close() file_val.close() file_test.close()
三、YOLO
3.1 YOLO2CoCo
import argparseimport jsonimport osimport sysimport shutilfrom datetime import datetimeimport cv2coco = dict()coco['images'] = []coco['type'] = 'instances'coco['annotations'] = []coco['categories'] = []category_set = dict()image_set = set()image_id = 000000annotation_id = 0def addCatItem(category_dict): for k, v in category_dict.items(): category_item = dict() category_item['supercategory'] = 'none' category_item['id'] = int(k) category_item['name'] = v coco['categories'].append(category_item)def addImgItem(file_name, size): global image_id image_id += 1 image_item = dict() image_item['id'] = image_id image_item['file_name'] = file_name image_item['width'] = size[1] image_item['height'] = size[0] image_item['license'] = None image_item['flickr_url'] = None image_item['coco_url'] = None image_item['date_captured'] = str(datetime.today()) coco['images'].append(image_item) image_set.add(file_name) return image_iddef addAnnoItem(object_name, image_id, category_id, bbox): global annotation_id annotation_item = dict() annotation_item['segmentation'] = [] seg = [] # bbox[] is x,y,w,h # left_top seg.append(bbox[0]) seg.append(bbox[1]) # left_bottom seg.append(bbox[0]) seg.append(bbox[1] + bbox[3]) # right_bottom seg.append(bbox[0] + bbox[2]) seg.append(bbox[1] + bbox[3]) # right_top seg.append(bbox[0] + bbox[2]) seg.append(bbox[1]) annotation_item['segmentation'].append(seg) annotation_item['area'] = bbox[2] * bbox[3] annotation_item['iscrowd'] = 0 annotation_item['ignore'] = 0 annotation_item['image_id'] = image_id annotation_item['bbox'] = bbox annotation_item['category_id'] = category_id annotation_id += 1 annotation_item['id'] = annotation_id coco['annotations'].append(annotation_item)def xywhn2xywh(bbox, size): bbox = list(map(float, bbox)) size = list(map(float, size)) xmin = (bbox[0] - bbox[2] / 2.) * size[1] ymin = (bbox[1] - bbox[3] / 2.) * size[0] w = bbox[2] * size[1] h = bbox[3] * size[0] box = (xmin, ymin, w, h) return list(map(int, box))def parseXmlFilse(image_path, anno_path, save_path, json_name='train.json'): assert os.path.exists(image_path), "ERROR {} dose not exists".format(image_path) assert os.path.exists(anno_path), "ERROR {} dose not exists".format(anno_path) if os.path.exists(save_path): shutil.rmtree(save_path) os.makedirs(save_path) json_path = os.path.join(save_path, json_name) category_set = [] with open(anno_path + '/classes.txt', 'r') as f: for i in f.readlines(): category_set.append(i.strip()) category_id = dict((k, v) for k, v in enumerate(category_set)) addCatItem(category_id) images = [os.path.join(image_path, i) for i in os.listdir(image_path)] files = [os.path.join(anno_path, i) for i in os.listdir(anno_path)] images_index = dict((v.split(os.sep)[-1][:-4], k) for k, v in enumerate(images)) for file in files: if os.path.splitext(file)[-1] != '.txt' or 'classes' in file.split(os.sep)[-1]: continue if file.split(os.sep)[-1][:-4] in images_index: index = images_index[file.split(os.sep)[-1][:-4]] img = cv2.imread(images[index]) shape = img.shape filename = images[index].split(os.sep)[-1] current_image_id = addImgItem(filename, shape) else: continue with open(file, 'r') as fid: for i in fid.readlines(): i = i.strip().split() category = int(i[0]) category_name = category_id[category] bbox = xywhn2xywh((i[1], i[2], i[3], i[4]), shape) addAnnoItem(category_name, current_image_id, category, bbox) json.dump(coco, open(json_path, 'w')) print("class nums:{}".format(len(coco['categories']))) print("image nums:{}".format(len(coco['images']))) print("bbox nums:{}".format(len(coco['annotations'])))if __name__ == '__main__': """ 脚本说明: 本脚本用于将yolo格式的标注文件.txt转换为coco格式的标注文件.json 参数说明: anno_path:标注文件txt存储路径 save_path:json文件输出的文件夹 image_path:图片路径 json_name:json文件名字 """ anno_path = 'D:/jinxData/TT100K45/labels/test' save_path = 'D:/jinxData/YOLO/yolo2coco/test' image_path = 'D:/jinxData/TT100K45/images/test' json_name = 'train.json' parseXmlFilse(image_path, anno_path, save_path, json_name)
train和val同理。
3.2 YOLO2VOC
import argparseimport osimport sysimport shutilimport cv2from lxml import etree, objectify# 将标签信息写入xmlfrom tqdm import tqdmimages_nums = 0category_nums = 0bbox_nums = 0def save_anno_to_xml(filename, size, objs, save_path): E = objectify.ElementMaker(annotate=False) anno_tree = E.annotation( E.folder("DATA"), E.filename(filename), E.source( E.database("The VOC Database"), E.annotation("PASCAL VOC"), E.image("flickr") ), E.size( E.width(size[1]), E.height(size[0]), E.depth(size[2]) ), E.segmented(0) ) for obj in objs: E2 = objectify.ElementMaker(annotate=False) anno_tree2 = E2.object( E.name(obj[0]), E.pose("Unspecified"), E.truncated(0), E.difficult(0), E.bndbox( E.xmin(obj[1][0]), E.ymin(obj[1][1]), E.xmax(obj[1][2]), E.ymax(obj[1][3]) ) ) anno_tree.append(anno_tree2) anno_path = os.path.join(save_path, filename[:-3] + "xml") etree.ElementTree(anno_tree).write(anno_path, pretty_print=True)def xywhn2xyxy(bbox, size): bbox = list(map(float, bbox)) size = list(map(float, size)) xmin = (bbox[0] - bbox[2] / 2.) * size[1] ymin = (bbox[1] - bbox[3] / 2.) * size[0] xmax = (bbox[0] + bbox[2] / 2.) * size[1] ymax = (bbox[1] + bbox[3] / 2.) * size[0] box = [xmin, ymin, xmax, ymax] return list(map(int, box))def parseXmlFilse(image_path, anno_path, save_path): global images_nums, category_nums, bbox_nums assert os.path.exists(image_path), "ERROR {} dose not exists".format(image_path) assert os.path.exists(anno_path), "ERROR {} dose not exists".format(anno_path) if os.path.exists(save_path): shutil.rmtree(save_path) os.makedirs(save_path) category_set = [] with open(anno_path + '/classes.txt', 'r') as f: for i in f.readlines(): category_set.append(i.strip()) category_nums = len(category_set) category_id = dict((k, v) for k, v in enumerate(category_set)) images = [os.path.join(image_path, i) for i in os.listdir(image_path)] files = [os.path.join(anno_path, i) for i in os.listdir(anno_path)] images_index = dict((v.split(os.sep)[-1][:-4], k) for k, v in enumerate(images)) images_nums = len(images) for file in tqdm(files): if os.path.splitext(file)[-1] != '.txt' or 'classes' in file.split(os.sep)[-1]: continue if file.split(os.sep)[-1][:-4] in images_index: index = images_index[file.split(os.sep)[-1][:-4]] img = cv2.imread(images[index]) shape = img.shape filename = images[index].split(os.sep)[-1] else: continue objects = [] with open(file, 'r') as fid: for i in fid.readlines(): i = i.strip().split() category = int(i[0]) category_name = category_id[category] bbox = xywhn2xyxy((i[1], i[2], i[3], i[4]), shape) obj = [category_name, bbox] objects.append(obj) bbox_nums += len(objects) save_anno_to_xml(filename, shape, objects, save_path)if __name__ == '__main__': """ 脚本说明: 本脚本用于将yolo格式的标注文件.txt转换为voc格式的标注文件.xml 参数说明: anno_path:标注文件txt存储路径 save_path:json文件输出的文件夹 image_path:图片路径 """ anno_path = 'D:/jinxData/TT100K45/labels/test' save_path = 'D:/jinxData/YOLO/yolo2voc/test' image_path = 'D:/jinxData/TT100K45/images/test' parseXmlFilse(image_path, anno_path, save_path) print("image nums: {}".format(images_nums)) print("category nums: {}".format(category_nums)) print("bbox nums: {}".format(bbox_nums))
train、val和test分别执行一次即可。
以上代码参考自博文数据转换。
四、TT100K
4.1 TT100K2YOLO
import osimport jsonfrom random import randomimport cv2import shutilimport jsonimport xml.dom.minidomfrom tqdm import tqdmimport argparseclass TT100K2COCO: def __init__(self): self.original_datasets = 'tt100k' self.to_datasets = 'coco' def class_statistics(self): # os.makedirs('annotations', exist_ok=True) # 存放数据的父路径 parent_path = 'D:/jinxData/TT100K/data' # 读TT100K原始数据集标注文件 with open(os.path.join(parent_path, 'annotations.json')) as origin_json: origin_dict = json.load(origin_json) classes = origin_dict['types'] # 建立统计每个类别包含的图片的字典 sta = {} for i in classes: sta[i] = [] images_dic = origin_dict['imgs'] # 记录所有保留的图片 saved_images = [] # 遍历TT100K的imgs for image_id in images_dic: image_element = images_dic[image_id] image_path = image_element['path'] # 添加图像的信息到dataset中 image_path = image_path.split('/')[-1] obj_list = image_element['objects'] # 遍历每张图片的标注信息 for anno_dic in obj_list: label_key = anno_dic['category'] # 防止一个图片多次加入一个标签类别 if image_path not in sta[label_key]: sta[label_key].append(image_path) # 只保留包含图片数超过100的类别 result = {k: v for k, v in sta.items() if len(v) >= 100} for i in result: print("the type of {} includes {} images".format(i, len(result[i]))) saved_images.extend(result[i]) saved_images = list(set(saved_images)) print("total types is {}".format(len(result))) type_list = list(result.keys()) result = {"type": type_list, "details": result, "images": saved_images} print(type_list) # 保存结果 json_name = os.path.join(parent_path, 'statistics.json') with open(json_name, 'w', encoding="utf-8") as f: json.dump(result, f, ensure_ascii=False, indent=1) def original_datasets2object_datasets_re(self): ''' 重新划分数据集 :return: ''' # os.makedirs('annotations2', exist_ok=True) # 存放数据的父路径 parent_path = 'D:/jinxData/TT100K/data' # 读TT100K原始数据集标注文件 with open(os.path.join(parent_path, 'annotations.json')) as origin_json: origin_dict = json.load(origin_json) with open(os.path.join(parent_path, 'statistics.json')) as select_json: select_dict = json.load(select_json) classes = select_dict['type'] train_dataset = {'info': {}, 'licenses': [], 'categories': [], 'images': [], 'annotations': []} val_dataset = {'info': {}, 'licenses': [], 'categories': [], 'images': [], 'annotations': []} test_dataset = {'info': {}, 'licenses': [], 'categories': [], 'images': [], 'annotations': []} label = {} # 记录每个标志类别的id count = {} # 记录每个类别的图片数 owntype_sum = {} info = { "year": 2021, # 年份 "version": '1.0', # 版本 "description": "TT100k_to_coco", # 数据集描述 "contributor": "Tecent&Tsinghua", # 提供者 "url": 'https://cg.cs.tsinghua.edu.cn/traffic-sign/', # 下载地址 "date_created": 2021 - 1 - 15 } licenses = { "id": 1, "name": "null", "url": "null", } train_dataset['info'] = info val_dataset['info'] = info test_dataset['info'] = info train_dataset['licenses'] = licenses val_dataset['licenses'] = licenses test_dataset['licenses'] = licenses # 建立类别和id的关系 for i, cls in enumerate(classes): train_dataset['categories'].append({'id': i, 'name': cls, 'supercategory': 'traffic_sign'}) val_dataset['categories'].append({'id': i, 'name': cls, 'supercategory': 'traffic_sign'}) test_dataset['categories'].append({'id': i, 'name': cls, 'supercategory': 'traffic_sign'}) label[cls] = i count[cls] = 0 owntype_sum[cls] = 0 images_dic = origin_dict['imgs'] obj_id = 1 # 计算出每个类别共‘包含’的图片数 for image_id in images_dic: image_element = images_dic[image_id] image_path = image_element['path'] image_name = image_path.split('/')[-1] # 在所选的类别图片中 if image_name not in select_dict['images']: continue # 处理TT100K中的标注信息 obj_list = image_element['objects'] # 记录图片中包含最多的实例所属的type includes_type = {} for anno_dic in obj_list: if anno_dic["category"] not in select_dict["type"]: continue # print(anno_dic["category"]) if anno_dic["category"] in includes_type: includes_type[anno_dic["category"]] += 1 else: includes_type[anno_dic["category"]] = 1 # print(includes_type) own_type = max(includes_type, key=includes_type.get) owntype_sum[own_type] += 1 # TT100K的annotation转换成coco的 for image_id in images_dic: image_element = images_dic[image_id] image_path = image_element['path'] image_name = image_path.split('/')[-1] # 在所选的类别图片中 if image_name not in select_dict['images']: continue print("dealing with {} image".format(image_path)) # shutil.copy(os.path.join(parent_path,image_path),os.path.join(parent_path,"dataset/JPEGImages")) # 处理TT100K中的标注信息 obj_list = image_element['objects'] # 记录图片中包含最多的实例所属的type includes_type = {} for anno_dic in obj_list: if anno_dic["category"] not in select_dict["type"]: continue # print(anno_dic["category"]) if anno_dic["category"] in includes_type: includes_type[anno_dic["category"]] += 1 else: includes_type[anno_dic["category"]] = 1 # print(includes_type) own_type = max(includes_type, key=includes_type.get) count[own_type] += 1 num_rate = count[own_type] / owntype_sum[own_type] # 切换dataset的引用对象,从而划分数据集根据每个类别类别的总数量按7:2:1分为了train_set,val_set,test_set。 # 其中每个图片所属类别根据该图片包含的类别的数量决定(归属为含有类别最多的类别) if num_rate < 0.7: dataset = train_dataset elif num_rate < 0.9: dataset = val_dataset else: print("dataset=test_dataset") dataset = test_dataset for anno_dic in obj_list: if anno_dic["category"] not in select_dict["type"]: continue x = anno_dic['bbox']['xmin'] y = anno_dic['bbox']['ymin'] width = anno_dic['bbox']['xmax'] - anno_dic['bbox']['xmin'] height = anno_dic['bbox']['ymax'] - anno_dic['bbox']['ymin'] label_key = anno_dic['category'] dataset['annotations'].append({ 'area': width * height, 'bbox': [x, y, width, height], 'category_id': label[label_key], 'id': obj_id, 'image_id': image_id, 'iscrowd': 0, # mask, 矩形是从左上角点按顺时针的四个顶点 'segmentation': [[x, y, x + width, y, x + width, y + height, x, y + height]] }) # 每个标注的对象id唯一 obj_id += 1 # 用opencv读取图片,得到图像的宽和高 im = cv2.imread(os.path.join(parent_path, image_path)) # print(image_path) H, W, _ = im.shape # 添加图像的信息到dataset中 dataset['images'].append({'file_name': image_name, 'id': image_id, 'width': W, 'height': H}) # 保存结果 for phase in ['train', 'val', 'test']: json_name = os.path.join(parent_path, 'dataset/annotations/{}.json'.format(phase)) json_name = json_name.replace('\\', '/') with open(json_name, 'w', encoding="utf-8") as f: if phase == 'train': json.dump(train_dataset, f, ensure_ascii=False, indent=1) if phase == 'val': json.dump(val_dataset, f, ensure_ascii=False, indent=1) if phase == 'test': json.dump(test_dataset, f, ensure_ascii=False, indent=1) def divide_TrainValTest(self, source, target): ''' 创建文件路径 :param source: 源文件位置 :param target: 目标文件位置 ''' # for i in ['train', 'val', 'test']: # path = target + '/' + i # if not os.path.exists(path): # os.makedirs(path) # 遍历目录下的文件名,复制对应的图片到指定目录 for root, dirs, files in os.walk(source): for file in files: file_name = os.path.splitext(file)[0] if file_name == 'train' or file_name == 'val' or file_name =='test' or file_name =='classes': continue image_path = os.path.join(file_name + '.jpg') # print(image_path) if 'train' in source: shutil.copyfile('D:/jinxData/TT100K/data/image_reparation/' + image_path, target + '/train/' + image_path) elif 'val' in source: shutil.copyfile('D:/jinxData/TT100K/data/image_reparation/' + image_path, target + '/val/' + image_path) elif 'test' in source: shutil.copyfile('D:/jinxData/TT100K/data/image_reparation/' + image_path, target + '/test/' + image_path)if __name__ == '__main__': tt100k = TT100K2COCO() # tt100k.class_statistics() # tt100k.original_datasets2object_datasets_re() # tt100k.coco_json2yolo_txt('train') # tt100k.coco_json2yolo_txt('val') # tt100k.coco_json2yolo_txt('test') tt100k.divide_TrainValTest('D:/jinxData/TT100K/data/dataset/annotations/train', 'D:/jinxData/TT100K/data') tt100k.divide_TrainValTest('D:/jinxData/TT100K/data/dataset/annotations/val', 'D:/jinxData/TT100K/data') tt100k.divide_TrainValTest('D:/jinxData/TT100K/data/dataset/annotations/test', 'D:/jinxData/TT100K/data')