⭐️ ⭐️ ⭐️ 还在完善中
⭐️ ⭐️ ⭐️
本节主要介绍在目标检测领域内,常用的格式转换脚本
json格式的目标检测数据集标签格式转yolo目标检测的标签txt的格式
代码如下(示例): 主要修改 classes, json_folder_path, output_dir
"""
目标检测的 json --> 转为 yolo的txt
"""
import json
import os
def convert(size, box):
dw = 1. / size[0]
dh = 1. / size[1]
x = (box[0] + box[2]) / 2.0
y = (box[1] + box[3]) / 2.0
w = box[2] - box[0]
h = box[3] - box[1]
x = x * dw
w = w * dw
y = y * dh
h = h * dh
return (x, y, w, h)
def decode_json(json_path, output_dir, classes):
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
base_name = os.path.splitext(os.path.basename(data['imagePath']))[0]
txt_path = os.path.join(output_dir, base_name + '.txt')
with open(txt_path, 'w', encoding='utf-8') as txt_file:
for shape in data['shapes']:
if shape['shape_type'] == 'rectangle':
label = shape['label']
if label not in classes:
continue
cls_id = classes.index(label)
points = shape['points']
x1, y1 = points[0]
x2, y2 = points[1] # Assuming the points are diagonal
bb = convert((data['imageWidth'], data['imageHeight']), [x1, y1, x2, y2])
txt_file.write(f"{cls_id} {' '.join(map(str, bb))}\n")
if __name__ == "__main__":
# 指定YOLO类别
classes = ['loose', 'un-loose'] # 根据实际类别名称进行修改
# JSON格式的标签文件路径
json_folder_path = './json' # 替换为实际的JSON文件夹路径
# 转换为YOLO格式的TXT标签文件存储路径
output_dir = './txt' # 替换为实际的TXT保存路径
if not os.path.exists(output_dir):
os.makedirs(output_dir)
json_files = [file for file in os.listdir(json_folder_path) if file.endswith('.json')]
for json_file in json_files:
json_path = os.path.join(json_folder_path, json_file)
decode_json(json_path, output_dir, classes)
# 将类别名称写入classes.txt文件
with open(os.path.join(output_dir, 'classes.txt'), 'w', encoding='utf-8') as file:
for class_name in classes:
file.write(class_name + '\n')
print(f"Conversion completed. TXT files are saved in {output_dir}")
yolov8的关键点labelme打的标签json格式转可训练的txt格式
代码如下(示例):只需修改 class_list, keypoint_list, img_list
将labelme标注关键点数据集的 json文件转为 yolo 格式
采用 labelme进行标注
"""
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
import glob
import json
import tqdm
# 物体类别
class_list = ["fks"]
# 关键点的顺序
keypoint_list = ["P1", "P2", "P3", "P4"]
def json_to_yolo(img_data, json_data):
h, w = img_data.shape[:2]
# 步骤:
# 1. 找出所有的矩形,记录下矩形的坐标,以及对应group_id
# 2. 遍历所有的head和tail,记下点的坐标,以及对应group_id,加入到对应的矩形中
# 3. 转为yolo格式
rectangles = {}
# 遍历初始化
for shape in json_data["shapes"]:
label = shape["label"] # pen, head, tail
group_id = shape["group_id"] # 0, 1, 2, ...
points = shape["points"] # x,y coordinates
shape_type = shape["shape_type"]
# 只处理矩形
if shape_type == "rectangle":
if group_id not in rectangles:
rectangles[group_id] = {
"label": label,
"rect": points[0] + points[1], # Rectangle [x1, y1, x2, y2]
"keypoints_list": []
}
# 遍历更新,将点加入对应group_id的矩形中
for keypoint in keypoint_list:
for shape in json_data["shapes"]:
label = shape["label"]
group_id = shape["group_id"]
points = shape["points"]
# 如果匹配到了对应的keypoint
if label == keypoint:
rectangles[group_id]["keypoints_list"].append(points[0])
# 转为yolo格式
yolo_list = []
for id, rectangle in rectangles.items():
result_list = []
label_id = class_list.index(rectangle["label"])
# x1, y1, x2, y2
x1, y1, x2, y2 = rectangle["rect"]
# center_x, center_y, width, height
center_x = (x1+x2)/2
center_y = (y1+y2)/2
width = abs(x1-x2)
height = abs(y1-y2)
# normalize
center_x /= w
center_y /= h
width /= w
height /= h
# 保留6位小数
center_x = round(center_x, 6)
center_y = round(center_y, 6)
width = round(width, 6)
height = round(height, 6)
# 添加 label_id, center_x, center_y, width, height
result_list = [label_id, center_x, center_y, width, height]
# 添加 p1_x, p1_y, p1_v, p2_x, p2_y, p2_v
for point in rectangle["keypoints_list"]:
x, y = point
x, y = int(x), int(y)
# normalize
x /= w
y /= h
# 保留6位小数
x = round(x, 6)
y = round(y, 6)
result_list.extend([x, y, 2])
yolo_list.append(result_list)
return yolo_list
# 获取所有的图片./fks-pose/images/train/
img_list = glob.glob(r"C:\Users\Administrator\Desktop\images/*.jpg")
for img_path in tqdm.tqdm(img_list):
img = cv2.imdecode(np.fromfile(img_path, dtype=np.uint8), -1)
json_file = img_path.replace('jpg', 'json')
# 对中文路径下进行遍历
with open(json_file, 'r', encoding='utf-8') as json_file:
# with open(json_file) as json_file:
json_data = json.load(json_file)
yolo_list = json_to_yolo(img, json_data)
yolo_txt_path = img_path.replace('jpg', 'txt')
with open(yolo_txt_path, "w") as f:
for yolo in yolo_list:
for i in range(len(yolo)):
if i == 0:
f.write(str(yolo[i]))
else:
f.write(" " + str(yolo[i]))
f.write("\n")
json格式的目标检测数据集标签格式转yolo目标检测的标签txt的格式
coco数据集格式如下:
├── data
│ ├── coco
│ │ ├── annotations
│ │ │ ├── instances_train2017.json
│ │ │ ├── instances_val2017.json
│ │ ├── train2017
│ │ ├── val2017
│ │ ├── test2017
————————————————
- 我们需要对yolo的数据集的训练集(train)、验证集(val)、测试集(test)标签分别进行转换生成coco数据集的标签格式(图片相对是不变的)instances_train2017.json、 instances_val2017.json(这里不需要对应的test的标签)
- 在回顾说明一下需要转换的,和保持相对不变的
保持相对不变的:- linhuo/images/train的图片直接复制到train2017
- linhuo/images/val的图片直接复制到val2017
- linhuo/images/test的图片直接复制到test2017
- 需要改变的是:
- linhuo/labels/train的所有标签需要转换成 instances_train2017.json(coco格式)
- linhuo/labels/vla的所有标签需要转换成instances_val2017.json(coco格式)
————————————————
代码如下(示例):
"""
yolo标签:
yolo数据集的标注文件是.txt文件,在label文件夹中每一个.txt文件对应数据集中的一张图片
其中每个.txt文件中的每一行代表图片中的一个目标。
coco标签:
而coco数据集的标注文件是.json文件,全部的数据标注文件由三个.json文件组成:train.json val.json test.json,
其中每个.json文件中包含全部的数据集图片中的所有目标(注意是所有目标不是数据集中的所有张图片)
准备工作:
1. 在根目录下创建coco文件格式对应的文件夹
dataset_coco:
annotations
images
labels
classes.txt(每一行是自定义数据集中的一个类别)
YOLO 格式的数据集转化为 COCO 格式的数据集
--root_dir 输入根路径
--save_path 保存文件的名字(没有random_split时使用)
--random_split 有则会随机划分数据集,然后再分别保存为3个文件。
--split_by_file 按照 ./train.txt ./val.txt ./test.txt 来对数据集进行划分
运行方式:
python yolo2coco.py --root_dir ./dataset_coco --random_split
datasetcoco/images: 数据集所有图片
datasetcoco/labels: 数据集yolo标签的txt文件
classes.txt(每一行是自定义数据集中的一个类别)
"""
import os
import cv2
import json
from tqdm import tqdm
from sklearn.model_selection import train_test_split
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--root_dir', default='./data', type=str,
help="root path of images and labels, include ./images and ./labels and classes.txt")
parser.add_argument('--save_path', type=str, default='./train.json',
help="if not split the dataset, give a path to a json file")
parser.add_argument('--random_split', action='store_true', help="random split the dataset, default ratio is 8:1:1")
parser.add_argument('--split_by_file', action='store_true',
help="define how to split the dataset, include ./train.txt ./val.txt ./test.txt ")
arg = parser.parse_args()
def train_test_val_split_random(img_paths, ratio_train=0.8, ratio_test=0.1, ratio_val=0.1):
# 这里可以修改数据集划分的比例。
assert int(ratio_train + ratio_test + ratio_val) == 1
train_img, middle_img = train_test_split(img_paths, test_size=1 - ratio_train, random_state=233)
ratio = ratio_val / (1 - ratio_train)
val_img, test_img = train_test_split(middle_img, test_size=ratio, random_state=233)
print("NUMS of train:val:test = {}:{}:{}".format(len(train_img), len(val_img), len(test_img)))
return train_img, val_img, test_img
def train_test_val_split_by_files(img_paths, root_dir):
# 根据文件 train.txt, val.txt, test.txt(里面写的都是对应集合的图片名字) 来定义训练集、验证集和测试集
phases = ['train', 'val', 'test']
img_split = []
for p in phases:
define_path = os.path.join(root_dir, f'{p}.txt')
print(f'Read {p} dataset definition from {define_path}')
assert os.path.exists(define_path)
with open(define_path, 'r') as f:
img_paths = f.readlines()
# img_paths = [os.path.split(img_path.strip())[1] for img_path in img_paths] # NOTE 取消这句备注可以读取绝对地址。
img_split.append(img_paths)
return img_split[0], img_split[1], img_split[2]
def yolo2coco(arg):
root_path = arg.root_dir
print("Loading data from ", root_path)
assert os.path.exists(root_path)
originLabelsDir = os.path.join(root_path, 'labels')
originImagesDir = os.path.join(root_path, 'images')
with open(os.path.join(root_path, 'classes.txt')) as f:
classes = f.read().strip().split()
# images dir name
indexes = os.listdir(originImagesDir)
if arg.random_split or arg.split_by_file:
# 用于保存所有数据的图片信息和标注信息
train_dataset = {'categories': [], 'annotations': [], 'images': []}
val_dataset = {'categories': [], 'annotations': [], 'images': []}
test_dataset = {'categories': [], 'annotations': [], 'images': []}
# 建立类别标签和数字id的对应关系, 类别id从0开始。
for i, cls in enumerate(classes, 0):
train_dataset['categories'].append({'id': i, 'name': cls, 'supercategory': 'mark'})
val_dataset['categories'].append({'id': i, 'name': cls, 'supercategory': 'mark'})
test_dataset['categories'].append({'id': i, 'name': cls, 'supercategory': 'mark'})
if arg.random_split:
print("spliting mode: random split")
train_img, val_img, test_img = train_test_val_split_random(indexes, 0.8, 0.1, 0.1)
elif arg.split_by_file:
print("spliting mode: split by files")
train_img, val_img, test_img = train_test_val_split_by_files(indexes, root_path)
else:
dataset = {'categories': [], 'annotations': [], 'images': []}
for i, cls in enumerate(classes, 0):
dataset['categories'].append({'id': i, 'name': cls, 'supercategory': 'mark'})
# 标注的id
ann_id_cnt = 0
for k, index in enumerate(tqdm(indexes)):
# 支持 png jpg 格式的图片。
txtFile = index.replace('images', 'txt').replace('.jpg', '.txt').replace('.png', '.txt')
# 读取图像的宽和高
im = cv2.imread(os.path.join(root_path, 'images/') + index)
height, width, _ = im.shape
if arg.random_split or arg.split_by_file:
# 切换dataset的引用对象,从而划分数据集
if index in train_img:
dataset = train_dataset
elif index in val_img:
dataset = val_dataset
elif index in test_img:
dataset = test_dataset
# 添加图像的信息
dataset['images'].append({'file_name': index,
'id': k,
'width': width,
'height': height})
if not os.path.exists(os.path.join(originLabelsDir, txtFile)):
# 如没标签,跳过,只保留图片信息。
continue
with open(os.path.join(originLabelsDir, txtFile), 'r') as fr:
labelList = fr.readlines()
for label in labelList:
label = label.strip().split()
x = float(label[1])
y = float(label[2])
w = float(label[3])
h = float(label[4])
# convert x,y,w,h to x1,y1,x2,y2
H, W, _ = im.shape
x1 = (x - w / 2) * W
y1 = (y - h / 2) * H
x2 = (x + w / 2) * W
y2 = (y + h / 2) * H
# 标签序号从0开始计算, coco2017数据集标号混乱,不管它了。
cls_id = int(label[0])
width = max(0, x2 - x1)
height = max(0, y2 - y1)
dataset['annotations'].append({
'area': width * height,
'bbox': [x1, y1, width, height],
'category_id': cls_id,
'id': ann_id_cnt,
'image_id': k,
'iscrowd': 0,
# mask, 矩形是从左上角点按顺时针的四个顶点
'segmentation': [[x1, y1, x2, y1, x2, y2, x1, y2]]
})
ann_id_cnt += 1
# 保存结果
folder = os.path.join(root_path, 'annotations')
if not os.path.exists(folder):
os.makedirs(folder)
if arg.random_split or arg.split_by_file:
for phase in ['train', 'val', 'test']:
json_name = os.path.join(root_path, 'annotations/{}.json'.format(phase))
with open(json_name, 'w') as f:
if phase == 'train':
json.dump(train_dataset, f)
elif phase == 'val':
json.dump(val_dataset, f)
elif phase == 'test':
json.dump(test_dataset, f)
print('Save annotation to {}'.format(json_name))
else:
json_name = os.path.join(root_path, 'annotations/{}'.format(arg.save_path))
with open(json_name, 'w') as f:
json.dump(dataset, f)
print('Save annotation to {}'.format(json_name))
if __name__ == "__main__":
yolo2coco(arg)
编写类函数,xml格式转yolo数据集标签的txt格式
代码如下(示例):
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
import glob
classes = ["person"]
def convert(size, box):
dw = 1.0 / size[0]
dh = 1.0 / 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_name):
in_file = open(r'C:\Users\Administrator\Desktop\person\labels\val-voc/' + image_name[:-3] + 'xml') # xml文件路径
out_file = open(r'C:\Users\Administrator\Desktop\person\labels\val/' + image_name[:-3] + 'txt', 'w') # 转换后的txt文件存放路径
f = open(r'C:\Users\Administrator\Desktop\person\labels\val-voc/' + image_name[:-3] + 'xml')
xml_text = f.read()
root = ET.fromstring(xml_text)
f.close()
size = root.find('size')
w = int(size.find('width').text)
h = int(size.find('height').text)
for obj in root.iter('object'):
cls = obj.find('name').text
if cls not in classes:
print(cls)
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()
if __name__ == '__main__':
for image_path in glob.glob(r"C:\Users\Administrator\Desktop\person\images\val/*.jpg"): # 每一张图片都对应一个xml文件这里写xml对应的图片的路径
image_name = image_path.split('\\')[-1]
convert_annotation(image_name)
编写类函数,根据yolo的目标检测训练的最好权重推理图片,并绘制对应的目标检测框
代码如下(示例):需要在yolov5-master文件夹下新建立一个脚本文件
path=‘./weight/fks.pt’ 训练得到的最好的权重
img_path = r’./test/V15R.jpg’ 需要检测的图片
oil_list = pd[pd[‘name’] == ‘fks’].to_numpy(),中的fks为需要检测的类别
import torch
import cv2
import matplotlib.pyplot as plt
import numpy as np
class yolo_demo:
def __init__(self):
# 加载目标检测模型
self.yolo_detector = torch.hub.load('./', 'custom',
path='./weight/fks.pt',
source='local')
# 设置一下置信度阈值
self.yolo_detector.conf = 0.5
def main(self):
img_path = r'./test/V15R.jpg'
img = cv2.imdecode(np.fromfile(img_path, dtype=np.uint8), -1)
# bgr 转为 rgb
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# Inference
results = self.yolo_detector(img_rgb)
# 推理结果pd
pd = results.pandas().xyxy[0]
# 筛选所需要的目标类别
oil_list = pd[pd['name'] == 'fks'].to_numpy()
# 遍历筛选的类别, 包含坐标前四个
for person in oil_list:
l, t, r, b = person[:4].astype('int')
conf, id_index, id_label = person[4:]
# 进行绘制边界框
cv2.rectangle(img, (l, t), (r, b), (0, 255, 0), 1)
# cv2.putText(img, str(round(conf, 2)), (l, t-10), cv2.FONT_HERSHEY_PLAIN, 1.5, (0,255,0),1)
#cv2.putText(img, f'{id_label} {conf:.2f}', (l, t - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 1)
cv2.imwrite('./dete.jpg', img)
plate_demo = yolo_demo()
plate_demo.main()
编写类函数,根据yolo的目标检测训练的最好权重推理图片,并绘制对应的目标检测框
代码如下(示例):
image_folder = ‘./train/img’ 图片路径
label_folder = ‘./train/labels’ 标签路径
output_folder = ‘./train/output’ 输出路径
""""
根据标签的txt文件提取某一个特征类别的标签, 并在原图上进行绘制
"""
import cv2
import numpy as np
import os
import glob
# 图片和标签文件夹路径
image_folder = './train/img'
label_folder = './train/labels'
output_folder = './train/output'
# 创建输出文件夹(如果不存在)
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# 获取图片文件夹中的所有文件名
image_files = glob.glob(os.path.join(image_folder, '*.jpg'))
for image_path in image_files:
# 获取不带后缀的文件名
base_name = os.path.splitext(os.path.basename(image_path))[0]
# 构造标签文件路径
label_path = os.path.join(label_folder, base_name + '.txt')
# 检查标签文件是否存在
if not os.path.exists(label_path):
print(f'Label file for {image_path} not found, skipping.')
continue
# 加载图片,处理中文路径
image = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), -1)
with open(label_path, 'r', encoding='utf-8') as file:
lines = file.readlines()
height, width, _ = image.shape
# 解析标签文件并绘制边界框
for line in lines:
class_id, x_center, y_center, w, h = map(float, line.strip().split())
# 仅在标签ID等于2或6时绘制
if class_id in [0]:
# YOLO格式中的坐标是相对于图片大小的比例,需要转换为实际像素坐标
x_center = int(x_center * width)
y_center = int(y_center * height)
w = int(w * width)
h = int(h * height)
# 计算边界框的左上角和右下角坐标
x1 = int(x_center - w / 2)
y1 = int(y_center - h / 2)
x2 = int(x_center + w / 2)
y2 = int(y_center + h / 2)
# 在图片上绘制矩形框
cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 构造输出图片路径
output_image_path = os.path.join(output_folder, base_name + '_labeled.jpg')
# 保存带有标签的图片,处理中文路径
cv2.imencode('.jpg', image)[1].tofile(output_image_path)
print(f'Processed {os.path.basename(image_path)}, saved labeled image to {output_image_path}')
# 图片和标签文件夹路径
image_folder = './train/img'
label_folder = './train/labels'
output_folder = './train/output'""""
根据标签的txt文件提取某一个特征类别的标签, 并在原图上进行绘制
"""
import cv2
import numpy as np
import os
import glob
# 图片和标签文件夹路径
image_folder = './train/img'
label_folder = './train/labels'
output_folder = './train/output'
# 创建输出文件夹(如果不存在)
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# 获取图片文件夹中的所有文件名
image_files = glob.glob(os.path.join(image_folder, '*.jpg'))
for image_path in image_files:
# 获取不带后缀的文件名
base_name = os.path.splitext(os.path.basename(image_path))[0]
# 构造标签文件路径
label_path = os.path.join(label_folder, base_name + '.txt')
# 检查标签文件是否存在
if not os.path.exists(label_path):
print(f'Label file for {image_path} not found, skipping.')
continue
# 加载图片,处理中文路径
image = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), -1)
with open(label_path, 'r', encoding='utf-8') as file:
lines = file.readlines()
height, width, _ = image.shape
# 解析标签文件并绘制边界框
for line in lines:
class_id, x_center, y_center, w, h = map(float, line.strip().split())
# 仅在标签ID等于2或6时绘制
if class_id in [2, 6]:
# YOLO格式中的坐标是相对于图片大小的比例,需要转换为实际像素坐标
x_center = int(x_center * width)
y_center = int(y_center * height)
w = int(w * width)
h = int(h * height)
# 计算边界框的左上角和右下角坐标
x1 = int(x_center - w / 2)
y1 = int(y_center - h / 2)
x2 = int(x_center + w / 2)
y2 = int(y_center + h / 2)
# 在图片上绘制矩形框
cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 构造输出图片路径
output_image_path = os.path.join(output_folder, base_name + '_labeled.jpg')
# 保存带有标签的图片,处理中文路径
cv2.imencode('.jpg', image)[1].tofile(output_image_path)
print(f'Processed {os.path.basename(image_path)}, saved labeled image to {output_image_path}')