基于YOLOv5的中式快餐店菜品识别系统[金鹰物联智慧食堂项目]


摘要

本文基于YOLOv5v6.1提出了一套适用于中式快餐店的菜品识别自助支付系统,综述了食品识别领域的发展现状,简要介绍了YOLOv5模型的历史背景、发展优势和网络结构。在数据集预处理过程中,通过解析UNIMIB2016,构建了一套行之有效的标签格式转换与校验流程,解决了YOLOv5中文件路径问题、标签格式转换问题和因EXIF信息的存在而导致的标记错位问题。在模型训练阶段,配置了云服务器,引入了Weights and Bias可视化工具,实现了在线监督训练和sweep超参数调优的功能,在sweep中使用hyperband剪枝算法加速了sweep过程,并且给出了对于训练过程中可能出现的问题的解决方法。最后介绍了目标识别领域的评价指标和YOLOv5的损失函数,分析了sweep超参数调优的结果,选取最优参数组合训练模型,通过分析样本分布、PR曲线等,选取最佳预测置信度,大幅提升了预测精度和召回率,部署了模型并制作了客户端


引言

随着智能信息化时代的到来,人工智能与传感技术取得了巨大进步,在智能交通、智能家居、智能医疗等民生领域产生积极正面影响。其中,社交网络、移动网络和物联网等新兴技术产生了食品大数据,这些大数据与人工智能,尤其是快速发展的深度学习催生了新的交叉研究领域食品计算。现在,在智慧健康、食品智能装备、智慧餐饮、智能零售及智能家居等方面都可以找到食品大数据与人工智能相结合的例子。

人工智能时代下的食品图像识别是当前计算机视觉研究的重要领域之一。我们希望研发一种可快速且高效识别菜品的校园菜肴识别系统,在校园食堂中应用本系统,可缩短收银员计算价格的时间、简化收银步骤;可协助管理者精准备餐、减少库存的浪费;就餐者还可以即时看见摄入的食物营养价值,实现膳食平衡;另外,可迅速实现食品的安全溯源,避免出现食品安全情况。

传统的食物图像识别方法是选择图像特征,然后使用某些方法(比如SIFT、HOG)提取图像特征点,再将特征点用矢量表示,最后采用机器学习的方法训练分类器(如SVM、K-Means)。传统食物图像识别提取特定特征或者关键点对食物进行分类,但在实际应用中,拍摄的图像会受到环境的光照强度、噪声干扰、环境光等外部因素的干扰,导致拍摄图像质量参差,从而影响最终的检测结果同一事物的颜色形状会有差异,不同食物直接的颜色形状也会相同。所以传统的图像识别方法很难准确识别出食物。

深度学习的发展使得当前大部分工作均采用卷积神经网络,思路是先对菜品图像中不同的菜品区域进行检测或分割,然后对其区域进行识别。从2014年开始,基于深度学习的目标检测网络井喷式爆发,先是二阶段网络,如R-CNN、Fast-RCNN、Mask-RCNN等,自2016年Joseph等提出You only Look Once(YOLOv1)以来学者者们的视野,开启了单阶段目标检测网络的新纪元。YOLO均是对单阶段目标检测模型改进的研究,为各研究领域提供了更快、更好的目标检测方法,也为单阶段目标检测算法的实际应用提供了重要理论保障。例如 Aguilar 等人微调物体检测算法 YOLOv2 来进行多种食物检测和识别。又如 Pandey 等人微调了 AlexNet、GoogLeNet、ResNet等三种CNN网络,然后基于微调的网络提取和融合来自不同网络的视觉特征,通过集成学习方法实现菜品图像识别。随着深度学习的发展,卷积神经网络(CNN)在各领域中获得不俗的效果,菜品识别也围绕卷积神经网络展开研究,不仅提出了新的方法,也提升了检测精度。

2020 年 6 月 10 日 YOLOv5 发布,随着版本迭代更新,其已成为现今最先进的目标检测技术之一。YOLOv5 使用Pytorch框架,对用户非常友好,能够方便地训练自己的数据集;能够直接对视频甚至网络摄像头端口输入进行有效推理,有着高达140FPS的目标识别速度;能够轻松的将Pytorch权重文件转化为安卓使用的ONXX格式,或者通过CoreML转化为IOS格式,以便直接部署到手机应用端。

YOLO的核心思想就是将整张图片作为网络的输入,利用“分而治之”的思想,对图片进行网格划分,直接在输出层回归边界框的检测位置及其所属的类别。与Faster R-CNN相比,YOLO产生的背景错误要少得多。通过使用YOLO来消除Faster RCNN的背景检测,可以显着提高模型性能。实验表明YOLO v5可以达到比Faster R-CNN更快的收敛速度,并且在小目标的检测上比SSD模型更加准确。

数据集

数据集来源和说明

本文所使用的托盘食物数据集来源于 UNIMIB2016 Food Database. 此数据集在真实餐厅环境中收集而来,每张照片的尺寸为 (3264, 2448),包含一个托盘和托盘上不同的食物,有些食物放在餐具垫上而非碟子中。有时,多种菜会被放置在同一碟子中,这给图像分割带来了困难。此外,图像畸变和光线环境等影响也会给分割和识别带来挑战。

The dataset has been collected in a real canteen environment. The particularities of this setting are that each image depicts different foods on a tray, and some foods (e.g. fruit, bread and dessert) are placed on the placemats rather than on plates. Sides are often served in the same plate as the main dish making it difficulty to separate the two. Moreover, the acquisition of the images has been performed in a semi-controlled settings so the images present visual distortions as well as illumination changes due to shadows. These characteristics make this dataset challenging requiring both the segmentation of the trays for food localization, and a robust way to deal with multiple foods.

如图3所示,在数据集中,许多类别的食物非常相似,例如,有四种不同的“Pasta al sugo”,其中添加了其他主要成分(如鱼肉、蔬菜或者其他的一些肉类)。最后,托盘上可能有其他物品造成干扰,比如有智能手机、钱包、校园卡等等。

Figure 3, many food classes have a very similar appearance. For example, we have four different “Pasta al sugo”, but with other main ingredients (e.g. fish, vegetables, or meat) added. Finally, on the tray there can be other “noisy” objects that must be ignored during the recognition. For example, we may find cell phones, wallets, id cards, and other personal items. For these reasons we need to design of a very accurate recognition algorithm.

数据集处理

作者团队一共收集了1442张照片,去除模糊和重复照片后,将剩余有效图片保存在UNIMIB2016-images中。其中,包含1027张照片,共计73种菜品,总计3616个菜品实例。一些种类的食物只是在成分上有所不同,所以命名为“FoodName 1”, “FoodName 2”.

接下来,处理UNIMIB2016-annotations.zip中的annotations.mat文件,将其转换为yolo格式。

UNIMIB2016-annotations中,存有annotations.mat标记文件,.mat文件是Matlab的Map对象(Map object),其介绍如下:

A Map object is a data structure that allows you to retrieve values using a corresponding key. Keys can be real numbers or character vectors. As a result, they provide more flexibility for data access than array indices, which must be positive integers. Values can be scalar or nonscalar arrays.

MAT文件解析

若使用scipy.io.loadmat工具解析.mat文件,如需要加载annotations.mat,在Map object多级嵌套时,解析可能出现意想不到的错误,故编写Matlab脚本将annotations.mat文件解析为YOLOv5所需的标记文件格式。

% .
% ├── annotations.mat
% ├── demo.m
% ├── formatted_annotations
% │   ├── 20151127_114556.txt
% │   ├── 20151127_114946.txt
% │   ├── 20151127_115133.txt
% │   ├── ...
% │   └── 20151221_135642.txt
% └── load_annotations.m%% load_annotations.mclc; clear;% output path
output = './formatted_annotations/';% Load the annotations in a map structure
load('annotations.mat');% Each entry in the map corresponds to the annotations of an image.
% Each entry contains many cell tuples as annotated food
% A tuple is composed of 8 cells with the annotated:
% - (1) item category (food for all tuples)
% - (2) item class (e.g. pasta, patate, ...)
% - (3) item name
% - (4) boundary type (polygonal for all tuples)
% - (5) item's boundary points [x1,y1,x2,y2,...,xn,yn]
% - (6) item's bounding box [x1,y1,x2,y2,x3,y3,x4,y4]image_names = annotations.keys;n_images = numel(image_names);for j = 1 : n_imagesimage_name = image_names{j};tuples = annotations(image_name);count = size(tuples,1);coordinate_mat = cell2mat(tuples(:,6));% open filefile_path = [output image_name '.txt'];ffile = fopen(file_path, 'w');% write filefor k = 1 : countitem = tuples(k,:);fprintf(ffile, '%s %d %d %d %d %d %d %d %d\n', ...string(item(2)), ...  % item classcoordinate_mat(k,:)); % item's bounding boxend% close filefclose(ffile);end%% fprintf
% Write data to text file
% https://www.mathworks.com/help/matlab/ref/fprintf.html

运行上述Matlab脚本文件,在./formatted_annotations文件夹下生成以图片名命名的*.txt文件,每一行的格式为class x1 y1 x2 y2 x3 y3 x4 y4.

bounding box如图所示:(xy1左上,xy3右下)

数据集有效性检验

下载并解压 [UNIMIB2016-images.zip]./original文件夹内为所有图片数据。将 original文件夹重命名为images,今后该文件夹用来存放图片数据,否则YOLOv5模型训练会发生错误,具体原因请看 一文彻底解决YOLOv5训练找不到标签问题。编写check_dataset.py,检查formatted_annotations中标签文件是否和images中图像文件一一对应,删除无效的标签和不匹配的标签。

# UNIMIB2016
# ├── UNIMIB2016-annotations
# │   ├── check_dataset.py <--
# │   └── formatted_annotations
# └── images# check_dataset.pyimport os# path of formatted_annotations
f_path = os.path.join(os.getcwd(), 'formatted_annotations')# path of images
img_path = os.path.join(os.getcwd(), os.pardir, 'images')def check_dataset():annotations = [i[:-4] for i in os.listdir(f_path)]imgs = [i[:-4] for i in os.listdir(img_path)]for annotation in annotations:label = annotation + '.txt'label_path = os.path.join(f_path, label)try:if annotation not in imgs:# remove annotation which is not in imagesprint('not found image: {}, remove its annotation'.format(annotation))print(label_path)raise FileExistsErrorelse:# check extra spaces in a linewith open(label_path) as f:lines = f.readlines()for line in lines:item = line.split()if len(item) > 9:print('wrong label format: {}, {}'.format(annotation, line))raise FileExistsErrorexcept FileExistsError:os.remove(label_path)print('os.remove({})'.format(label_path))if __name__ == '__main__':check_dataset()

部分输出如下,check_dataset.py检查出21份在images中找不到对应图片的*.txt标记文件,检查出1份在类别标签中含有空格的*.txt标记文件,剔除这22份无效标记文件后,formatted_annotations中还剩余1005份有效标记文件。

食物类别统计

编写class_count.py,生成formatted_annotations中所有食品种类的统计数据:

# UNIMIB2016
# ├── UNIMIB2016-annotations
# │   ├── check_dataset.py
# │   ├── class_count.py <--
# │   └── formatted_annotations
# └── images# class_count.pyimport os
import pandas as pd# formatted_annotations path
path = os.path.join(os.getcwd(), 'formatted_annotations')# output path
output = os.path.join(os.getcwd(), 'class_counts_result.csv')# read file list of formatted_annotations
annotations = os.listdir(path)if __name__ == '__main__':labels = []for annotation in annotations:with open(os.path.join(path, annotation)) as file:for line in file:item = line.split()cls = item[0]labels.append(cls)counts = pd.Series(labels).value_counts()counts.to_csv(output, header=False)

分类统计结果存于class_counts_result.csv. 部分统计数据如下:(未进行上一目有前性检验前共73个分类),按出现次数从高到低,从0开始为每个分类进行编号。

Class Num
pane 479
mandarini 198
carote 161
patate/pure 151
cotoletta 148
fagiolini 131
yogurt 130

标签格式转换

接下来编写python脚本,将这些数据转换为YOLOv5所需格式:

编写toYolo.py,将formatted_annotations中所有*.txt转换为Yolo格式,将生成的结果存于labels中。

# UNIMIB2016
# ├── UNIMIB2016-annotations
# │   ├── check_dataset.py
# │   ├── class_count.py
# │   ├── toYolo.py <--
# │   ├── class_counts_result.csv
# │   └── formatted_annotations (1005)
# ├── labels
# └── images (1005)# toYolo.pyimport os
from PIL import Image# formatted_annotations path
path = os.path.join(os.getcwd(), 'formatted_annotations')# path of images
img_path = os.path.join(os.getcwd(), os.pardir, 'images')# output path
output_path = os.path.join(os.getcwd(), os.pardir, 'labels')# class count file path
class_file_path = os.path.join(os.getcwd(), 'class_counts_result.csv')def convert_box(size, box):# convert VOC to yolo formatdw, dh = 1. / size[0], 1. / size[1]x, y, w, h = (box[0] + box[1]) / 2.0, (box[2] + box[3]) / 2.0, box[1] - box[0], box[3] - box[2]return [x * dw, y * dh, w * dw, h * dh]def convert_bbox(ibb):# convert ibb to VOC format# ibb = [x1,y1,x2,y2,x3,y3,x4,y4]X = ibb[0::2]Y = ibb[1::2]xmin = min(X)ymin = min(Y)xmax = max(X)ymax = max(Y)return xmin, ymin, xmax, ymaxdef get_classes():# output: class listcf = open(class_file_path, 'r')clss = [line.split(',')[0] for line in cf.readlines()]cf.close()return clssdef toYolo():# read file list of formatted_annotationsannotations = os.listdir(path)# get class listclss = get_classes()# convert every annotation in ./formatted_annotations/ to yolo formatfor annotation in annotations:with open(os.path.join(path, annotation)) as file, open(os.path.join(output_path, annotation), 'w') as opfile:# read imgimg_f_path = os.path.join(img_path, annotation[:-4] + '.jpg')img = Image.open(img_f_path)# get img sizesize = img.size# process every item in ./formatted_annotations/*.txtfor line in file:item = line.split(' ')# get class numcls = item[0]cls_num = clss.index(cls)# get bbox coordinatesitem_bounding_box = list(map(float, item[1:]))xmin, ymin, xmax, ymax = convert_bbox(item_bounding_box)b = [xmin, xmax, ymin, ymax]bb = convert_box(size, b)# append item to output file: ../labels/*.txtitem_str = list(map(str, [cls_num] + bb))line_yolo = ' '.join(item_str)opfile.write(line_yolo + '\n')print(annotation)if __name__ == '__main__':toYolo()

数据集校验

图片修正

由于 EXIF Rotation Information 的存在,在 YOLOv5 使用的 cv2 读取图片时,对图片参考系的选取产生影响,导致labels偏离原图片,故需要对图片进行修正,具体原因请查阅 yolov5踩坑记录:标签错位(PIL读取图片方向异常)。

修正前(标记错位)

修正后

修正代码
# UNIMIB2016
# ├── UNIMIB2016-annotations
# │   ├── check_dataset.py
# │   ├── class_count.py
# │   ├── toYolo.py
# │   ├── class_counts_result.csv
# │   └── formatted_annotations
# ├── rectify_imgs.py <--
# ├── labels (1005)
# └── images (1005)# rectify_imgs.pyimport os
from PIL import Image
import numpy as np# image type
img_type = '.jpg'# image folder path
path = os.path.join(os.getcwd(), 'images')def rectify_imgs():for img_name in os.listdir(path):if not img_name[-4:] == img_type:continueimg_path = os.path.join(path, img_name)img = Image.open(img_path)img_rectified = Image.fromarray(np.asarray(img))img_rectified.save(img_path)print(img_name)if __name__ == '__main__':rectify_imgs()

标签正确性检验

完成上述所有数据集准备工作后,编写labels_shower.py模块,随机选取n张图片,使用 YOLOv5内的图像加载和标记函数,校验 labels文件夹中标记是否正确转换。

# .
# ├── datasets
# │   └── UNIMIB2016
# │       ├── UNIMIB2016-annotations
# │       ├── images
# │       ├── labels
# │       └── split
# └── yolov5
#     └── labels_shower.py <--# labels_shower.pyimport os
import yaml
import numpy as np
from random import sample
from utils.general import xywhn2xyxy
from utils.plots import Annotator
from utils.general import cv2
from utils.datasets import LoadImages
from utils.plots import Colorsn = 5  # how many images you want to show# file path set# ../datasets/UNIMIB2016/labels/
labels_path = os.path.join(os.path.pardir, 'datasets', 'UNIMIB2016', 'labels')
# ../datasets/UNIMIB2016/images/
imgs_path = os.path.join(os.path.pardir, 'datasets', 'UNIMIB2016', 'images')
# data/UNIMIB2016.yaml
cls_path = os.path.join(os.getcwd(), 'data', 'UNIMIB2016.yaml')# model data preparation
# you shouldn't change them
pt = True
stride = 2
imgsz = (640, 640)
datasets = os.listdir(labels_path)
line_thickness = 3  # bounding box thickness (pixels)
colors = Colors()  # create instance for 'from utils.plots import colors'
with open(cls_path, errors='ignore') as f:names = yaml.safe_load(f)['names']  # class namesdef labels_shower():sources = sample(datasets, n)for source in sources:# Add bbox to imagewith open(os.path.join(labels_path, source)) as file:lines = file.readlines()dataset = LoadImages(os.path.join(imgs_path, source[:-4] + '.jpg'),img_size=imgsz, stride=stride, auto=pt)im0s = dataset.__iter__().__next__()[2]im0 = im0s.copy()annotator = Annotator(im0, line_width=line_thickness, example=str(names))for line in lines:annot = line.split()c = int(annot[0])  # integer classlabel = names[c]xywhn = np.asarray([[float(i) for i in annot[1:]]])xyxy = xywhn2xyxy(xywhn, w=annotator.im.shape[1], h=annotator.im.shape[0])annotator.box_label(xyxy.tolist()[0], label, color=colors(c, True))im0 = annotator.result()cv2.imshow(str(source[:-4] + '.jpg'), im0)# press ESC to destroy cv2 windowsif cv2.waitKey(0) == 27:cv2.destroyAllWindows()if __name__ == '__main__':labels_shower()

YOLOv5 网络结构

YOLOv5模型集成了FPN多尺度检测及Mosaic数据增强和SPP结构,整体结构可以分为四个模块,具体为:输入端(Input)、主干特征提取网络(Backbone) 、Neck与输出层(Prediction) 。

输入端

输入端(Input)主要包括了Mosaic数据增强、自适应锚框计算和自适应图片缩放三大部分。

  1. Mosaic数据增强是将数据集图片以随机缩放、随机裁剪、随机排布的方式进行拼接
  2. 自适应锚框计算是指在网络训练中,网络在初始锚框的基础上输出预测框,进而和真实框进行比对,计算两者差距,再反向迭代,更新网络参数
  3. 自适应图片缩放常用的方式是将原始图片统一缩放到一个标准尺寸,再送入检测网络中

主干特征提取网络

主干特征网络提取网络Backbone由Focus结构和CSP结构组成。YOLOv5中分别设计和使用了两种不同的CSP结构,其中CSP1_X结构应用于主干特征提取网络中,同时在Neck中使用了另一种CSP2_X结构。使用CPS模块有如下优点:

  1. 增强网络的学习能力,使得训练出的模型,既能保持轻量化,又能有较高的准确性
  2. 有效降低了计算瓶颈,通过较少的计算量获得较高是检测性能
  3. 降低内存成本,使得训练使用一个GPU即可完成训练

Neck层

Neck层由FPN和PAN组成。FPN是通过向上采样的方法将上层的特征进行传输融合,从而得到预测特征图,其中含有两个PAN结构。通过下采样操作,将低层的特征信息和高层特征进行融合,输出预测的特征图。

FPN采用了自顶向下的结构,这样就可以进行对于强语义特征的传输;特征金字塔采用了自底向上的结构,这样就可以进行对于强定位特征的传输,这两者经过练手结合后,就可以将每一个检测层做到特征聚合,这样就成功提高了特征提取的能力。

输出端

输出端(Prediction),即网络预测层,负责在特征图上应用anchors,并生成带有类概率、目标得分和坐标的输出向量,并进行NMS非极大值抑制处理,最后输出预测结果。

Adam优化器

本文选用Adam作为模型训练过程中梯度下降的优化器,Adam优化器是AdaGrad和RMSPropAdam参数优化器的结合,它具有如下优点:

  1. 实现简单、计算高效、对内存需求少
  2. 参数的更新不受梯度伸缩变换影响
  3. 参数具有很好的解释性、且通常无需调整调整或者微调
  4. 更新步长能够被限制在大致的的范围内
  5. 自动调整学习率

激活函数选择

隐藏层激活函数

隐藏层使用带泄露的ReLU(Leaky ReLU)激活函数,在输入 x<0x\lt 0x<0 时,保持一个很小的梯度 γ\gammaγ,这样神经元非激活时也能有一个非零的梯度可以更新参数,避免永远不能被激活。

采用ReLU激活函数只需要进行加、减、乘和比较的操作,计算上更加高效,ReLU函数也被认为具有生物学合理性(Biological Plausibility),比如单侧抑制、宽兴奋边界(即兴奋程度高)。Sigmoid型激活函数会导致一个非稀疏性的神经网络,而ReLU却具有很好的稀疏性。

在优化方面,相比Sigmoid型函数的两端饱和,ReLU函数左饱和函数且 x>0x\gt 0x>0 时导数为 111,在一定程度上缓解了神经网络梯度消失的问题,加速梯度下降的收敛速度。

输出层激活函数

输出层使用了Sigmoid型激活函数。使用Sigmoid型函数,其输出可以直接看成一个概率分布,使得神经网络可以更好地统计学习模型进行结合,并且它还可以看成一个软性门(Soft Gate),用来控制其他的神经元输出信息的数量。

模型优化

YOLOv5 的模型优化内容包括:

  1. Focus层优化:使用一个卷积层 Conv(k=6, s=2, p=2) 替换掉 backbone 中的 Focus 层;
  2. SPP层优化:SSP空间金字塔池化层的作用是使卷积神经网络(CNN)能够输入任意大小的图片,在CNN的最后一层卷积层后面加入一层SSP层,它能使不同任意尺寸的特征图通过SSP层之后都能输出一个固定长度的向量。然后将这个固定长度的向量输入到全连接层,进行后续的分类检测任务。SPP层只通过指定三次卷积核大小,将来自CBL模块的数据进行三次池化并拼接,然后再过一个CBL,有效避免了对图像区域剪裁、缩放操作导致的图像失真等问题,解决了卷积神经网络对图像重复特征提取的问题,大大提高了产生候选框的速度,且节省了计算成本,增强特征图特征表达能力;
  3. C3层优化:Bottleneck 为基本残差块,被堆叠嵌入到C3模块中进行特征学习,它利用两个Conv模块将通道数先减小再扩大对齐,以此提取特征信息,并使用shortcut控制是否进行残差连接。在C3模块中,输入特征图会通过两个分支,第一个分支先经过一个Conv模块,之后通过堆叠的Botleneck模块对特征进行学习;另一分支作为残差连接,仅通过一个Conv模块。两分支最终按通道进行拼接后,再通过一个Conv模块进行输出。在backbone结构的最后一层的C3层改用shorcut短连接,因为原先的骨干网络最后一层是C3,而现在是SPPF层。所以最后一层改用shortcut层,这样能够使网络正常训练。

本地环境搭建

  1. 创建虚拟环境
  2. 克隆YOLOv5项目
  3. 安装依赖库
git clone https://github.com/ultralytics/yolov5
(venv) ➜  food_detect pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/

当前项目结构

.
├── venv
├── datasets
│   └── UNIMIB2016
│       ├── images (1005)
│       └── labels (1005)
└── yolov5

注:上述目录结构中只列写了项目的关键文件和文件夹.

W&B配置

Weights & Biases可被用作替代tensorboard的监督模型训练过程的可视化工具,拥有如下几个优点:

  • 其已经兼容各种深度学习框架(Pytorch/Tensorflow/Keras)
  • 界面简洁无需与服务器连接,甚至可在移动端随时随地登录自己的account浏览模型训练情况
  • 其不仅可以monitor深度学习loss、reward等与训练强相关的标量,还会监督CPU、GPU等硬件占用率等参数
  • 不仅作为Dashboard显示一些curve,还可通过设置可视化model的weights以调整接下来的调参策略等
  • 通过训练呈现的各种分析Dashboard或可视化界面可直接创建report导出pdf分享

W&B由以下四个组件构成:

  1. Dashboard: 实验跟踪
  2. Artifacts: 数据集版本控制、模型版本控制
  3. Sweeps: 超参数优化
  4. Reports: 保存和共享可重现的结果

基于上述优势,本项目选择W&B作为模型训练和结果可视化的管理平台。版本号如下,虽然YOLOv5v6.1推荐使用wandb version 0.12.10 or below.

版本号
0.12.11
# From the command line, install and log in to wandb, Copy this key and paste it into your command line when asked to authorize your account
pip install wandb==0.12.11
wandb login

环境配置说明表

YOLOv5 v6.1
wandb 0.12.11
IDE PyCharm
python 3.8
OS MacOS

模型训练准备

预训练模型的选取

