yolov8源码解读Detect层

yolov8源码解读Detect层

  • Detect层解读
  • 网络各层解读及detect层后的处理

关于网络的backbone,head,以及detect层后处理,可以参考文章结尾博主的文章。

Detect层解读

先贴一下全部代码,下面一一解读。

class Detect(nn.Module):
    """YOLOv8 Detect head for detection models."""
    dynamic = False  # force grid reconstruction
    export = False  # export mode
    shape = None
    anchors = torch.empty(0)  # init
    strides = torch.empty(0)  # init

    def __init__(self, nc=80, ch=()):
        """Initializes the YOLOv8 detection layer with specified number of classes and channels."""
        super().__init__()
        self.nc = nc  # number of classes
        self.nl = len(ch)  # number of detection layers
        self.reg_max = 16  # DFL channels (ch[0] // 16 to scale 4/8/12/16/20 for n/s/m/l/x)
        self.no = nc + self.reg_max * 4  # number of outputs per anchor
        self.stride = torch.zeros(self.nl)  # strides computed during build
        c2, c3 = max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], min(self.nc, 100))  # channels
        self.cv2 = nn.ModuleList(
            nn.Sequential(Conv(x, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for x in ch)
        self.cv3 = nn.ModuleList(nn.Sequential(Conv(x, c3, 3), Conv(c3, c3, 3), nn.Conv2d(c3, self.nc, 1)) for x in ch)
        self.dfl = DFL(self.reg_max) if self.reg_max > 1 else nn.Identity()

    def forward(self, x):
        """Concatenates and returns predicted bounding boxes and class probabilities."""
        shape = x[0].shape  # BCHW
        # print(">>>>", x[0].shape)
        # print(">>>>", x[1].shape)
        # print(">>>>", x[2].shape)
        for i in range(self.nl):
            x[i] = torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1)

        if self.training:
            return x
        elif self.dynamic or self.shape != shape:
            self.anchors, self.strides = (x.transpose(0, 1) for x in make_anchors(x, self.stride, 0.5))
            self.shape = shape

        x_cat = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2)
        if self.export and self.format in ('saved_model', 'pb', 'tflite', 'edgetpu', 'tfjs'):  # avoid TF FlexSplitV ops
            box = x_cat[:, :self.reg_max * 4]
            cls = x_cat[:, self.reg_max * 4:]
        else:
            box, cls = x_cat.split((self.reg_max * 4, self.nc), 1)

        dbox = dist2bbox(self.dfl(box), self.anchors.unsqueeze(0), xywh=True, dim=1) * self.strides

        if self.export and self.format in ('tflite', 'edgetpu'):
            # Normalize xywh with image size to mitigate quantization error of TFLite integer models as done in YOLOv5:
            # https://github.com/ultralytics/yolov5/blob/0c8de3fca4a702f8ff5c435e67f378d1fce70243/models/tf.py#L307-L309
            # See this PR for details: https://github.com/ultralytics/ultralytics/pull/1695
            img_h = shape[2] * self.stride[0]
            img_w = shape[3] * self.stride[0]
            img_size = torch.tensor([img_w, img_h, img_w, img_h], device=dbox.device).reshape(1, 4, 1)
            dbox /= img_size
        # print(cls.shape)
        y = torch.cat((dbox, cls.sigmoid()), 1)
        # print(y.shape)
        return y if self.export else (y, x)
	dynamic = False #这个属性指示网格(通常是特征图上的锚框网格)是否需要动态地重建
    export = False  #这个属性用于指示模型是否处于导出模式。
    shape = None # 用于存储输入图像或特征图的尺寸。
    anchors = torch.empty(0)  # 创建了一个空的PyTorch张量
    strides = torch.empty(0)

