SSD目标检测的个人总结(2)—— IOU的计算

SSD目标检测的个人总结(2)—— IOU的计算和锚框的分配

前言

沐神的代码看了很久、B站上的视频也刷了很多遍,感叹下自己的基础确实不怎么扎实,锚框部分的底层代码几乎是一行行撸过来的,因此对整个学习的部分做一个总结先放上沐神的b站和电子教程,以示敬意!
《动手学深度学习》第二版预览版
跟李沐学AI

上一篇主要总结了锚框,以及如何以一副图像的每个像素点为中心,进行锚框的生成,具体可以移步:
SSD目标检测的个人总结(1)—— 锚框的生成

交并比

在目标检测任务中,如果在已知真实框的情况下,如何判断生成锚框的质量,如何选择生成的最优锚框?
通常我们会通过交并比,既杰卡德系数(Jaccard)来衡量,需要注意的是,杰卡德系数的计算结果在0-1之间,越接近0则代表锚框和真实框重合较少,越接近1则代表锚框和真实框重合度较高;等于0完全不重合,等于1完全重合
杰卡德系数的计算
我们以上一章节里使用的猫狗图片为例,假设我们事先定义好猫和狗的真实框,同时假定拥有5个anchors的坐标相对值,和两个真实框(ground_truth)的标签数据,每个真实框的标签数据的第一个元素为所属类别,在该例中,0代表dog,1代表cat;
在这里插入图片描述

我们可以逐一计算出每个锚框(anchors)与真实框(ground_truth)的IOU,具体计算如下:

# 构建的anchors和真实框
# 锚框和真实框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]])
# 计算交并比的部分
box_area = lambda boxes: ((boxes[:, 2] - boxes[:, 0]) *(boxes[:, 3] - boxes[:, 1]))
areas1 = box_area(anchors.unsqueeze(dim=0)[0,:,:])
areas2 = box_area(ground_truth.unsqueeze(dim=0)[0,:,:][:,1:])
# 取出左上角锚框和真实框交集部分较大的值,对anchors取前两个坐标同时保持当前维度
inter_upperlefts = torch.max(anchors.unsqueeze(dim=0)[0,:,:][:, None, :2], ground_truth.unsqueeze(dim=0)[0,:,:][:,1:][:, :2])
# 取出右下角锚框和真实框交集部分较小的值,对anchors取后两个坐标同时保持当前维度
inter_lowerrights = torch.min(anchors.unsqueeze(dim=0)[0,:,:][:, None, 2:], ground_truth.unsqueeze(dim=0)[0,:,:][:,1:][:, 2:])
inters = (inter_lowerrights - inter_upperlefts).clamp(min=0)
# 计算交集面积____矩形____长*宽
inter_areas = inters[:, :, 0] * inters[:, :, 1]
# 锚框面积+真实框面积-交集面积
union_areas = areas1[:, None] + areas2 - inter_areas
# 交并比 = 交集面积占并集面积的比例
# 数据格式为M个锚框与N个类别的真实框的IOU值的缩放
IOUS = inter_areas/union_areas

通过计算后返回IOU的值,进一步封装以后可以得到计算交并比的函数,提供锚框和真实框的具体坐标信息作为参数

def box_iou(boxes1, boxes2):
    """计算两个锚框或边界框列表中成对的交并比"""
    box_area = lambda boxes: ((boxes[:, 2] - boxes[:, 0]) *
                              (boxes[:, 3] - boxes[:, 1]))
    areas1 = box_area(boxes1)
    areas2 = box_area(boxes2)

    inter_upperlefts = torch.max(boxes1[:, None, :2], boxes2[:, :2])
    inter_lowerrights = torch.min(boxes1[:, None, 2:], boxes2[:, 2:])
    inters = (inter_lowerrights - inter_upperlefts).clamp(min=0)

    inter_areas = inters[:, :, 0] * inters[:, :, 1]
    union_areas = areas1[:, None] + areas2 - inter_areas
    return inter_areas / union_areas

计算真实框和锚框的交并比结果如下图所示,shape为(5,2),每一个元素内分别对应两个分类,如[0.00,0.75]则表示当前锚框与分类2的交并比为0.75
真实框和锚框计算IOU的结果

为真实框分配锚框

