本文基于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.
若使用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.m
clc; 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_images
image_name = image_names{j};
tuples = annotations(image_name);
count = size(tuples,1);
coordinate_mat = cell2mat(tuples(:,6));
% open file
file_path = [output image_name '.txt'];
ffile = fopen(file_path, 'w');
% write file
for k = 1 : count
item = tuples(k,:);
fprintf(ffile, '%s %d %d %d %d %d %d %d %d\n', ...
string(item(2)), ... % item class
coordinate_mat(k,:)); % item's bounding box
end
% close file
fclose(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.py
import 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 images
print('not found image: {}, remove its annotation'.format(annotation))
print(label_path)
raise FileExistsError
else:
# check extra spaces in a line
with 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 FileExistsError
except 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.py
import 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.py
import 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 format
dw, 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, ymax
def get_classes():
# output: class list
cf = open(class_file_path, 'r')
clss = [line.split(',')[0] for line in cf.readlines()]
cf.close()
return clss
def toYolo():
# read file list of formatted_annotations
annotations = os.listdir(path)
# get class list
clss = get_classes()
# convert every annotation in ./formatted_annotations/ to yolo format
for annotation in annotations:
with open(os.path.join(path, annotation)) as file, open(os.path.join(output_path, annotation), 'w') as opfile:
# read img
img_f_path = os.path.join(img_path, annotation[:-4] + '.jpg')
img = Image.open(img_f_path)
# get img size
size = img.size
# process every item in ./formatted_annotations/*.txt
for line in file:
item = line.split(' ')
# get class num
cls = item[0]
cls_num = clss.index(cls)
# get bbox coordinates
item_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/*.txt
item_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.py
import 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:
continue
img_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.py
import 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 Colors
n = 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 names
def labels_shower():
sources = sample(datasets, n)
for source in sources:
# Add bbox to image
with 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 class
label = 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 windows
if cv2.waitKey(0) == 27:
cv2.destroyAllWindows()
if __name__ == '__main__':
labels_shower()
YOLOv5模型集成了FPN多尺度检测及Mosaic数据增强和SPP结构,整体结构可以分为四个模块,具体为:输入端(Input)、主干特征提取网络(Backbone) 、Neck与输出层(Prediction) 。
输入端(Input)主要包括了Mosaic数据增强、自适应锚框计算和自适应图片缩放三大部分。
主干特征网络提取网络Backbone由Focus结构和CSP结构组成。YOLOv5中分别设计和使用了两种不同的CSP结构,其中CSP1_X结构应用于主干特征提取网络中,同时在Neck中使用了另一种CSP2_X结构。使用CPS模块有如下优点:
Neck层由FPN和PAN组成。FPN是通过向上采样的方法将上层的特征进行传输融合,从而得到预测特征图,其中含有两个PAN结构。通过下采样操作,将低层的特征信息和高层特征进行融合,输出预测的特征图。
FPN采用了自顶向下的结构,这样就可以进行对于强语义特征的传输;特征金字塔采用了自底向上的结构,这样就可以进行对于强定位特征的传输,这两者经过练手结合后,就可以将每一个检测层做到特征聚合,这样就成功提高了特征提取的能力。
输出端(Prediction),即网络预测层,负责在特征图上应用anchors,并生成带有类概率、目标得分和坐标的输出向量,并进行NMS非极大值抑制处理,最后输出预测结果。
本文选用Adam作为模型训练过程中梯度下降的优化器,Adam优化器是AdaGrad和RMSPropAdam参数优化器的结合,它具有如下优点:
隐藏层使用带泄露的ReLU(Leaky ReLU)激活函数,在输入 x < 0 x\lt 0 x<0 时,保持一个很小的梯度 γ \gamma γ,这样神经元非激活时也能有一个非零的梯度可以更新参数,避免永远不能被激活。
采用ReLU激活函数只需要进行加、减、乘和比较的操作,计算上更加高效,ReLU函数也被认为具有生物学合理性(Biological Plausibility),比如单侧抑制、宽兴奋边界(即兴奋程度高)。Sigmoid型激活函数会导致一个非稀疏性的神经网络,而ReLU却具有很好的稀疏性。
在优化方面,相比Sigmoid型函数的两端饱和,ReLU函数左饱和函数且 x > 0 x\gt 0 x>0 时导数为 1 1 1,在一定程度上缓解了神经网络梯度消失的问题,加速梯度下降的收敛速度。
输出层使用了Sigmoid型激活函数。使用Sigmoid型函数,其输出可以直接看成一个概率分布,使得神经网络可以更好地统计学习模型进行结合,并且它还可以看成一个软性门(Soft Gate),用来控制其他的神经元输出信息的数量。
YOLOv5 的模型优化内容包括:
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
注:上述目录结构中只列写了项目的关键文件和文件夹.
Weights & Biases可被用作替代tensorboard的监督模型训练过程的可视化工具,拥有如下几个优点:
W&B由以下四个组件构成:
基于上述优势,本项目选择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.txt
和test.txt
.
# .
# ├── venv
# ├── datasets
# │ └── UNIMIB2016
# │ ├── splitDataset.py <--
# │ ├── images (1005)
# │ └── labels (1005)
# └── yolov5
# splitDataset.py
import 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 = .7
def splitDataset():
all_samples = os.listdir(ya_path)
num = len(all_samples)
train_num = int(train_percent * num)
# shuffle samples list
random.seed(82322)
shuffle(all_samples)
train_set = all_samples[:train_num]
test_set = all_samples[train_num:]
# generate train set file
with 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 file
with 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()
新建yolov5/data/UNIMIB2016.yaml
,内容设置如下:
# UNIMIB2016 dataset http://www.ivl.disco.unimib.it/activities/food-recognition/ (1027 available photos)
# parent
# ├── yolov5
# └── datasets
# └── UNIMIB2016 ← downloads here
path: ../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
创建 yolov5/my_train.py
,编写单次训练的启动程序,并设置模型各个参数:(这一步也可融入下一目中进行——超参优化)
my_train.py
使用预置超参数data/hyps/hyp.scratch-myself.yaml
,优化器Adam
,输入图像尺寸640
,batch size = 16
.
# my_train
import train
params = {'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.5
goal: maximize
early_terminate:
type: hyperband
min_iter: 3
eta: 3
parameters:
# hyperparameters: set either min, max range or values list
data:
value: "data/UNIMIB2016.yaml"
weights:
value: "yolov5s6.pt"
cfg:
value: "models/hub/yolov5s6.yaml"
epochs:
value: 100
imgsz:
value: 640
optimizer:
value: "Adam"
batch_size:
values: [4, 8, 16]
lr0:
distribution: uniform
min: 0.005
max: 0.015
lrf:
distribution: uniform
min: 0.005
max: 0.015
momentum:
distribution: uniform
min: 0.92
max: 0.95
weight_decay:
distribution: uniform
min: 4e-4
max: 5e-4
warmup_epochs:
value: 3.0
warmup_momentum:
value: 0.8
warmup_bias_lr:
value: 0.1
box:
distribution: uniform
min: 0.045
max: 0.055
cls:
distribution: uniform
min: 0.45
max: 0.55
cls_pw:
value: 1.0
obj:
distribution: uniform
min: 0.95
max: 1.05
obj_pw:
value: 1.0
iou_t:
distribution: uniform
min: 0.18
max: 0.22
anchor_t:
value: 4.0
fl_gamma:
value: 0.0
hsv_h:
value: 0.015
hsv_s:
value: 0.7
hsv_v:
value: 0.4
degrees:
value: 8.0
translate:
value: 0.005
scale:
value: 0.20
shear:
value: 0.0
perspective:
value: 0.0
flipud:
value: 0.7
fliplr:
value: 0.7
mosaic:
value: 0.95
mixup:
value: 0
copy_paste:
value: 0
[email protected]
random
,每次迭代时随机地在超参数搜索范围中选择一组参数data/hyps/hyp.scratch-low.yaml
来确定,hyp.scratch-low.yaml
也被用来作为 baseline,在开始 sweep 前先以该参数训练模型hyperband
方法对表现较差的迭代进行减枝(prune),提前结束该次超参尝试,加速模型超参数优化速度。参数设置: η = 3 \eta=3 η=3, m i n _ i t e r = 3 min\_iter=3 min_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).
运行超参数调优程序,迭代次数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 安装
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 [email protected]:~/
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/
# 注意:下载速度可能很慢
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 -- [email protected], 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!'
解决方法:
在自己主机上下载好再上传到服务器,或者用 wget 再服务器上下载再移至指定字体文件夹
wget https://ultralytics.com/assets/Arial.ttf
scp -r -P61500 /Users/sylvanding/Downloads/Arial.ttf [email protected]:/home/mist/.config/Ultralytics/
# 从“云端”拷贝数据集到项目文件夹
cp -v /data/UNIMIB2016-images.zip ~/model_training
Cuda is out of memory
,内存或显存不足,应该 kill 释放其他占用内存的进程p r e c i s i o n = T P T P + F P = T P a l l d e t e c t i o n s precision = \frac{TP}{TP+FP} = \frac{TP}{\mathrm{all\ detections}} precision=TP+FPTP=all detectionsTP
r e c a l l = T P T P + F N = T P a l l g r o u n d t r u t h s recall = \frac{TP}{TP+FN} = \frac{TP}{\mathrm{all\ ground\ truths}} recall=TP+FNTP=all ground truthsTP
all detections
是所有bounding box
的数量all ground truths
是所有ground truths
的数量在目标检测(object detection)中,混淆矩阵的定义如下:
C o n f i d e n c e ≥ T h r e s h o l d 2 Confidence\ge Threshold_2 Confidence≥Threshold2 | C o n f i d e n c e < T h r e s h o l d 2 Confidence\lt Threshold_2 Confidence<Threshold2 | |
---|---|---|
I o U ≥ T h r e s h o l d 1 IoU\ge Threshold_1 IoU≥Threshold1 | TP | FN |
I o U < T h r e s h o l d 1 IoU\lt Threshold_1 IoU<Threshold1 | FP | TN |
在不同语境下,mAP主要针对COCO数据集,AP主要针对VOC数据集。mAP(mean Average Precision)是 AP 的平均值,即计算在各个分类类别上的 AP 后求平均所得。AP 的求法如下:
在VOC物体检测任务中,Pascal VOC 2008 中设置 T h r e s h o l d 1 = 0.5 Threshold_1=0.5 Threshold1=0.5,使用差值平均精确度(interpolated average precision)的评测方法。绘制 P-R 曲线后,选取横轴上的11个点(间隔为0.1)所对应的最大精度,然后再取平均作为最终检测的平均精确度,其表达式如下:
A P = 1 11 ∑ r ∈ { 0 , 0.1 , … , 1 } P i n t e r p ( r ) AP=\frac{1}{11} \sum _{r\in \{0,0.1,\dots , 1 \}} P_{interp} (r) AP=111r∈{0,0.1,…,1}∑Pinterp(r)
P i n t e r p ( r ) = max r ′ ≥ r P ( r ′ ) P_{interp} (r) = \mathop {\max } \limits _{r'\ge r} P(r') Pinterp(r)=r′≥rmaxP(r′)
其中, r r r 是横轴召回率的值, P i n t e r p ( r ) P_{interp}(r) Pinterp(r) 是 r r r 时的差值精度, P ( r ) P(r) P(r) 是 r
对应的纵轴精度。
在 Pascal VOC 中,检测结果只评测了 T h r e s h o l d 1 = 0.5 Threshold_1=0.5 Threshold1=0.5 阈值下的 mAP 值(记为 m A P @ 0.5 [email protected] mAP@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 的平均作为结果,记为 m A P @ [ 0.5 : 0.95 ] mAP@[0.5:0.95] mAP@[0.5:0.95]. 不仅评估到物体检测模型的分类能力,同时也能体现出检测模型的定位能力。
预测的时候会产生很多FP,为了减少FP的数量,一般检测器的最后都会引入一步Non-maximum Suppression(NMS),以去除一部分重复预测的bounding box,YOLOv5中采用加权NMS的方式。
YOLOv1 的损失函数包括三部分,分别是定位损失、置信度损失和分类损失,其形式如下:
YOLOv5 改进了损失函数,进一步提高了模型的收敛速度和训练稳定性,避免了梯度消失和梯度爆炸。YOLOv5 的损失函数也有三部分构成:
box loss
,是预测框与 GT 之间的误差)obj loss, Objectness of the box
)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 ρ、外接矩形框的对角线长度 c c c 都考虑进去,使训练更稳定、收敛更快。YOLOv5使用 CIoU loss 来衡量矩形框的损失。
CIoU loss 将重叠面积、中心点距离、宽高比同时加入了计算,其计算公式如下:
C I o U = I o U − ρ 2 c 2 − α v = D I o U − α v CIoU = IoU - \frac{\rho ^2}{c^2} - \alpha v= DIoU - \alpha v CIoU=IoU−c2ρ2−αv=DIoU−αv
v = 4 π 2 ( arctan w g t h g t − arctan w p r e d h p r e d ) 2 v = \frac{4}{\pi ^2} \left ( \arctan \frac{w_{gt}}{h_{gt}} - \arctan \frac{w_{pred}}{h_{pred}} \right ) ^2 v=π24(arctanhgtwgt−arctanhpredwpred)2
α = v 1 − I o U + v \alpha = \frac{v}{1-IoU+v} α=1−IoU+vv
l o s s C I o U = 1 − C I o U loss_{CIoU} = 1-CIoU lossCIoU=1−CIoU
其中, w g t w_{gt} wgt、 h g t h_{gt} hgt 为 GT 宽、高, w p r e d w_{pred} wpred、 h p r e d h_{pred} hpred 为预测框宽、高, ρ \rho ρ 是两框中心点距离, c c c 是是包围两框的最小矩形框对角线长度, v v v 是两框宽高比的相似度, α \alpha α 是 v v v 的影响因子。
IoU 越大,两框的重叠区域越大,则 α α α 越大,从而 v v v 的影响越大,对宽高比的惩罚力度越大,着重优化宽高比;反之,IoU 越小,两框的重叠区域越小, α α α 越小,从而 v v v 的影响越小,对两框距离的惩罚力度越大,着重优化距离。
YOLOv5 将一张输入的 640 × 640 640\times 640 640×640 图像分割成的 N × N N\times N N×N 网格,每个网格预测 M M M 个预测框(anchor),所以总共预测了 M × N × N M\times N\times N M×N×N 个预测框。每个预测框的预测信息包括矩形框信息、置信度、分类概率。
由于并不是每个预测框内都存在目标,所以在训练时首先需要根据标签作初步判断,判断每个预测框内是否存在目标,以此建立 mask 矩阵(矩阵的每个元素是布尔型)。实际上,并非所有预测框都需要计算所有类别的损失函数值,而是根据 mask 矩阵来决定,决定原则如下:
mask 矩阵的布尔值,由 anchor 框的保留或剔除决定,依照 anchor 框和 GT 的宽高比(aspect ratio)决定 anchor 是否保留。
置信度标签的维度应该与神经网络的输出维度保持一致,因此置信度的标签也是维度为 M × N × N M\times N\times N M×N×N 的矩阵。计算对应预测框与目标框的 CIoU,使用 CIoU 作为该预测框的置信度标签,对 mask 矩阵为 false 的位置,预测框的置信度标签赋值 0. 当 CIoU 小于 0 时,直接取 0 值作为标签,对 CIoU 做截断。由此得到预测置信度矩阵 P.
假设置信度标签为矩阵 L,那么置信度损失的 BCE loss(二元交叉熵损失)函数定义如下:
l o s s B C E ( 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 < M 0 \le z \lt M 0≤z<M, 0 ≤ x , y < N 0 \le x,y \lt N 0≤x,y<N.
从而得到该网络的置信度损失值:
{ l o b j = 1 n u m o f ( m a s k = t r u e ) ∑ m a s k = t r u e l o s s B C E ( z , x , y ) l n o b j = 1 n u m o f ( m a s k = f a l s e ) ∑ m a s k = f a l s e l o s s B C E ( z , x , y ) l o s s o b j = a ∗ l o b j + ( 1 − a ) ∗ l n o b j \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. ⎩⎨⎧lobjlnobjlossobj===num of (mask=true)1∑mask=truelossBCE(z,x,y)num of (mask=false)1∑mask=falselossBCE(z,x,y)a∗lobj+(1−a)∗lnobj
其中, a a a 为 mask = true 时的置信度损失权重, a a a 越大,网络训练时越专注于 mask = true 的正样本情况。为了使训练更专注于正样本,后来 Focal loss 又被提了出来。
为了减少过拟合、增加训练的稳定性,YOLOv5 对独热码标签做了平滑操作,如下所示:
l a b e l s m o o t h = l a b e l ∗ ( 1 − α ) + α / c l a s s n u m label_{smooth} = label*(1-\alpha )+\alpha /class num labelsmooth=label∗(1−α)+α/classnum
α \alpha α 是平滑系数, l a b e l label label 是经过独热编码后的标签向量。
接着,使用 BCE loss 函数计算矩阵中每个 mask=true 元素的分类损失并累加求平均,得到总的分类损失,计算过程如下:
{ l o s s B C E ( z , x , y , c ) = − L s m o o t h ( z , x , y , c ) ∗ log P ( z , x , y , c ) − ( 1 − L s m o o t h ( z , x , y , c ) ) ∗ log ( 1 − P ( z , x , y , c ) ) l o s s c l s = 1 c l a s s n u m ∗ n u m o f ( m a s k = t r u e ) ∑ m a s k = t r u e l o s s B C E ( 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=truelossBCE(z,x,y,c)
其中, L s m o o t h L_{smooth} Lsmooth 是平滑后的 GT 标签, 0 ≤ c < c l a s s n u m 0\le c\lt classnum 0≤c<classnum 对应样本类别数.
相比于AutoML框架的超参数调优,wandb sweeps具有更强的实验管理和数据可视化的能力。wandb sweeps具有一下几个优点:
经过sweep后得到的平行坐标图(parallel coordinates plot)、散点图和相关性分析图如下所示。其中,学习率 lr0
对 [email protected]
的影响最大,呈负相关趋势。
根据 sweep 的结果,选取 [email protected] 值最高的超参数训练模型,具体参数如下所示(未列出参数和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时得到,最佳模型的评价指标如下:
[email protected] | 0.983 |
---|---|
[email protected]:0.95 | 0.939 |
precision | 0.954 |
recall | 0.939 |
最终,模型在验证集上的混淆矩阵如下图所示,该混淆矩阵在列上做归一化,那么,对角元素表示每类的召回率,因为一小部分类别样本量太少,所以召回率较低。背景被判定为菜品的现象存在,但误判率极低。
后期可依据该图,根据需要选取合适的置信度,调整菜品识别的精度和召回率。(精度和置信度成正比,而召回率和置信度成反比)
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 图可知,该样本的分类是有偏的,模型在某些小数量分类上的标签可能不优秀。
模型的部署及界面开发拟采用 PyQt5.
Qt is set of cross-platform C++ libraries that implement high-level APIs for accessing many aspects of modern desktop and mobile systems. These include location and positioning services, multimedia, NFC and Bluetooth connectivity, a Chromium based web browser, as well as traditional UI development.
PyQt5 is a comprehensive set of Python bindings for Qt v5. It is implemented as more than 35 extension modules and enables Python to be used as an alternative application development language to C++ on all supported platforms including iOS and Android.
PyQt5 may also be embedded in C++ based applications to allow users of those applications to configure or enhance the functionality of those applications.
学生端拟用 Android 平台开发,管理端拟用网页开发。
食品图像识别在计算机视觉和多媒体等研究领域中具有重要的理论意义和实际应用价值,但目前仍存在诸多问题与挑战。本文基于YOLOv5v6.1提出了一套适用于中式快餐店的菜品识别自助支付系统,综述了食品识别领域的发展现状,简要介绍了YOLOv5模型的历史背景、发展优势和网络结构。在数据集预处理过程中,通过解析UNIMIB2016,构建了一套行之有效的标签格式转换与校验流程,解决了YOLOv5中文件路径问题、标签格式转换问题和因EXIF信息的存在而导致的标记错位问题。在模型训练阶段,配置了云服务器,引入了Weights and Bias可视化工具,实现了在线监督训练和sweep超参数调优的功能,在sweep中使用hyperband剪枝算法加速了sweep过程,并给出了对于训练过程中可能出现的问题的解决方法。最后介绍了目标识别领域的评价指标和YOLOv5的损失函数,分析了sweep超参数调优的结果,选取最优参数组合训练模型,通过分析样本分布、PR曲线等,选取最佳预测置信度,大幅提升了预测精度和召回率,部署了模型并制作了客户端。
本文虽然选取了一组相对优秀的参数组合以提升模型的[email protected]等评价指标,但是并没有针对目标数据集对模型进行优化,比如微调网络结构以提升精度和速度、使用图像加权策略以缓解样本不平衡问题、使用矩阵推理训练模型以加快推理过程并减少冗余信息、改进k-means以聚类生成anchor、对标签进行平滑处理以增强模型的鲁棒性、冻结backbone以便在数据量不足时获得较好的特征提取效果并提升训练速度。
通过本次实验,对YOLOv5模型训练提出了一些改进方法,认识到了现存的技术难题,为后续项目落地打下基础,也证明了项目落地在技术层面上的可行性。
❤️ 原创文章,转载请注明出处!©️ SylvanDing’s Blog