非极大值抑制算法(Non-Maximum Suppression,NMS)

https://tcnull.github.io/nms/
https://blog.csdn.net/weicao1990/article/details/103857298

目标检测中检测出了许多的候选框,候选框之间是有重叠的,NMS作用重叠的候选框只保留一个

算法:

  1. 将所有候选框放入到集和B
  2. 从B中选出分数S最大的bm
  3. 将bm 从集和B中移除到集和D
  4. 计算bm与B中剩余的候选框之间的IOU。
  5. 如果iou>Nt则将其从B中删除。(去除重叠比较多的候选框)
  6. 循环直至B为空。
  7. D会越来越多。
def box_iou_union_2d(boxes1: Tensor, boxes2: Tensor, eps: float = 0) -> Tuple[Tensor, Tensor]:
    """
    Return intersection-over-union (Jaccard index) and  of boxes.
    Both sets of boxes are expected to be in (x1, y1, x2, y2) format.

    Arguments:
        boxes1: set of boxes (x1, y1, x2, y2)[N, 4]
        boxes2: set of boxes (x1, y1, x2, y2)[M, 4]
        eps: optional small constant for numerical stability

    Returns:
        iou (Tensor[N, M]): the NxM matrix containing the pairwise
            IoU values for every element in boxes1 and boxes2
        union (Tensor[N, M]): the nxM matrix containing the pairwise union
            values
    """
    area1 = box_area(boxes1)
    area2 = box_area(boxes2)

    x1 = torch.max(boxes1[:, None, 0], boxes2[:, 0])  # [N, M]
    y1 = torch.max(boxes1[:, None, 1], boxes2[:, 1])  # [N, M]
    x2 = torch.min(boxes1[:, None, 2], boxes2[:, 2])  # [N, M]
    y2 = torch.min(boxes1[:, None, 3], boxes2[:, 3])  # [N, M]

    inter = ((x2 - x1).clamp(min=0) * (y2 - y1).clamp(min=0)) + eps  # [N, M]
    union = (area1[:, None] + area2 - inter)
    return inter / union, union
def nms_cpu(boxes, scores, thresh):
    """
    Performs non-maximum suppression for 3d boxes on cpu
   
    Args:
        boxes (Tensor): tensor with boxes (x1, y1, x2, y2, (z1, z2))[N, dim * 2]
        scores (Tensor): score for each box [N]
        iou_threshold (float): threshould when boxes are discarded
   
    Returns:
        keep (Tensor): int64 tensor with the indices of the elements that have been kept by NMS,
            sorted in decreasing order of scores
    """
    ious = box_iou(boxes, boxes)
    _, _idx = torch.sort(scores, descending=True)
   
    keep = []
    while _idx.nelement() > 0:
        keep.append(_idx[0])
        # get all elements that were not matched and discard all others.
        non_matches = torch.where((ious[_idx[0]][_idx] <= thresh))[0]
        _idx = _idx[non_matches]
    return torch.tensor(keep).to(boxes).long()

