【Yolov5+Deepsort】训练自己的数据集(2)| 目标检测追踪 | 轨迹绘制

news2024/11/26 8:34:23

📢前言:本篇是关于如何使用YoloV5+Deepsort训练自己的数据集,从而实现目标检测与目标追踪,并绘制出物体的运动轨迹。本章讲解的为第二部分内容:训练集的采集与划分,Yolov5模型的训练。本文中用到的数据集均为自采,实验动物为斑马鱼。

💻环境&配置:RTX 3060、CUDA Version: 11.1、torch_version:1.9.1+cu111、python:3.8

源码如下:

GitHub - mikel-brostrom/yolo_tracking: A collection of SOTA real-time, multi-object tracking algorithms for object detectors

GitHub - Sharpiless/Yolov5-Deepsort: 最新版本yolov5+deepsort目标检测和追踪,能够显示目标类别,支持5.0版本可训练自己数据集

如果想进一步了解Yolov5+Deepsort中的算法,猛戳这里:【Yolov5+Deepsort】训练自己的数据集(1)| 目标检测&追踪 | 轨迹绘制

目录

Ⅰ准备数据集 

0x00 数据集的采集

0x01 数据集的标注

0x02 数据集的划分

Ⅱ Yolov5模型训练

0x00 修改配置文件

0x01 选择预训练模型

0x02 训练结果

0x03 训练结果浅析

0x04 替换权重文件


Ⅰ准备数据集 

0x00 数据集的采集

使用USB3.0工业相机对运动的斑马鱼进行照片的抓拍采集,共采集到照片1w+。

数据集示例:

❓为什么使用工业相机呢:

  • 可以提供高分辨率和高质量的图像,确保准确的视觉分析和检测。
  • 具备高帧率和快速曝光时间,能够在高速运动或快速生产线上捕获清晰的图像,确保高效的生产过程。
  • 工业相机经过严格的测试和质量控制,具有高度的稳定性和可靠性,能够长时间稳定工作。

由于工业相机的成本等问题,不使用工业相机也可以采集到质量较高的图片,但是采集的图片一般要满足以下要求:

  1. 图像质量:图像应该具有足够的清晰度和图像质量,以确保模型能够正确地提取特征并进行准确的预测。低质量或模糊的图像可能会导致模型性能下降。
  2. 统一尺寸:数据集中的图像应该具有统一的尺寸。在训练过程中,通常需要将图像调整为相同的大小,以便于批量处理。
  3. 多样性:数据集应该包含多样性的图像样本,涵盖不同的场景、角度、光照条件、背景等。这样可以确保模型在各种情况下都能表现良好。
  4. 平衡类别:如果数据集是分类任务,每个类别的样本应该尽量保持平衡。不平衡的类别分布可能导致模型对少数类别的表现不佳。

0x01 数据集的标注

在机器学习和计算机视觉领域,有许多常用的图像数据标注软件,用于对图像数据进行标注和注释。

我们使用LabelImg对抓拍的图片进行标记:

由于我们后续要使用Yolov5作为目标检测的模型,故我们的数据集需采用YOLO的格式进行标记。 

 得到标记后的txt格式

 🚩为了方便读者的实际操作,在这里给出VOC格式转YOLO格式(txt格式)的代码:

import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join


def convert(size, box):
    x_center = (box[0] + box[1]) / 2.0
    y_center = (box[2] + box[3]) / 2.0
    x = x_center / size[0]
    y = y_center / size[1]
    w = (box[1] - box[0]) / size[0]
    h = (box[3] - box[2]) / size[1]
    return (x, y, w, h)


def convert_annotation(xml_files_path, save_txt_files_path, classes):
    xml_files = os.listdir(xml_files_path)
    print(xml_files)
    for xml_name in xml_files:
        print(xml_name)
        xml_file = os.path.join(xml_files_path, xml_name)
        out_txt_path = os.path.join(save_txt_files_path, xml_name.split('.')[0] + '.txt')
        out_txt_f = open(out_txt_path, 'w')
        tree = ET.parse(xml_file)
        root = tree.getroot()
        size = root.find('size')
        w = int(size.find('width').text)
        h = int(size.find('height').text)

        for obj in root.iter('object'):
            difficult = obj.find('difficult').text
            cls = obj.find('name').text
            if cls not in classes or int(difficult) == 1:
                continue
            cls_id = classes.index(cls)
            xmlbox = obj.find('bndbox')
            b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
                 float(xmlbox.find('ymax').text))
            # b=(xmin, xmax, ymin, ymax)
            print(w, h, b)
            bb = convert((w, h), b)
            out_txt_f.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')


if __name__ == "__main__":
    # 需要转换的类别,需要一一对应
    classes1 = ['helmet']
    # 2、voc格式的xml标签文件路径
    xml_files1 = r'path'
    # 3、转化为yolo格式的txt标签文件存储路径
    save_txt_files1 = r'path'

    convert_annotation(xml_files1, save_txt_files1, classes1)

因为有多种标记工具可以用于标记,若使用LabelMe进行标记,则需要将json格式转为txt格式

import os
import json
from PIL import Image


json_dir = 'path'  # json文件路径
out_dir = 'path'  # 输出的 txt 文件路径


def get_json(json_file, filename):
    # 读取 json 文件数据
    with open(json_file, 'r') as load_f:
        content = json.load(load_f)

    file_path = 'D:/train/image/'+filename+'.jpg' # 每个json文件对应的图片文件路径
    img = Image.open(file_path)
    # imgSize = img.size  # 大小/尺寸
    image_width = img.width  # 图片的宽
    image_height = img.height  # 图片的高

    filename_txt = out_dir + filename+'.txt'

    # 创建txt文件
    fp = open(filename_txt, mode="w", encoding="utf-8")
    # 将数据写入文件
    #fp.close()
    for t in content:
        if(t['type']<=7):
            # 计算 yolo 数据格式所需要的中心点的 相对 x, y 坐标, w,h 的值
            x = (t['x'] + t['width']/ 2) / image_width #归一化
            y = (t['y']+ t['height']/ 2 )/ image_height #归一化
            w = t['width']/ image_width #归一化
            h = t['height'] / image_height #归一化
            fp = open(filename_txt, mode="r+", encoding="utf-8")
            file_str = str(t['type']-1) + ' ' + str(round(x, 6)) + ' ' + str(round(y, 6)) + ' ' + str(round(w, 6)) + \
                       ' ' + str(round(h, 6))
            line_data = fp.readlines()

            if len(line_data) != 0:
                fp.write('\n' + file_str)
            else:
                fp.write(file_str)
            fp.close()

def main():
    files = os.listdir(json_dir)  # 得到文件夹下的所有文件名称
    s = []
    for file in files:  # 遍历文件夹
        filename = file.split('.')[0]
        get_json(json_dir+'/'+file, filename)


if __name__ == '__main__':
    main()

0x02 数据集的划分

数据集划分是在机器学习和深度学习任务中至关重要的步骤,用于将数据集分成训练集、验证集和测试集。以下是简单的数据集划分方法:

  • 训练集(Training set):用于训练模型的数据集。训练集占据整个数据集的大部分,通常约为总数据集的60-80%。模型通过训练集来学习数据的特征和模式。
  • 验证集(Validation set):用于调整模型的超参数和选择最佳模型。验证集是用来评估模型在训练过程中的性能,并帮助确定哪些超参数设置最优。验证集通常约占数据集的10-20%。
  • 测试集(Test set):用于评估模型的泛化能力和性能。测试集是在训练和调参完成后,用来验证模型在新数据上的表现。测试集应该与训练集和验证集没有重叠,通常约占数据集的10-20%。  

数据集划分应该尽量保持数据的随机性,避免训练集、验证集和测试集之间的数据分布差异过大。

若数据集划分不当,则容易出现过拟合和欠拟合:

 1.过拟合(Overfitting):

  • 当训练集过小,无法充分代表整个数据分布时,模型可能会在训练集上表现得很好,但在未见过的数据上表现不佳,这称为过拟合。
  • 过拟合问题会导致模型过度记忆训练集中的噪声和细节,而无法泛化到新数据上。
  • 过拟合通常在验证集和测试集上表现较差,但在训练集上表现优秀。

2.欠拟合(Underfitting):

  • 当训练集过大或模型复杂度不够高时,模型可能会无法充分学习数据的规律,而表现不佳,这称为欠拟合。
  • 欠拟合问题会导致模型无法学习数据的真实分布和特征,表现较差且泛化能力差。
  • 欠拟合通常在训练集、验证集和测试集上表现均较差。

 划分好的格式如下: 

可以手动进行数据集的划分,也可以使用如下代码进行数据集的划分:

import os
import shutil
import random

random.seed(0)


def split_data(file_path,xml_path, new_file_path, train_rate, val_rate, test_rate):
    each_class_image = []
    each_class_label = []
    for image in os.listdir(file_path):
        each_class_image.append(image)
    for label in os.listdir(xml_path):
        each_class_label.append(label)
    data=list(zip(each_class_image,each_class_label))
    total = len(each_class_image)
    random.shuffle(data)
    each_class_image,each_class_label=zip(*data)
    train_images = each_class_image[0:int(train_rate * total)]
    val_images = each_class_image[int(train_rate * total):int((train_rate + val_rate) * total)]
    test_images = each_class_image[int((train_rate + val_rate) * total):]
    train_labels = each_class_label[0:int(train_rate * total)]
    val_labels = each_class_label[int(train_rate * total):int((train_rate + val_rate) * total)]
    test_labels = each_class_label[int((train_rate + val_rate) * total):]

    for image in train_images:
        print(image)
        old_path = file_path + '/' + image
        new_path1 = new_file_path + '/' + 'train' + '/' + 'images'
        if not os.path.exists(new_path1):
            os.makedirs(new_path1)
        new_path = new_path1 + '/' + image
        shutil.copy(old_path, new_path)

    for label in train_labels:
        print(label)
        old_path = xml_path + '/' + label
        new_path1 = new_file_path + '/' + 'train' + '/' + 'labels'
        if not os.path.exists(new_path1):
            os.makedirs(new_path1)
        new_path = new_path1 + '/' + label
        shutil.copy(old_path, new_path)

    for image in val_images:
        old_path = file_path + '/' + image
        new_path1 = new_file_path + '/' + 'val' + '/' + 'images'
        if not os.path.exists(new_path1):
            os.makedirs(new_path1)
        new_path = new_path1 + '/' + image
        shutil.copy(old_path, new_path)

    for label in val_labels:
        old_path = xml_path + '/' + label
        new_path1 = new_file_path + '/' + 'val' + '/' + 'labels'
        if not os.path.exists(new_path1):
            os.makedirs(new_path1)
        new_path = new_path1 + '/' + label
        shutil.copy(old_path, new_path)

    for image in test_images:
        old_path = file_path + '/' + image
        new_path1 = new_file_path + '/' + 'test' + '/' + 'images'
        if not os.path.exists(new_path1):
            os.makedirs(new_path1)
        new_path = new_path1 + '/' + image
        shutil.copy(old_path, new_path)

    for label in test_labels:
        old_path = xml_path + '/' + label
        new_path1 = new_file_path + '/' + 'test' + '/' + 'labels'
        if not os.path.exists(new_path1):
            os.makedirs(new_path1)
        new_path = new_path1 + '/' + label
        shutil.copy(old_path, new_path)


if __name__ == '__main__':
    file_path = "D:/Files/dataSet/drone_images"
    xml_path = 'D:/Files/dataSet/drone_labels'
    new_file_path = "D:/Files/dataSet/droneData"
    split_data(file_path,xml_path, new_file_path, train_rate=0.7, val_rate=0.1, test_rate=0.2)

Ⅱ Yolov5模型训练

0x00 修改配置文件

 在data中找到coco128.yaml并打开

其中,nc是标签名个数,names就是标签的名字。

train是在path绝对路径条件下的训练集路径,val同上,但是是验证集,这里为了方便,合并训练集和验证集。

0x01 选择预训练模型

yolov5共有4中配置,本次演示选择yolov5s,这个版本对显存的要求较低,但效果一般。

在yolov5下找到train.py 

 

只需要修改以下参数即可:

 --weights:训练的初始权重的位置,以.pt结尾的文件,可在官网上下载权重。

--cfg:训练模型文件,在本项目中对应yolov5s.yaml。

--data:数据集参数文件,在本项目中对应coco128.yaml

--epochs:训练的轮数,这里设置为1000,可根据需要修改

--batch-size:每次迭代(或称为训练步骤)中模型处理的样本数量,决定了训练的速度,要根据自己电脑的显存选择合理的batch。

0x02 训练结果

可在如下路径下找到最后的结果(在train中的最后一个exp文件夹中,训练次数越多,exp的数量越多):

 可以看到训练结果:

可在如下路径下找到训练后得到的权重:

 

best.pt和last.pt是我们训练出来的权重文件,

其中last是最后一次的训练结果,best是效果最好的训练结果。

🚩注:

可能随着不同的训练设定和实验有所变化,因此在不同的实验中,得到的最佳模型参数可能不同。

只是在训练过程中在验证集上表现最好的一个模型快照,但并不能保证它在所有情况下都是最佳的。

0x03 训练结果浅析

1. F1_curve.png —— F1曲线

  • F1分数与置信度阈值(x轴)之间的关系。F1分数是分类的一个衡量标准,是精确率和召回率的调和平均数,介于0,1之间。越大越好。
  • 若F1曲线很“宽敞”且顶部接近1,说明在训练数据集上表现得很好的置信度阈值区间很大。

R_curve.png —— 单一类召回率(置信度阈值 - 召回率曲线图)

  • 当置信度越小的时候,类别检测的越全面(不容易被漏掉,但容易误判)。

P_curve.png —— 单一类准确率(置信度阈值 - 准确率曲线图)

  •  当判定概率超过置信度阈值时,各个类别识别的准确率。当置信度越大时,类别检测越准确。

PR_curve.png —— 精确率和召回率的关系图

  • 在准确率很高的前提下,尽可能的检测到全部的类别。因此希望我曲线接近(1,1),即希望mAP曲线的面积尽可能接近1。

0x04 替换权重文件

将训练后得到的best.pt替换到track.py中:

至此,yolov5部分已经全部结束。

 END


📝因为作者的能力有限,所以文章可能会存在一些错误和不准确之处,恳请大家指出!

 📃参考文献:

[1] Simple Online and Realtime Tracking with a Deep Association Metric

[1703.07402] Simple Online and Realtime Tracking with a Deep Association Metric (arxiv.org)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/859071.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言学习——————文件处理操作的简单介绍与讲解】

欢迎阅读新一期的c语言学习模块————文件处理操作 ✒️个人主页&#xff1a;-_Joker_- &#x1f3f7;️专栏&#xff1a;C语言 &#x1f4dc;代码仓库&#xff1a;c_code &#x1f339;&#x1f339;欢迎大佬们的阅读和三连关注&#xff0c;顺着评论回访&#x1f339;&#…

【工作记录】mysql中实现分组统计的三种方式

