这个文件在用l1损失的时候会用到
主要作用是,输入预测框和anchor输出偏移量,或者输入偏移量和anchor输出预测框
类定义:
class DeltaXYWHAOBBoxCoder(BaseBBoxCoder):
"""Delta XYWHA OBBox 编码器。这个编码器用于旋转对象检测
(例如DOTA数据集的任务1)。该编码器可以将边界框
(xc, yc, w, h, a) 编码为偏移量 (dx, dy, dw, dh, da),并且可以将偏移量
(dx, dy, dw, dh, da) 解码回原始边界框 (xc, yc, w, h, a)。
参数:
target_means (Sequence[float]): 目标偏移量的去标准化均值。
target_stds (Sequence[float]): 目标偏移量的去标准化标准差。
angle_range (str, optional): 角度表示方式。默认为'oc'。
norm_factor (None|float, optional): 角度的规范化因子。
edge_swap (bool, optional): 如果宽度小于高度是否交换边。默认为False。
proj_xy (bool, optional): 是否根据角度投影x和y。默认为False。
add_ctr_clamp (bool): 是否添加中心夹紧,当添加时,如果预测框的中心偏离原始锚点的中心过远,则会被夹紧。仅YOLOF使用。默认为False。
ctr_clamp (int): 最大像素偏移量夹紧。仅YOLOF使用。默认为32。
"""
def __init__(self,
target_means=(0., 0., 0., 0., 0.),
target_stds=(1., 1., 1., 1., 1.),
angle_range='oc',
norm_factor=None,
edge_swap=False,
proj_xy=False,
add_ctr_clamp=False,
ctr_clamp=32):
super(BaseBBoxCoder, self).__init__()
self.means = target_means # 目标偏移量的均值
self.stds = target_stds # 目标偏移量的标准差
self.angle_range = angle_range # 角度表示方式
self.norm_factor = norm_factor # 角度的规范化因子
self.edge_swap = edge_swap # 宽高交换标志
self.proj_xy = proj_xy # x和y的投影标志
self.add_ctr_clamp = add_ctr_clamp # 中心夹紧标志
self.ctr_clamp = ctr_clamp # 中心夹紧的最大像素偏移量
编码:
def encode(self, bboxes, gt_bboxes):
"""获取可以用来将``bboxes``变换到``gt_bboxes``的边界框回归变换量。
参数:
bboxes (torch.Tensor): anchor
gt_bboxes (torch.Tensor): GT
返回:
torch.Tensor: 边界框变换量
"""
assert bboxes.size(0) == gt_bboxes.size(0)
assert bboxes.size(-1) == 5
assert gt_bboxes.size(-1) == 5
if self.angle_range in ['oc', 'le135', 'le90']:
# 利用bbox2delta函数将得到的差值返回
return bbox2delta(bboxes, gt_bboxes, self.means, self.stds,
self.angle_range, self.norm_factor,
self.edge_swap, self.proj_xy)
else:
raise NotImplementedError
解码:
def decode(self,
bboxes,
pred_bboxes,
max_shape=None,
wh_ratio_clip=16 / 1000):
"""将`pred_bboxes`的变换应用于`bboxes`上。
参数:
bboxes (torch.Tensor): anchor。形状为(B, N, 5)或(N, 5)
pred_bboxes (torch.Tensor): 编码偏移量
形状可以是(B, N, num_classes * 5)或(B, N, 5)或
(N, num_classes * 5)或(N, 5)。
max_shape (Sequence[int] or torch.Tensor or Sequence[
Sequence[int]], optional): 边界框的最大界限,指定
(H, W, C)或(H, W)。如果bboxes的形状是(B, N, 5),那么
max_shape应该是一个Sequence[Sequence[int]]
并且max_shape的长度也应该是B。
wh_ratio_clip (float, optional): 允许的宽高比。
返回:
torch.Tensor: 解码后的边界框。
"""
assert pred_bboxes.size(0) == bboxes.size(0)
if self.angle_range in ['oc', 'le135', 'le90']:
#上面是判断、下面进行转换
return delta2bbox(bboxes, pred_bboxes, self.means, self.stds,
max_shape, wh_ratio_clip, self.add_ctr_clamp,
self.ctr_clamp, self.angle_range,
self.norm_factor, self.edge_swap, self.proj_xy)
else:
raise NotImplementedError
计算GT和anchor的偏移量
def bbox2delta(proposals,
gt,
means=(0., 0., 0., 0., 0.),
stds=(1., 1., 1., 1., 1.),
angle_range='oc',
norm_factor=None,
edge_swap=False,
proj_xy=False):
"""
计算提议框相对于真实边界框的偏移量(deltas)。
参数:
proposals (torch.Tensor): 要转换的边界框,形状为(N, ..., 5)
gt (torch.Tensor): 作为基准的真实边界框,形状为(N, ..., 5)
means (Sequence[float]): 偏移量坐标的去标准化均值
stds (Sequence[float]): 偏移量坐标的去标准化标准差
angle_range (str, optional): 角度表示方式,默认为'oc'
norm_factor (None|float, optional): 角度的规范化因子
edge_swap (bool, optional): 如果宽度小于高度是否交换边,默认为False
proj_xy (bool, optional): 是否根据角度投影x和y,默认为False
返回:
Tensor: 形状为(N, 5)的偏移量,其中列代表dx, dy, dw, dh, da。
"""
# 确保提议框和真实边界框的尺寸相同
assert proposals.size() == gt.size()
# 转换为浮点类型
proposals = proposals.float()
gt = gt.float()
# 解绑提议框和真实边界框的坐标和角度
px, py, pw, ph, pa = proposals.unbind(dim=-1)
gx, gy, gw, gh, ga = gt.unbind(dim=-1)
# 根据是否进行x和y的角度投影来计算dx和dy
if proj_xy:
dx = (torch.cos(pa) * (gx - px) + torch.sin(pa) * (gy - py)) / pw
dy = (-torch.sin(pa) * (gx - px) + torch.cos(pa) * (gy - py)) / ph
else:
dx = (gx - px) / pw
dy = (gy - py) / ph
# 如果启用了边缘交换
if edge_swap:
# 计算角度差,并判断使用哪个角度差
dtheta1 = norm_angle(ga - pa, angle_range)
dtheta2 = norm_angle(ga - pa + np.pi / 2, angle_range)
abs_dtheta1 = torch.abs(dtheta1)
abs_dtheta2 = torch.abs(dtheta2)
# 根据角度差调整宽高
gw_regular = torch.where(abs_dtheta1 < abs_dtheta2, gw, gh)
gh_regular = torch.where(abs_dtheta1 < abs_dtheta2, gh, gw)
da = torch.where(abs_dtheta1 < abs_dtheta2, dtheta1, dtheta2)
dw = torch.log(gw_regular / pw)
dh = torch.log(gh_regular / ph)
else:
# 不交换边缘时直接计算角度差和宽高比
da = norm_angle(ga - pa, angle_range)
dw = torch.log(gw / pw)
dh = torch.log(gh / ph)
# 如果有角度的规范化因子,对角度差进行规范化
if norm_factor:
da /= norm_factor * np.pi
# 组合dx, dy, dw, dh, da为偏移量
deltas = torch.stack([dx, dy, dw, dh, da], dim=-1)
# 应用去标准化均值和标准差
means = deltas.new_tensor(means).unsqueeze(0)
stds = deltas.new_tensor(stds).unsqueeze(0)
deltas = deltas.sub_(means).div_(stds)
return deltas
由偏移量计算bbox
def delta2bbox(rois, deltas, means=(0., 0., 0., 0., 0.), stds=(1., 1., 1., 1., 1.), max_shape=None, wh_ratio_clip=16 / 1000, add_ctr_clamp=False, ctr_clamp=32, angle_range='oc', norm_factor=None, edge_swap=False, proj_xy=False):
"""
将偏移量应用于基础边界框以进行平移/缩放。典型地,rois是锚框或提议的边界框,
deltas是网络输出用于平移/缩放这些框的。
参数:
rois (torch.Tensor): 要转换的边界框,形状为(N, 5)。
deltas (torch.Tensor): 相对于每个roi的编码偏移量,形状为(N, num_classes * 5)或(N, 5)。
means (Sequence[float]): 偏移坐标的去标准化均值,默认(0., 0., 0., 0., 0.)。
stds (Sequence[float]): 偏移坐标的去标准化标准差,默认(1., 1., 1., 1., 1.)。
max_shape (Sequence[int] or torch.Tensor or Sequence[Sequence[int]], optional): 边界框的最大界限。
wh_ratio_clip (float): 边界框的最大宽高比,默认16/1000。
add_ctr_clamp (bool): 是否添加中心夹紧,默认False。
ctr_clamp (int): 中心夹紧的最大像素偏移量,默认32。
angle_range (str, optional): 角度表示方式,默认为'oc'。
norm_factor (None|float, optional): 角度的规范化因子。
edge_swap (bool, optional): 如果宽度小于高度是否交换边,默认为False。
proj_xy (bool, optional): 是否根据角度投影x和y,默认为False。
返回:
Tensor: 形状为(N, num_classes * 5)或(N, 5)的边界框,5代表cx, cy, w, h, a。
"""
# 对偏移量进行去标准化
means = deltas.new_tensor(means).view(1, -1).repeat(1, deltas.size(1) // 5)
stds = deltas.new_tensor(stds).view(1, -1).repeat(1, deltas.size(1) // 5)
denorm_deltas = deltas * stds + means
# 提取各个偏移量
dx, dy, dw, dh, da = denorm_deltas[:, 0::5], denorm_deltas[:, 1::5], denorm_deltas[:, 2::5], denorm_deltas[:, 3::5], denorm_deltas[:, 4::5]
# 角度规范化
if norm_factor:
da *= norm_factor * np.pi
# 计算roi的中心、宽度、高度和角度
px, py, pw, ph, pa = rois[:, 0].unsqueeze(1), rois[:, 1].unsqueeze(1), rois[:, 2].unsqueeze(1), rois[:, 3].unsqueeze(1), rois[:, 4].unsqueeze(1)
# 应用偏移量
dx_width, dy_height = pw * dx, ph * dy
# 宽高比限制和中心夹紧
max_ratio = np.abs(np.log(wh_ratio_clip))
if add_ctr_clamp:
dx_width, dy_height = torch.clamp(dx_width, min=-ctr_clamp, max=ctr_clamp), torch.clamp(dy_height, min=-ctr_clamp, max=ctr_clamp)
dw, dh = dw.clamp(min=-max_ratio, max=max_ratio), dh.clamp(min=-max_ratio, max=max_ratio)
# 缩放
gw, gh = pw * dw.exp(), ph * dh.exp()
# 平移
if proj_xy:
gx, gy = dx * pw * torch.cos(pa) - dy * ph * torch.sin(pa) + px, dx * pw * torch.sin(pa) + dy * ph * torch.cos(pa) + py
else:
gx, gy = px + dx_width, py + dy_height
# 计算角度
ga = norm_angle(pa + da, angle_range)
# 边界限制
if max_shape is not None:
gx, gy = gx.clamp(min=0, max=max_shape[1] - 1), gy.clamp(min=0, max=max_shape[0] - 1)
# 边缘交换
if edge_swap:
w_regular, h_regular = torch.where(gw > gh, gw, gh), torch.where(gw > gh, gh, gw)
theta_regular = torch.where(gw > gh, ga, ga + np.pi / 2)
return torch.stack([gx, gy, w_regular, h_regular, norm_angle(theta_regular, angle_range)], dim=-1)
else:
return torch.stack([gx, gy, gw, gh, ga], dim=-1)