(1)案例背景
在传统的零售柜中,无论是大型超市,还是小型便利店,在居民密集区以及消费高峰时段(如周末)总是会出现结算排队的现象,这无疑使消费者的购物体验大打折扣。要缓解结算排队现象,则需要增加收银人员,增设结算通道,该解决方案人工成本的增加过于昂贵。自助扫码结算技术的出现解决了人工成本增加的问题,但是本质上该技术只是将扫描条形码的操作由收银员完成转移到了由消费者完成。在这个过程中,顾客可能遇到扫描条码失败,无法完成结算等问题,使购物结算耗费更多时间。针对以上现象,可以将一些先进技术手段运用到商品的销售过程中来,如计算机视觉的发展使得商品识别技术日益成熟,使用基于图像的目标检测技术可以提高自动化程度,减少成本,提高效率,改变传统的扫条形码的结算方式,打造“新零售”,成为了一种不可避免的趋势。
(2)实验目标
本次任务要求基于上述案例的场景,使用paddlex框架搭建YOLOv3网络,对下图中的原图进行商品检测操作。
(3)实验数据
“信也科技杯图像算法大赛——智能零售柜商品识别”官方数据集,数据共2000余张,本实验采用900余张。
(4)实施步骤
1、将图像的名称先按8:2比例提取出来,用于后期生成模型训练的训练集txt文件、验证集txt文件所需的图像及标签信息路径。
import os # 获取文件夹内容
train_precent = 0.8 # 训练集划分比例,即以8:2划分训练集、测试集
image_name = "./datasets/data/JPEGImages" # 图像数据集路径
save = "./datasets/data/ImageSets/Main" # 提取后保存路径
total_image = os.listdir(image_name) # 获取图像数据集下文件内容
num = len(total_ image) # 获取图像数据集个数
tr = int(num*train_precent) # 训练集划分个数
train = range(0,tr) # 训练集索引范围
2、对图像名称进行提取,并按比例分别划分到“ImageSets/Main”下的train.txt和val.txt文件,同时统计其个数。
# 打开train.txt和val.txt文件
ftrain = open("./datasets/data/ImageSets/Main/train.txt","w")
ftest = open("./datasets/data/ImageSets/Main/val.txt","w")
# 统计训练集及测试集图像名称个数
count_train = 0
count_val = 0
# 遍历所有图像名称,并对图像名称按比例划分
for i in range(num):
name = total_ image [i][:-4]+"\n" # 提取图像名称
# 将图像名称划分到train文件,并统计个数
if i in train:
ftrain.write(name)
count_train += 1
# 将图像名称划分到val文件,并统计个数
else:
ftest.write(name)
count_val += 1
# 关闭文件
ftrain.close()
ftest.close()
# 打印提取结果
print('train.txt文件包含{}个图像名称'.format(count_train))
print('val.txt文件包含{}个图像名称'.format(count_val))
3、接着新建代码文件生成模型训练txt数据集,这里导入实验所需模块,os用于获取相应文件夹下的内容;re用于对字符进行匹配。
import os # 获取文件夹内容
import re # 正则表达式库
4、设置生成YOLOv3模型训练所需文件txt的保存路径;并定义get_dir函数,用于生成文件路径。
# 保存路径
devkit_dir = './datasets/data'
# 定义get_dir函数(type为devkit_dir下的文件夹)
def get_dir(devkit_dir, type):
return os.path.join(devkit_dir, type)
5、定义walk_dir函数,用于获取任务2提取图像名称的train.txt、val.txt文件路径,并将文件里的内容(图像名称)与路径进行整合,生成训练集和测试集的图像路径、xml标签信息路径,并分别保存至trainval_list,test_list列表中。
# 定义walk_dir函数
def walk_dir(devkit_dir):
filelist_dir = get_dir(devkit_dir, 'ImageSets/Main') # 任务1图像名称文件路径
annotation_dir = get_dir(devkit_dir, 'Annotations') # 标签信息xml文件路径
img_dir = get_dir(devkit_dir, 'JPEGImages') # 图像数据集文件路径
trainval_list = [] # 训练集列表
test_list = [] # 测试集列表
# 遍历文件夹,提取train.txt/val.txt文件
for _, _, files in os.walk(filelist_dir):
for fname in files:
img_ann_list = [] # 中间变量,用来记录train.txt/val.txt文件里的内容
if re.match('train.txt', fname): # 判断是否为train.txt文件
img_ann_list = trainval_list
elif re.match('val.txt', fname): # 判断是否为val.txt文件
img_ann_list = test_list
else:
continue
# 获取train.txt/val.txt文件路径
fpath = os.path.join(filelist_dir, fname)
# 打开train.txt/val.txt文件
for line in open(fpath):
name_prefix = line.strip().split()[0] # 提取train.txt/val.txt文件里的内容
ann_path = os.path.join(annotation_dir, name_prefix + '.xml') # 生成xml文件路径
img_path = os.path.join(img_dir, name_prefix + '.jpg') # 生成图像文件路径
# 警告信息(如没有找到路径,则执行该语句)
assert os.path.isfile(ann_path), 'file %s not found.' % ann_path
assert os.path.isfile(img_path), 'file %s not found.' % img_path
img_ann_list.append((img_path, ann_path)) # 将图像路径、xml文件路径保存到img_ann_list中
return trainval_list, test_list
6、得到训练集及测试集的图像、xml标签信息后,我们需要将其生成txt文件,方便后期YOLOv3进行模型训练。
# 定义prepare_filelist函数
def prepare_filelist(devkit_dir, output_dir):
trainval_list = [] # 保存训练集路径信息
test_list = [] # 保存测试集路径信息
# 获取训练集、测试集图像、xml标签信息
trainval, test = walk_dir(devkit_dir)
trainval_list.extend(trainval)
test_list.extend(test)
# 生成训练集txt文件
with open(os.path.join(output_dir, 'train.txt'), 'w') as ftrainval:
for item in trainval_list:
name1 = os.path.abspath(item[0]) # 图像路径
name2 = os.path.abspath(item[1]) # xml标签信息路径
ftrainval.write(name1 + ' ' + name2 + '\n') # 写入训练集txt文件
# 生成测试集txt文件
with open(os.path.join(output_dir, 'val.txt'), 'w') as ftest:
for item in test_list:
name1 = os.path.abspath(item[0]) # 图像路径
name2 = os.path.abspath(item[1]) # xml标签信息路径
ftest.write(name1 + ' ' + name2 + '\n') # 写入测试集txt文件
# 执行程序
if __name__ == '__main__':
prepare_filelist(devkit_dir, './datasets/data')
7、上述任务我们完成了生成实验模型训练所需的txt文件,接下来新建代码文件基于paddle进行YOLOv3网络模型的搭建,将图像进行一些处理操作后输入模型,并设置相关超参数进行模型训练。这里设置工作路径:使用0为GPU(如无GPU,执行此代码后仍然会使用CPU训练模型)。
# 设置工作路径
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
8、定义数据预处理模块:对图像进行处理,以增强数据的特征,有利于增强模型的泛化能力,提高识别准确率。
# 导入实验模块
from paddlex.det import transforms
# 定义训练集图像预处理参数
train_transforms = transforms.Compose([
transforms.MixupImage(mixup_epoch=250), # 对图像进行mixup操作
transforms.RandomDistort(), # 随机像素变换
transforms.RandomExpand(), # 随机膨胀
transforms.RandomCrop(), # 随机裁剪
transforms.Resize(target_size=608, interp='RANDOM'), # 调整图像大小
transforms.RandomHorizontalFlip(), # 随机水平翻转
transforms.Normalize(), # 对图像进行标准化
])
# 定义测试集图像预处理参数
eval_transforms = transforms.Compose([
transforms.Resize(target_size=608, interp='CUBIC'), # 调整图像大小
transforms.Normalize(), # 对图像进行标准化
])
9、定义好数据相关预处理模块后,接下来就是利用pdx.datasets里的VOCDetection模块将数据输入进行处理,然后作为模型训练的输入数据。
# 导入paddle模块
import paddlex as pdx # 对训练集txt进行处理 train_dataset = pdx.datasets.VOCDetection( data_dir='datasets/data', # 文件目录
file_list='datasets/data/train.txt', # 训练集文件
label_list='datasets/data/label_list.txt', # 类型标签文件
transforms=train_transforms, # 预处理操作
shuffle=True) # 随机打乱
# 对测试集txt进行处理
eval_dataset = pdx.datasets.VOCDetection( data_dir='datasets/data', # 文件目录
file_list='datasets/data/val.txt', # 测试集文件
label_list='datasets/data/label_list.txt', # 类型标签文件
transforms=eval_transforms) # 预处理操作
10、得到处理过的模型训练输入数据后,接下来我们将基于paddle搭建YOLOv3网络模型,并设置好迭代次数、学习率等参数进行模型训练。
# 加载类型标签文件
num_classes = len(train_dataset.labels) # 获取类别个数
# 导入paddle框架的YOLOv3网络模型,骨干网络采用ResNet50
model = pdx.det.YOLOv3(num_classes=num_classes, backbone='DarkNet53') loss = model.train( num_epochs=50, # 迭代次数
train_dataset=train_dataset, # 训练集
train_batch_size=4, # 单批次输入大小
eval_dataset=eval_dataset, # 验证集
learning_rate=0.000125, # 学习率
lr_decay_epochs=[20, 40], # 学习率衰减轮数
save_interval_epochs=10, # 每10次保存一次模型
save_dir='output/yolov3_darknet53', # 模型输出路径
use_vdl=True)
11、训练完成后利用测试集评估模型。
# 加载模型
model = pdx.load_model('output/yolov3_darknet53/best_model') # 查看效果
map = model.evaluate(eval_dataset, batch_size=5, epoch_id=None, metric=None, return_details=False)
print(map)
如模型在测试集上的效果不佳,可通过修改模型参数,如迭代次数、数据集数量等,虽然模型训练较久,但对于提升模型的效果却有着明显的作用。
12、训练结束后,代码目录下会生成” best_model”文件夹,打开会看到“eval_details.json”,“model.pdmodel”,“model.pdopt”,“model.paparams”,“model_yml”这五个文件,这五个文件就是YOLOv3网络导出的参数和权重文件。
13、接着导入实验所需模块,准备可视化检测结果。
import paddlex as pdx # 加载模型
import time # 时间模块
import matplotlib.pyplot as plt # 显示图像
from PIL import Image # 读取图像
%matplotlib inline # 在juputer上显示图像
14、导入模型对image下的图像进行预测,并设置阈值threshold对检测结果进行筛选,提取识别度高一点的检测结果;同时将推理时间、检测结果(商品名及置信度)进行输出。
model = pdx.load_model('output/yolov3_darknet53/best_model') # 加载模型
image_name = './image/test.jpg' # 待预测图像
start = time.time() # 获取当前时间
result = model.predict(image_name) # 执行预测
print('1.模型推断时间:{:.3f}s'.format(time.time()-start)) # 模型推理时间
print('2.检测结果个数:', len(result)) # 检测结果
count = 0 # 统计符合要求个数
threshold = 0.19 # 设置阈值
cls_list = [] # 保存检测类型
score_list = [] # 保存置信度
# 遍历检测结果
for value in result:
cls = value['category'] # 检测信息
score = value['score'] # 置信度
if score < threshold:
continue
count += 1
cls_list.append(cls)
score_list.append(score)
print('3.不满足threshold值个数:', len(result)-count)
print('4.满足threshold值的检测结果:\n')
for i,t in zip(cls_list,score_list):
print("商品名:",i,"置信度:",t)
输出结果如下:
15、得到商品的检测类型及置信度后,我们可以对结果进行可视化输出。如这里设置阈值为0.19,表示商品检测置信度大于0.19的便进行绘制目标框,绘制好的图像保存在“output”目录下(如无output目录会自动生成),随后加载图像,并利用plt模块进行可视化显示。
pdx.det.visualize(image_name, result, threshold=0.19, save_dir='./output') # 设置阈值为0.19,并将检测结果保存在"./output"下
img = Image.open("./output/visualize_test_0.jpg") # 读取图像
plt.figure(figsize=(15,12)) # 设置画布比例
plt.imshow(img) # 读取图像信息
plt.axis('on') # 关掉坐标轴为off
plt.show() # 显示图像
输出结果如下: