前言
本人从一个小白,一路走来,已能够熟练使用YOLOv5算法来帮助自己解决一些问题,早就想分析一下自己的学习心得,一直没有时间,最近工作暂时告一段落,今天抽空写点东西,一是为自己积累一些学习笔记,二是可以为一些刚接触YOLOv5算法的小白们提供一些参考,希望大家看之前能够动动你的小手,给我点个关注,给文章点个赞,如果此文确实给你提供了帮助,希望你能在留言区打两个字个“此文有用!”,以此来让这篇文章获得更多的流量,让更多小白能够看到。
YOLOv5
那么多深度学习算法,为什么要用YOLOv5?我觉得很简单,因为YOLOv5快、YOLOv5火、YOLOv5流行啊,为什么不用YOLOv7、YOLOv8,因为不成熟、不稳定,噱头大于改进。所以你要做深度学习,用YOLOv5算法是最稳妥的,就像做人一样,不会很优秀,也不会犯错。中庸之道才是王道。
数据集是什么?就是一堆包含你最终要检测的目标的照片,例如猫、狗、猪。
训练是什么?就是不停的告诉电脑这是猫,这是狗,这是猪的一个不断重复的过程。
检测是什么?就是你教了电脑300遍后,你再拿张有着猫、狗、猪的照片问电脑,哪是猫、哪是狗、哪是猪。
我这样讲,大家应该对这是个怎么回事有个初步的了解了吧,那我们继续往下走。
1.目标检测整体流程
获取算法源码 --------》 配置运行环境 --------》 建立数据集 --------》 标注数据集 --------》 数据集分组 --------》 文件、代码修改 --------》 开始训练、得到模型 --------》 用得到的模型、检测目标 --------》 OVER
2.获取算法源码
YOLOv5代码是开源的,可以免费下载不同的版本,我使用的是YOLOv5 6.1版本,如果你想参考我的步骤做,就下载这个版本的,别的版本我也不熟悉,操作步骤有可能不同。
yolov5-6.1版本代码下载地址
点开网址后,点击左上角的master,下拉框里选择v6.1版本,如下图:
然后点击右边的克隆,下载源代码里选择zip,这样就把源代码下载下来了。如下图:
3.配置运行环境
有了代码,怎样让代码运行通,这需要配置环境,新手小白在这一步搞个三五天很正常,各种错误,各种bug,我也是这么过来的,但是你配置好环境,运行代码没有bug后,你会感觉非常有成就感。记住,不要放弃,就一点一点的解决bug,不要中途放弃。
这一块有其他博主写的很好很详细,大家可以点进去跟着做。
点击配置YOLOv5运行环境
我的环境贴出来给大家看看:
我用的是笔记本:联想拯救者Y9000P2022
操作系统:windows11
显卡:RTX 3060
编译软件:Pycharm
python版本:3.9.1
pytorch版本:1.9.1
cuda版本:11.1
怎样判断环境是否配置好了呢?那就是运行检测程序——detcet.py文件,然后会在runs文件夹里的detcet文件夹下生成一个exp文件夹,里面是一张框出几个人或者车的图片,具体什么我也忘了。反正就是能够运行detect.py文件。
4.建立数据集
在data目录下新建Annotations, images, ImageSets, labels 四个文件夹。
目录结构如下图所示:
其中images存放的是原始的图片数据集,Annotations存放的是标记后生成的xml文件,labels存放的是保存标记内容的txt文件,ImageSets存放的是训练数据集和测试数据集的分类情况。
巧妇难为无米之炊。做好准备工作之后,下一步要干什么?
那就是数据,数据从哪里来?一个就是下载网上公开的数据集,一个就是自己收集数据集。
基本上现在研究YOLOv5算法的主要有两个方向:一是在源代码上做文章,以此能够达到提高精度或者提高速度的目标,然后发论文。二是在数据集上做文章,用自己的数据集去解决某一实际问题,然后发论文。往往第一种方向会用公开的数据集,第二个方向会用自己的数据集。由于第一个方向需要对编程和数学有深厚的学识造诣,所以自然而然我就奔向了第二个方向。我使用的是自己的数据集。
5.标注数据集
Yolov5 使用精灵标注助手制作数据集详细教程
6.数据集分组
在yolov5-6.1根目录下新建一个文件makeTxt.py,(根目录大家都知道啥意思吧?我还真担心有人不知道)
代码如下:
import os
import random
trainval_percent =0.9
train_percent =0.9
xmlfilepath ='data/Annotations'
txtsavepath ='data/ImageSets'
total_xml = os.listdir(xmlfilepath)
num =len(total_xml)list=range(num)
tv =int(num * trainval_percent)
tr =int(tv * train_percent)
trainval = random.sample(list, tv)
train = random.sample(trainval, tr)
ftrainval =open('data/ImageSets/trainval.txt','w')
ftest =open('data/ImageSets/test.txt','w')
ftrain =open('data/ImageSets/train.txt','w')
fval =open('data/ImageSets/val.txt','w')for i inlist:
name = total_xml[i][:-4]+'\n'if i in trainval:
ftrainval.write(name)if i in train:
ftrain.write(name)else:
fval.write(name)else:
ftest.write(name)
ftrainval.close()
ftrain.close()
fval.close()
ftest.close()
接着再新建另一个文件voc_label.py,切记,代码中classes=[……] 中填入的一定要是自己在数据集中所标注的类别名称,标记了几个类别就填写几个类别名,填写错误的话会造成读取不出xml文件里的标注信息。代码如下:
# -*- coding: utf-8 -*-# xml解析包import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
sets =['train','test','val']
classes =['cat','dog','pig']# 进行归一化操作defconvert(size, box):# size:(原图w,原图h) , box:(xmin,xmax,ymin,ymax)
dw =1./size[0]# 1/w
dh =1./size[1]# 1/h
x =(box[0]+ box[1])/2.0# 物体在图中的中心点x坐标
y =(box[2]+ box[3])/2.0# 物体在图中的中心点y坐标
w = box[1]- box[0]# 物体实际像素宽度
h = box[3]- box[2]# 物体实际像素高度
x = x*dw # 物体中心点x的坐标比(相当于 x/原图w)
w = w*dw # 物体宽度的宽度比(相当于 w/原图w)
y = y*dh # 物体中心点y的坐标比(相当于 y/原图h)
h = h*dh # 物体宽度的宽度比(相当于 h/原图h)return(x, y, w, h)# 返回 相对于原图的物体中心点的x坐标比,y坐标比,宽度比,高度比,取值范围[0-1]# year ='2012', 对应图片的id(文件名)defconvert_annotation(image_id):'''
将对应文件名的xml文件转化为label文件,xml文件包含了对应的bunding框以及图片长款大小等信息,
通过对其解析,然后进行归一化最终读到label文件中去,也就是说
一张图片文件对应一个xml文件,然后通过解析和归一化,能够将对应的信息保存到唯一一个label文件中去
labal文件中的格式:calss x y w h 同时,一张图片对应的类别有多个,所以对应的bunding的信息也有多个
'''# 对应的通过year 找到相应的文件夹,并且打开相应image_id的xml文件,其对应bund文件
in_file =open('data/Annotations/%s.xml'%(image_id), encoding='utf-8')# 准备在对应的image_id 中写入对应的label,分别为# <object-class> <x> <y> <width> <height>
out_file =open('data/labels/%s.txt'%(image_id),'w', encoding='utf-8')# 解析xml文件
tree = ET.parse(in_file)# 获得对应的键值对
root = tree.getroot()# 获得图片的尺寸大小
size = root.find('size')# 如果xml内的标记为空,增加判断条件if size !=None:# 获得宽
w =int(size.find('width').text)# 获得高
h =int(size.find('height').text)# 遍历目标objfor obj in root.iter('object'):# 获得difficult ??
difficult = obj.find('difficult').text
# 获得类别 =string 类型
cls = obj.find('name').text
# 如果类别不是对应在我们预定好的class文件中,或difficult==1则跳过if cls notin classes orint(difficult)==1:continue# 通过类别名称找到id
cls_id = classes.index(cls)# 找到bndbox 对象
xmlbox = obj.find('bndbox')# 获取对应的bndbox的数组 = ['xmin','xmax','ymin','ymax']
b =(float(xmlbox.find('xmin').text),float(xmlbox.find('xmax').text),float(xmlbox.find('ymin').text),float(xmlbox.find('ymax').text))print(image_id, cls, b)# 带入进行归一化操作# w = 宽, h = 高, b= bndbox的数组 = ['xmin','xmax','ymin','ymax']
bb = convert((w, h), b)# bb 对应的是归一化后的(x,y,w,h)# 生成 calss x y w h 在label文件中
out_file.write(str(cls_id)+" "+" ".join([str(a)for a in bb])+'\n')# 返回当前工作目录
wd = getcwd()print(wd)for image_set in sets:'''
对所有的文件数据集进行遍历
做了两个工作:
1.将所有图片文件都遍历一遍,并且将其所有的全路径都写在对应的txt文件中去,方便定位
2.同时对所有的图片文件进行解析和转化,将其对应的bundingbox 以及类别的信息全部解析写到label 文件中去
最后再通过直接读取文件,就能找到对应的label 信息
'''# 先找labels文件夹如果不存在则创建ifnot os.path.exists('data/labels/'):
os.makedirs('data/labels/')# 读取在ImageSets/Main 中的train、test..等文件的内容# 包含对应的文件名称
image_ids =open('data/ImageSets/%s.txt'%(image_set)).read().strip().split()# 打开对应的2012_train.txt 文件对其进行写入准备
list_file =open('data/%s.txt'%(image_set),'w')# 将对应的文件_id以及全路径写进去并换行for image_id in image_ids:
list_file.write('data/images/%s.jpg\n'%(image_id))# 调用 year = 年份 image_id = 对应的文件名_id
convert_annotation(image_id)# 关闭文件
list_file.close()# os.system(‘comand’) 会执行括号中的命令,如果命令成功执行,这条语句返回0,否则返回1# os.system("cat 2007_train.txt 2007_val.txt 2012_train.txt 2012_val.txt > train.txt")# os.system("cat 2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt > train.all.txt")
先运行makeTxt.py,makeTxt.py主要是将数据集分类成训练数据集和测试数据集,默认train,val,test按照8:1:1的比例进行随机分类,运行后ImagesSets文件夹中会出现四个文件,主要是生成的训练数据集和测试数据集的图片名称,如下图:
这几个txt你要点开看看,如果时空白的,说明你出错了,如果里面是下图这样的内容,说明没问题。
再运行voc_label.py文件,主要是将图片数据集标注后的xml文件中的标注信息读取出来并写入txt文件,运行后在labels文件夹中出现所有图片数据集的标注信息,如下图:
到这一步,已经把米准备好了,下一步就是修改几个参数就可以训练了。是不是感觉很麻烦,其实不是,你以后这些步骤都不用再做了,主要做的就是train和detect,也就是训练和检测。
7.文件、代码修改
7.1 文件修改
首先在data目录下,找到存在的coco.yaml,然后复制一份重命名为animal.yaml,这个animal是我的名字,你的名字自己取就行,打开animal.yaml,其中path,train,val,test分别为数据集的路径, nc为数据集的类别数,我这里只分了3类,names为类别的名称。这几个参数均按照自己的实际需求来修改。总之,这一步只用修改nc后面的数字,别的不要动。
animal.yaml的代码如下:
# Train command: python train.py --data data/cat.yaml# Dataset should be placed next to yolov5 folder:# parent# ├── yolov5# └── data# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: data # dataset root dir
train: train.txt # train images (relative to 'path')
val: val.txt # val images (relative to 'path')
test: test.txt # test images (optional)# number of classes
nc:3# class names
names:['cat','dog','pig']
7.2 代码修改
yolov5提供了好几个训练模型,让你作为“基模型”进行训练,我用的是yolov5s模型,所以我找到models目录下提供了yolov5s.yaml文件进行修改,只用修改类的个数,我的类是3,所以nc:3.你根据你的实际情况决定n是多少,其余的不用修改。代码如下:
# Parameters
nc:3# number of classes
depth_multiple:1.0# model depth multiple
width_multiple:1.0# layer channel multiple
最后要对train.py中的一些参数进行修改,train.py顾名思义就是用来训练的,
我们平时训练的话,主要用到的只有这几个参数而已:–weights,–cfg,–data,–epochs,–batch-size,–img-size,–project。
parser = argparse.ArgumentParser()# 加载预训练的模型权重文件,如果文件夹下没有该文件,则在训练前会自动下载
parser.add_argument('--weights',type=str, default=ROOT /'yolov5s.pt',help='initial weights path')# 模型配置文件,网络结构,使用修改好的yolov5l.yaml文件
parser.add_argument('--cfg',type=str, default='models/yolov5s.yaml',help='model.yaml path')# 数据集配置文件,数据集路径,类名等,使用配置好的animal.yaml文件
parser.add_argument('--data',type=str, default=ROOT /'data/animal.yaml',help='dataset.yaml path')# 超参数文件
parser.add_argument('--hyp',type=str, default=ROOT /'data/hyps/hyp.scratch.yaml',help='hyperparameters path')# 训练总轮次,1个epoch等于使用训练集中的全部样本训练一次,值越大模型越精确,训练时间也越长,默认为300
parser.add_argument('--epochs',type=int, default=300)# 批次大小,一次训练所选取的样本数,显卡不太行的话,就调小点,反正3060是带不动batch-size=16的,传-1的话就是autobatch
parser.add_argument('--batch-size',type=int, default=16,help='total batch size for all GPUs')# 输入图片分辨率大小,默认为640
parser.add_argument('--imgsz','--img','--img-size',type=int, default=640,help='train, val image size (pixels)')# 是否采用矩形训练,默认False,开启后可显著的减少推理时间
parser.add_argument('--rect', action='store_true',help='rectangular training')# 继续训练,默认从打断后的最后一次训练继续,需开启default=True
parser.add_argument('--resume', nargs='?', const=True, default=False,help='resume most recent training')# 仅保存最终一次epoch所产生的模型
parser.add_argument('--nosave', action='store_true',help='only save final checkpoint')# 仅在最终一次epoch后进行测试
parser.add_argument('--noval', action='store_true',help='only validate final epoch')# 禁用自动锚点检查
parser.add_argument('--noautoanchor', action='store_true',help='disable autoanchor check')# 超参数演变
parser.add_argument('--evolve',type=int, nargs='?', const=300,help='evolve hyperparameters for x generations')# 谷歌云盘bucket,一般不会用到
parser.add_argument('--bucket',type=str, default='',help='gsutil bucket')# 是否提前缓存图片到内存,以加快训练速度,默认False
parser.add_argument('--cache',type=str, nargs='?', const='ram',help='--cache images in "ram" (default) or "disk"')# 选用加权图像进行训练
parser.add_argument('--image-weights', action='store_true',help='use weighted image selection for training')# 训练的设备,cpu;0(表示一个gpu设备cuda:0);0,1,2,3(多个gpu设备)。值为空时,训练时默认使用计算机自带的显卡或CPU
parser.add_argument('--device', default='',help='cuda device, i.e. 0 or 0,1,2,3 or cpu')# 是否进行多尺度训练,默认False
parser.add_argument('--multi-scale', action='store_true',help='vary img-size +/- 50%%')# 数据集是否只有一个类别,默认False
parser.add_argument('--single-cls', action='store_true',help='train multi-class data as single-class')# 是否使用adam优化器,默认False
parser.add_argument('--adam', action='store_true',help='use torch.optim.Adam() optimizer')# 是否使用跨卡同步BN,在DDP模式使用
parser.add_argument('--sync-bn', action='store_true',help='use SyncBatchNorm, only available in DDP mode')# dataloader的最大worker数量,大于0时使用子进程读取数据,训练程序有可能会卡住
parser.add_argument('--workers',type=int, default=8,help='maximum number of dataloader workers')# 训练结果所存放的路径,默认为runs/train
parser.add_argument('--project', default=ROOT /'runs/train',help='save to project/name')# 训练结果所在文件夹的名称,默认为exp
parser.add_argument('--name', default='exp',help='save to project/name')# 如训练结果存放路径重名,不覆盖已存在的文件夹
parser.add_argument('--exist-ok', action='store_true',help='existing project/name ok, do not increment')# 使用四合一dataloader
parser.add_argument('--quad', action='store_true',help='quad dataloader')# 线性学习率
parser.add_argument('--linear-lr', action='store_true',help='linear LR')# 标签平滑处理,默认0.0
parser.add_argument('--label-smoothing',type=float, default=0.0,help='Label smoothing epsilon')# 已训练多少次epoch后结果仍没有提升就终止训练,默认100
parser.add_argument('--patience',type=int, default=100,help='EarlyStopping patience (epochs without improvement)')# 冻结模型层数,默认0不冻结,冻结主干网就传10,冻结所有就传24
parser.add_argument('--freeze',type=int, default=0,help='Number of layers to freeze. backbone=10, all=24')# 设置多少次epoch保存一次模型
parser.add_argument('--save-period',type=int, default=-1,help='Save checkpoint every x epochs (disabled if < 1)')# 分布式训练参数,请勿修改
parser.add_argument('--local_rank',type=int, default=-1,help='DDP parameter, do not modify')# Weights & Biases arguments(一般上用不着)
parser.add_argument('--entity', default=None,help='W&B: Entity')
parser.add_argument('--upload_dataset', action='store_true',help='W&B: Upload dataset as artifact table')
parser.add_argument('--bbox_interval',type=int, default=-1,help='W&B: Set bounding-box image logging interval')
parser.add_argument('--artifact_alias',type=str, default='latest',help='W&B: Version of dataset artifact to use')
opt = parser.parse_known_args()[0]if known else parser.parse_args()
6.开始训练、得到模型
完成以上步骤后,把电脑所以运行的程序都关掉,就留一个P有Charm,运行train.py文件开始训练,这是就是检验你电脑性能的时候了,主要是看你的显卡性能,时间长短由你的显卡性能决定,也由你的数据集的数量决定。
当程序出现如下界面时,说明程序开始训练,没有错误。
为了截图,我的训练轮次epochs设置的时2次,默认是300次。
当训练结束后,会在runs文件夹下train文件里生成一个exp文件夹,里面有一堆文件,其中的weights里的best.pt就是训练得到的检测模型,用于下一步的检测之中。如下图所示:
6.用得到的模型、检测目标
有了训练得到的模型后,下一步我们就要干嘛了?检测试验,看训练得到的检测模型好不好用?那就要在detect.py文件里做文章了,这里面也有一些参数需要设置一下,主要用到的有这几个参数:–weights,–source,–img-size,–conf-thres,–project。详细如下图所示:
parser = argparse.ArgumentParser()# 选用训练的权重,不指定的话会使用yolov5l.pt预训练权重
parser.add_argument('--weights', nargs='+',type=str, default=ROOT /'runs/train/exp/weights/best.pt',help='model path(s)')# 检测数据,可以是图片/视频路径,也可以是'0'(电脑自带摄像头),也可以是rtsp等视频流
parser.add_argument('--source',type=str, default=ROOT /'inference/videos/动物识别.mp4',help='file/dir/URL/glob, 0 for webcam')# 指定推理图片分辨率,默认640
parser.add_argument('--imgsz','--img','--img-size', nargs='+',type=int, default=[640],help='inference size h,w')# 置信度阈值,检测到的对象属于特定类(狗,猫,香蕉,汽车等)的概率,默认为0.25
parser.add_argument('--conf-thres',type=float, default=0.25,help='confidence threshold')# 指定NMS(非极大值抑制)的IOU阈值,默认为0.45
parser.add_argument('--iou-thres',type=float, default=0.45,help='NMS IoU threshold')# 每张图最多检测多少目标,默认为1000个
parser.add_argument('--max-det',type=int, default=1000,help='maximum detections per image')# 检测的设备,cpu;0(表示一个gpu设备cuda:0);0,1,2,3(多个gpu设备)。值为空时,训练时默认使用计算机自带的显卡或CPU
parser.add_argument('--device', default='',help='cuda device, i.e. 0 or 0,1,2,3 or cpu')# 是否展示检测之后的图片/视频,默认False
parser.add_argument('--view-img', action='store_true',help='show results')# 是否将检测的框坐标以txt文件形式保存(yolo格式),默认False
parser.add_argument('--save-txt', action='store_true',help='save results to *.txt')# 在输出标签结果txt中同样写入每个目标的置信度,默认False
parser.add_argument('--save-conf', action='store_true',help='save confidences in --save-txt labels')# 从图片\视频上把检测到的目标抠出来保存,默认False
parser.add_argument('--save-crop', action='store_true',help='save cropped prediction boxes')# 不保存图片/视频,默认False
parser.add_argument('--nosave', action='store_true',help='do not save images/videos')# 设置只检测特定的类,如--classes 0 2 4 6 8,默认False
parser.add_argument('--classes', nargs='+',type=int,help='filter by class: --classes 0, or --classes 0 2 3')# 使用agnostic NMS(前背景),默认False
parser.add_argument('--agnostic-nms', action='store_true',help='class-agnostic NMS')# 推理的时候进行多尺度,翻转等操作(TTA)推理,属于增强识别,速度会慢不少,默认False
parser.add_argument('--augment', action='store_true',help='augmented inference')# 特征可视化,默认False
parser.add_argument('--visualize', action='store_true',help='visualize features')# 更新所有模型,默认False
parser.add_argument('--update', action='store_true',help='update all models')# 检测结果所存放的路径,默认为runs/detect
parser.add_argument('--project', default=ROOT /'runs/detect',help='save results to project/name')# 检测结果所在文件夹的名称,默认为exp
parser.add_argument('--name', default='exp',help='save results to project/name')# 若现有的project/name存在,则不进行递增
parser.add_argument('--exist-ok', action='store_true',help='existing project/name ok, do not increment')# 画图时线条宽度
parser.add_argument('--line-thickness', default=3,type=int,help='bounding box thickness (pixels)')# 隐藏标签
parser.add_argument('--hide-labels', default=False, action='store_true',help='hide labels')# 隐藏置信度
parser.add_argument('--hide-conf', default=False, action='store_true',help='hide confidences')# 半精度检测(FP16)
parser.add_argument('--half', action='store_true',help='use FP16 half-precision inference')# 在onnx推理中使用OpenCV DNN
parser.add_argument('--dnn', action='store_true',help='use OpenCV DNN for ONNX inference')
opt = parser.parse_args()
修改好参数后,直接执行detect.py文件,如果不更改检测结果所产生的路径的话,检测完成后会在runs/detect/exp文件夹得到检测后的图片。如下图所示:
![
至此,整个从0到最后能检测你想要的目标就结束了,到这里是不感觉一点都不难了。万事开头难真的是句真理,等你掌握了这一整套流程后,你会发现你有很多好想法,利用YOLOv5算法来解决生活中很多的现实问题。
大家过程中有什么问题,可以在文章下面留言,我看到会及时回复的,别的朋友看到也会回复的!
再次提醒,别忘了点赞、关注、留言!
我告诉大家一个很残酷的现实,那就是你在学习过程中,出现bug了,你想让哪位博主给你解决,或者你想加别人联系方式,然后想有问题就问,在无偿情况下是不可能的,因为大家都很忙,所以我告诉大家一个方法,那就是遇到问题,就在CSDN上查,没有什么解决不了的,反正我一路走来,遇到的所以问题都是在CSDN上自己查的解决方法,相信我,你也可以的。
版权归原作者 自学小学生 所有, 如有侵权,请联系我们删除。