经典目标检测YOLO系列(一)复现YOLOV1(2)反解边界框及后处理

经典目标检测YOLO系列(一)复现YOLOV1(2)反解边界框及后处理

在上个博客,我们提出了新的YOLOV1架构,这次我们解决前向推理过程中的两个问题。
经典目标检测YOLO系列(一)YOLOV1的复现(1)总体架构

1、边界框的计算

1.1 反解边界框公式的改变

1.1.1 原版YOLOV1的中心点量化误差的计算公式

如下图,目标狗的中心点所在网格为黄色部分,中心点为图中红点。

在原版的YOLOV1中,我们发现中心点【红点】距离黄色网格左上角处【坐标为(1, 4)】是有差距的,这其实就是由于降采样带来的量化误差,因此,我们只要获得了这个量化误差,就能获得中心点的准确坐标了。

在这里插入图片描述

YOLOv1原版中计算这个量化误差的过程如下:

在这里插入图片描述

在上图中计算出grid_x,grid_y的坐标,我们可以用矩阵进行表示,我们不妨称这个矩阵为G,矩阵的值如下

[
  [0., 0.],[1., 0.],[2., 0.],[3., 0.],[4., 0.],[5., 0.],[6., 0.],
  [0., 1.],[1., 1.],[2., 1.],[3., 1.],[4., 1.],[5., 1.],[6., 1.],
  [0., 2.],[1., 2.],[2., 2.],[3., 2.],[4., 2.],[5., 2.],[6., 2.],
  [0., 3.],[1., 3.],[2., 3.],[3., 3.],[4., 3.],[5., 3.],[6., 3.],
  [0., 4.],[1., 4.],[2., 4.],[3., 4.],[4., 4.],[5., 4.],[6., 4.],
  [0., 5.],[1., 5.],[2., 5.],[3., 5.],[4., 5.],[5., 5.],[6., 5.],
  [0., 6.],[1., 6.],[2., 6.],[3., 6.],[4., 6.],[5., 6.],[6., 6.]
]

计算出来的量化误差,其实就是中心点【红点】距离【黄色网格左上角点】的X轴和Y轴方向的偏移量。

在这里插入图片描述

1.1.2 原版YOLOV1反解边界框的公式

YOLOv1原版中根据预测值反解边界框的过程如下:
c e n t e r x = ( g r i d x + c x ) × s t r i d e c e n t e r y = ( g r i d y + c y ) × s t r i d e w = w p r e d × w i m a g e h = h p r e d × h i m a g e center_x = (grid_x + c_x)×stride \\ center_y = (grid_y + c_y)×stride \\ w = w_{pred} × w_{image} \\ h = h_{pred} × h_{image} centerx=(gridx+cx)×stridecentery=(gridy+cy)×stridew=wpred×wimageh=hpred×himage

1.1.3 改进YOLOV1

在原版的YOLOv1中,bbox预测主要包括目标中心点的偏移量 cx,cy 和归一化的边界框的宽高 w,h ,但是不论是哪个量,原版的YOLOv1均使用线性函数来输出,未加任何约束限制,很明显会有以下两点问题:

  • 由于偏移量cx,cy是介于01范围内的数,因此,其本身就是有上下界的,而线性输出并没有上下界,这就容易导致在学习的初期,网络可能预测的值非常大,导致bbox分支学习不稳定。

  • 边界框的宽高显然是个非负数,而线性输出不能保证这一点,这也可能造成训练过程中的不稳定,一些输出一些不合理的数值(比如负数)。

因此对于这两个问题,我们进行改进:

  • 第一个问题:假设模型的输出为 tx,ty ,我们使用sigmoid函数将其映射到0~1的范围内,保证网络的输出值是合理的,使得训练更加稳定。

  • 第二个问题:采用log-exp方法来处理。

    • YOLOv1所要预测的不是归一化的边界框宽高,而是经过log函数压缩后的宽高。
    • 由于log函数的指数级的压缩特性,在一定程度上可以拉近大目标和小目标之间的尺寸量级,因此,对于平衡不同尺度的目标的检测问题还能起到一定的缓解作用。
    • 为了更好地学习这一标签,会将目标框的坐标先映射到网格的尺度上:ws=w/stride,hs=h/stride ,然后再做log处理

    t w = l o g ( w s ) t h = l o g ( h s ) t_w = log(w_s) \\ t_h = log(h_s) tw=log(ws)th=log(hs)

    • 在推理阶段,使用exp函数即可将预测恢复到正常的尺度上。
      w = e ( t w ) ∗ s t r i d e h = e ( t h ) ∗ s t r i d e w = e^{(t_w)}*stride \\ h = e^{(t_h)}*stride w=e(tw)strideh=e(th)stride

因此,改进后根据预测值反解边界框的公式如下:
c e n t e r x = ( g r i d x + c x ) × s t r i d e c e n t e r y = ( g r i d y + c y ) × s t r i d e w = e ( t w ) ∗ s t r i d e h = e ( t h ) ∗ s t r i d e center_x = (grid_x + c_x)×stride \\ center_y = (grid_y + c_y)×stride \\ w = e^{(t_w)}*stride \\ h = e^{(t_h)}*stride centerx=(gridx+cx)×stridecentery=(gridy+cy)×stridew=e(tw)strideh=e(th)stride

1.2 反解边界框代码实现

前向推理过程中,我们通过yoloV1网络得到置信度、分类以及回归的预测值。然后,对其进行一些调整,方便后续处理。

    # RT-ODLab/models/detectors/yolov1/yolov1.py  
    @torch.no_grad()
    def inference(self, x):
        # 测试阶段的前向推理代码

        # 主干网络
        feat = self.backbone(x)

        # 颈部网络
        feat = self.neck(feat)

        # 检测头
        cls_feat, reg_feat = self.head(feat)

        # 预测层
        obj_pred = self.obj_pred(cls_feat)
        cls_pred = self.cls_pred(cls_feat)
        reg_pred = self.reg_pred(reg_feat)
        fmp_size = obj_pred.shape[-2:]

        # 对 pred 的size做一些view调整,便于后续的处理
        # [B, C, H, W] -> [B, H, W, C] -> [B, H*W, C]
        obj_pred = obj_pred.permute(0, 2, 3, 1).contiguous().flatten(1, 2)
        cls_pred = cls_pred.permute(0, 2, 3, 1).contiguous().flatten(1, 2)
        reg_pred = reg_pred.permute(0, 2, 3, 1).contiguous().flatten(1, 2)

        # 测试时,笔者默认batch是1,
        # 因此,我们不需要用batch这个维度,用[0]将其取走。
        obj_pred = obj_pred[0]       # [H*W, 1]
        cls_pred = cls_pred[0]       # [H*W, NC]
        reg_pred = reg_pred[0]       # [H*W, 4]

        # 每个边界框的得分
        scores = torch.sqrt(obj_pred.sigmoid() * cls_pred.sigmoid())
        
        # 解算边界框, 并归一化边界框: [H*W, 4]
        bboxes = self.decode_boxes(reg_pred, fmp_size)
        
        ......

然后,我们通过回归参数,反解边界框。要想反解边界框,首先,我们需要一个由grid_x,grid_y组成的矩阵G。

    # RT-ODLab/models/detectors/yolov1/yolov1.py  
    def create_grid(self, fmp_size):
        """ 
            用于生成G矩阵,其中每个元素都是特征图上的像素坐标。
        """
        # 特征图的宽和高
        ws, hs = fmp_size

        # 生成网格的x坐标和y坐标
        grid_y, grid_x = torch.meshgrid([torch.arange(hs), torch.arange(ws)])

        # 将xy两部分的坐标拼起来:[H, W, 2]
        grid_xy = torch.stack([grid_x, grid_y], dim=-1).float()

        # [H, W, 2] -> [HW, 2] -> [HW, 2]
        grid_xy = grid_xy.view(-1, 2).to(self.device)
        
        return grid_xy

不了解torch.meshgrid()函数的可以参考:

np.meshgrid()和torch.meshgrid()函数解析

然后,我们按照改进后的公式,得到边界框中心坐标以及宽高,最后转换为(xmin,ymin,xmax,ymax)的格式。

    # RT-ODLab/models/detectors/yolov1/yolov1.py  
    def decode_boxes(self, pred, fmp_size):
        """
            将txtytwth转换为常用的x1y1x2y2形式。

            pred:回归预测参数
            fmp_size:特征图宽和高
        """
        # 生成网格坐标矩阵
        grid_cell = self.create_grid(fmp_size)

        # 计算预测边界框的中心点坐标和宽高
        pred_ctr = (torch.sigmoid(pred[..., :2]) + grid_cell) * self.stride
        pred_wh = torch.exp(pred[..., 2:]) * self.stride

        # 将所有bbox的中心带你坐标和宽高换算成x1y1x2y2形式
        pred_x1y1 = pred_ctr - pred_wh * 0.5
        pred_x2y2 = pred_ctr + pred_wh * 0.5
        pred_box = torch.cat([pred_x1y1, pred_x2y2], dim=-1)

        return pred_box

2、后处理

反解边界框后,我们会遇到两个问题:

  • 一些边界框的score过低,我们需要剔除。
  • 边界框有很多冗余,即多个box检测到了同一个物体,而我们对于每一个物体只需要一个框就够了。因此,我们有必要去剔除掉多余的结果(通过非极大值抑制nms)。

2.1 非极大值抑制

非极大值抑制的步骤:

  1. 首先挑选出得分score最高的框;

  2. 依次计算其他框与这个得分最高的框的 IoU ,超过给定 IoU 阈值的框舍掉。

  3. 对每一类别都进行以上的操作,直到无框可剔除为止。

非极大值抑制的python实现:

def nms(bboxes, scores):
    # 1、将xmin,ymin,xmax,ymax拿出
    xmin = bboxes[:, 0]
    ymin = bboxes[:, 1]
    xmax = bboxes[:, 2]
    ymax = bboxes[:, 3]

    # 2、置信度从大到小的下标
    order = scores.argsort()[::-1]
    print(order)

    # 3、每个bbox的面积
    area = (ymax - ymin) * (xmax - xmin)
    print(area)
    keep = [] # 保存框的索引
    while order.size > 0:
        i = order[0]
        keep.append(i)

        # 4、求当前置信度最大的bbox与其他bbox的iou
        # 4.1 计算交集的坐上角的点 和  右下角的点
        x1 = np.maximum(xmin[i], xmin[order[1:]])
        y1 = np.maximum(ymin[i], ymin[order[1:]])
        x2 = np.minimum(xmax[i], xmax[order[1:]])
        y2 = np.minimum(ymax[i], ymax[order[1:]])

        # 4.2 计算交集的宽和高
        w = np.maximum(1e-10, x2 - x1)
        h = np.maximum(1e-10, y2 - y1)

        # 4.3 计算iou
        inter = w * h
        iou = inter / (area[i] + area[order[1:]]  - inter)
        print('iou = ', iou)

        # 滤除超过nms阈值的检测框
        nms_thresh = 0.4
        inds = np.where(iou <= nms_thresh)[0]
        print(inds + 1)
        order = order[inds + 1]
    return keep


if __name__ == '__main__':
    bboxes = np.asarray([
        [1, 1, 3, 3],
        [1, 1, 4, 4],
        [0, 0, 1.9, 1.9],
        [0, 0, 2, 2],
    ]) * 100
    scores = np.asarray([0.6, 0.7, 0.9, 0.1])
    keep = nms(bboxes, scores)
    print(keep)
    print(bboxes[[2, 1]])
[2, 1]
[[  0.   0. 190. 190.]
 [100. 100. 400. 400.]]

2.2 后处理代码实现

