Python如何將LabelMe生成的JSON格式轉(zhuǎn)換成YOLOv8支持的TXT格式
標(biāo)注工具 LabelMe 生成的標(biāo)注文件為JSON格式,而YOLOv8中支持的為TXT文件格式。以下Python代碼實(shí)現(xiàn)3個(gè)功能:
1.將JSON格式轉(zhuǎn)換成TXT格式;
2.將數(shù)據(jù)集進(jìn)行隨機(jī)拆分,生成YOLOv8支持的目錄結(jié)構(gòu);
3.生成YOLOv8支持的YAML文件。
代碼test_labelme2yolov8.py如下:
import os import json import argparse import colorama import random import shutil def parse_args(): parser = argparse.ArgumentParser(description="json(LabelMe) to txt(YOLOv8)") parser.add_argument("--dir", required=True, type=str, help="images, json files, and generated txt files, all in the same directory") parser.add_argument("--labels", required=True, type=str, help="txt file that hold indexes and labels, one label per line, for example: face 0") parser.add_argument("--val_size", default=0.2, type=float, help="the proportion of the validation set to the overall dataset:[0., 0.5]") parser.add_argument("--name", required=True, type=str, help="the name of the dataset") args = parser.parse_args() return args def get_labels_index(name): labels = {} # key,value with open(name, "r") as file: for line in file: # print("line:", line) key_value = [] for v in line.split(" "): # print("v:", v) key_value.append(v.replace("\n", "")) # remove line breaks(\n) at the end of the line if len(key_value) != 2: print(colorama.Fore.RED + "Error: each line should have only two values(key value):", len(key_value)) continue labels[key_value[0]] = key_value[1] with open(name, "r") as file: line_num = len(file.readlines()) if line_num != len(labels): print(colorama.Fore.RED + "Error: there may be duplicate lables:", line_num, len(labels)) return labels def get_json_files(dir): jsons = [] for x in os.listdir(dir): if x.endswith(".json"): jsons.append(x) return jsons def parse_json(name): with open(name, "r") as file: data = json.load(file) width = data["imageWidth"] height = data["imageHeight"] # print(f"width: {width}; height: {height}") objects=[] for shape in data["shapes"]: if shape["shape_type"] != "rectangle": print(colorama.Fore.YELLOW + "Warning: only the rectangle type is supported:", shape["shape_type"]) continue object = [] object.append(shape["label"]) object.append(shape["points"]) objects.append(object) return width, height, objects def get_box_width_height(box): dist = lambda val: max(val) - min(val) x = [pt[0] for pt in box] y = [pt[1] for pt in box] return min(x), min(y), dist(x), dist(y) def bounding_box_normalization(width, height, objects, labels): boxes = [] for object in objects: box = [] # class x_center y_center width height box.append(labels[object[0]]) # print("point:", object[1]) x_min, y_min, box_w, box_h = get_box_width_height(object[1]) box.append(round((float(x_min + box_w / 2.0) / width), 6)) box.append(round((float(y_min + box_h / 2.0) / height), 6)) box.append(round(float(box_w / width), 6)) box.append(round(float(box_h / height), 6)) boxes.append(box) return boxes def write_to_txt(dir, json, width, height, objects, labels): boxes = bounding_box_normalization(width, height, objects, labels) # print("boxes:", boxes) name = json[:-len(".json")] + ".txt" # print("name:", name) with open(dir + "/" + name, "w") as file: for item in boxes: # print("item:", item) if len(item) != 5: print(colorama.Fore.RED + "Error: the length must be 5:", len(item)) continue string = item[0] + " " + str(item[1]) + " " + str(item[2]) + " " + str(item[3]) + " " + str(item[4]) + "\r" file.write(string) def json_to_txt(dir, jsons, labels): for json in jsons: name = dir + "/" + json # print("name:", name) width, height, objects = parse_json(name) # print(f"width: {width}; height: {height}; objects: {objects}") write_to_txt(dir, json, width, height, objects, labels) def is_in_range(value, a, b): return a <= value <= b def get_random_sequence(length, val_size): numbers = list(range(0, length)) val_sequence = random.sample(numbers, int(length*val_size)) # print("val_sequence:", val_sequence) train_sequence = [x for x in numbers if x not in val_sequence] # print("train_sequence:", train_sequence) return train_sequence, val_sequence def get_files_number(dir): count = 0 for file in os.listdir(dir): if os.path.isfile(os.path.join(dir, file)): count += 1 return count def split_train_val(dir, jsons, name, val_size): if is_in_range(val_size, 0., 0.5) is False: print(colorama.Fore.RED + "Error: the interval for val_size should be:[0., 0.5]:", val_size) raise dst_dir_images_train = "datasets/" + name + "/images/train" dst_dir_images_val = "datasets/" + name + "/images/val" dst_dir_labels_train = "datasets/" + name + "/labels/train" dst_dir_labels_val = "datasets/" + name + "/labels/val" try: os.makedirs(dst_dir_images_train) #, exist_ok=True os.makedirs(dst_dir_images_val) os.makedirs(dst_dir_labels_train) os.makedirs(dst_dir_labels_val) except OSError as e: print(colorama.Fore.RED + "Error: cannot create directory:", e.strerror) raise # supported image formats img_formats = (".bmp", ".jpeg", ".jpg", ".png", ".webp") # print("jsons:", jsons) train_sequence, val_sequence = get_random_sequence(len(jsons), val_size) for index in train_sequence: for format in img_formats: file = dir + "/" + jsons[index][:-len(".json")] + format # print("file:", file) if os.path.isfile(file): shutil.copy(file, dst_dir_images_train) break file = dir + "/" + jsons[index][:-len(".json")] + ".txt" if os.path.isfile(file): shutil.copy(file, dst_dir_labels_train) for index in val_sequence: for format in img_formats: file = dir + "/" + jsons[index][:-len(".json")] + format if os.path.isfile(file): shutil.copy(file, dst_dir_images_val) break file = dir + "/" + jsons[index][:-len(".json")] + ".txt" if os.path.isfile(file): shutil.copy(file, dst_dir_labels_val) num_images_train = get_files_number(dst_dir_images_train) num_images_val = get_files_number(dst_dir_images_val) num_labels_train = get_files_number(dst_dir_labels_train) num_labels_val = get_files_number(dst_dir_labels_val) if num_images_train + num_images_val != len(jsons) or num_labels_train + num_labels_val != len(jsons): print(colorama.Fore.RED + "Error: the number of files is inconsistent:", num_images_train, num_images_val, num_labels_train, num_labels_val, len(jsons)) raise def generate_yaml_file(labels, name): path = os.path.join("datasets", name, name+".yaml") # print("path:", path) with open(path, "w") as file: file.write("path: ../datasets/%s # dataset root dir\n" % name) file.write("train: images/train # train images (relative to 'path')\n") file.write("val: images/val # val images (relative to 'path')\n") file.write("test: # test images (optional)\n\n") file.write("# Classes\n") file.write("names:\n") for key, value in labels.items(): # print(f"key: {key}; value: {value}") file.write(" %d: %s\n" % (int(value), key)) if __name__ == "__main__": colorama.init() args = parse_args() # 1. parse JSON file and write it to a TXT file labels = get_labels_index(args.labels) # print("labels:", labels) jsons = get_json_files(args.dir) # print("jsons:", jsons) json_to_txt(args.dir, jsons, labels) # 2. split the dataset split_train_val(args.dir, jsons, args.name, args.val_size) # 3. generate a YAML file generate_yaml_file(labels, args.name) print(colorama.Fore.GREEN + "====== execution completed ======")
代碼有些多,主要函數(shù)說明如下:
1.函數(shù)parse_args:解析輸入?yún)?shù);
2.函數(shù)get_labels_index:解析labels文件,數(shù)據(jù)集中的所有類別及對(duì)應(yīng)的索引,格式labels.txt如下所示:生成YOLOv8的YAML文件時(shí)也需要此文件
face 0
hand 1
eye 2
mouth 3
horse 4
tree 5
bridge 6
house 7
3.函數(shù)get_json_files:獲取指定目錄下的所有json文件;
4.函數(shù)parse_json:解析json文件,將txt文件中需要的數(shù)據(jù)提取出來;
5.函數(shù)bounding_box_normalization:將bounding box值歸一化到(0,1)區(qū)間;
6.函數(shù)write_to_txt:將最終結(jié)果寫入txt文件;
7.函數(shù)split_train_val:將數(shù)據(jù)集隨機(jī)拆分為訓(xùn)練集和驗(yàn)證集,并按YOLOv8支持的目錄結(jié)構(gòu)存放,根目錄為datasets,接著是指定的數(shù)據(jù)集名,例如為fake,與YOLOv8中數(shù)據(jù)集coco8目錄結(jié)構(gòu)完全一致
8.函數(shù)generate_yaml_file:生成YOLOv8支持的yaml文件,存放在datasets/數(shù)據(jù)集名下,例如為fake.yaml
接收4個(gè)參數(shù):參數(shù)dir為存放數(shù)據(jù)集的目錄;參數(shù)labels指定labels文件;參數(shù)val_size指定驗(yàn)證集所占的比例;參數(shù)name指定新生成的YOLOv8數(shù)據(jù)集的名字
這里從網(wǎng)上隨機(jī)下載了10幅圖像,使用LabelMe進(jìn)行了標(biāo)注,執(zhí)行結(jié)果如下圖所示:
生成的fake.yaml文件如下圖所示:
path: ../datasets/fake # dataset root dir train: images/train # train images (relative to 'path') val: images/val # val images (relative to 'path') test: # test images (optional) # Classes names: 0: face 1: hand 2: eye 3: mouth 4: horse 5: tree 6: bridge 7: house
將生成的fake數(shù)據(jù)集進(jìn)行訓(xùn)練,測(cè)試代碼test_yolov8_detect.py如下:
import argparse import colorama from ultralytics import YOLO def parse_args(): parser = argparse.ArgumentParser(description="YOLOv8 object detect") parser.add_argument("--yaml", required=True, type=str, help="yaml file") parser.add_argument("--epochs", required=True, type=int, help="number of training") args = parser.parse_args() return args def train(yaml, epochs): model = YOLO("yolov8n.pt") # load a pretrained model results = model.train(data=yaml, epochs=epochs, imgsz=640) # train the model metrics = model.val() # It'll automatically evaluate the data you trained, no arguments needed, dataset and settings remembered model.export(format="onnx", dynamic=True) # export the model if __name__ == "__main__": colorama.init() args = parse_args() train(args.yaml, args.epochs) print(colorama.Fore.GREEN + "====== execution completed ======")
執(zhí)行結(jié)果如下圖所示:目前此測(cè)試代碼接收2個(gè)參數(shù):參數(shù)yaml指定yaml文件;參數(shù)epochs指定訓(xùn)練次數(shù);由以下結(jié)果可知,生成的新數(shù)據(jù)集無需做任何改動(dòng)即可進(jìn)行訓(xùn)練
GitHub:https://github.com/fengbingchun/NN_Test
到此這篇關(guān)于Python實(shí)現(xiàn)將LabelMe生成的JSON格式轉(zhuǎn)換成YOLOv8支持的TXT格式的文章就介紹到這了,更多相關(guān)Python JSON格式轉(zhuǎn)換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Python+Yolov5人臉口罩識(shí)別的詳細(xì)步驟
- python目標(biāo)檢測(cè)YoloV4當(dāng)中的Mosaic數(shù)據(jù)增強(qiáng)方法
- Python3.7 + Yolo3實(shí)現(xiàn)識(shí)別語音播報(bào)功能
- Python Flask搭建yolov3目標(biāo)檢測(cè)系統(tǒng)詳解流程
- opencv-python+yolov3實(shí)現(xiàn)目標(biāo)檢測(cè)
- 對(duì)YOLOv3模型調(diào)用時(shí)候的python接口詳解
- Python+樹莓派+YOLO打造一款人工智能照相機(jī)
- 使用python和yolo方法實(shí)現(xiàn)yolo標(biāo)簽自動(dòng)標(biāo)注
相關(guān)文章
Python字符串和正則表達(dá)式中的反斜杠(''\'')問題詳解
在本篇文章里小編給大家整理的是關(guān)于Python字符串和正則表達(dá)式中的反斜杠('\')問題以及相關(guān)知識(shí)點(diǎn),有需要的朋友們可以學(xué)習(xí)下。2019-09-09Python利用Nagios增加微信報(bào)警通知的功能
Nagios是一款開源的免費(fèi)網(wǎng)絡(luò)監(jiān)視工具,能有效監(jiān)控Windows、Linux和Unix的主機(jī)狀態(tài),交換機(jī)路由器等網(wǎng)絡(luò)設(shè)置,打印機(jī)等,本文給大家介紹Python利用Nagios增加微信報(bào)警通知的功能,需要的朋友參考下2016-02-02python創(chuàng)建exe文件的實(shí)現(xiàn)步驟
本文主要介紹了python創(chuàng)建exe文件的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-09-09在Windows中安裝Spire.XLS?for?Python的操作指南
Spire.XLS?for?Python?是一款專業(yè)的?Python?Excel?庫,可用于在各種?Python?應(yīng)用程序中讀取、創(chuàng)建、編輯和轉(zhuǎn)換?Excel?(.xls?&?.xlsx)?文件,本文將介紹如何在?Windows?中安裝?Spire.XLS?for?Python,需要的朋友可以參考下2025-02-02詳解python環(huán)境安裝selenium和手動(dòng)下載安裝selenium的方法
這篇文章主要介紹了詳解python環(huán)境安裝selenium和手動(dòng)下載安裝selenium的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03Python實(shí)現(xiàn)在圖像中隱藏二維碼的方法詳解
隱寫是一種類似于加密卻又不同于加密的技術(shù)。這篇文章主要介紹了如何利用Python語言實(shí)現(xiàn)在圖像中隱藏二維碼功能,感興趣的可以了解一下2022-09-09Python處理yaml和嵌套數(shù)據(jù)結(jié)構(gòu)技巧示例
這篇文章主要為大家介紹了Python處理yaml和嵌套數(shù)據(jù)結(jié)構(gòu)技巧示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06python實(shí)現(xiàn)簡(jiǎn)單的飛機(jī)大戰(zhàn)游戲
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)簡(jiǎn)單的飛機(jī)大戰(zhàn)游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05