锚框
列项:有4个的边缘框,即,最后应该生成4个真实的框
行项:算法初始生成了9个锚框,即,要在9个初始框中筛选出4个最匹配的真实框。然后对4个边缘框进行大小调整,至恰好合适。
怎么匹配?用loU值
步骤1、找到全场,相似度最高的锚框,该框默认为未来的真实框1
步骤2、删除该锚框的所在行,所在列。表示,已找到真实框1的预备框a,不需要再匹配框1和预备框a
步骤3、在剩下的8个预备框和3个未匹配的边缘框中,找到最匹配的一对。即,相似度最高的锚框,重复步骤1、2
步骤4、重复步骤3,直至所有的真实框都找到预备框
当有许多锚框时,可能会输出很多相似的明显重叠的预测边界框,他们都围绕同一个目标。如,dog的三条边界框
对于每一个预测边框,目标检测模型会给出一个概率(预测边框的置信度),将其从高到低排列,生成一个list。
步骤1、选取list最高的预测边界框b1,得到所有与b1相似度(loU值)高于一定阈值的边界框,并删除之。 即,删除了dog的所有框,除了0.9那个。
步骤2、重复步骤1,找到cat的唯一框,直到list中所有的框,要么被用作基准,要么被删除
%matplotlib inline
import torch
from d2l import torch as d2l
torch.set_printoptions(3)#精简输出精度
def multibox_prior(data, sizes, ratios):#@save
#以每个像素点为中心生成具有不同形状的锚框
'''
agrs:
data: tensor
[batch_size, channel, height, width]
sizes: list
ratios: list
'''
in_height, in_width = data.shape[-2:]
device, num_size, num_ratios = data.device, len(sizes), len(ratios)
#num_size个缩放比取值 num_rations个宽高比取值
boxes_per_pixel = (num_size + num_ratios - 1)#1个像素点对应的锚框个数
size_tensor = torch.tensor(sizes, device=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
steps_w = 1.0 / in_width
#生成锚框的所有中心点
center_h = (torch.arange(in_height, device=device) + offset_h) * steps_h
center_w = (torch.arange(in_width, device=device) + offset_w) * steps_w
'''将中心点网格化之后拉平,进行匹配'''
shift_y, shift_x = torch.meshgrid(center_h, center_w)
shift_y, shift_x = shift_y.reshape(-1), shift_x.reshape(-1)
#注意这里合并的为sizes元素与第一个ratios相乘和第一个size元素与ratios第一个元素之后所有元素相乘的结果
w = torch.cat(
(size_tensor * torch.sqrt(ratio_tensor[0]),
sizes[0] * torch.sqrt(ratio_tensor[1:])))*in_height / in_width
h = torch.cat(
(size_tensor / torch.sqrt(ratio_tensor[0]),
sizes[0] / torch.sqrt(ratio_tensor[1:])))
#获取锚框的所有高宽,并进行转置,重复处理,与格点数量匹配
anchor_manipulations = torch.stack((-w,-h,w,h)).T.repeat(in_height * in_width, 1) / 2
#每一行代表一个像素点的锚框的高和宽,因为一个像素点有boxes_per_pixel个锚框,
#因此每boxes_per_pixel行代表一个像素的所有锚框。
#因为所有像素点的锚框个数和高宽都是一样的,因此需要复制in_height*in_width次,
#所以anchor_manipulations.size=(5x561x728,4)
out_grid = torch.stack([shift_x, shift_y, shift_x, shift_y],
dim=1).repeat_interleave(boxes_per_pixel, dim=0)
#因此out_grid与anchor_manipulations相加得到一个像素点中一个锚框的左上,右下的坐标,
#因此每boxes_per_pixel行代表一个像素点的所有锚框的左上,右下坐标值,
#也相当于生成所有像素点的所有锚框
output = out_grid + anchor_manipulations
return output.unsqueeze(0)
def show_bboxes(axes,bboxs,labels=None,colors=None):#@save
def _make_list(obj,default_values=None):
if obj is None:
obj = default_values
elif not isinstance(obj,(list,tuple)):
obj = [obj]
return obj
labels = _make_list(labels)
colors = _make_list(colors,['b','g','r','m','c'])
for i,bbox in enumerate(bboxs):
color = colors[i % len(colors)]
rect = d2l.bbox_to_rect(bbox.detach().numpy(),color)
axes.add_patch(rect)
if labels and len(labels)>i:
test_color = 'k' if color=='w' else 'w'
axes.text(rect.xy[0],rect.xy[1],labels[i],va='center',ha='center',
fontsize=9,color=test_color,bbox=dict(facecolor=color,lw=0))
img = d2l.plt.imread('C:\\Users\\13930\\Pictures\\Saved Pictures\\猫狗.jpg')
h, w = img.shape[:2]
data = torch.rand(size=(1,3,h,w))
output = multibox_prior(data,sizes=[0.75,0.5,0.25],ratios=[1,2,0.5])
#返回的锚框变量output的形状是(批量大小,锚框的数量,4)。
print(output.shape)
print(h,w)
d2l.set_figsize()
bbox_scale = torch.tensor((w,h,w,h))
fig = d2l.plt.imshow(img)
show_bboxes(axes=fig.axes,bboxs=boxes[150,150,:,:]*bbox_scale,labels=['s=0.75, r=1', 's=0.5, r=1', 's=0.25, r=1', 's=0.75, r=2',
's=0.75, r=0.5'])
"""计算两个锚框或边界框列表中成对的交并比"""
def boxes_iou(boxes1,boxes2):#@save
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)
'''
inter_upperlefts,inter_lowerrights,inters的形状:
(boxes1的数量,boxes2的数量,2)
'''
#计算相交面积中的左上点的坐标
inner_upperlefts = torch.max(boxes1[:,None,:2],boxes2[:,:2])
#计算相交面积中的右下点的坐标
inner_lowrights = torch.min(boxes1[:,None,2:],boxes2[:,2:])
#求出相交面积的宽和高,并且宽和高最小值必须大于0,因此加上clamp(min=0)函数表示将两个锚框不相交的宽和高赋值为0
inners = (inner_lowrights-inner_upperlefts).clamp(min=0)
'''
inter_areas和union_areas的形状:(boxes1的数量,boxes2的数量)
'''
#求出相交部分的面积,不相交面积为0
inner_areas = inners[:,:,0]*inners[:,:,1]
#求出两个锚框面积的并集
union_areas = areas1[:,None]+areas2-inner_areas
#求出面积的交并比
return inner_areas/union_areas
def assign_anchors_to_boxes(anchors,ground_truth,device,iou_threshold=0.5):#@save
"""将最接近的真实边界框分配给锚框"""
num_anchors,num_gt_boxes = anchors.shape[0],ground_truth.shape[0]
# 对于每个锚框,分配的真实边界框的张量
anchors_bboxes_map = torch.full((num_anchors,),fill_value=-1,device=device,dtype=torch.long)
# 位于第i行和第j列的元素x_ij是锚框i和真实边界框j的IoU
jaccard = boxes_iou(anchors,ground_truth)
#计算出每行IOU最大值,然后将这个值对应的真实边界框的索引分配给这个当前的锚框
max_iou,indexing = torch.max(jaccard,dim=1)
# 根据阈值,决定是否分配真实边界框
anchors_i = torch.nonzero(max_iou>=iou_threshold).reshape(-1)
box_indices = indexing[max_iou>=iou_threshold]
anchors_bboxes_map[anchors_i] = box_indices
column_discard = torch.full((num_anchors,),fill_value=-1)
row_discard = torch.full((num_gt_boxes,),fill_value=-1)
for _ in range(num_gt_boxes):
#计算矩阵中IOU最大值的元素所在行和列索引,行代表锚框的索引,列代表真实边界框的索引,然后将真实边界框分配给这个锚框
maxiou_index = torch.argmax(jaccard)
maxiou_i = (maxiou_index/num_gt_boxes).long()
maxiou_j = (maxiou_index%num_gt_boxes).long()
anchors_bboxes_map[maxiou_i] = maxiou_j
#将真实边界框分配给锚框后所在的行和列都丢弃,赋值为-1
jaccard[maxiou_i,:] = row_discard
jaccard[:,maxiou_j] = column_discard
return anchors_bboxes_map
def offset_boxes(anchors,assign_bboxes,eps = 1e-6):#@save
"""对锚框偏移量的转换,计算分配的真实边界框与对应的锚框的偏移量"""
anchors_center = d2l.box_corner_to_center(anchors)
assign_bboxes_center = d2l.box_corner_to_center(assign_bboxes)
offset_xy = 10*(assign_bboxes_center[:,:2]-anchors_center[:,:2])/anchors_center[:,2:]
offset_wh = 5*torch.log(eps+assign_bboxes_center[:,2:]/anchors_center[:,2:])
offset = torch.cat((offset_xy,offset_wh),dim=1)
print('offset.shape = ',offset.shape)
return offset
def multibox_target(anchors,labels):#@save
batch_size,anchors = labels.shape[0],anchors.squeeze(0)
num_anchors,device = anchors.shape[0],anchors.device
batch_mask,batch_offset,batch_class_labels = [],[],[]
for i in range(batch_size):
label = labels[i,:,:]
anchors_bboxes_map = assign_anchors_to_boxes(anchors,label[:,1:],device)
# 初始化锚框和真实边界框之间偏移量的掩码,因为偏移量每行有四个元素,因此掩码每一行有四个元素,初始化为0或1,0代表这个锚框没有分配给任何一个真实边界框,1代表这个锚框已经分配给了一个真实边界框
anchors_mask = ((anchors_bboxes_map>=0).float().unsqueeze(-1)).repeat(1,4)
# 将类标签和分配的边界框坐标初始化为零
class_label = torch.zeros(num_anchors,device=device,dtype=torch.long)
assign_bb = torch.zeros(size=(num_anchors,4),device=device,dtype=torch.float32)
'''
使用真实边界框来标记锚框的类别。
如果一个锚框没有被分配,我们标记其为背景(值为零)
'''
anchors_idx = torch.nonzero(anchors_bboxes_map>=0).reshape(-1)
bbox_idx = anchors_bboxes_map[anchors_idx]
class_label[anchors_idx] = label[bbox_idx,0].long()+1
assign_bb[anchors_idx] = label[bbox_idx,1:]
# 计算真实边界框和锚框的偏移量,没有分配真实边界框的锚框的偏移量为0
anchors_offset = offset_boxes(anchors,assign_bb)*anchors_mask
batch_mask.append(anchors_mask.reshape(-1))
batch_offset.append(anchors_offset.reshape(-1))
batch_class_labels.append(class_label)
bboxes_mask = torch.stack(batch_mask)
bboxes_class_label = torch.stack(batch_class_labels)
bboxes_offset = torch.stack(batch_offset)
return (bboxes_offset,bboxes_mask,bboxes_class_label)
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])
fig = d2l.plt.imshow(img)
show_bboxes(axes=fig.axes,bboxs=ground_truth[:,1:]*bbox_scale,labels=['dog','cat'],colors='k')
#因为真实Img图片并没有进行高和宽缩放,因此需要将已经缩放的锚框的高和宽重新进行扩展
show_bboxes(axes=fig.axes,bboxs=anchors*bbox_scale,labels=['0','1','2','3','4'])
def offset_inverse(anchors,offset_preds):#@save
"""根据带有预测偏移量的锚框来预测边界框,应用逆偏移变换来返回预测的边界框坐标"""
anchors = d2l.box_corner_to_center(anchors)
predict_bbox_xy = (offset_preds[:,:2]*anchors[:,2:])/10+anchors[:,:2]
predict_bbox_wh = torch.exp(offset_preds[:,2:]/5)*anchors[:,2:]
predict_bbox = torch.cat((predict_bbox_xy,predict_bbox_wh),dim=1)
predict_bbox_corner = d2l.box_center_to_corner(predict_bbox)
return predict_bbox_corner
#nms相当于去掉预测真实边界框重复率比较大的一些预测真实边界框
def nms(boxes,scores,iou_threshold):#@save
"""对预测边界框的置信度进行排序"""
#从大到小排序,得到排序后的元素在排序前的索引值
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_iou(boxes1=boxes[i,:].reshape(-1,4),boxes2=boxes[B[1:],:].reshape(-1,4)).reshape(-1)
box_iou_idx = torch.nonzero(box_iou<=iou_threshold).reshape(-1)
B = B[box_iou_idx+1]
return torch.tensor(keep,device=boxes.device)
"""使用非极大值抑制来预测边界框"""
def multibox_detection(cls_probs,offset_preds,anchors,nms_threshold=0.5,pos_threshold=0.009999999):#@save
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 = cls_probs[i]
offset_pred = offset_preds[i].reshape(-1,4)
conf,class_id = torch.max(cls_prob[1:],dim=0)
#根据anchors和锚框偏移量得到预测真实边界框的值
predict_bb = offset_inverse(anchors,offset_pred)
#根据nms去除一些重合比较大的预测真实边界框,保留重合率不大的预测真实边界框
keep = nms(predict_bb,conf,nms_threshold)
# 找到所有的non_keep索引,并将类设置为背景-1
anchors_idx = torch.arange(num_anchors,device=device,dtype=torch.long)
idx = torch.cat((keep,anchors_idx))
uniques,counts = idx.unique(return_counts=True)
non_keep = uniques[counts==1]
class_id[non_keep] = -1
anchors_all_id = torch.cat((keep,non_keep))
# pos_threshold是一个用于非背景预测的阈值,将低于pos_threshold阈值的预测真实边界框的类别设置为背景类-1
below_min_idx = (conf<pos_threshold)
class_id[below_min_idx] = -1
conf[below_min_idx] = 1-conf[below_min_idx]
#对class_id,conf,predict_bb按照anchors_all_id索引列表重复排列
class_id = class_id[anchors_all_id]
conf = conf[anchors_all_id]
predict_bb = predict_bb[anchors_all_id]
predict_info = torch.cat((class_id.unsqueeze(1),conf.unsqueeze(1),predict_bb),dim=1)
out.append(predict_info)
return torch.stack(out)
anchors = torch.tensor([[0.1, 0.08, 0.52, 0.92], [0.08, 0.2, 0.56, 0.95],
[0.15, 0.3, 0.62, 0.91], [0.55, 0.2, 0.9, 0.88]])
offset_preds = torch.tensor([0]*anchors.numel())
cls_probs = torch.tensor([[0] * 4, # 背景的预测概率
[0.9, 0.8, 0.7, 0.1], # 狗的预测概率
[0.1, 0.2, 0.3, 0.9]]) # 猫的预测概率
fig = d2l.plt.imshow(img)
show_bboxes(fig.axes,anchors*bbox_scale,labels=['dog=0.9', 'dog=0.8', 'dog=0.7', 'cat=0.9'])
out = multibox_detection(cls_probs.unsqueeze(0),offset_preds.unsqueeze(0),anchors.unsqueeze(0),nms_threshold=0.5)
print(out)
fig = d2l.plt.imshow(img)
for i in out[0].detach().numpy():
#当类别是背景类-1时不需要在图片中将预测真实边界框展示出来
if i[0] == -1:
continue
label = ('dog=','cat=')[int(i[0])]+str(i[1])
show_bboxes(fig.axes,[torch.tensor(i[2:])*bbox_scale],label)
1、生成多个锚框
2、根据交并比,将真实框分配给锚框
3、对分配好的,标注类别和偏移量
4、根据偏移量,留下最准确的边界框,并显示出来