1. 内容描述
本文包含了各种数据集格式的转换;数据集中标注文件为.mat格式如何转换;如何通过画目标框的方式确定自己标注文件中每行代表什么(因为数据集格式不同,标签文件中的每行数字代表的目标框也不同);csv文件加载模型失败等。
2. COCO转YOLO
1. 在图片上画出目标框:在某些情况下,拿到的数据集并没有很多细节的描述,所以很多信息都需要我们自己找,但在这个过程中,较为重要的信息之一就是边框的坐标值,换句话说,数据集中得到的四个数值,不知道代表的是什么含义;比如:【左上角x坐标,左上角y坐标,w,h】或者【左上角x坐标,左上角y坐标,右下角x坐标,右下角y坐标】或者【中心点x坐标,中心点y坐标,w,h】。在以下代码中,获取到的mat文件中的框1、框2、框3、框4就存在这种问题。四个数字不确定到底是哪种坐标类型。所以就必须在图片上根据数值用代码画出相对应的目标框,才可以判断。该数据集通过画出目标框,可以确定坐标类型为【左上角x坐标,左上角y坐标,w,h】,为后续生成txt标注文件做准备。
from scipy import io
import cv2
from PIL import Image
# 读取.mat文件的内容(data中包含边界框坐标)
data = io.loadmat('E:/Datasets/dataset/trainval/labels/T0001_XM_20110809100243_02.mat')
# 图片的路径
fname = 'E:/Datasets/dataset/trainval/images/T0001_XM_20110809100243_02.jpg'
# 读取图片
img = cv2.imread(fname, 1)
# 由于该mat文件中有四个边框坐标,所以输出4个边界框信息
for i in range(4):
print(data['annotation'][0][0][1][i])
# [24 1338 390 280] 边框1
# [3058 369 256 323] 边框2
# [ 590 2072 130 155] 边框3
# [5.10000e-01 1.04851e+03 1.07980e+02 1.38980e+02] # 边框4
pt1 = (24, 1338) # 框1左上角坐标
pt2 = (390+24, 280+1338) # 框1右下角坐标
pt3 = (3058, 369) # 框2左上角坐标
pt4 = (256+3058, 323+369) # 框2右下角坐标
pt5 = (590, 2072)
pt6 = (130+590, 155+2072)
pt7 = (0, 1048)
pt8 = (107+0, 138+1048)
cv2.rectangle(img, pt1, pt2, (0, 0, 255), 2) # 在图片上画出框1
cv2.rectangle(img, pt3, pt4, (0, 0, 255), 2)
cv2.rectangle(img, pt5, pt6, (0, 0, 255), 2)
cv2.rectangle(img, pt7, pt8, (0, 0, 255), 2)
save_path = 'img.jpg' # 画好的图片,保存名称
cv2.imwrite(save_path, img)
2. 描述:文件夹中包含多个mat文件,一个mat文件中包含一个图片上的所有目标框信息。所以生成一个文件夹,其中包含多个txt文件,一个txt文件中包含一个图片上的所有目标框信息。适用于yolo模型。
from scipy import io
from PIL import Image
import os
# 创建一个文件夹(运行一次创建文件夹即可)
os.mkdir('E:/Datasets/dataset/test/labels_txt')
path = "E:/Datasets/dataset/test/labels/" # 文件夹名字
path_img = "E:/Datasets/dataset/test/images/" # 图片文件夹
def mat_to_txt():
path_files_name = os.listdir(path) # 所有文件的名字
for i in path_files_name:
all_file_name = os.path.join(path, i) # 完整的文件名mat
data = io.loadmat(all_file_name) # 读到mat文件
txt_file_name = data['annotation'][0][0][0][0]+'.txt' # txt文件名
img_file_name = data['annotation'][0][0][0][0]+'.jpg' # 图片名
path_txt = 'E:/Datasets/maize_counting_dataset/test/labels_txt/' # 前缀
path_file_txt = os.path.join(path_txt, txt_file_name) # 完整的文件名txt
path_img_file = os.path.join(path_img, img_file_name) # 完整的图片名jpg
img2 = Image.open(path_img_file) # 读到图片
w1 = img2.width # 图片的宽
h1 = img2.height # 图片的高
file = open(path_file_txt, 'a') # 创建文件名正常的文件
label_counts = int(data['annotation'][0][0][1].size / 4) # 所有锚框数
for i in range(label_counts):
label_lines = [] # 一个目标框
x, y, w, h = data['annotation'][0][0][1][i] # 获取mat文件中的坐标信息
x2 = round((x + w + x) / (2.0 * w1), 6) # 进行归一化,因为yolo模型使用的是归一化的坐标
y2 = round((y + h + y) / (2.0 * h1), 6)
w2 = round((w) / (1.0 * w1), 6)
h2 = round((h) / (1.0 * h1), 6)
label_lines.append(0)
label_lines.append(x2)
label_lines.append(y2)
label_lines.append(w2)
label_lines.append(h2)
s = str(label_lines)[1:-1].split(',') # 截取'['与']'
a = ''
for i in s:
a = a + str(i) + ''
file.write(a + '\n') # 写入文件
if __name__ == '__main__':
mat_to_txt()
3. 数据集中确定四个值的具体含义。YOLO模型使用的是归一化后的(中心点x,中心点y,w,h)。
import cv2
# 图片和标签路径
image_path = "E:/project/yh/due_data/check_box/Hebei2010_00140_Gc.jpg"
label_path = "E:/project/yh/due_data/check_box/Hebei2010_00140_Gc.txt"
# 读取图片
img = cv2.imread(image_path, 1) # 在图片上标记box
height, width, channels = img.shape # 得到图片宽高
# 读标签文件中的标注框
c = open(label_path) # 打开文件
data = c.readline() # 读取第一行
x, y, w, h = data.split(" ")[1:] # 读取的数值为字符串类型
x = float(x)
y = float(y)
w = float(w)
h = float(h)
# 将归一化的x,y,w,h转换为原来的值,在图片上画box
w1 = w * width
h1 = h * height
# x,y有两种情况:1.x,y是box左上角点坐标,2.x,y是box中心点坐标
# 1. 左上角点
(x + w + x) / (2.0 * w1)
x1 = (x * (2.0 * width) - w1) / 2
y1 = (y * (2.0 * height) - h1) / 2
print(x1, y1, w1, h1) # 2757.001536 1.0013760000000218 162.000384 277.999488
pt1 = (2757, 1) # 左上角的点
pt2 = (2757 + 162, 1 + 278) # 右下角的点
# 画图
# pt1 = (x1, y1)
# pt2 = (x1 + w1, y1 + h1)
cv2.rectangle(img, pt1, pt2, (0, 0, 255), 2) # 在图片上画出框1
save_path = 'img.jpg' # 画好的图片,保存名称
cv2.imwrite(save_path, img)
图1. 已画框的图片(标注框在图片右上部分)
4. 数据集处理:在训练模型过程中,可能会需要对数据集图片尺寸进行改变,例如将原图片大小改变为(1240, 960),在这种情况下,将数据集格式转换为YOLO格式(标签,归一化后的中心点X,归一化后的中心点Y,归一化后的W,归一化后的H)是不需要改变的,也就是标签文件夹中所有内容不需要改变(因为都是归一化后的,就相当于已经进行了单位化),所以只需要给图片进行尺寸转换即可。
import os
from PIL import Image
image_folder = "" # 原始图片文件路径
os.mkdir("") # 创建新的图片文件夹,存放改变大小后的图片
def resize_img(folder):
all_images = os.listdir(folder) # 图片名
for i in all_images:
image_name = os.path.join(folder, i) # 图片全名
img = Image.open(image_name)
img_size = img.resize((1024, 960), Image.ANTIALIAS) # 改变的尺寸大小
path_img = "" # 创建的新的文件夹名
path_image_name = os.path.join(path_img, i) # 修改后的图片路径
img_size.save(path_image_name) # 保存
if __name__ == '__main__':
resize_img(image_folder)
5. 数据集处理:在某些数据集中,会存在不同格式的情况,所以需要进行重新划分(划分为适合YOLO的格式),以下代码就是将数据集划分为train、test、val(适合YOLO格式)
import os
import shutil
# 一直替换两个文件(因为日期)
path_imgs_folder = "F:/datasets/MT/images"
path_labels_foler = "F:/datasets/MT/labels"
img_train_folder = "F:/datasets/MT_dataset/images/train"
img_test_folder = "F:/datasets/MT_dataset/images/test"
img_val_folder = "F:/datasets/MT_dataset/images/val"
label_train_folder = "F:/datasets/MT_dataset/labels/train"
label_test_folder = "F:/datasets/MT_dataset/labels/test"
label_val_folder = "F:/datasets/MT_dataset/labels/val"
def due_files():
path_labels = os.listdir(path_labels_foler) # 读取标签文件夹
for i in path_labels:
if i.split('_')[-1] == 'valid.txt':
path_img = i.split('.')[0] + ".jpg"
path_im = os.path.join(path_imgs_folder, path_img) # 图片完整路径
path_la = os.path.join(path_labels_foler, i) # 标签完整路径
shutil.copy(path_im, img_val_folder) # 图片 原文件,新文件夹
shutil.copy(path_la, label_val_folder) # 标签 原文件,新文件夹
elif i.split('_')[-1] == 'test.txt':
path_img = i.split('.')[0] + ".jpg"
path_im = os.path.join(path_imgs_folder, path_img) # 图片完整路径
path_la = os.path.join(path_labels_foler, i) # 标签完整路径
shutil.copy(path_im, img_test_folder) # 图片 原文件,新文件夹
shutil.copy(path_la, label_test_folder) # 标签 原文件,新文件夹
else: # train
path_img = i.split('.')[0] + ".jpg"
path_im = os.path.join(path_imgs_folder, path_img) # 图片完整路径
path_la = os.path.join(path_labels_foler, i) # 标签完整路径
shutil.copy(path_im, img_train_folder) # 图片 原文件,新文件夹
shutil.copy(path_la, label_train_folder) # 标签 原文件,新文件夹
if __name__ == '__main__':
due_files()
3. YOLO转VOC
1. coco转voc:(左上点x, 左上点y, w , h) -----> (左上点x, 左上点y, 右下点x, 右下点y)。原图像的大小,原图像的(左上角x, 左上角y, w, h),我们将原图像进行尺寸调整到现有尺寸,原图像的(左上角x, 左上角y, w, h)也会发生变化。
2. yolo转voc:(批量归一化中心点x, 批量归一化中心点y, w, h)---> (左上角x, 左上角y, 右下角x, 右下角y)。先使用一张图片进行测试,将yolo格式的labels转为voc格式,再使用cv2将标注框画出来,发现计算过程正确。结果如图2所示:
import os
import cv2
# 先用一张图片进行测试(原图像大小1024*960)
if __name__ == '__main__':
# yolo_to_voc(path)
img = cv2.imread("img.png", 1) # 读取一张图片进行测试
data = open("test.txt").readlines() # 标签文件名
for j in data:
x = float(j.split(" ")[1])
y = float(j.split(" ")[2])
w = float(j.split(" ")[3])
h = float(j.split(" ")[4])
w1 = w * 1024
h1 = h * 960
x1 = x * 1024 - w1 / 2
y1 = y * 960 - h1 / 2
x2 = x1 + w1
y2 = y1 + h1
pt1 = (int(x1), int(y1))
pt2 = (int(x2), int(y2))
cv2.rectangle(img, pt1, pt2, (0, 0, 255), 3)
save_path = "img_new.png"
cv2.imwrite(save_path, img)
图2
3. yolo转voc:批量转换步骤
3.1 第一步:参照原始VOC2007数据集的文件层次创建四个文件夹,首先创建一个VOCdevkit文件夹,VOCdevkit文件夹中再创建Annotations、JPEGImages、ImageSets三个文件夹,最后在ImageSets文件夹下再创建一个Main文件夹。
3.2 第二步:将数据集所有图片(包括test、train、val中所有图片)都放到JPEGImages文件夹下。
3.3 第三步: 在Main文件夹下创建四个文件,train.txt、test.txt、val.txt、trainval.txt。
3.4 第四步:将训练集、测试集、验证集、训练验证集图片名称,分别放入train.txt、test.txt、val.txt、trainval.txt文件中(注意:没有后缀),如图3所示,每行代表一个图片名(目前查看的资料上都会对文件名进行修改,修改为01、02、03这种名称,但是本人在实验过程中并没有按照资料进行名称修改,主要是麻烦,所以初步尝试使用自己原有数据集图片名称,图3是简单示例图),添加名称的代码如下:
图3
import os
# yolo格式标签文件夹路径
init_test_path = ""
init_train_path = ""
init_val_path = ""
# VOC格式中Main下的test.txt等文件,每次运行,一一对应即可
new_test_path = ""
new_train_path = ""
new_val_path = ""
def due_txt():
all_val_name = os.listdir(init_val_path) # open folder
file = open(new_val_path, 'a') # open file
for i in all_val_name:
file.write(i.split('.')[0] + '\n') # write image_name
if __name__ == '__main__':
due_txt()
3.5 第五步:处理Annotations文件夹,如图4所示,为Annotations文件夹中数据格式。代码如下。
图4 VOC数据格式中,Annotations文件夹中每个xml文件的格式
import os
from lxml.etree import Element, tostring
from lxml.etree import SubElement as subElement
# 所有图片所在文件夹data/VOC/JPEGImages
init_all_image_path = "data/VOC/JPEGImages"
# Annotations文件夹
annotation_folder = "data/VOC/Annotations"
# YOLO格式中test、train、val标签所在位置
#init_all_test_labels = "dataset/labels/test"
# init_all_train_labels = "dataset/labels/train"
init_all_val_labels = "dataset/labels/val"
def due_annotation():
# all_label_name = os.listdir(init_all_train_labels)
# all_label_name = os.listdir(init_all_test_labels)
all_label_name = os.listdir(init_all_val_labels) # 改变val、train、test
for i in all_label_name:
file_name = i.split(".")[0] # image_name 前缀
file_name_xml = file_name + ".xml"
file_image_name = file_name + '.jpg'
file_name_xml_path = os.path.join(annotation_folder, file_name_xml)
file_name_image_path = os.path.join(init_all_image_path, file_image_name)
# file_name_label_path = os.path.join(init_all_train_labels, i)
# file_name_label_path = os.path.join(init_all_test_labels, i)
file_name_label_path = os.path.join(init_all_val_labels, i) # 改变val、train、test
if os.path.exists(file_name_xml_path):
break
else:
node_root = Element('annotation') # root
node_folder = subElement(node_root, 'folder') # 子节点
node_folder.text = "VOC"
node_filename = subElement(node_root, 'filename') #子节点
node_filename.text = file_image_name
node_path = subElement(node_root, 'path') # 子节点
node_path.text = file_name_image_path
node_source = subElement(node_root, 'source') #子节点
node_source.text = "Unknown"
node_size = subElement(node_root, 'size') #子节点
node_size_width = subElement(node_size, 'width') # 子子节点
node_size_width.text = '%s' % int("1024")
node_size_height = subElement(node_size, 'height')
node_size_height.text = '%s' % int("960")
node_size_depth = subElement(node_size, 'depth')
node_size_depth.text = '%s' % int("3")
node_segmented = subElement(node_root, 'segmented')
node_segmented.text = '%s' % int("0")
# 将YOLO格式标签转换为VOC格式,如上文中3.YOLO转VOC代码所示
with open(file_name_label_path, 'r') as f: # 打开标签文件
for line in f.readlines(): # 读每行数据
node_object = subElement(node_root, 'object') #子节点
node_object_name = subElement(node_object, 'name')
node_object_name.text = 'maize'
node_object_pose = subElement(node_object, 'pose')
node_object_pose.text = 'Frontal'
node_object_truncated = subElement(node_object, 'truncated')
node_object_truncated.text = '%s' % int("0")
node_object_diffcult = subElement(node_object, 'diffcult')
node_object_diffcult.text = '%s' % int("0")
node_object_bndbox = subElement(node_object, 'bndbox')
x = float(line.split(" ")[1])
y = float(line.split(" ")[2])
w = float(line.split(" ")[3])
h = float(line.split(" ")[4])
w1 = w * 1024
h1 = h * 960
x1 = x * 1024 - w1 / 2
y1 = y * 960 - h1 / 2
x2 = x1 + w1
y2 = y1 + h1
node_object_bndbox_xmin = subElement(node_object_bndbox, 'xmin')
node_object_bndbox_xmin.text = '%s' % int(x1)
node_object_bndbox_ymin = subElement(node_object_bndbox, 'ymin')
node_object_bndbox_ymin.text = '%s' % int(y1)
node_object_bndbox_xmax = subElement(node_object_bndbox, 'xmax')
node_object_bndbox_xmax.text = '%s' % int(x2)
node_object_bndbox_ymax = subElement(node_object_bndbox, 'ymax')
node_object_bndbox_ymax.text = '%s' % int(y2)
xml = tostring(node_root, pretty_print=True) # 将上面设定的一串节点信息导出
with open(file_name_xml_path, 'wb') as f1: # 将节点信息写入到文件路径中
f1.write(xml)
if __name__ == '__main__':
due_annotation()
4. 训练pytorch-ssd模型
主要参考:http://t.csdnimg.cn/XHSkG
权重参考:http://t.csdnimg.cn/hklvD
4. YOLO转COCO
1. 在制作annotations.json文件时,可能会出现该错误:PermissionError: [Errno 13] Permission denied: ‘XXXX ,
解决:该错误一般情况下,并不是因为文件权限不够造成,一般都是因为前面代码中生成的文件路径有问题,检查自己的文件路径是否正确。
2. yolo数据转coco
链接:目标检测标注文件yolov5(txt)格式转coco(json)格式详解及代码实现_txt转coco-CSDN博客
5. 使用CSV标签文件,加载模型失败
csv标注文件在使用时,很容易忽略一个重要问题,csv文件中有可能保留着列名。在读取时就会出现错误,提示无法找到对应的图片文件。因此需要对读取的内容进行简单处理。在DataLoader中使用下列代码进行读取,原始代码在读取时,会将列名作为数据加入列表中,所以只需要在列表中去除列表中index为0的内容即可。
class DatasetLoader(Dataset):
def __init__(self, csv_path):
self.csv_file = csv_path
with open(self.csv_file, 'r') as file:
# self.data = list(csv_read(file)) 原始代码
self.data = list(csv.reader(file))[1:]
6. 在yolov8预测图中标记目标总数(适用于单目标)
步骤一:运行predict,将结果栏中结果复制,放在yolov8.txt文件中(代码中file_yolov8所指的路径)
步骤二:创建images文件夹,将predict后的带有标签以及置信度的图片,全部复制到该文件中(代码中folder_images所指的路径)
步骤三:创建images_total文件夹(代码中folder_images_total所指的路径)
运行即可
import cv2
import os
folder_images = "E:/project/yh/Utils_Data/draw_image_total/images/" # 预测后的图片文件夹(图片上包含标签与置信度)
folder_images_total = "E:/project/yh/Utils_Data/draw_image_total/images_total/" # 标记总数图片的文件夹
# 根据模型预测的数量:在每张图片上的标记玉米雄穗总数量
def yolov8_total():
file_yolov8 = "E:/project/yh/Utils_Data/draw_image_total/yolov8.txt"
c = open(file_yolov8) # 打开文件
yolov8_data = c.readlines()
yolov8_image = [] # 图片路径
yolov8_number = [] # 标签总数
for i in yolov8_data:
images_total = i.split(" ")[-3]
if not images_total.isdigit():
images_total = "0"
image = i.split("\\")[-1].split(":")[0]
images_path = os.path.join(folder_images, image) # 预测后图片的绝对路径
img = cv2.imread(images_path, 1)
cv2.putText(img, images_total, (100, 200), cv2.FONT_HERSHEY_SIMPLEX, 5, (255, 0, 0), 5)
images_total_path = os.path.join(folder_images_total, image)
cv2.imwrite(images_total_path, img)
if __name__ == '__main__':
yolov8_total()
结果展示图