经典目标检测YOLO系列(二)YOLOV2的复现(2)正样本的匹配、损失函数的实现及模型训练

经典目标检测YOLO系列(二)YOLOV2的复现(2)正样本的匹配、损失函数的实现及模型训练

我们在之前实现YOLOv1的基础上,加入了先验框机制,快速的实现了YOLOv2的网络架构,并且实现了前向推理过程。

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

如前所述,我们使用基于先验框的正样本匹配策略。

1 正样本匹配策略

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

  • 由于每个网格只输出一个边界框,因此在YOLOv1中的正样本匹配策略很简单,目标框的中心点落在哪个网格,这个网格(左上角点)就是正样本。
  • 但是,我们现在引入了先验框机制,每个网格会输出5个预测框。那么目标框的中心点所在的每一个网格,我们都需要确定这5个预测框中,哪些是正样本,哪些是负样本。
  • 既然我们已经有了具有边界框尺寸信息的先验框,那么我们可以基于先验框来筛选正样本。

假设一个含有目标框中心的网格上的5个先验框分别为A、B、C、D、E,那么需要计算这5个先验框与目标框O的IoU值,分别为:IoU_A、IoU_B、IoU_C、IoU_D、IoU_E,然后设定一个阈值iou_thresh:

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

这种正样本匹配策略,似乎保证了每个目标都会至少匹配上一个正样本,但其实存在漏洞。假如,有2个目标的中心点都落到了同一个目标框,可能会导致原本属于目标A的先验框后来又分配给目标B

  • 在YOLOv1中,2个目标的中心点都落到了同一个目标框,网络就只能学习一个。
  • 在YOLOv2中,虽然每个网格会输出多个预测框,但是在制作正样本时候,也会存在刚才说的语义歧义现象,会使得某些目标匹配不到正样本,其信息也就不会被网络学习到,不过我们现在不做处理。

在这里插入图片描述

1.2 代码实现

1.2.1 正样本匹配

pytorch读取VOC数据集:

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

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

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

[
    {
        'boxes':     tensor([[ 29., 230., 148., 321.]]),  # bbox的坐标(xmin, ymin, xmax, ymax)
        'labels':    tensor([18.]),                       # 标签
        'orig_size': [281, 500]                           # 图片的原始大小
    }, 
    {
        'boxes':      tensor([[  0.,  79., 416., 362.]]), 
        'labels':     tensor([1.]),
        'orig_size': [375, 500]
    }
]

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

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

1.2.2 具体代码实现

# RT-ODLab/models/detectors/yolov2/matcher.py

import torch
import numpy as np


