13.4 目标检测锚框标注 非极大值抑制

news2025/1/31 2:53:40

锚框的形状计算公式

假设原图的高为H,宽为W
在这里插入图片描述

锚框形状详细公式推导

在这里插入图片描述

以每个像素为中心生成不同形状的锚框

请添加图片描述

# s是缩放比,ratio是宽高比
def multibox_prior(data, sizes, ratios):
    """生成以每个像素为中心具有不同形状的锚框"""
    in_height,in_width = data.shape[-2:] # 取出最后两个元素,即h和w
    device,num_sizes,num_ratios = data.device,len(sizes),len(ratios)
    boxes_per_pixel = (num_sizes+num_ratios -1) # 以某个像素坐标为中心的锚框为n+m-1
    size_tensor = torch.tensor(sizes,device=device) # 将缩放比例列表sizes转为tensor, device参数指定设备
    ratio_tensor = torch.tensor(ratios,device=device)

    # 为了将锚点移动到像素的中心,需要设置偏移量。
    # 因为一个像素的高为1且宽为1,我们选择偏移我们的中心0.5
    offset_h, offset_w = 0.5, 0.5
    steps_h = 1.0 / in_height # 在y轴上缩放步⻓
    steps_w = 1.0 / in_width # 在x轴上缩放步⻓
    print(f'steps_h,steps_w = {steps_h,steps_w}')

    # 生成锚框的所有中心点
    center_h = (torch.arange(in_height, device=device) + offset_h) * steps_h
    center_w = (torch.arange(in_width, device=device) + offset_w) * steps_w
    print(f'center_h,center_w={center_h,center_w}')

    #网格化中心点坐标
    shift_y,shift_x = torch.meshgrid(center_h,center_w)
    #reshape成一维,shift_y和shift_x坐标一一对应
    shift_y,shift_x = shift_y.reshape(-1),shift_x.reshape(-1)
    print(f'shift_y, shift_x={shift_y, shift_x}') #

     #norm=√(H/W),这个就是个标号,方便计算
    norm = torch.sqrt(torch.tensor(in_height)/torch.tensor(in_width))

    # 生成“boxes_per_pixel”个高和宽,
    #只考虑包含s1或r1的组合,因此S*r1 与s1*R合并即为n+m-1个锚框
    w = torch.cat((size_tensor * torch.sqrt(ratio_tensor[0]),
                   size_tensor[0] * torch.sqrt(ratio_tensor[1:]))) * norm
    h = torch.cat((size_tensor / torch.sqrt(ratio_tensor[0]),
                   size_tensor[0] / torch.sqrt(ratio_tensor[1:]))) / norm

    # 获得归一化后的锚框的w,h的一半,形成偏移量,为了让归一化后的锚框根据中心点 + 偏移量找到 左上角和右下角坐标
    anchor_manipulations = torch.stack((-w, -h, w, h)).T.repeat(in_height * in_width, 1) / 2
    # 每个中心点都将有“boxes_per_pixel”个锚框,
    # 所以生成含所有锚框中心的网格,重复了“boxes_per_pixel”次
    out_grid = torch.stack([shift_x, shift_y, shift_x, shift_y],dim=1).repeat_interleave(boxes_per_pixel, dim=0)

      # 每个中心点都将有“boxes_per_pixel”个锚框,
    # 所以生成含所有锚框中心的网格,重复了“boxes_per_pixel”次
    out_grid = torch.stack([shift_x, shift_y, shift_x, shift_y],dim=1).repeat_interleave(boxes_per_pixel, dim=0)
    #(x_min,y_min,x_max,y_max) =  归一化后的锚框中心点 + 往左上角和右下角走的偏移量
    output = out_grid + anchor_manipulations
    return output.unsqueeze(0)
