参考文章:详细!正确!COCO数据集(.json)训练格式转换成YOLO格式(.txt)
YOLOv8官方转换代码:ultralytics/JSON2YOLO
COCO官方转换代码:cocodataset/cocoapi
COCO标签转YOLO:将下载的训练集标签instances_train2017.json和验证集标签instances_val2017.json转成YOLO格式。
转换的坑:
1.坐标转换:COCO坐标是左上+宽高。
2.iscrowd:一条标注信息中该key对应的值为1,则存在覆盖问题。如下图红色箭头所指,由于人过多,用一个大的框覆盖了所有人,因而未对所有目标进行详细标注。
3.不是所有图片都用于训练or验证:由第二条坑,存在覆盖问题的图片,应该不加入有效图片列表,这一点在image_info[image_id]['valid']中实现,save_yolo_labels默认获取了全部标签。在获取自己想要的类别数据时,则去除了含有覆盖问题的图片。
4.关于第3条坑,我的建议是重新复制保存自己所需图片,因为自己用公共数据集不需要所有类别图片,有效图片(存在目标类别,且不存在覆盖问题)作为正样本,无效的图(不存在目标类别)作为负样本。
转换代码:
import json
import os
from collections import defaultdict
def tlwh2xywhn(xywh, shape, precision=8):
"""左上+宽高 => 归一化的中心+宽高"""
x, y, w, h = xywh[:4]
x_center = round((x + w / 2.0) / shape[1], precision)
y_center = round((y + h / 2.0) / shape[0], precision)
box_width = round(w / shape[1], precision)
box_height = round(h / shape[0], precision)
return [x_center, y_center, box_width, box_height]
def coco2yolo_get_dict(coco_json_path):
"""
读取coco检测标签文件地址,将其转化成字典信息
:param coco_json_path: 标签地址
:return: 字典信息
"""
coco_data = json.loads(open(coco_json_path).read())
image_list = coco_data['images'] # 列表存放字典,需要用到的key{'file_name', 'height', 'width', 'id'}
annotations = coco_data['annotations'] # 列表存放字典,需要用到的key{'bbox': xywh, 'category_id', 'id'}
categories = coco_data['categories'] # 列表存放字典,需要用到的key{'supercategory', 'id', name}
print(f"INFO:读取到的图片总共有: [{len(image_list)}] 张,获取的标签条目总共有: [{len(annotations)}] 个。")
print(f"INFO:第一个图片条目:{image_list[0]}")
print(f"INFO:第一个标注条目:{annotations[0]}")
# name2index = {} # {类别名称:标签索引}
# index2name = {} # {标签索引:类别名称},用于生成标签文件
id2index = {} # {id:标签索引}
# index2id = {} # {标签索引:id}
for i, category in enumerate(categories): # 构建类别名和序号的映射关系
# name2index[category['name']] = i
# index2name[i] = category['name']
id2index[category['id']] = i
# index2id[i] = category['id']
image_info = defaultdict(dict) # 存储图片信息
# 先遍历图片,为所有图片建立一个字典条目,用于储存信息
for image in image_list:
image_id = image['id'] # coco_data['images']的'id'对应coco_data['annotations']的'image_id'
file_name = image['file_name']
shape = (image['height'], image['width']) # (高度,宽度)
image_info[image_id]['file_name'] = file_name # 一张图片的文件名
image_info[image_id]['shape'] = shape # 一张图片的形状
image_info[image_id]['yolo_data'] = [] # 一张图片的yolo格式数据
image_info[image_id]['yolo_label'] = set() # 一张图片还有的标签字典,用于筛选数据,放在自己的数据集中
image_info[image_id]['valid'] = True # 一张图是否有用,原数据有‘iscrowd’属性,表示覆盖,去除改类图片
# 然后遍历标注信息,因为一张图片可能有多个标注条目信息,所以需要用哈希映射到对应图片
for anno in annotations:
image_id = anno['image_id'] # 图片id
bbox_xywh = anno['bbox'] # 原始xywh,需要归一化
category_id = anno['category_id'] # 类别id,需要映射成标签索引
is_crowd = anno['iscrowd'] # 是否有类别覆盖
index = id2index[category_id] # 获取标签索引
bbox = tlwh2xywhn(xywh=bbox_xywh, shape=image_info[image_id]['shape']) # 获取归一化坐标
image_info[image_id]['yolo_data'].append([index] + bbox)
image_info[image_id]['yolo_label'].add(index)
image_info[image_id]['valid'] = image_info[image_id]['valid'] and not is_crowd
# 给标签排个序
for _, image in image_info.items():
image['yolo_data'].sort(key=lambda x: x[0]) # 按类别序号排序
print(f"INFO:提取数据成功,获取的第一条数据:{image_info[image_list[0]['id']]}")
return image_info
def save_yolo_labels(image_info, output_dir, image_root, txt_path):
"""
将字典信息里的yolo坐标保存到指定文件夹下
:param image_info: 字典信息
:param output_dir: 保存路径
:param image_root: 图片根路径
:param txt_path: 保存图片路径的txt的保存路径
:return:
"""
if not os.path.exists(output_dir):
os.makedirs(output_dir)
i = 0 # 标注框个数
file_list = [] # 文件列表
# 遍历每张图片的信息
for image_id, info in image_info.items():
file_name = info['file_name']
yolo_data = info['yolo_data']
txt_file_path = os.path.join(output_dir, f"{os.path.splitext(file_name)[0]}.txt") # 构建输出文件路径
file_list.append(os.path.join(image_root, file_name))
with open(txt_file_path, 'w') as f: # 写入YOLO格式数据到TXT文件
for data in yolo_data:
line = ' '.join(map(str, data)) # 将列表中的数据转换成字符串并写入文件
f.write(line + '\n')
i += 1
print(f"INFO:成功将YOLO标签保存到目录:{output_dir},共{i}个标注。")
# 生成包含所有图片路径的文件
with open(txt_path, 'w') as f:
for file_path in file_list:
f.write(file_path + '\n')
print(f"INFO:所有文件路径已保存到:{txt_path}")
def save_yolo_labels_valid(image_info, output_dir, image_root, txt_path, valid_label, label_map):
"""
只保留给定标签的图片。
:param image_info: 字典信息
:param output_dir: 保存路径
:param image_root: 图片根路径
:param txt_path: 保存图片路径的txt的保存路径
:param valid_label: 有效的标签列表
:param label_map: 标签映射成新的标签
:return:
"""
if not os.path.exists(output_dir):
os.makedirs(output_dir)
i = 0 # 标注框个数
file_list = [] # 文件列表
# 遍历每张图片的信息
for image_id, info in image_info.items():
file_name = info['file_name']
yolo_data = info['yolo_data']
yolo_label = info['yolo_label']
valid = info['valid']
if not valid or not set(valid_label) & yolo_label: # 有效标签和图片含有的标签交集为空,则跳过
continue
txt_file_path = os.path.join(output_dir, f"{os.path.splitext(file_name)[0]}.txt") # 构建输出文件路径
file_list.append(os.path.join(image_root, file_name))
valid_yolo_data = []
for data in yolo_data:
label = data[0]
if label in valid_label:
new_label = label_map[label] # 将标签映射成新的标签
valid_yolo_data.append([new_label] + data[1:])
with open(txt_file_path, 'w') as f: # 写入YOLO格式数据到TXT文件
for data in valid_yolo_data:
line = ' '.join(map(str, data)) # 将列表中的数据转换成字符串并写入文件
f.write(line + '\n')
i += 1
print(f"INFO:成功将YOLO标签保存到目录:{output_dir},共{i}个标注。")
# 生成包含所有图片路径的文件
with open(txt_path, 'w') as f:
for file_path in file_list:
f.write(file_path + '\n')
print(f"INFO:所有文件路径已保存到:{txt_path}")
def save_yolo_negative(image_info, image_root, txt_path, exclude_label):
"""
只保留不含exclude_label标签的图片
:param image_info: 字典信息
:param image_root: 图片根路径
:param txt_path: 保存图片路径的txt的保存路径
:param exclude_label: 需要排除的标签
:return:
"""
file_list = [] # 文件列表
# 遍历每张图片的信息
for image_id, info in image_info.items():
file_name = info['file_name']
yolo_label = info['yolo_label']
if set(exclude_label) & yolo_label: # 有效标签和图片含有的标签存在交集,则跳过
continue
file_list.append(os.path.join(image_root, file_name))
# 生成包含所有图片路径的文件
with open(txt_path, 'w') as f:
for file_path in file_list:
f.write(file_path + '\n')
print(f"INFO:所有文件路径已保存到:{txt_path}")
if __name__ == '__main__':
json_path = r'./COCO2017/annotations_trainval2017/instances_val2017.json'
image_info_all = coco2yolo_get_dict(json_path)
# ######################################## 处理所有标签 #######################################################
# save_path = r'./COCO2017/labels_det/val2017'
# txt_path = r'./COCO2017/val2017_det.txt'
# save_yolo_labels(image_info_all, save_path, './images/val2017', txt_path)
# ######################################## 获取目标标签 #######################################################
# save_path_kx = r'./COCO2017/det_box/labels/train2017'
# txt_path_kx = r'./COCO2017/train2017_det_kx.txt'
# valid_label_kx = {28}
# label_map_kx = {28: 1}
#
# save_yolo_labels_valid(image_info_all, save_path_kx, './images/train2017', txt_path_kx, valid_label_kx,
# label_map_kx)
# save_path_ren = r'./COCO2017/det_ren/labels/train2017'
# txt_path_ren = r'./COCO2017/train2017_det_ren.txt'
# valid_label_ren = {0}
# label_map_ren = {0: 0}
#
# save_yolo_labels_valid(image_info_all, save_path_ren, './images/train2017', txt_path_ren, valid_label_ren,
# label_map_ren)
# ######################################## 获取负样本图 #######################################################
# txt_path_neg = r'./COCO2017/val2017_det_neg.txt'
# save_yolo_negative(image_info_all, './images/val2017', txt_path_neg, exclude_label={0, 24, 26, 28})
转换结果:
只获取和人相关的图片:
只获取和箱子相关的图片,并设置箱子新的ID为1: