数据集
数据集介绍
数据集我们选择UEC FOOD 100
数据集。
数据集“UEC FOOD 100”包含100种食物照片。每张食物照片都有一个边框,指示照片中食物的位置。
该数据集中的大多数食品类别都是日本流行的食品。因此,有些目录可能不为日本人以外的其他人所熟悉。
[1-100]:目录名称对应食物ID。
[1-100]/*.jpg :食物照片文件(某些照片在两个或更多目录中重复,因为它们包含两个或更多食物。)
[1-100]/bb_info.txt:各目录下照片文件的边界框信息
category.txt :食物列表,包括食物ID和食物英文名称的对应关系
category_ja.txt :食物列表,包括食物ID和食物名称的日文对应关系
multiple_food.txt:表示包含两个或多个食品的食品照片的列表
以上是官网的介绍,实际上我下载下来解压后,是一个UECFOOD100
文件夹:
查看内部结构:
是1-100文件夹:
还有几个index和类别的映射文件:
category.txt
记录了1到100对应着什么食物:
multiple_food.txt
记录了表示包含两个或多个食品的食品照片的列表:
每个文件夹下存放了一类食物的图片,还有一个bb_info.txt
文件,是每张图片的标注框的坐标信息。其中img
一列表示的是UECFOOD100/1/
文件夹下图片的名称,x1
、y1
、x2
、y2
分别表示该类食物(1类食物)在该图片中的位置的框的左上坐标和右下坐标。
数据集格式转换
显然,这个数据集的格式不是我们想要的,于是我写了一个python脚本,将其变成符合yolo格式的数据集
yolo格式数据集要求如下:
由两个文件夹构成:images和labels
images文件夹下是图片,labels文件夹中有每张图片对应的同名的txt文件,该txt文件中内容格式如下:
1 0.6245612541289654 0.8125469325469415 0.0794512586321524 0.0808451256325814
3 0.3245614232849654 0.4125469348669415 0.0394512584621724 0.0408454686325544
第一个数字表示这张图片中的每个目标对象的类别编号(即是什么食物),剩下四个为x
、y
、w
、h
,其中x
、y
表示目标对象框的中心在该图片中的相对位置(归一化),即将该图片左上角设为(0,0)
,向右为x正半轴,图片右上角(1,0)
,向下为y正半轴,图片左下角(0,1)
。w和h代表归一化后框的相对宽度和高度。
脚本如下:
import os
import shutil
from PIL import Image
# 读取bb_info.txt获取边界框信息
def read_bounding_boxes(input_folder):
bounding_boxes = {}
if os.path.isdir(input_folder):
bb_info_file = os.path.join(input_folder, "bb_info.txt")
if os.path.isfile(bb_info_file):
with open(bb_info_file, 'r') as file:
next(file) # 跳过文件的第一行(标题行)
for line in file:
parts = line.strip().split()
img_id = int(parts[0])
boxes = [(int(parts[i]), int(parts[i + 1]), int(parts[i + 2]), int(parts[i + 3])) for i in
range(1, len(parts), 4)]
bounding_boxes[img_id] = boxes
return bounding_boxes
# 生成YOLO格式标签
def generate_yolo_labels(input_folder, output_folder):
for folder_name in os.listdir(input_folder):
folder_path = os.path.join(input_folder, folder_name)
if os.path.isdir(folder_path):
if not folder_name.isdigit() or int(folder_name) < 1 or int(folder_name) > 100:
raise ValueError(f"Invalid folder name: {folder_name}")
img_sizes = read_image_sizes(folder_path)
bounding_boxes = read_bounding_boxes(folder_path)
for img_name in os.listdir(folder_path):
img_path = os.path.join(folder_path, img_name)
if os.path.isfile(img_path) and img_name.endswith(".jpg"):
img_id = int(img_name[:-4])
img_width, img_height = img_sizes[img_id]
bounding_box = bounding_boxes[img_id]
yolo_label_path = os.path.join(output_folder, "labels", f"{img_id}.txt")
with open(yolo_label_path, 'a') as label_file:
for box in bounding_box:
category_id = folder_name
# 计算框的中心坐标
x_center = (box[0] + box[2]) / (2.0 * img_width)
y_center = (box[1] + box[3]) / (2.0 * img_height)
# 计算归一化后的框的宽度和高度
box_width = (box[2] - box[0]) / (1.0 * img_width)
box_height = (box[3] - box[1]) / (1.0 * img_height)
# 将计算结果保留16位小数
label_file.write(f"{category_id} {x_center:.16f} {y_center:.16f} {box_width:.16f} {box_height:.16f}\n")
# 复制图片文件
def copy_images(input_folder, output_folder):
image_output_folder = os.path.join(output_folder, "images")
os.makedirs(image_output_folder, exist_ok=True)
for folder_name in os.listdir(input_folder):
folder_path = os.path.join(input_folder, folder_name)
if os.path.isdir(folder_path):
for file_name in os.listdir(folder_path):
if file_name.endswith(".jpg"):
shutil.copy(os.path.join(folder_path, file_name), image_output_folder)
# 读取图片尺寸信息
def read_image_sizes(input_folder):
img_sizes = {}
for file_name in os.listdir(input_folder):
if file_name.endswith(".jpg"):
img_id = int(file_name[:-4])
img = Image.open(os.path.join(input_folder, file_name))
img_sizes[img_id] = img.size
return img_sizes
# 主函数
def main():
input_folder = "UECFOOD100"
output_folder = "UECFOOD_YOLO"
os.makedirs(os.path.join(output_folder, "images"), exist_ok=True)
os.makedirs(os.path.join(output_folder, "labels"), exist_ok=True)
# 生成YOLO格式标签
generate_yolo_labels(input_folder, output_folder)
copy_images(input_folder, output_folder)
if __name__ == "__main__":
main()
然后得到了UECFOOD_YOLO
文件夹:
但是还没有划分训练集和测试集。
划分数据集
于是再写一脚本进行划分。
import os
import random
import shutil
def split_dataset(input_folder, output_folder, train_ratio=0.7):
# 创建输出文件夹
os.makedirs(output_folder, exist_ok=True)
os.makedirs(os.path.join(output_folder, "images", "train"), exist_ok=True)
os.makedirs(os.path.join(output_folder, "images", "val"), exist_ok=True)
os.makedirs(os.path.join(output_folder, "labels", "train"), exist_ok=True)
os.makedirs(os.path.join(output_folder, "labels", "val"), exist_ok=True)
# 遍历images文件夹中的图片
image_folder = os.path.join(input_folder, "images")
label_folder = os.path.join(input_folder, "labels")
filenames = os.listdir(image_folder)
random.shuffle(filenames) # 随机打乱文件顺序
num_train = int(len(filenames) * train_ratio)
for i, filename in enumerate(filenames):
# 决定该图片应该放入训练集还是测试集
if i < num_train:
shutil.copy(os.path.join(image_folder, filename), os.path.join(output_folder, "images", "train"))
shutil.copy(os.path.join(label_folder, filename[:-4] + ".txt"), os.path.join(output_folder, "labels", "train"))
else:
shutil.copy(os.path.join(image_folder, filename), os.path.join(output_folder, "images", "val"))
shutil.copy(os.path.join(label_folder, filename[:-4] + ".txt"), os.path.join(output_folder, "labels", "val"))
# 调用函数
input_folder = "UECFOOD_YOLO" # 修改为你的输入文件夹路径
output_folder = "datasets" # 修改为你的输出文件夹路径
split_dataset(input_folder, output_folder)
更新标签index
这时我们发现,此时的数据集的labels是从1到100,但是我们需要的是0-99,因此进行更新。
import os
def update_label_file(file_path):
with open(file_path, 'r') as file:
lines = file.readlines()
updated_lines = []
for line in lines:
parts = line.strip().split()
parts[0] = str(int(parts[0]) - 1) # 减小第一个数字
updated_lines.append(' '.join(parts) + '\n')
with open(file_path, 'w') as file:
file.writelines(updated_lines)
def process_label_folder(folder_path):
for root, dirs, files in os.walk(folder_path):
for file_name in files:
if file_name.endswith('.txt'):
file_path = os.path.join(root, file_name)
update_label_file(file_path)
def main():
labels_folder = "labels"
train_folder = os.path.join(labels_folder, "train")
val_folder = os.path.join(labels_folder, "val")
process_label_folder(train_folder)
process_label_folder(val_folder)
if __name__ == "__main__":
main()
最终得到数据集datasets
。
制作yaml文件
我们从数据集自带的category.txt
中取出类别名,并将其翻译成中文,然后写一个小脚本,将其前面添加0-99的index:
input_file = "classes.txt"
output_file = "classes_with_index.txt"
with open(input_file, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 在每一行前添加序号
numbered_lines = [f"{i}: {line.strip()}\n" for i, line in enumerate(lines)]
# 将带有序号的行写入新的文件
with open(output_file, 'w', encoding='utf-8') as f:
f.writelines(numbered_lines)