6 -【Faster R-CNN 代码精读】之 Proposals 、 Filter Proposals

news2025/1/19 17:15:04

6 -【Faster R-CNN 代码精读】之 Proposals 、 Filter Proposals

  • 1、前言
  • 2、数据回顾
  • 3、计算候选框位置(proposal coordinates)
  • 4、筛选候选框(filter proposals)及相关处理
    • 1)筛选出 预测概率 排前 2000 的proposals
    • 2)将概率值转化到 0~1
    • 3)截断 proposals 超出原图像的部分
    • 4)删除宽高都小于 1的proposals
    • 5)筛除概率小于阈值的 proposal
    • 6)使用 NMS 筛除冗余的 proposal
    • 7)最后再根据取 top k 个proposals
  • 5、代码
    • 1、计算候选框坐标
    • 2、筛选候选框

目的:将预测的bbox回归参数应用到对应anchors上得到预测bbox的坐标

1、前言

  • 在上一篇文章 【Faster R-CNN】之 AnchorGenerator代码精读 中,我们获取到了 anchor 在原图中的坐标
  • 在上上篇文章【Faster R-CNN】之 RPN Head 代码精读 中,我们得到了 预测出的 bounding box 的概率 以及 预测出的 bounding box 回归参数(regression parameters)。这里的 bounding box 回归参数 并不是 预测bounding box 在原图中的绝对位置,而是相对于每一个 anchor 位置的 偏移量 offset。

所以,我们需要结合 anchor 原图中的绝对位置 以及 bounding box 回归参数,计算出 预测 bounding box 在原图中的绝对位置。 求 predicted bounding box 的绝对坐标位置,是为了和 ground truth bounding box 的坐标位置 做对比,计算出 loss。


2、数据回顾

  • raw anchor:使用超参数 s i z e = ( 32 , 64 , 128 , 256 , 512 ) size=(32, 64, 128, 256, 512) size=(32,64,128,256,512) a s p e c t _ r a t i o s = ( 0.5 , 1.0 , 2.0 ) aspect\_ratios=(0.5, 1.0, 2.0) aspect_ratios=(0.5,1.0,2.0) 创建anchors,可创建 5 ∗ 3 = 15 5*3=15 53=15 个 anchors。( feature map 上的每一个像素点都会产生这15个anchors)

  • 映射到原图像上的 anchors :feature map 上的每个像素都会产生15个anchors,将 feature map 上的每个像素点都映射回原图像中,原图像中的这些像素点就是 anchors 的中心点。然后,将 anchors 也映射回原图中,就得到了原图像中的 anchors 的绝对坐标位置。

    每张图像 anchors总量 = 15 ∗ h e i g h t ∗ w i d t h 15 * height * width 15heightwidth ,其中,height、width 为feature map 的高和宽。

  • 预测 bounding box 回归参数:由 RPN Head 网络得出,shape=(batch_size, 60, height, width) ,我们将它形状转换为 shape= ( b a t c h _ s i z e ∗ 15 ∗ h e i g h t ∗ w i d t h , 4 ) (batch\_size*15*height*width, 4) (batch_size15heightwidth,4) 用于之后的计算


3、计算候选框位置(proposal coordinates)

目的:计算出 predicted boxes 在原图上的 坐标位置。
方法:在原图上 anchor 坐标的基础上,加上 predicted box 的偏移量。 predicted box 的偏移量 是由 预测bounding box 回归参数 计算得到。

1)将anchor 的坐标形式 从(xmin, ymin, xmax, ymax) 转换为 (ctr_x, ctr_y, widths, heights)

  • anchor 宽度: anchor_width = xmax - xmin
  • anchor 高度:anchor_height = ymax - ymin
  • anchor中心x坐标:anchor_ctr_x = xmin + anchor_width
  • anchor中心y坐标:anchor_ctr_y= ymin + anchor_height

在这里插入图片描述

anchor_widths = anchors[:, 2] - anchors[:, 0]  # anchors 宽度
anchor_heights = anchors[:, 3] - anchors[:, 1]  # anchors 高度
ctr_x = anchors[:, 0] + 0.5 * anchor_widths  # anchors 中心x坐标
ctr_y = anchors[:, 1] + 0.5 * anchor_heights  # anchors 中心y坐标

\quad
2)预测回归参数 pred_box_regression,shape 为 ( b a t c h _ s i z e ∗ 15 ∗ h e i g h t ∗ w i d t h , 4 ) (batch\_size*15*height*width, 4) (batch_size15heightwidth,4),其中 b a t c h _ s i z e ∗ 15 ∗ h e i g h t ∗ w i d t h batch\_size*15*height*width batch_size15heightwidth 是 anchor 的数量, 4 就是每个anchor 对应的坐标回归参数,将这4个值解析出来,分别记为:dx, dy, dw, dh。

限制 dw, dh 的最大值为 1000./16=4.135,预防 后面计算的时候,传递过大的值到 torch.exp()中

# self.bbox_xform_clip = math.log(1000. / 16)
dw = torch.clamp(dw, max=self.bbox_xform_clip)
dh = torch.clamp(dh, max=self.bbox_xform_clip)

\quad
3)计算出 预测box 相对于anchor 中心的偏移量

  • x方向的偏移量: pred_x_offset = dx * anchor_widths,
  • y方向的偏移量: pred_y_offset = dy * anchor_heights

将偏移量加到 anchor 中心坐标,就得到 预测box 的中心坐标位置

  • 预测 box 的中心 x方向坐标:pred_ctr_x = pred_x_offset + anchor_widths
  • 预测 box 的中心 y方向坐标:pred_ctr_x = pred_y_offset + anchor_heights

预测 box 的高度 和宽度

  • 预测 box 的宽度:pred_w = e d w e^{dw} edw * anchor_widths
  • 预测 box 的高度:pred_h = e d w h e^{dwh} edwh * anchor_heights
pred_ctr_x = dx * anchor_widths[:, None] + ctr_x[:, None]
pred_ctr_y = dy * anchor_heights[:, None] + ctr_y[:, None]
pred_w = torch.exp(dw) * anchor_widths[:, None]
pred_h = torch.exp(dh) * anchor_heights[:, None]

\quad
4)最后,将 预测 box的坐标形式 从 (ctr_x, ctr_y, width, height) 转换为 (xmin, ymin, xmax, ymax)

  • 预测box 的左上角x坐标:pred_boxes_xmin = pred_ctr_x - 0.5 * pred_w
  • 预测box 的左上角y坐标:pred_boxes_ymin = pred_ctr_y - 0.5 * pred_h
  • 预测box 的右下角x坐标:pred_boxes_xmax = pred_ctr_x + 0.5 * pred_w
  • 预测box 的右下角y坐标:pred_boxes_ymax = pred_ctr_y + 0.5 * pred_h
pred_boxes_xmin = pred_ctr_x - torch.tensor(0.5, dtype=pred_ctr_x.dtype, device=pred_w.device) * pred_w
pred_boxes_ymin = pred_ctr_y - torch.tensor(0.5, dtype=pred_ctr_y.dtype, device=pred_h.device) * pred_h
pred_boxes_xmax = pred_ctr_x + torch.tensor(0.5, dtype=pred_ctr_x.dtype, device=pred_w.device) * pred_w
pred_boxes_ymax = pred_ctr_y + torch.tensor(0.5, dtype=pred_ctr_y.dtype, device=pred_h.device) * pred_h

\quad
到此,我们就计算出了 proposals 在原图中的坐标了。

\quad


当前获取到的 候选框 (proposals) 数量太大了,一般都是 十万级的数量。
我们接下来我们要筛选出 1000+ 个proposals.(不超过2000个)

4、筛选候选框(filter proposals)及相关处理

1)筛选出 预测概率 排前 2000 的proposals

筛选出 预测概率 排前 2000 的proposals,也就是最有可能包含 objects 的 proposals

还记得我们之前从 RPN Head 网络(如下图)中得到了 所有 proposals 的概率嘛,我们从中筛选出 top 2000
在这里插入图片描述
筛选出的 proposals,我们按照概率的降序的顺序 存储,包括 概率 和 对应的proposals的坐标。

'''
objectness :预测概率值
proposals :proposals的坐标
train阶段, pre_nms_top_n = 2000
'''
# 预测概率值为 top 2000 的 proposals 的索引
_, top_n_idx = objectness.topk(pre_nms_top_n, dim=1)  

# top 2000 的 proposals 的 预测概率值 和 坐标
batch_idx = torch.arange(num_images, device=device).reshape(-1, 1)
objectness = objectness[batch_idx, top_n_idx]
proposals = proposals[batch_idx, top_n_idx]

bjectness_prob = torch.sigmoid(objectness)

2)将概率值转化到 0~1

将 预测概率 做 sigmoid 运算,转成真正的概率值 : 数值 映射到 0~1

objectness_prob = torch.sigmoid(objectness)

3)截断 proposals 超出原图像的部分

截断 proposals 超出原图像的部分,并将坐标调整到图片边界上。
这里 width 和 height 分别为 图像 resize 之后,padding 之前的高和宽。 细节在这里

在这里插入图片描述

boxes = clip_boxes_to_image(boxes, img_shape)

def clip_boxes_to_image(boxes, size):
    boxes_x = boxes[:, 0::2]  # x1, x2
    boxes_y = boxes[:, 1::2]  # y1, y2
    height, width = size

    boxes_x = boxes_x.clamp(min=0, max=width)   # 限制x坐标范围在[0,width]之间
    boxes_y = boxes_y.clamp(min=0, max=height)  # 限制y坐标范围在[0,height]之间

    clipped_boxes = torch.stack((boxes_x, boxes_y), dim=2)
    return clipped_boxes.reshape(boxes.shape) # (top_k_num, 4)

4)删除宽高都小于 1的proposals

这里的 min_size=1 , 是超参数

keep = remove_small_boxes(boxes, self.min_size)
boxes, scores = boxes[keep], scores[keep]

def remove_small_boxes(boxes, min_size):
    ws = boxes[:, 2] - boxes[:, 0]   # boxes的宽
    hs = boxes[:, 3] - boxes[:, 1]  # boxes的高
    keep = torch.logical_and(torch.ge(ws, min_size), torch.ge(hs, min_size))
    keep = torch.where(keep)[0]
    return keep

5)筛除概率小于阈值的 proposal

这里 阈值设置的是 0.0

keep = torch.where(torch.ge(scores, self.score_thresh))[0]  
boxes, scores = boxes[keep], scores[keep]

6)使用 NMS 筛除冗余的 proposal

使用的 shreshold=0.7

keep = batched_nms(boxes, scores, self.nms_thresh)
def batched_nms(boxes, scores, iou_threshold):
    if boxes.numel() == 0:
        return torch.empty((0,), dtype=torch.int64, device=boxes.device)
    keep = torch.ops.torchvision.nms(boxes, scores, iou_threshold)
    return keep

7)最后再根据取 top k 个proposals

我们第一步处理 的时候,是先筛选出了 top 2000 的proposals (pre_nms_top_n=20000)
最后一步,我们会再设置超参数 post_nms_top_n 筛选出 一定数量的 proposals,只是这里我们设置的 post_nms_top_n = pre_nms_top_n = 2000


5、代码

1、计算候选框坐标


def decode(self, pred_box_regression, anchors):
    # concatgate batch_size 张图片的所有 anchors, 形为  (batch_size * Anchor * Height * Width, 1)
    anchors = torch.cat(anchors, dim=0)

    # 将预测的bbox回归参数应用到对应anchors上得到预测bbox的坐标
    anchors = anchors.to(pred_box_regression.dtype)

    # anchor 的坐标形式 从(xmin, ymin, xmax, ymax) 转换为 (ctr_x, ctr_y, anchor_widths, anchor_heights)
    anchor_widths = anchors[:, 2] - anchors[:, 0]  # anchor/proposal宽度
    anchor_heights = anchors[:, 3] - anchors[:, 1]  # anchor/proposal高度
    ctr_x = anchors[:, 0] + 0.5 * anchor_widths  # anchor/proposal中心x坐标
    ctr_y = anchors[:, 1] + 0.5 * anchor_heights  # anchor/proposal中心y坐标

	# 解析出 predicted box 的中心坐标回归参数
    dx, dy, dw, dh = pred_box_regression.split([1, 1, 1, 1], dim=1) 

    # limit max value, prevent sending too large values into torch.exp()
    # self.bbox_xform_clip = math.log(1000. / 16) = 4.135
    dw = torch.clamp(dw, max=self.bbox_xform_clip)
    dh = torch.clamp(dh, max=self.bbox_xform_clip)

    # 在anchor的基础上计算 predicted box 的坐标: pred_ctr_x, pred_ctr_y, pred_w, pred_h
    pred_ctr_x = dx * anchor_widths[:, None] + ctr_x[:, None]
    pred_ctr_y = dy * anchor_heights[:, None] + ctr_y[:, None]
    pred_w = torch.exp(dw) * anchor_widths[:, None]
    pred_h = torch.exp(dh) * anchor_heights[:, None]

    # 将 predicted box 坐标形式 由(pred_ctr_x, pred_ctr_y, pred_w, pred_h) 转换为 (xmin、ymin、xmax、ymax)
    pred_boxes_xmin = pred_ctr_x - torch.tensor(0.5, dtype=pred_ctr_x.dtype, device=pred_w.device) * pred_w
    pred_boxes_ymin = pred_ctr_y - torch.tensor(0.5, dtype=pred_ctr_y.dtype, device=pred_h.device) * pred_h
    pred_boxes_xmax = pred_ctr_x + torch.tensor(0.5, dtype=pred_ctr_x.dtype, device=pred_w.device) * pred_w
    pred_boxes_ymax = pred_ctr_y + torch.tensor(0.5, dtype=pred_ctr_y.dtype, device=pred_h.device) * pred_h

    pred_boxes = torch.stack((pred_boxes_xmin, pred_boxes_ymin, pred_boxes_xmax, pred_boxes_ymax), dim=2).flatten(1)
    return pred_boxes

2、筛选候选框

def filter_proposals(self, proposals, objectness, image_shapes):
    # # type: (Tensor, Tensor, List[Tuple[int, int]], List[int]) -> Tuple[List[Tensor], List[Tensor]]

    num_images = proposals.shape[0]
    device = proposals.device

    # do not backprop throught objectness
    objectness = objectness.detach()
    objectness = objectness.reshape(num_images, -1)

    # select top_n boxes before applying nms

    if self.training:
        pre_nms_top_n = self.pre_nms_top_n['training']   # pre_nms_top_n = 2000
    else:
        pre_nms_top_n = self.pre_nms_top_n['testing']   # pre_nms_top_n = 1000

    # 预测概率值为 top 2000 的 proposals 的索引
    _, top_n_idx = objectness.topk(pre_nms_top_n, dim=1)

    # top 2000 的 预测概率值 & proposals坐标
    batch_idx = torch.arange(num_images, device=device).reshape(-1, 1)
    objectness = objectness[batch_idx, top_n_idx]
    proposals = proposals[batch_idx, top_n_idx]

    objectness_prob = torch.sigmoid(objectness)

    final_boxes = []
    final_scores = []
    # 遍历每张图像的相关预测信息
    for boxes, scores, img_shape in zip(proposals, objectness_prob, image_shapes):
        # 调整 proposals 的坐标,将越界的坐标调整到图片边界上
        boxes = clip_boxes_to_image(boxes, img_shape)

        # 返回boxes满足宽,高都大于min_size的索引
        keep = remove_small_boxes(boxes, self.min_size)
        boxes, scores = boxes[keep], scores[keep]

        # 移除小概率boxes,参考 https://github.com/pytorch/vision/pull/3205
        keep = torch.where(torch.ge(scores, self.score_thresh))[0]  # ge: >=
        boxes, scores = boxes[keep], scores[keep]

        # NMS (non-maximum suppression), independently done per level
        keep = batched_nms(boxes, scores, self.nms_thresh)

        # keep only topk scoring predictions  # self.pre_nms_top_n={'training': 2000, 'testing': 1000}
        if self.training:
            keep = keep[: self.post_nms_top_n['training']]
        else:
            keep = keep[: self.post_nms_top_n['testing']]
        boxes, scores = boxes[keep], scores[keep]

        final_boxes.append(boxes)
        final_scores.append(scores)
    return final_boxes, final_scores


