这么简单的东西,但是莫名其妙搞了好几天,很抑郁,话不多说直接开始。
首先你得有YoLo格式的数据集,从最简单的开始,需要有图片数据和对应的yolo类型的txt标签数据。(下面截图中第一个文件夹是存放图片,命名规则用纯数字,后一个文件夹是txt文件,对应与每一张图片的检测标签)。
有了这两个文件夹之后,第一步,划分数据集。将图片数据集划分成8:2。所使用到的代码如下:
import os
import shutil
import random
from tqdm import tqdm
"""
标注文件是yolo格式(txt文件)
训练集:验证集 (8:2)
"""
def split_img(img_path, label_path, split_list):
try: # 创建数据集文件夹
Data = 'D:\datasets\small-object-detection-datasets\Solar_1103\ImageSets'
# os.mkdir(Data)
train_img_dir = Data + '/images/train'
val_img_dir = Data + '/images/val'
# test_img_dir = Data + '/images/test'
train_label_dir = Data + '/labels/train'
val_label_dir = Data + '/labels/val'
# test_label_dir = Data + '/labels/test'
# 创建文件夹
os.makedirs(train_img_dir)
os.makedirs(train_label_dir)
os.makedirs(val_img_dir)
os.makedirs(val_label_dir)
# os.makedirs(test_img_dir)
# os.makedirs(test_label_dir)
except:
print('文件目录已存在')
train, val = split_list
all_img = os.listdir(img_path)
all_img_path = [os.path.join(img_path, img) for img in all_img]
# all_label = os.listdir(label_path)
# all_label_path = [os.path.join(label_path, label) for label in all_label]
train_img = random.sample(all_img_path, int(train * len(all_img_path)))
train_img_copy = [os.path.join(train_img_dir, img.split('\\')[-1]) for img in train_img]
train_label = [toLabelPath(img, label_path) for img in train_img]
train_label_copy = [os.path.join(train_label_dir, label.split('\\')[-1]) for label in train_label]
for i in tqdm(range(len(train_img)), desc='train ', ncols=80, unit='img'):
_copy(train_img[i], train_img_dir)
_copy(train_label[i], train_label_dir)
all_img_path.remove(train_img[i])
val_img = all_img_path
val_label = [toLabelPath(img, label_path) for img in val_img]
for i in tqdm(range(len(val_img)), desc='val ', ncols=80, unit='img'):
_copy(val_img[i], val_img_dir)
_copy(val_label[i], val_label_dir)
def _copy(from_path, to_path):
shutil.copy(from_path, to_path)
def toLabelPath(img_path, label_path):
img = img_path.split('\\')[-1]
label = img.split('.tiff')[0] + '.txt'
return os.path.join(label_path, label)
if __name__ == '__main__':
#修改成自己的图片路径
img_path = 'D:\datasets\small-object-detection-datasets/aluminum_1103\JPEGImages'
#修改成自己的标签路径
label_path = 'D:\datasets\small-object-detection-datasets/aluminum_1103\labels'
split_list = [0.8, 0.2] # 数据集划分比例[train:val]
split_img(img_path, label_path, split_list)
ok,现在多了一个这个文件夹
里面的结构如下:
imageSets | |||
images | |||
train | 里面存放训练集的图片 | ||
val | 里面存放验证集的图片 | ||
labels | |||
train | 里面存放训练集的标签 | ||
val | 里面存放验证集的标签 |
接下来生成train.txt和val.txt(这两个文件是yolov5读取图片路径的文件),代码如下:
import os
#TODO:需要改动的地方就是三个路径地址,因为是要生成train.txt和val.txt所以要执行两次
# 指定B文件夹的路径
folder_B = 'D:\datasets\small-object-detection-datasets\Solar_1103\ImageSets\images/train'
# 获取B文件夹下的所有图片文件
image_files = [f for f in os.listdir(folder_B) if f.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff'))]
# 创建train.txt文件并写入图片文件名
with open('D:\datasets\small-object-detection-datasets\Solar_1103/train.txt', 'w') as file:
for image_file in image_files:
# image_files_myhouzhui = image_file[:6] +".txt" #切分后缀
file.write("D:\datasets\small-object-detection-datasets\Solar_1103\JPEGImages/"+image_file + '\n')
print('train.txt 文件已创建并写入图片文件名')
这样之后你就有五个文件了:
解下来就是生成json文件的最后一步,运行yolo转COCO脚本,代码如下:
import os
import json
import random
import time
from PIL import Image
import csv
#TODO:下面四个路径是需要对应修改的、除此之外class_names这个列表的内容修改成自己数据的类别
coco_format_save_path = 'D:\datasets\small-object-detection-datasets\Solar_1103' # 要生成的标准coco格式标签所在文件夹
yolo_format_annotation_path = 'D:\datasets\small-object-detection-datasets\Solar_1103\ImageSets\labels/val' # yolo格式标签所在文件夹
img_pathDir = 'D:\datasets\small-object-detection-datasets\Solar_1103\JPEGImages' # 图片所在文件夹
val_img_pathDir = 'D:\datasets\small-object-detection-datasets\Solar_1103\ImageSets\images/val'
# 类别设置
categories = []
class_names = ['bl', 'ce', 'ch', 'cr', 'hi', 'ir', 'oi', 'pe'] #需要修改成自己的类别
for label in class_names:
categories.append({'id': class_names.index(label), 'name': label, 'supercategory': ""})
write_json_context = dict() # 写入.json文件的大字典
write_json_context['licenses'] = [{'name': "", 'id': 0, 'url': ""}]
write_json_context['info'] = {'contributor': "", 'date_created': "", 'description': "", 'url': "", 'version': "", 'year': ""}
write_json_context['categories'] = categories
write_json_context['images'] = []
write_json_context['annotations'] = []
# 接下来的代码主要添加'images'和'annotations'的key值
imageFileList = os.listdir(img_pathDir)
#按照数字来进行排序
def sort_list_by_number(strings):
def extract_number(s):
# 从字符串中提取数字部分
return int(''.join(filter(str.isdigit, s)))
# 使用自定义的排序键对字符串列表进行排序
sorted_strings = sorted(strings, key=extract_number)
return sorted_strings
imageFileList = sort_list_by_number(imageFileList)
'''
再新创建一个只有val文件夹的列表进行二次判断!!
'''
val_imageFile = os.listdir(val_img_pathDir)
val_imageFile = sort_list_by_number(val_imageFile)
# 遍历该文件夹下的所有文件,并将所有文件名添加到列表中
img_id = 0 # 图片编号
anno_id = 0 # 标注标号
for i, imageFile in enumerate(imageFileList):
if '_' not in imageFile:
img_id += 1
if imageFile in val_imageFile:
imagePath = os.path.join(img_pathDir, imageFile) # 获取图片的绝对路径
image = Image.open(imagePath) # 读取图片
W, H = image.size # 获取图片的高度宽度
img_context = {} # 使用一个字典存储该图片信息
# img_name=os.path.basename(imagePath)
img_context['id'] = img_id # 每张图像的唯一ID索引
img_context['width'] = W
img_context['height'] = H
img_context['file_name'] = imageFile
img_context['license'] = 0
img_context['flickr_url'] = ""
img_context['color_url'] = ""
img_context['date_captured'] = ""
write_json_context['images'].append(img_context) # 将该图片信息添加到'image'列表中
txtFile = imageFile.split('.')[0] + '.txt' # 获取该图片获取的txt文件
with open(os.path.join(yolo_format_annotation_path, txtFile), 'r') as fr:
lines = fr.readlines() # 读取txt文件的每一行数据,lines2是一个列表,包含了一个图片的所有标注信息
for j, line in enumerate(lines):
anno_id += 1 # 标注的id从1开始
bbox_dict = {} # 将每一个bounding box信息存储在该字典中
class_id, x, y, w, h = line.strip().split(' ') # 获取每一个标注框的详细信息
class_id, x, y, w, h = int(class_id), float(x), float(y), float(w), float(h) # 将字符串类型转为可计算的int和float类型
# 坐标转换
xmin = (x - w / 2) * W
ymin = (y - h / 2) * H
xmax = (x + w / 2) * W
ymax = (y + h / 2) * H
w = w * W
h = h * H
height, width = abs(ymax - ymin), abs(xmax - xmin)
# bounding box的坐标信息
bbox_dict['id'] = anno_id # 每个标注信息的索引
bbox_dict['image_id'] = img_id # 当前图像的ID索引
bbox_dict['category_id'] = class_id # 类别信息
bbox_dict['segmentation'] = [[xmin, ymin, xmax, ymin, xmax, ymax, xmin, ymax]]
bbox_dict['area'] = height * width
bbox_dict['bbox'] = [xmin, ymin, w, h] # 注意目标类别要加一
bbox_dict['iscrowd'] = 0
bbox_dict['attributes'] = ""
write_json_context['annotations'].append(bbox_dict) # 将每一个由字典存储的bounding box信息添加到'annotations'列表中
name = os.path.join(coco_format_save_path, "annotations" + '.json')
with open(name, 'w') as fw: # 将字典信息写入.json文件中
json.dump(write_json_context, fw, indent=4, ensure_ascii=False)
这样就可以获得这个json文件。
对这个文件进行说明一下,并讲解一下为什么要这个文件。
json文件是对数据集中验证集(即val文件夹中的图片进行描述,以及它的坐标的描述--还有其他东西,但做小目标检测指标知道这个就足够了)。
需要这个文件是因为,你在使用val.py文件进行APs的指标评估的时候,会生成一个best.json,那个json文件包含了你对验证集中图片的检测信息,其实就是你生成的检测框的信息,将best.json和annotations.json进行对比就可以得到APs的检测指标了。
贴一个成功的图片:
------注意要调用val.py执行COCO检测指标时还需要修改val.py中的三处代码,今天没什么时间了,先放着(自行搜索一下,这个好搜),等有空了我会贴链接,现在懒得找了。
------图片标注完之后是json格式的文件,要生成yolo的txt文件可以参考我这一篇博客记录一下labelme标注完之后生成yolo的txt格式标签-CSDN博客
------另外注意,ImageSets的这个文件夹,在生成完json文件之后可以删掉了,你用yolo跑检测的话不需要用到它。