YoLov9从环境搭建到训练自己的数据,以车牌检测与多标签视网膜疾病的分类为例(非常详细,包括项目源码和模型权重下载、配置运行环境、代码改写、数据集制作、训练、预测和优化)等

一、项目文件与模型权重下载

1、下载地址

https://gitcode.com/WongKinYiu/yolov9/overview

2、下载项目文件与模型权重

(1)项目文件下载:点击左侧代码,再点击右侧下载zip即可
在这里插入图片描述
(2)模型权重下载:同样在该网址里面,先点击首页,往下拉找到Performance,点击模型权重名称进行下载
在这里插入图片描述
在这里插入图片描述
如果Performance这里点击下载没有反应,也可以找到Evaluation这里进行下载,如下所示:
在这里插入图片描述
(3)下载结果如下所示:
在这里插入图片描述
在这里插入图片描述

二、创建虚拟环境,安装所需要的包/库

1.打开cmd或者Anaconda Prompt(install)创建虚拟环境

方法一:默认情况下虚拟环境创建在Anaconda安装目录下的envs文件夹中

conda create --name Yolov9   #Yolov9是虚拟环境名称(自定义)

方法二:如果想将虚拟环境创建在指定位置,使用–prefix参数即可:

conda create --prefix /home/Zeroad/Envs/Yolov9 python==3.9   #/home/Zeroad/Envs地址(自定义)

在这里插入图片描述
在这里插入图片描述

查看并激活上面创建的虚拟环境:
#查看虚拟环境
conda env list

在这里插入图片描述

#激活虚拟环境
source activate /home/Zeroad/Envs/Yolov9

在这里插入图片描述

2、查看当前电脑的cuda版本,再根据版本去安装合适的torch

nvidia-smi

在这里插入图片描述
打开网址:https://pytorch.org/get-started/previous-versions/,查找本机对应的torch,并通过命令在线安装,如下图所示;

pip install torch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 --index-url https://download.pytorch.org/whl/cu118

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、进入项目文件,找到requirements.txt文件,安装所需要包/库

(1)进入yolov9项目文件,找到requirements.txt文件并查看所需要安装的包/库
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(2)使用清华镜像源等地址进行快速安装:

清华大学 :https://pypi.tuna.tsinghua.edu.cn/simple/
阿里云:http://mirrors.aliyun.com/pypi/simple/
中国科学技术大学 :http://pypi.mirrors.ustc.edu.cn/simple/
华中科技大学:http://pypi.hustunique.com/
豆瓣源:http://pypi.douban.com/simple/
腾讯源:http://mirrors.cloud.tencent.com/pypi/simple
华为镜像源:https://repo.huaweicloud.com/repository/pypi/simple/
比如:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ gitpython

(3)激活虚拟环境并查看下面已安装的库:
在这里插入图片描述
在这里插入图片描述
(4)如果想要删除yolov5这个虚拟环境:

conda remove --prefix /home/Zeroad/Envs/Yolov9 --all

到这里项目运行环境的设置就完成了!

三、车牌检测数据集的制作(目标检测)

1、数据集下载
url:https://www.cvmart.net/
大家打开网址,进行注册登录,再点击里面的数据集就可以去筛选自己需要的数据进行下载。

在这里插入图片描述

2、简介

下载文件解压后,里面有两个文件夹,分别annotations与images;
在这里插入图片描述

其中:annotations文件夹存放是标注数据的xml文件433

在这里插入图片描述

其中:images文件夹存放的是原始图片433

在这里插入图片描述

3、由于yolo系列模型不能使用xml形式的数据集进行训练,因此将xml数据转为txt形式

(1)首先对xml文件中的内容进行解读:关键参数在下面标注了

<annotation>
    <folder>images</folder>            
    <filename>Cars399.png</filename> #图片名称
    <size>                           #图片的大小
        <width>400</width>
        <height>225</height>
        <depth>3</depth>
    </size>
    <segmented>0</segmented>
    <object>
        <name>licence</name>         #标注目标的分类(标签)
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <occluded>0</occluded>
        <difficult>0</difficult>
        <bndbox>                     #标注目标的目标框
            <xmin>106</xmin>
            <ymin>62</ymin>
            <xmax>309</xmax>
            <ymax>161</ymax>
        </bndbox>
    </object>
</annotation>

(2)把 xml 中的目标框在原图上绘制出来 ,并显示标签【注:有需要就做这一步,没需要就可以不管,这步对训练没有什么影响】

import xml.etree.ElementTree as ET # 读取xml。
import os
from PIL import Image,ImageDraw,ImageFont

def parse_rec(filename): 
	tree = ET.parse(filename) # 解析读取xml函数
	objects = []
	img_dir =[]
	for xml_name in tree.findall('filename'):
		img_path = os.path.join(pic_path, xml_name.text)
		img_dir.append(img_path)
	for obj in tree.findall('object'):
		obj_struct = {}
		obj_struct['name'] = obj.find('name').text
		obj_struct['pose'] = obj.find('pose').text
		obj_struct['truncated'] = int(obj.find('truncated').text)
		obj_struct['difficult'] = int(obj.find('difficult').text)
		bbox = obj.find('bndbox')
		obj_struct['bbox'] = [int(bbox.find('xmin').text),
							  int(bbox.find('ymin').text),
							  int(bbox.find('xmax').text),
							  int(bbox.find('ymax').text)]
		objects.append(obj_struct)
	
	return objects,img_dir
# 可视化
def visualise_gt(objects,img_dir): 
	for id,img_path in enumerate(img_dir):
		img = Image.open(img_path)
		draw = ImageDraw.Draw(img)
		for a in objects:
			xmin =int(a['bbox'][0])
			ymin =int(a['bbox'][1])
			xmax =int(a['bbox'][2])
			ymax =int(a['bbox'][3])
			label = a['name']
			draw.rectangle((xmin,ymin,xmax,ymax), fill=None, outline=(0,255,0),width=2)
			draw.text((xmin-10,ymin-15), label, fill = (0,255,0),font=font)  # 利用ImageDraw的内置函数,在图片上写入文字
		img.show()
fontPath = "C:\Windows\Fonts\Consolas\consola.ttf" # 字体路径
root = 'F:/dataset/AQM'
ann_path = os.path.join(root, 'Annotations')  # xml文件所在路径
pic_path = os.path.join(root, 'JPEGImages')  # 样本图片路径
font = ImageFont.truetype(fontPath, 16)

for filename in os.listdir(ann_path):
	xml_path = os.path.join(ann_path,filename)
	object,img_dir = parse_rec(xml_path)
	visualise_gt(object,img_dir )

(3)将xml批量转化为txt文件

import os
import random
import xml.etree.ElementTree as ET
import numpy as np
 
def convert_annotation(xml_name, class_list, labels_folder):
    xml_path = os.path.join(XML_FOLDER, xml_name)
    print("xml_path:", xml_path)
    in_file = open(xml_path, encoding='utf-8')
    tree = ET.parse(in_file)
    root = tree.getroot()
 
    for SIZE in root.iter("size"):
        width = float(SIZE.find("width").text)
        height = float(SIZE.find("height").text)
 
    label_name = xml_name.split(".")[0]+".txt"
 
    label_path = os.path.join(labels_folder, label_name)
    if os.path.exists(label_path):
        os.remove(label_path)
    with open(label_path, "a", encoding="utf-8") as f:
        for obj in root.iter('object'):
            difficult = 0
            if obj.find('difficult') != None:
                difficult = obj.find('difficult').text
            cls = obj.find('name').text
            if cls not in class_list or int(difficult) == 1:
                continue
            cls_id = classes_list.index(cls)
            xmlbox = obj.find('bndbox')
            # x1, y1, x2, y2 = (float(xmlbox.find('xmin').text)/width, float(xmlbox.find('ymin').text)/height,
            #      float(xmlbox.find('xmax').text)/width, float(xmlbox.find('ymax').text)/height)
 
            x1, y1, x2, y2 = (float(xmlbox.find('xmin').text), float(xmlbox.find('ymin').text),
                              float(xmlbox.find('xmax').text), float(xmlbox.find('ymax').text))
            print("cls:", cls_id)
            print("box:", (x1+x2)/2/width, (y1+y2)/2/height, (x2-x1)/width, (y2-y1)/height)
            x_c, y_c, w, h = (x1+x2)/2/width, (y1+y2)/2/height, (x2-x1)/width, (y2-y1)/height
 
            # f.write("%s %s %s %s %s\n" % (cls_id, x1, y1, x2, y2))
            f.write("%s %s %s %s %s\n"%(cls_id, x_c, y_c, w, h))
 
if __name__ == "__main__":
    classes_list = ["licence"]  # 对应类别名称
    XML_FOLDER = r"/home/xh/YoLo/del_data/CarLicensePlateDetection/annotations" # xml路径
    Labels_FOLDER = r"/home/xh/YoLo/del_data/CarLicensePlateDetection/labels"   # 保存路径
    random.seed(0)
    if " " in os.path.abspath(XML_FOLDER):
        raise ValueError("数据集存放的文件夹路径与图片名称中不可以存在空格,否则会影响正常的模型训练,请注意修改。")
 
    # 1.读取xml文件
    for xml_name in os.listdir(XML_FOLDER):
        if xml_name.endswith(".xml"):
            convert_annotation(xml_name, classes_list, Labels_FOLDER)

