assigner.py
utils\tal\assigner.py
目录
assigner.py
1.所需的库和模块
2.def select_candidates_in_gts(xy_centers, gt_bboxes, eps=1e-9):
3.def select_highest_overlaps(mask_pos, overlaps, n_max_boxes):
4.class TaskAlignedAssigner(nn.Module):
1.所需的库和模块
import torch
import torch.nn as nn
import torch.nn.functional as Ffrom utils.metrics import bbox_iou
2.def select_candidates_in_gts(xy_centers, gt_bboxes, eps=1e-9):
# 这段代码定义了一个名为 select_candidates_in_gts 的函数,其作用是选择那些与任何真实目标(ground truth boxes)有重叠的锚点(anchors)。
# 定义 select_candidates_in_gts 函数,它接受三个参数。
# 1.xy_centers :锚点的中心坐标,形状为 (n_anchors, 2) 。
# 2.gt_bboxes :真实目标的边界框,形状为 (bs, n_boxes, 4) ,其中 bs 是批次大小, n_boxes 是每个图像中目标的数量。
# 3.eps :一个小的常数,用于避免数值计算中的除以零错误,默认值为 1e-9 。
def select_candidates_in_gts(xy_centers, gt_bboxes, eps=1e-9):# 选择 gt 中的正锚点中心。"""select the positive anchor center in gtArgs:xy_centers (Tensor): shape(h*w, 4)gt_bboxes (Tensor): shape(b, n_boxes, 4)Return:(Tensor): shape(b, n_boxes, h*w)"""# 获取 锚点的数量 n_anchors 。n_anchors = xy_centers.shape[0]# 获取 gt_bboxes 张量的形状,分别得到 批次大小 bs 和每个图像中 目标的数量 n_boxes 。bs, n_boxes, _ = gt_bboxes.shape# 将 gt_bboxes 张量重塑并分割成两半,分别得到左上角坐标 lt 和右下角坐标 rb 。lt, rb = gt_bboxes.view(-1, 1, 4).chunk(2, 2) # left-top, right-bottom# 计算每个锚点与每个真实目标的边界框之间的 距离 。这包括 左上角 和 右下角 的距离,然后沿着第三个维度(锚点)拼接这些距离,并重塑张量以匹配 批次大小 、 目标数量 、 锚点数量 和 距离维度 。bbox_deltas = torch.cat((xy_centers[None] - lt, rb - xy_centers[None]), dim=2).view(bs, n_boxes, n_anchors, -1)# 这行代码被注释掉了,它展示了一个可能的返回值,即每个锚点与真实目标的最小距离是否大于 eps 。# return (bbox_deltas.min(3)[0] > eps).to(gt_bboxes.dtype)# 返回一个布尔张量 ( (Tensor): shape(b, n_boxes, h*w) ),表示每个锚点与真实目标的最小距离是否大于 eps 。 amin(3) 计算每个锚点在所有真实目标上的最小距离, gt_(eps) 检查这些最小距离是否大于 eps 。return bbox_deltas.amin(3).gt_(eps)
# select_candidates_in_gts 函数用于确定哪些锚点与任何真实目标有重叠。这是目标检测中目标分配步骤的一部分,有助于确定哪些锚点应该被考虑在内,因为它们有可能包含真实目标。通过检查锚点与真实目标的边界框之间的最小距离是否大于一个小的阈值 eps ,我们可以确定哪些锚点是有效的候选者。
3.def select_highest_overlaps(mask_pos, overlaps, n_max_boxes):
# 这段代码定义了一个名为 select_highest_overlaps 的函数,其作用是在目标检测中选择与每个预测框(anchor)重叠度最高的那个真实目标(ground truth)。
# 定义 select_highest_overlaps 函数,它接受三个参数。
# 1.mask_pos :正样本掩码,标识哪些预测框与真实目标有足够高的重叠。
# 2.overlaps :预测框与真实目标之间的 IoU(交并比)。
# 3.n_max_boxes :每个图像中真实目标的最大数量。
def select_highest_overlaps(mask_pos, overlaps, n_max_boxes):# 如果一个anchor box被分配给多个gts,则将选择具有最高iou的那个。"""if an anchor box is assigned to multiple gts,the one with the highest iou will be selected.Args:mask_pos (Tensor): shape(b, n_max_boxes, h*w)overlaps (Tensor): shape(b, n_max_boxes, h*w)Return:target_gt_idx (Tensor): shape(b, h*w)fg_mask (Tensor): shape(b, h*w)mask_pos (Tensor): shape(b, n_max_boxes, h*w)"""# (b, n_max_boxes, h*w) -> (b, h*w)# 计算前景掩码 fg_mask ,它标识 每个预测框 是否至少与一个真实目标有足够高的重叠。这是通过对 mask_pos 沿着倒数第二个维度(真实目标维度)求和来实现的。fg_mask = mask_pos.sum(-2)# 检查是否有任何预测框被分配给了多个真实目标。如果 fg_mask 中的最大值大于1,则表示至少有一个预测框与多个真实目标重叠。if fg_mask.max() > 1: # one anchor is assigned to multiple gt_bboxes# 对于被分配给多个真实目标的预测框,创建一个掩码 mask_multi_gts ,它标识哪些预测框与多个真实目标重叠。mask_multi_gts = (fg_mask.unsqueeze(1) > 1).repeat([1, n_max_boxes, 1]) # (b, n_max_boxes, h*w)# 找到每个预测框与哪个真实目标的 IoU 最大,返回这些最大 IoU 的索引。max_overlaps_idx = overlaps.argmax(1) # (b, h*w)# torch.nn.functional.one_hot(tensor, num_classes=-1) -> LongTensor# F.one_hot() 函数是 PyTorch 中的一个函数,用于将整数类型的张量(通常代表类别索引)转换为 one-hot 编码形式的张量。# 参数 :# tensor :输入的整数张量,其中的每个元素都表示一个类别索引。 数据类型必须是整数类型(如 torch.long 或 torch.int )。# num_classes :输出独热编码向量的长度,即类别的总数。 如果设置为默认值 -1 ,则 num_classes 会自动设置为输入张量中最大值加1,即 max(tensor) + 1 。 如果指定了 num_classes ,则生成的每个独热向量的长度就是 num_classes ,即使某些类别索引可能小于该值。# 输出 :# 输出是一个新张量,其中输入张量的每个整数都被转换为一个独热编码向量。# 输出张量的形状为 (*input_shape, num_classes) ,即在输入张量的最后增加一个维度,代表类别的独热编码。# 应用场景 :# 分类任务 :在神经网络的分类任务中,通常需要将类别标签转换为独热编码。例如在多分类问题中,将标签转换为独热编码后,可以与交叉熵损失函数配合使用。# 序列数据处理 :在自然语言处理任务中,可以使用独热编码将词汇表中的每个单词转换为独热向量,表示该单词在词汇表中的位置。# 距离计算 :在某些算法中,使用独热编码表示类别或索引可以帮助计算不同类别或位置之间的距离。# torch.nn.functional.one_hot 是一个简单但强大的工具,用于将整数标签或类别索引转换为独热编码,特别适用于分类问题的标签预处理,尤其是在多类别分类任务中非常有用。# 将最大 IoU 的索引转换为 one-hot 编码形式,以便后续处理。is_max_overlaps = F.one_hot(max_overlaps_idx, n_max_boxes) # (b, h*w, n_max_boxes)# 调整 one-hot 编码张量的形状,并转换为与 overlaps 相同的数据类型。is_max_overlaps = is_max_overlaps.permute(0, 2, 1).to(overlaps.dtype) # (b, n_max_boxes, h*w)# 更新 mask_pos ,对于与多个真实目标重叠的预测框,选择 IoU 最大的那个。# 这行代码使用了 torch.where 函数来条件性地更新 mask_pos 张量。# mask_multi_gts :这是一个布尔张量,标识哪些预测框被分配给了多个真实目标。如果一个预测框被分配给了多个真实目标,那么 mask_multi_gts 对应的位置为 True 。# is_max_overlaps :这是一个通过 one-hot 编码得到的张量,表示每个预测框与哪个真实目标的 IoU 最大。如果 mask_multi_gts 为 True ,则使用 is_max_overlaps 中的值来更新 mask_pos 。# mask_pos :这是原始的正样本掩码,表示每个预测框是否至少与一个真实目标有足够高的重叠。如果 mask_multi_gts 为 False ,则保持 mask_pos 的原始值。# 对于每个预测框,检查它是否被分配给了多个真实目标( mask_multi_gts 为 True )。# 如果是,那么使用 is_max_overlaps 中的值来更新 mask_pos ,这意味着只保留与 IoU 最大的那个真实目标的匹配。# 如果不是(即 mask_multi_gts 为 False ),则保持 mask_pos 的原始值不变。# 这行代码通过 torch.where 函数实现了对 mask_pos 的条件更新,确保了每个预测框只与一个真实目标匹配(即 IoU 最大的那个),即使某些预测框可能与多个真实目标有较高的重叠。这种处理有助于减少训练过程中的不确定性,确保目标分配的一致性和准确性。mask_pos = torch.where(mask_multi_gts, is_max_overlaps, mask_pos) # (b, n_max_boxes, h*w)# 重新计算前景掩码 fg_mask 。fg_mask = mask_pos.sum(-2) # (b, h*w)# find each grid serve which gt(index)# 找到每个预测框分配给哪个真实目标的索引。target_gt_idx = mask_pos.argmax(-2) # (b, h*w)# 返回 目标索引 target_gt_idx ( shape(b, h*w) ) 、 前景掩码 fg_mask ( shape(b, h*w) ) 和 更新后的正样本掩码 mask_pos ( shape(b, n_max_boxes, h*w) )。return target_gt_idx, fg_mask, mask_pos
# select_highest_overlaps 函数负责在目标检测中为每个预测框分配最匹配的真实目标。它首先确定哪些预测框与真实目标重叠,然后对于每个预测框,选择与其 IoU 最大的那个真实目标。这个函数处理了预测框可能与多个真实目标重叠的情况,并确保每个预测框只被分配给一个真实目标。
4.class TaskAlignedAssigner(nn.Module):
# 这段代码定义了一个名为 TaskAlignedAssigner 的类,它是一个 PyTorch 模块,用于在目标检测任务中进行目标分配。这个类的主要作用是为每个预测的边界框分配最匹配的真实目标,并计算相应的目标标签和边界框。
# 定义了一个名为 TaskAlignedAssigner 的新类,继承自 PyTorch 的 nn.Module 类。
class TaskAlignedAssigner(nn.Module):# 这段代码是 TaskAlignedAssigner 类的构造函数 __init__ ,它用于初始化类的实例。# 定义 TaskAlignedAssigner 类的构造函数,接受五个参数,每个参数都有默认值。# 1.topk :在每个真实目标中选择的候选预测框的数量,默认为13。# 2.num_classes :目标检测任务中的类别数量,默认为80。# 3.alpha :用于计算对齐度量的超参数,默认为1.0。# 4.beta :用于计算对齐度量的另一个超参数,默认为6.0。# 5.eps :用于数值稳定性的小常数,默认为1e-9。def __init__(self, topk=13, num_classes=80, alpha=1.0, beta=6.0, eps=1e-9):# 调用父类 nn.Module 的构造函数,这是 Python 中类的初始化标准做法,确保 TaskAlignedAssigner 类正确地继承了 nn.Module 的所有属性和方法。super().__init__()# 将传入的 topk 参数保存为类的实例变量 self.topk ,这样它就可以在类的其他方法中被访问和使用。self.topk = topk# 将传入的 num_classes 参数保存为类的实例变量 self.num_classes 。self.num_classes = num_classes# 将背景类别的索引设置为类别总数,即 self.bg_idx 等于 self.num_classes 。这通常用于表示背景或“无目标”类别。self.bg_idx = num_classes# 将传入的 alpha 参数保存为类的实例变量 self.alpha 。self.alpha = alpha# 将传入的 beta 参数保存为类的实例变量 self.beta 。self.beta = beta# 将传入的 eps 参数保存为类的实例变量 self.eps 。self.eps = eps# 构造函数 __init__ 初始化了 TaskAlignedAssigner 类的实例,设置了用于目标分配的关键参数,包括 topk 、 num_classes 、 alpha 、 beta 和 eps 。这些参数将在后续的目标分配计算中使用,以确定预测框和真实目标之间的最佳匹配。# 这段代码是 TaskAlignedAssigner 类的 forward 方法,它实现了目标分配的核心逻辑。# 这个装饰器告诉 PyTorch 在执行这个方法时不需要计算梯度,这通常用于推理阶段或者当某个操作不需要反向传播时,以减少内存消耗和计算量。@torch.no_grad()# 定义 forward 方法,它是 PyTorch 模型中进行前向传播的函数。这个方法接受以下参数。# 1.pd_scores :模型预测的类别分数。# 2.pd_bboxes :模型预测的边界框。# 3.anc_points :锚点坐标。# 4.gt_labels :真实目标的类别标签。# 5.gt_bboxes :真实目标的边界框。# 6.mask_gt :真实目标的掩码。def forward(self, pd_scores, pd_bboxes, anc_points, gt_labels, gt_bboxes, mask_gt):"""This code referenced tohttps://github.com/Nioolek/PPYOLOE_pytorch/blob/master/ppyoloe/assigner/tal_assigner.pyArgs:pd_scores (Tensor): shape(bs, num_total_anchors, num_classes)pd_bboxes (Tensor): shape(bs, num_total_anchors, 4)anc_points (Tensor): shape(num_total_anchors, 2)gt_labels (Tensor): shape(bs, n_max_boxes, 1)gt_bboxes (Tensor): shape(bs, n_max_boxes, 4)mask_gt (Tensor): shape(bs, n_max_boxes, 1)Returns:target_labels (Tensor): shape(bs, num_total_anchors)target_bboxes (Tensor): shape(bs, num_total_anchors, 4)target_scores (Tensor): shape(bs, num_total_anchors, num_classes)fg_mask (Tensor): shape(bs, num_total_anchors)"""# 在目标检测任务中,特别是在使用锚点(anchor points)或先验框的情况下, num_total_anchors , n_max_boxes , max_num_obj , h*w ,术语通常表示如下含义 :# num_total_anchors :# 这个术语表示在特征图上所有位置的锚点(anchor points)的总数。在目标检测中,锚点是一组预定义的边界框,它们用于预测目标的位置和尺寸。每个特征图上的位置都可能有多个锚点,因此 num_total_anchors 通常是特征图上每个位置的锚点数量乘以特征图的总位置数(高度乘以宽度)。# n_max_boxes :# 这个术语表示每个图像中真实目标(ground truth objects)的最大数量。在训练目标检测模型时, n_max_boxes 用于确定每个图像中需要处理的最大目标数量。在某些情况下,如果一个图像中的真实目标数量少于这个值,可能需要用背景或“无目标”类别来填充;如果真实目标的数量超过这个值,则可能需要截断或采样以适应这个最大值。# max_num_obj :# 这个术语表示每个图像中考虑的最大目标(object)数量。在实际应用中,一个图像可能包含多个目标,但为了计算效率和简化问题,我们可能限制每个图像中考虑的目标数量。 max_num_obj 就是这个上限值。# h*w :# 这个术语表示特征图(feature map)上的网格点总数。在目标检测模型中,特征图是输入图像经过一系列卷积层和下采样操作后的结果。特征图的高度( h )和宽度( w )决定了其上的网格点数量, h*w 就是这些网格点的总数。 每个网格点可能对应一个或多个锚点,模型会在这些网格点上预测边界框和类别分数。# max_num_obj :决定了真实目标标签和边界框张量的大小。# h*w :决定了预测框的数量,这直接关系到模型的预测能力和复杂度。# 在目标检测任务中, max_num_obj 和 n_max_boxes 通常表示相同的含义,即每个图像中考虑的最大目标(object)数量。这两个术语在不同的上下文中可能有不同的用法,但它们的基本概念是相同的。# max_num_obj :这个术语表示每个图像中考虑的最大目标数量。在实际应用中,一个图像可能包含多个目标,但为了计算效率和简化问题,我们可能限制每个图像中考虑的目标数量。 max_num_obj 就是这个上限值。# n_max_boxes :这个术语也表示每个图像中考虑的最大目标数量。在某些情况下,它可能用于表示每个图像中真实目标的最大数量,或者在预测时,表示每个图像中预测框的最大数量。# 在目标检测的上下文中, max_num_obj 和 n_max_boxes 通常相等,它们都是用来限制每个图像中考虑的目标数量。这个数量是根据具体的应用场景和模型设计来确定的。例如,在训练目标检测模型时,我们可能设置 max_num_obj (或 n_max_boxes )为一个固定的值,以便在每个图像中只考虑最多的目标数量,从而提高计算效率和减少内存消耗。# 因此, max_num_obj 和 n_max_boxes 在大多数情况下是相等的,它们都表示每个图像中考虑的最大目标数量。# max_num_obj == n_max_boxes# n_max_boxes 和 h*w 它们之间没有必然的相等关系,但在特定的目标检测模型和数据集中, n_max_boxes 可能会被设置为小于或等于 h*w 的值。# 在目标检测任务中, num_total_anchors 和 h*w 有直接的大小关系,并且它们之间存在以下联系 :# h*w :这个术语表示特征图(feature map)上的网格点总数,其中 h 是特征图的高度, w 是特征图的宽度。在目标检测模型中,特征图是输入图像经过一系列卷积层和下采样操作后的结果。每个网格点可能对应一个或多个锚点。# num_total_anchors :这个术语表示在特征图上所有位置的锚点(anchor points)的总数。在目标检测中,锚点是一组预定义的边界框,它们用于预测目标的位置和尺寸。如果每个网格点有 A 个锚点(这取决于模型的设计,例如不同尺度和比例的组合),那么 num_total_anchors 就是 A * h * w 。# 因此, num_total_anchors 实际上是特征图上的网格点总数 h*w 乘以每个网格点上的锚点数量 A 。这意味着 num_total_anchors 直接依赖于特征图的尺寸和模型中使用的锚点策略。这种关系对于理解模型的预测能力和复杂度至关重要,因为它决定了模型需要处理的预测框的数量。# 这段代码是 TaskAlignedAssigner 类的 forward 方法的一部分,它处理输入数据并处理没有真实目标的情况。# 获取模型预测分数 pd_scores 的批次大小(即图像数量),并将其保存在实例变量 self.bs 中。 size(0) 方法返回张量的第一个维度的大小,通常对应于批次维度。self.bs = pd_scores.size(0)# 获取真实边界框 gt_bboxes 的第二个维度的大小,并将其保存在实例变量 self.n_max_boxes 中。这个维度通常对应于每个图像中的 最大目标数量 。self.n_max_boxes = gt_bboxes.size(1)# 检查是否有任何真实目标。如果 self.n_max_boxes 为0,意味着当前批次中没有真实目标。if self.n_max_boxes == 0:# 如果没有任何真实目标,获取真实边界框 gt_bboxes 所在的设备(CPU或GPU),并将其保存在变量 device 中。device = gt_bboxes.device# 如果没有真实目标,函数返回四个值 :# torch.full_like(pd_scores[..., 0], self.bg_idx).to(device) :创建一个与 pd_scores 的第一个切片形状相同的张量,填充值为背景索引 self.bg_idx ,并将其移动到正确的设备上。 pd_scores (Tensor): shape(bs, num_total_anchors, num_classes)# torch.zeros_like(pd_bboxes).to(device) :创建一个与预测边界框 pd_bboxes 形状相同的全零张量,并将其移动到正确的设备上。# torch.zeros_like(pd_scores).to(device) :创建一个与预测分数 pd_scores 形状相同的全零张量,并将其移动到正确的设备上。# torch.zeros_like(pd_scores[..., 0]).to(device) :创建一个与 pd_scores 的第一个切片形状相同的全零张量,并将其移动到正确的设备上。# 这些返回值分别代表 :# 分配给每个预测框的 目标标签 (填充背景索引)。# 分配给每个预测框的 目标边界框 (全零)。# 分配给每个预测框的 目标分数 (全零)。# 前景掩码 (全零,表示没有目标)。return (torch.full_like(pd_scores[..., 0], self.bg_idx).to(device),torch.zeros_like(pd_bboxes).to(device),torch.zeros_like(pd_scores).to(device),torch.zeros_like(pd_scores[..., 0]).to(device))# 这段代码处理了没有真实目标的特殊情况,确保在这种情况下,目标分配器能够返回合适的默认值,这些值不会影响模型的训练过程。# 这段代码是 TaskAlignedAssigner 类的 forward 方法中的一部分,它负责计算正样本掩码、选择最佳匹配的目标,并获取分配的目标标签和边界框。# 调用实例方法 get_pos_mask ,传入 预测分数 pd_scores 、 预测边界框 pd_bboxes 、 真实标签 gt_labels 、 真实边界框 gt_bboxes 、 锚点 anc_points 和 真实目标掩码 mask_gt 。# 该方法返回三个值。 mask_pos :正样本掩码,标识哪些预测框与真实目标有足够高的重叠度,应该被考虑在内。 align_metric :对齐度量,衡量预测框与真实目标的对齐程度。 overlaps :重叠度量,衡量预测框与真实目标的 IoU(交并比)。# def get_pos_mask(self, pd_scores, pd_bboxes, gt_labels, gt_bboxes, anc_points, mask_gt):# -> 返回 正样本掩码 mask_pos ( shape(b, max_num_obj, h*w) ) 、 对齐度量 align_metric ( shape(bs, n_max_boxes, h*w) ) 和 重叠度量 overlaps ( shape(bs, n_max_boxes, h*w) ) 。# -> return mask_pos, align_metric, overlapsmask_pos, align_metric, overlaps = self.get_pos_mask(pd_scores, pd_bboxes, gt_labels, gt_bboxes, anc_points,mask_gt)# 调用 select_highest_overlaps 函数,传入 正样本掩码 mask_pos 、 重叠度量 overlaps 和 每个图像中最大目标数量 self.n_max_boxes 。# 该函数返回三个值。 target_gt_idx :最佳匹配目标的索引,标识每个预测框应该分配给哪个真实目标。 fg_mask :前景掩码,标识哪些预测框被分配给了真实目标(即不是背景)。 mask_pos :更新后的正样本掩码,根据重叠度量进行了调整。# def select_highest_overlaps(mask_pos, overlaps, n_max_boxes):# -> 在目标检测中选择与每个预测框(anchor)重叠度最高的那个真实目标(ground truth)。返回 目标索引 target_gt_idx ( shape(b, h*w) ) 、 前景掩码 fg_mask ( shape(b, h*w) ) 和 更新后的正样本掩码 mask_pos ( shape(b, n_max_boxes, h*w) )。# -> return target_gt_idx, fg_mask, mask_postarget_gt_idx, fg_mask, mask_pos = select_highest_overlaps(mask_pos, overlaps, self.n_max_boxes)# assigned target# 调用实例方法 get_targets ,传入 真实标签 gt_labels 、 真实边界框 gt_bboxes 、 目标索引 target_gt_idx 和 前景掩码 fg_mask 。# 该方法返回三个值。 target_labels :分配给预测框的目标标签。 target_bboxes :分配给预测框的目标边界框。 target_scores :分配给预测框的目标分数,通常是基于预测框与真实目标的匹配程度。# def get_targets(self, gt_labels, gt_bboxes, target_gt_idx, fg_mask):# -> 用于根据目标分配索引和前景掩码为每个预测框分配目标标签和边界框。返回分配给每个预测框的 目标标签 ( target_labels shape(b, h*w) ) 、 边界框 ( target_bboxes shape(b, h*w) ) 和 类别分数 ( target_scores shape(b, h*w, num_classes) ) 。# -> return target_labels, target_bboxes, target_scorestarget_labels, target_bboxes, target_scores = self.get_targets(gt_labels, gt_bboxes, target_gt_idx, fg_mask)# 这段代码实现了目标分配的关键步骤,包括计算正样本掩码、选择最佳匹配的目标,并获取分配的目标标签和边界框。这些步骤确保了模型的预测能够与真实目标正确对应,从而在计算损失时能够准确地衡量模型的性能# 这段代码是 TaskAlignedAssigner 类的 forward 方法中的一部分,它负责对齐度量(align_metric)和重叠度量(overlaps)的归一化,并计算最终的目标分数(target_scores)。# normalize# 将 对齐度量 ( align_metric )与 正样本掩码 ( mask_pos )相乘,以确保只有正样本(即预测框与真实目标有足够高重叠的框)的对齐度量被考虑。align_metric *= mask_pos# 计算每个真实目标的 最大对齐度量 ( pos_align_metrics ),沿着最后一个维度(即每个预测框的维度)进行,并且保持维度不变,以便后续的广播操作。pos_align_metrics = align_metric.amax(axis=-1, keepdim=True) # b, max_num_obj# 计算每个真实目标的 最大重叠度量 ( pos_overlaps ),同样沿着最后一个维度进行,并且保持维度不变。这里, 重叠度量 ( overlaps )也与 正样本掩码 ( mask_pos )相乘,以确保只有正样本的重叠度量被考虑。pos_overlaps = (overlaps * mask_pos).amax(axis=-1, keepdim=True) # b, max_num_obj# 计算 归一化的对齐度量 ( norm_align_metric )。它首先将 对齐度量 与 最大重叠度量 相乘,然后除以 最大对齐度量 加上一个小的常数(self.eps)以避免除以零。接着,沿着倒数第二个维度(即每个真实目标的维度)计算最大值,并使用 unsqueeze(-1) 在最后一个维度上增加一个维度,以便与目标分数( target_scores )的形状匹配。norm_align_metric = (align_metric * pos_overlaps / (pos_align_metrics + self.eps)).amax(-2).unsqueeze(-1)# 将 目标分数 ( target_scores )与 归一化的对齐度量 相乘,以调整目标分数,使其反映预测框与真实目标的匹配程度。# 通过将 target_scores 与 norm_align_metric 相乘,得到最终的 目标分数 ,这些分数将用于后续的损失计算。相乘操作是 逐元素 的,因此 target_scores 的形状在操作后仍然是 (bs, h*w, num_classes) 。 num_total_anchors == h*w 。target_scores = target_scores * norm_align_metric# 返回 分配的目标标签 ( target_labels shape(bs, num_total_anchors) )、 目标边界框 ( target_bboxes shape(bs, num_total_anchors, 4) )、 调整后的目标分数 ( target_scores shape(bs, num_total_anchors, num_classes) )和 前景掩码 ( fg_mask shape(bs, num_total_anchors) )。return target_labels, target_bboxes, target_scores, fg_mask.bool()# 这段代码通过对齐度量和重叠度量进行归一化处理,调整目标分数,以确保目标分数能够更准确地反映预测框与真实目标之间的匹配程度。这是目标检测模型训练过程中目标分配的关键步骤,有助于优化模型的性能。# forward 方法实现了目标分配的核心逻辑,包括获取正样本掩码、选择最佳匹配的目标、归一化对齐度量和重叠度量,并最终返回分配的目标标签和边界框。这个方法是目标检测模型训练过程中的关键步骤,它确保了模型的预测能够与真实目标正确对应。# 这段代码定义了 TaskAlignedAssigner 类中的 get_pos_mask 方法,它用于生成 正样本掩码( mask_pos 这个掩码标识了哪些预测框与真实目标有足够高的重叠,并且应该被考虑在内 ),对齐度量 ( align_metric )和 重叠度量 ( overlaps )。# 定义 get_pos_mask 方法,它接受以下参数 :# 1.pd_scores :模型预测的类别分数。 pd_scores (Tensor): shape(bs, num_total_anchors, num_classes)# 2.pd_bboxes :模型预测的边界框。 pd_bboxes (Tensor): shape(bs, num_total_anchors, 4)# 3.gt_labels :真实目标的类别标签。 gt_labels (Tensor): shape(bs, n_max_boxes, 1)# 4.gt_bboxes :真实目标的边界框。 gt_bboxes (Tensor): shape(bs, n_max_boxes, 4)# 5.anc_points :锚点坐标。 anc_points (Tensor): shape(num_total_anchors, 2) num_total_anchors == h*w# 6.mask_gt :真实目标的掩码。 mask_gt (Tensor): shape(bs, n_max_boxes, 1)def get_pos_mask(self, pd_scores, pd_bboxes, gt_labels, gt_bboxes, anc_points, mask_gt):# get anchor_align metric, (b, max_num_obj, h*w)# 调用 get_box_metrics 方法计算每个预测框与每个真实目标的 对齐度量 ( align_metric )和 重叠度量 ( overlaps )。这些度量将用于后续的目标分配。# def get_box_metrics(self, pd_scores, pd_bboxes, gt_labels, gt_bboxes):# -> 它用于计算预测框(pd_boxes)和真实目标(gt_bboxes)之间的对齐度量(align_metric)和重叠度量(overlaps)。返回计算得到的 对齐度量 ( align_metric shape(bs, n_max_boxes, h*w) ) 和 重叠度量 ( overlaps shape(bs, n_max_boxes, h*w) ) 。# -> return align_metric, overlaps align_metric, overlaps = self.get_box_metrics(pd_scores, pd_bboxes, gt_labels, gt_bboxes)# get in_gts mask, (b, max_num_obj, h*w)# 调用 select_candidates_in_gts 方法生成一个掩码 mask_in_gts ,它标识哪些锚点位于真实目标内部。这是通过检查每个锚点是否在任何真实目标的边界框内来实现的。# def select_candidates_in_gts(xy_centers, gt_bboxes, eps=1e-9):# -> 选择那些与任何真实目标(ground truth boxes)有重叠的锚点(anchors)。返回一个布尔张量 ( (Tensor): shape(b, n_boxes, h*w) ),表示每个锚点与真实目标的最小距离是否大于 eps 。# -> return bbox_deltas.amin(3).gt_(eps)mask_in_gts = select_candidates_in_gts(anc_points, gt_bboxes) # mask_in_gts (Tensor): shape(b, n_boxes, h*w)# get topk_metric mask, (b, max_num_obj, h*w)# 调用 select_topk_candidates 方法生成一个掩码 mask_topk ,它标识在每个真实目标中,哪些预测框的对齐度量最高的 topk 个候选。这里, align_metric 与 mask_in_gts 相乘,以确保只考虑位于真实目标内部的预测框。# align_metric * mask_in_gts (Tensor): shape(b, max_num_obj, h*w)# def select_topk_candidates(self, metrics, largest=True, topk_mask=None):# -> 用于从每个真实目标中选择 top-k 个候选预测框,这些预测框具有最高的匹配度量(例如,IoU 或其他对齐度量)。返回更新后的 is_in_topk ( shape(b, max_num_obj, h*w) ) 张量,并将其数据类型转换为与 metrics 张量相同。# -> return is_in_topk.to(metrics.dtype)mask_topk = self.select_topk_candidates(align_metric * mask_in_gts, # mask_topk (Tensor): shape(b, max_num_obj, h*w)topk_mask=mask_gt.repeat([1, 1, self.topk]).bool())# merge all mask to a final mask, (b, max_num_obj, h*w)# 将 mask_topk 、 mask_in_gts 和 mask_gt 相乘,生成最终的正样本掩码 mask_pos 。这个掩码标识了哪些预测框既是 top-k 候选,又位于真实目标内部,并且是有效的真实目标。 mask_pos (Tensor): shape(b, max_num_obj, h*w)mask_pos = mask_topk * mask_in_gts * mask_gt # shape(b, max_num_obj, h*w) * shape(b, n_boxes, h*w) * shape(b, n_max_boxes, 1)# 返回 正样本掩码 mask_pos ( shape(b, max_num_obj, h*w) ) 、 对齐度量 align_metric ( shape(bs, n_max_boxes, h*w) ) 和 重叠度量 overlaps ( shape(bs, n_max_boxes, h*w) ) 。return mask_pos, align_metric, overlaps# 这段代码是 TaskAlignedAssigner 类中的 get_box_metrics 方法,它用于计算预测框(pd_boxes)和真实目标(gt_bboxes)之间的对齐度量(align_metric)和重叠度量(overlaps)。# 定义 get_box_metrics 方法,它接受以下参数 :# 1.pd_scores :模型预测的 类别分数 。 pd_scores (Tensor): shape(bs, num_total_anchors, num_classes)# 2.pd_bboxes :模型预测的 边界框 。 pd_bboxes (Tensor): shape(bs, num_total_anchors, 4)# 3.gt_labels :真实目标的 类别标签 。 gt_labels (Tensor): shape(bs, n_max_boxes, 1)# 4.gt_bboxes :真实目标的 边界框 。 gt_bboxes (Tensor): shape(bs, n_max_boxes, 4)def get_box_metrics(self, pd_scores, pd_bboxes, gt_labels, gt_bboxes):# 将真实目标的 类别标签 转换为长整型(long)。gt_labels = gt_labels.to(torch.long) # b, max_num_obj, 1# 创建一个形状为 [2, self.bs, self.n_max_boxes] 的零张量,用于存储 索引 。 ind (Tensor): shape(2, bs, n_max_boxes)ind = torch.zeros([2, self.bs, self.n_max_boxes], dtype=torch.long) # 2, b, max_num_obj# 填充索引张量的第一行,其中包含从0到 self.bs (批次大小)的序列,为 每个批次 创建一个 索引 。 ind[0] (Tensor): shape(b, max_num_obj)ind[0] = torch.arange(end=self.bs).view(-1, 1).repeat(1, self.n_max_boxes) # b, max_num_obj# 填充索引张量的第二行,其中包含真实目标的 类别标签 。 ind[1] (Tensor): shape(b, max_num_obj)ind[1] = gt_labels.squeeze(-1) # b, max_num_obj# get the scores of each grid for each gt cls# 根据索引张量,从预测分数 pd_scores 中提取每个真实目标对应的 分数 。 bbox_scores (Tensor): shape(b, max_num_obj, h*w)bbox_scores = pd_scores[ind[0], :, ind[1]] # b, max_num_obj, h*w# 计算真实目标和预测框之间的交并比(IoU)。这里使用 bbox_iou 函数,并且指定使用 CIoU(Complete IoU)算法。结果张量通过 unsqueeze 和 squeeze 调整形状以匹配其他张量。# def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, MDPIoU=False, feat_h=640, feat_w=640, eps=1e-7):# -> 用于计算两个边界框之间的交并比(IoU)以及其变体,如GIoU、DIoU、CIoU和MDPIoU。# -> return iou - (rho2 / c2 + v * alpha) # CIoU return iou - rho2 / c2 # DIoU return iou - (c_area - union) / c_area # GIoU return iou - d1 / mpdiou_hw_pow - d2 / mpdiou_hw_pow # MPDIoU return iou # IoU# 这行代码使用 bbox_iou 函数计算真实目标边界框 gt_bboxes 和预测边界框 pd_bboxes 之间的交并比(IoU)。# gt_bboxes.unsqueeze(2) : gt_bboxes 是 真实目标 的 边界框 ,形状通常是 (bs, n_max_boxes, 4) ,其中 bs 是批次大小, n_max_boxes 是每个图像中目标的最大数量。 unsqueeze(2) 在第三个维度(索引为2)上增加一个维度,将形状变为 (bs, n_max_boxes, 1, 4) ,以便进行广播操作。# pd_bboxes.unsqueeze(1) : pd_bboxes 是 预测 的 边界框 ,形状通常是 (bs, h*w, 4) ,其中 h*w 是特征图上的网格点总数。 unsqueeze(1) 在第二个维度(索引为1)上增加一个维度,将形状变为 (bs, 1, h*w, 4) ,以便进行广播操作。# bbox_iou(..., xywh=False, CIoU=True) : bbox_iou 函数计算两个边界框之间的 IoU。 xywh=False 表示边界框的格式是 (x1, y1, x2, y2) 而不是 (x, y, w, h) 。 CIoU=True 表示使用 Complete IoU 算法,这是一种改进的 IoU 算法,它考虑了边界框的对齐程度。# .squeeze(3) : squeeze 函数移除单一维度(即第三个维度,索引为3),将形状从 (bs, n_max_boxes, 1, h*w) 变为 (bs, n_max_boxes, h*w) 。# .clamp(0) : clamp 函数将所有值限制在 [0, 1] 范围内,确保 IoU 值不会小于0。# 这行代码计算了每个预测边界框与每个真实目标边界框之间的 CIoU,并将结果限制在 [0, 1] 范围内。这种计算对于目标检测任务中的目标分配至关重要,因为它帮助确定哪些预测框应该与哪些真实目标匹配。通过使用 CIoU,这种方法不仅考虑了边界框的重叠程度,还考虑了边界框的形状和对齐程度。overlaps = bbox_iou(gt_bboxes.unsqueeze(2), pd_bboxes.unsqueeze(1), xywh=False, CIoU=True).squeeze(3).clamp(0) # overlaps (Tensor): shape(bs, n_max_boxes, h*w)# 计算对齐度量 ( align_metric ),它是 预测分数 的 self.alpha 次幂和 IoU 的 self.beta 次幂的乘积。 # align_metric (Tensor): shape(bs, n_max_boxes, h*w)align_metric = bbox_scores.pow(self.alpha) * overlaps.pow(self.beta)# 返回计算得到的 对齐度量 ( align_metric shape(bs, n_max_boxes, h*w) ) 和 重叠度量 ( overlaps shape(bs, n_max_boxes, h*w) ) 。return align_metric, overlaps# get_box_metrics 方法用于评估预测框和真实目标之间的匹配程度。它首先提取每个真实目标对应的预测分数,然后计算这些预测框和真实目标之间的 IoU。最后,它结合预测分数和 IoU 来计算一个对齐度量,这个度量将用于后续的目标分配过程。# 这段代码定义了一个名为 select_topk_candidates 的方法,它用于从每个真实目标中选择 top-k 个候选预测框,这些预测框具有最高的匹配度量(例如,IoU 或其他对齐度量)。# 定义 select_topk_candidates 方法,它接受以下参数 :# 1.metrics :一个形状为 (b, max_num_obj, h*w) 的张量,包含每个预测框与每个真实目标的匹配度量。# 2.largest :一个布尔值,指示是否选择最大的 topk 个值,默认为 True 。# 3.topk_mask :一个可选的掩码张量,用于过滤某些候选框。def select_topk_candidates(self, metrics, largest=True, topk_mask=None):"""Args:metrics: (b, max_num_obj, h*w).topk_mask: (b, max_num_obj, topk) or None"""# 获取特征图上的网格点总数 num_anchors ,这是预测框的数量。num_anchors = metrics.shape[-1] # h*w# (b, max_num_obj, topk)# 使用 torch.topk 函数从 metrics 张量中选择每个真实目标的 topk 个最高匹配度量的预测框。结果包括两个张量 : topk_metrics 包含 top-k 个最大的度量值, topk_idxs 包含这些度量值的索引。# 这行代码使用了 PyTorch 的 torch.topk 函数来从给定的 metrics 张量中选择每行的 topk 个最大值。# dim=-1 :指定在哪个维度上选择 top-k 值。 dim=-1 表示最后一个维度,即每个真实目标对应的预测框的匹配度量。# topk_metrics :一个张量,包含每个真实目标对应的 topk 个最大匹配度量值。# topk_idxs :一个张量,包含 topk_metrics 中每个值在原始 metrics 张量中的索引。# topk_metrics 和 topk_idxs 的形状都为 (b, max_num_obj, self.topk) 。 topk_metrics (Tensor): shape(b, max_num_obj, topk) topk_idxs (Tensor): shape(b, max_num_obj, topk)topk_metrics, topk_idxs = torch.topk(metrics, self.topk, dim=-1, largest=largest)# 如果 topk_mask 未提供,则创建一个掩码,它标识 topk_metrics 中的最大值是否大于阈值 self.eps 。这个掩码用于过滤掉那些度量值过低的候选框。if topk_mask is None:# torch.tile(input, dims) -> Tensor# torch.tile(input, dims) 是 PyTorch 中的一个函数,它用于将输入张量 input 在指定的维度 dims 上复制扩展。这个函数的目的是将张量在某些维度上重复多次,以创建一个新的、更大的张量。# 参数 :# input :要进行复制的输入张量。# dims :一个元组或列表,指定了每个维度上复制的次数。# 输出 :# 返回一个新的张量,它是输入张量在指定维度上复制的结果。# 应用场景 :# 数据增强 :在机器学习和深度学习中, torch.tile 可以用来创建数据增强的样本。# 扩展特征 :在某些情况下,可能需要将特征张量扩展到更大的尺寸, torch.tile 可以用于这种场景。# 初始化张量 :创建具有特定模式的初始化张量,例如,通过重复某个模式来生成一个更大的张量。# torch.tile 是一个灵活的函数,用于在指定维度上复制张量,它在数据处理和张量操作中非常有用。通过指定每个维度的复制次数,可以轻松地扩展张量的尺寸。# 这行代码用于创建一个掩码 topk_mask ,它标识在 topk_metrics 中每个真实目标的 topk 个最大匹配度量中,哪些是有效的(即大于一个很小的阈值 self.eps )。# topk_metrics.max(-1, keepdim=True) :计算 topk_metrics 张量在最后一个维度(即 topk 维度)上的最大值。# topk_metrics.max(-1, keepdim=True) > self.eps :检查每个最大值是否大于 self.eps ,返回一个布尔张量。# .tile([1, 1, self.topk]) :使用 tile 函数将布尔张量在最后一个维度上重复 self.topk 次。# topk_mask 是一个形状为 (b, max_num_obj, topk) 的布尔张量,它标识在 topk_metrics 中每个真实目标的 topk 个最大匹配度量中,哪些是有效的(即大于 self.eps )。这个掩码将用于后topk_mask = (topk_metrics.max(-1, keepdim=True) > self.eps).tile([1, 1, self.topk])# (b, max_num_obj, topk)# 使用 torch.where 函数根据 topk_mask 更新 topk_idxs ,将掩码为 False 的索引设置为 0。 topk_idxs (Tensor): shape(b, max_num_obj, topk)topk_idxs = torch.where(topk_mask, topk_idxs, 0)# (b, max_num_obj, topk, h*w) -> (b, max_num_obj, h*w)# 将 topk_idxs 转换为 one-hot 编码形式,然后沿着倒数第二个维度( topk 维度)求和,得到一个形状为 (b, max_num_obj, h*w) 的张量 is_in_topk ,它标识每个预测框是否在 top-k 候选框中。 is_in_topk (Tensor): shape(b, max_num_obj, h*w)is_in_topk = F.one_hot(topk_idxs, num_anchors).sum(-2)# filter invalid bboxes 过滤无效的 bbox。# assigned topk should be unique, this is for dealing with empty labels 分配的 topk 应该是唯一的,这是为了处理空标签。# since empty labels will generate index `0` through `F.one_hot` 因为空标签将通过 `F.one_hot` 生成索引 `0`。# NOTE: but what if the topk_idxs include `0`? 注意:但是如果 topk_idxs 包含 `0` 怎么办?# 过滤掉无效的边界框。如果 is_in_topk 中的值大于 1,表示该预测框被分配给了多个真实目标,这通常是不允许的,因此将这些值设置为 0。is_in_topk = torch.where(is_in_topk > 1, 0, is_in_topk)# 返回更新后的 is_in_topk ( shape(b, max_num_obj, h*w) ) 张量,并将其数据类型转换为与 metrics 张量相同。return is_in_topk.to(metrics.dtype)# select_topk_candidates 方法用于从每个真实目标中选择 top-k 个候选预测框,这些预测框具有最高的匹配度量。它处理了候选框的选择、过滤和唯一性检查,确保每个预测框只被分配给一个真实目标。这个方法是目标检测中目标分配策略的一部分,有助于优化模型的训练过程。# 这段代码定义了 TaskAlignedAssigner 类中的 get_targets 方法,它用于根据目标分配索引和前景掩码为每个预测框分配目标标签和边界框。# 定义 get_targets 方法,它接受以下参数 :# 1.gt_labels :真实目标的类别标签,形状为 (b, max_num_obj, 1) 。# 2.gt_bboxes :真实目标的边界框,形状为 (b, max_num_obj, 4) 。# 3.target_gt_idx :每个预测框分配给哪个真实目标的索引,形状为 (b, h*w) 。# 4.fg_mask :前景掩码,标识哪些预测框是前景,形状为 (b, h*w) 。def get_targets(self, gt_labels, gt_bboxes, target_gt_idx, fg_mask):"""Args:gt_labels: (b, max_num_obj, 1)gt_bboxes: (b, max_num_obj, 4)target_gt_idx: (b, h*w)fg_mask: (b, h*w)"""# assigned target labels, (b, 1)# 创建一个包含 批次索引 的张量 batch_ind ,形状为 (b, 1) ,用于后续的索引计算。batch_ind = torch.arange(end=self.bs, dtype=torch.int64, device=gt_labels.device)[..., None]# 将 批次索引 与 目标索引 相加,计算 全局索引 ,以便从 gt_labels 和 gt_bboxes 中提取正确的 目标标签 和 边界框 。target_gt_idx = target_gt_idx + batch_ind * self.n_max_boxes # (b, h*w)# 使用 全局索引 从 gt_labels 中提取分配给每个预测框的 目标标签 。# 这行代码用于从真实目标的类别标签 gt_labels 中提取分配给每个预测框的目标标签。# gt_labels.long() :将 gt_labels 转换为长整型(long)张量。 gt_labels 是一个整数张量,表示每个真实目标的类别索引。# gt_labels.long().flatten() :将 gt_labels 扁平化为一个一维张量。扁平化操作将多维张量的所有元素按顺序排列到一个一维张量中。# gt_labels.long().flatten()[target_gt_idx] :使用 target_gt_idx 作为索引,从扁平化后的 gt_labels 中提取分配给每个预测框的目标标签。# target_gt_idx 是一个形状为 (b, h*w) 的张量,其中 b 是批次大小, h*w 是特征图上的网格点总数。它包含每个预测框分配给哪个真实目标的索引。# 因此, target_labels 是一个形状为 (b, h*w) 的张量,其中每个元素是分配给对应预测框的目标标签。这个张量将用于后续的损失计算,确保模型能够针对每个预测框计算正确的损失。# 这行代码通过索引操作从真实目标的类别标签中提取分配给每个预测框的目标标签,这是目标检测中目标分配步骤的一部分,确保模型能够针对每个预测框计算正确的损失。 target_labels (Tensor): shape(b, h*w)target_labels = gt_labels.long().flatten()[target_gt_idx] # (b, h*w)# assigned target boxes, (b, max_num_obj, 4) -> (b, h*w)# 使用 全局索引 从 gt_bboxes 中提取分配给每个预测框的 目标边界框 。 target_bboxes (Tensor): shape(b, h*w)target_bboxes = gt_bboxes.view(-1, 4)[target_gt_idx]# assigned target scores# 确保目标标签的值在合理的范围内,这里将标签值限制在非负整数。target_labels.clamp(0)# 将目标标签转换为 one-hot 编码形式,为每个预测框生成一个 类别分数向量 。target_scores = F.one_hot(target_labels, self.num_classes) # (b, h*w, 80)# 将前景掩码 fg_mask 扩展到与目标分数相同的维度,并重复 每个类别分数 ,以便进行后续的过滤操作。fg_scores_mask = fg_mask[:, :, None].repeat(1, 1, self.num_classes) # (b, h*w, 80)# 使用 torch.where 函数根据前景掩码过滤目标分数,确保只有前景的预测框有有效的类别分数。 target_scores (Tensor): shape(b, h*w, num_classes)target_scores = torch.where(fg_scores_mask > 0, target_scores, 0)# 返回分配给每个预测框的 目标标签 ( target_labels shape(b, h*w) ) 、 边界框 ( target_bboxes shape(b, h*w) ) 和 类别分数 ( target_scores shape(b, h*w, num_classes) ) 。return target_labels, target_bboxes, target_scores# get_targets 方法根据目标分配索引和前景掩码为每个预测框分配目标标签和边界框,并生成对应的类别分数。这个方法是目标检测中目标分配策略的一部分,确保模型能够针对每个预测框计算正确的损失。
# TaskAlignedAssigner 类实现了一个复杂的目标分配策略,它考虑了预测框和真实目标之间的对齐度量和重叠度量,以优化目标检测模型的训练过程。这个类的方法负责计算正样本掩码、选择最佳匹配的目标、归一化对齐度量和重叠度量,并最终返回分配的目标标签和边界框。