template <typename T>
__device__ inline float devIoU(T const* const a, T const* const b) {
  // a, b hold box coords as (y1, x1, y2, x2) with y1 < y2 etc.
  T bottom = max(a[0], b[0]), top = min(a[2], b[2]);
  T left = max(a[1], b[1]), right = min(a[3], b[3]);
  T width = max(right - left, (T)0), height = max(top - bottom, (T)0);
  T interS = width * height;

  T Sa = (a[2] - a[0]) * (a[3] - a[1]);
  T Sb = (b[2] - b[0]) * (b[3] - b[1]);

  return interS / (Sa + Sb - interS);
}
at::Tensor nms_cuda(const at::Tensor& dets, const at::Tensor& scores, float iou_threshold) {
  /* dets expected as (n_dets, dim) where dim=4 in 2D, dim=6 in 3D */
  AT_ASSERTM(dets.type().is_cuda(), "dets must be a CUDA tensor");
  AT_ASSERTM(scores.type().is_cuda(), "scores must be a CUDA tensor");
  at::cuda::CUDAGuard device_guard(dets.device());//管理CUDA设备上下文,并指制定使用的CUDA设备

  bool is_3d = dets.size(1) == 6;
  //按照第一维(索引为0)对scores降序排序,并返回一个包含排序后索引的 Tensor,std::get<1>(...) 提取排序后索引的 Tensor。
  auto order_t = std::get<1>(scores.sort(0, /* descending=*/true));
  //使用排序后的索引 order_t 从 dets 中选择对应的元素,返回一个新的 Tensor; dets_sorted,其中的元素按照排序后的顺序排列。
  auto dets_sorted = dets.index_select(0, order_t);
  //bbox个数
  int dets_num = dets.size(0);
  //该函数用于计算在CUDA中进行并行化计算时所需的最大列块数。其中,dets_num代表待处理的数据数量,threadsPerBlock表示每个CUDA块中的线程数量。
  //函数通过将dets_num除以threadsPerBlock并向上取整得到最大列块数col_blocks。这个函数常用于确定CUDA并行计算中需要启动多少个CUDA块来处理所有数据。
  const int col_blocks = at::cuda::ATenCeilDiv(dets_num, threadsPerBlock);//获取block个数

  //该函数创建了一个名为mask的空Tensor,其大小为dets_num * col_blocks,数据类型为长整型(at::kLong)。该Tensor的属性与dets相同。
  at::Tensor mask = at::empty({dets_num * col_blocks}, dets.options().dtype(at::kLong));//创建一个空容器  D

  dim3 blocks(col_blocks, col_blocks);//定义了一个二维网格,其维度为col_blocks行col_blocks列
  dim3 threads(threadsPerBlock);//定义了一个线程块,其维度为threadsPerBlock;
  cudaStream_t stream = at::cuda::getCurrentCUDAStream();//获取当前的CUDA流,用于异步计算。


  if (is_3d) {
  //std::cout << "performing NMS on 3D boxes in CUDA" << std::endl;
  AT_DISPATCH_FLOATING_TYPES_AND_HALF(
      dets_sorted.type(), "nms_kernel_cuda", [&] {
        nms_kernel_3d<scalar_t><<<blocks, threads, 0, stream>>>(
            dets_num,
            iou_threshold,
            dets_sorted.data_ptr<scalar_t>(),
            (unsigned long long*)mask.data_ptr<int64_t>());
      });
   }
   else {
    //该函数是PyTorch CUDA扩展中的一个宏定义,用于在CUDA代码中处理浮点类型数据(包括float、double、half)的泛型编程。
    //它会根据传入的输入数据类型,自动选择对应的CUDA内核函数进行计算。
    //这样可以避免为每种数据类型编写重复的代码,提高代码的可维护性和可扩展性。
    //相当与模板类
   AT_DISPATCH_FLOATING_TYPES_AND_HALF(
      dets_sorted.type(), "nms_kernel_cuda", [&] {
        nms_kernel<scalar_t><<<blocks, threads, 0, stream>>>(
            dets_num,
            iou_threshold,
            dets_sorted.data_ptr<scalar_t>(),
            (unsigned long long*)mask.data_ptr<int64_t>());
      });

   }

  //将mask_cpu的数据拷贝到主机内存中
  at::Tensor mask_cpu = mask.to(at::kCPU);
  unsigned long long* mask_host = (unsigned long long*)mask_cpu.data_ptr<int64_t>();

  //创建一个空的remv Tensor,其大小为col_blocks,数据类型为unsigned long long。用于记录每个block中哪些线程被标记为无效。
  std::vector<unsigned long long> remv(col_blocks);
  memset(&remv[0], 0, sizeof(unsigned long long) * col_blocks);

  //创建一个空的keep Tensor,其大小为dets_num,数据类型为long。用于存放整个筛选后的索引。
  at::Tensor keep = at::empty({dets_num}, dets.options().dtype(at::kLong).device(at::kCPU));
  int64_t* keep_out = keep.data_ptr<int64_t>();

  int num_to_keep = 0;
  for (int i = 0; i < dets_num; i++) {
    int nblock = i / threadsPerBlock;//第几个block
    int inblock = i % threadsPerBlock;//block中第几个threads

    //判断当前线程是否被标记为无效
    if (!(remv[nblock] & (1ULL << inblock))) {
      keep_out[num_to_keep++] = i;
      unsigned long long* p = mask_host + i * col_blocks;//实际上将所有的形式设置成one hot形式。
      for (int j = nblock; j < col_blocks; j++) {
        remv[j] |= p[j];//按位或运算,并将结果保留在remv数组中
      }
    }
  }

  AT_CUDA_CHECK(cudaGetLastError());//检测CUDA 是否发生错误
  return order_t.index(
      {keep.narrow(/*dim=*/0, /*start=*/0, /*length=*/num_to_keep)//从张量keep中选择制定维度、起始位置和长度的子张量
           .to(order_t.device(), keep.scalar_type())});
}
template <typename T>
__global__ void nms_kernel(const int n_boxes, const float iou_threshold, const T* dev_boxes, unsigned long long* dev_mask) {
  const int row_start = blockIdx.y;
  const int col_start = blockIdx.x;

  // if (row_start > col_start) return;
  const int row_size = min(n_boxes - row_start * threadsPerBlock, threadsPerBlock);//当前block行可以放多少个box
  const int col_size = min(n_boxes - col_start * threadsPerBlock, threadsPerBlock);//获取block列可以放多少个box

  __shared__ T block_boxes[threadsPerBlock * 4];
  if (threadIdx.x < col_size) {//将当前block列的box拷贝到shared memory
    block_boxes[threadIdx.x * 4 + 0] =  dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 4 + 0];
    block_boxes[threadIdx.x * 4 + 1] =  dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 4 + 1];
    block_boxes[threadIdx.x * 4 + 2] =  dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 4 + 2];
    block_boxes[threadIdx.x * 4 + 3] =  dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 4 + 3];
  }
  __syncthreads();//同步

  if (threadIdx.x < row_size) {
    const int cur_box_idx = threadsPerBlock * row_start + threadIdx.x;//当前block行box的索引
    const T* cur_box = dev_boxes + cur_box_idx * 4;//当前block行box的指针
    int i = 0;
    unsigned long long t = 0;
    int start = 0;
    if (row_start == col_start) {
      start = threadIdx.x + 1;//自己的IOU就不算了
    }
    for (i = start; i < col_size; i++) {
      if (devIoU<T>(cur_box, block_boxes + i * 4) > iou_threshold) {//计算各自的IOU
        t |= 1ULL << i;//以二进制的形式表示重叠关系成立
      }
    }
    const int col_blocks = at::cuda::ATenCeilDiv(n_boxes, threadsPerBlock);//列block数
    dev_mask[cur_box_idx * col_blocks + col_start] = t;//将重叠关系写入shared memory
  }
}

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

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