(4)转化结果查看
在这里插入图片描述
在这里插入图片描述
分别是【类别、x1、y1、x2、y2】

四、改写代码,进行车牌目标检测训练和验证实战

1、划分数据集:【划分比例为 train :val:test = 8 :1:1】

在项目中新建DataSplit.py文件并放入以下代码,根据自己的需求改写路径和比例(如下图所示)

# 将图片和标注数据按比例切分为 训练集和测试集
import shutil
import random
import os

# 原始路径
image_original_path = "/home/xh/YoLo/del_data/CarLicensePlateDetection/images/"
label_original_path = "/home/xh/YoLo/del_data/CarLicensePlateDetection/labels/"

cur_path = os.getcwd()

# 训练集路径
train_image_path = os.path.join(cur_path, "/home/xh/YoLo/del_data/CarLicensePlateDetection/datasets/images/train/")
train_label_path = os.path.join(cur_path, "/home/xh/YoLo/del_data/CarLicensePlateDetection/datasets/labels/train/")

# 验证集路径
val_image_path = os.path.join(cur_path, "/home/xh/YoLo/del_data/CarLicensePlateDetection/datasets/images/val/")
val_label_path = os.path.join(cur_path, "/home/xh/YoLo/del_data/CarLicensePlateDetection/datasets/labels/val/")

# 测试集路径
test_image_path = os.path.join(cur_path, "/home/xh/YoLo/del_data/CarLicensePlateDetection/datasets/images/test/")
test_label_path = os.path.join(cur_path, "/home/xh/YoLo/del_data/CarLicensePlateDetection/datasets/labels/test/")

# 训练集目录
list_train = os.path.join(cur_path, "/home/xh/YoLo/del_data/CarLicensePlateDetection/datasets/train.txt")
list_val = os.path.join(cur_path, "/home/xh/YoLo/del_data/CarLicensePlateDetection/datasets/val.txt")
list_test = os.path.join(cur_path, "/home/xh/YoLo/del_data/CarLicensePlateDetection/datasets/test.txt")

train_percent = 0.8  # 划分比例
val_percent = 0.1
test_percent = 0.1

def del_file(path):
    for i in os.listdir(path):
        file_data = path + "\\" + i
        os.remove(file_data)

def mkdir():
    if not os.path.exists(train_image_path):
        os.makedirs(train_image_path)
    else:
        del_file(train_image_path)
    if not os.path.exists(train_label_path):
        os.makedirs(train_label_path)
    else:
        del_file(train_label_path)

    if not os.path.exists(val_image_path):
        os.makedirs(val_image_path)
    else:
        del_file(val_image_path)
    if not os.path.exists(val_label_path):
        os.makedirs(val_label_path)
    else:
        del_file(val_label_path)

    if not os.path.exists(test_image_path):
        os.makedirs(test_image_path)
    else:
        del_file(test_image_path)
    if not os.path.exists(test_label_path):
        os.makedirs(test_label_path)
    else:
        del_file(test_label_path)

def clearfile():
    if os.path.exists(list_train):
        os.remove(list_train)
    if os.path.exists(list_val):
        os.remove(list_val)
    if os.path.exists(list_test):
        os.remove(list_test)

def main():
    mkdir()
    clearfile()

    file_train = open(list_train, 'w')
    file_val = open(list_val, 'w')
    file_test = open(list_test, 'w')

    total_txt = os.listdir(label_original_path)
    num_txt = len(total_txt)
    list_all_txt = range(num_txt)

    num_train = int(num_txt * train_percent)
    num_val = int(num_txt * val_percent)
    num_test = num_txt - num_train - num_val

    train = random.sample(list_all_txt, num_train)
    # train从list_all_txt取出num_train个元素
    # 所以list_all_txt列表只剩下了这些元素
    val_test = [i for i in list_all_txt if not i in train]
    # 再从val_test取出num_val个元素,val_test剩下的元素就是test
    val = random.sample(val_test, num_val)
    
    print("训练集数目:{}, 验证集数目:{}, 测试集数目:{}".format(len(train), len(val), len(val_test) - len(val)))
    for i in list_all_txt:
        name = total_txt[i][:-4]
        srcImage = image_original_path + name + '.png'
        srcLabel = label_original_path + name + ".txt"
        if i in train:
            dst_train_Image = train_image_path + name + '.png'
            dst_train_Label = train_label_path + name + '.txt'
            shutil.copyfile(srcImage, dst_train_Image)
            shutil.copyfile(srcLabel, dst_train_Label)
            file_train.write(dst_train_Image + '\n')
        elif i in val:
            dst_val_Image = val_image_path + name + '.png'
            dst_val_Label = val_label_path + name + '.txt'
            shutil.copyfile(srcImage, dst_val_Image)
            shutil.copyfile(srcLabel, dst_val_Label)
            file_val.write(dst_val_Image + '\n')
        else:
            dst_test_Image = test_image_path + name + '.png'
            dst_test_Label = test_label_path + name + '.txt'
            shutil.copyfile(srcImage, dst_test_Image)
            shutil.copyfile(srcLabel, dst_test_Label)
            file_test.write(dst_test_Image + '\n')
    file_train.close()
    file_val.close()
    file_test.close()

if __name__ == "__main__":
    main()

结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、制作数据集的yaml文件:

(1)找到项目data文件夹中的coco.yaml文件,复制并重命名为cardata仍放在data文件夹中:
在这里插入图片描述
(2)对cardata.yaml文件进行修改:这里根据自己实际情况进行修改
在这里插入图片描述

3、找到yolov9-main\models\detect下的yolov9-c.yaml,进行修改

在这里插入图片描述
nc表示标签的数量,按照自己实际标签数进行修改!

4、修改项目文件中tran_dual.py文件
注意:项目文件夹中有train.py、tran_dual.py、train_triple.py 三个训练文件,都能用,但是你得对应起来!
train.py:常规的训练,没有辅助分支的
train_dual:一个辅助分支 + 一个主分支
triple_branch:两个辅助分支 + 一个主分支
所以当我们训练 YOLOv9-c 或者 YOLOv9-e 就用 train_dual.py:

必须修改:以下三个参数【–weights、–cfg、–data:分别是模型权重路径、yolov9-c.yaml文件路径、cardata.yaml文件路径】
在这里插入图片描述
其它参数解释如下:其它参数根据自己的设备和需求进行调整

def parse_opt(known=False):
    parser = argparse.ArgumentParser()
    # parser.add_argument('--weights', type=str, default=ROOT / 'yolo.pt', help='initial weights path')
    # parser.add_argument('--cfg', type=str, default='', help='model.yaml path')
    parser.add_argument('--weights', type=str, default='', help='权重初始化的路径,在这里面设置权重的地址')
    parser.add_argument('--cfg', type=str, default='models/detect/gelan.yaml', help='模型的配置文件,我们可以根据自己需求配置yaml文件')
    parser.add_argument('--data', type=str, default=ROOT / r'', help='大家的数据集yaml文件的地址')
    parser.add_argument('--hyp', type=str, default=ROOT / 'data/hyps/hyp.scratch-high.yaml', help='用于指定超参数文件,大家根据这个地址就能够找到一个yaml文件里面配置了各种超参数')
    parser.add_argument('--epochs', type=int, default=150, help='训练的轮次')
    parser.add_argument('--batch-size', type=int, default=4, help='训练的批次大小,决定了一次向模型里输入多少张图片')
    parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='输入模型里的图像尺寸,YOLO系列默认都是640')
    parser.add_argument('--rect', action='store_true', help='矩形训练或验证。矩形训练或验证是一种数据处理技术,其中在训练或验证过程中,输入数据会被调整为具有相同宽高比的矩形形状')
    parser.add_argument('--resume', nargs='?', const=True, default=False, help='这个如果设置为True,那么会根据你最新一次没有训练完成的权重进行继续完成训练')
    parser.add_argument('--nosave', action='store_true', help='只保存之后一次训练的权重文件地址,即我们得到的那个权重,默认是这样!')
    parser.add_argument('--noval', action='store_true', help='当设置时,只在最后一个周期(epoch)进行验证,而不是在每个周期都进行')
    parser.add_argument('--noautoanchor', action='store_true', help='当设置时,禁用自动锚定(AutoAnchor)')
    parser.add_argument('--noplots', action='store_true', help='当设置时,不保存任何绘图文件,就是我们runs下面的训练结果')
    parser.add_argument('--evolve', type=int, nargs='?', const=300, help='允许超参数的进化优化,参数是进化的代数。')
    parser.add_argument('--bucket', type=str, default='', help='指定一个gsutil存储桶的路径')
    parser.add_argument('--cache', type=str, nargs='?', const='ram', help='设置图像缓存方式,可以是内存(ram)或磁盘(disk),默认是内存')
    parser.add_argument('--image-weights', action='store_true', help='使用加权图像选择进行训练,简单的说就是,处理一些复杂或不平衡的数据集')
    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu, 这个不在多说了,我们的GPU设备号')
    parser.add_argument('--multi-scale', action='store_true', help='当设置时,图像大小会在训练过程中变化,上下浮动50%')
    parser.add_argument('--single-cls', action='store_true', help='当设置时,将多类别数据作为单类别数据训练,一般都设置为False')
    parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW', 'LION'], default='SGD', help='优化器的选择,默认为SGD')
    parser.add_argument('--sync-bn', action='store_true', help='当设置时,使用同步批处理归一化(SyncBatchNorm)')
    parser.add_argument('--workers', type=int, default=0, help='工作的线程,Linux系统可以设置,Windows系统必须设置0否则会导致各种问题')
    parser.add_argument('--project', default=ROOT / 'runs/train', help='设置项目保存路径,就是我们训练结果保存的地方')
    parser.add_argument('--name', default='exp', help='保存的项目名称,类似于runs/train/exp/一堆我们训练的结果')
    parser.add_argument('--exist-ok', action='store_true', help='当设置时,如果项目/名称已存在则不会报错,不会自动增加编号')
    parser.add_argument('--quad', action='store_true', help='使用四边形数据加载器, 这些默认都是开启的')
    parser.add_argument('--cos-lr', action='store_true', help='使用余弦学习率调度器, 这些默认都是开启的')
    parser.add_argument('--flat-cos-lr', action='store_true', help='启用一个平坦余弦学习率调度器(Flat Cosine Learning Rate Scheduler')
    parser.add_argument('--fixed-lr', action='store_true', help='启用一个固定学习率调度器(Fixed Learning Rate Scheduler)')
    parser.add_argument('--label-smoothing', type=float, default=0.0, help='参数是用来设置标签平滑的 epsilon 值的,用来提高模型的泛化能力')
    parser.add_argument('--patience', type=int, default=100, help='早停机制,损失超过多少个回合变化的较小就停止训练')
    parser.add_argument('--freeze', nargs='+', type=int, default=[0], help='用于在神经网络训练过程中“冻结”(freeze)一部分层。冻结的意思是在训练过程中保持这些层的权重不变,不对它们进行更新.')
    parser.add_argument('--save-period', type=int, default=-1, help='每隔x个周期保存一次检查点,如果小于1则禁用')
    parser.add_argument('--seed', type=int, default=0, help='训练的随机数种子,计算机其实是没有随机数的,都是根据随机数种子来演变出来的随机数')
    parser.add_argument('--local_rank', type=int, default=-1, help='自动设置用于多GPU DDP训练的局部排名,通常不需要手动修改')
    parser.add_argument('--min-items', type=int, default=0, help='目前还不知道这个参数的含义')
    parser.add_argument('--close-mosaic', type=int, default=0, help='关闭mosaic数据增强的参数,比如设置0则不关闭mosaic数据增强如果设置10则在训练后10轮关闭mosaic增强')
    
    # Logger arguments
    parser.add_argument('--entity', default=None, help='设置实体,用于日志记录')
    parser.add_argument('--upload_dataset', nargs='?', const=True, default=False, help='设置是否上传数据集,如果指定“val”,则上传验证集')
    parser.add_argument('--bbox_interval', type=int, default=-1, help='设置边界框图像记录间隔')
    parser.add_argument('--artifact_alias', type=str, default='latest', help='设置要使用的数据集工件的版本,默认为latest')
    
    return parser.parse_known_args()[0] if known else parser.parse_args()
