数据准备:
- 首先,通过!unzip命令将包含数据的zip文件解压缩到指定的目录(data/)中。
- 然后,使用一个Python脚本(未在代码中显示,但被注释掉了)创建了一个数据列表,该列表包含用于训练的图像文件路径和相应的标注文件路径。该数据列表可能包括数据集中图像和相应标注的信息。
- # 解压数据文件
- !unzip -d data/ data/data85408/roadsign_voc.zip
- # 创建数据列表
- # !python data/create_list.py images annotations .png 0.8
引入需要的库
import os
import matplotlib.pyplot as plt
import numpy as np
import xml.etree.ElementTree as et
from PIL import Image, ImageDraw
目标边框可视化:
- 定义了一个名为BndBox的Python类,该类用于可视化目标边框。
- 在BndBox类的初始化函数中,传入了训练数据的列表文件路径和标签文件路径,以及一些类别颜色信息。
- 在draw方法中,遍历了训练数据列表,读取图像和相应的XML标注文件。
- 对于每张图像,解析XML标注以提取目标类别和边框信息,并使用matplotlib绘制边框。这个过程被重复执行,以便在前三张图像上绘制边框。
- 最后,使用plt.show()来显示绘制有边框的图像。
- class BndBox():
- def __init__(self, lists_txt, label_txt):
- """
- 初始化目标边框
- params:
- - lists_txt: 列表文件
- - label_txt: 标签文件
- - lists_dir: 列表目录
- """
- self.lists_txt = lists_txt
- self.label_txt = label_txt
- self.lists_dir = os.path.split(lists_txt)[0] # 列表目录
- self.cname2cid = self.get_cname2cid() # 标签字典
- self.cls_color = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0)] # 类别颜色
- def draw(self):
- """
- 绘制目标边界框
- """
- # 创建画布
- plt.figure(figsize=(18, 6))
- # 绘制边框
- with open(self.lists_txt, 'r') as f:
- for i, line in enumerate(f.readlines()):
- # 读取标注
- if i > 2: # 读取前三张图片
- break
- image_path, annotation_path = line.strip().split() # 提取信息
- image = Image.open(os.path.join(self.lists_dir, image_path)) # 打开图像
- annotation = et.parse(os.path.join(self.lists_dir, annotation_path)) # 打开标注
- draw = ImageDraw.Draw(image) # 创建画笔
- # 绘制边框
- img_w = float(annotation.find('size').find('width').text) # 图像宽度
- img_h = float(annotation.find('size').find('height').text) # 图像高度
- object_list = annotation.findall('object') # 目标列表
- gtcls = np.zeros((len(object_list), ), dtype='float32') # 目标类别
- gtbox = np.zeros((len(object_list), 4), dtype='float32') # 目标边框
- for object_id, object_item in enumerate(object_list):
- # 获取目标类别
- cname = object_item.find('name').text # 类别名称
- gtcls[object_id] = self.cname2cid[cname] # 类别编号
- # 获取目标边框
- x1 = float(object_item.find('bndbox').find('xmin').text)
- y1 = float(object_item.find('bndbox').find('ymin').text)
- x2 = float(object_item.find('bndbox').find('xmax').text)
- y2 = float(object_item.find('bndbox').find('ymax').text)
- x1 = max(0, x1)
- y1 = max(0, y1)
- x2 = min(x2, img_w - 1)
- y2 = min(y2, img_h - 1)
- gtbox[object_id] = [x1, y1, x2, y2]
- # 绘制边框
- draw.rectangle(xy=gtbox[object_id], outline=self.cls_color[int(gtcls[object_id])], width=3)
- # 绘制图像
- image = np.array(image, dtype='uint8')
- plt.subplot(1, 3, i+1)
- plt.axis('off')
- plt.imshow(image)
- # 显示图像
- plt.tight_layout()
- plt.show()
- def get_cname2cid(self):
- """
- 获取标签字典
- return:
- - cname2cid: 标签字典
- """
- # 文件是否存在
- if not os.path.exists(self.label_txt):
- print(f'错误:{self.label_txt}不存在!')
- sys.exit()
- # 设置标签字典
- cname2cid = {} # 标签字典
- with open(label_txt, 'r') as f:
- for cid, cname in enumerate(f.readlines()):
- cname2cid[cname.strip()] = cid
- return cname2cid
- # 绘制目标边框
- lists_txt = './data/train.txt' # 列表文件
- label_txt = './data/label.txt' # 标签文件
- bndbox = BndBox(lists_txt, label_txt)
- bndbox.draw()
数据加载和增强:
- 导入与数据加载和数据增强相关的库,例如VOCDataset和一些自定义的数据增强操作。
- 设置数据增强的变换,包括随机失真、随机扩展、随机裁剪和随机翻转等,这些变换可以增强训练数据。
- 使用VOCDataset加载训练数据集,该数据集根据提供的训练数据列表文件和标签文件,应用了定义的数据增强操作。
- 对增强后的数据进行可视化,以确保数据增强操作有效。
- 遍历加载的训练数据,并显示原始图像和相应的增强图像,以及它们的边界框。这有助于验证数据增强操作是否正确应用
- import matplotlib.pyplot as plt
- import matplotlib.patches as patches
- import paddle
- from src.data import VOCDataset, Compose, RandomDistort, RandomExpand, RandomCrop, RandomFlip, BatchCompose, RandomResize, PadClassBox
- # 读取数据
- train_txt = './data/train.txt' # 训练文件
- label_txt = './data/label.txt' # 标签文件
- batch_size = 3 # 批样本数
- worker_num = 0 # 子进程数
- transforms = Compose([RandomDistort(), # 随机变换图像
- RandomExpand(fill_value=(123.675, 116.28, 103.53)), # 随机扩大图像
- RandomCrop(), # 随机裁剪图像
- RandomFlip() # 随机水平翻转
- ]) # 数据增强
- train_dataset = VOCDataset(train_txt, label_txt, transforms) # 训练数据
- batch_sampler = paddle.io.DistributedBatchSampler(dataset=train_dataset,
- batch_size=batch_size,
- shuffle=False,
- drop_last=False) # 批采样器
- batch_transforms = BatchCompose([RandomResize(dsize=[320, 352, 384, 416, 448, 480, 512, 544, 576, 608]), # 随机缩放图像
- PadClassBox(num_max=50) # 填充类别边框
- ]) # 批次数据增强
- train_loader = paddle.io.DataLoader(dataset=train_dataset,
- batch_sampler=batch_sampler,
- collate_fn=batch_transforms,
- num_workers=worker_num,
- return_list=False,
- use_buffer_reader=True,
- use_shared_memory=False) # 训练集迭代器
- # 显示图像
- for i, data in enumerate(train_loader()):
- # 创建画布
- print(f'train_dataset - image:{data[0].shape}, gtcls:{data[1].shape}, gtbox:{data[2].shape}, imghw:{data[3].shape}')
- plt.figure(figsize=(18, 6))
- # 绘制边框
- for j in range(batch_size):
- # 读取图像边框
- image = data[0].numpy()[j] # 读取图像
- gtbox = data[2].numpy()[j] # 读取边框
- # 设置子图坐标
- ax = plt.subplot(1, 3, j+1) # 获取子图坐标轴
- ax.xaxis.set_ticks_position('top') # 设置上面坐标轴为x坐标轴
- ax.spines['top'].set_position(('data', 0)) # 刻度从零开始
- ax.yaxis.set_ticks_position('left') # 设置左边坐标轴为y坐标轴
- ax.spines['left'].set_position(('data', 0)) # 刻度从零开始
- ax.spines['bottom'].set_color('none') # 关闭下面坐标轴
- ax.spines['right'].set_color('none') # 关闭右边坐标轴
- # 绘制图像边框
- for k in range(len(gtbox)): # 遍历边框
- if (gtbox[k, 2] - gtbox[k, 0]) > 1e-3 and (gtbox[k, 3] - gtbox[k, 1]) > 1e-3:
- # 获取边框坐标
- x = gtbox[k, 0]
- y = gtbox[k, 1]
- w = gtbox[k, 2] - gtbox[k, 0]
- h = gtbox[k, 3] - gtbox[k, 1]
- # 绘制目标边框
- rectangle = patches.Rectangle((x, y), w, h, linewidth=1, edgecolor='r', fill=False) # 设置边框
- ax.add_patch(rectangle) # 绘制边框
- ax.imshow(image) # 填充子图图像
- # 显示图像
- plt.tight_layout()
- plt.show()
- break
模型架构:
- 导入与YOLOv3模型架构相关的库,包括骨干网络(DarkNet53)、检测颈部(YOLOv3FPN)和检测头部(YOLOv3Head)。
- 创建YOLOv3模型的各个部分,包括骨干网络、检测颈部和检测头部
- import paddle
- from src.model import DarkNet53, YOLOv3FPN, YOLOv3Head
- # 定义网络
- backbone = DarkNet53() # 骨干网络
- neck = YOLOv3FPN() # 检测颈部
- head = YOLOv3Head(num_classes=4) # 检测头部
- # 输入数据
- x = paddle.randn([1, 3, 608, 608], dtype='float32') # BCHW格式数据
- # 处理数据
- c_list = backbone(x)
- t_list = neck(c_list)
- p_list = head(t_list)
- # 输出特征
- print(f'骨干网络: c0: {c_list[0].shape}, c1: {c_list[1].shape}, c2: {c_list[2].shape}')
- print(f'检测颈部: t0: {t_list[0].shape}, t1: {t_list[1].shape}, t2: {t_list[2].shape}')
- print(f'检测头部: p0: {p_list[0].shape} , p1: {p_list[1].shape} , p2: {p_list[2].shape}')
导入库和模块:首先,代码导入了PaddlePaddle库,以及与数据加载和模型相关的自定义模块。这些模块包括DataLoader和YOLOv3。
定义训练数据加载器:通过指定训练数据文件(train_txt)和标签文件(label_txt)的路径,创建了一个数据加载器(train_loader)。这个数据加载器将用于从数据集中加载训练样本。在这里,使用了DataLoader类,它负责加载训练数据,并可以指定批次大小、工作进程数和模式(这里是训练模式)。
定义YOLOv3模型:创建了一个YOLOv3模型(model),并通过YOLOv3类进行实例化。模型的num_classes参数被设置为4,表示要检测的目标类别数。接着,通过调用model.train()方法将模型设置为训练模式,以确保它在训练过程中执行必要的操作,如计算损失。
处理数据:代码使用一个for循环遍历了训练数据加载器(train_loader),并在每次迭代中执行以下操作:
从加载器中获取一个批次的数据,其中包括图像数据(images)和相应的标签等信息。
将图像数据(images)通过YOLOv3模型(model)进行前向传播,得到预测结果(p_list)。YOLOv3是一个目标检测模型,它在图像中识别和定位目标对象。
计算损失(losses):代码调用model.losses()方法,传递预测结果(p_list)和批次的输入数据(inputs),用于计算损失函数的值。
显示结果:最后,代码打印出批次中的预测损失总和。这个损失值用于衡量模型在当前批次上的性能和训练进展情况。
import paddle
from src.data import DataLoader
from src.model import YOLOv3
# 训练数据
train_txt = './data/train.txt' # 训练文件
label_txt = './data/label.txt' # 标签文件
train_loader = DataLoader(train_txt, label_txt, batch_size=1, worker_num=0, mode='train') # 训练数据读取器
# 定义网络
model = YOLOv3(num_classes=4) # 检测网络
model.train() # 训练模式
# 网络参数
# params = paddle.summary(model, (1, 3, 608, 608))
# print('网络参数:', params)
# 处理数据
for inputs in train_loader:
# 读取图像
images = inputs['image']
# 前向传播
p_list = model(images)
# 计算损失
losses = model.losses(p_list, inputs)
break
# 显示结果
print(f'网络损失: losses: {losses.numpy()}') # 打印批次预测损失总和
- 导入库和模块:首先,代码导入了PaddlePaddle库以及与数据加载和模型相关的自定义模块。这些模块包括DataLoader和YOLOv3。
- 定义验证数据加载器:通过指定验证数据文件(valid_txt)和标签文件(label_txt)的路径,创建了一个数据加载器(valid_loader)。这个数据加载器将用于从验证数据集中加载数据进行验证。在这里,使用了DataLoader类,它负责加载验证数据,批次大小为1,表示每次验证一张图像。
- 定义YOLOv3模型:创建了一个YOLOv3模型(model),并通过YOLOv3类进行实例化。模型的num_classes参数被设置为4,表示要检测的目标类别数。然后,通过调用model.eval()方法将模型设置为验证模式,以确保它在验证过程中不执行任何训练相关的操作。
- 加载权重:代码指定了训练过程中保存的模型权重的路径(save_path),然后使用PaddlePaddle的paddle.load()方法加载这些权重。加载后的权重通过model.set_state_dict()方法设置到模型中,以恢复模型的参数。
- 处理数据:代码使用一个for循环遍历了验证数据加载器(valid_loader),并在每次迭代中执行以下操作:
- 从加载器中获取一张图像的数据(images)和图像的高度和宽度信息(imghws)。
- 前向传播:将图像数据(images)通过YOLOv3模型(model)进行前向传播,得到预测结果(p_list)。YOLOv3模型会在图像中检测目标对象,并返回检测结果。
- 计算预测结果:通过model.infers()方法,将预测结果(p_list)和图像高度和宽度信息(imghws)传递给模型,用于计算目标检测的结果(infers)。
- 显示结果:最后,代码遍历了批次中的每个目标检测结果(infers),并打印出每个批次中检测到的物体数量。这可以用于评估模型在验证数据集上的性能。
import paddle
from src.data import DataLoader
from src.model import YOLOv3
# 验证数据
valid_txt = './data/valid.txt' # 验证文件
label_txt = './data/label.txt' # 标签文件
valid_loader = DataLoader(valid_txt, label_txt, batch_size=1, worker_num=0, mode='valid') # 验证数据读取器
# 定义网络
model = YOLOv3(num_classes=4) # 检测网络
model.eval() # 验证模式
# 加载权重
save_path = './out/' # 权重路径
model_state_dict = paddle.load(save_path + 'model.pdparams') # 加载权重
model.set_state_dict(model_state_dict) # 设置权重
# 处理数据
for inputs in valid_loader:
# 读取数据
images = inputs['image'] # 图像数据
imghws = inputs['imghw'] # 图像高宽
# 前向传播
p_list = model(images)
# 计算预测
infers = model.infers(p_list, imghws)
break
# 显示结果
for infer in infers: # 遍历批次
print(f'网络预测: infer: {len(infer)}') # 打印每批预测物体数量
导入训练器模块:首先,代码导入了自定义的训练器模块,其中包括了一个名为Trainer的训练器类。
配置和启动目标检测模型的训练过程,包括设置各种训练参数和启动训练循环,以使模型逐渐学习目标检测任务
from src.train import Trainer
# 实例化训练器
trainer = Trainer(train_batch=16, # 训练批次大小
valid_batch=12, # 验证批次大小
num_classes=4, # 物体类别数量
bbox_thresh=0.50, # 验证边框阈值
train_txt ='./data/train.txt', # 训练文件路径
valid_txt ='./data/valid.txt', # 验证文件路径
label_txt ='./data/label.txt', # 标签文件路径
save_path ='./out/', # 保存文件路径
load_flag ='none', # 加载断点标志
epoch_num =600, # 训练轮次轮数
iter_show =3, # 迭代显示周期
eval_save =5, # 验证保存周期
pie_epoch =[360, 480], # 分段衰减轮数
pie_value =[0.001, 0.0001, 0.00001], # 分段学习率值
lin_epoch =5, # 线性预热轮数
lin_start =0, # 线性始学习率
lin_ended =0.001) # 线性止学习率
# 启动训练数据
trainer.train()
导入推理器模块:首先,代码导入了自定义的推理器模块,其中包括了一个名为Inferer的推理器类。
实例化推理器:通过创建一个Inferer类的实例,设置了许多推理相关的参数,以配置推理过程。
启动推理:最后,通过调用inferer.infer()方法启动推理过程。这会使用指定的模型参数、输入图像和其他配置参数来进行目标检测推理。
from src.infer import Inferer
# 实例化推理器
inferer = Inferer(num_classes=4, # 物体类别数量
model_param='./out/model.pdparams', # 模型参数路径
label_path ='./data/label.txt', # 物体标签路径
output_dir ='./out/', # 输出结果目录
scale_size =608, # 图像缩放大小
bbox_thick =1, # 物体边框粗细
font_thick =1, # 文本边框粗细
font_scale =0.4) # 文本字体大小
# 启动推理图像
inferer.infer(image_path='./data/images/road554.png')
总结:
本次实验不仅加深了我们对计算机视觉和深度学习的理解,还提高了我们的问题解决能力。通过自己动手完成整个实验过程,我们学到了如何处理真实世界的图像数据和应用深度学习模型进行目标检测的技能。训练了一个YOLOv3目标检测模型,该模型在验证集上取得了良好的性能。
实验中,我们学会了如何准备和处理图像数据,选择适当的深度学习模型,并进行训练和验证。
我们了解了模型训练过程中的参数调整对性能的影响,以及如何使用不同的评估指标来衡量模型的准确性。