步长(strides)是卷积神经网络中特征图相对于输入图像的缩小比例。
例如,如果步长是32,那么一个32x32像素的区域在特征图上就对应一个单元。
和anchors一样,这里的torch.empty(0)表示步长尚未初始化。

    def __init__(self, nc=80, ch=()):
        """Initializes the YOLOv8 detection layer with specified number of classes and channels."""
        super().__init__()
        self.nc = nc  # number of classes
        self.nl = len(ch)  # number of detection layers
        self.reg_max = 16  # DFL channels (ch[0] // 16 to scale 4/8/12/16/20 for n/s/m/l/x)
        self.no = nc + self.reg_max * 4  # number of outputs per anchor
        self.stride = torch.zeros(self.nl)  # strides computed during build
        c2, c3 = max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], min(self.nc, 100))  # channels
        self.cv2 = nn.ModuleList(
            nn.Sequential(Conv(x, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for x in ch)
        self.cv3 = nn.ModuleList(nn.Sequential(Conv(x, c3, 3), Conv(c3, c3, 3), nn.Conv2d(c3, self.nc, 1)) for x in ch)
        self.dfl = DFL(self.reg_max) if self.reg_max > 1 else nn.Identity()

nc:类别数
nl:检测层的数量,目标检测中为3。
ch:传入的图片通道尺寸,在yolov8n,图片大小为640*640时。这里的ch为(256,128,64)
no:两个卷积再拼接后输出通道数,为4×reg_max+nc
c2,c3:计算卷积层的通道数。
cv2,cv3:定义的卷积操作,以输出有关类别和选框的特征图。
dfl:通过将分布式的概率分布转化为单一的预测值

class DFL(nn.Module):

    def __init__(self, c1=16):
        """Initialize a convolutional layer with a given number of input channels."""
        super().__init__()
        self.conv = nn.Conv2d(c1, 1, 1, bias=False).requires_grad_(False)
        x = torch.arange(c1, dtype=torch.float)
        self.conv.weight.data[:] = nn.Parameter(x.view(1, c1, 1, 1))
        self.c1 = c1

    def forward(self, x):
        """Applies a transformer layer on input tensor 'x' and returns a tensor."""
        b, c, a = x.shape  # batch, channels, anchors
        return self.conv(x.view(b, 4, self.c1, a).transpose(2, 1).softmax(1)).view(b, 4, a)

self.conv:创建了一个输入通道为16,输出为1,没有偏置项,不需要进行梯度更新的卷积层。
这样的权重设置实际上模拟了一个积分过程,将卷积操作变成了加权求和的形式。
x:1到15的整数。
self.conv.weight.data[:] = nn.Parameter(x.view(1, c1, 1, 1)):
这里使用nn.Parameter将重塑后的张量设置为模型的参数,并且参数不会被更新。
假设前向传播中,x的形状为(1, 64, 8400),下面解释下forword中的变化。
1,x.view(b, 4, self.c1, a): 这个操作是对x的形状进行重塑。self.c1是16(因为输入通道数是64,即4*self.c1),那么a是8400(代表了所有锚点的数量)。b是批次大小,这里为1。所以x.view(b, 4, self.c1, a)将x从(1, 64, 8400)重塑为(1, 4, 16, 8400)。在这个形状中,我们得到了每个锚点的每个坐标轴(x, y, 宽度, 高度)上的16个预测值(可能代表某种概率分布)。
2,transpose(2, 1): 这个操作交换第二维和第三维。在应用transpose之后,张量的形状变为(1, 16, 4, 8400)。这样做的目的是让每组概率分布的16个预测值连续地排列在一起,为后面的softmax运算做准备。
3,softmax(1): softmax函数应用于第一维(现在是16个预测值的这一维)。softmax确保了这16个值之和为1,转换为一个有效的概率分布,表示每个预测值的可能性。
4,self.conv(…): 这个操作将配置好的卷积层应用在进行了softmax操作的张量上。由于卷积层的权重已被设置为从0到15的整数,并且不更新权重(不进行梯度下降优化),这个步骤实际上是在计算期望值。卷积层将每个离散的概率值乘以其相应的索引(也就是权重),然后对结果进行求和,得到该坐标的预测值。
5,view(b, 4, a): 最后一步是将张量的形状从卷积操作后的(1, 1, 4, 8400)转换回(1, 4, 8400)。这样确保了最终的输出张量与每个坐标轴的预测值(x, y, 宽度, 高度)和所有锚点的数量对齐。
总的来说,dfl层就是对预测的坐标求加权期望值。将(1,64,8400)先变为(1,16,4,8400),然后对这16个通道求加权期望,变为(1,4,8400)即这8400个锚点中的每一个锚点,x,y,width,hight的加权平均值。
接下来是前向传播的过程。打印传入的x形状,发现通道数是64,128,256。

在这里插入图片描述
在这里插入图片描述
原因:Detect层接受15,18,21层的输入。原本通道数是1024,512,256。但是yolov8n还需要乘0.25。
在这里插入图片描述
在这里插入图片描述

经过cv2,通道数变为64,经过cv3通道数变为nc,我这里nc为2(二分类)。在经过cat拼接,在通道维度上拼接,所以x[i]的通道数变为66。

如果处于训练模式,就直接返回x。
否则执行下面的代码,将特征图列表x(1×66×40×40,1×66×80×80,1×66×20×20)传递给make_anchors()函数。make_anchors函数用于生成锚点(anchors),它通常用在目标检测网络中。每个锚点代表了特征图上的一个点,可以用来预测相对于该点的边界框。strides是这些特征图相对于原始图像的下采样步长。简单来说,生成了8400个锚点(40×40+80×80+20×20),变量为anchors,形状为1×2×8400)。同时生成了8400个步长,变量为strides,形状为1×8400。参数0.5表示每个锚点处于每个像素块的中央。

        if self.training:
            return x
        elif self.dynamic or self.shape != shape:
            self.anchors, self.strides = (x.transpose(0, 1) for x in make_anchors(x, self.stride, 0.5))
            self.shape = shape

将xi按照2维度进行拼接,xi分别为1×66×40×40,1×66×80×80,1×66×20×20。拼接后的x_cat为1×66×8400

x_cat = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2)