相关文章

探讨数字化背景下VSM(价值流程图)的挑战和机遇

在信息化、数字化飞速发展的今天&#xff0c;各行各业都面临着前所未有的挑战与机遇。作为源自丰田生产模式的VSM&#xff08;价值流程图&#xff09;&#xff0c;这一曾经引领制造业革命的工具&#xff0c;在数字化背景下又将如何乘风破浪&#xff0c;应对新的市场格局和技术变…

西门子智能电气阀门定位器在冶金生产控制的应用

西门子智能电气阀门定位器在冶金生产控制的应用 1 前 言 在自动化程度越来越高的冶金行业中 ,调节阀起着至关重要的作用,一旦其发生故障, 轻则出现生产事故,停机,停炉影响各级生产指标,生产任务,影响装置的安全运行。重则可能出现人身安全事故,将直接影响家庭的幸福和企…

ubuntu 18 虚拟机安装(1)

ubuntu 18 虚拟机安装 ubuntu 18.04.6 Ubuntu 18.04.6 LTS (Bionic Beaver) https://releases.ubuntu.com/bionic/ 参考&#xff1a; 设置固定IP地址 https://blog.csdn.net/wowocpp/article/details/126160428 https://www.jianshu.com/p/1d133c0dec9d ubuntu-18.04.6-l…

网络安全学习(持续更新中~)

网络安全学习 前言 本目录索引持续更新中&#xff0c;记录网络安全学习过程~ 博客最新更新时间&#xff1a;2024.6.25 学习路线 Windows内网服务 Windows内网服务模块包含了Windows服务器的相关知识。 文章链接包含要点Windows账户相关Windows权限相关Windows日志Windows…

关于 AD21导入电子元器件放置“3D体”STEP模型失去3D纹理贴图 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/139969415 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

数据交换的桥梁:深入探索JSON序列化和反序列化

目录 JSON序列化 一、查看JSON文件&#xff0c;设置数据模板类 ​编辑 二、Newtonsoft.Json下载 三、代码理解 1.创建BatteryList的实例 2.初始化Batterys属性 3.添加Battery对象到Batterys列表中 4.完整的代码如下 四、运行结果展示 JSON反序列化 序列化是将对象或…

2024年生物技术与食品科学国际会议(ICBFS 2024)

2024 International Conference on Biotechnology and Food Science 2024年生物技术与食品科学国际会议 【会议信息】 会议简称&#xff1a;ICBFS 2024 大会地点&#xff1a;中国厦门 会议邮箱&#xff1a;icbfssub-paper.com 审稿结果&#xff1a;投稿后3日左右 提交检索&…

fiddle查看请求耗时 设置超时背景

windows 下&#xff0c;打开 fiddler 时直接用 快捷键&#xff1a;CTRL R 打开 或 从路径&#xff1a;Rules -> Customize Rules… 打开 // 显示每行请求的服务端耗时时间 public static BindUIColumn("TimeTaken/ms", 120)function TimeTaken(oS: Session):Stri…

安卓直装植物大战僵尸杂交版V2.1版完美运行

