经典目标检测YOLO系列(三)YOLOv3的复现(2)正样本的匹配、损失函数的实现

经典目标检测YOLO系列(三)YOLOv3的复现(2)正样本的匹配、损失函数的实现

我们在之前实现YOLOv2的基础上,加入了多级检测及FPN,快速的实现了YOLOv3的网络架构,并且实现了前向推理过程。

经典目标检测YOLO系列(三)YOLOV3的复现(1)总体网络架构及前向处理过程

我们继续进行YOLOv3的复现。

1 正样本匹配策略

1.1 基于先验框的正样本匹配策略

  • 官方YOLOv2的正样本匹配思路是根据预测框和目标框的IoU来确定中心点所在的网格,哪一个预测框是正样本。

  • 大体上,官方YOLOv3也沿用这一思路,但是细节上有差距。官方YOLOv3也会出现之前所说的三种情况:

    • 前2种情况,IoU都小于iou_thresh或者仅有一个IoU值大于iou_thresh,那么此时会有一个正样本;
    • 第3种情况,即有多个IoU值大于iou_thresh时候,仅仅将IoU最大的哪一个作为正样本。对于剩下样本,由于IoU值已经大于iou_thresh,因此不会被标记为正样本,将其忽略。
  • 我们继续沿用之前复现YOLOv2的做法。对于第3种情况,我们不忽略,还是标记为正样本。

    • 第1种情况:如果IoU都小于iou_thresh,为了不丢失这个训练样本,我们选择选择IoU值最大的先验框P_A。将P_A对应的预测框B_A,标记为正样本,即先验框决定哪些预测框会参与到何种损失的计算中去
    • 第2种情况:仅有一个IoU值大于iou_thresh,那么这个先验框所对应的预测框会被标记为正样本,会参与到置信度、类别及位置损失的计算。
    • 第3种情况:有多个IoU值大于iou_thresh,那么这些先验框所对应的预测框都会被标记为正样本,即一个目标会被匹配上多个正样本
  • 由于YOLOv3中添加了多级检测,因此部分代码细节有所差异。

1.2 代码实现

1.2.1 正样本匹配

pytorch读取VOC数据集:

  • 一批图像数据的维度是 [B, 3, H, W] ,分别是batch size,色彩通道数,图像的高和图像的宽。

  • 标签数据是一个包含 B 个图像的标注数据的python的list变量(如下所示),其中,每个图像的标注数据的list变量又包含了 M 个目标的信息(类别和边界框)。

  • 获得了这一批数据后,图片是可以直接喂到网络里去训练的,但是标签不可以,需要再进行处理一下。

