YOLOv5单目标检测


用于个人记录,好记性不如烂笔头

一、打标签

​ 首先要确立要识别什么,比如这里我要识别的是数字“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,左上角坐标是(9030),右下角坐标(198173)

中心点坐标:[(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

完结!!!!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值