安卓直装植物大战僵尸杂交版V2.1版完美运行 链接&#xff1a;https://pan.baidu.com/s/1SPFouV8T-AV2LnUoZfy6lQ?pwd3gl6 提取码&#xff1a;3gl6

裸机与操做系统区别(RTOS)

声明&#xff1a;该系列笔记是参考韦东山老师的视频&#xff0c;链接放在最后&#xff01;&#xff01;&#xff01; rtos&#xff1a;这种系统只实现了内核功能&#xff0c;比较简单&#xff0c;在嵌入式开发中&#xff0c;某些情况下我们只需要多任务&#xff0c;而不需要文件…

49-3 内网渗透 - MSI安 装策略提权

靶场环境搭建: 这里还是用我们之前的windows2012虚拟机进行搭建 1)打开一些设置让靶场存在漏洞 打开组策略编辑器(gpedit.msc) 使用运行命令打开: 按下 Win + R 组合键来打开运行对话框。输入 gpedit.msc,然后按下 Enter 键。使用搜索打开: 点击任务栏上的搜索框(W…

简单的text/html无法解析解决记录

简单的text/html无法解析解决记录 1. bug发现 我们所有的服务都是微服务&#xff0c;服务间调用都是使用feign接口进行调用&#xff0c;正常调用都没有问题&#xff0c;但是某一天发现部分从esb服务调用过来到我们本地的服务&#xff0c;本地服务再使用feign接口调用其他微服…

Java洗鞋小程序预约系统源码

&#x1f4a5;洗鞋神器来袭&#xff01;轻松预约&#xff0c;让你的鞋子焕然一新&#x1f45f; &#x1f389; 告别洗鞋烦恼&#xff0c;洗鞋预约小程序来啦&#xff01; 你是不是常常为洗鞋而烦恼&#xff1f;手洗太累&#xff0c;送去洗衣店又贵又麻烦。现在&#xff0c;好…

js计算某个时间距离当前时间多少天,少于7天红色展示

效果图 后端返回数据格式 info:{vip_validity:"2027-09-07" }<div>到期时间&#xff1a;{{ info.vip_validity }}, 剩余<span :class"countdownDays(info.vip_validity) < 7 ? surplus : ">{{ !!info.vip_validity ? countdownDays(inf…

【ARM-Linux篇】项目:智能家居

一、项目概述 •项目功能 通过语音控制客厅灯、卧室灯、风扇、人脸识别开门等,可以进行火灾险情监测,可以并且实现Sockect发送指令远程控制各类家电等 •项目描述 全志H616通过串口连接各模块硬件,检测语音的识别结果,分析语音识别的结果来对家电设备进行控制。摄像头拍…

Superagent:一个开源的AI助手框架与API

在人工智能日益普及的今天,如何将AI助手无缝集成到应用中成为了开发者们关注的焦点。今天,我们要介绍的Superagent正是一个为这一需求量身打造的开源框架与API。它结合了LLM、检索增强生成(RAG)和生成式AI技术,为开发者们提供了一个强大而灵活的解决方案。 一、Superagen…

PID原理及控制算法详解

文章目录 1. 概念 1.1 PID框图 1.2 具体示例&#xff1a;无人机高度控制 2. PID原理 3. 常用术语 4. 计算过程 4.1 比例控制&#xff08;Proportional&#xff09; 4.2 积分控制&#xff08;Integral&#xff09; 4.3 微分控制&#xff08;Derivative&#xff09; 5.…

6.18-6.26 旧c语言

第一章 概述 32关键字 9种控制语句 优点&#xff1a;能直接访问物理地址&#xff0c;位操作&#xff0c;代码质量高&#xff0c;执行效率高 可移植性好 面向过程&#xff1a;以事件为中心 面向对象&#xff1a;以实物为中心 printf&#xff1a;系统定义的标准函数 #include&l…

[图解]建模相关的基础知识-19

1 00:00:00,640 --> 00:00:04,900 前面讲了关系的这些范式 2 00:00:06,370 --> 00:00:11,570 对于我们建模思路来说&#xff0c;有什么样的作用 3 00:00:12,660 --> 00:00:15,230 我们建模的话&#xff0c;可以有两个思路 4 00:00:16,790 --> 00:00:20,600 一个…

《Redis设计与实现》阅读总结-3

第 12 章 事件 Redis服务器是一个事件驱动程序&#xff0c;服务器需要处理两类事件&#xff1a;文件事件和时间事件 一、文件事件 1. 文件处理器&#xff1a;Redis基于Reactor模式开发了自己的网络事件处理器被称为文件处理器 文件事件处理器使用I/O多路复用程序来同时监听多…