Appearance
锚框
一、什么是锚框
在目标检测里,我们需要在图像里找目标的位置,但直接找目标的边界框太麻烦了。所以我们可以先在图像里生成很多预设的边界框,这些预设的边界框就叫 锚框(anchor box)。
锚框的作用是:
先在图像里生成很多不同大小、不同形状的锚框,然后判断每个锚框里有没有我们感兴趣的目标,
再调整锚框的位置和大小,让它更贴近目标的真实边界框。
二、怎么生成锚框
我们以图像的每个像素为中心,生成多个不同缩放比和宽高比的锚框。
缩放比:就是锚框的大小,比如缩放比 0.5,就是锚框的大小是图像的一半。
宽高比:就是锚框的宽度和高度的比例,比如宽高比 2,就是宽度是高度的 2 倍。
1. 锚框的尺寸计算
假设图像的高度是 h,宽度是 w,缩放比是 s,宽高比是 r,那么锚框的宽度是w*s*sqrt(r),高度是h*s/sqrt(r)。
2. 生成锚框的策略
为了避免生成太多锚框(导致计算量太大),我们不会用所有缩放比和宽高比的组合,而是只使用:
第一个缩放比和所有宽高比的组合
所有缩放比和第一个宽高比的组合
这样,每个像素中心生成的锚框数量是n+m-1(n 是缩放比的数量,m 是宽高比的数量),整个图像生成的锚框数量是w*h*(n+m-1)。
3. 生成锚框的代码实现(PyTorch 版)
python
import torch
from d2l import torch as d2l
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)
shift_y, shift_x = shift_y.reshape(-1), shift_x.reshape(-1)
# 生成锚框的宽和高
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
# 生成所有锚框的坐标
out_grid = torch.stack([shift_x, shift_y, shift_x, shift_y], dim=1)
out_grid = out_grid.repeat_interleave(boxes_per_pixel, dim=0)
output = out_grid + anchor_manipulations
return output.unsqueeze(0)4. 查看生成的锚框
我们可以用一张图片来测试生成的锚框:
python
# 加载图片
img = d2l.plt.imread('../img/catdog.jpg')
h, w = img.shape[:2]
# 生成锚框
X = torch.rand(size=(1, 3, h, w))
sizes = [0.75, 0.5, 0.25]
ratios = [1, 2, 0.5]
anchors = multibox_prior(X, sizes, ratios)
# 查看以(250,250)为中心的锚框
box_scale = torch.tensor((w, h, w, h), device=X.device)
bidx = 250 * w + 250
print(anchors[0, bidx, :] * box_scale)三、交并比(IoU):衡量锚框和真实边界框的相似度
我们用 交并比(Intersection over Union,IoU) 来衡量锚框和真实边界框的相似度,它是锚框和真实边界框的相交面积除以相并面积。
IoU 的取值范围是 0 到 1,越接近 1,说明两个框越相似。
1. IoU 的计算
假设锚框 A 的坐标是 (x1,y1,x2,y2),真实边界框 B 的坐标是 (x3,y3,x4,y4),那么:
相交区域的左上角坐标是 (max (x1,x3), max (y1,y3))
相交区域的右下角坐标是 (min (x2,x4), min (y2,y4))
相交面积是
max(0, min(x2,x4)-max(x1,x3)) * max(0, min(y2,y4)-max(y1,y3))相并面积是
(x2-x1)*(y2-y1) + (x4-x3)*(y4-y3) - 相交面积IoU = 相交面积 / 相并面积
2. IoU 的代码实现
python
def box_iou(boxes1, boxes2):
"""计算两个边界框的交并比"""
def box_area(boxes):
return (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
area1 = box_area(boxes1)
area2 = box_area(boxes2)
# 计算相交区域
inter_upleft = torch.max(boxes1[:, None, :2], boxes2[:, :2])
inter_botright = torch.min(boxes1[:, None, 2:], boxes2[:, 2:])
inter = torch.clamp(inter_botright - inter_upleft, min=0)
inter_area = inter[:, :, 0] * inter[:, :, 1]
union_area = area1[:, None] + area2 - inter_area
return inter_area / union_area四、在训练数据中标注锚框
在训练目标检测模型的时候,我们需要给每个锚框标注两个标签:
- 类别标签:这个锚框里的目标是什么类别,如果没有目标,就是背景类。
- 偏移量标签:真实边界框相对于锚框的位置和大小的偏移量。
1. 把真实边界框分配给锚框
我们用下面的算法把真实边界框分配给锚框:
- 计算所有锚框和真实边界框的 IoU,得到一个矩阵。
- 找到矩阵里最大的元素,把对应的真实边界框分配给对应的锚框,然后删除这个元素所在的行和列。
- 重复步骤 2,直到所有真实边界框都被分配出去。
- 对于剩下的锚框,如果它和某个真实边界框的 IoU 大于阈值(比如 0.5),就把这个真实边界框分配给它;否则,这个锚框就是背景类。
2. 标注偏移量
如果一个锚框被分配了真实边界框,我们需要标注它的偏移量,偏移量的计算方式是:
Plain
offset_x = (x_b - x_a) / w_a
offset_y = (y_b - y_a) / h_a
offset_w = log(w_b / w_a)
offset_h = log(h_b / h_a)其中,(x_a,y_a) 是锚框的中心坐标,w_a 和 h_a 是锚框的宽和高;(x_b,y_b) 是真实边界框的中心坐标,w_b 和 h_b 是真实边界框的宽和高。
为了让偏移量的分布更均匀,我们还会对这些偏移量进行标准化:
Plain
offset_x = (offset_x - mu_x) / sigma_x
offset_y = (offset_y - mu_y) / sigma_y
offset_w = (offset_w - mu_w) / sigma_w
offset_h = (offset_h - mu_h) / sigma_h默认的mu_x=mu_y=mu_w=mu_h=0,sigma_x=sigma_y=0.1,sigma_w=sigma_h=0.2。
五、非极大值抑制:去除重复的预测边界框
在预测的时候,我们会生成很多锚框,然后预测每个锚框的类别和偏移量,得到很多预测边界框。但这些预测边界框里,很多是围绕同一个目标的,我们需要把这些重复的边界框去掉,只保留最准确的一个,这个过程就是非极大值抑制(NMS)。
1. 非极大值抑制的步骤
- 把所有预测边界框按置信度(预测的类别概率)从高到低排序。
- 选取置信度最高的边界框作为基准,把所有和它的 IoU 大于阈值的边界框都去掉。
- 从剩下的边界框里,选取置信度第二高的边界框作为基准,去掉和它 IoU 大于阈值的边界框。
- 重复步骤 3,直到所有边界框都被处理过。
2. 非极大值抑制的代码实现
python
def nms(boxes, scores, iou_threshold):
"""非极大值抑制"""
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[i, :].reshape(-1, 4), boxes[B[1:], :].reshape(-1, 4)).reshape(-1)
inds = torch.nonzero(iou <= iou_threshold).reshape(-1)
B = B[inds + 1]
return torch.tensor(keep, device=boxes.device)六、小结
锚框是预设的边界框,用来在图像里采样区域,方便目标检测模型找到目标的位置。
我们以每个像素为中心,生成不同缩放比和宽高比的锚框,为了减少计算量,我们会用特定的策略生成锚框。
交并比(IoU)用来衡量锚框和真实边界框的相似度,是目标检测里很重要的指标。
在训练的时候,我们需要给每个锚框标注类别和偏移量,标注的时候会把真实边界框分配给最相似的锚框。
非极大值抑制用来去除重复的预测边界框,让输出的结果更简洁。
(注:文档部分内容可能由 AI 生成) 源地址