# 将锚框变量Y的形状更改为(图像高度,图像宽度,以同一像素为中心的锚框的数量,4)
boxes = Y.reshape(h, w, 5, 4)#                                                  此处的5由 缩放的数量n + 宽高比的数量m -1 而得
# 访问以(250,250)为中心的第一个锚框。它有四个元素:锚框左上⻆的(x, y)轴坐标和右下⻆的(x, y)轴坐标
boxes[250, 250, 0, :] # 输出的坐标是归一化后的,即归一化前的锚框 w/in_weight 和 h/in_height
img = d2l.plt.imread('../data/images/cat_and_dog.jpg')
h, w = img.shape[:2]
print(h, w)
X = torch.rand(size=(1, 3, h, w))
# 返回的锚框变量Y的形状是(批量大小,锚框的数量,4 (表示锚框的左上角右下角坐标))。
Y = multibox_prior(X, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5])
Y.shape

根据真实框来标注生成的锚框

请添加图片描述
请添加图片描述

# 计算IOU
def box_iou(boxes1,boxes2):
    '''
    :param boxes1: shape = (boxes1的数量,4)
    :param boxes2: shape = (boxes2的数量,4)
    :param areas1: boxes1中每个框的面积 ,shape = (boxes1的数量)
    :param areas2: boxes2中每个框的面积 ,shape = (boxes2的数量)
    :return:
    '''
    # 定义一个Lambda函数,输入boxes,内容是计算得到框的面积
    box_area = lambda  boxes:((boxes[:,2] - boxes[:,0]) * (boxes[:,3] - boxes[:,0]))
    # 计算面积
    areas1 = box_area(boxes1)
    areas2 = box_area(boxes2)
    # 计算交集 要把所有锚框的左上角坐标 与 真实框的所有左上角坐标 作比较,大的就是交集的左上角 ,加个None 可以让锚框与所有真实框作对比
    inter_upperlefts = torch.max(boxes1[:,None,:2],boxes2[:,:2])
    # 把所有锚框的右下角坐标 与 真实框的所有右下角坐标 作比较,小的就是交集的右下角坐标 ,加个None 可以让锚框与所有真实框作对比
    inter_lowerrights = torch.min(boxes1[:,None,2:],boxes2[:,2:])
    # 如果右下角-左上角有元素小于0,那就说明没有交集,clamp(min-0)会将每个元素与0比较,小于0的元素将会被替换成0
    inters = (inter_lowerrights - inter_upperlefts).clamp(min=0) # 得到w和h
    inter_areas = inters[:,:,0] * inters[:,:,1] # 每个样本的 w*h

    # 求锚框与真实框的并集
    # 将所有锚框与真实框相加,他们会多出来一个交集的面积,所以要减一个交集的面积
    union_areas = areas1[:,None] * areas2 - inter_areas
    return inter_areas/union_areas
# 每个真实框都要跟所有锚框计算iou,Iou数量等于,真实框数量 * 锚框的数量
def assign_anchor_to_bbox(ground_truth,anchors,devices,iou_threshold=0.5):
    # 得到锚框和真实框的个数
    num_anchors,num_gt_boxes = anchors.shape[0],ground_truth.shape[0]
    # jaccard是计算 所有锚框anchors和真实框ground_truth的交并比
    jaccard = box_iou(anchors,ground_truth)
    # torch.full(size,fill_value,dtype,device),如下代码生产成一个一位数组,长度为锚框的个数,值为-1
    anchors_bbox_map = torch.full((num_anchors,),-1,dtype=torch.long,device=devices)

    # 对行取最大值,得到每个真实框对应的最大IOU的锚框
    max_ious,indices = torch.max(jaccard,dim=1)
    # 返回张量中非0元素的索引,即Max_iou>设定的阈值,位于第i行和第j列的元素x_ij是锚框i和真实边界框j的IoU
    anc_i = torch.nonzero(max_ious>=iou_threshold).reshape(-1)
    box_j = indices[max_ious>=iou_threshold]
    anchors_bbox_map[anc_i] = box_j
    col_discard = torch.full((num_anchors,), -1)
    row_discard = torch.full((num_gt_boxes,), -1)
    for _ in range(num_gt_boxes):
        max_idx = torch.argmax(jaccard)
        box_idx = (max_idx % num_gt_boxes).long()
        anc_idx = (max_idx / num_gt_boxes).long()
        anchors_bbox_map[anc_idx] = box_idx
        jaccard[:, box_idx] = col_discard
        jaccard[anc_idx, :] = row_discard
    return anchors_bbox_map