了解非极大值后,我们就可以进行后处理了。

    # RT-ODLab/models/detectors/yolov1/yolov1.py    
    
    def postprocess(self, bboxes, scores):
        """
        后处理代码,包括阈值筛选和非极大值抑制

        1、滤掉低得分(边界框的score低于给定的阈值)的预测边界框;
        2、滤掉那些针对同一目标的冗余检测。
        Input:
            bboxes: [HxW, 4]
            scores: [HxW, num_classes]
        Output:
            bboxes: [N, 4]
            score:  [N,]
            labels: [N,]
        """
        # 获取最大分数
        labels = np.argmax(scores, axis=1)
        scores = scores[(np.arange(scores.shape[0]), labels)]
        
        # threshold
        # 1、滤掉低得分(边界框的score低于给定的阈值)的预测边界框;
        keep = np.where(scores >= self.conf_thresh)
        bboxes = bboxes[keep]
        scores = scores[keep]
        labels = labels[keep]

        # nms
        # 2、滤掉那些针对同一目标的冗余检测。
        scores, labels, bboxes = multiclass_nms(
            scores, labels, bboxes, self.nms_thresh, self.num_classes, self.nms_class_agnostic)

        return bboxes, scores, labels
# RT-ODLab/utils/misc.py


# ---------------------------- NMS ----------------------------
## basic NMS
def nms(bboxes, scores, nms_thresh):
    """"Pure Python NMS."""
    x1 = bboxes[:, 0]  #xmin
    y1 = bboxes[:, 1]  #ymin
    x2 = bboxes[:, 2]  #xmax
    y2 = bboxes[:, 3]  #ymax

    areas = (x2 - x1) * (y2 - y1)
    order = scores.argsort()[::-1]

    keep = []
    while order.size > 0:
        i = order[0]
        keep.append(i)
        # compute iou
        xx1 = np.maximum(x1[i], x1[order[1:]])
        yy1 = np.maximum(y1[i], y1[order[1:]])
        xx2 = np.minimum(x2[i], x2[order[1:]])
        yy2 = np.minimum(y2[i], y2[order[1:]])

        w = np.maximum(1e-10, xx2 - xx1)
        h = np.maximum(1e-10, yy2 - yy1)
        inter = w * h

        iou = inter / (areas[i] + areas[order[1:]] - inter + 1e-14)
        #reserve all the boundingbox whose ovr less than thresh
        inds = np.where(iou <= nms_thresh)[0]
        order = order[inds + 1]

    return keep

## class-agnostic NMS
def multiclass_nms_class_agnostic(scores, labels, bboxes, nms_thresh):
    # nms
    # 在所有的检测结果上执行的,不会考虑类别的差异
    keep = nms(bboxes, scores, nms_thresh)
    scores = scores[keep]
    labels = labels[keep]
    bboxes = bboxes[keep]

    return scores, labels, bboxes

## class-aware NMS 
def multiclass_nms_class_aware(scores, labels, bboxes, nms_thresh, num_classes):
    # nms
    # 逐类别地去做NMS操作,不同类别之间的检测不会相互影响
    keep = np.zeros(len(bboxes), dtype=np.int32)
    for i in range(num_classes):
        inds = np.where(labels == i)[0]
        if len(inds) == 0:
            continue
        c_bboxes = bboxes[inds]
        c_scores = scores[inds]
        c_keep = nms(c_bboxes, c_scores, nms_thresh)
        keep[inds[c_keep]] = 1
    keep = np.where(keep > 0)
    scores = scores[keep]
    labels = labels[keep]
    bboxes = bboxes[keep]

    return scores, labels, bboxes

## multi-class NMS 
def multiclass_nms(scores, labels, bboxes, nms_thresh, num_classes, class_agnostic=False):
    if class_agnostic:
        return multiclass_nms_class_agnostic(scores, labels, bboxes, nms_thresh)
    else:
        return multiclass_nms_class_aware(scores, labels, bboxes, nms_thresh, num_classes)

如此,yolov1的推理过程就已经介绍完毕。

接下来,在训练过程中,我们需要改进损失函数,训练过程中,如何确定正负样本呢?

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

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

相关文章

机器学习三要素与拟合问题

1.如何构建机器学习模型&#xff1f; 机器学习工作流程总结 1.获取数据 2.数据基本处理 3.特征工程 4.机器学习(模型训练) 5.模型评估 结果达到要求&#xff0c;上线服务&#xff0c;没有达到要求&#xff0c;重新上面步骤 我们使用机器学习监督学习分类预测模型的工作流…