class Yolov2Matcher(object):
    def __init__(self, iou_thresh, num_classes, anchor_size):
        self.num_classes = num_classes
        self.iou_thresh = iou_thresh
        # anchor box
        self.num_anchors = len(anchor_size)
        self.anchor_size = anchor_size
        self.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):
        """
            函数功能: 计算目标框和5个先验框的IoU值
            anchor_boxes : ndarray -> [KA, 4] (cx, cy, bw, bh).
            gt_box : ndarray -> [1, 4] (cx, cy, bw, bh).

            返回值: iou变量,类型为ndarray类型,shape为[5,], iou[i]就表示该目标框和第i个先验框的IoU值

        """
        # 1、计算5个anchor_box的面积
        # anchors: [KA, 4]
        anchors = np.zeros_like(anchor_boxes)
        anchors[..., :2] = anchor_boxes[..., :2] - anchor_boxes[..., 2:] * 0.5  # x1y1
        anchors[..., 2:] = anchor_boxes[..., :2] + anchor_boxes[..., 2:] * 0.5  # x2y2
        anchors_area = anchor_boxes[..., 2] * anchor_boxes[..., 3]
        # 2、gt_box复制5份,计算5个相同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  # x1y1
        gt_box_[..., 2:] = gt_box[..., :2] + gt_box[..., 2:] * 0.5  # x2y2
        gt_box_area = np.prod(gt_box[..., 2:] - gt_box[..., :2], axis=1)
        # 3、计算计算目标框和5个先验框的IoU值
        # intersection  交集
        inter_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
        
        # union
        union_area = anchors_area + gt_box_area - inter_area

        # iou
        iou = inter_area / union_area
        iou = np.clip(iou, a_min=1e-10, a_max=1.0)
        
        return iou


    @torch.no_grad()
    def __call__(self, fmp_size, stride, targets):
        """
            img_size: (Int) input image size
            stride: (Int) -> stride of YOLOv1 output.
            targets: (Dict) dict{'boxes': [...], 
                                 'labels': [...], 
                                 'orig_size': ...}
        """
        # prepare
        bs = len(targets)
        fmp_h, fmp_w = fmp_size
        gt_objectness = np.zeros([bs, fmp_h, fmp_w, self.num_anchors, 1]) 
        gt_classes = np.zeros([bs, fmp_h, fmp_w, self.num_anchors, self.num_classes]) 
        gt_bboxes = np.zeros([bs, fmp_h, fmp_w, self.num_anchors, 4])

        # 第一层for循环遍历每一张图像的标签
        for batch_index in range(bs):
            # targets_per_image是python的Dict类型
            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):
                x1, y1, x2, y2 = gt_box
                # xyxy -> cxcywh
                xc, yc = (x2 + x1) * 0.5, (y2 + y1) * 0.5
                bw, bh = x2 - x1, y2 - y1
                gt_box = [0, 0, bw, bh]

                # check
                if bw < 1. or bh < 1.:
                    continue    

                # 1、计算该目标框和5个先验框的IoU值
                iou = 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)
                    anchor_idx = iou_ind
                    # compute the grid cell
                    xc_s = xc / stride
                    yc_s = yc / stride
                    grid_x = int(xc_s)
                    grid_y = int(yc_s)

                    label_assignment_results.append([grid_x, grid_y, anchor_idx])
                else:
                    # 第二种和第三种情况:至少有一个IoU值大于阈值
                    for iou_ind, iou_m in enumerate(iou_mask):
                        if iou_m:
                            anchor_idx = iou_ind
                            # compute the gride cell
                            xc_s = xc / stride
                            yc_s = yc / stride
                            grid_x = int(xc_s)
                            grid_y = int(yc_s)

                            label_assignment_results.append([grid_x, grid_y, anchor_idx])

                # label assignment
                # 获取到被标记为正样本的先验框,我们就可以为这次先验框对应的预测框制作学习标签
                for result in label_assignment_results:
                    grid_x, grid_y, anchor_idx = result
                    if grid_x < fmp_w and grid_y < fmp_h:
                        # objectness标签,采用0,1离散值
                        gt_objectness[batch_index, grid_y, grid_x, anchor_idx] = 1.0
                        # classification标签,采用one-hot格式
                        cls_ont_hot = np.zeros(self.num_classes)
                        cls_ont_hot[int(gt_label)] = 1.0
                        gt_classes[batch_index, grid_y, grid_x, anchor_idx] = cls_ont_hot
                        # box标签,采用目标框的坐标值
                        gt_bboxes[batch_index, grid_y, grid_x, anchor_idx] = np.array([x1, y1, x2, y2])

        # [B, H, W, A, C] -> [B, HWA, C]
        gt_objectness = gt_objectness.reshape(bs, -1, 1)
        gt_classes = gt_classes.reshape(bs, -1, self.num_classes)
        gt_bboxes = gt_bboxes.reshape(bs, -1, 4)

        # to tensor
        gt_objectness = torch.from_numpy(gt_objectness).float()
        gt_classes = torch.from_numpy(gt_classes).float()
        gt_bboxes = torch.from_numpy(gt_bboxes).float()

        return gt_objectness, gt_classes, gt_bboxes



if __name__ == '__main__':
    anchor_size  = [[17, 25], [55, 75], [92, 206], [202, 21], [289, 311]]
    matcher = Yolov2Matcher(iou_thresh=0.5, num_classes=20, anchor_size=anchor_size)
    targets = [
    {
        'boxes':     torch.tensor([[ 29., 230., 148., 321.]]),  # bbox的坐标(xmin, ymin, xmax, ymax)
        'labels':    torch.tensor([18.]),                       # 标签
        'orig_size': [281, 500]                                 # 图片的原始大小
    },
    {
        'boxes':      torch.tensor([[  0.,  79., 416., 362.]]),
        'labels':     torch.tensor([1.]),
        'orig_size': [375, 500]
    }
]
    gt_objectness, gt_classes, gt_bboxes = matcher(fmp_size=(13, 13),stride=32, targets=targets )
    print(gt_objectness.shape)
    print(gt_classes.shape)
    print(gt_bboxes.shape)
  • 最终这段代码返回了gt_objectness, gt_classes, gt_bboxes三个Tensor类型的变量:
    • gt_objectness包含一系列的0和1,标记了哪些预测框是正样本,哪些预测框是负样本
    • gt_classes包含一系列的one-hot格式的类别标签
    • gt_bboxes包含的是正样本要学习的边界框的位置参数
  • 在上述代码实现中,在计算IoU时候,我们将目标框的中心点坐标和先验框的中心点坐标都设置为0,这是因为一个目标框在做匹配时候,仅仅考虑到目标框中心点所在的网格中的5个先验框,周围的网格都不进行考虑
  • 在SSD以及Faster R-CNN中,每一个目标框都是和全局的先验框去计算IoU,这些算法都会考虑目标框的中心点坐标和先验框的中心点坐标。因此,其每一个目标框匹配上的先验框不仅来自中心点所在的网格,也会来自周围的网格。这是YOLO和其他工作一个重要差别所在,YOLO这种只考虑中心点的做法,处理起来更加简便、更易学习。

2 损失函数的计算、YOLOv2的训练

2.1 损失函数的计算

  • YOLOv2损失函数计算(RT-ODLab/models/detectors/yolov2/loss.py)和之前实现的YOLOv1基本一致,不再赘述
  • 我们实现的YOLOv2和之前实现的YOLOv1相比,仅仅多了先验框以及由此带来的正样本匹配上的一些细节上的差别。

2.2 YOLOv2的训练

  • 完成了YOLOv2的网络搭建,标签匹配以及损失函数的计算,就可以进行训练了

  • 数据读取、数据预处理及数据增强操作,和之前实现的YOLOv1一致,不再赘述

  • YOLOv1和YOLOv2都在同一个项目代码中,数据代码、训练代码及测试代码均一致,我们只需要修改训练脚本即可

    nohup python -u train.py --cuda \
             -d voc                 \
             -m yolov2              \
             -bs 16                 \
             -size 640              \
             --wp_epoch 3           \
             --max_epoch 150        \
             --eval_epoch 10        \
             --no_aug_epoch 10      \
             --ema                  \
             --fp16                 \
             --multi_scale          \
             --num_workers 8 1>./logs/yolo_v2_train_log.txt 2>./logs/yolo_v2_warning_log.txt &
    

相关参数讲解可以参考YOLOv1:

经典目标检测YOLO系列(一)复现YOLOV1(5)模型的训练及验证

2.3 可视化检测结果、计算mAP指标

  • 训练结束后,模型默认保存在weights/voc/yolov2/文件夹下,名为yolov2_voc_best.pth,保存了训练阶段在测试集上mAP指标最高的模型。

  • 运行项目中所提供的eval.py文件可以验证模型的性能,具体命令如下行所示

  • 可以给定不同的图像尺寸来测试实现的YOLOv1在不同输入尺寸下的性能

    python eval.py \
    --cuda -d voc \
    --root path/to/voc -m yolov2 \
    --weight path/to/yolov2_voc_best.pth \
    -size 416
    
  • 也可以可视化训练好的模型

    python test.py \
    --cuda -d voc \
    --root path/to/voc -m yolov2 
    --weight path/to/yolov2_voc_best.pth \
    -size 416 -vt 0.3 \
    --show
    
    # -size表示输入图像的最大边尺寸
    # -vt是可视化的置信度阈值,只有高于此值的才会被可视化出来
    # --show表示展示检测结果的可视化图片
    

2.4 训练结果

《YOLO目标检测》作者训练好的模型,在VOC2007测试集测试指标如下:

从表格中可以看到,实现的YOLOv2达到了官方YOLOv2的性能。

模型输入尺寸mAP(%)
YOLOv2*(官方)41676.8
YOLOv2*(官方)48077.8
YOLOv2*(官方)54478.6
YOLOv241676.8
YOLOv248078.4
YOLOv254479.6
YOLOv264079.8

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

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

相关文章

VUE---自定义指令

自定义指令&#xff1a;自己定义的指令&#xff0c;可以封装一些dom操作&#xff0c;扩展额外功能。可分为全局注册与 局部注册。 全局注册&#xff08;main.js中注册&#xff09;&#xff1a; Vue.directive(指令名称,{ bind(ele,binding) {}, // 只执…

【办公类-21-01】20240117育婴员操作题word合并1.0

背景需求&#xff1a; 最近学校组织老师们学习“育婴员”高级&#xff0c;每周学习2题操作&#xff0c;所以我是把每个学习内容单独做在一个word文件里 上周8套保健操作学完了&#xff0c;需要整理&#xff0c;并将8份Word文件合并 第一步&#xff1a;doc装docx 合并时程序报…

CentOS搭建DNS服务器

服务器规划 DNS服务器IP为&#xff1a;172.16.32.253 需要自定义域名解析 172.16.32.253 dns.zhangsan.com 172.16.32.128 test1.zhangsan.com 172.16.32.129 test2.zhangsan.com 172.16.32.130 www.zhangsan.com 1. 服务器初始化 [rootlocalhost ~]# hostnamectl set-hostnam…

Python OpenCV 影像处理:影像二值化

► 前言 本篇将介绍使用OpenCV Python对于图像上的二值化操作&#xff0c;二值化主要用途包括图像分割、物体侦测、文字识别等。这种转换可以帮助检测图像中的物体或特定特征&#xff0c;并提取有用的信息。透过程式码的说明&#xff0c;让各位了解OpenCV Python于图像处理上的…

Python数据分析(1)Matrix Manipulation

主要根据的是这学期修的一门data science and analysis课程里的lab&#xff0c;自己做完lab之后会反思一下自己学到的内容吧。 然后这周lab的话主要是用numpy来处理矩阵&#xff08;毕竟numpy可以有效地处理数组&#xff09;。 创建矩阵 np.zeros(k), np.ones(k) #一维矩阵 …

uniapp h5 生成 ubuntu桌面程序 并运行方法

uniapp h5 生成 ubuntu桌面程序 并运行方法,在window环境下开发&#xff0c;发布到ubuntu桌面&#xff0c;并运行 1、安装Nodejs 安装包官方下载地址&#xff1a;https://www.nodejs.com.cn/ 安装完后cmd&#xff0c;如图&#xff0c;即安装成功 2、通过Nodejs安装 electron…

适合初学者的机器学习开源项目合集(已加入Github加速计划)

目录 开源项目合集[>> 机器学习路线图&#xff1a;mrdbourke/machine-learning-roadmap](https://gitcode.com/mrdbourke/machine-learning-roadmap)[>> 机器学习资源的汇总&#xff1a;johnmyleswhite/ML_for_Hackers](https://gitcode.com/johnmyleswhite/ML_for…

golang面试题大全

go基础类 1、与其他语言相比&#xff0c;使用 Go 有什么好处&#xff1f; 与其他作为学术实验开始的语言不同&#xff0c; Go 代码的设计是务实的。每个功能和语法决策都旨在让程序员的生活更轻松。Golang 针对并发进行了优化&#xff0c;并且在规模上运行良好。由于单一的标…

2024 前端高频面试题之 HTML/CSS 篇

【前言】随着市场的逐渐恶劣&#xff0c;通过总结面试题的方式来帮助更多的coder&#xff0c;也是记录自己的学习过程&#xff0c;温故而知新。欢迎各位同胞大大点评补充~ 前端面试题之 HTML/CSS 篇 1、HTML 语义化&#xff1f;2、块级元素&内联样式3、盒子模型的理解&…

提升开发效率,Fiddler Everywhere for Mac助您解决网络调试难题

在现代软件开发中&#xff0c;网络调试是一个不可或缺的环节。无论是前端开发还是后端开发&#xff0c;我们经常需要对网络请求进行监控和调试&#xff0c;以便及时发现并解决问题。而Fiddler Everywhere for Mac作为一款强大的网络调试工具&#xff0c;能够帮助开发者提升工作…

Dubbo使用详解

简介 Dubbo是一个高性能、轻量级的开源Java RPC框架&#xff0c;由阿里巴巴公司开发并开源。它提供了三大核心能力&#xff1a;面向接口的远程方法调用&#xff0c;智能容错和负载均衡&#xff0c;以及服务自动注册和发现。Dubbo使得应用可通过高性能的 RPC 实现服务的输出和输…

CentOS 8.5 安装图解

特特特别的说明 CentOS发行版已经不再适合应用于生产环境&#xff0c;客观条件不得不用的话&#xff0c;优选7.9版本&#xff0c;8.5版本次之&#xff0c;最次6.10版本&#xff08;比如说Oracle 11GR2就建议在6版本上部署&#xff09;&#xff01; 引导和开始安装 选择倒计时结…

深度解析 Compose 的 Modifier 原理 -- Modifier.composed()、ComposedModifier

" Jetpack Compose - - Modifier 系列文章 " &#x1f4d1; 《 深入解析 Compose 的 Modifier 原理 - - Modifier、CombinedModifier 》 &#x1f4d1; 《 深度解析 Compose 的 Modifier 原理 - - Modifier.composed()、ComposedModifier 》 &#x1f4d1; 《 深入解…

强化学习(二)多臂老虎机 “Multi-armed Bandits”——1

将强化学习与机器学习、深度学习区分开的最重要的特征为&#xff1a;它通过训练中信息来评估所采取的动作&#xff0c;而不是给出正确的动作进行指导&#xff0c;这极大地促进了寻找更优动作的需求。 1、多臂老虎机&#xff08;Multi-armed Bandits&#xff09;问题 赌场的老虎…

springBoot如何动态切换数据源

项目背景&#xff1a;最近公司中需要搭建mysql的主从&#xff0c;想着在spring中集成多数据源。mybatisplus提供的有插件用DS注解就能够实现&#xff0c;但是这种在mysql服务宕机的情况下不能够进行自动切换&#xff0c;于是就想着用aop自定义注解的方式来实现 项目实现效果&a…

el-tree获取当前选中节点及其所有父节点的id(包含半选中父节点的id)

如下图,我们现在全勾中的有表格管理及其下的子级,而半勾中的有工作台和任务管理及其子级 现在点击保存按钮后,需要将勾中的节点id及该节点对应的父节点,祖先节点的id(包含半选中父节点的id)也都一并传给后端,那这个例子里就应该共传入9个id,我们可以直接将getCheckedK…

架构篇07-复杂度来源:低成本、安全、规模

文章目录 低成本安全规模小结关于复杂度来源,前面的专栏已经讲了高性能、高可用和可扩展性,今天我们来聊聊复杂度另外三个来源低成本、安全和规模。 低成本 当我们的架构方案只涉及几台或者十几台服务器时,一般情况下成本并不是我们重点关注的目标,但如果架构方案涉及几百…

【Docker】在centos中安装nginx

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是平顶山大师&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的博客专栏《【Docker】安装nginx》。&#x1f3af;&#…

Macos flatter(用于快速LLL)本地编译安装(解决安装过程各种疑难杂症)

flatter是一个开源项目&#xff0c;能大大提高LLL的速度&#xff0c;项目提供的安装文档适用于Ubuntu&#xff0c;但是在macos上安装&#xff0c;总会遇到各种各样的问题&#xff0c;这里记录下所踩坑&#xff0c;帮助大家快速在macos上安装flatter。 文章目录 1.安装依赖库&am…

【linux进程间通信(一)】匿名管道和命名管道

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; 进程间通信 1. 前言2. 进程间…