5、运行tran_dual.py文件,训练模型

(1)运行tran_dual.py文件报的错误1:
我这里就报错了:缺少下面这个库,安装即可。如果遇见类似错误,根据提醒信息进行修改就可以!
在这里插入图片描述
(2)运行tran_dual.py文件报的错误2:因为安装了新版本的 Pillow (10)删除了该getsize 功能,降级到 Pillow 9.5 解决该问题
在这里插入图片描述
(3)再次运行tran_dual.py文件就正常跑模型了:部分运行结果如下
在这里插入图片描述
在这里插入图片描述

在目标检测任务中,常用的评估指标包括Precision(P)、Recall(R)、mAP50和mAP50-95。
Precision(精确率)表示检测出的正样本中有多少是真正的正样本。它的计算公式是:P = TP / (TP + FP),其中TP是真正例(正确检测的正样本数),FP是假正例(错误检测的正样本数)。Precision越高,表示检测结果中的正样本越准确。
Recall(召回率)表示真实的正样本中有多少被正确地检测出来。它的计算公式是:R = TP / (TP + FN),其中TP是真正例,FN是假负例(未能检测到的正样本数)。Recall越高,表示模型能够更好地检测出真实的正样本。
mAP50(平均精确率,IoU阈值为0.5)表示在不同类别上的平均精确率。它的计算方法是先计算每个类别的精确率,然后对所有类别的精确率取平均值。mAP50衡量了模型在不同类别上的整体性能,当IoU阈值为0.5时,表示检测框与真实框的重叠程度达到50%以上时才被视为正确检测。
mAP50-95(平均精确率,IoU阈值从0.50.95)是在不同IoU阈值范围内的平均精确率。它计算了从0.50.95不同IoU阈值下的精确率,并对其取平均值。mAP50-95更全面地评估了模型在不同IoU阈值下的检测性能。
这些评估指标可以帮助我们了解目标检测模型的准确性和召回率,并对模型的性能进行综合评估。
保存的结果存放在 【yolov9-main/runs/train/exp4】文件夹里面,里面有以下主要文件:
里面包含生成的权重文件:weights,精确率与召回率的图像等,如下图所示:

在这里插入图片描述

weights:存放模型权重;
在训练深度学习模型时,通常会保存模型的权重文件,以便在需要时进行加载和使用。YOLOv5模型中,best.pt和last.pt是两个常见的权重文件。
best.pt:best.pt是在训练过程中根据验证集表现选择的最佳模型权重文件。它是根据模型在验证集上的性能指标(如精度、损失等)来决定的。通常情况下,best.pt对应于在验证集上获得最佳性能的模型。
last.pt:last.pt是训练过程中最后一次保存的模型权重文件。它保存了模型在训练过程中的最新状态,无论其在验证集上的性能如何。last.pt可以用作继续训练的起点或进行推理和预测。
区别:
best.pt是基于验证集性能选择的最佳模型权重文件,而last.pt是训练过程中最后一次保存的模型权重文件。
best.pt对应于在验证集上获得最佳性能的模型,而last.pt保存了模型在训练过程中的最新状态。
在模型训练完毕后,可以使用best.pt进行性能评估和推理,而last.pt可以用作继续训练的起点。
选择使用哪个权重文件取决于具体的需求。如果需要获得在验证集上最佳性能的模型,可以使用best.pt。如果需要继续训练模型或进行推理和预测,可以使用last.pt。

(1)"labels.jpg"通常是指包含训练数据集中所有类别标签的图像文件。

    第一个图是训练集得数据量,每个类别有多少个;
    第二个图是框的尺寸和数量;
    第三个图是中心点相对于整幅图的位置;
    第四个图是图中目标相对于整幅图的高宽比例;

在这里插入图片描述
(2)labels_correlogram.jpg是一张颜色矩阵图,它展示了目标检测算法在训练过程中预测标签之间的相关性。

矩阵的行列分别代表了模型训练时使用的标签(classes),而每个单元格则代表了对应标签的预测结果之间的相关性。
矩阵中的颜色越深,表示对应标签之间的相关性越强;颜色越浅,表示相关性越弱。对角线上的颜色表示每个标签的自身相关性,通常都是最深的。
通过这张图,我们可以看出哪些标签之间具有较强的相关性,从而有助于优化模型的训练和预测效果。例如,如果我们发现某些标签之间的相关性过强,可以考虑将它们合并成一个标签,从而简化模型并提高效率。
最上面的图(00)表明中心点横坐标x的分布情况;
(11)图表明中心点纵坐标y的分布情况;
(22)图表明框的宽的分布情况;
(33)图表明框的宽的分布情况

在这里插入图片描述

(3)P曲线:P_curve.png:准确率precision与置信度confidence的关系图。

【置信度confidence:用来判断边界框内的物体是正样本还是负样本,大于置信度阈值的判定为正样本,小于置信度阈值的判定为负样本即背景。】

在这里插入图片描述
(4)R_curve.png(信度阈值 - 召回率曲线图):Recall-Confidence Curve (RCC)图是目标检测中用于评估算法性能的一种方法。它是在不同置信度阈值下,召回率的变化情况的可视化表示。

通常情况下,我们希望算法能够在高召回率的同时保持较高的精度。
当RCC图中的曲线在较高的置信度水平下具有较高的召回率时,说明算法在检测目标时能够较为准确地预测目标的存在,并且在过滤掉低置信度的预测框后,依然可以保持较高的召回率。这说明算法在目标检测任务中具有较好的性能。
需要注意的是,在RCC图中,曲线的斜率越陡峭,说明在过滤掉低置信度的预测框后,能够获得较大的召回率提升,从而提高模型的检测性能。
在该图表中,曲线越靠近右上角,则表示模型的性能越好。当曲线接近图表的右上角时,意味着模型在保持高召回率的同时,也能够保持较高的精确度。因此,R_curve.png可以用于评估模型的整体表现和找到一个合适的阈值,来平衡模型的召回率和精确度。