[{'boxes': torch.tensor([[120.,   0., 408.,  23.],[160.,  59., 416., 256.],[172.,  24., 218., 128.],[408.,  35., 416.,  75.],[  0.,  64.,   8., 186.]]),  # bbox的坐标(xmin, ymin, xmax, ymax'labels': torch.tensor([ 6,  6, 14,  6, 19]),       # 标签'orig_size': [416, 416]                             # 图片的原始大小},{'boxes': torch.tensor([[367., 255., 416., 416.],[330., 302., 416., 416.]]),'labels': torch.tensor([14, 13]),'orig_size': [416, 416]}
]

标签处理主要包括3个部分,

  • 一是将真实框中心所在网格对应正样本位置(anchor_idx)的置信度置为1,其他默认为0
  • 二是将真实框中心所在网格对应正样本位置(anchor_idx)的标签类别为1(one-hot格式),其他类别设置为0
  • 三是将真实框中心所在网格对应正样本位置(anchor_idx)的bbox信息设置为真实框的bbox信息。
# 处理好的shape如下:
# gt_objectness  
torch.Size([2, 10647, 1])  # 10647=52×52×3 + 26×26×3 + 13×13×3
# gt_classes
torch.Size([2, 10647, 20])
# gt_bboxes
torch.Size([2, 10647, 4])

1.2.2 具体代码实现

  • 对于一个目标框,我们先计算它和9个先验框的IoU,然后先用阈值进行筛选
  • 然后,我们会遇到之前说的3种情况,处理方法和YOLOv2一致。
  • 在确定哪个先验框为正样本后,我们还要通过公式iou_ind // self.num_anchors确定这个先验框来自哪个尺度。
    • 一个很小的目标框,它和较小的先验框的IoU理应大一些,因此会被分配到网格密集的C3尺度上;
    • 相反,一个很大的目标框,它和较大的先验框的IoU理应大一些,因此会被分配到网格稀疏的C5尺度上;
    • 中等大小的目标框,被分配到C4尺度上。
# RT-ODLab/models/detectors/yolov3/matcher.py
import numpy as np
import torchclass Yolov3Matcher(object):def __init__(self, num_classes, num_anchors, anchor_size, iou_thresh):self.num_classes = num_classesself.num_anchors = num_anchorsself.iou_thresh = iou_threshself.anchor_boxes = np.array([[0., 0., anchor[0], anchor[1]]for anchor in anchor_size])  # [KA, 4]def compute_iou(self, anchor_boxes, gt_box):"""函数功能: 计算目标框和9个先验框的IoU值anchor_boxes : ndarray -> [KA, 4] (cx, cy, bw, bh).gt_box : ndarray -> [1, 4] (cx, cy, bw, bh).返回值: iou变量,类型为ndarray类型,shape为[9,], iou[i]就表示该目标框和第i个先验框的IoU值"""# 1、计算9个anchor_box的面积# anchors: [KA, 4]anchors = np.zeros_like(anchor_boxes)anchors[..., :2] = anchor_boxes[..., :2] - anchor_boxes[..., 2:] * 0.5  # x1y1anchors[..., 2:] = anchor_boxes[..., :2] + anchor_boxes[..., 2:] * 0.5  # x2y2anchors_area = anchor_boxes[..., 2] * anchor_boxes[..., 3]# 2、gt_box复制9份,计算9个相同gt_box的面积# gt_box: [1, 4] -> [KA, 4]gt_box = np.array(gt_box).reshape(-1, 4)gt_box = np.repeat(gt_box, anchors.shape[0], axis=0)gt_box_ = np.zeros_like(gt_box)gt_box_[..., :2] = gt_box[..., :2] - gt_box[..., 2:] * 0.5  # x1y1gt_box_[..., 2:] = gt_box[..., :2] + gt_box[..., 2:] * 0.5  # x2y2gt_box_area = np.prod(gt_box[..., 2:] - gt_box[..., :2], axis=1)# 3、计算计算目标框和9个先验框的IoU值# intersectioninter_w = np.minimum(anchors[:, 2], gt_box_[:, 2]) - \np.maximum(anchors[:, 0], gt_box_[:, 0])inter_h = np.minimum(anchors[:, 3], gt_box_[:, 3]) - \np.maximum(anchors[:, 1], gt_box_[:, 1])inter_area = inter_w * inter_h# unionunion_area = anchors_area + gt_box_area - inter_area# iouiou = inter_area / union_areaiou = np.clip(iou, a_min=1e-10, a_max=1.0)return iou@torch.no_grad()def __call__(self, fmp_sizes, fpn_strides, targets):"""fmp_size: (List) [fmp_h, fmp_w]fpn_strides: (List) -> [8, 16, 32, ...] stride of network output.targets: (Dict) dict{'boxes': [...], 'labels': [...], 'orig_size': ...}"""assert len(fmp_sizes) == len(fpn_strides)# preparebs = len(targets)gt_objectness = [torch.zeros([bs, fmp_h, fmp_w, self.num_anchors, 1]) for (fmp_h, fmp_w) in fmp_sizes]gt_classes = [torch.zeros([bs, fmp_h, fmp_w, self.num_anchors, self.num_classes]) for (fmp_h, fmp_w) in fmp_sizes]gt_bboxes = [torch.zeros([bs, fmp_h, fmp_w, self.num_anchors, 4]) for (fmp_h, fmp_w) in fmp_sizes]# 第一层for循环遍历每一张图像for batch_index in range(bs):targets_per_image = targets[batch_index]# [N,]   N表示一个图像中有N个目标对象tgt_cls = targets_per_image["labels"].numpy()# [N, 4]tgt_box = targets_per_image['boxes'].numpy()# 第二层for循环遍历这张图像标签的每一个目标数据for gt_box, gt_label in zip(tgt_box, tgt_cls):# get a bbox coordsx1, y1, x2, y2 = gt_box.tolist()# xyxy -> cxcywhxc, yc = (x2 + x1) * 0.5, (y2 + y1) * 0.5bw, bh = x2 - x1, y2 - y1gt_box = [0, 0, bw, bh]# check targetif bw < 1. or bh < 1.:# invalid targetcontinue# 1、计算该目标框和9个先验框的IoU值# compute IoUiou = self.compute_iou(self.anchor_boxes, gt_box)iou_mask = (iou > self.iou_thresh)# 2、基于先验框的标签分配策略label_assignment_results = []# 第一种情况:所有的IoU值均低于阈值,选择IoU最大的先验框if iou_mask.sum() == 0:# We assign the anchor box with highest IoU score.iou_ind = np.argmax(iou)# 确定选择的先验框在pyramid上的level及anchor indexlevel = iou_ind // self.num_anchors              # pyramid levelanchor_idx = iou_ind - level * self.num_anchors  # anchor index# get the corresponding stridestride = fpn_strides[level]# compute the grid cell# 计算该目标框在level尺度的网格坐标xc_s = xc / strideyc_s = yc / stridegrid_x = int(xc_s)grid_y = int(yc_s)# 存下网格坐标、尺度level以及anchor_idxlabel_assignment_results.append([grid_x, grid_y, level, anchor_idx])else:# 第二种和第三种情况:至少有一个IoU值大于阈值for iou_ind, iou_m in enumerate(iou_mask):if iou_m:level = iou_ind // self.num_anchors              # pyramid levelanchor_idx = iou_ind - level * self.num_anchors  # anchor index# get the corresponding stridestride = fpn_strides[level]# compute the gride cellxc_s = xc / strideyc_s = yc / stridegrid_x = int(xc_s)grid_y = int(yc_s)label_assignment_results.append([grid_x, grid_y, level, anchor_idx])# label assignment# 获取到被标记为正样本的先验框,我们就可以为这次先验框对应的预测框制作学习标签for result in label_assignment_results:grid_x, grid_y, level, anchor_idx = resultfmp_h, fmp_w = fmp_sizes[level]if grid_x < fmp_w and grid_y < fmp_h:# objectness标签,采用0,1离散值(gt_objectness为list,存3个尺度的正样本)gt_objectness[level][batch_index, grid_y, grid_x, anchor_idx] = 1.0# classification标签,采用one-hot格式cls_ont_hot = torch.zeros(self.num_classes)cls_ont_hot[int(gt_label)] = 1.0gt_classes[level][batch_index, grid_y, grid_x, anchor_idx] = cls_ont_hot# box标签,采用目标框的坐标值gt_bboxes[level][batch_index, grid_y, grid_x, anchor_idx] = torch.as_tensor([x1, y1, x2, y2])# [B, M, C]gt_objectness = torch.cat([gt.view(bs, -1, 1) for gt in gt_objectness], dim=1).float()gt_classes = torch.cat([gt.view(bs, -1, self.num_classes) for gt in gt_classes], dim=1).float()gt_bboxes = torch.cat([gt.view(bs, -1, 4) for gt in gt_bboxes], dim=1).float()return gt_objectness, gt_classes, gt_bboxesif __name__ == '__main__':anchor_size = [[10, 13], [16, 30], [33, 23],[30, 61], [62, 45], [59, 119],[116, 90], [156, 198], [373, 326]]matcher = Yolov3Matcher(iou_thresh=0.5, num_classes=20, anchor_size=anchor_size, num_anchors=3)fmp_sizes =   [torch.Size([52, 52]), torch.Size([26, 26]), torch.Size([13, 13])]fpn_strides = [8, 16, 32]targets = [{'boxes': torch.tensor([[120.,   0., 408.,  23.],[160.,  59., 416., 256.],[172.,  24., 218., 128.],[408.,  35., 416.,  75.],[  0.,  64.,   8., 186.]]),  # bbox的坐标(xmin, ymin, xmax, ymax'labels': torch.tensor([ 6,  6, 14,  6, 19]),       # 标签'orig_size': [416, 416]                             # 图片的原始大小},{'boxes': torch.tensor([[367., 255., 416., 416.],[330., 302., 416., 416.]]),'labels': torch.tensor([14, 13]),'orig_size': [416, 416]}]gt_objectness, gt_classes, gt_bboxes = matcher(fmp_sizes=fmp_sizes, fpn_strides=fpn_strides, targets=targets)print(gt_objectness.shape)print(gt_classes.shape)print(gt_bboxes.shape)

2 损失函数的计算

  • YOLOv3损失函数计算(RT-ODLab/models/detectors/yolov3/loss.py)和之前实现的YOLOv2基本一致,不再赘述
  • 对于数据预处理、数据增强等,我们不再采用之前SSD风格的处理手段,而是选择YOLOv5的数据处理方法来训练我们的YOLOv3,我们下次再聊。

结语

  • 我们现在已经知道,在多级检测框架时候,先验框自身尺度在标签分配环节起到了重要的作用。

  • 自Faster R-CNN工作问世以来,anchor box几乎成为了大多数先进的目标检测器的标准配置之一。但是anchor box的缺陷也是十分明显的,比如以下几点:

    • 首先,anchor box的长宽比、面积和数量依赖于人工设计。纵然YOLOv2给出了基于kmeans聚类算法的设计anchor box的尺寸,但是anchor box的数量仍旧是个问题;
    • 无论多么精心设计anchor box,一旦固定下来后,就不会再被改变。模型在一个训练集上被训练之后,已设定好的anchor box尽管可能在这个数据分布上表现够好,可一旦遇到不位于该数据分布的场景时,anchor box就可能存在不能泛化到新目标的问题;
    • 另外,大量的anchor box使得预测框的数量变多,从而使得后处理阶段要处理大量的预测框,不仅加剧了算力消耗,也会拖慢模型的检测速度;
  • 但是,如果没有先验框,能否做多级检测呢?

    • 没有先验框进行多级检测,即anchor-free架构,首先要解决哪个目标框应该被来自哪个尺度的预测框学习,即多尺度标签匹配问题。

    • 在2019年,FCOS检测器被提出,其最大的特点就是彻底抛去了一直以来的anchor box,那么FCOS如何解决多尺度匹配问题呢?

      • FCOS一共使用五个特征图 P3、P4、P5、P6和P7 ,其输出步长stride分别为 8、16、32、64和128。FCOS为这每一个尺度都设定了一个尺度范围,即对于特征图 P_i ,其尺度范围是 (m_i−1,m_i) ,这五个尺度范围分别为 (0,64) 、(64,128)、(128,256)、(256,512),以及(512,∞)。

        首先,我们去遍历特征图Pi上的每一个anchor,假设每一个anchor的坐标为 (xs_a+0.5,ys_a+0.5) ,其中(xs_a,ys_a)为anchor的左上角点坐标,也就是我们以前熟悉的网格左上角坐标的概念,但我们又为之加上了0.5亚像素坐标,即网格的中心点。我们求出特征图P_i上的anchor在输入图像上的坐标 (x_a,y_a) ,计算公式如下所示:
        x a = x s a ∗ s + s / 2 y a = y s a ∗ s + s / 2 x_a=xs_a∗s+s/2 \\ y_a=ys_a∗s+s/2 xa=xsas+s/2ya=ysas+s/2
        然后,我们求出处在边界框内的每一个anchor到边界框的四条边的距离:
        l ∗ = x a − x 1 t ∗ = y a − y 1 r ∗ = x 2 − x a b ∗ = y 2 − y a l^∗=x_a−x_1 \\ t^∗=y_a−y_1 \\ r^∗=x_2−x_a \\ b^∗=y_2−y_a l=xax1t=yay1r=x2xab=y2ya
        我们取其中的最大值 m=max(l∗,t∗,r∗,b∗) ,如果 m 满足 m_i−1<m<m_i ,则该anchor将被视为正样本,去学习自己到目标框四条边的距离。反之则为负样本。

        若是目标框的尺寸偏小,那它内部的anchor就会更多地落在较小的范围内,比如: (0,64),反之,则会更多地落在较大的范围内,如: (256,512) 。

        换言之,FCOS设置的五个范围本质上是一种和目标自身大小相关的尺度范围,是基于一种 小的目标框更应该让输出步长小的也就是更大的特征图去学习,大的目标框则应该让输出步长更大的特征图去学习的直观理解。

      • 但这个尺度还需要人工设计,没有摆脱人工先验的超参。

    • 旷视科技在YOLOX种提出了SimOTA,摆脱了人工先验的超参,实现了真正意义的anchor-free,具体细节以后再讲。

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

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

相关文章

将应用的log4j换成logback

将应用的log4j换成logback 考虑到log4j很久不更新、性能相对弱&#xff0c;以及一些项目本身的原因&#xff0c;经过较为谨慎的考虑&#xff0c;决定改用logback。迁移还是比较顺利的&#xff0c;花了1个小时左右就搞定了&#xff0c;做个简单的笔记。 (1) 首先去掉所有log4j…

MFC串行化的应用实例

之前写过一篇MFC串行化的博文;下面看一个具体例子; 新建一个单文档应用程序;在最后一步,把View类的基类改为CFormView; 然后在资源面板编辑自己的字段; 然后到doc类的头文件添加对应变量, public:CString name;int age;CString sex;CString dept;CString zhiwu;CStrin…

C#中的装箱和拆箱操作详解

在C#中&#xff0c;“装箱”&#xff08;Boxing&#xff09;和"拆箱"&#xff08;Unboxing&#xff09;是类型转换的过程&#xff0c;特别是在值类型和引用类型之间的转换。 1、 装箱&#xff08;Boxing&#xff09; 装箱是指将一个值类型&#xff08;例如整数、浮…

蓝桥杯2024/1/31----第十届省赛题笔记

题目要求&#xff1a; 1、 基本要求 1.1 使用大赛组委会提供的国信长天单片机竞赛实训平台&#xff0c;完成本试题的程序设计 与调试。 1.2 选手在程序设计与调试过程中&#xff0c;可参考组委会提供的“资源数据包”。 1.3 请注意&#xff1a; 程序编写、调试完成后选手…

JAVA Web 学习(二)ServLet

二、动态web 资源开发技术——Servlet Servlet&#xff08;小服务程序&#xff09;是一个与协议无关的、跨平台的Web组件&#xff0c;由Servlet容器所管理。运行在服务器端&#xff0c;可以动态地扩展服务器的功能&#xff0c;并采用“请求一响应”模式提供Web服务。 Servlet的…

将java对象转换为json字符串的几种常用方法

目录 1.关于json 2.实现方式 1.Gson 2.jackson 3.fastjson 3.与前端的联系 1.关于json JSON是一种轻量级的数据交换格式。它由Douglas Crockford在2001年创造。JSON的全称是JavaScript Object Notation&#xff0c;它是一种文本格式&#xff0c;可以轻松地在各种平台之间传…

一文详解docker compose

文章目录 1、前言2、Compose 简介3、compose的安装和卸载3.1、安装3.2、卸载3.3、使用 4、yml 配置指令参考5、Compose 命令说明5.1、命令对象与格式5.2、命令选项5.3、命令使用详细说明 6、compose使用案例6.1、准备6.2、Dockerfile 文件6.3、docker-compose.yml6.4、使用 Com…

C#学习笔记_类(Class)

类的定义 类的定义是以关键字 class 开始&#xff0c;后跟类的名称。类的主体&#xff0c;包含在一对花括号内。 语法格式如下&#xff1a; 访问标识符 class 类名 {//变量定义访问标识符 数据类型 变量名;访问标识符 数据类型 变量名;访问标识符 数据类型 变量名;......//方…

基于低代码的管理系统模板库的设计与实现

近年来&#xff0c;随着企业管理需求的不断增长&#xff0c;传统的软件开发方式显得过于繁琐&#xff0c;低代码开发平台应运而生。本文基于低代码开发理念&#xff0c;探讨了管理系统模板库的设计与实现&#xff0c;旨在提高开发效率、降低系统维护成本&#xff0c;并使管理系…

EXCEL VBA调用百度API翻译

EXCEL VBA调用百度API翻译 Public Function translateZh_En(ByVal str As String) As String调用百度翻译API&#xff0c;将指定内容由中文翻译为英文Dim par As StringDim data() As Stringpar generateReqStr(str, "zh", "en")data() translateJson(g…

正则表达式与文本三剑客

目录 一、正则表达式 1. 定义 2. 字符匹配 3. 重复限定符 4. 位置锚点 5. 分组和引用 6. 扩展正则表达式 二、文本三剑客 1. grep 1.1 定义 1.2 语法 1.3 选项 1.4 示例 2. sed 2.1 定义 2.2 通式 2.3 选项 2.4 脚本格式&#xff08;脚本语法&#xff09; 2.…

【Unity3D小功能】Unity3D中Text使用超链接并绑定点击事件

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 在开发中遇到了要给Text加超链接的需求&#xff0c;研究了实现…

什么样的评论更容易得到别人的关注

要发表吸引人的评论&#xff0c;可以注意这些个方面&#xff1a; 合适的软件&#xff1a;用DT浏览器的笔记本写文本&#xff0c;保存为图片&#xff0c;用图片的方式评论更容易得到别人的关注。 特别的观点&#xff1a;发表与众不同的观点&#xff0c;或者从不同的角度看待问…

USACO 1月比赛 铜组题解

比赛链接&#xff1a;http://usaco.org/ 第一题&#xff1a;MAJORITY OPINION 标签&#xff1a;思维、模拟题意&#xff1a;给定一个长度为 n n n的序列 a a a&#xff0c;操作&#xff1a;若区间 [ i , j ] [i,j] [i,j]内某个数字 k k k出现的次数 大于区间长度的一半&#…

已解决解决java.util.concurrent.BrokenBarrierException异常的正确解决方法,亲测有效!!!

已解决解决java.util.concurrent.BrokenBarrierException异常的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 问题分析 报错原因 解决思路 解决方法 总结 在并发编程中&#xff0c;CyclicBarrier是Java提供的一个同步辅助工具类&#xff…

WiFi基础知识介绍(超详细)

1.WiFi专业名词概念 AP(Access Point):无线接入点&#xff1a;这个概念特别广&#xff0c;在这里&#xff0c;用大白话说&#xff0c;你可以把CC3200当做一个无线路由器&#xff0c;这个路由器的特点不能插入网线&#xff0c;没有接入Internet&#xff0c;只能等待其他设备的链…

C语言中的数组操作技巧:提升程序的效率和可读性

1. 概念 数组是C语言中常见且重要的数据结构&#xff0c;在许多应用中都被广泛使用。合理地处理数组操作可以提高程序的效率和可读性。本文将介绍C语言中常用的数组操作方法和技巧&#xff0c;帮助读者优化数组操作并提升程序效果。 2.常用的数组操作方法 2.1 数组的初始化 C…

canvas自定义扩展方法:文字自动换行

查看专栏目录 canvas实例应用100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

hyperf 二十四 模型缓存

教程&#xff1a;Hyperf 一 安装及配置 1.1 安装 目前仅支持redis。 composer require hyperf/model-cache 1.2 配置 配置位置&#xff1a;config/autoload/databases.php 配置类型默认值备注handlerstringHyperf\ModelCache\Handler\RedisHandler::class无cache_keystri…

python时间格式数据处理。

由于编码和格式问题&#xff0c;有时候从read_csv或者read_excel读取到的时间类型是不确定的。 1.字符串转datetime.datetime时间格式 如果是字符串string类型&#xff0c;则可以使用&#xff1a; df[PDATE] pd.to_datetime(df[PDATE]).dt.date 转换为date格式&#xff0c…