# 标注类别和偏移量
def offset_boxes(anchors, assigned_bb, eps=1e-6):
    """对锚框偏移量的转换"""
    c_anc = d2l.box_corner_to_center(anchors)
    c_assigned_bb = d2l.box_corner_to_center(assigned_bb)
    offset_xy = 10 * (c_assigned_bb[:, :2] - c_anc[:, :2]) / c_anc[:, 2:]
    offset_wh = 5 * torch.log(eps + c_assigned_bb[:, 2:] / c_anc[:, 2:])
    offset = torch.cat([offset_xy, offset_wh], axis=1)
    return offset
'''
    如果一个锚框没有被分配真实边界框,将锚框的类别标记为背景。背景类别的锚框通常被称为负类锚框,其余的被称为正类锚框。
    我们使用真实边界框(labels参数)实现以下multibox_target函数,来标记锚框的类别和偏移量(anchors参数)。
    此函数将背景类别的索引设置为零,然后将新类别的整数索引递增一。
'''
def multibox_target(anchors, labels):
    """使用真实边界框标记锚框"""
    batch_size, anchors = labels.shape[0], anchors.squeeze(0)
    batch_offset, batch_mask, batch_class_labels = [], [], []
    device, num_anchors = anchors.device, anchors.shape[0]
    for i in range(batch_size):
        label = labels[i, :, :]
        anchors_bbox_map = assign_anchor_to_bbox(
            label[:, 1:], anchors, device)
        bbox_mask = ((anchors_bbox_map >= 0).float().unsqueeze(-1)).repeat(1, 4)
        # 将类标签和分配的边界框坐标初始化为零
        class_labels = torch.zeros(num_anchors, dtype=torch.long,
        device=device)
        assigned_bb = torch.zeros((num_anchors, 4), dtype=torch.float32,
        device=device)
        # 使用真实边界框来标记锚框的类别。
        # 如果一个锚框没有被分配,标记其为背景(值为零)
        indices_true = torch.nonzero(anchors_bbox_map >= 0)
        bb_idx = anchors_bbox_map[indices_true]
        class_labels[indices_true] = label[bb_idx, 0].long() + 1
        assigned_bb[indices_true] = label[bb_idx, 1:]
        # 使用真实边界框来标记锚框的类别。
        # 如果一个锚框没有被分配,标记其为背景(值为零)
        indices_true = torch.nonzero(anchors_bbox_map >= 0)
        bb_idx = anchors_bbox_map[indices_true]
        class_labels[indices_true] = label[bb_idx, 0].long() + 1
        assigned_bb[indices_true] = label[bb_idx, 1:]
        # 偏移量转换
        offset = offset_boxes(anchors, assigned_bb) * bbox_mask
        batch_offset.append(offset.reshape(-1))
        batch_mask.append(bbox_mask.reshape(-1))
        batch_class_labels.append(class_labels)

    bbox_offset = torch.stack(batch_offset)
    bbox_mask = torch.stack(batch_mask)
    class_labels = torch.stack(batch_class_labels)
    return (bbox_offset, bbox_mask, class_labels)
# 第一个元素表示类别,0代表狗,1代表猫。其余四个元素是左下角坐标和右上角坐标(归一化后的介于0-1之间),归一化的方法是,x坐标 / 宽,y坐标/高
ground_truth = torch.tensor([[0, 0.1, 0.08, 0.52, 0.92],
                             [1, 0.55, 0.2, 0.9, 0.88]])
# 锚框
anchors = torch.tensor([[0, 0.1, 0.2, 0.3], [0.15, 0.2, 0.4, 0.4],
                        [0.63, 0.05, 0.88, 0.98], [0.66, 0.45, 0.8, 0.8],
                        [0.57, 0.3, 0.92, 0.9]])
bbox_scale = torch.tensor((w, h, w, h))
# img = d2l.plt.imread('../data/images/cat_dog.png')
img = d2l.plt.imread('../data/images/cat_and_dog.jpg')
fig = d2l.plt.imshow(img)
# 画出真实框 :(坐标轴,归一化*bbox_scale得到原图规模的坐标,标签,颜色)
show_bboxes(fig.axes,ground_truth[:,1:] * bbox_scale,['dog','cat'],'k') # k最后画出来是黑色
# 画出设置的锚框,把锚框类别标记为0-4
show_bboxes(fig.axes, anchors * bbox_scale, ['0', '1', '2', '3', '4']);
'''
    labels[0]:
    labels[1]:掩码,形状为(批量大小,锚框数的4倍),对应每个锚框的4个偏移量(负类掩码为0),通过元素乘法,将负类的偏移量过滤掉
    labels[2]:锚框对应的标签
'''
labels = multibox_target(anchors.unsqueeze(dim=0),ground_truth.unsqueeze(dim=0))

非极大值抑制

请添加图片描述

'''
    在预测时,我们先为图像生成多个锚框,再为这些锚框一一预测类别和偏移量。一个预测好的边界框则根据其中某个带有预测偏移量的锚框而生成。下面我们实现了offset_inverse函数,该函数将锚框和偏移量预测作为输入,并应用逆偏移变换来返回预测的边界框坐标。
    输入: 锚框 和 偏移量预测
    输出:根据锚框的原始坐标和预测的偏移量 计算出的 预测的边界框坐标
'''
def offset_inverse(anchors, offset_preds):
    """根据带有预测偏移量的锚框来预测边界框"""
    anc = d2l.box_corner_to_center(anchors)
    pred_bbox_xy = (offset_preds[:, :2] * anc[:, 2:] / 10) + anc[:, :2]
    pred_bbox_wh = torch.exp(offset_preds[:, 2:] / 5) * anc[:, 2:]
    pred_bbox = torch.cat((pred_bbox_xy, pred_bbox_wh), axis=1)
    predicted_bbox = d2l.box_center_to_corner(pred_bbox)
    return predicted_bbox
'''按降序对置信度进行排序并返回其索引'''
#@save
def nms(boxes, scores, iou_threshold):
    """对预测边界框的置信度进行排序"""
    B = torch.argsort(scores, dim=-1, descending=True)
    keep = []
    # 保留预测边界框的指标
    while B.numel() > 0:
        i = B[0]
        keep.append(i)
        if B.numel() == 1: break
        iou = box_iou(boxes[i, :].reshape(-1, 4),
                      boxes[B[1:], :].reshape(-1, 4)).reshape(-1)
        inds = torch.nonzero(iou <= iou_threshold).reshape(-1)
        B = B[inds + 1]
    return torch.tensor(keep, device=boxes.device)
def multibox_detection(cls_probs, offset_preds, anchors, nms_threshold=0.5,pos_threshold=0.009999999):
    """使用非极大值抑制来预测边界框"""
    device, batch_size = cls_probs.device, cls_probs.shape[0]
    anchors = anchors.squeeze(0)
    num_classes, num_anchors = cls_probs.shape[1], cls_probs.shape[2]
    out = []
    for i in range(batch_size):
        cls_prob, offset_pred = cls_probs[i], offset_preds[i].reshape(-1, 4)
        conf, class_id = torch.max(cls_prob[1:], 0)
        
        '''调用offset_inverse'''
        predicted_bb = offset_inverse(anchors, offset_pred)
        
        '''调用nms'''
        keep = nms(predicted_bb, conf, nms_threshold)
        
        # 找到所有的non_keep索引,并将类设置为背景
        all_idx = torch.arange(num_anchors, dtype=torch.long, device=device)
        combined = torch.cat((keep, all_idx))
        uniques, counts = combined.unique(return_counts=True)
        non_keep = uniques[counts == 1]
        all_id_sorted = torch.cat((keep, non_keep))
        class_id[non_keep] = -1
        class_id = class_id[all_id_sorted]
        conf, predicted_bb = conf[all_id_sorted], predicted_bb[all_id_sorted]
        # pos_threshold是一个用于非背景预测的阈值

        below_min_idx = (conf < pos_threshold)
        class_id[below_min_idx] = -1
        conf[below_min_idx] = 1 - conf[below_min_idx]
        pred_info = torch.cat((class_id.unsqueeze(1),
                               conf.unsqueeze(1),
                               predicted_bb), dim=1)
        out.append(pred_info)
    return torch.stack(out)

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

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

