作者:欧新宇(Xinyu OU)
当前版本:Release v1.0
开发平台:Paddle 2.3.2
运行环境:Intel Core i7-7700K CPU 4.2GHz, nVidia GeForce GTX 1080 Ti
本教案所涉及的数据集仅用于教学和交流使用,请勿用作商用。
最后更新:2025年3月24日
垃圾分类数据集Garbage 是一个包含有40个类别,14802张图像的数据集。该数据集已经事先实现了训练(train)
和测试(test)
的分割,其中测试集没有类别标签。数据集没有给出验证集的划分建议,因此在进行数据列表生成的时候,可以自行按照一定的比列来将官方提供的训练集看作是trainval进行二次划分,即将原来的 train
文件夹划分为训练集train和验证集val。使用train和val完成训练之后,再按照训练获得超参数,对整个训练验证集trainval进行训练,完成后直接输出 测试集结果
到平台进行评估。注意,本数据集给出了类别的标签字典,因此在生成和划分数据子集的时候,建议考虑基于该类别标签字典而非遍历文件夹的方式来完成。
# pcodes00401_train_initialization
import os
import cv2
import json
import numpy as np
import matplotlib.pyplot as plt
import paddle
import paddle.vision.transforms as T
import paddle.vision.transforms.functional as F
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei'] # Windows中指定绘图区域的字体为“微软雅黑”
plt.rcParams['font.size'] = 14
# from matplotlib.font_manager import FontProperties # Linux中指定绘图区域的字体为“微软雅黑”
# fontpath = '/home/aistudio/work/msyh.ttc'
# myFont = FontProperties(fname=fontpath, size=14)
paddle.vision.set_image_backend('cv2') # 设置vision图像处理的背板为OpenCV
# 1. 定义数据集基本信息
dataset_name = 'Garbage'
dataset_path = 'D:/Workspace/ExpDatasets/' # Windows: D:/Workspace/ExpDatasets/, Linux: /home/aistudio/work/ExpDatasets
dataset_root_path = os.path.join(dataset_path, dataset_name)
dataset_info_path = os.path.join(dataset_root_path, 'dataset_info.json')
# 2. 图像基本信息
args = {
'input_size': [3, 227, 227], # 定义图像输入模型时的尺寸
'mean_value': [0.485, 0.456, 0.406], # Imagenet均值
'std_value': [0.229, 0.224, 0.225], # Imagenet标准差
}
【关于中文字体的设置说明】
在使用 python 的绘图包 matplotlib 绘制图形时,若要使用中文标签,需要手动指定中文字体。这个操作在Windows和Linux中略有不同。
matplotlib
自带的字体设置命令指定系统自带的字体即可。plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['font.size'] = 14
work
目录,你可以和直接对它进行引用。此外,在使用该字体的位置,还需要手动指定字体的配置信息。# 1. 初始化部分设置
from matplotlib.font_manager import FontProperties
fontpath = '/home/aistudio/work/msyh.ttc'
myFont = FontProperties(fname=fontpath, size=14)
# 2. 显示中文的代码处进行指定
ax.set_title('中文标题', fontproperties=myFont)
plt.title('中文标题', fontproperties=myFont)
plt.ylabel('y轴标签', fontproperties=myFont)
在调用数据集的样本前需要先手动解压数据集到工作目录,
/home/aistudio/work/Garbage
目录..\Workspace\ExpDataset\Garbage
目录此外,完成数据集的解压后,还需要生成数据列表,此处可以直接使用目录下的 generate_annotation.py
文件来实现,但注意需要将文件中的默认路径 dataset_path
修改为当前数据集的保存路径。
注意,以上生成过程,只需要执行一次即可。
在AIStudio上,可以使用以下命令对数据集进行解压
!unzip /home/aistudio/data/data71361/Garbage.zip -d /home/aistudio/work/Garbage/
# !python /home/aistudio/work/Garbage/generate_annotation.py
!python D://Workspace/ExpDatasets/Garbage/generate_annotation.py #
图像列表已生成, 其中训练验证集样本14402,训练集样本11504个, 验证集样本2898个, 测试集样本400个, 共计14802个。
.txt
列表文件,并返回图像绝对路径和图像的标签,对于没有标签的测试集,返回值为-1。 ([Your codes 1])(10分)__getitem__
,返回单条数据,包括返回样本数据和对应的标签。返回的图像需要能够应用Q1-2中所定义的数据预处理, ([Your codes 3])(10分)class DatasetGarbage(paddle.io.Dataset):
"""定义十二生肖Zodiac数据集"""
# args: 数据集定义所使用的相关超参数
# transforms_type=[0|1|2]: 定义Transforms类型,选择是否使用数据增广和数据归约
# 0:仅使用必要的数据规约,包括尺度变换、数据格式和数据类型变换。用于不使用图像变换的特殊场景。
# 1:使用完整的数据归约和数据增广。用于train和trainval
# 2:仅使用数据规约,包括尺度变换、数据格式和数据类型变换、均值消除。用于test和val,以及部分train(原图送入模型)
def __init__(self, dataset_root_path, mode='test', args=None, transforms_type=None):
self.data = [] # 定义数据序列,用于保存数据的路径和标签
self.args = args # 定义超参数列表
self.transforms_type = transforms_type # 定义数据预处理类型
# 1.2 根据模式,选择对应的数据预处理类型
if mode in ['train', 'trainval']:
self.transforms_type = 1
elif mode in ['val', 'test'] and transforms_type != 0:
self.transforms_type = 2
# [Q1-1] 读取数据列表文件,将每一行都按照路径和标签进行拆分成两个字段的序列,并将序列依次保存至data序列中
# 1) 若列表信息长度为2,则表示包含路径和标签信息。
# 2) 若列表信息长度为1,则表示只包含路径,不包含标签。一般正式的测试文件都只包含路径,不包含标签。
# [Your codes 1]
with open(os.path.join(dataset_root_path, mode+'.txt'), encoding='utf-8') as f:
for line in f.readlines():
info = line.strip().split('\t') # 拆分从列表文件中读取到数据信息
image_path = info[0].strip() # 信息的[0]位置为路径
if len(info) == 2: # 判断信息的长度,若包含标签则写入image_label
image_label = info[1].strip()
elif len(info) == 1: # 判断信息的长度,若不包含标签,则用"-1"表示
image_label = -1
self.data.append([image_path, image_label]) # 将路径和标签写入[data]容器
# [Q1-2] 对训练数据和验证、测试数据采用不同的数据预处理方法
# 1) train和trainval:执行随机裁剪,并完成标准化预处理
# 2) train和trainval:直接执行尺度缩放,并完成标准化预处理
# [Your codes 2]
inputSize = self.args['input_size'][1:3] if len(self.args['input_size'])==3 else self.args['input_size'] # 获取输入图片的尺寸,若输入尺寸为3维(彩色图),则取后两个维度,否则取整个尺寸(灰度图)
prob = np.random.random() # 生成一个随机数,用于判断是否执行数据增广
if self.transforms_type == 0:
self.transforms = T.Compose([ # 0) 输出原始数据,仅作必要数据规约
T.Resize(inputSize), # 直接尺度缩放
T.ToTensor(), # 转换成Paddle规定的Tensor格式
])
elif self.transforms_type == 1 and prob <= 0.5:
self.transforms = T.Compose([ # 1) 训练数据预处理,包含数据增广(trainval, train)
T.Resize((256, 256)), # 直接尺度缩放
T.RandomResizedCrop(inputSize), # 随机裁剪
T.RandomHorizontalFlip(prob=0.5), # 水平翻转
T.RandomVerticalFlip(prob=0.5), # 垂直翻转(谨慎使用)
T.RandomRotation(15), # 随机旋转
T.ColorJitter(brightness=0.4, # 色彩扰动:亮度、对比度、饱和度和色度
contrast=0.4,
saturation=0.4,
hue=0.4),
T.ToTensor(), # 转换成Paddle规定的Tensor格式
T.Normalize(mean=self.args['mean_value'], # Z-Score标准化
std=self.args['std_value'])
])
else:
self.transforms = T.Compose([ # 2) 基本数据预处理,不含数据增广(val, test, 不参与增强的train)
T.Resize(inputSize), # 直接尺度缩放
T.ToTensor(), # 转换成Paddle规定的Tensor格式
T.Normalize(mean=self.args['mean_value'], # Z-Score标准化
std=self.args['std_value'])
])
# [Q1-3] 定义数据获取函数,返回单条数据,并对图像执行数据预处理,返回样本数据和对应的标签
# [Your codes 3]
def __getitem__(self, index):
image_path, label = self.data[index] # 根据索引,从列表中取出指定[index]图像,并将数据拆分成路径和列表
img = cv2.imread(image_path, 1) # 使用cv2进行数据读取,0为灰度模式,1为彩色模式
img = self.transforms(img) # 执行数据预处理
label = np.array(label, dtype='int64') # 将标签转换为64位整型
return img, label
# 获取数据集的样本总数
def __len__(self):
# 返回self.data的长度
return len(self.data)
# pcodes00403_create_DataReader_and_DataLoader # 1. 生成数据读取器 [Q1-4] # [Your codes 4] dataset_trainval = DatasetGarbage(dataset_root_path, mode='trainval', args=args) dataset_train = DatasetGarbage(dataset_root_path, mode='train', args=args) dataset_val = DatasetGarbage(dataset_root_path, mode='val', args=args) dataset_test = DatasetGarbage(dataset_root_path, mode='test', args=args) # 2. 生成数据迭代读取器 [Q1-5] # [Your codes 5] trainval_reader = paddle.io.DataLoader(dataset_trainval, batch_size=32, shuffle=True) train_reader = paddle.io.DataLoader(dataset_train, batch_size=32, shuffle=True) val_reader = paddle.io.DataLoader(dataset_val, batch_size=32, shuffle=True) test_reader = paddle.io.DataLoader(dataset_test, batch_size=32, shuffle=True) # 3. 生成测试数据 [Q1-6] # [Your codes 6] print(f"trainval: {len(dataset_trainval)}, train: {len(dataset_train)}, val: {len(dataset_val)}, test: {len(dataset_test)}") for i, (image, label) in enumerate(train_reader): if i < 2: print(f"训练集batch_{i}的图像形态:{image.shape}, 标签形态:{label.shape}") else: break for i, (image, label) in enumerate(val_reader): if i < 2: print(f"验证集batch_{i}的图像形态:{image.shape}, 标签形态:{label.shape}") else: break
trainval: 14402, train: 11504, val: 2898, test: 400
训练集batch_0的图像形态:[32, 3, 227, 227], 标签形态:[32]
训练集batch_1的图像形态:[32, 3, 227, 227], 标签形态:[32]
验证集batch_0的图像形态:[32, 3, 227, 227], 标签形态:[32]
验证集batch_1的图像形态:[32, 3, 227, 227], 标签形态:[32]
# pcodes00404_get_Garbage_labelname def get_Garbage_labelname_from_labelID_by_json(label_id, dataset_info_path): # 1. 根据标签ID,返回Zodiac数据集的文本标签,标签信息来源于dataset_info.json [Q1-7] # [Your codes 7] dataset_info = json.load(open(dataset_info_path, 'r', encoding='utf-8')) label_dict = dataset_info['label_dict'] return label_dict[str(label_id)] # 2. 测试标签获取函数 print(f"标签 15 的名称为:{get_Garbage_labelname_from_labelID_by_json(15, dataset_info_path)}")
标签 15 的名称为:可回收物/包。
import random
test_data_list = {'train': train_reader, 'test': test_reader}
for show in test_data_list:
for i, (image, label) in enumerate(test_data_list[show]):
print('测试数据集 batch_{} 的图像形态:{}, 标签形态:{}'.format(i, image.shape, label.shape))
indices = random.sample(range(0, len(image)), 6)
imgs = image[indices] # 假设图像存储在'image'键中
labels = label[indices] # 假设标签存储在'label'键中
plt.figure(figsize=(14, 2))
plt.title(f"{show}ing data", pad=25)
plt.axis('off')
for j in range(6):
ax = plt.subplot(1, 6, j+1)
if show != 'test':
ax.set_title(get_Garbage_labelname_from_labelID_by_json(int(labels[j]), dataset_info_path), fontsize=12)
img = imgs[j].transpose((1, 2, 0)) # 如果图像是CHW格式,转为HWC cv2.cvtColor(imgs[j].numpy(), cv2.COLOR_BGR2RGB)
img = img.numpy()
img = img * args['std_value'] + args['mean_value']
plt.axis('off')
plt.imshow(img) # 如果图像是CHW格式,转为HWC
break
测试数据集 batch_0 的图像形态:[32, 3, 227, 227], 标签形态:[32]
测试数据集 batch_0 的图像形态:[32, 3, 227, 227], 标签形态:[32]
import os
import cv2
import numpy as np
import random
import matplotlib.pyplot as plt
import paddle
import paddle.vision.transforms as T
import paddle.vision.transforms.functional as F
from matplotlib.font_manager import FontProperties # Linux中指定绘图区域的字体为“微软雅黑”
fontpath = '/home/aistudio/work/msyh.ttc'
myFont = FontProperties(fname=fontpath, size=14)
# 1. 定义数据集基本信息
dataset_name = 'Garbage'
root_path = 'D:/Workspace/ExpDatasets' # Windows: 'D:\\Workspace\\'; Linux '/home/aistudio/'
dataset_root_path = os.path.join(root_path, dataset_name)
# 2. 图像基本信息
args = {
'input_size': [3, 227, 227], # 定义图像输入模型时的尺寸
'mean_value': [0.485, 0.456, 0.406], # Imagenet均值
'std_value': [0.229, 0.224, 0.225], # Imagenet标准差
}
title = ['左上', '右上', '左下', '右下', '中央', '左上翻转', '右上翻转', '左下翻转', '右下翻转', '中央翻转']
# pcodes00407_function_TenCrop def TenCrop(img, crop_size=227): """AlexNet模型种定义的十重切割法,用于分类预测的测试过程""" # 1. 实现从256像素的输入图像中切割出10个224×224的patch # input_data: Height x Width x Channel img_size = 256 img = F.resize(img, (img_size, img_size)) blob = np.zeros([10, crop_size, crop_size, 3]) # 2. 获取左上、右上、左下、右下、中央及其对应的翻转,共计10个切片样本 [Q2-1] # [Your codes 10] blob[0] = F.crop(img,0,0,crop_size,crop_size) blob[1] = F.crop(img,0,img_size-crop_size,crop_size,crop_size) blob[2] = F.crop(img,img_size-crop_size,0,crop_size,crop_size) blob[3] = F.crop(img,img_size-crop_size,img_size-crop_size,crop_size,crop_size) blob[4] = F.center_crop(img, crop_size) blob[5] = F.hflip(blob[0, :, :, :]) blob[6] = F.hflip(blob[1, :, :, :]) blob[7] = F.hflip(blob[2, :, :, :]) blob[8] = F.hflip(blob[3, :, :, :]) blob[9] = F.hflip(blob[4, :, :, :]) return blob
# pcodes00408_function_SimplePreprocessing def SimplePreprocessing(img, args=None, isTenCrop=True): # 1. 定义数据预处理功能,包含尺度规约、数据类型变换和均值消除 [Q2-2] # [Your codes 11] transform = T.Compose([ T.Resize([args['input_size'][1],args['input_size'][2]]), T.ToTensor(), T.Normalize(mean=args['mean_value'], std=args['std_value']) ]) # 2. 根据超参数iSTenCrop判断是否对数据进行十重切割处理 [Q2-3] # [Your codes 12] if isTenCrop: fake_data = np.zeros([10] + args['input_size'], dtype=np.float32) # 初始化一个4D的numpy数组,第一个维度是batch,后面的维度为channel× Height x Width img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) fake_blob = TenCrop(img) for i in range(10): fake_data[i] = transform(fake_blob[i]).numpy() else: fake_data = transform(img) return fake_data
提示:
数据预处理前的数据形态: (227, 227, 3)
仅十重切割后的数据形态: (10, 227, 227, 3)
数据预处理后的数据形态: (10, 3, 227, 227)
# pcodes00409_print_results # 1. 从测试集中随机获取一个样本用于统计和可视化测试 [Q2-4] # [Your codes 13.1] dataset_test = DatasetGarbage(dataset_root_path, args=args, mode='test', isTransforms=0) i = random.randrange(0, len(dataset_test)) # 生成一个随机数用于从测试集中获取样本 img = np.transpose(np.array(dataset_test[i][0]), [1,2,0]) # 根据随机生成的整数,匹配测试集中的样本 img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 将图像从BRG转换成RGB # 2. 对数据执行数据预处理 # 2.1 对数据执行十重切割,用于显示未做预处理的样本 # [Your codes 13.2] fake_blob = TenCrop(img_rgb) # 2.2 对数据执行十重切割,并进行简单预处理 # [Your codes 13.3] fake_data = SimplePreprocessing(img,args=args, isTenCrop=True) # 3. 输出预处理后的数据形态 print('数据预处理前的数据形态: {}'.format(img.shape)) print('仅十重切割后的数据形态: {}'.format(fake_blob.shape)) print('数据预处理后的数据形态: {}'.format(fake_data.shape))
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers). Got range [-2.117904..2.4945679].
注意,在以下的图像可视化过程中,可能会出现如上的 [Warning] 信息,主要原因是我们进行预处理后,数据可能会偏离图像像数值的规定取值范围 [0, 1] 或 [0, 255],此时若强制进行可视化,则会出现警告信息,这属于正常现象。
# 1. 可视化仅执行十重切割后的图像切片 [Q2-5.1] # [Your codes 14.1] plt.figure(1, figsize=(18, 6)) for i in range(10): ax = plt.subplot(2, 5, i+1) # ax.set_title(title[i], fontproperties=myFont) # Linux 需要手动指定字体 ax.set_title(title[i], fontsize=14) plt.imshow(fake_blob[i]) plt.axis('off') # 2. 可视化执行数据预处理后的图像切片 [Q2-5.2] # [Your codes 14.2] plt.figure(2, figsize=(18, 6)) for i in range(10): ax = plt.subplot(2, 5, i+1) # ax.set_title(title[i] + 'with均值消除', fontproperties=myFont) # Linux 需要手动指定字体 # ax.set_title(title[i] + 'with均值消除') fake_img_pre = np.transpose(fake_data[i], (1,2,0)) fake_img_pre_rgb = cv2.cvtColor(fake_img_pre, cv2.COLOR_BGR2RGB) plt.imshow(fake_img_pre_rgb) plt.axis('off')