在预训练模型的选择上,为了同时兼顾菜品识别的速率和准确性,我们选择最近才发布的预训练模型YOLOv5s6. (22 Feb 2022, v6.1

在COCO数据集上,虽然YOLOv5n在识别速度上远超其他模型,但精度相对较低。而YOLOv5s在保持着较高识别速度的前提下,识别准确性优于YOLOv5n。在近期更新的版本中,YOLOv5s6模型识别的准确性进一步提高,识别速度也有所提升,模型参数量大幅减少,故选择该预训练模型。

下载模型:YOLOv5s6.pt,放置于yolov5/文件夹下.

训练集和验证集的划分

编写脚本,将datasets/UNIMIB2016/labels中的有效数据按7:3划分训练集和验证集,验证集也做测试集之用。最终,训练集数据量为703,验证集为302. 将结果存入UNIMIB2016目录下的train.txttest.txt.

# .
# ├── venv
# ├── datasets
# │   └── UNIMIB2016
# │       ├── splitDataset.py <--
# │       ├── images (1005)
# │       └── labels (1005)
# └── yolov5# splitDataset.pyimport os
import random
from random import shuffle# labels relative path
# ./labels
ya_path = os.path.join(os.getcwd(), 'labels')# images path (relative to 'dataset root dir' in UNIMIB2016.yaml)
# ./images/
img_path = os.path.join(os.getcwd(), 'images')# output files name
output_train = 'train.txt'
output_test = 'test.txt'# the percentage of train set
train_percent = .7def splitDataset():all_samples = os.listdir(ya_path)num = len(all_samples)train_num = int(train_percent * num)# shuffle samples listrandom.seed(82322)shuffle(all_samples)train_set = all_samples[:train_num]test_set = all_samples[train_num:]# generate train set filewith open(os.path.join(os.getcwd(), output_train), 'w') as f:for item in train_set:f.write(os.path.join(img_path, item[:-4] + '.jpg') + '\n')# generate test set filewith open(os.path.join(os.getcwd(), output_test), 'w') as f:for item in test_set:f.write(os.path.join(img_path, item[:-4] + '.jpg') + '\n')print('train set num = ' + str(train_num))print('test set num = ' + str(num - train_num))if __name__ == '__main__':splitDataset()

模型训练文件配置

UNIMIB2016.yaml

新建yolov5/data/UNIMIB2016.yaml,内容设置如下:

# UNIMIB2016 dataset http://www.ivl.disco.unimib.it/activities/food-recognition/ (1027 available photos)
# parent
# ├── yolov5
# └── datasets
#     └── UNIMIB2016  ← downloads herepath: ../datasets/UNIMIB2016  # dataset root dir
train: train.txt  # train images (relative to 'path') 703 images
val: test.txt  # val images (relative to 'path') 302 images
test: test.txt # test images (optional) 302 images# Classes
nc: 73  # number of classes
names: [ 'pane', 'mandarini', 'carote', 'patate/pure', 'cotoletta', 'fagiolini', 'yogurt', 'budino', 'spinaci', 'scaloppine','pizza', 'pasta_sugo_vegetariano', 'mele', 'pasta_pesto_besciamella_e_cornetti', 'zucchine_umido','lasagna_alla_bolognese', 'arancia', 'pasta_sugo_pesce', 'patatine_fritte', 'pasta_cozze_e_vongole', 'arrosto','riso_bianco', 'medaglioni_di_carne', 'torta_salata_spinaci_e_ricotta', 'pasta_zafferano_e_piselli','patate/pure_prosciutto', 'torta_salata_rustica_(zucchine)', 'insalata_mista', 'pasta_mare_e_monti','polpette_di_carne', 'pasta_pancetta_e_zucchine', 'pasta_ricotta_e_salsiccia', 'orecchiette_(ragu)', 'pizzoccheri','finocchi_gratinati', 'pere', 'pasta_tonno', 'riso_sugo', 'pasta_tonno_e_piselli', 'piselli', 'torta_salata_3','torta_salata_(alla_valdostana)', 'banane', 'salmone_(da_menu_sembra_spada_in_realta)', 'pesce_2_(filetto)','bruscitt', 'guazzetto_di_calamari', 'pasta_e_fagioli', 'pasta_sugo', 'arrosto_di_vitello', 'stinco_di_maiale','minestra_lombarda', 'finocchi_in_umido', 'pasta_bianco', 'cavolfiore', 'merluzzo_alle_olive', 'zucchine_impanate','pesce_(filetto)', 'torta_crema_2', 'roastbeef', 'rosbeef', 'cibo_bianco_non_identificato', 'torta_crema','passato_alla_piemontese', 'pasta_e_ceci', 'crema_zucca_e_fagioli', 'focaccia_bianca', 'minestra','torta_cioccolato_e_pere', 'torta_ananas', 'rucola', 'strudel', 'insalata_2_(uova' ]  # class names

my_train.py

创建 yolov5/my_train.py,编写单次训练的启动程序,并设置模型各个参数:(这一步也可融入下一目中进行——超参优化

my_train.py使用预置超参数data/hyps/hyp.scratch-myself.yaml,优化器Adam,输入图像尺寸640batch size = 16.

# my_trainimport trainparams = {'weights': 'yolov5s6.pt','cfg': 'hub/yolov5s6.yaml','data': 'UNIMIB2016.yaml','hyp': 'data/hyps/hyp.scratch-myself.yaml','epochs': 300,'batch_size': 16,'imgsz': 640,'optimizer': 'Adam'}if __name__ == '__main__':train.run(**params)

图像增强

数据增强也叫数据扩增,意思是在不实质性的增加数据的情况下,让有限的数据产生等价于更多数据的价值。

yolov5/data/hyps目录下,作者提供的初始超参数就包含了图像增强的参数,如下图所示(hyp.scratch-med.yaml):

图例为一次运行时(batch_size=16),经过mosaic、hsv、flip up-down、flip left-right后得到的增强图片。

超参数调优

YOLOv5的开发团队在 PR #3938 中添加了对于 W&B sweep 的支持。所以,对于YOLOv5s6预训练模型的超参数调优,我们使用W&B提供的sweep工具。

参数和配置

编写yolov5/utils/loggers/wandb/sweep.yaml,确定项目路径配置和超参数搜索范围、方法等:

# sweep.yaml
# Hyperparameters for training
program: utils/loggers/wandb/sweep.py
method: random
metric:name: metrics/mAP_0.5goal: maximize
early_terminate:type: hyperbandmin_iter: 3eta: 3parameters:# hyperparameters: set either min, max range or values listdata:value: "data/UNIMIB2016.yaml"weights:value: "yolov5s6.pt"cfg:value: "models/hub/yolov5s6.yaml"epochs:value: 100imgsz:value: 640optimizer:value: "Adam"batch_size:values: [4, 8, 16]lr0:distribution: uniformmin: 0.005max: 0.015lrf:distribution: uniformmin: 0.005max: 0.015momentum:distribution: uniformmin: 0.92max: 0.95weight_decay:distribution: uniformmin: 4e-4max: 5e-4warmup_epochs:value: 3.0warmup_momentum:value: 0.8warmup_bias_lr:value: 0.1box:distribution: uniformmin: 0.045max: 0.055cls:distribution: uniformmin: 0.45max: 0.55cls_pw:value: 1.0obj:distribution: uniformmin: 0.95max: 1.05obj_pw:value: 1.0iou_t:distribution: uniformmin: 0.18max: 0.22anchor_t:value: 4.0fl_gamma:value: 0.0hsv_h:value: 0.015hsv_s:value: 0.7hsv_v:value: 0.4degrees:value: 8.0translate:value: 0.005scale:value: 0.20shear:value: 0.0perspective:value: 0.0flipud:value: 0.7fliplr:value: 0.7mosaic:value: 0.95mixup:value: 0copy_paste:value: 0
  • 超参数调优的目标是最大化mAP@0.5
  • 最优超参数搜索方法使用random,每次迭代时随机地在超参数搜索范围中选择一组参数
  • 参数范围的选取根据data/hyps/hyp.scratch-low.yaml来确定,hyp.scratch-low.yaml也被用来作为 baseline,在开始 sweep 前先以该参数训练模型
  • sweeping过程中,使用hyperband方法对表现较差的迭代进行减枝(prune),提前结束该次超参尝试,加速模型超参数优化速度。参数设置:η=3\eta=3η=3, min_iter=3min\_iter=3min_iter=3. 意味着每轮运行将在[3, 9, 27, 81]次brackets时,对模型优化目标进行评估,及时终止无效的运行
  • Random search chooses a random set of values on each iteration.
  • Hyperparameters. Default hyperparameters are in hyp.scratch.yaml. We recommend you train with default hyperparameters first before thinking of modifying any. In general, increasing augmentation hyperparameters will reduce and delay overfitting, allowing for longer trainings and higher final mAP.
  • Hyperband stopping evaluates whether a program should be stopped or permitted to continue at one or more pre-set iteration counts, called “brackets”. When a run reaches a bracket, its metric value is compared to all previous reported metric values and the run is terminated if its value is too high (when the goal is minimization) or low (when the goal is maximization).

调优程序运行(sweep)

运行超参数调优程序,迭代次数100次.

# get the sweep id
wandb sweep --project YOLOv5 utils/loggers/wandb/sweep.yaml
# set a target to automatically stop the sweep
NUM=100
# input the sweep id got in preceding step
SWEEPID="xxxxxxxx"
# run an agent by nohup
nohup wandb agent --count $NUM sylvanding/YOLOv5/$SWEEPID > ./sweeping.log 2>&1 &

模型训练

云服务器选取

本项目的模型训练使用MistGPU平台提供的带有GPU加速功能的主机. 服务器的配置如下:

操作系统 Linux-4.18.0-15-generic-x86_64-with-glibc2.27
显卡 NVIDIA GeForce GTX 1080 Ti
显存 11 Gbps
CPU Intel Xeon CPU E5-2678 v3 @ 2.50GHz

YOLOv5开发环境配置如下:

Python version 3.8.13
W&B CLI Version 0.12.11
PyTorch 1.11.0
Opencv 4.5.5
Cuda/cudnn Cuda10.1/cudnn7.6.5

服务器环境配置

安装python3.8

# python3.8 安装
1. 以root用户或具有sudo访问权限的用户身份运行以下命令,以更新软件包列表并安装必备组件:
2. $ sudo apt update$ sudo apt install software-properties-common
3. 将Deadsnakes PPA添加到系统的来源列表中:$ sudo add-apt-repository ppa:deadsnakes/ppa
4. 启用存储库后,请使用以下命令安装Python 3.8:$ sudo apt install python3.8
5. 通过键入以下命令验证安装是否成功:$ python3.8 --version

上传项目

项目文件的组织结构如下(整个项目的必要文件均打包到model_training/文件夹下):

  • labels/文件夹存有前文得到的yolov5格式.txt标记文件1005份
  • test.txt, train.txt存放前文划分好的测试集、训练集图片文件路径
  • yolov5/存放上文修改的yolov5项目
  • 初始时,images文件夹为空,需要编写脚本下载、解压、修正图片,图片压缩文件UNIMIB2016-images.zip
  • 上图UNIMIB2016中缺少rectify_imgs.py,应当添加进来
scp -r -P61500 /Users/sylvanding/Downloads/food_detect/model_training.zip mist@ygg.mistgpu.xyz:~/

创建虚拟环境和安装项目依赖

pip install virtualenv
whereis python3.8 # get python3.8 path
virtualenv -p /usr/bin/python3.8 venv # use python3.8 as interpreter
source venv/bin/activate
cd yolov5
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/

下载数据和初始化W&B

# 注意:下载速度可能很慢
bash datasets/UNIMIB2016/imageSets_downloads.sh
# 初始化W&B
wandb login

服务器图片处理脚本

编写imageSets_downloads.sh,以下载、解压、修正图片:

#!/bin/bash
# Download UNIMIB2016 dataset
# http://www.ivl.disco.unimib.it/activities/food-recognition/
# created by Sylvan Ding -- https://blog.csdn.net/IYXUAN
# 2022.04.22 -- sylvanding@qq.com, sylvanding.online# Example usage: bash datasets/UNIMIB2016/imageSets_downloads.sh
# before execution, you need to install wget and zip!
# parent  ← you should be here
# ├── yolov5
# └── datasets
#     └── UNIMIB2016
#         ├── labels
#         └── images  ← downloads here# Download/unzip images
d='./datasets/UNIMIB2016/images' # unzip directory
file='UNIMIB2016-images.zip' # images.zip
url='wget http://www.ivl.disco.unimib.it/download/http://www.ivl.disco.unimib.it/minisites/UNIMIB2016/UNIMIB2016-images.zip'
wget $url
echo 'Unzipping...'
unzip -q -j -d $d $file
echo 'Downloaded successfully!'
python3.8 $d/../rectify_imgs.py
echo 'Rectified successfully!'

运行截图

常见错误

Arial.ttf

  • 第一次启动,需要下载 Arial.ttf 字体,卡住.

  • 解决方法:

    在自己主机上下载好再上传到服务器,或者用 wget 再服务器上下载再移至指定字体文件夹

wget https://ultralytics.com/assets/Arial.ttf
scp -r -P61500 /Users/sylvanding/Downloads/Arial.ttf mist@ygg.mistgpu.xyz:/home/mist/.config/Ultralytics/

图片下载速度慢

  • 图片数据集下载速度慢,用腾讯云SVM下载,下载好后传至自己的主机上,再用 MistGPU 提供的云储存上传数据集即可
# 从“云端”拷贝数据集到项目文件夹
cp -v /data/UNIMIB2016-images.zip ~/model_training

Cuda out of memory

  • 出现Cuda is out of memory,内存或显存不足,应该 kill 释放其他占用内存的进程

results.png 生成问题

  • #7650 When generating results.png, bug happened with disorder on Y-axis of val/box_loss, val/obj_loss and val/cls_loss

结果分析

Metrics

precision & recall

precision=TPTP+FP=TPalldetectionsprecision = \frac{TP}{TP+FP} = \frac{TP}{\mathrm{all\ detections}}precision=TP+FPTP​=all detectionsTP​

recall=TPTP+FN=TPallgroundtruthsrecall = \frac{TP}{TP+FN} = \frac{TP}{\mathrm{all\ ground\ truths}}recall=TP+FNTP​=all ground truthsTP​

  • Precision 指的是预测出的样本中正例比例(查准率)
  • Recall 指的是所有正例中预测出的正例比例(查全率)
  • all detections 是所有bounding box的数量
  • all ground truths是所有ground truths的数量

在目标检测(object detection)中,混淆矩阵的定义如下:

Confidence≥Threshold2Confidence\ge Threshold_2Confidence≥Threshold2​ Confidence<Threshold2Confidence\lt Threshold_2Confidence<Threshold2​
IoU≥Threshold1IoU\ge Threshold_1IoU≥Threshold1​ TP FN
IoU<Threshold1IoU\lt Threshold_1IoU<Threshold1​ FP TN
  • IoU=areaofoverlapareaofunion=area(Bp∩Bgt)area(Bp∪Bgt)IoU = \frac{\mathrm{area\ of\ overlap}}{\mathrm{area\ of\ union}} = \frac{area(B_p\cap B_{gt})}{area(B_p\cup B_{gt})}IoU=area of unionarea of overlap​=area(Bp​∪Bgt​)area(Bp​∩Bgt​)​
  • Threshold1Threshold_1Threshold1​ 为 IoUIoUIoU 阈值
  • Threshold2Threshold_2Threshold2​ 为 ConfidenceConfidenceConfidence 分类置信度阈值

mAP

在不同语境下,mAP主要针对COCO数据集,AP主要针对VOC数据集。mAP(mean Average Precision)是 AP 的平均值,即计算在各个分类类别上的 AP 后求平均所得。AP 的求法如下:

  • 根据 BB(bounding box)的 ConfidenceConfidenceConfidence 由高到低排序
  • 依次以各个 BB 的 ConfidenceConfidenceConfidence 为 Threshold2Threshold_2Threshold2​ (包括 0、1)
  • 依次计算在此分类置信度下,该分类的 recallrecallrecall 和 precisionprecisionprecision,绘制 P-R 曲线
  • 根据不同的策略,计算 AUC(area under the curve),即 AP

在VOC物体检测任务中,Pascal VOC 2008 中设置 Threshold1=0.5Threshold_1=0.5Threshold1​=0.5,使用差值平均精确度(interpolated average precision)的评测方法。绘制 P-R 曲线后,选取横轴上的11个点(间隔为0.1)所对应的最大精度,然后再取平均作为最终检测的平均精确度,其表达式如下:

AP=111∑r∈{0,0.1,…,1}Pinterp(r)AP=\frac{1}{11} \sum _{r\in \{0,0.1,\dots , 1 \}} P_{interp} (r)AP=111​r∈{0,0.1,…,1}∑​Pinterp​(r)

Pinterp(r)=max⁡r′≥rP(r′)P_{interp} (r) = \mathop {\max } \limits _{r'\ge r} P(r')Pinterp​(r)=r′≥rmax​P(r′)

其中,rrr 是横轴召回率的值,Pinterp(r)P_{interp}(r)Pinterp​(r) 是 rrr 时的差值精度,P(r)P(r)P(r) 是 r 对应的纵轴精度。

在 Pascal VOC 中,检测结果只评测了 Threshold1=0.5Threshold_1=0.5Threshold1​=0.5 阈值下的 mAP 值(记为 mAP@0.5mAP@0.5mAP@0.5 )。相比 VOC 而言,COCO 数据集的评测则更加全面。

COCO 评估了在不同的交并比 [0.5:0.05:0.95][0.5:0.05:0.95][0.5:0.05:0.95] 下的 mAP,并且在最后以这些阈值下的 mAP 的平均作为结果,记为 mAP@[0.5:0.95]mAP@[0.5:0.95]mAP@[0.5:0.95]. 不仅评估到物体检测模型的分类能力,同时也能体现出检测模型的定位能力。

预测的时候会产生很多FP,为了减少FP的数量,一般检测器的最后都会引入一步Non-maximum Suppression(NMS),以去除一部分重复预测的bounding box,YOLOv5中采用加权NMS的方式。

Loss Function

YOLOv1 的损失函数包括三部分,分别是定位损失、置信度损失和分类损失,其形式如下:

YOLOv5 改进了损失函数,进一步提高了模型的收敛速度和训练稳定性,避免了梯度消失和梯度爆炸。YOLOv5 的损失函数也有三部分构成:

  • Localization loss 定位损失(又称 box loss,是预测框与 GT 之间的误差)
  • Confidence loss 置信度损失(又称 obj loss, Objectness of the box
  • Classification loss 分类损失(cls loss

总损失函数是上述三者的加权和,通常置信度损失取最大权重、矩形框损失(或定位损失)和分类损失的权重次之。

YOLOv5 使用 CIoU loss 计算定位损失,置信度和分类损失都用 BCE loss 计算。

定位损失

对于矩形框的预测损失来说,可用 L1、L2 或 smooth L1 损失函数来描述。训练后期,L1 损失函数会导致其值在某范围内波动,难以收敛。虽然 L2 损失函数在 0 点处可导,最终可以收敛,但在训练前期,可能会导致梯度爆炸问题,从而训练没能朝着最优化的方向进行。smooth L1 损失函数将二者优点相结合,即避免了梯度爆炸,又避免了不熟练问题。上述计算矩形框的 L1、L2、smooth L1 损失时有一个共同点,都是分别计算矩形框中心点 x 坐标、中心点 y 坐标、宽、高的损失,最后再将四个损失值相加得到该矩形框的最终损失值。这种计算方法的前提假设是中心点 x 坐标、中心点 y 坐标、宽、高这四个值是相互独立的,实际上它们具有相关性,所以该计算方法存在问题。

于是,IoU系列损失函数(IoU、GIoU、DIoU、CIoU)又被陆续提了出来。IoU loss 关注预测框和 GT 的交并比;GIoU loss 把包围预测框和 GT 的最小矩形框的面积也加入到计算中,解决了 IoU loss 中,当两个矩形框完全没有重叠区域时,无论它们距离多远,它们的 IoU 都为 0,导致梯度也为 0,因而无法优化的情况;DIoU loss,把两矩形框的中心点距离 ρ\rhoρ、外接矩形框的对角线长度 ccc 都考虑进去,使训练更稳定、收敛更快。YOLOv5使用 CIoU loss 来衡量矩形框的损失。

CIoU loss 将重叠面积、中心点距离、宽高比同时加入了计算,其计算公式如下:

CIoU=IoU−ρ2c2−αv=DIoU−αvCIoU = IoU - \frac{\rho ^2}{c^2} - \alpha v= DIoU - \alpha vCIoU=IoU−c2ρ2​−αv=DIoU−αv

v=4π2(arctan⁡wgthgt−arctan⁡wpredhpred)2v = \frac{4}{\pi ^2} \left ( \arctan \frac{w_{gt}}{h_{gt}} - \arctan \frac{w_{pred}}{h_{pred}} \right ) ^2v=π24​(arctanhgt​wgt​​−arctanhpred​wpred​​)2

α=v1−IoU+v\alpha = \frac{v}{1-IoU+v}α=1−IoU+vv​

lossCIoU=1−CIoUloss_{CIoU} = 1-CIoUlossCIoU​=1−CIoU

其中,wgtw_{gt}wgt​、hgth_{gt}hgt​ 为 GT 宽、高,wpredw_{pred}wpred​、hpredh_{pred}hpred​ 为预测框宽、高,ρ\rhoρ 是两框中心点距离,ccc 是是包围两框的最小矩形框对角线长度,vvv 是两框宽高比的相似度,α\alphaα 是 vvv 的影响因子。

IoU 越大,两框的重叠区域越大,则 ααα 越大,从而 vvv 的影响越大,对宽高比的惩罚力度越大,着重优化宽高比;反之,IoU 越小,两框的重叠区域越小, ααα 越小,从而 vvv 的影响越小,对两框距离的惩罚力度越大,着重优化距离。

置信度损失

YOLOv5 将一张输入的 640×640640\times 640640×640 图像分割成的 N×NN\times NN×N 网格,每个网格预测 MMM 个预测框(anchor),所以总共预测了 M×N×NM\times N\times NM×N×N 个预测框。每个预测框的预测信息包括矩形框信息、置信度、分类概率。

  • 矩形框:表征目标的大小以及精确位置
  • 置信度:表征预测框的可信程度,取值范围0~1,值越大说明该矩形框中越可能存在目标
  • 分类概率:表征目标的类别

由于并不是每个预测框内都存在目标,所以在训练时首先需要根据标签作初步判断,判断每个预测框内是否存在目标,以此建立 mask 矩阵(矩阵的每个元素是布尔型)。实际上,并非所有预测框都需要计算所有类别的损失函数值,而是根据 mask 矩阵来决定,决定原则如下:

  • 仅 mask 矩阵对应位置为 True 的预测框需要计算 box loss 和 cls loss
  • 所有预测框都要计算 obj loss,但是 mask 为 true 的预测框与 mask为 false 的预测框的置信度标签值不一样

mask 矩阵的布尔值,由 anchor 框的保留或剔除决定,依照 anchor 框和 GT 的宽高比(aspect ratio)决定 anchor 是否保留。

置信度标签的维度应该与神经网络的输出维度保持一致,因此置信度的标签也是维度为 M×N×NM\times N\times NM×N×N 的矩阵。计算对应预测框与目标框的 CIoU,使用 CIoU 作为该预测框的置信度标签,对 mask 矩阵为 false 的位置,预测框的置信度标签赋值 0. 当 CIoU 小于 0 时,直接取 0 值作为标签,对 CIoU 做截断。由此得到预测置信度矩阵 P.

假设置信度标签为矩阵 L,那么置信度损失的 BCE loss(二元交叉熵损失)函数定义如下:

lossBCE(z,x,y)=−L(z,x,y)∗log⁡P(z,x,y)−(1−L(z,x,y))∗log⁡(1−P(z,x,y))loss_{BCE}(z,x,y)=-L(z,x,y)* \log P(z,x,y) - (1-L(z,x,y))*\log (1-P(z,x,y))lossBCE​(z,x,y)=−L(z,x,y)∗logP(z,x,y)−(1−L(z,x,y))∗log(1−P(z,x,y))

其中,0≤z<M0 \le z \lt M0≤z<M, 0≤x,y<N0 \le x,y \lt N0≤x,y<N.

从而得到该网络的置信度损失值:

{lobj=1numof(mask=true)∑mask=truelossBCE(z,x,y)lnobj=1numof(mask=false)∑mask=falselossBCE(z,x,y)lossobj=a∗lobj+(1−a)∗lnobj\left\{\begin{matrix} l_{obj} &=& \frac{1}{num\ of\ (mask=true)} \sum _{mask=true} loss_{BCE}(z,x,y) \\ l_{nobj} &=& \frac{1}{num\ of\ (mask=false)} \sum _{mask=false} loss_{BCE}(z,x,y) \\ loss_{obj} &=& a*l_{obj}+(1-a)*l_{nobj} \end{matrix}\right. ⎩⎨⎧​lobj​lnobj​lossobj​​===​num of (mask=true)1​∑mask=true​lossBCE​(z,x,y)num of (mask=false)1​∑mask=false​lossBCE​(z,x,y)a∗lobj​+(1−a)∗lnobj​​

其中,aaa 为 mask = true 时的置信度损失权重,aaa 越大,网络训练时越专注于 mask = true 的正样本情况。为了使训练更专注于正样本,后来 Focal loss 又被提了出来。

分类损失

为了减少过拟合、增加训练的稳定性,YOLOv5 对独热码标签做了平滑操作,如下所示:

labelsmooth=label∗(1−α)+α/classnumlabel_{smooth} = label*(1-\alpha )+\alpha /class numlabelsmooth​=label∗(1−α)+α/classnum

α\alphaα 是平滑系数,labellabellabel 是经过独热编码后的标签向量。

接着,使用 BCE loss 函数计算矩阵中每个 mask=true 元素的分类损失并累加求平均,得到总的分类损失,计算过程如下:

{lossBCE(z,x,y,c)=−Lsmooth(z,x,y,c)∗log⁡P(z,x,y,c)−(1−Lsmooth(z,x,y,c))∗log⁡(1−P(z,x,y,c))losscls=1classnum∗numof(mask=true)∑mask=truelossBCE(z,x,y,c)\left\{\begin{matrix} loss_{BCE}(z,x,y,c) &=& -L_{smooth}(z,x,y,c)* \log P(z,x,y,c) - (1-L_{smooth}(z,x,y,c))*\log (1-P(z,x,y,c)) \\ loss_{cls} &=& \frac{1}{classnum\ *\ num\ of\ (mask=true)} \sum _{mask=true} loss_{BCE}(z,x,y,c) \\ \end{matrix}\right. {lossBCE​(z,x,y,c)losscls​​==​−Lsmooth​(z,x,y,c)∗logP(z,x,y,c)−(1−Lsmooth​(z,x,y,c))∗log(1−P(z,x,y,c))classnum ∗ num of (mask=true)1​∑mask=true​lossBCE​(z,x,y,c)​

其中,LsmoothL_{smooth}Lsmooth​ 是平滑后的 GT 标签,0≤c<classnum0\le c\lt classnum0≤c<classnum 对应样本类别数.

sweep超参数调优结果

相比于AutoML框架的超参数调优,wandb sweeps具有更强的实验管理和数据可视化的能力。wandb sweeps具有一下几个优点:

  • 较好的可视化效果
  • 较小的代码入侵
  • 较好的实验管理

经过sweep后得到的平行坐标图(parallel coordinates plot)、散点图和相关性分析图如下所示。其中,学习率 lr0mAP@0.5的影响最大,呈负相关趋势。

选取较优参数进行训练

根据 sweep 的结果,选取 mAP@0.5 值最高的超参数训练模型,具体参数如下所示(未列出参数和data/hyps/hyp.scratch-low.yaml一致,对结果的影响不大):

weights yolov5s6.pt
cfg hub/yolov5s6.yaml
epochs 300
batch_size 16
imgsz 640
optimizer Adam
lr0 0.01
lrf 0.01
momentum 0.937
anchors 3
hsv_h/s/v 0.015/0.7/0.4
degrees 5.0
flipud/lr 0.5/0.5
mosaic 1.0

结果展示

评价指标

训练结束,训练集的三类损失函数均收敛。在验证集上,模型各个评价指标均高于 0.93. 最佳模型在第 299 次epoch时得到,最佳模型的评价指标如下:

mAP@0.5 0.983
mAP@0.5:0.95 0.939
precision 0.954
recall 0.939

混淆矩阵

最终,模型在验证集上的混淆矩阵如下图所示,该混淆矩阵在列上做归一化,那么,对角元素表示每类的召回率,因为一小部分类别样本量太少,所以召回率较低。背景被判定为菜品的现象存在,但误判率极低。

PR曲线

置信度和P、R的关系曲线

后期可依据该图,根据需要选取合适的置信度,调整菜品识别的精度和召回率。(精度和置信度成正比,而召回率和置信度成反比)

F1 Score

F1分数(F1-Score),又称为平衡F分数(BalancedScore),它被定义为精确率和召回率的调和平均数。 可以看到,当 Confidence 在 0.7 附近时,F1-Score 最优。

关于训练集中标签分布信息的描述

相关性直方图

[#5138] Correlogram is a group of 2d histograms showing each axis of your data against each other axis. The labels in your image are in xywh space.

很明显,width 和 height 成正相关分布;x 成双峰分布,意味着大多数人餐盘上食物是左边放一份、右边放一份的;height、width 都集中在 0.5 以下。相关性直方图也为后期 anchor 的选取提供了依据,为模型的进一步优化提供了参考。

分类直方图和标记框

显然,从 top-left 图可知,该样本的分类是有偏的,模型在某些小数量分类上的标签可能不优秀。

预测效果展示

项目亮点

基于YOLOv5的中式快餐店菜品识别系统相关推荐

  1. 基于YOLOv5的舰船检测与识别系统(Python+清新界面+数据集)

    摘要:基于YOLOv5的舰船检测与识别系统用于识别包括渔船.游轮等多种海上船只类型,检测船舰目标并进行识别计数,以提供海洋船只的自动化监测和管理.本文详细介绍船舰类型识别系统,在介绍算法原理的同时,给 ...

  2. 基于YOLOv5行人车辆跟踪检测识别计数系统源码+数据集,实现出/入分别计数

    yolov5 deepsort 行人 车辆 跟踪 检测 计数 下载地址:基于YOLOv5行人车辆跟踪检测识别计数系统源码+数据集 实现了 出/入 分别计数. 默认是 南/北 方向检测,若要检测不同位置 ...

  3. 基于YOLOv7的室内场景智能识别系统(源码&教程)

    1.项目背景: 近年来,随着移动互联网与定位技术的发展,基于位置服务越来越多地出现在人们的日常生活中.虽然智能手机都包含很多基于位置服务的应用,但是传统的基于位置服务常常将服务范围划分为室内与室外两种 ...

  4. 基于YOLOv5&Deepsort的山药计数系统(源码&教程)

    1.研究背景 目前缺少针对西南喀斯特山区作物种植地块破碎和多云雾天气导致影像存在阴影进而影响作物遥感识别精度等问题的研究,上述研究也未能找到较好消除杂草对作物植株识别影响的方法.此外,西南喀斯特山区作 ...

  5. opencv交通标志识别_教你从零开始做一个基于深度学习的交通标志识别系统

    教你从零开始做一个基于深度学习的交通标志识别系统 基于Yolo v3的交通标志识别系统及源码 自动驾驶之--交通标志识别 在本文章你可以学习到如何训练自己采集的数据集,生成模型,并用yolo v3算法 ...

  6. android声纹识别技术,基于Android平台的声纹识别系统的研究与实现

    摘要: 社会的发展越来越快,计算机技术的应用也愈来愈广,已经渗透到生活的各个方面.在快节奏.信息化的时代,需要识别和交互的应用日益广泛,要求验证身份的场合越来越多,迅速判定一个人的身份是一个非常重要的 ...

  7. 基于opencv和pillow实现人脸识别系统(附demo)

    更多python教程请到友情连接: 菜鸟教程https://www.piaodoo.com 初中毕业读什么技校 http://cntkd.net 茂名一技http://www.enechn.com p ...

  8. 基于深度学习的单人步态识别系统

    基于深度学习的单人步态识别系统(目前数据集大小15人,准确率100%) 一.数据预处理 a.步态轮廓图 b.头部轮廓图 实现方式: 核心代码: c.骨架图 二.提取步态特征 a.角度特征 b.下肢特征 ...

  9. 基于Yolov5的草莓病虫害检测识别

    项目介绍 上一篇文章介绍了基于卷积神经网络的交通标志分类识别Python交通标志识别基于卷积神经网络的保姆级教程(Tensorflow),并且最后实现了一个pyqt5的GUI界面,并且还制作了一个简单 ...

最新文章

  1. mybatis中mysql ON DUPLICATE KEY UPDATE写法
  2. HDU 2191 - 悼念512汶川大地震遇难同胞——珍惜现在,感恩生活 (多重背包)
  3. 利用Java流进行类的整型字段求和的例子
  4. 成都电子计算机实验中学 孙蕾,痴迷物理 成都男孩保送清华最牛的计算机专业实验班...
  5. 拳王虚拟项目公社:小白如何从0到1搭建个人私域流量池?6招玩转流量裂变法
  6. 计算机网络数据链路层之使用点对点信道
  7. SQLSERVER Tempdb的作用及优化
  8. Android意图Intent总结(隐式意图,显示意图,意图数据传递,意图数据回传)
  9. 区块链+各行业应用案例
  10. cad卸载不干净_流氓软件卸不干净?这6款超强软件卸载神器专治各种流氓软件...
  11. java-se-包装类
  12. 解决windows10下总是很快自动黑屏进入睡眠问题
  13. iPhone7 plus分辨率行不行
  14. MMdet修改检测框字体大小、位置、颜色、填充框
  15. wp8.1 java_UWP tips (与wp8.1的不同)
  16. wireshark流量分析--巧观察
  17. jmeter压力测试及服务器性能监控
  18. 2017华为实习生招聘机考模拟题——0交换排序
  19. 我用 Python 分析了 “青你 2” 漂亮小姐姐的颜值,结果真香!
  20. 关于关于接口测试自动化的总结与思考接口测试自动化的总结与思考

热门文章

  1. Windows 查看文件占用
  2. php开启xml服务,Windows下的PHP开启DomXML
  3. 第65天-内网安全-域环境工作组局域网探针方案
  4. Cortex-A15 Processor 简介
  5. Python 爬虫json格式化输出
  6. 计算机视觉之利用颜色进行肤色检测(基于OpenCV自带库函数)
  7. 使用Python,OpenCV构建移动文档扫描仪
  8. 交易码 MWS X7 会计科目表 XXX 没有在表 T030K 中定义/交易码 VST J2 会计科目表 XXX 没有在表 T030K 中定义
  9. android在国内如何盈利,安卓系统在中国靠什么盈利?
  10. 近期Java高级开发岗面试总结