一文看懂YOLO v8

news2024/11/16 23:52:18

2023年,YOLO系列已经迭代到v8,v8与v5均出自U神,为了方便理解,我们将通过与v5对比来讲解v8。想了解v5的可以参考文章yolov5。

首先,回归一下yolov5:

  • Backbone:CSPDarkNet结构,主要结构思想的体现在C3模块,这里也是梯度分流的主要思想所在的地方;
  • PAN-FPN:双流的FPN,这里除了上采样、CBS卷积模块,最为主要的还有C3模块;
  • Head:Coupled Head+Anchor-base,YOLOv3、YOLOv4、YOLOv5、YOLOv7都是Anchor-Base的
  • label assignment:多正样本参考点,采用shape匹配规则,分别将GT的宽高与anchor宽高求比值,比值小于阈值,认定为正样本点;
  • Loss:分类用BEC Loss,回归用CIoU Loss。还有一个存在物体的置信度损失,总损失为三个损失的加权和。
    在这里插入图片描述

yolov8具体改进如下:

  • Backbone:使用的依旧是CSP的思想,不过YOLOv5中的C3模块被替换成了C2f模块,实现了进一步的轻量化,同时YOLOv8依旧使用了YOLOv5等架构中使用的SPPF模块;
  • PAN-FPN:YOLOv8依旧使用了PAN的思想,不过通过对比YOLOv5与YOLOv8的结构图可以看到,YOLOv8将YOLOv5中PAN-FPN上采样阶段中的CBS 1*1的卷积结构删除了,同时也将C3模块替换为了C2f模块;
  • Decoupled-Head:YOLOv8使用了Decoupled-Head;即通过两个头分别输出cls与reg的输出;
  • Anchor-Free:YOLOv8抛弃了以往的Anchor-Base,使用了Anchor-Free的思想;
  • Loss:YOLOv8使用VFL Loss作为分类损失(实际训练中并未使用),使用DFL Loss+CIOU Loss作为分类损失;
  • label assignmet:YOLOv8抛弃了以往的IOU匹配或者单边比例的分配方式,而是使用了Task-Aligned Assigner匹配方式。
    在这里插入图片描述

v8模型结构

了解yolo的朋友看完上面的对比应该对v8的结构有了大致的了解,最主要的更新就是c2,c2f结构,以及在Detect中将cls与reg解耦,并使用dfl的积分求取reg。

c2f

C3模块,其主要是借助CSPNet提取分流的思想,同时结合残差结构的思想,设计了C3 Block,这里的CSP主分支梯度模块为BottleNeck模块,也就是残差模块。结构如下图所示。
在这里插入图片描述为了进一步轻量化,v8设计了c2f结构,与c3相比,少了一层conv,采用split将特征分层,而不是conv。
在这里插入图片描述

class C2(nn.Module):
    # CSP Bottleneck with 2 convolutions
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super().__init__()
        self.c = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, 2 * self.c, 1, 1)
        self.cv2 = Conv(2 * self.c, c2, 1)  # optional act=FReLU(c2)
        self.m = nn.Sequential(*(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n)))

    def forward(self, x):
        a, b = self.cv1(x).split((self.c, self.c), 1)
        return self.cv2(torch.cat((self.m(a), b), 1))

Decoupled-head
而YOLOv8则是参考了YOLOX和YOLOV6,使用了Decoupled-Head,即使用两个卷积分别做分类和回归,同时由于使用了DFL 的思想,因此回归头的通道数也变成了4*reg_max的形式:

如代码所示,其中reg是由cv2输出,cls有cv3输出,yolov8与v5一样有3个特征层(8,16,32倍下采样),通过for x in ch 遍历特征层。

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)

label-assignment
v8最重要的更新是采取anchor-free的方式,并学习TOOD,使用Task-Alignment learning对齐cls与reg任务,那么下面我们对tood的label assignment进行详细解读。

正常对齐的Anchor应当可以预测高分类得分,同时具有精确定位;于此Tood设计了一个新的Anchor alignment metric 在Anchor level 衡量Task-Alignment的水平。并且,Alignment metric 被集成在了 sample 分配和 loss function里来动态的优化每个 Anchor 的预测。

def forward(self, pd_scores, pd_bboxes, anc_points, gt_labels, gt_bboxes, mask_gt):
        self.bs = pd_scores.size(0)
        self.n_max_boxes = gt_bboxes.size(1)

        if self.n_max_boxes == 0:
            device = gt_bboxes.device
            return (torch.full_like(pd_scores[..., 0], self.bg_idx).to(device), torch.zeros_like(pd_bboxes).to(device),
                    torch.zeros_like(pd_scores).to(device), torch.zeros_like(pd_scores[..., 0]).to(device),
                    torch.zeros_like(pd_scores[..., 0]).to(device))

        mask_pos, align_metric, overlaps = self.get_pos_mask(pd_scores, pd_bboxes, gt_labels, gt_bboxes, anc_points,
                                                             mask_gt)

        target_gt_idx, fg_mask, mask_pos = select_highest_overlaps(mask_pos, overlaps, self.n_max_boxes)

        # assigned target
        target_labels, target_bboxes, target_scores = self.get_targets(gt_labels, gt_bboxes, target_gt_idx, fg_mask)

        # normalize
        align_metric *= mask_pos
        pos_align_metrics = align_metric.amax(axis=-1, keepdim=True)  # b, max_num_obj
        pos_overlaps = (overlaps * mask_pos).amax(axis=-1, keepdim=True)  # b, max_num_obj
        # norm_align_metric = (align_metric * pos_overlaps / (pos_align_metrics + self.eps)).amax(-2).unsqueeze(-1)
        norm_align_metric = (align_metric  / (pos_align_metrics + self.eps)).amax(-2).unsqueeze(-1)
        target_scores = target_scores * norm_align_metric

        return target_labels, target_bboxes, target_scores, fg_mask.bool(), target_gt_idx

在v8中,label assignment分为3个步骤。首先,根据self.get_pos_mask()计算出正样本的mask,GT与pred_bboxes的iou以及align_metric,其中

align_metric = bbox_scores.pow(self.alpha) * overlaps.pow(self.beta)

mask_pos(即正样本的mask)需要通过mask_topk * mask_in_gts * mask_gt相乘获得。mask_in_gts表示在GT内部的mask,当左上角与右下角的坐标分别与anchor的中心点做差获得bbox_deltas, bbox_deltas的值均大于0则说明该点在GT内。

bbox_deltas = torch.cat((xy_centers[None] - lt, rb - xy_centers[None]), dim=2).view(bs, n_boxes, n_anchors, -1)

mask_topk 这个很好理解,就是align_metric的topk。align_metric同时考虑了cls与reg,因此可以更好的对齐cls与reg两个任务。

def get_pos_mask(self, pd_scores, pd_bboxes, gt_labels, gt_bboxes, anc_points, mask_gt):
        # get anchor_align metric, (b, max_num_obj, h*w)
        align_metric, overlaps = self.get_box_metrics(pd_scores, pd_bboxes, gt_labels, gt_bboxes)
        # get in_gts mask, (b, max_num_obj, h*w)
        mask_in_gts = select_candidates_in_gts(anc_points, gt_bboxes)
        # get topk_metric mask, (b, max_num_obj, h*w)
        mask_topk = self.select_topk_candidates(align_metric * mask_in_gts,
                                                topk_mask=mask_gt.repeat([1, 1, self.topk]).bool())
        # merge all mask to a final mask, (b, max_num_obj, h*w)
        mask_pos = mask_topk * mask_in_gts * mask_gt

        return mask_pos, align_metric, overlaps

此时,我们已经获得正样本的mask,但是GT存在交叠的情况,因此一个点可能对应多个GT,我们需要杜绝这种情况,将面积最大的GT赋值给有歧义的点。select_highest_overlaps函数可以完成这样的任务。

def select_highest_overlaps(mask_pos, overlaps, n_max_boxes):
    # (b, n_max_boxes, h*w) -> (b, h*w)
    fg_mask = mask_pos.sum(-2)
    if fg_mask.max() > 1:  # one anchor is assigned to multiple gt_bboxes
        mask_multi_gts = (fg_mask.unsqueeze(1) > 1).repeat([1, n_max_boxes, 1])  # (b, n_max_boxes, h*w)
        max_overlaps_idx = overlaps.argmax(1)  # (b, h*w)
        is_max_overlaps = F.one_hot(max_overlaps_idx, n_max_boxes)  # (b, h*w, n_max_boxes)
        is_max_overlaps = is_max_overlaps.permute(0, 2, 1).to(overlaps.dtype)  # (b, n_max_boxes, h*w)
        mask_pos = torch.where(mask_multi_gts, is_max_overlaps, mask_pos)  # (b, n_max_boxes, h*w)
        fg_mask = mask_pos.sum(-2)
    # find each grid serve which gt(index)
    target_gt_idx = mask_pos.argmax(-2)  # (b, h*w)
    return target_gt_idx, fg_mask, mask_pos

将mask_pos在n_max_boxes维度上叠加,当fg_mask.max() > 1时,说明存在歧义点。找到歧义点的索引mask_multi_gts 以及每个预测框对应的面积最大GT的索引max_overlaps_idx ,将max_overlaps_idx变成onehot形式,将有歧义点的值换成is_max_overlaps就可以祛除歧义。通过select_highest_overlaps函数还获得target_gt_idx ,即可以找到每个点对应的哪个GT。

最后,我们根据target_gt_idx 可以获得计算loss的targets,get_targets逻辑较为清晰不赘述。至此,v8的label assignment讲解完毕。


    def get_targets(self, gt_labels, gt_bboxes, target_gt_idx, fg_mask):
        """
        Args:
            gt_labels: (b, max_num_obj, 1)
            gt_bboxes: (b, max_num_obj, 4)
            target_gt_idx: (b, h*w)
            fg_mask: (b, h*w)
        """

        # assigned target labels, (b, 1)
        batch_ind = torch.arange(end=self.bs, dtype=torch.int64, device=gt_labels.device)[..., None]
        target_gt_idx = target_gt_idx + batch_ind * self.n_max_boxes  # (b, h*w)
        target_labels = gt_labels.long().flatten()[target_gt_idx]  # (b, h*w)

        # assigned target boxes, (b, max_num_obj, 4) -> (b, h*w)
        target_bboxes = gt_bboxes.view(-1, 4)[target_gt_idx]

        # assigned target scores
        target_labels.clamp(0)
        target_scores = F.one_hot(target_labels, self.num_classes)  # (b, h*w, 80)
        fg_scores_mask = fg_mask[:, :, None].repeat(1, 1, self.num_classes)  # (b, h*w, 80)
        target_scores = torch.where(fg_scores_mask > 0, target_scores, 0)

        return target_labels, target_bboxes, target_scores

Loss
对于YOLOv8,其分类损失为VFL Loss,其回归损失为CIOU Loss+DFL的形式,这里Reg_max默认为16。

VFL主要改进是提出了非对称的加权操作,FL和QFL都是对称的。而非对称加权的思想来源于论文PISA,该论文指出首先正负样本有不平衡问题,即使在正样本中也存在不等权问题,因为mAP的计算是主正样本。
在这里插入图片描述
q是label,正样本时候q为bbox和gt的IoU,负样本时候q=0,当为正样本时候其实没有采用FL,而是普通的BCE,只不过多了一个自适应IoU加权,用于突出主样本。而为负样本时候就是标准的FL了。可以明显发现VFL比QFL更加简单,主要特点是正负样本非对称加权、突出正样本为主样本。

针对这里的DFL(Distribution Focal Loss),其主要是将框的位置建模成一个 general distribution,让网络快速的聚焦于和目标位置距离近的位置的分布。
在这里插入图片描述
DFL 能够让网络更快地聚焦于目标 y 附近的值,增大它们的概率;
DFL的含义是以交叉熵的形式去优化与标签y最接近的一左一右2个位置的概率,从而让网络更快的聚焦到目标位置的邻近区域的分布;也就是说学出来的分布理论上是在真实浮点坐标的附近,并且以线性插值的模式得到距离左右整数坐标的权重。

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

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

相关文章

结构体的内存对齐规则

结构体的内存 一、对齐规则 1.数据成员对齐规则:结构(struct或联合union)的数据成员,第一个成员在与结构体变量偏移量为0的地址处,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节&#xff0…

anaconda/python安装虚拟环境并安装特定版本的库文件

anaconda/python安装虚拟环境并安装特定版本的库文件 文章目录anaconda/python安装虚拟环境并安装特定版本的库文件python安装虚拟环境安装教程pycharm加载虚拟环境以及安装指定的库文件python安装虚拟环境 安装教程 安装主要的代码指令就是下面这一句: conda create -n Env…

金融风控10

深度学习与金融风控 反欺诈生命周期 第一层设备与网络 - 代理检测 - IDC检测 - 模拟器/虚拟机检测 - 木马检测 第二层用户行为 - 注册行为 - 登录行为 - 交易行为 - 事件行为 - 时间间隔异常 第三层业务频次 - 注册频次 - 登录频次 - 交易频次 - 地域频次 - …

计算机组成原理笔记记录(第二章)

次为笔记记录&#xff0c;原视频链接为B站视频链接&#xff0c;若有错误请指出&#xff0c;看到就会改正 进制 r进制数及其转换成十进制数 r^n就是第n位的维权,n<0就是小数部分的位权。 例子:1011 为十进制的时候:10111103010211011100 为二进制的时候 1011123022121120(10…

Scipy误差函数详解

文章目录误差函数简介复平面上的误差函数与误差函数相关的函数误差函数简介 误差函数的实质是正态分布的概率&#xff0c;其重要性可见一斑&#xff0c;其表达式为 erf⁡2π∫0xe−t2dt\operatorname{erf}\frac{2}{\sqrt{\pi}}\int^x_0e^{-t^2}\text dt erfπ​2​∫0x​e−t2…

vue老项目增加提交格式化功能[eslint+prettier+husky+lint-staged]

一、当前项目情况说明 当前项目是已经开发迭代很久的项目&#xff0c;之前因为需求紧急&#xff0c;所以没有对代码格式话进行规范要求&#xff0c;导致每个开发人员提交的代码格式千差万别&#xff0c;增加了阅读难度及后期的维护成本&#xff0c;如果直接run lint&#xff0…

网络拓扑七大类型:总线、环形、星形、网状、树形、点对点、混合,我背的滚瓜烂熟!

大家好&#xff0c;这里是网络技术联盟站。 在网络世界中&#xff0c;经常会看到各种各样的网络拓扑&#xff0c;网络拓扑主要就是描述网络中各个元素的对应关系&#xff0c;那么网络中包含哪些类型的拓扑呢&#xff1f; 如上图所示&#xff0c;网络拓扑一般有两大类型&#x…

JS---数组的方法

一、方法 1.1、Pushing和Poping arr.push(1)&#xff1a;往arr数组的最后面压入1&#xff0c;push() 方法返回新数组的长度。 var fruits ["Banana", "Orange", "Apple", "Mango"]; fruits.push("Kiwi"); // fruits …

Ruoyi-Cloud框架学习-【04 用户登录】

前端 路由配置在router/index.js里 首页在views/index.vue4 前端端口与后台端口在vue.config.js里定义 vue.config.js 前台端口 后台端口 Ruoyi-Cloud登录流程 Login.vue 定义了登录handlerlogin&#xff0c;具体方法调用modules/user.js store/index.js 调用了modul…

【Stm32杂谈】:Stm32F103野火指南针开发板红外遥控程序问题记录和解析(个人理解)

项目场景&#xff1a; 最近在使用Stm32F103野火指南针开发板开发红外遥控外设得时候&#xff0c;用得是野火得开发板&#xff0c;本来发现应该很简单的事情&#xff0c;官方也很贴切的提供了官方例程。但是居然有问题&#xff0c;无法正常使用。 于是这篇文章应运而生&#xff…

环保数采仪 5G无线环保数采仪 智能环保数采仪

计讯物联智能环保数采仪&#xff0c;无线远距离数据传输、采集、控制、存储。支持全网通5G/4G移动网络、北斗、有线通信&#xff0c;数据上报云监控中心&#xff0c;支持GPS定位分散设备远程统一管理。支持环保协议&#xff0c;对接各省市县级监管平台&#xff0c;广泛应用于废…

Kubernetes集群搭建

Kubernetes集群搭建 目录 前言前期准备K8S集群安装 虚拟机设置安装K8S集群k8s部署Nginx 附录1 Docker安装附录2 yum k8s 问题附录3 k8s start问题附录4 k8s master init附录5 node节点添加进集群失败&#xff0c;可以删除节点重新添加 前言 本文指定Docker与K8s版本&#xf…

DynaSLAM-3 DynaSLAM中Mask R-CNN部分源码解析(Ⅱ)

目录 1.FPN 1.1 FPN层原理 1.2 FPN代码解析 2. 候选框的生成 2.1 根据特征图生成候选框 1.FPN 1.1 FPN层原理 在Faster R-CNN网络中&#xff0c;提取特征的时候&#xff0c;将原始数据经过一系列的卷积层&#xff0c;我们只用最后一层的特征图进行提取。 比如五层卷积神经…

C++(36)-VS2019- 动态库调用

1.被调用的动态库 MyDll 2.调用的可执行文件 MyExe 源码实例链接&#xff1a;MFC-VS2019-EXE调用DLL-demo.zip-C代码类资源-CSDN下载 1.MyDll 1.1 MyDll头文件&#xff1a;MyDll.h 声明此动态库为导出动态库。 声明导出函数。 #pragma once#define MYDECLARE_PUB…

【05】FreeRTOS的中断管理

目录 1.什么是中断 2.中断优先级分组 2.1中断优先级分组-介绍 2.2中断优先级分组-配置 2.3中断优先级分组-特点 3.中断相关寄存器 3.1寄存器地址 3.2在FreeRTOS中配置PendSV和Systick中断优先级 3.3中断相关寄存器 4.FreeRTOS中断管理实验 4.1修改freertos_demo.c …

2023.1.30作业-【尝试移植TF-A】

1、解压源码&#xff0c;进入目录如图一 2、解压源码包 3、进入解压后的目录&#xff0c;打入官方补丁 4、查看SD卡的分区&#xff0c;发现正常无需重新分区 5、导入编译工具查看是否正常导入 6、添加设备树等相关文件 7、修改上层目录下的 Makefile.sdk中添加 stm32mp157a-fsm…

chatGPT模型简介

ChatGPT的工作原理 chatGPT 是一款由 OpenAI 开发的聊天机器人模型&#xff0c;它能够模拟人类的语言行为&#xff0c;与用户进行自然的交互。它的名称来源于它所使用的技术—— GPT-3架构&#xff0c;即生成式语言模型的第3代。 chatGPT的核心技术是 GPT-3 架构。它通过使用大…

vue 自动生成swagger接口请求文件

前端: vue-element-admin 后端: .net core (6.0) 找了很多自动生成的代码的&#xff0c;感觉不太行&#xff0c;可能是我不太懂。所以自己根据swagger.json去生成了js请求文件。 后端很简单&#xff0c;就不说了&#xff0c;只要能访问到swagger的地址就可以&#xff0c;主要…

【My Electronic Notes系列——低频功率放大器】

目录 序言&#xff1a; &#x1f3c6;&#x1f3c6;人生在世&#xff0c;成功并非易事&#xff0c;他需要破茧而出的决心&#xff0c;他需要永不放弃的信念&#xff0c;他需要水滴石穿的坚持&#xff0c;他需要自强不息的勇气&#xff0c;他需要无畏无惧的凛然。要想成功&…

【自学Docker】Docker rename命令

Docker rename命令 大纲 docker rename命令教程 docker rename 命令可以用于重命名一个 Docker容器。docker rename命令后面的 CONTAINER 可以是容器Id&#xff0c;或者是容器名。 docker rename语法 haicoder(www.haicoder.net)# docker rename CONTAINER NEW_NAME案例 重…