CV党福音:YOLOv8实现分类

news/2025/1/15 12:15:47/

YOLO作为目标检测领域的常青树,如今以及更新到了YOLOv10,并且还有YOLOXYOLOS等变体,可以说该系列已经在目标检测领域占据了半壁江山,如今,YOLOv8的发行者ultralytics竟有一统江山之意,其在提出的框架中不但集成了v3v10YOLO目标检测模型,还包揽了分类,语义分割、目标追踪和姿态估计等计算机视觉任务。

那么,今天我们就来看看YOLOv8是如何将这些计算机视觉任务融合在一起吧

其实从思路上很简单,YOLOv8继续沿用了YOLO的基本架构,即将整个模型分为特征提取骨干网络(Backbone),用于进行特征提取,特征融合网络(Neck),用于融合提取的特征信息以及最后的检测输出模块(Head),由于前面的BackboneNeck已经完成了特提取与特征融合的功能,而最后的输出头其实就是根据不同任务所设计的,因此,要想让YOLOv8具备分类、分割以及姿态估计的功能,只需要将我们的检测头(Detect)替换为相应的分类头、分割头以及姿态估计头即可,当然,相应的还要替换损失函数与数据集标签。

YOLOv8模型结构如下:

在这里插入图片描述

目标检测模型

那么,我们接下来便来看看YOLOv8是如何去替换的:

首先是模型结构,博主以分类为例,要修改模型结构,只需要修改对应的yaml文件即可,这里博主推荐可以参考这篇文章:YOLOv8模型yaml结构图理解(逐层分析)

YOLOv8的模型结构如下:

# Ultralytics YOLO 🚀, AGPL-3.0 license
# YOLOv8 object detection model with P3-P5 outputs. For Usage examples see https://docs.ultralytics.com/tasks/detect
# Parameters
nc: 80  # 类别数目,nc代表"number of classes",即模型用于检测的对象类别总数。
scales: # 模型复合缩放常数,例如 'model=yolov8n.yaml' 将调用带有 'n' 缩放的 yolov8.yaml# [depth, width, max_channels]n: [0.33, 0.25, 1024]  # YOLOv8n概览:225层, 3157200参数, 3157184梯度, 8.9 GFLOPss: [0.33, 0.50, 1024]  # YOLOv8s概览:225层, 11166560参数, 11166544梯度, 28.8 GFLOPsm: [0.67, 0.75, 768]   # YOLOv8m概览:295层, 25902640参数, 25902624梯度, 79.3 GFLOPsl: [1.00, 1.00, 512]   # YOLOv8l概览:365层, 43691520参数, 43691504梯度, 165.7 GFLOPsx: [1.00, 1.25, 512]   # YOLOv8x概览:365层, 68229648参数, 68229632梯度, 258.5 GFLOPs
# YOLOv8.0n backbone 骨干层
backbone:# [from, repeats, module, args]- [-1, 1, Conv, [64, 3, 2]]  # 0-P1/2 第0层,-1代表将上层的输入作为本层的输入。第0层的输入是640*640*3的图像。Conv代表卷积层,相应的参数:64代表输出通道数,3代表卷积核大小k,2代表stride步长。- [-1, 1, Conv, [128, 3, 2]]  # 1-P2/4 第1层,本层和上一层是一样的操作(128代表输出通道数,3代表卷积核大小k,2代表stride步长)- [-1, 3, C2f, [128, True]] # 第2层,本层是C2f模块,3代表本层重复3次。128代表输出通道数,True表示Bottleneck有shortcut。- [-1, 1, Conv, [256, 3, 2]]  # 3-P3/8 第3层,进行卷积操作(256代表输出通道数,3代表卷积核大小k,2代表stride步长),输出特征图尺寸为80*80*256(卷积的参数都没变,所以都是长宽变成原来的1/2,和之前一样),特征图的长宽已经变成输入图像的1/8。- [-1, 6, C2f, [256, True]] # 第4层,本层是C2f模块,可以参考第2层的讲解。6代表本层重复6次。256代表输出通道数,True表示Bottleneck有shortcut。经过这层之后,特征图尺寸依旧是80*80*256。- [-1, 1, Conv, [512, 3, 2]]  # 5-P4/16 第5层,进行卷积操作(512代表输出通道数,3代表卷积核大小k,2代表stride步长),输出特征图尺寸为40*40*512(卷积的参数都没变,所以都是长宽变成原来的1/2,和之前一样),特征图的长宽已经变成输入图像的1/16。- [-1, 6, C2f, [512, True]] # 第6层,本层是C2f模块,可以参考第2层的讲解。6代表本层重复6次。512代表输出通道数,True表示Bottleneck有shortcut。经过这层之后,特征图尺寸依旧是40*40*512。- [-1, 1, Conv, [1024, 3, 2]]  # 7-P5/32 第7层,进行卷积操作(1024代表输出通道数,3代表卷积核大小k,2代表stride步长),输出特征图尺寸为20*20*1024(卷积的参数都没变,所以都是长宽变成原来的1/2,和之前一样),特征图的长宽已经变成输入图像的1/32。- [-1, 3, C2f, [1024, True]] #第8层,本层是C2f模块,可以参考第2层的讲解。3代表本层重复3次。1024代表输出通道数,True表示Bottleneck有shortcut。经过这层之后,特征图尺寸依旧是20*20*1024。- [-1, 1, SPPF, [1024, 5]]  # 9 第9层,本层是快速空间金字塔池化层(SPPF)。1024代表输出通道数,5代表池化核大小k。结合模块结构图和代码可以看出,最后concat得到的特征图尺寸是20*20*(512*4),经过一次Conv得到20*20*1024。
# YOLOv8.0n head 头部层
head:- [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 第10层,本层是上采样层。-1代表将上层的输出作为本层的输入。None代表上采样的size(输出尺寸)不指定。2代表scale_factor=2,表示输出的尺寸是输入尺寸的2倍。nearest代表使用的上采样算法为最近邻插值算法。经过这层之后,特征图的长和宽变成原来的两倍,通道数不变,所以最终尺寸为40*40*1024。- [[-1, 6], 1, Concat, [1]]  # cat backbone P4 第11层,本层是concat层,[-1, 6]代表将上层和第6层的输出作为本层的输入。[1]代表concat拼接的维度是1。从上面的分析可知,上层的输出尺寸是40*40*1024,第6层的输出是40*40*512,最终本层的输出尺寸为40*40*1536。- [-1, 3, C2f, [512]]  # 12 第12层,本层是C2f模块,可以参考第2层的讲解。3代表本层重复3次。512代表输出通道数。与Backbone中C2f不同的是,此处的C2f的bottleneck模块的shortcut=False。- [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 第13层,本层也是上采样层(参考第10层)。经过这层之后,特征图的长和宽变成原来的两倍,通道数不变,所以最终尺寸为80*80*512。- [[-1, 4], 1, Concat, [1]]  # cat backbone P3 第14层,本层是concat层,[-1, 4]代表将上层和第4层的输出作为本层的输入。[1]代表concat拼接的维度是1。从上面的分析可知,上层的输出尺寸是80*80*512,第6层的输出是80*80*256,最终本层的输出尺寸为80*80*768。- [-1, 3, C2f, [256]]  # 15 (P3/8-small) 第15层,本层是C2f模块,可以参考第2层的讲解。3代表本层重复3次。256代表输出通道数。经过这层之后,特征图尺寸变为80*80*256,特征图的长宽已经变成输入图像的1/8。- [-1, 1, Conv, [256, 3, 2]] # 第16层,进行卷积操作(256代表输出通道数,3代表卷积核大小k,2代表stride步长),输出特征图尺寸为40*40*256(卷积的参数都没变,所以都是长宽变成原来的1/2,和之前一样)。- [[-1, 12], 1, Concat, [1]]  # cat head P4 第17层,本层是concat层,[-1, 12]代表将上层和第12层的输出作为本层的输入。[1]代表concat拼接的维度是1。从上面的分析可知,上层的输出尺寸是40*40*256,第12层的输出是40*40*512,最终本层的输出尺寸为40*40*768。- [-1, 3, C2f, [512]]  # 18 (P4/16-medium) 第18层,本层是C2f模块,可以参考第2层的讲解。3代表本层重复3次。512代表输出通道数。经过这层之后,特征图尺寸变为40*40*512,特征图的长宽已经变成输入图像的1/16。- [-1, 1, Conv, [512, 3, 2]] # 第19层,进行卷积操作(512代表输出通道数,3代表卷积核大小k,2代表stride步长),输出特征图尺寸为20*20*512(卷积的参数都没变,所以都是长宽变成原来的1/2,和之前一样)。- [[-1, 9], 1, Concat, [1]]  # cat head P5 第20层,本层是concat层,[-1, 9]代表将上层和第9层的输出作为本层的输入。[1]代表concat拼接的维度是1。从上面的分析可知,上层的输出尺寸是20*20*512,第9层的输出是20*20*1024,最终本层的输出尺寸为20*20*1536。- [-1, 3, C2f, [1024]]  # 21 (P5/32-large) 第21层,本层是C2f模块,可以参考第2层的讲解。3代表本层重复3次。1024代表输出通道数。经过这层之后,特征图尺寸变为20*20*1024,特征图的长宽已经变成输入图像的1/32。- [[15, 18, 21], 1, Detect, [nc]]  # Detect(P3, P4, P5) 第20层,本层是Detect层,[15, 18, 21]代表将第15、18、21层的输出(分别是80*80*256、40*40*512、20*20*1024)作为本层的输入。nc是数据集的类别数。

分类模型结构

接下来,我们看一下YOLOv8用于分类的模型结构,可以看到,除了最后的Head模块发生了改变,Backbone部分并没有明显改动(分类Backbone还是去掉了SPPF模块的),并且,分类直接去掉了特征融合模块(Neck),直接连接了分类头,这说明分类相较于检测更简单些。

nc: 1000 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolov8n-cls.yaml' will call yolov8-cls.yaml with scale 'n'# [depth, width, max_channels]n: [0.33, 0.25, 1024]s: [0.33, 0.50, 1024]m: [0.67, 0.75, 1024]l: [1.00, 1.00, 1024]x: [1.00, 1.25, 1024]# YOLOv8.0n backbone
backbone:# [from, repeats, module, args]- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4- [-1, 3, C2f, [128, True]]- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8- [-1, 6, C2f, [256, True]]- [-1, 1, Conv, [512, 3, 2]] # 5-P4/16- [-1, 6, C2f, [512, True]]- [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32- [-1, 3, C2f, [1024, True]]# YOLOv8.0n head
head:- [-1, 1, Classify, [nc]] # Classify

具体的,可以通过分类头的代码来查看其输出的结果:\ultralytics\nn\modules\head.py

class Classify(nn.Module):"""YOLOv8 classification head, i.e. x(b,c1,20,20) to x(b,c2)."""def __init__(self, c1, c2, k=1, s=1, p=None, g=1):"""Initializes YOLOv8 classification head with specified input and output channels, kernel size, stride,padding, and groups."""super().__init__()c_ = 1280  # efficientnet_b0 sizeself.conv = Conv(c1, c_, k, s, p, g)self.pool = nn.AdaptiveAvgPool2d(1)  # to x(b,c_,1,1)self.drop = nn.Dropout(p=0.0, inplace=True)self.linear = nn.Linear(c_, c2)  # to x(b,c2)def forward(self, x):"""Performs a forward pass of the YOLO model on input image data."""if isinstance(x, list):x = torch.cat(x, 1)x = self.linear(self.drop(self.pool(self.conv(x)).flatten(1)))return x if self.training else x.softmax(1)

Debug可知,其输入到分类头的数据维度为torch.Size([1, 256, 7, 7])分类头结构如下,可以看到,其最终输出结果的维度为(1,1000),对应1000个类别。

Classify((conv): Conv((conv): Conv2d(256, 1280, kernel_size=(1, 1), stride=(1, 1))(act): SiLU(inplace=True))(pool): AdaptiveAvgPool2d(output_size=1)(drop): Dropout(p=0.0, inplace=True)(linear): Linear(in_features=1280, out_features=1000, bias=True)
)

分类推理

from ultralytics import YOLO
model = YOLO("yolov8n-cls.pt")  # load an official model
model.predict("image.jpg",save=True)

YOLO模型能够根据pt文件来获取当然模型所进行的任务,pt文件中包含任务类型task,模型的yaml文件,predict方法也因此可以根据其任务类型选择不同的推理形式,结果如下:其分类为泰迪,应该是泰迪熊的意思,当然它应该是分错的,人家明明是猫好吧。

在这里插入图片描述

分类训练

分类开始训练代码如下,当然这里可以直接传入YOLO8n-cls的预训练模型,因为pt文件包含这些yaml文件内容的

from ultralytics import YOLO
if __name__ == '__main__':
# 代码model = YOLO("ultralytics\cfg\models/v8\yolov8-cls.yaml").load("yolov8n-cls.pt")  # build from YAML and transfer weightsresults = model.train(data="imagenette160", epochs=100, imgsz=64)

此外,需要更改的便是训练过程中的数据集与损失函数了
分类损失定义在:\ultralytics\utils\loss.py

class v8ClassificationLoss:"""Criterion class for computing training losses."""def __call__(self, preds, batch):"""Compute the classification loss between predictions and true labels."""loss = F.cross_entropy(preds, batch["cls"], reduction="mean")loss_items = loss.detach()return loss, loss_items

batch即真实类别标签,由于batch=16,因此共有16个标签,数字即对应的类别索引,preds为预测结果,维度为(16,10),即有16个预测结果,10为类别格式,其结果为16张图像的各个类别得分。

在这里插入图片描述

loss.detach()返回一个新的tensor,从当前计算图中分离下来的,但是仍指向原变量的存放位置,不同之处只是requires_gradfalse,得到的这个tensor永远不需要计算其梯度,不具有grad

在这里插入图片描述

最终的分类训练结果

在这里插入图片描述

F.cross_entropy函数是torch提供的用于求交叉熵损失函数的工具包,其用法为:F.cross_entropy(input, target)
交叉熵公式:

在这里插入图片描述

其中P为真实值,Q 为预测值。
计算交叉熵的详细步骤:

①将predict_scores进行softmax运算,将运算结果记为pred_scores_soft
②将pred_scores_soft进行log运算,将运算结果记为pred_scores_soft_log
③将pred_scores_soft_log与真实值进行计算处理。
思路即:

scores→softmax→log→compute

计算案例如下:
在这里插入图片描述

分类评价指标

accuracy_top-1
就是你预测的label取最后概率向量里面最大的那一个作为预测结果,如果你的预测结果中概率最大的那个分类正确,则预测正确。否则预测错误
accuracy_top-5
就是最后概率向量最大的前五名中,只要出现了正确概率即为预测正确。否则预测错误。

由此可以看出,top5一般比top1
与目标检测一样,YOLOv8分类结果也会被保存,如下:

在这里插入图片描述
混淆矩阵

在机器学习领域,混淆矩阵(Confusion Matrix),又称为可能性矩阵或错误矩阵。混淆矩阵是可视化工具,特别用于监督学习,在无监督学习一般叫做匹配矩阵。在图像精度评价中,主要用于比较分类结果和实际测得值,可以把分类结果的精度显示在一个混淆矩阵里面。

混淆矩阵要表达的含义:

混淆矩阵的每一列代表了预测类别,每一列的总数表示预测为该类别的数据的数目;
每一行代表了数据的真实归属类别,每一行的数据总数表示该类别的数据实例的数目;每一列中的数值表示真实数据被预测为该类的数目。
在这里插入图片描述

在这里插入图片描述
batch推断结果可视化

在这里插入图片描述


http://www.ppmy.cn/news/1506070.html

相关文章

使用 Streamlit 和 Python 构建 Web 应用程序

一.介绍 在本文中,我们将探讨如何使用 Streamlit 构建一个简单的 Web 应用程序。Streamlit 是一个功能强大的 Python 库,允许开发人员快速轻松地创建交互式 Web 应用程序。Streamlit 旨在让 Python 开发人员尽可能轻松地创建 Web 应用程序。以下是一些主…

SQLite批量INSERT

SQLite是一种轻量级、零配置的数据库管理系统。它的数据存储在一个单一的磁盘文件上,使得它非常适合嵌入式系统和移动应用。 在SQLite数据库中进行大批量记录INSERT,有三种方法,三种方法的效率由高低,本文举例说明。 方法一:逐条记录INSERT,这也是效率最低的方法 下面以…

用录制好的视频文件模拟PC电脑摄像头进行无人值守直播/抖音直播/视频号直播/快手直播

现在几乎全面都在刷短视频、看视频直播,现在市场上的数字人直播、无人直播设备也很多,但起步门槛都太高了,对于普通用户或者企业,实际只需要有那么一个简单的软件或者硬件,能将我们已经提前录制的视频作为实时视频源&a…

postgresql 分组合并 字段

postgresql 分组合并 字段 在PostgreSQL中,如果你想要将多个行的字段值合并为一个字段,你可以使用string_agg函数。这个函数可以将同一个组内的指定字段的值连接成一个字符串,并且可以自定义连接符。 下面是一个简单的例子,假设我…

等保测评中的访问控制与用户认证:构建安全的访问管理机制

在当今数字化时代,信息安全已成为企业和组织不可忽视的关键议题。等保测评,作为我国信息安全等级保护制度的重要组成部分,对访问控制与用户认证提出了严格要求,旨在构建安全的访问管理机制,保护信息资产不受未授权访问…

图片转换之heic转jpg(使用ImageMagick)

缘由:iphone的图库,用jpg拍照保存后内存占比较大,heic格式会微缩不少。问题来了,电脑不能直接小图预览heic。 分析:现在就是解决小图预览的问题(大图用wps可以看) 解决:查找了一些…

IO进程----文件IO

目录 IO进程 文件IO 1. 概念 2. 特点 3. 函数 3.1. 打开文件 3.2. 关闭文件 3.3. 读写文件 read write 3.4. 文件定位操作 文件属性获取 目录操作 IO进程 文件IO 1. 概念 在posix(可移植操作系统接口)中定义的一组输入输出的函数 2. 特点 1. 没有缓冲机制&#xff0c…

数据结构(java实现)——优先级队列,堆

文章目录 优先级队列堆堆的概念堆的模拟实现创建堆入堆判满删除判空获取栈顶元素 创建堆两种方式的时间复杂度堆排序java提供的PriorityQueue类基本的属性关于PriorityQueue类的三个构造方法关于PriorityQueue类中,入堆方法是怎样实现的?PriorityQueue注…