目录
三.锚框的选择
3.1 交并比(IOU)
1. 定义
2.实现思路
3.实现
3.2 锚框标号
1.定义
2.实现思路
3.实现
对于生成的锚框,我们应当采取一种量化手段来评价当前锚框对于真实边界框的匹配度,等价于衡量锚框于真实边界框之间的相似性。因此,我们引入了交并比的概念。(即通过像素集的杰卡德系数来测量锚框和真实边框的相似性)。交并比的取值范围为[0, 1] , 0表示完全不重合, 1表示完全重合。
图示:
1)得到锚框和真实边框的面积
2)得到交集的左上和右下顶点坐标
3)计算每一个锚框和所有真实边款的交集,并集面积
4)将得到的面积与并集相除得到IOU值矩阵
第一步:得到锚框和真实边框的面积
def box_iou(boxes1, boxes2):
"""计算两个锚框或边界框列表中成对的交并比
boxes1 : 锚框
boxes2 : 真实边框
"""
box_area = lambda boxes: ((boxes[:, 2] - boxes[:, 0]) *
(boxes[:, 3] - boxes[:, 1]))
# boxes1,boxes2,areas1,areas2的形状:
# boxes1:(boxes1的数量,4),
# boxes2:(boxes2的数量,4),
# areas1:(boxes1的数量,),
# areas2:(boxes2的数量,)
areas1 = box_area(boxes1)
areas2 = box_area(boxes2)
第二步:交集的左上和右下顶点坐标
"""
这里运用的广播机制
boxes1.shape : [anchors_num, 4]
boxes2.shape : [classes_num, 4]
boxes1[:, None, :2].shape : [anchors_num, 1, 2]
torch.max(boxes1[:, None, :2], boxes2[:, :2]).shape
= [anchors_num, classes_num, 2]
通过广播机制能够将每个锚框与所有的真是边框进行计算,也就是一个锚框与classes_num种
真实边框进行计算。
"""
inter_upperlefts = torch.max(boxes1[:, None, :2], boxes2[:, :2])
inter_lowerrights = torch.min(boxes1[:, None, 2:], boxes2[:, 2:])
第三步:计算交集,并集面积
# tensor.clamp(min=0)的意思是如果值为负数则设为0 ,因为得到的交集的宽度和高度 >= 0
inters = (inter_lowerrights - inter_upperlefts).clamp(min=0)
# inter_areas的形状:(boxes1的数量,boxes2的数量)
"""
inters.shape : [anchors_num, class_num, 2]
提取宽度:inters[:, :, 0].shape : [anchors_num, classes_num]
提取高度:inters[:, :, 1].shape : [anchors_num, classes_num]
其中每一行为一个锚框与每个真实边框交集的宽高
"""
# 计算交集面积
inter_areas = inters[:, :, 0] * inters[:, :, 1]
# 计算并集面积
"""
这里依然运用了广播机制,将一个锚框和这个锚框与每种真实边框的交集相加
union_areas.shape : [anchors_num, classes_num]
"""
union_areas = areas1[:, None] + areas2 - inter_areas
第四步:返回IOU值矩阵
return inter_areas / union_areas
在拥有了对锚框的量化标准后,就可以通过算法来进行锚框的选择和标号。
在锚框的标号中,我们采取两步
a.选出当前IOU矩阵的最大值,将其下标进行存储,然后删除所在的行和列,循环执行
图示:
b) 设置IOU阈值,将高于IOU阈值的锚框下标进行存储。
如果不进行b步那么就会只有真实锚框个数个正类锚框, 其余全部是负类锚框。
1)用一个一维tensor来保存分配结果,下标表示第几个锚框,值表示列(类别)
2)由于找到最大的过程会修改IOU值矩阵,因此先找到大于阈值的锚框进行标号存储
3)循环找到全局最大值,进行存储
第一步:准备需要的数据
def assign_anchor_to_bbox(ground_truth, anchors, device=None, iou_thread=0.5):
"""
将最接近的真实边框分配给锚框
:param ground_truth: 真实框
:param anchors: 所有锚框
:param device: 设备
:param iou_thread: iou限度
:return: 锚框列表 索引为i 值为j
"""
# num_anchors : 锚框数量 num_get_boxes : 真实边框数量
num_anchors, num_get_boxes = anchors.shape[0], ground_truth.shape[0]
# 每个锚框与真实框的iou值
jaccard = box_iou(anchors, ground_truth)
# 生成初始一维tensor用来保存锚框标号 初始值为-1 长度为锚框数量
anchors_bbox_map = torch.full((num_anchors,), -1, dtype=torch.long,
device=device)
第二步:找到所有IOU值大于阈值的锚框进行标号
代码采取的是找到每行中的最大值(即每个锚框最有可能的类别)
# 返回一行的最大值 和 索引
max_ious, indices = torch.max(jaccard, dim=1)
# nonzero 得到非0元素的下标
# 得到每一行iou值大于0.5的行索引
anc_i = torch.nonzero(max_ious >= 0.5).reshape(-1)
# 类别索引
box_j = indices[max_ious >= 0.5]
# 保存相对应锚框的类别
anchors_bbox_map[anc_i] = box_j
代码难点:
1)torth.max(input, dim=None) : 返回的是dim维度下的最大值,和其维度索引
补充:torch.argmax(input, dim=None) : 仅仅返回索引
eg :
2) torch.nonzero : 返回非0元素的索引 ,按列布局。
eg:
第三步:循环找取全局最大值,并存储
# 由于IOU值的范围为[0, 1], 因此我们仅需将选中的最大值信息保存后,
# 将其所在行列的值修改为-1,就等价于删除
col_discard = torch.full((num_anchors,), -1) # 按照行数生成-1
row_discard = torch.full((num_gt_boxes,), -1) # 按照列数生成-1
# 循环寻找最大值,由于我们仅需要找到每种类别的最大值因此仅循环真实锚框数目次
for _ in range(num_gt_boxes):
# 将IOU矩阵 flatte然后得到全局最大值
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
# 修改其所在行例得值为-1
jaccard[:, box_idx] = col_discard
jaccard[anc_idx, :] = row_discard
第四步: 返回结果
return anchors_bbox_map