前言 实际工作中对范围分组统计的需求还是相对普遍的&#xff0c;本文记录下在mysql中通过函数和sql完成分组统计的实现过程。 数据及期望 比如我们获取到了豆瓣电影top250&#xff0c;现在想知道各个分数段的电影总数. 表数据如下: 期望结果: 实现方案 主要思路是根据s…

国产芯力特Mini LIN SBC SIT1028Q应用方案,可替代TJA1028

SIT1028Q是一款内部集成高压LDO稳压源的本地互联网络&#xff08;LIN&#xff09;物理层收发器&#xff0c;可为外部ECU&#xff08;Electronic Control Unit&#xff09;微控制器或相关外设提供稳定的5V/3.3V电源&#xff0c;该LIN收发器符合LIN2.0、LIN2.1、LIN2.2、LIN2.2A、…

学习嵌入式系统的推荐步骤:

C语言&#xff1a;作为基础中的基础&#xff0c;选择一本常用的C语言教材&#xff0c;并注意通过实践编写习题、编译运行代码来加深理解。动手实践是非常重要的。 微机原理与接口技术&#xff1a;这本教材将帮助你了解CPU的基本结构、工作原理以及与外设的交互。虽然开始可能有…

手把手教你如何从零开始搭建自己的鞋店商城

对于不懂技术的新手来说&#xff0c;建立一个鞋店商城可能会显得有些困难。然而&#xff0c;现在有一些方便易用的网站建设平台可以帮助您快速搭建一个鞋店商城。本文将介绍乔拓云网的建站教程&#xff0c;让您轻松完成建站。 步骤1&#xff1a;注册乔拓云网账号并登录 首先&a…

苹果Mac像Windows一样使用

一、将磁盘访问设置的像Windows一样&#xff1a; 1.1、点击任务栏第一个按钮打开“访达”&#xff0c;点击菜单栏上的访达-偏好设置&#xff1a; 1.2、勾选“硬盘”&#xff0c;这样macOS的桌面上就会显示一个本地磁盘&#xff0c;之后重命名为磁盘根&#xff0c;相当于window…

Token 失效退出至登录页面

1. 在登录页面&#xff0c;调用登录的接口后&#xff0c;直接写上当前时间&#xff0c;保存在本地 代码&#xff1a; // 点击登录login(form) {this.$refs[form].validate((valid) > {if (valid) {this.$API.Login(this.form).then((res) > {// console.log(res, "1…

专注于创意设计,为您的小程序和网站建设带来更多的可能性

随着移动互联网的快速发展&#xff0c;越来越多的企业开始关注小程序和网站建设&#xff0c;以此来拓展业务和提升品牌形象。 在这个领域中&#xff0c;创意设计扮演着关键的角色。它不仅可以帮助企业打造独特的形象和品牌&#xff0c;还能够提高用户体验和购买决策的效率。 因…

C语言每日一题:15:寻找峰值。

题目链接 思路一&#xff1a; 思路二&#xff1a; int findPeakElement(int* nums, int numsLen ) {// write code hereint left0;int rightnumsLen-1;int* curnums;int mid0;//特殊情况判断两个值&#xff0c;单增和单减if(cur[0]>cur[1]){return 0;}if(cur[numsLen-1]>…

PROFINET转DeviceNet网关普通网线能代替profinet吗

捷米JM-DNT-PN这款神器&#xff0c;连接PROFINET和DeviceNet网络&#xff0c;让两边数据轻松传输。 这个网关不仅从ETHERNET/IP和DEVICENET一侧读写数据&#xff0c;还可以将缓冲区数据交换&#xff0c;这样就可以在两个网络之间愉快地传递数据了&#xff01;而且&#xff0c;…

找不到msvcr120.dll,无法继续执行代码,怎么修复?

当msvcp120.dll文件丢失或找不到时&#xff0c;会导致无法运行使用C编写的程序。这可能是由于以下原因导致的&#xff1a; 1.删除或移动文件&#xff1a;如果你不小心删除了或移动了msvcp120.dll文件&#xff0c;你将无法找到它并加载它&#xff0c;从而导致程序无法正常运行。…