相关文章

mysql 默认的4个数据库 介绍

mysql 存储MySQL的用户账号和权限信息&#xff0c;一些存储过程、事件的定义信息 一些运行过程中产生的日志信息&#xff0c;一些帮助信息以及时区信息等 information_schema 存储Mysql服务器 维护的所有其它数据库的信息&#xff0c;比如有哪些表、哪些视图、哪些触发器、哪…

C++设计模式(工厂方法模式)

文章目录 前言一、工厂方法模式介绍二、工厂方法模式和简单工厂模式对比三、工厂方法模式适用场景四、工厂方法模式示例代码总结 前言 本篇文章来带大家学习C中的工厂方法模式。 一、工厂方法模式介绍 工厂方法模式是一种创建型设计模式&#xff0c;用于通过工厂方法创建对象…

jdk新特性 02 .接口增强和函数式接口,方法引用

1.JDK8中接口的新增 在JDK8中针对接口有做增强&#xff0c;在JDK8之前 interface 接口名{ 静态常量; 抽象方法; }JDK8之后对接口做了增加&#xff0c;接口中可以有默认方法和静态方法 interface 接口名{ 静态常量; 抽象方法; 默认方法; 静态方法; }2.默认方法 2.1 为什么要增…

OS 内核级线程

用户级线程是两个栈&#xff0c;核心级线程是两套栈&#xff0c;用户栈和内核栈 用户级是并发&#xff08;同时触发、交替执行&#xff09;&#xff0c;这个是并行&#xff08;同时触发可以同时执行&#xff09; 进入内核的唯一方式是中断 根据TCB的切换&#xff0c;实现内核…

【经验贴】新手项目经理如何接手并管好项目?

最近有刷到这样一些求助帖&#xff1a;初入职场两三年的项目经理现在开始独立带项目&#xff0c;由于缺乏经验不知道从何下手&#xff0c;询问如何能快速接手并管好项目呢&#xff1f;这个话题也引起了大家的热议&#xff0c;今天就给大家分享一下一些实践经验。 1.刚拿到项目时…

如何做好项目进度管理?来看这几个要点!

8个项目管理工具模板、60个项目管理甘特图标模板、赠送30本项目管理电子书https://download.csdn.net/download/XMWS_IT/19886618?spm1001.2014.3001.5503 项目进度管理是指在项目实施过程中&#xff0c;对各阶段的进展程度和项目最终完成的期限所进行的管理。其目的是保证项目…

clickhouse-压测

一、数据集准备 数据集可以使用官网数据集&#xff0c;也可以用ssb-dbgen来准备 1.准备数据 这里最后生成表的数据行数为60亿行&#xff0c;数据量为300G左右 git clone https://github.com/vadimtk/ssb-dbgen.git cd ssb-dbgen/ make1.1 生成数据 # -s 指生成多少G的数据…

Linux C 多进程编程(面试考点)

嵌入式开发为什么要移植操作系统&#xff1f; 1.减小软硬件的耦合度&#xff0c;提高软件的移植性 2. 操作系统提供很多库和工具&#xff08;QT Open CV&#xff09;&#xff0c;提高开发效率 3.操作系统提供多任务机制&#xff0c;______________________? (提高C…

Zenity 简介

什么使 Zenity Zenity 是一个开源的命令行工具&#xff0c;它提供了一种简单的方式来创建图形化的用户界面&#xff08;GUI&#xff09;对话框&#xff0c;以与用户进行交互。它基于 GTK 库&#xff0c;可以在 Linux 和其他 UNIX-like 系统上使用。 Zenity 可以通过命令行或脚…

最新政策丨政务服务电子文件归档和电子档案管理办法说了什么?

随着数字化时代的持续演进&#xff0c;我国政府部门正积极推动数字政府建设&#xff0c;以优化政务服务&#xff0c;提升办事效率。为了适应这一背景&#xff0c;国务院发布了《政务服务电子文件归档和电子档案管理办法》&#xff0c;旨在规范电子档案管理&#xff0c;加强政务…