这段代码就是把x_cat进行拆分。box形状为1×64×8400,包含每个边界框的回归参数。cls形状为1×2×8400,会包含类别预测,2是因为我这里类别为2。

        if self.export and self.format in ('saved_model', 'pb', 'tflite', 'edgetpu', 'tfjs'):  # avoid TF FlexSplitV ops
            box = x_cat[:, :self.reg_max * 4]
            cls = x_cat[:, self.reg_max * 4:]
        else:
            box, cls = x_cat.split((self.reg_max * 4, self.nc), 1)

dfl层就是对预测的坐标求加权期望值。将(1,64,8400)先变为(1,16,4,8400),然后对这16个通道求加权期望,变为(1,4,8400)即这8400个锚点中的每一个锚点,x1,y1,x2,y2的加权平均值。dist2bbox()函数的作用是将锚点x1,y1,x2,y2转换为x,y,width,hight的形式。最后在乘以步长,还原到原图的大小比例。

dbox = dist2bbox(self.dfl(box), self.anchors.unsqueeze(0), xywh=True, dim=1) * self.strides

在这里插入图片描述

此代码片段的作用是在模型导出为 Tensorflow Lite (tflite) 或 Edge TPU 兼容格式时,对预测框 (dbox) 进行归一化处理。

 if self.export and self.format in ('tflite', 'edgetpu'):
            # Normalize xywh with image size to mitigate quantization error of TFLite integer models as done in YOLOv5:
            # https://github.com/ultralytics/yolov5/blob/0c8de3fca4a702f8ff5c435e67f378d1fce70243/models/tf.py#L307-L309
            # See this PR for details: https://github.com/ultralytics/ultralytics/pull/1695
            img_h = shape[2] * self.stride[0]
            img_w = shape[3] * self.stride[0]
            img_size = torch.tensor([img_w, img_h, img_w, img_h], device=dbox.device).reshape(1, 4, 1)
            dbox /= img_size

此时y的形状为1×66×8400。代表有8400个锚点,每个锚点包含坐标框的x,y,width,hight,以及类别得分信息。

y = torch.cat((dbox, cls.sigmoid()), 1)

返回值,至此detect层结束。完整的预测,后续还需要进行一些处理。如进行非极大抑制,对这8400个锚点进行筛选

return y if self.export else (y, x)

