from math import sqrt
import itertools
import numpy as np
import torch
class DefaultBoxes(object):
def __init__(self):
self.fig_size = 300 # 输入网络的图像大小 300
self.feat_size = [38, 19, 10, 5, 3, 1] # 每个预测层的feature map尺寸: [38, 19, 10, 5, 3, 1]
self.scales = [21, 45, 99, 153, 207, 261, 315] # 每个特征层上预测的default box的scale: [21, 45, 99, 153, 207, 261, 315]
self.aspect_ratios = [[2], [2, 3], [2, 3], [2, 3], [2], [2]] # 每个预测特征层上预测的default box的ratios: [[2], [2, 3], [2, 3], [2, 3], [2], [2]]
# According to https://github.com/weiliu89/caffe
# Calculation method slightly different from paper
self.steps = [8, 16, 32, 64, 100, 300] # 每个特征层上的一个cell在原图上的跨度: [8, 16, 32, 64, 100, 300]
fk = self.fig_size / np.array(self.steps) # 计算每层特征层的fk
self.scale_xy_ = 0.1
self.scale_wh_ = 0.2
self.default_boxes = []
# 遍历每层特征层,计算default box
for idx, sfeat in enumerate(self.feat_size):
# 先计算两个 高宽比为 1:1 的 default box 的 高和宽 (相对高宽 0~1)
sk1 = self.scales[idx] / self.fig_size # scale转为相对值[0-1]
sk2 = self.scales[idx + 1] / self.fig_size # scale转为相对值[0-1]
sk3 = sqrt(sk1 * sk2)
all_sizes = [(sk1, sk1), (sk3, sk3)]
# 再将剩下 高宽比为 1/2、2、1/3、3 的 default box 的宽和高添加到 all_sizes 中
for alpha in self.aspect_ratios[idx]:
w, h = sk1 * sqrt(alpha), sk1 / sqrt(alpha)
all_sizes.append((w, h))
all_sizes.append((h, w))
# 计算特征图上的每个像素 映射到原图上的位置(default box 的中心坐标)
for w, h in all_sizes:
for i, j in itertools.product(range(sfeat), repeat=2): # i -> 行(y), j -> 列(x)
# 计算每个default box的中心坐标。
# 这么设计是为了保证 default box 的中心点都均匀的落在原图像中,
# 想象一下最小的 1x1 的 feature map,它对应的 default box 的中心点应该落在原图像的中心 (0.5, 0.5)
cx, cy = (j + 0.5) / fk[idx], (i + 0.5) / fk[idx]
self.default_boxes.append((cx, cy, w, h))
# 将default_boxes 转为 tensor格式
self.dboxes = torch.as_tensor(self.default_boxes, dtype=torch.float32)
self.dboxes.clamp_(min=0, max=1) # 将坐标(x, y, w, h)都限制在0-1之间
# 将(x, y, w, h)转换成(xmin, ymin, xmax, ymax),方便后续计算IoU(匹配正负样本)
# ltrb is left top coordinate and right bottom coordinate
self.dboxes_ltrb = self.dboxes.clone()
self.dboxes_ltrb[:, 0] = self.dboxes[:, 0] - 0.5 * self.dboxes[:, 2] # xmin
self.dboxes_ltrb[:, 1] = self.dboxes[:, 1] - 0.5 * self.dboxes[:, 3] # ymin
self.dboxes_ltrb[:, 2] = self.dboxes[:, 0] + 0.5 * self.dboxes[:, 2] # xmax
self.dboxes_ltrb[:, 3] = self.dboxes[:, 1] + 0.5 * self.dboxes[:, 3] # ymax
@property
def scale_xy(self):
return self.scale_xy_
@property
def scale_wh(self):
return self.scale_wh_
def __call__(self, order='ltrb'):
# 根据需求返回对应格式的default box
if order == 'ltrb':
return self.dboxes_ltrb
if order == 'xywh':
return self.dboxes
dboxes = DefaultBoxes() # default box 的shape 为 [8732, 4]
【SSD】之 DefaultBoxes
于 2023-02-17 20:45:53 首次发布