week 8月6日

锚框
列项:有4个的边缘框,即,最后应该生成4个真实的框
行项:算法初始生成了9个锚框,即,要在9个初始框中筛选出4个最匹配的真实框 。然后对4个边缘框进行大小调整,至恰好合适 。
怎么匹配?用loU值
步骤1、找到全场,相似度最高的锚框,该框默认为未来的真实框1
步骤2、删除该锚框的所在行,所在列 。表示,已找到真实框1的预备框a,不需要再匹配框1和预备框a
步骤3、在剩下的8个预备框和3个未匹配的边缘框中,找到最匹配的一对 。即,相似度最高的锚框,重复步骤1、2
【week 8月6日】步骤4、重复步骤3,直至所有的真实框都找到预备框
当有许多锚框时,可能会输出很多相似的明显重叠的预测边界框,他们都围绕同一个目标 。如,dog的三条边界框
对于每一个预测边框,目标检测模型会给出一个概率(预测边框的置信度),将其从高到低排列,生成一个list 。
步骤1、选取list最高的预测边界框b1,得到所有与b1相似度(loU值)高于一定阈值的边界框,并删除之 。即,删除了dog的所有框,除了0.9那个 。
步骤2、重复步骤1,找到cat的唯一框,直到list中所有的框,要么被用作基准,要么被删除
%matplotlib inlineimport torchfrom d2l import torch as d2ltorch.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.5offset_h, offset_w = 0.5, 0.5#锚框大小按图片比例设置大小steps_h = 1.0 / in_heightsteps_w = 1.0 / in_width #生成锚框的所有中心点center_h = (torch.arange(in_height, device=device) + offset_h) * steps_hcenter_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_widthh = 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_manipulationsreturn output.unsqueeze(0)def show_bboxes(axes,bboxs,labels=None,colors=None):#@savedef _make_list(obj,default_values=None):if obj is None:obj = default_valueselif not isinstance(obj,(list,tuple)):obj = [obj]return objlabels = _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 = http://www.kingceram.com/post/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):#@savebox_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)函数表示将两个锚框不相交的宽和高赋值为0inners = (inner_lowrights-inner_upperlefts).clamp(min=0)'''inter_areas和union_areas的形状:(boxes1的数量,boxes2的数量)'''#求出相交部分的面积,不相交面积为0inner_areas = inners[:,:,0]*inners[:,:,1]#求出两个锚框面积的并集union_areas = areas1[:,None]+areas2-inner_areas#求出面积的交并比return inner_areas/union_areasdef 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=http://www.kingceram.com/post/-1,device=device,dtype=torch.long)# 位于第i行和第j列的元素x_ij是锚框i和真实边界框j的IoUjaccard = 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_indicescolumn_discard = torch.full((num_anchors,),fill_value=http://www.kingceram.com/post/-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#将真实边界框分配给锚框后所在的行和列都丢弃,赋值为-1jaccard[maxiou_i,:] = row_discardjaccard[:,maxiou_j] = column_discardreturn anchors_bboxes_mapdef 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 offsetdef multibox_target(anchors,labels):#@savebatch_size,anchors = labels.shape[0],anchors.squeeze(0)num_anchors,device = anchors.shape[0],anchors.devicebatch_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()+1assign_bb[anchors_idx] = label[bbox_idx,1:]# 计算真实边界框和锚框的偏移量,没有分配真实边界框的锚框的偏移量为0anchors_offset = offset_boxes(anchors,assign_bb)*anchors_maskbatch_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