经过计算交并比IOU后,我们可以得到不同锚框anchors对应同一个真实框ground_truth的交并比,真实框的标签数据构成为类别+偏移量,对于IOU值最大的锚框,我们可以认为该锚框与真实框最为接近,因此可以将一个真实框的类别和偏移量分配给该锚框.具体实现代码如下

  1. 获得交并比相关信息,主要获取交并比大小,以及该大小所属的分类是什么
  2. 构建真实框与锚框的索引矩阵
  3. 创建行列掩码,全填充-1
  4. 根据真实框数量创建循环,IOU值最大的分类下找到索引,则将该数值所在的行\列填充为-1,知道所有真实框都被分配到锚框
# 分配真实框的部分
num_anchors, num_gt_boxes = anchors.shape[0], ground_truth.shape[0]
anchors_bbox_map = torch.full((num_anchors,), -1, dtype=torch.long)
# 返回最大交并比及其下标,IOUS的格式为,M个锚框,N个类别真实框;在N个类别上获得最大IOU的值;在N个类别上获得最大IOU值的下标
max_ious, indices = torch.max(IOUS, dim=1)
print('max_ious:',max_ious)
print('indices:',indices)
# 在maxiou上获得超过阈值的数组下标
anc_i = torch.nonzero(max_ious >= 0.5).reshape(-1)
# 在indices上获得超过阈值的值
box_j = indices[max_ious >= 0.5]
# # 建立对应坐标
# anc_i对应数组下标,box_j对应下标索引的值
anchors_bbox_map[anc_i] = box_j
# 有五列——对应五个锚框,有两行,对应两个真实框,创建空白的形状,统统填充-1
col_discard = torch.full((num_anchors,), -1)
row_discard = torch.full((num_gt_boxes,), -1)
# 根据真实边框数量,将真实边框分配给锚框
for _ in range(num_gt_boxes):
    # 获取最大IOU的索引,在使用argmax时,获取索引以顺序方式返回,类似于展平成一维后返回索引,如shape为(5,2)的tensor,若最大值在tensor[4][1]的位置,则直接返回9
    max_idx = torch.argmax(IOUS)
    print('max_idx is',max_idx)
    # 与真实边框取余数,作为列索引
    box_idx = (max_idx % num_gt_boxes).long()
    # 与真实边框取商,作为行索引
    anc_idx = (max_idx / num_gt_boxes).long()
    # 决定将哪个锚框分配给真实框,即将类别给对应的索引
    anchors_bbox_map[anc_idx] = box_idx
    # 对最大IOU所在的行列赋值为-1,因为一个类别的真实框只分配IOU最大的锚框,因此所在列的类别可以删除
    IOUS[:, box_idx] = col_discard
    IOUS[anc_idx, :] = row_discard

运行该代码后,我们可以得到几个关键变量:

  1. max_ious: 交并比计算后,在不同分类下,取交并比的最大值;
  2. indices: 不同交并比取最大之后所属的分类;
  3. anc_i: 大于thresholds阈值的交并比数值;
  4. box_j: anc_i所对应的类别;
  5. max_idx: 根据真实框所构建循环内的最大iou值的索引

在这里插入图片描述
通过上述代码的运行,我们可以为两个真实框分配到当前类别下iou值最大的锚框,我们通过进一步封装,提供,真实框信息,锚框信息,iou阈值等,以便后续代码调用,

def assign_anchor_to_bbox(ground_truth, anchors, device, iou_threshold=0.5):
    """将最接近的真实边界框分配给锚框"""
    num_anchors, num_gt_boxes = anchors.shape[0], ground_truth.shape[0]
    jaccard = box_iou(anchors, ground_truth)
    # 通过full,利用num_anchors构建坐标,有多少个锚框就有多少行
    anchors_bbox_map = torch.full((num_anchors,), -1, dtype=torch.long,
                                  device=device)
    max_ious, indices = torch.max(jaccard, dim=1)
    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):
        # 获取最大IOU的索引
        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

以我们提供的真实框和锚框的信息为输入,最后返回anchors_bbox_map,该信息存储了当前锚框和真实框iou值所对应的类别,-1为未小于iou阈值的类别,统一标记为-1.

anchors_bbox_map is : tensor([-1, 0, 1, -1, 1])

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值