为什么使用消息队列?消息队列能够做什么?消息队列有哪些?怎么选择?

❤ 作者主页&#xff1a;李奕赫揍小邰的博客 ❀ 个人介绍&#xff1a;大家好&#xff0c;我是李奕赫&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 记得点赞、收藏、评论⭐️⭐️⭐️ &#x1f4e3; 认真学习!!!&#x1f389;&#x1f389; 文章目录 为什么使用消…

msvcp110.dll下载安装方法分享,教你怎么快速的修复msvcp110.dll文件

当你的电脑出现msvcp110.dll文件缺失时&#xff0c;这时候不要慌张&#xff0c;其实要解决这个问题很简单&#xff0c;我们只要重新下载安装msvcp110.dll文件就可以了&#xff0c;今天主要是来给大家讲解一下这方面的信息&#xff0c;教大家如何下载安装msvcp110.dll。 一.了解…

MPDIoU:有效和准确的边界框回归的损失

文章目录 摘要1、简介2、相关工作2.1、目标检测和实例分割2.2. 场景文本识别2.3、边界框回归的损失函数 3、点距最小的并集交点4、实验结果4.1、 实验设置4.2、数据集4.3、 评估协议4.4、 目标检测的实验结果4.5、 字符级场景文本识别的实验结果4.6、 实例分割的实验结果 5、 结…

Anomalib:异常检测的深度学习库 -- 应用Anomalib训练自己的图片

文章目录 资料汇总 Github链接&#xff1a;https://github.com/openvinotoolkit/anomalib/blob/main/README.md 论文链接&#xff1a;https://arxiv.org/pdf/2202.08341v1.pdf 其他参考资料&#xff1a;https://paperswithcode.com/paper/efficientad-accurate-visual-anomaly-…

突破限制,创造佳绩!国内工作流厂商助您腾飞!

随着业务量的激增&#xff0c;很多企业单位都想在办公领域更上一层楼&#xff0c;实现飞跃式地腾飞。采用什么样的软件设备能助力企业降本增质&#xff1f;国内工作流厂商流辰信息作为研发低代码技术平台的服务商&#xff0c;一直深知行业形式和发展动态&#xff0c;将全力以赴…

前端开发工具: VSCode

VSCode 安装使用教程&#xff08;图文版&#xff09; | arry老师的博客-艾编程 1. 下载 在官方网站&#xff1a;https://code.visualstudio.com/ 下载最新版本的 VSCode 即可 2. VSCode 常见插件安装 所有插件安装后,需要重启一下才生效 2.1 简体中文语言包 2.2 编辑器主…

四信重磅推出5G RedCap AIoT摄像机 RedCap轻量级5G终端新品首发!

6月6日&#xff0c;四信受邀出席移动物联网高质量发展论坛&#xff0c;并在移动物联网新产品发布环节隆重推出5G RedCap AIoT摄像机&#xff0c;再次抓紧需求先机&#xff0c;为行业用户创造无限可能&#xff01; 两大应用场景 助推RedCap走深向实 火遍全网络的RedCap应用场景可…

Git gui教程---第七篇 Git gui的使用 返回上一次提交

1&#xff0e; 查看历史&#xff0c;打开gitk程序 2&#xff0e; 选中需要返回的版本&#xff0c;右键&#xff0c;然后点击Rest master branch to here 3.出现弹窗 每个选项我们都试一下&#xff0c;从Hard开始 返回的选项 HardMixedSoft Hard 会丢失所有的修改【此处的…

List 去重两种方式:stream(需要JDK1.8及以上)、HashSet

1、使用Stream 方法 使用JDK1.8及以上 /*** Java合并两个List并去掉重复项的几种做法* param args*/public static void main(String[] args) {String[] str1 {"1", "2", "3", "4", "5", "6"};List<String&…

Protobuf 原理大揭秘

一、定义 Google推出的一种 结构化数据 的数据存储格式&#xff08;类似于 XML、Json &#xff09;。 多个版本的源码地址 https://github.com/protocolbuffers/protobuf/ 1、为什么选择它 优点&#xff1a; 效率高&#xff1a;Protobuf 以二进制格式存储数据&#xff0c;比…