在这里插入图片描述

(5)PR_curve.png —— 精确率和召回率的关系图:PR Curve是Precision-Recall Curve的缩写,表示的是在不同阈值下,精确率与召回率之间的关系曲线。其中精确率(Precision)表示预测为正例的样本中真正为正例的比例,召回率(Recall)表示真正为正例的样本中被预测为正例的比例。

在PR Curve中,横坐标为召回率,纵坐标为精确率。一般而言,当召回率较高时,精确率较低;当精确率较高时,召回率较低。而PR Curve则体现了这种“取舍”关系。当PR Curve越靠近右上角时,表示模型在预测时能够同时保证高的精确率和高的召回率,即预测结果较为准确。相反,当PR Curve越靠近左下角时,表示模型在预测时难以同时保证高的精确率和高的召回率,即预测结果较为不准确。
通常,PR Curve与ROC Curve(受试者工作特征曲线)一同使用,以更全面地评估分类模型的性能。

在这里插入图片描述

(6)result.png:

定位损失box_loss:预测框与标定框之间的误差(CIoU),越小定位得越准;
置信度损失obj_loss:计算网络的置信度,越小判定为目标的能力越准;
分类损失cls_loss:计算锚框与对应的标定分类是否正确,越小分类得越准;
mAP@0.5:0.95(mAP@[0.5:0.95]:
表示在不同IoU阈值(从0.50.95,步长0.05)(0.50.550.60.650.70.750.80.850.90.95)上的平均mAP;
mAP@0.5:表示阈值大于0.5的平均mAP
6、改写detect.py代码,进行预测,如下图所示:

在这里插入图片描述
选择一张图片预测:原图片
在这里插入图片描述
发生了报错:
在这里插入图片描述
将general.py出错位置进行改正:注释掉原来形式,将替换代码放上去即可
在这里插入图片描述

  if isinstance(prediction, (list, tuple)):
        processed_predictions = []  # 用于存储处理后的张量列表
        for pred_tensor in prediction:
            # 对每个张量进行处理
            processed_tensor = pred_tensor[0]  # 假设你只关心张量中的第一个结果
            processed_predictions.append(processed_tensor)  # 将处理后的张量添加到列表中

        # 使用处理后的张量列表中的第一个张量作为预测结果
        prediction = processed_predictions[0]

    # 在此之后可以继续使用 prediction 变量
    device = prediction.device

再次运行detect.py文件,结果如下:结果保存在yolov9-main/runs/detect/exp3里面
在这里插入图片描述
在这里插入图片描述
再在验证集数据中选择一张图片预测:原图片
在这里插入图片描述
预测结果如下:
在这里插入图片描述

五、多标签视网膜疾病的分类实战

1、划分数据集:【划分比例为 train :val:test = 8 :1:1】:

(1)下载的数据集分为四类,分别是【normal:cataract:glaucoma:retina_disease=正常:白内障:青光眼:视网膜疾病=300:100:101:100】
在这里插入图片描述
在这里插入图片描述
(2)数据集划分:

import os
import random
import shutil 

def split_dataset(imges_file_list,imges_file,split_save):
    for file in imges_file_list:
        all_data = os.listdir(imges_file + file)  # (图片文件夹)
        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 = split_save + 'train/' + file  # (将训练集放在这个文件夹下)
        if not os.path.exists(trainDir):
            os.mkdir(trainDir)

        validDir = split_save + 'val/' + file  # (将验证集放在这个文件夹下)
        if not os.path.exists(validDir):
            os.mkdir(validDir)

        testDir = split_save + 'test/' + file  # (将测试集放在这个文件夹下)
        if not os.path.exists(testDir):
            os.mkdir(testDir)

        for i in index_list:
            fileName = os.path.join(file, all_data[i])
            if num < num_all_data * 0.8:
                # print(str(fileName))
                print(imges_file+fileName)
                shutil.copy(imges_file+fileName, trainDir)
                print(imges_file+fileName)
            elif num > num_all_data * 0.8 and num < num_all_data * 0.9:
                # print(str(fileName))
                shutil.copy(imges_file+fileName, validDir)
            else:
                shutil.copy(imges_file+fileName, testDir)
            num += 1
            
    return 'Success'

if __name__ == "__main__":
    imges_file = '/home/xh/YoLo/del_data/cataractdataset/'
    split_save = "/home/xh/YoLo/del_data/cataractdatasetSplitdata/"
    imges_file_list = [i for i in os.listdir(imges_file)]
    jg = split_dataset(imges_file_list,imges_file,split_save)
    print(jg)

(3)结果如下:
在这里插入图片描述
在这里插入图片描述

2、修改分类训练代码train.py:
在这里必须修改模型权重路径和数据集路径,其它参数根据自己的需求进行修改;

在这里插入图片描述

3、运行train.py文件,训练模型,结果如下:

在这里插入图片描述
在这里插入图片描述

上图中的【..\runs\train-cls\exp3】路径就是存放训练出的权重路径;
Epoch(迭代轮数):一个 epoch 表示将训练集中的所有样本都用于训练的一次迭代。在每个 epoch 中,模型会依次使用训练集中的每个样本进行训练。
GPU_mem(显存占用):指的是在训练过程中 GPU 使用的显存量。显存占用通常会随着模型的复杂度和批量大小的增加而增加。
train_loss(训练损失):在每个 epoch 结束时,使用训练集计算的损失值。训练损失用于衡量模型在训练集上的拟合程度。
test_loss(测试损失):在每个 epoch 结束时,使用测试集计算的损失值。测试损失用于衡量模型在测试集上的拟合程度。通常用于评估模型的泛化能力。
top1_acc(Top-1 准确率):在每个 epoch 结束时,使用测试集计算的准确率。Top-1 准确率表示模型在预测时,与真实标签完全匹配的样本所占的比例。
top5_acc(Top-5 准确率):在每个 epoch 结束时,使用测试集计算的准确率。Top-5 准确率表示模型在预测时,真实标签在模型预测的前五个最高概率中的样本所占的比例。
这些指标可以用来评估模型的性能和训练过程的进展。通常,我们希望训练损失和测试损失都能够逐渐减小,而准确率则逐渐提高。

在这里插入图片描述

4、修改predict.py文件,进行测试验证,结果如下:

在这里插入图片描述
测试数据为normal中NL_289.png图片:
在这里插入图片描述
测试结果如下:
在这里插入图片描述
再测试一张图片,选择cataract(白内障)中的一张图片:
在这里插入图片描述
测试结果如下:
在这里插入图片描述

六、简单总结

1、以上就是使用yolov9模型进行一个分类与目标检测的介绍和实践,大家可以参考与提出问题,看见了就会回答,一起讨论!项目代码和数据也上传了,大家可以下载!
2、再这个过程中大家肯定会遇见一些错误,但是不要害怕,可以去根据提示进行改正或者上网查找解决方法,还有就是询问chatgpt大模型!
3、不管是分类训练,还是目标检测,已经达到一个不错的效果了!,但是大家如果还需要提高精确度,比如增加数据,我这里的数据量不是太多,数据增强,调整学习率,增加训练轮数等,可以参考以下方法:
要提高YOLOv9模型的精确度和准确度,可以尝试以下方法:

增加训练数据量:增加训练数据可以提供更多的样本和场景,有助于模型学习更多的特征和模式。

数据增强:使用数据增强技术,如随机缩放、翻转、旋转、裁剪等,可以扩充训练数据集,增加模型的鲁棒性和泛化能力。

调整模型架构:可以根据任务需求调整YOLOv5的网络架构,如增加卷积层、调整通道数、调整网络深度等,以提升模型的表达能力。

调整训练参数:可以尝试不同的学习率、批量大小、优化器、学习率衰减策略等训练参数,以找到最优的训练配置。

使用预训练模型:可以使用在大规模数据集上预训练的模型作为初始权重,然后在目标数据集上进行微调,以加快收敛速度和提高模型性能。

多尺度训练和推理:使用多尺度训练和推理可以提升模型对不同尺寸目标的检测能力,可以在训练时随机选择不同的输入尺寸,并在推理时使用多个尺度的图像进行预测。

使用更大的输入图像尺寸:增加输入图像的尺寸可以提高模型对小目标的检测能力,但同时也会增加计算和内存开销。

引入注意力机制:可以在YOLOv9中引入注意力机制,如SENet、CBAM等,以增强模型对重要特征的关注和响应能力。

集成学习:通过模型集成方法,如投票、平均等,结合多个训练好的YOLOv9模型的预测结果,可以提高模型的稳定性和性能。

以上是一些常用的方法,可以根据具体的任务和数据集进行尝试和调整,以获得更好的精确度和准确度。

  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值