另外,最后的bias_init()函数用于初始化一个目标检测模型中的Detect层的偏置。确保在训练开始时偏置值是基于合理假设的。这种方法的目标是为模型提供一个好的起点,并有助于加速训练过程中的收敛。

    def bias_init(self):
        """Initialize Detect() biases, WARNING: requires stride availability."""
        m = self  # self.model[-1]  # Detect() module
        # cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1
        # ncf = math.log(0.6 / (m.nc - 0.999999)) if cf is None else torch.log(cf / cf.sum())  # nominal class frequency
        for a, b, s in zip(m.cv2, m.cv3, m.stride):  # from
            a[-1].bias.data[:] = 1.0  # box
            b[-1].bias.data[:m.nc] = math.log(5 / m.nc / (640 / s) ** 2)  # cls (.01 objects, 80 classes, 640 img)

网络各层解读及detect层后的处理

关于backbone,head层,以及detect层参考下面博主的文章,讲的非常好。

链接: Yolov 8源码超详细逐行解读+ 网络结构细讲(自我用的小白笔记)
链接: 最细致讲解yolov8模型推理完整代码–(前处理,后处理)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

每日五道java面试题之java基础篇(十一)

目录: 第一题. Java死锁如何避免?第二题. 为什么⽤线程池?解释下线程池参数?第三题. 线程池的底层⼯作原理第四题. ReentrantLock中tryLock()和lock()⽅法的区别第五题. Sychronized和ReentrantLock的区别? 第一题. Java死锁如何避免&#x…

Open CASCADE学习|曲线的切线

今天要实现的功能是在曲线的终点处沿切线方向延长该曲线。为了解决这个问题,需要求解该曲线在终点处的坐标值以及切矢量。问题转化为:已知曲线TopoDS_Edge aE,求其在终点处的坐标值及切线方向向量。 首先,将TopoDS_Edge对象转化为…

数组转二叉树的一种方法-java(很特殊)

