最近在进行目标检测和实例分割任务时,对无聊的标注工作十分苦恼。由于Labelme标注的数据格式是json格式,但YOLO使用的格式是txt格式,于是需要格式转换,但无奈转换工作太繁琐,而且txt格式无法直接被labelme软件使用,再加上YOLO目标检测和实例分割使用的txt标签方式不完全相同,于是下定决心实现格式互转。
通过这个方法,先用Labelme标注少量图片,再用YOLO训练得到一个初步的检测模型,然后把结果保存为txt,再将结果转化为Labelme可编辑的json格式,对标注进行微调即可,从而避免了大量重复标注带来的麻烦。
下面是具体代码:
一、Labelme标签转YOLO目标检测标签
import json
import os
def labelme2yolo_det(class_name, json_dir, labels_dir):
list_labels = [] # 存放json文件的列表
# 0.创建保存转换结果的文件夹
if (not os.path.exists(labels_dir)):
os.mkdir(labels_dir)
# 1.获取目录下所有的labelme标注好的Json文件,存入列表中
for files in os.listdir(json_dir): # 遍历json文件夹下的所有json文件
file = os.path.join(json_dir, files) # 获取一个json文件
list_labels.append(file) # 将json文件名加入到列表中
for labels in list_labels: # 遍历所有json文件
with open(labels, "r") as f:
file_in = json.load(f)
shapes = file_in["shapes"]
txt_filename = os.path.basename(labels).replace(".json", ".txt")
txt_path = os.path.join(labels_dir, txt_filename) # 使用labels_dir变量指定保存路径
with open(txt_path, "w+") as file_handle:
for shape in shapes:
line_content = [] # 初始化一个空列表来存储每个形状的坐标信息
line_content.append(str(class_name.index(shape['label']))) # 添加类别索引
[[x1, y1], [x2, y2]] = shape['points']
x1, x2 = x1 / file_in['imageWidth'], x2 / file_in['imageWidth']
y1, y2 = y1 / file_in['imageHeight'], y2 / file_in['imageHeight']
cx, cy = (x1 + x2) / 2, (y1 + y2) / 2 # 中心点归一化的x坐标和y坐标
wi, hi = abs(x2 - x1), abs(y2 - y1) # 归一化的目标框宽度w,高度h
line_content.append(str(cx))
line_content.append(str(cy))
line_content.append(str(wi))
line_content.append(str(hi))
# 使用空格连接列表中的所有元素,并写入文件
file_handle.write(" ".join(line_content) + "\n")
print("转换完成:", txt_filename)
二、Labelme标签转YOLO实例分割标签
import json
import os
def labelme2yolo_seg(class_name, json_dir, labels_dir):
"""
此函数用来将labelme软件标注好的json格式转换为yolov_seg中使用的txt格式
:param json_dir: labelme标注好的*.json文件所在文件夹
:param labels_dir: 转换好后的*.txt保存文件夹
:param class_name: 数据集中的类别标签
:return:
"""
list_labels = [] # 存放json文件的列表
# 0.创建保存转换结果的文件夹
if (not os.path.exists(labels_dir)):
os.mkdir(labels_dir)
# 1.获取目录下所有的labelme标注好的Json文件,存入列表中
for files in os.listdir(json_dir): # 遍历json文件夹下的所有json文件
file = os.path.join(json_dir, files) # 获取一个json文件
list_labels.append(file) # 将json文件名加入到列表中
for labels in list_labels: # 遍历所有json文件
with open(labels, "r") as f:
file_in = json.load(f)
shapes = file_in["shapes"]
print(labels)
txt_filename = os.path.basename(labels).replace(".json", ".txt")
txt_path = os.path.join(labels_dir, txt_filename) # 使用labels_dir变量指定保存路径
with open(txt_path, "w+") as file_handle:
for shape in shapes:
line_content = [] # 初始化一个空列表来存储每个形状的坐标信息
line_content.append(str(class_name.index(shape['label']))) # 添加类别索引
# 添加坐标信息
for point in shape["points"]:
x = point[0] / file_in["imageWidth"]
y = point[1] / file_in["imageHeight"]
line_content.append(str(x))
line_content.append(str(y))
# 使用空格连接列表中的所有元素,并写入文件
file_handle.write(" ".join(line_content) + "\n")
三、YOLO实例分割标签转Labelme标签
import os
import glob
import numpy as np
import cv2
import json
# 可以将yolov8实例分割生成的txt格式的标注转为json,可以使用labelme查看标注
# 该方法可以用于辅助数据标注
def convert_txt_to_labelme_json(txt_path, image_path, output_dir, class_name, image_fmt='.jpg' ):
"""
将文本文件转换为LabelMe格式的JSON文件。
此函数处理文本文件中的数据,将其转换成LabelMe标注工具使用的JSON格式。包括读取图像,
解析文本文件中的标注信息,并生成相应的JSON文件。
:param txt_path: 文本文件所在的路径
:param image_path: 图像文件所在的路径
:param output_dir: 输出JSON文件的目录
:param class_name: 类别名称列表,索引对应类别ID
:param image_fmt: 图像文件格式,默认为'.jpg'
:return:
"""
# 获取所有文本文件路径
txts = glob.glob(os.path.join(txt_path, "*.txt"))
for txt in txts:
# 初始化LabelMe JSON结构
labelme_json = {
'version': '5.5.0', # labelme版本号
'flags': {},
'shapes': [],
'imagePath': None,
'imageData': None,
'imageHeight': None,
'imageWidth': None,
}
# 获取文本文件名
txt_name = os.path.basename(txt)
# 根据文本文件名生成对应的图像文件名
image_name = txt_name.split(".")[0] + image_fmt
labelme_json['imagePath'] = image_name
# 构造完整图像路径
image_name = os.path.join(image_path, image_name)
# 检查图像文件是否存在,如果不存在则抛出异常
if not os.path.exists(image_name):
raise Exception('txt 文件={},找不到对应的图像={}'.format(txt, image_name))
# 读取图像
image = cv2.imdecode(np.fromfile(image_name, dtype=np.uint8), cv2.IMREAD_COLOR)
# 获取图像高度和宽度
h, w = image.shape[:2]
labelme_json['imageHeight'] = h
labelme_json['imageWidth'] = w
# 读取文本文件内容
with open(txt, 'r') as t:
lines = t.readlines()
for line in lines:
point_list = []
content = line.split(' ')
# 根据类别ID获取标签名称
label = class_name[int(content[0])] # 标签
# 解析点坐标
for index in range(1, len(content)):
if index % 2 == 1: # 下标为奇数,对应横坐标
x = (float(content[index])) * w
point_list.append(x)
else: # 下标为偶数,对应纵坐标
y = (float(content[index])) * h
point_list.append(y)
# 将点列表转换为二维列表,每两个值表示一个点
point_list = [point_list[i:i+2] for i in range(0, len(point_list), 2)]
# 构造shape字典
shape = {
'label': label,
'points': point_list,
'group_id': None,
'description': None,
'shape_type': 'polygon',
'flags': {},
'mask': None
}
labelme_json['shapes'].append(shape)
# 生成JSON文件名
json_name = txt_name.split('.')[0] + '.json'
json_name_path = os.path.join(output_dir, json_name)
# 写入JSON文件
fd = open(json_name_path, 'w')
json.dump(labelme_json, fd, indent=2)
fd.close()
# 输出保存信息
print("save json={}".format(json_name_path))
if __name__ == '__main__':
txt_path = r'../test_json'
image_path = r'../test_json'
output_dir = r'../test_json'
# 标签列表
class_name = ['box_top', 'tape', 'tape_defect', 'box_bottom'] # 标签类别名
convert_txt_to_labelme_json(txt_path, image_path, output_dir, class_name)
四、YOLO目标检测标签转Labelme标签
import os
import glob
import numpy as np
import cv2
import json
# 可以将yolov8目标检测生成的txt格式的标注转为json,可以使用labelme查看标注
# 该方法可以用于辅助数据标注
def convert_txt_to_labelme_json(txt_path, image_path, output_dir, class_name, image_fmt='.jpg' ):
"""
将文本文件转换为LabelMe格式的JSON文件。
此函数处理文本文件中的数据,将其转换成LabelMe标注工具使用的JSON格式。包括读取图像,
解析文本文件中的标注信息,并生成相应的JSON文件。
:param txt_path: 文本文件所在的路径
:param image_path: 图像文件所在的路径
:param output_dir: 输出JSON文件的目录
:param class_name: 类别名称列表,索引对应类别ID
:param image_fmt: 图像文件格式,默认为'.jpg'
:return:
"""
# 获取所有文本文件路径
txts = glob.glob(os.path.join(txt_path, "*.txt"))
for txt in txts:
# 初始化LabelMe JSON结构
labelme_json = {
'version': '5.5.0',
'flags': {},
'shapes': [],
'imagePath': None,
'imageData': None,
'imageHeight': None,
'imageWidth': None,
}
# 获取文本文件名
txt_name = os.path.basename(txt)
# 根据文本文件名生成对应的图像文件名
image_name = txt_name.split(".")[0] + image_fmt
labelme_json['imagePath'] = image_name
# 构造完整图像路径
image_name = os.path.join(image_path, image_name)
# 检查图像文件是否存在,如果不存在则抛出异常
if not os.path.exists(image_name):
raise Exception('txt 文件={},找不到对应的图像={}'.format(txt, image_name))
# 读取图像
image = cv2.imdecode(np.fromfile(image_name, dtype=np.uint8), cv2.IMREAD_COLOR)
# 获取图像高度和宽度
h, w = image.shape[:2]
labelme_json['imageHeight'] = h
labelme_json['imageWidth'] = w
# 读取文本文件内容
with open(txt, 'r') as t:
lines = t.readlines()
for line in lines:
point_list = []
content = line.split(' ')
# 根据类别ID获取标签名称
label = class_name[int(content[0])] # 标签
# 解析点坐标
for index in range(1, len(content)):
if index == 1: # 中心点归一化的x坐标
cx = float(content[index])
if index == 2: # 中心点归一化的y坐标
cy = float(content[index])
if index == 3: # 归一化的目标框宽度
wi = float(content[index])
if index == 4: # 归一化的目标框高度
hi = float(content[index])
x1 = (2 * cx * w - w * wi) / 2
x2 = (w * wi + 2 * cx * w) / 2
y1 = (2 * cy * h - h * hi) / 2
y2 = (h * hi + 2 * cy * h) / 2
point_list.append(x1)
point_list.append(y1)
point_list.append(x2)
point_list.append(y2)
# 将点列表转换为二维列表,每两个值表示一个点
point_list = [point_list[i:i+2] for i in range(0, len(point_list), 2)]
# 构造shape字典
shape = {
'label': label,
'points': point_list,
'group_id': None,
'description': None,
'shape_type': 'rectangle',
'flags': {},
'mask': None
}
labelme_json['shapes'].append(shape)
# 生成JSON文件名
json_name = txt_name.split('.')[0] + '.json'
json_name_path = os.path.join(output_dir, json_name)
# 写入JSON文件
fd = open(json_name_path, 'w')
json.dump(labelme_json, fd, indent=2)
fd.close()
# 输出保存信息
print("save json={}".format(json_name_path))
if __name__ == '__main__':
txt_path = r'../test_json'
image_path = r'../test_json'
output_dir = r'../test_json'
# 标签列表
class_name = ['logo', 'spec', 'anniversary_logo'] # 标签类别名
convert_txt_to_labelme_json(txt_path, image_path, output_dir, class_name)