护肤品种草软文怎么写?教你几招写作技巧

护肤品种草软文以独特的方式将产品的优势和特点传递给消费者&#xff0c;从而引导消费者购买。然而&#xff0c;随着护肤品市场的竞争日益激烈&#xff0c;如何写出一篇高质量的护肤品种草软文已经成为了很多品牌方和企业方的难题。本文伯乐网络传媒将从多个角度教你如何写出一…

学习C语言的好处:

基础编程语言&#xff1a;C语言是其他编程语言的基础&#xff0c;学习C语言可为后续学习打下坚实基础&#xff0c;广泛应用于嵌入式系统、操作系统、网络协议等。 简单易学&#xff1a;C语言语法简单易懂&#xff0c;适合初学者。只需文本编辑器和编译器&#xff0c;即可开始编…

μCOS-Ⅲ+GD32_SysTick与PendSV中断管理配置浅解

μCOS-ⅢGD32_SysTick与PendSV中断管理配置浅解 GD32移植μCOS-Ⅲ时&#xff0c;需要特别关注的两个与系统相关的且非常重要的中断&#xff0c;一个是提供OS系统时基的滴答定时器(SysTick_Handler中断)&#xff0c;另一个是跟任务调度有关的(PendSV_Handler中断)&#xff0c;成…

常见的数据结构:树Tree

目录 1.概念 1.1 满二叉树 1.2 完全二叉树 1.3 平衡二叉树 2.遍历方式 2.1 先序遍历 2.2 中序遍历 2.3 后序遍历 2.4 层序遍历 1.概念 原理&#xff1a;一种特殊的数据结构&#xff0c;每个节点有零个或多个子节点&#xff1b;没有父节点的节点称为根节点&#xff1b;每…

【Flutter】【基础】CustomPaint 绘画功能,绘制各种图形(二)

CustomPaint 使用实例和代码&#xff1a; 1.canvas.drawColor 绘制背景颜色 class MyPainter1 extends CustomPainter {overridevoid paint(Canvas canvas, Size size) {//绘制背景颜色&#xff0c;整个UI 现在就是红色的canvas.drawColor(Colors.red, BlendMode.srcATop);}…

STM32--EXTI外部中断

前文回顾---STM32--GPIO 相关回顾--有关中断系统简介 目录 STM32中断 NVIC EXTI外部中断 AFIO EXTI框图 旋转编码器简介 对射式红外传感器工程 代码&#xff1a; 旋转编码器工程 代码&#xff1a; STM32中断 先说一下基本原理&#xff1a; 1.中断请求发生&#xff1a…

创建型设计模式:4、建造者模式(Builder Pattern)

目录 1、建造者模式含义 2、建造者模式的讲解 3、使用C实现建造者模式的实例 4、建造者模式的优缺点 5、建造者模式VS工厂模式 1、建造者模式含义 The intent of the Builder design pattern is to separate the construction of a complex object from its representatio…

Java utgard连接OPC问题记录

1- 0x00000005 user access deny,用户校验不通过: 这个问题检查了一天,首先确认用户名密码没错,检查了一遍DCOM配置,也没有问题, 接下来陷入了困境, 后来查了些资料,顺着用户校验不通过这条线索, 查看Windows系统日志,如下: 安全日志中记录用户登录行为, OPC通信需要使用Windo…

这种鼠标悬浮,图片放大,鼠标移出,图片变回原来的大小,是如何实现的?

在Vue中实现鼠标悬浮时图片放大效果&#xff0c;以及鼠标移出时图片恢复原来大小&#xff0c;可以使用Vue的事件绑定和样式绑定功能来完成。以下是一个基本的示例&#xff1a; 首先&#xff0c;在Vue组件中&#xff0c;定义一个数据属性来控制图片的放大和恢复&#xff1a; &…