上代码 Node节点的代码 public class ThreadNode {private int data;private ThreadNode left;private boolean leftTag; // 左子节点是否为线索private ThreadNode right;private boolean rightTag; // 右子节点是否为线索// ... 省略get和set方法// ... 省略构造方法// ... …

C语言系列(所需基础:大学C语言及格)-1-编译器/简单的求和代码/数据类型/变量的分类/变量的作用域和生命周期

文章目录 一、编译器(使用在线编译器)二、简单的求和代码三、数据类型四、变量的分类五、变量的作用域和生命周期 一、编译器(使用在线编译器) 为了方便,我使用的是在线的C语言编译器进行程序的运行。 链接&#xff1…

turn服务器debug

turn服务器正常能连通的调用堆栈 turn_port.cc AddRequestAuthInfo check 崩溃 有问题的turn msg type是259 request type 是3 用不了的turn 服务器turnmessage type 275

代码随想录 Leetcode763. 划分字母区间

题目&#xff1a; 代码(首刷看解析 2024年2月18日&#xff09;&#xff1a; class Solution { public:vector<int> partitionLabels(string s) {int hash[27] {0};for (int i 0; i < s.size(); i) {hash[s[i] - a] i;}vector<int> res;int left 0;int righ…

C++学习Day05之强化训练---数组类封装

目录 一、程序及输出1.1 数组类头文件1.2 数组类.cpp1.3 主程序 二、分析与总结 一、程序及输出 1.1 数组类头文件 myArray.h #include<iostream> using namespace std;class MyArray { public:MyArray(); //默认构造 可以给100容量MyArray(int capacity); //有参构造…

CogVLM训练源码解读--数据处理

文章目录 前言一、数据主函数源码解读1、图像函数源码调用解读2、文本函数源码调用解读3、tokenizer生成函数4、llama2_text_processor文本处理函数解读 二、create_dataset_function函数源码代码解读三、sat库之make_loaders函数源码解读1、make_loaders函数调用说明2、make_l…

CSS 多色阴影效果和旋转动画的加载指示器

<template><!-- 创建一个装载加载动画的容器 --><view class="loader"><!-- 内部阴影层,用于放置动态文本 --><view class="intern"></view><!-- 外部阴影层,包含旋转和颜色变化的圆形阴影 --><view class…

RK3399平台开发系列讲解(USB篇)USB 枚举和断开过程

🚀返回专栏总目录 文章目录 一、连接与检测二、USB设备枚举三、断开过程沉淀、分享、成长,让自己和他人都能有所收获!😄 📢介绍 USB 枚举/断开过程。 一、连接与检测 二、USB设备枚举 USB设备枚举一

第六节笔记:OpenCompass 大模型评测

视频链接&#xff1a;https://www.bilibili.com/video/BV1Gg4y1U7uc/?spm_id_from333.788&vd_source3bbd0d74033e31cbca9ee35e111ed3d1

手写myscrapy(二)

我们看一下scrapy的系统架构设计方法和思路&#xff1a; 模块化设计&#xff1a; Scrapy采用模块化设计&#xff0c;将整个系统划分为多个独立的模块&#xff0c;包括引擎&#xff08;Engine&#xff09;、调度器&#xff08;Scheduler&#xff09;、下载器&#xff08;Downl…

RIP协议详解

​RIP是最早的动态路由协议&#xff0c;虽然已经过时并且很少使用&#xff0c;但是可以通过学习RIP并且和ospf等现在正在使用的路由协议对比&#xff0c;了解其工作原理和过时原因&#xff0c;具有很强的学习性。 一、RIP协议简介 RIP&#xff08;Routing Information Protoc…

Vue22 Vue监测数据改变的原理_数组

实例 <!DOCTYPE html> <html><head><meta charset"UTF-8" /><title>Vue监测数据改变的原理_数组</title><!-- 引入Vue --><script type"text/javascript" src"../js/vue.js"></script>&…

如何避免发送HTTP请求

资料来源 : 小林coding 小林官方网站 : 小林coding (xiaolincoding.com) 如何避免发送HTTP请求? 这个思路你看到是不是觉得很奇怪&#xff0c;不发送 HTTP 请求&#xff0c;那客户端还怎么和服务器交互数据?小林你这不是要流氓嘛? 冷静冷静&#xff0c;你说的没错&#xf…

jmeter-12jmeter的录制功能

文章目录 什么情况下使用录制功能?操作流程具体设置如下观察结果什么情况下使用录制功能? 在测试过程中,很多时候可能会没有接口文档,这样你不知道请求方式,url,等等如何进行测试? jmeter提供了对应的录制功能。录制功能可以抓到具体的接口信息 操作流程 创建线程组 …

Pandas 数据处理:从基础到高级的完整指南【第84篇—Pandas 数据处理】

Pandas 数据处理&#xff1a;从基础到高级的完整指南 Pandas 是一个强大的数据分析工具&#xff0c;广泛应用于数据科学、机器学习和统计分析等领域。本文将介绍 Pandas 模块的基础知识&#xff0c;包括数据结构、数据导入、数据选择与过滤等方面&#xff0c;通过实际代码示例…

R语言课程论文-飞机失事数据可视化分析

数据来源&#xff1a;Airplane Crashes Since 1908 (kaggle.com) 代码参考&#xff1a;Exploring historic Air Plane crash data | Kaggle 数据指标及其含义 指标名 含义 Date 事故发生日期(年-月-日) Time 当地时间&#xff0c;24小时制&#xff0c;格式为hh:mm Locat…

Android 11以上获取不到第三方app是否安装

开年第一篇&#xff0c;处理了一下年前的小问题。 问题&#xff1a;本地app跳转到第三方app地图进行导航&#xff0c;获取不到第三方地图是否安装。 解决&#xff1a; 1.添加包名 This can be done by adding a <queries> element in the Android manifest.在app下的…

红队打靶练习:IMF: 1

目录 信息收集 1、arp 2、nmap 3、nikto 目录探测 gobuster dirsearch WEB 信息收集 get flag1 get flag2 get flag3 SQL注入 漏洞探测 脱库 get flag4 文件上传 反弹shell 提权 get flag5 get flag6 信息收集 1、arp ┌──(root㉿ru)-[~/kali] └─# a…