一.相关概念
锚框:以每个像素为中心,生成多个缩放比和宽高比不同的边界框,这些边界框被称为锚框。
二.技术实现
2.1 生成多个锚框
原理
对于给定的输入图像,我们主要确定锚框的缩放比和宽高比,同时在确定了缩放比和宽高比之后,锚框的宽度和高度就可以通过公式求出。
为了生成不同形状的锚框,我们需要设置多种不同的缩放比和宽高比,但同时为了避免生成过多,导致计算复杂度太高,我们从 n 种缩放比和 m 种宽高比中,分别取出一种,也就是 1 种缩放比和其他 m 种宽高比进行组合,1 种宽高比和 n 种缩放比进行组合,最后形成 m+n-1 个锚框。
代码实现
首先,我们要获取到图片的宽高,我们可以先采用 PIL 库中的 Image 读取图片,并利用torchvision 的 transform 工具箱的ToTensor()转化为 tensor 数据格式,并利用 shape 函数获取到图像的宽高
from PIL import Image
from torchvision import transforms
image = Image.open('data/luffy.jpeg').convert('RGB')
image = transforms.ToTensor()(image)
image.shape
输出:torch.Size([3, 354, 463])
image.shape[-2:]
获得:torch.Size([354, 463])
整体代码:
def multibox_prior(data,sizes,ratios):
in_height, in_width = data.shape[-2:]
device, num_sizes, num_ratios = data.device,len(sizes),len(ratios)
boxes_per_pixel = (num_sizes + num_ratios - 1)
size_tensor = torch.tensor(sizes,device=device)
ratio_tensor = torch.tensor(ratios,device=device)
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,indexing='ij')
shift_y, shift_x = shift_y.reshape(-1), shift_x.reshape(-1)
# 生成“boxes_per_pixel”个高和宽,
# 之后用于创建锚框的四角坐标(xmin,xmax,ymin,ymax)
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:])))
# 除以2来获得半高和半宽
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)
output = out_grid + anchor_manipulations
return output.unsqueeze(0)
2.2 计算交并比
通过计算锚框和真实边框之间的两个边界框相交面积和相并面积之比
#@save
def box_iou(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)
# inter_upperlefts,inter_lowerrights,inters的形状:
# (boxes1的数量,boxes2的数量,2)
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_areasandunion_areas的形状:(boxes1的数量,boxes2的数量)
inter_areas = inters[:, :, 0] * inters[:, :, 1]
union_areas = areas1[:, None] + areas2 - inter_areas
return inter_areas / union_areas
2.3 锚框标注
在创建好锚框,并有了计算交并比的函数之后,我们就可以进行锚框的标注了
对于锚框,我们主要标注两点:
- 类别:该锚框中的目标和哪个类别更贴切
- 偏移量:真实边界框相对于锚框的偏移量
对于类别而言,我们先找到每个类别下交并比最大的锚框,然后将以该锚框为中心的其他的锚框设置为 0。
找到每个类别最大交并比的锚框之后,对于剩余的锚框,进行对比,只有当该交并比大于预定义的阈值时,才将对应的类别赋值给该锚框。
对于没有被分配真实类别的锚框,我们将其标准为背景,背景类别经常被称为负类锚框,其余被称为正类锚框。
2.4 锚框去重
当然,这里会遇到一个问题,就是锚框之间可能会有相互重复的地方,这里我们采用极大值抑制法:
非极大值抑制的核心目标就是从这些众多的重叠边界框中,挑选出最具代表性、置信度最高且最准确的一个(或几个)边界框,抑制掉那些和高质量边界框重叠程度高且置信度相对较低的框。
具体步骤一般是:
- 首先,收集模型预测出的所有边界框,每个边界框都带有一个置信度得分(一般由分类分支给出的目标属于各类别的概率经过处理得到)。然后,按照置信度得分从高到低对这些边界框进行排序,优先处理置信度高的框。
- 从排序后的边界框列表中,取出置信度最高的边界框,将其作为一个基准框,这个框初步被认定为是高质量的、很可能准确框住目标的框,并且把它添加到最终要保留的边界框集合中。
- 对于剩下的边界框(除了已经选入最终集合的那个最高置信度框),依次计算它们与当前已选的这个基准框的交并比(Intersection over Union,IoU)。IoU 的计算方式是:两个边界框的交集区域面积除以它们的并集区域面积,其值范围在 0 到 1 之间,值越大表示两个框重叠程度越高。
- 根据设定的一个 IoU 阈值(比如常见的 0.5、0.7 等,可根据具体任务调整),如果某个剩余边界框与当前基准框的 IoU 值大于这个阈值,那就认为这个框和基准框重叠过多,很可能是对同一个目标的重复检测,所以将这个重叠度过高的边界框抑制掉(也就是从后续的筛选考虑中移除)。
重复上述 2 - 4 的步骤,也就是继续从剩下未被抑制且未被选入最终集合的边界框中,再选取置信度最高的框作为新的基准框,然后计算它与其余框的 IoU 并进行抑制,不断循环这个过程,直到所有的边界框都被处理完,即要么被选入最终保留的集合,要么被抑制掉。
经过上述一系列的操作后,最终留下的边界框集合就是经过非极大值抑制筛选后的高质量边界框,它们能更准确地表示图像中各个目标的位置,减少了冗余的、重复的边界框,为后续的目标定位、识别等进一步处理提供了更简洁、准确的结果。