文章目录
用于个人记录,好记性不如烂笔头
一、打标签
首先要确立要识别什么,比如这里我要识别的是数字“8”,那么我就需要给“8打标签”,使用labelimg这个工具进行打标签。其中Frames文件夹是图片,GroundTruth文件夹是对应的xml文件,下面的图片是对应文件夹中对应的文件。
注意!!!!:labelimg是可以直接打标成txt的,这里打标的时候忘选择了
二、标签转换
1.xml和txt展示
由于yolo需要的是txt的形式,我们需要对GroundTruth中的xml进行转换,xml中的坐标是左上角和右下角的坐标,我们需要转成中心点+宽高的样式,并且是txt的文本
下面的图片是xml和转成txt后的样式:
2.坐标转换解释
这里解释一下为什么转成txt后会是小数,并且它的转换过程是什么样的。txt中一共有5个数值,分别代表
的是:(下面提到的标记框就是我们打标的时候框起来的部分)
0 :为标签名称在标签数组中的索引,这里我们只是识别单个目标,下标从0开始,所以是0
0.45:标记框中心点的x坐标,也就是cx
0.3171875:标记框中心点的y坐标,也就是cy
0.3375:标记框的 宽,也就是w
0.446875:标记框的 高,也就是h
计算过程:
通过上面的xml我们知道原图大小是320,左上角坐标是(90,30),右下角坐标(198,173)
中心点坐标:[(xmax+xmin)/2,(ymax+ymin)/2]--->[(90+198)/2=144,(30+173)/2=101.5]
cx:144/320=0.42
cy:101.5/320=0.3171875
w: (198-90)/320=0.3375
h: (173-30)/320=0.446875
代码实现
# -- coding: utf-8 --
# @author : Future
import os
import glob
import shutil
import re
import tqdm
def xml_to_txt(xml_path):
with open(xml_path, "r") as f:
data = f.read()
xmin = int(re.findall(r'<xmin>(.*?)</xmin>', data)[0])
ymin = int(re.findall(r'<ymin>(.*?)</ymin>', data)[0])
xmax = int(re.findall(r'<xmax>(.*?)</xmax>', data)[0])
ymax = int(re.findall(r'<ymax>(.*?)</ymax>', data)[0])
cx = ((xmin + xmax) / 2) / 320 # 320在这里是图片的size
cy = ((ymin + ymax) / 2) / 320
w = (xmax - xmin) / 320
h = (ymax - ymin) / 320
return cx, cy, w, h
def move_file(file_floder):
imgs = glob.glob(os.path.join(file_floder, "Frames", '*.png'))
# 创建images文件夹 labels文件夹
images_path=os.path.join(file_floder, "images")
labels_path=os.path.join(file_floder, "labels")
if not os.path.exists(images_path):
os.mkdir(images_path, 0o777)
if not os.path.exists(labels_path):
os.mkdir(labels_path, 0o777)
# 移动图片到images文件夹,并且对应xml转成txt并移动到labels文件夹
for img in tqdm.tqdm(imgs):
xml_path=img.replace("Frames", "GroundTruth").replace("png", "xml")
if os.path.exists(xml_path):
cx, cy, w, h = xml_to_txt(xml_path)
with open(os.path.join(labels_path, os.path.basename(img).replace("png", "txt")),"w") as f:
f.write("0 " + str(cx) + " " + str(cy) + " " + str(w) + " " + str(h))
f.close()
shutil.copy(img, os.path.join(images_path, os.path.basename(img)))
file_floder = r"F:\yolodata\8"
move_file(file_floder)
执行完代码后,文件夹的变化如下图:
其中images存放的是Frames的图片,其实就是将Frames的图片全部copy到images文件夹中,当然直接移动过去也可以。而labels而存放的是GroundTruth中xml转成txt后的文件。这两个文件他们的文件名字跟原来是一样的,只是labels中的是txt后缀.
三、训练集和验证集
完成上面的操作后,我们就可以拆分训练集和验证集了。就是将images文件夹中的图片按照8比2的比例进行拆分,然后将图片的路径(绝对路径)统一写入到一个train.txt文件中
代码的实现方式:
执行完该脚本后,会在"F:\yolodata\8\images"目录下多出两个txt文件
# -- coding: utf-8 --
# @author : Future
import os
import glob
import random
imgs_path = r"F:\yolodata\8\images"
imgs_list = glob.glob(os.path.join(imgs_path, "*.png"))
# 将图片顺序打乱
random.shuffle(imgs_list)
tran_txt = open(r"F:\yolodata\8\train.txt", "w")
val_txt = open(r"F:\yolodata\8\val.txt", "w")
# 写入文件
for img in imgs_list[:int(len(imgs_list) * 0.8)]:
tran_txt.write(img + "\n")
for img in imgs_list[int(len(imgs_list) * 0.8):]:
val_txt.write(img + "\n")
tran_txt.close()
val_txt.close()
四、yolov5配置项
1.yaml的配置
下面就是yolov5模型的配置文件了,在目录下的data文件中,我们可以创建一个yaml文件,亦或者是copy一份coco.yaml,修改里面的内容成我们自己的内容即可,将我们的训练集和验证集的路径复制上去即可,并填写好类别。
注意:一定要去官方那里下载一下预训练的模型,我这里使用的yolo5n.pt这个预训练的模型
2.train.py的配置
五、训练的模型做预测
1.训练文件说明
当启动训练后,会在runs文件夹中生成一系列的文件,其中weight文件中存放的是我们的pt文件,也就是我们训练出来的模型,下面的图片就是我自己训练过程图片
2.pt模型对视频做预测
在weight文件中,我们使用best.pt来进行做预测,此时还需要修改detect.py文件。下图是修改的配置和运行结果
3.pt模型转onnx模型
将PyTorch(PT)模型转换为ONNX格式的主要原因包括:
模型的可移植性:ONNX作为一个开放的标准化格式,支持多种深度学习框架之间的模型互操作,这意味着转换后的模型可以在不同的框架和硬件上运行,提高了模型的部署灵活性。
加速推理过程:ONNX模型通常针对推理进行优化,可以在各种支持ONNX的平台上快速运行,这对于生产环境中的模型部署尤为重要。
广泛的硬件支持:许多芯片和推理引擎都提供了对ONNX模型的支持,这使得ONNX成为一个通用的模型格式,便于在多种硬件上部署。
易于集成和维护:使用ONNX模型可以简化模型服务的构建和维护工作,因为它减少了不同框架间的依赖和兼容性问题。
促进协作和共享:研究人员和工程师可以更容易地分享和复用ONNX模型,因为接收方不需要关心原始模型是用哪个特定框架训练的。
4.onnx模型对视频做预测
onnx模型需要去netron官网,直接将我们的onnx模型直接拖进去就可以打开了,而onnx模型只有INPUTS和OUTPUTS这俩是需要关注的
从下图可以知道,输入的size必须是[1,3,320,320]的大小,而输出结果是[1,6300,6]。解释一下输出结果:
输出的是一个三维度的数组,而且有6300行,每行有6列。代表着,输入一张图片,经过onnx推理后,一共有6300种结果,每个结果有6个数值信息,代表着:[cx cy w h score label],最后一位是类别,为什么是类别却是小数呢?这是因为我们训练的时候是有一个类,这个小数就是无限接近那个类,可以理解为概率。所以我们只需要将分数最大的那一列拿出来就可以拿到坐标了
6300是它检测框的数量,因为yolov5使用三个尺寸的下采样比例分别是8,16,32,每个尺度对应的特征图大小就是
320/8=40,shape为[40x40]
320/16=20,shape为[20x20]
320/32=10,shape为[10x10]
由于每个特征图中的每个网格会产生3个预测框,所以一共是3x(40x40+20x20+10x10)=6300
这里介绍两种调用onnx的方式,一个是使用pytorch,一个是使用cv2
(1)pytorch方式
# -- coding: utf-8 --
# @author : Future
import numpy as np
import cv2
import onnxruntime as ort
provider = ort.get_available_providers()[1 if ort.get_device() == "GPU" else 0]
# 创建推理实例
ort_session = ort.InferenceSession(
r"E:\yolomaster\yolov5\runs\train\exp36\weights\best.onnx",
providers=[provider]
)
video_path = "E:\数字5-9\WIN_20240716_15_11_39_Pro.mp4"
cap = cv2.VideoCapture(video_path)
while True:
ret, frame = cap.read()
if ret:
# 增加维度并归一化
new_frame = frame[np.newaxis, :, :, :].astype(np.float32)
new_frame = new_frame.transpose(0, 3, 1, 2) / 255.0
# 推理
result = ort_session.run(output_names=["output"],
input_feed={"images": new_frame})
# 输出结果是列表嵌套一个数组,shape是 (1, 6300, 6)
print(result[0].shape)
# 将数组中的第三个维度中,倒数第二个值最大值索引找出来,也就是将第三维度中的倒数第二个值作为条件,将最大值的索引找出来
best_index = result[0][:, :, -2].argmax()
best_reslut = result[0][:, best_index, :]
cx, cy, w, h, score, label = best_reslut[0][0], best_reslut[0][1], best_reslut[0][2], best_reslut[0][3], \
best_reslut[0][4], best_reslut[0][5]
cv2.putText(frame,str(score),(int(cx-w/2),int(cy-h/2)-5),cv2.FONT_HERSHEY_SIMPLEX,1,(255,255,255),2)
# 绘制目标的边框
cv2.rectangle(frame, (int(cx - w / 2), int(cy - h / 2)), (int(cx + w / 2), int(cy + h / 2)), (0, 0, 255),
2)
cv2.imshow("video", frame)
if cv2.waitKey(1) & 0xFF == 27:
cv2.destroyAllWindows()
break
else:
break
cv2.destroyAllWindows()
cap.release()
(2)cv2方式
通过opencv中的DNN包,就可以在opencv中实现深度学习训练好的模型
1.通过下面的api可以读取不同框架下训练好的模型
cv2.dnn.readNet()
cv2.dnn.readNetFromTorch()
cv2.dnn.readNetFromDarknet()
cv2.dnn.readNetFromONNX()
2.通过下面的api将图片变成tensor类型
cv2.dnn.blobFromImage()
3.通过下面api将转成tensor的图传递给网络
setInput(blob对象)
比如:
net=cv2.dnn.readNetFromTorch()
blob=cv2.dnn.blobFromImage(img)
net.setInput(blob)
4.通过下面的api进行网络的预测,前向传播得到结果
output=net.forward()
5.根据不同的模型输出的结果也会不同,自行的将结果中最好的结果拿出来
注意:opencv默认安装的是CPU版本的。如果要调用cuda需要加载模型后进行设置,但是如果安装的opencv不支持cuda则会出现
setUpNet DNN module was not built with CUDA backend; switching to CPU的字
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
Opencv-python DNN模块使用CUDA加速可以看下面这位博主的文章,链接如下,我仅仅是转发他的链接
https://www.jianshu.com/p/2ec17797a924
# 创建网络
net=cv2.dnn.readNetFromONNX(r"E:\yolomaster\yolov5\runs\train\exp36\weights\best.onnx")
video_path = "E:\数字5-9\WIN_20240716_15_11_39_Pro.mp4"
cap = cv2.VideoCapture(video_path)
while True:
ret, frame = cap.read()
if ret:
print(frame.shape)
# 增加维度并归一化,并转成网络需要的格式
# new_frame = frame[np.newaxis, :, :, :].astype(np.float32)
# new_frame = new_frame.transpose(0, 3, 1, 2) / 255.0
input_image = cv2.dnn.blobFromImage(frame, scalefactor=1.0 / 255) # 这一步操作比较耗时,建议使用上面写的方式处理成net需要的shape
net.setInput(input_image)
# 推理
output = net.forward()
max_score_index=np.argmax(output[:,:,-2])
cx, cy, w, h, score, labe = output[0, max_score_index, :]
print(cx)
# 绘制目标的边框
cv2.rectangle(frame, (int(cx - w / 2), int(cy - h / 2)), (int(cx + w / 2), int(cy + h / 2)), (0, 0, 255),
2)
cv2.imshow("video", frame)
if cv2.waitKey(1) & 0xFF == 27:
cv2.destroyAllWindows()
break
else:
break
cv2.destroyAllWindows()
cap.release()
六、数据增强和参数说明
1.数据增强
在yolov5中,工程已经做了数据增强的操作,在工程目录中的data\hyps中,存放的均为yaml格式的文件,这些文件不仅有增强数据的配置项,还有其他超参数的配置项。
hyps文件夹说明:
- hyp.scratch-high.yaml 数据增强高,使用于大型号 例如:v5l、v5x
- hyp.scratch-low.yaml 数据增强低,使用于小型号 例如:v5n、v5s
- hyp.scratch-med.yaml 数据增强中,使用于中型号 例如:v5m
在这次的训练中我用的是hyp.scratch-low.yaml,可以直接在train.py进行设置
2.参数说明
参数说明比较多,可以参考这位博主的文章,里面非常详细的记录了每个参数的用法
https://blog.csdn.net/m0_47026232/article/details/129869740
完结!!!!