计算机网络:知识回顾

0 本节主要内容 问题描述 解决思路 1 问题描述 通过一个应用场景来回顾计算机网络涉及到的协议&#xff08;所有层&#xff09;。如下图所示场景&#xff1a; 学生Bob将笔记本电脑用一根以太网电缆连接到学校的以太网交换机&#xff1b;交换机又与学校的路由器相连&#xf…

ubuntu 开机自报IP地址(用于无屏幕小车-远程连接)

目录 1.环境安装2.代码3.打包成可执行文件4.开启开机自启 1.环境安装 sudo apt-get install espeak #先安装这个库 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyttsx32.90 #再安装pyttsx3 pyinstaller pip install -i https://pypi.tuna.tsinghua.edu.cn/si…

了解.NET 通用主机

写在前面 .NET 通用主机负责应用启动和生存期管理&#xff0c;主机是封装应用资源和生存期功能的对象&#xff0c;通用主机可用于其他类型的 .NET 应用程序&#xff0c;如控制台应用&#xff1b;.NET 通用主机基于类库Microsoft.Extensions.Hosting 来实现&#xff0c;本文记录…

labuladong日常刷题-双指针 | LeetCode 83删除排序链表中的重复元素 5最长回文子串

双指针操作链表与字符串 LeetCode 83 删除排序链表中的重复元素 2023.12.28 题目链接labuladong讲解[链接] ListNode* deleteDuplicates(ListNode* head) {/*暴力求解ListNode* cur new ListNode();ListNode* prenode cur;cur->next head;cur cur->next;while(cu…

dart 学习 区分好setter,getter和一般的函数

看看代码可以很好的分析 setter &#xff0c;getter 和普通的函数有什么区别 void main() {car a new car("奔驰", 12);print(a.name);a.age 100;print(a.age);a.setname "宝马"; // 调用 setterprint(a.getage); // 调用gettera.setter("雷克萨斯…

<JavaEE> TCP 的通信机制(五) -- 延时应答、捎带应答、面向字节流

目录 TCP的通信机制的核心特性 七、延时应答 1&#xff09;什么是延时应答&#xff1f; 2&#xff09;延时应答的作用 八、捎带应答 1&#xff09;什么是捎带应答&#xff1f; 2&#xff09;捎带应答的作用 九、面向字节流 1&#xff09;沾包问题 2&#xff09;“沾包…

【iptables】增加规则和删除规则

我们在另外一台机器上&#xff0c;使用ping命令&#xff0c;向当前机器发送报文&#xff0c;如下图所示&#xff0c;ping命令可以得到回应&#xff0c;证明ping命令发送的报文已经正常的发送到了防火墙所在的主机&#xff0c;ping命令所在机器IP地址为31.133&#xff08;黑色&a…

数据结构与算法教程,数据结构C语言版教程!(第一部分、数据结构快速入门,数据结构基础详解)三

第一部分、数据结构快速入门&#xff0c;数据结构基础详解 数据结构基础&#xff0c;主要研究数据存储的方式。 本章作为数据结构的入门课程&#xff0c;主要让读者明白&#xff0c;数据结构到底是什么&#xff0c;常用的数据存储结构有哪些&#xff0c;数据结构和算法之间到底…

LMX2571 芯片配置Verliog SPI驱动

前言 本实验使用ZYNQ的PL(FPGA)对LMX2571芯片进行配置&#xff0c;以下连接为相关的原理和软件使用资料。 TICS Pro 配置时钟芯片 文献阅读–Σ-Δ 小数频率合成器原理 LMX2571芯片数据手册 一、LMX2571配置时序分析 1.1 写时序 LMX2571使用24位寄存器进行编程。一个24位移位…

什么是高并发系统?

1.1 什么是高并发&#xff1f; 高并发&#xff08;High Concurrency&#xff09;&#xff0c;通常是指通过设计保证系统能够同时处理很多请求。即在同一个时间点&#xff0c;有很多的请求同时访问同一个接口。高并发意味着大流量&#xff0c;需要运用技术手段去抵抗这种大流量…

LeetCode刷题--- 解数独

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏 力扣递归算法题 http://t.csdnimg.cn/yUl2I 【C】 ​​​​​​http://t.csdnimg.cn/6AbpV 数据结构与算法 ​​​​​http://t.csdnimg.cn/hKh2l 前言&#xff1a;这个专栏主…

【Vue2+3入门到实战】(10)Vue基础之一文掌握 组件通信 详细示例(组件通信语法、父传子 、 子传父、非父子通信)

这里写自定义目录标题 一、学习目标1.组件通信2.综合案例&#xff1a;小黑记事本&#xff08;组件版&#xff09; 二、组件通信1.什么是组件通信&#xff1f;2.组件之间如何通信3.组件关系分类4.通信解决方案5.父子通信流程6.父向子通信代码示例7.子向父通信代码示例8.总结 三、…

易趋产品升级(EasyTrack 11_V1.3) | 集成飞书、WPS、个性化设置,增强团队协作和用户体验

企业在项目管理过程中&#xff0c;经常会遇到项目信息同步不及时、沟通障碍以及管理软件使用不便捷等难题&#xff0c;导致团队协作效率低下。这种情况下&#xff0c;如果使用了多个办公软件&#xff08;如&#xff1a;钉钉、企业微信、项目管理软件等&#xff09;&#xff0c;…

AIGC盛行,带你轻松调用开发

文章目录 前言一、&#x1f4d6;AIGC简介二、&#x1f4e3;开通体验开通模型获取API-KEY 三、&#x1f4dd;基于java实现调用1.设置API-KEY2.体验大语言模型多轮对话演示补充流式输出 3.体验通义千问VL使用官方提供照片本地文件多轮对话流式输出 总结 前言 本篇文章基于java和…

IPv4归属地信息查询方法与应用

IPv4地址归属地信息查询是网络管理和安全领域的关键工具。本文将介绍IPv4地址的概念&#xff0c;探讨IPv4归属地信息的重要性&#xff0c;并详细介绍几种查询IPv4归属地信息的方法以及其应用场景。 第一部分&#xff1a;IPv4地址简介 1.1 什么是IPv4地址 IPv4&#xff08;In…

CGAL的形状规则化

规则化之前&#xff08;红色&#xff09;和之后&#xff08;绿色&#xff09;的闭合轮廓。 1、介绍 这个 CGAL包能够规范2D中的一组线段和开闭轮廓以及3D中的一组平面&#xff0c;以便所有输入对象根据用户指定的条件进行旋转和对齐。此外&#xff0c;我们提供了一个全局规范框…

旧衣回收小程序搭建,稳占回收市场

近几年我国大众的消费水平不断提升&#xff0c;闲置物品也相应增加了不少&#xff0c;尤其是闲置衣服&#xff0c;为了减少资源浪费&#xff0c;旧衣服回收回收行业受到了大众的关注。 目前我国旧衣服回收行业的市场规模达到了300多亿元&#xff0c;旧衣回收行业的商业价值非常…

跨模态检索论文阅读:Plug-and-Play Regulators for Image-Text Matching用于图像文本匹配的即插即用调节器

Plug-and-Play Regulators for Image-Text Matching用于图像文本匹配的即插即用调节器 利用细粒度的对应关系和视觉语义比对在图像-文本匹配中显示出巨大的潜力。通常&#xff0c;最近的方法首先使用跨模态注意力单元来捕捉潜在的区域-单词交互&#xff0c;然后整合所有比对以获…

椭圆中点算法

原理 椭圆的扫描转换与圆的扫描转换有相似之处&#xff0c;但也有不同&#xff0c;主要区别是椭圆弧上存在改变主位移方向的临界点。瞬时针绘制四分椭圆弧的中点算法&#xff0c;根据对称性可以绘制完整的椭圆。 四分椭圆弧 中心在原点&#xff0c;长半轴为 a a a、短半轴为…