- 本设计主要面向于公共场所宠物狗是否佩戴狗嘴套的自动检测场景。
- 运用了YOLOv5目标检测深度学习框架
- 应用场景的相似性,表明可充分利用新冠疫情过后遗留的检测设备,降低普及本应用的社会设备成本。为利用疫情遗留电子设备提供了思路。
1. 设计前言
1.1 设计内容说明
社会问题:
随着宠物陪伴在生活中的现象不断加深,生活中经常出现宠物狗主人带着宠物狗出入公共场所。然而尽管这些宠物狗或许有接种狂犬病疫苗,又或者是温顺。但是宠物狗伤人事件屡有发生。其中狗类往往通过锋利的犬牙造成攻击,因此在公共场所为宠物狗佩戴狗嘴套和牵戴狗绳是实现路人与宠物狗和谐相处的重要措施。然而,专门为公共场所配置宠物狗规范出行引导人员,不但只是一笔较大的社会公共支出,而且容易引发劝导人员和宠物狗主人的矛盾。注意到新冠肺炎疫情结束后,大量自动识别口罩佩戴的设施将会被闲置。
设计意义:
因此结合场景任务相似性,本设计提出了狗罩佩戴自动识别算法的设计与实现(基于YOLO5s),以望能够充分利用已有设备,降低社会成本的情况下,实现公共场所狗罩佩戴的自动无人监督。提高宠物狗与社会的和谐相处。
1.2 相关工具版本说明
工具 | 版本 |
---|---|
Python | 3.8 |
Sublime Text | 4126 |
Pychram | 2022.2.4 |
YOLO | v5 |
torch | 2.0.0 |
LabelImg | 1.8.6 |
1.3 设计思路
2. YOLO5
2.1 YOLO5原理
YOLO属于多目标检测,但不同与简单的cnn模型,YOLO模型的任务还需要标注出具体目标的在图片中的位置:
1) 识别出图片中的物体的种类(根据训练喂给的种类)
2) 给出物体的种类在图片中的具体位置
问题1为分类问题,问题2为回归问题。输出的结果通过给出图片中的(x,y, w, h)来进行框体标注,体现了任务2;并在框体上给出相应的类别标签和置信度,体现了任务1。
2.2 安装 YOLO5
使用git bash here,将github的YOLOv5克隆到本地,就可以顺利下载YOLOv5了
git clone https://github.com/ultralytics/yolov5.git
3. 图片收集
3.1 使用爬虫爬取百度照片
从百度图片接口,提交关键词,将返回的图片爬取并保存。
爬虫思路:
百度图片的网页是一个动态网页,通过不断的对API传入新的参数可以获得新的图片数据,而每批次都含有一定的图片数据。因此可通过嵌套两个循环,爬取关键词百度图片。
循环一: 向百度图片接口传入给定参数,因此通过for循环,可以调取到想要的照片数量。
循环二: 每一次百度图片API返回的数据中,含有一定数量的关键词图片,通过对数据的for循环遍历,爬取关键词图片并保存到本地。
3.1.2 百度图片爬虫代码实现
#!/usr/bin/python3
# encoding=utf-8
from fake_useragent import UserAgent
import requests
import re
import os
headers = {'User-agent': UserAgent().random,
"Accept-Encoding": "gzip,deflate,br",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"Connection": "keep-alive"}
img_re = re.compile('"thumbURL":"(.*?)"')
img_format = re.compile("f=(.*).*?w")
def file_op(father_file, img, dog_class, picture_name):
foldername = os.path.join(father_file, dog_class)
tmp_file_name = f'{foldername}/{picture_name}.jpg'
if not os.path.exists(foldername):
os.makedirs(foldername)
with open(file=tmp_file_name, mode='wb') as file:
try:
print("it works")
file.write(img) # 因为这里已经隐式的进行数据输出,所以即使没有return函数也没有关系
except:
print('found it')
pass
def xhr_url(father_file, url_xhr, text_, start_num=0, page=5):
end_num = page * 30
picture_num = 0
picture_name = f'P_{picture_num}'
#--------------------循环一------------------
for page_num in range(start_num, end_num, 30):
resp = requests.get(url=url_xhr + str(page_num), headers=headers)
if resp.status_code == 200:
img_url_list = img_re.findall(resp.text) # 这是个列表形式
#print(img_url_list)
#------------------循环二----------------------
for img_url in img_url_list:
try: # 直接跳过数据错误的乱码
img_url = img_url.replace("\\", "")
print(img_url)
img_rsp = requests.get(url=img_url, headers=headers)
file_op(father_file = father_file, img=img_rsp.content, dog_class=text_,
picture_name=picture_name)
print("{} 下载完毕".format(picture_name))
picture_num += 1
picture_name = f'P_{picture_num}'
except:
print("遇到了一个错误的图片url")
continue
else:
print("遇到了网页反爬")
break
print("内容已经完全爬取")
if __name__ == "__main__":
father_file = input('分类照片将放置在: ')
text_ = input("输入你想检索内容:")
# 学习参数批量导入
org_url = "https://image.baidu.com/search/index?ct=201326592&tn=baiduimage&word={}&pn=".format(
text_)
url_xhr = org_url
start_num = int(input("开始页:")) # 建议为0
page = int(input("所需爬取页数:")) # 照片最终数量可通过 30*page 初略估计
xhr_url(father_file, org_url, text_, start_num, page)
3.1.3 爬取关键词图片
关键词1: 狗套、狗嘴套
关键词2: 狗的照片
cd <爬虫代码文件父亲目录>
python dog_scrap.py
# 以下内容由python的input函数实现交互,请填入尖括号内的信息
分类照片将放置在:<提供一个文件地址>
输入你想检索内容:<关键词1>
开始页:<0>
所需爬取页数:<30>
输入你想检索内容:<关键词2>
开始页:<0>
所需爬取页数:<30>
爬取过程
最后我们将获得两个分别以两个关键词命名的文件夹,文件夹中有相应关键词的百度图片搜索结果。
3.2 从视频数据中获取图片
视频是将多张图片在一定频率播放形成的。视频之中每秒的帧数都是固定的,有多少帧就表示有多少张图片。
video_to_jpg.py能够实现将视频文件转换为图片文件。
import cv2
def video2frame(videos_path, frames_save_path,time_interval):
vidcap = cv2.VideoCapture(videos_path)
success, image = vidcap.read()
count = 0
while success:
success, image = vidcap.read()
count += 1
if count % time_interval == 0:
cv2.imencode('.jpg', image)[1].tofile(frames_save_path + "/frame%d.jpg" % count)
if __name__ == '__main__':
video2frame(r'E:\p1.mp4', r'E:\pen',2)
由于设计中并没有用到视频数据进行训练,此处只简介相关方法
在工程的data文件夹下新建video文件夹,在里面存放要识别的视频(也可以放在其他位置,注意修改路径即可)
在detect.py文件中运行修改参数
parser.add_argument('--source', type=str, default='data/video', help='source') # file/folder, 0 for webcam
代码将会将视频自动切成多张图片,进行框体识别,并将识别后的图片合成一个视频,存放在runs\detect\exp2目录下
视频识别
4. 训练图片处理
4.1 数据扩充
数据扩充(Data Augmentation)有助于提高识别精度。Data Augmentation基于算法“人为地”扩充输入图像(训练图像)。具体地说,对于输入图像,通过施加旋转、垂直或水平方向上的移动等微小变化,增加图像的数量。这在数据集的图像数量有限时尤其有效。——《深度学习入门 基于Python的理论与实现》
通过python包 cv2 可便捷的实现数据扩充
基础的数据扩充方式:
翻转、旋转、尺度变换、随机抠取、色彩抖动、高斯噪声、随机模糊、随机擦除
进阶的数据扩充 方式:
Fancy PCA、监督式抠取、GAN生成
Data Augmentation
数据扩充工具
Python实现11种图像扩增方法
4.2 重命名
一下代码可以对收集到的图片进行重命名,格式为
rename.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
class ImageRename():
def __init__(self):
self.path = 'D:\\dog_picture\\YOLO_dog_mask\\JPEGImages'
def rename(self):
filelist = os.listdir(self.path)
total_num = len(filelist)
# 设置文件名范围
max_num = len(str(total_num))
name_format = '0>{}s'.format(max_num)
i = 1
for item in filelist:
if item.endswith('.jpg'):
# abspath 比如桌面有文件的快捷方式,abs就是快捷方式的路径,
# realpath real就是快捷方式对应文件的路径,在def盘之类的
# format(str(i), '0>6s') 表示将int转换为字符串后,用0填补6个位中的空缺位置
src = os.path.join(os.path.abspath(self.path), item)
dst = os.path.join(os.path.abspath(
self.path), format(str(i), name_format) + '.jpg')
os.rename(src, dst)
print('converting %s to %s ...' % (src, dst))
i = i + 1
print('total %d to rename & converted %d jpgs' % (total_num, i))
if __name__ == '__main__':
newname = ImageRename()
newname.rename()
4.3 切分数据集
YOLOv5附属目录允许有相同文件名的文件,所以即使train文件夹中有1.jpg,test文件夹中也可以重新从1.jpg命名图片数据。
yolov5根目录下创建文件夹visdronedata及其附属目录
将visdrone的images文件夹里面的图片全部复制到images/train和iamges/val里面,上面程序生成的labels文件夹,将里面的所有txt复制到labels/train和labels/val里面
参考博客
picture_split.py
# -*- coding:utf-8 -*-
# 将一个文件夹下图片按比例分在三个文件夹下
import os
import random
import shutil
from shutil import copy2
datadir_normal = "E:\\BaiduNetdiskDownload\\1test\\"
all_data = os.listdir(datadir_normal) # (图片文件夹)
num_all_data = len(all_data)
print("num_all_data: " + str(num_all_data))
index_list = list(range(num_all_data))
# print(index_list)
random.shuffle(index_list)
num = 0
# 将训练集放在这个文件夹下
trainDir = 'images/train/'
if not os.path.exists(trainDir):
os.mkdir(trainDir)
# 将验证集放在这个文件夹下
validDir = 'images/val/'
if not os.path.exists(validDir):
os.mkdir(validDir)
# 将测试集放在这个文件夹下
testDir = 'images/test/'
if not os.path.exists(testDir):
os.mkdir(testDir)
train_index = 0
val_index = 0
test_index = 0
for i in index_list:
fileName = os.path.join(datadir_normal, all_data[i])
if num < num_all_data * 0.6:
train_dst = os.path.join(os.path.abspath(
trainDir), '' + str(train_index) + '.jpg')
train_index = train_index + 1
copy2(fileName, train_dst)
# os.rename(trainDir, train_dst)
elif num >= num_all_data * 0.6 and num < num_all_data * 0.8:
# print(str(fileName))
val_dst = os.path.join(os.path.abspath(
validDir), '' + str(val_index) + '.jpg')
val_index = val_index + 1
copy2(fileName, val_dst)
else:
test_dst = os.path.join(os.path.abspath(
testDir), '' + str(test_index) + '.jpg')
test_index = test_index + 1
copy2(fileName, test_dst)
num += 1
4.4 labelImg标注
这里需要注意的是在LableImg中,将标注的格式调整为 yolo_text格式
可参考这篇博客
5. 训练
5.1 数据集配置文件
在yolov5目录下的data文件下新建一个mydata.yaml文件,用来存放训练集和验证集的划分文件。同时修改类别的names
# 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: ../datasets/imagenet # dataset root dir
train: train # train images (relative to 'path') 1281167 images
val: val # val images (relative to 'path') 50000 images
test: # test images (optional)
# Classes
names:
0:dog_with_mask
1:dog_without_mask
其中names通过
int:class_name 形式定义类别。注意int需要与labelImg的标注结果的类别数字保持一致
文件目录通过
path: …/datasets/catdog
train: …/datasets/catdog/images/train
val: …/datasets/catdog/images/val
5.2 调整模型文件
文件位置: yolov5 --> model
yolov5提供了5种不同类型的模型配置文件:
yolov5l.yaml、yolov5m.yaml、 yolov5n.yaml 、yolov5s.yaml、 yolov5x.yaml
不同的网络模型,由其中的depth_multiple和width_multiple来控制其网络的宽度和深度
depth_multiple(模型深度) | width_multiple (模型宽度) | |
---|---|---|
yolov5s.yaml | 0.33 | 0.50 |
yolov5n.yaml | 0.33 | 0.25 |
yolov5m.yaml | 0.67 | 0.75 |
yolov5l.yaml | 1.0 | 1.0 |
yolov5x.yaml | 1.33 | 1.25 |
下面是官方给出的相应模型参数,大家可根据自己需要选择模型。注:YOLOv5n应该是目前相对较小的模型。
随着深度和宽度的睁大,训练时间也会相应的增大。若对类别较小的,细节要求不高的数据集进行训练,推荐使用s、n两个版本之一
需要调整的参数:
nc (number of classes)应该调整为数据集中的类别总数
anchors:
- [10,13, 16,30, 33,23] # P3/8
- [30,61, 62,45, 59,119] # P4/16
- [116,90, 156,198, 373,326] # P5/32
5.3 下载预训练模型
根据所选的YOLOv5模型的版本,在GtiHub下载相应的模型。
yolo5s.pt是git yolo5之后就在文件下存在的。因为许多博客中使用的是yolo5s.pt。所以有些博客就略去模型权重的下载。这里根据自己的需要下载相应的pt文件。
不知道如何知道文件地址的,可以参考这个博客
插入一点迁移学习的概念:
实践中经常会灵活应用ImageNet这个巨大的数据集学习到的权重数据,这称为迁移学习,将学习完的权重(的一部分)复制到其他神经网络,进行再学习(fine tuning)。比如,准备一个和VGG相同结构的网络,把学习完的权重作为初始值,以新数据集为对象,进行再学习。迁移学习在手头数据集较少时非常有效。——《深度学习入门 基于Python的理论与实现》
5.4 训练
(1)方式一:修改train.py
从train.py中可以看出,default部分为需要调参的地方。可根据需要进行调整。
train.py在 yolov5文件下即可查看。
如果修改train.py, 那么可以直接在命令行中直接运行
python train.py
(2)方式二:通过命令行参数传递参数
下面的cmd命令是官方推荐的
python train.py --data coco.yaml --epochs 300 --weights '' --cfg yolov5n.yaml --batch-size 128
yolov5s 64
yolov5m 40
yolov5l 24
yolov5x 16
结合一些博客后,给出一些命令行命令
example1:参考博客
python train.py --img 640 --batch 32 --epoch 300 --data data/mydata.yaml --cfg models/yolov5x.yaml --weights weights/yolov5x.pt --device '0,1'
example2:
python train.py --data data.yaml --cfg yolov5s.yaml --weights pretrained/yolov5s.pt --epoch 100 --batch-size 4 --device cpu
我的
python train.py --img 640 --batch 16 --epoch 300 --data data/mydata.yaml --cfg models/yolov5s.yaml --weights weights/yolov5s.pt --device '0'
参数说明
--data 数据配置文件路径
--weights 权重文件的路径
--cfg 模型配置文件的路径
--batch-size 批处理大小
--cache 将图片缓存进内存,加快读取速度
--epoch 迭代次数
--device 训练设备 0为gpu, 1为cpu
5.5 训练报错
1). ImportError: Bad git executable.遇到git包调用问题
通过anaconda prompt转到虚拟环境YOLO中,
conda install git
2).
Validating runs\train\exp4\weights\best.pt...
RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)`
6. 模型评估
6.1 lables_correlogam.jpg
xywh
从框体(x, y, w, h) 的相关图可以看出
- width 与 height 呈现明显的线性关系
- x 与 y 的散点图表明,框体目标主要集中在中间偏下部位
- width 的分布较 height 的直方图分布更为均匀
6.2 labels.jpg
本设计中只有dog_with_mask和dog_without_mash两个类别。从labels.jpg中可以观察到
dog_with_mask: 框体较多,为700多个
dog_without_mask: 框体较少,为550多个
框体位置: 框体在图片中的位置分布较为均匀,几乎图片的所有位置都覆盖过框体,说明了数据集的质量具有较强的场景囊括性
x和y的散点图: 表明尽管框体位置几乎覆盖图片所有位置,但是框体仍然比较倾向于分布在图片中央,这是由于网络图片中主体往往在图片构图中央导致
width和height的散点图: 可以看出,width和height存在较强的线性关系,且可以预估其相关系数约等于1.
6.3 result.png
result.png文件中反映了YOLO5在多个epoch过程中的相应的模型评价指标。
在yolov5 --> train --> exp 文件夹中可以找到。
如果找不到result.png,只找到result.csv可参考这篇博客
在yolov5文件夹下新建result_png.py文件
# 将results.csv可视化
from utils.plots import plot_results
plot_results(file='C:/pythonx/yolo/yolov5-fire/runs/train/exp/results.csv', dir='')
主义file=''改为你的results.csv路径,然后在YOLO5虚拟环境下运行该脚本,即可生成result.png文件
box_loss | 方框损失均值,越小方框标注的(w, h)越准 |
obj_loss | 推测为目标检测loss均值,越小目标检测越准(x,y)越准 |
cls_loss | 为分类loss均值,越小分类越准,也就是LabelImg最前面的class那行越准 |
mAP_0.5 | AP是用Precision和Recall作为两轴作图后围成的面积,m表示平均,后面的数表示判定iou为正负样本的阈值 |
precision | 准确率(找对的/找到的) |
recall | 召回率(找对的/该找对的 |
mAP_0.5:0.95 | AP是用Precision和Recall作为两轴作图后围成的面积,m表示平均,后面的数表示判定iou为正负样本的阈值,0.5:0.95表示阈值取0.5:0.95后取均值。 |
result分析: 其中可以看到precision和recall在80次epoch后趋于平稳,而box_loss、obj_loss和cls_loss在200次epoch后,已达到不错的水平,但是仍有发展空间,可以在下次训练中将epoch参数调高至300。
7. 使用模型
使用detect.py脚本进行模型调用,覆盖了基本的需求,使用本地图片或者视频、使用电脑摄像头、使用网络摄像头或者流媒体。调用detect.py修改参数有两种方法:
方法一:直接在detect.py修改相关参数
方法二:在cmd中使用通过系统进行传参
个人推荐使用方法二,因为不会直接修改detect.py,降低了往后其他项目使用detect.py文件出现bug的几率
7.1 识别本地图片或者视频
找到detec.py 文件: yolov5 --> detect.py
找到parse_opt()函数
主要修改 --weights --source这两个参数
– weights runs/train/exp8/weights/last.pt (使用训练出来的模型)
- 识别多个图片
– source mydata/images/test (需要进行识别的图片) - 识别单个视频或图片
– source video\test.mp4
在YOLO5虚拟环境中转到detect.py的文件目录下,运行一下命令
python detect.py
数据监测结果输出路径:runs\detect\exp
或者直接运行下列指令,(需要更换相应的参数)
python detect.py --weights runs/train/exp4/weights/last.pt --source mydata/images/test
7.2 调用电脑主机摄像头
anaconda prompt下切换到虚拟环境
conda activate yolo5
转到 detect.py文件目录下
cd D:\YOLO5\yolov5
运行指令
python detect.py --weights runs/train/exp4/weights/last.pt --source 0
参数说明:
- weights: 当模型权重与detect.py同在yolov5文件夹下时,应该使用全文件路径。本文中使用了最后一次epoch训练得到的权重last.pt,可根据自身需求调整为best.pt 。
- source: 设置为0,表示调用摄像头0,如果有多个可以改为1、2、3
7.3 识别网络摄像头或者流媒体
设计中并没有实际运用网络摄像头或者流媒体的需求,考虑到博客协作框架的整体性,可参考以下博客
使用模型
7.4 使用模型bug
引起这种问题的原因主要有两个:
问题一:由于绝对路径的问题导致文件路径错误 参考博客
问题二:由于图片格式错误导致的cv2.imread§代码值为None导致的错误
博主的情况为问题二,所以只要剔除格式错误的图片即可。
7.5 识别结果
恭喜看到这里的同学,我们康康图片放松一下吧。
7.6 改进方向
问题一:
在收集到的百度图片中,佩戴狗嘴套的图片大都来自网络电商,大多图片是相似的。因此复杂的识别场景,该设计的表现可能会逊色。
解决方案:
可爬取视频数据,进行抽帧生成图片,丰富训练图片数据的来源
完善设计使用窗口
通过将模型设计成pyqt5相关易交互界面,有助于提供设计的实用性。但限于博主个人技术水平,暂且未实现。
目标结果参考该博客
8. 设计总结
作为新手,可以说本文许多技术问题都是从各博客学习过来的,再结合自己的设计思路,属于是拾人牙慧了。对引用了博客或者是遗漏的借鉴,小giao客都是无比感谢的。
- 记录我的学习历程
- 通过写教程的方式来加强我对YOLOv5的使用
- 希望能够帮助其他学习YOLOv5的同学。
前途是美好的,道路是曲折的!共勉!
9. 参考资料
https://blog.csdn.net/qq_40716944/article/details/118188085
https://blog.csdn.net/weixin_43356770/article/details/124657257
https://zhuanlan.zhihu.com/p/585271517
https://blog.csdn.net/q839039228/article/details/126299110
https://blog.csdn.net/JF823255922/article/details/119324552
https://www.cnblogs.com/chen1880/p/17054949.html
https://blog.csdn.net/JF823255922/article/details/119324552
https://blog.csdn.net/weixin_41990671/article/details/107300314
https://blog.csdn.net/Albert_yeager/article/details/128763279
https://www.ycpai.cn/python/xHgm6pGB.html
ttps://blog.csdn.net/m0_55317949/article/details/125268136
https://blog.csdn.net/qq_36756866/article/details/109111065