def clip_boxes_to_image(boxes, size):
    boxes_x = boxes[:, 0::2]  # x1, x2
    boxes_y = boxes[:, 1::2]  # y1, y2
    height, width = size

    boxes_x = boxes_x.clamp(min=0, max=width)   # 限制x坐标范围在[0,width]之间
    boxes_y = boxes_y.clamp(min=0, max=height)  # 限制y坐标范围在[0,height]之间

    clipped_boxes = torch.stack((boxes_x, boxes_y), dim=2)
    return clipped_boxes.reshape(boxes.shape)


def remove_small_boxes(boxes, min_size):
    ws = boxes[:, 2] - boxes[:, 0]   # boxes的宽
    hs = boxes[:, 3] - boxes[:, 1]  # boxes的高
    keep = torch.logical_and(torch.ge(ws, min_size), torch.ge(hs, min_size))
    keep = torch.where(keep)[0]
    return keep


def batched_nms(boxes, scores, iou_threshold):
    if boxes.numel() == 0:
        return torch.empty((0,), dtype=torch.int64, device=boxes.device)
    keep = torch.ops.torchvision.nms(boxes, scores, iou_threshold)
    return keep

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

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

相关文章

TCP协议面试灵魂12 问(四)

005: 介绍一下 TCP 报文头部的字段 报文头部结构如下(单位为字节): 请大家牢记这张图! 源端口、目标端口 如何标识唯一标识一个连接?答案是 TCP 连接的四元组——源 IP、源端口、目标 IP 和目标端口。 那 TCP 报文怎么没有源 IP 和目标 IP 呢&#x…

2021年下半年信息系统项目管理师《综合知识》《案例分析》《论文》真题与答案

1、“十四五”期间,我国关注推动政务信息化共建共用、推动构建网络空间命运共同体,属于()的建设内容.A、科技中国 B、数字中国 C、制造强国 D、创新强国0参考答案:B2、()关注的是业务,以业务驱动技术,强调IT与业务的对…

零基础学FPGA(八):可编程逻辑单元(基本结构,Xilinx+Altera)

目录日常唠嗑一、概述二、基于多路选择器的逻辑单元1、基于多路选择器的逻辑单元(早期)2、基于PLD结构的逻辑单元(类CPLD)3、基于查询表的逻辑单元(目前主流)三、Xilinx基本结构四、Altera 基本结构日常唠嗑…

Java语言还能火多久? 还能选择Java开发吗?

​​整个互联网行业“不进则退,慢进亦退”。对于用人要求持续增高的互联网企业来说,中高级Java程序员才是当下市场最紧缺的。 现在的你,是十年前你的决定,十年后的你,是现在你的决定。选择很重要 为什么选择Java开发…

代码随想录算法训练营第六十天_第九章_动态规划 | 647. 回文子串、516.最长回文子序列、动态规划总结篇

LeetCode 647. 回文子串 给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。 视频讲解https://www.bilibili.com/video/BV17G4y1y7z9/?spm_i…

Linux 进程知识总结

✅作者简介:热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏:Linux操作…

高阶数据结构之哈希的应用

文章目录位图(bitMap)位图的实现将数据添加到位图中检查数据是否在位图中存在将数据的对应位置置为0位图的应用布隆过滤器为什么会有误差布隆过滤器的实现布隆过滤器的删除使用Google下的guava组件操作布隆过滤器布隆过滤器的缺陷布隆过滤器的使用场景海…

分享69个ASP源码,总有一款适合您

分享69个ASP源码,总有一款适合您 69个ASP源码下载链接:https://pan.baidu.com/s/1XXwBL0Y0nOSel9xJVqz0Ow?pwdertw 提取码:ertw Python采集代码下载链接:https://wwgn.lanzoul.com/iKGwb0kye3wj base_url "https://d…

关于JavaScript编译原理以及作用域的深入探讨

前言 几乎所有编程语言最基本的功能之一,就是能够储存变量当中的值,并且能在之后对这个值进行访问或修改。事实上,正是这种储存和访问变量的值的能力将状态带给了程序。 若没有了状态这个概念,程序虽然也能够执行一些简单的任务…

高码率QPSK调制解调方案(2.1 QPSK调制)

2 全数字高码率QPSK调制解调软件设计 2.1 QPSK调制 2.1.1 QPSK调制原理 QPSK调制即正交相移键控信号,其信号表达式为 (1) 其中,和分别表示每比特的能量和持续

​LabView​动态改变显示文本框的属性(颜色、字体)

目录 问题: 解决方式 1、在程序框图面板上,选择显示文本框,右键创建属性节点 2、选择属性节点为数值文本,依据需要改变的类型选择文本颜色或字体 3、依据下位机上传的数值大小,选择-条件结构类型,依据…

03俯瞰全局:gRPC是如何进行通信的

gRPC作为Web网站和APP后端的最常用服务提供方式之一和分布式微服务架构不同节点间的最常见通信方式之一,它自身是如何进行通信的呢,这个问题就是我们本篇文章将要研究的重点,先从全局的角度分析gRPC服务端和客户端进行通信的主要流程,再从细节剖析gRPC是如何进行高效的远程…

Qt QVector “isDetached()“

文章目录摘要尝试通过加锁解决lock&unlockQMutexLockertry_lock改变量属性Q_ASSERT参考关键字: ASSERT、 isDetached、 崩溃、 QVector、 Detach摘要 今天在公司填坑的时候,有随机获得了一个新的BUG,就是一直报ASSERT:”isD…

剑指 Offer 35. 复杂链表的复制

题目 请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。 思路 方法一:哈希表 利用哈希表的查询特点&#xff…

【Linux】项目自动化构建工具-make/Makefile与Linux调试器-gdb使用

文章目录Linux项目自动化构建工具-make/MakefileLinux调试器-gdb使用Linux项目自动化构建工具-make/Makefile 背景 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录…

SpringBoot+Vue图书馆管理系统

简介:本项目采用了基本的SpringBootVue设计的图书馆管理系统。详情请看截图。经测试,本项目正常运行。本项目适用于Java毕业设计、课程设计学习参考等用途。 项目描述 项目名称SpringBootVue图书馆管理系统源码作者LHL项目类型Java EE项目 (…

【iMessage苹果推推送】将看到一个可扩大选项“AppleDevelopmentPushServices”6.扩展此选项并右键单击“Appledge”>

推荐内容IMESSGAE相关 作者✈️IMEAX推荐内容iMessage苹果推软件 *** 点击即可查看作者要求内容信息作者✈️IMEAX推荐内容1.家庭推内容 *** 点击即可查看作者要求内容信息作者✈️IMEAX推荐内容2.相册推 *** 点击即可查看作者要求内容信息作者✈️IMEAX推荐内容3.日历推 *** …

基于NOSTR协议的“公有制”版本的Twitter,去中心化社交软件Damus用后感,一个极端走向另一个极端

最近,一个幽灵,Web3的幽灵,在网络游荡,它叫Damus,这玩意诠释了什么叫做病毒式营销,滑稽的是,一个Web3产品却在Web2的产品链上疯狂传销,各方大佬纷纷为其背书,到底发生了什…

基于python实现疫情期间微博、B站网民情绪分析

一项目概述:“疫情下的社会心理学”这一课题旨在通过疫情期间大众在自媒体上的新闻评论等信息,凭借一些方法分析出总体的心理变化和情绪走向,并在宏观上把握了总体的心态格局。对于该课题,我们小组首先爬取了哔哩哔哩和微博的大量…

Spring Boot(三):第二种导入依赖方式的实战案例(常用)

文章目录 第二种导入依赖方式的实战案例 一、导入依赖方式1(拓展方法) 二、使用idea自带springBoot项目初始化插件 第二种导入依赖方式的实战案例 一、导入依赖方式1(拓展方法) 在公司中可能会出现必须继承某个项目&#xff…