赞
踩
训练之前的准备,主要是初始化模型以及数据集。
在ByteTrack中图像输入大小为:(896,1600)
初始化
YOLOXPAFPN
YOLOXHead
采用Darknet-53
self.backbone = CSPDarknet(depth, width, depthwise=depthwise, act=act)
backbone的输入input为一个batch的图片 (B,C,H,W)
out_features = self.backbone(input)
out_features的输出为3个特征层(分别为’dark3’,‘dark4’,‘dark5’)组成的字典,举个例子,各特征层的shape如下:
{‘dark3’:(B,320,112,200),
‘dark4’:(B,640,56,100),
‘dark5’:(B,1280,28,50)}
均为(B,C,H,W),只是尺寸和特征维度不同。
在Neck结构中,Yolox采用PAFPN的结构进行融合。如下图所示,将高层的特征信息,先通过上采样的方式进行传递融合,再通过下采样融合方式得到预测的特征图,最终输出3个特征层组成的元组结果,各特征层的shape如下:
{‘dark3’:(B,320,112,200),
‘dark4’:(B,640,56,100),
‘dark5’:(B,1280,28,50)}
均为(B,C,H,W),只是尺寸和特征维度不同。
在Yolox中,作者增加了三个Decoupled Head,俗称“解耦头”。
总共有三个分支:
首先,相对anchor-based参数量大大减小。
举个例子,最后8400个预测框中,其中有400个框,所对应锚框的大小,为32*32。中间的分支,最后有1600个预测框,所对应锚框的大小,为16*16。最下面的分支,最后有6400个预测框,所对应锚框的大小,为8*8。
当有了29400个预测框的信息,每张图片也有标注的目标框的信息。
这时的锚框,就相当于桥梁。
这时需要做的,就是将29400个锚框,和图片上所有的目标框进行关联,挑选出正样本锚框。
而相应的,正样本锚框所对应的位置,就可以将正样本预测框,挑选出来。
这里采用的关联方式,就是标签分配。
yolo_head.py的get_in_boxes_info
函数中,如果 anchor bbox 中心落在 groundtruth bbox或 fixed bbox,则被选中为候选正样本。
anchor box的中心点落在人工标注框(Ground Truth Boxes)的矩形范围中的所有anchor;
gt_bboxes_per_image_l = ( (gt_bboxes_per_image[:, 0] - 0.5 * gt_bboxes_per_image[:, 2]).unsqueeze(1) .repeat(1, total_num_anchors) ) # [n_gt, n_anchor] gt_bboxes_per_image_r = ( (gt_bboxes_per_image[:, 0] + 0.5 * gt_bboxes_per_image[:, 2]).unsqueeze(1) .repeat(1, total_num_anchors) ) # [n_gt, n_anchor] gt_bboxes_per_image_t = ( (gt_bboxes_per_image[:, 1] - 0.5 * gt_bboxes_per_image[:, 3]).unsqueeze(1) .repeat(1, total_num_anchors) ) # [n_gt, n_anchor] gt_bboxes_per_image_b = ( (gt_bboxes_per_image[:, 1] + 0.5 * gt_bboxes_per_image[:, 3]).unsqueeze(1) .repeat(1, total_num_anchors) ) # [n_gt, n_anchor]
b_l = x_centers_per_image - gt_bboxes_per_image_l
b_r = gt_bboxes_per_image_r - x_centers_per_image
b_t = y_centers_per_image - gt_bboxes_per_image_t
b_b = gt_bboxes_per_image_b - y_centers_per_image
bbox_deltas = torch.stack([b_l, b_t, b_r, b_b], 2)
is_in_boxes = bbox_deltas.min(dim=-1).values > 0.0
is_in_boxes_all = is_in_boxes.sum(dim=0) > 0
以Ground Truth Boxes中心点为基准,四周向外扩展2.5倍stride,构成边长为5倍stride的正方形,挑选anchor box中心点落在正方形内的所有锚框。
总体来说get_in_boxes_info返回两个值,fg_mask和is_in_boxes_and_center,fg_mask为29400维数组,即29400个框的正负性,用ture和false表示,is_in_boxes_and_center为[gt_num, 正样本个数]
假定图片上有3个目标框,即3个groundtruth,且检测类别为1。
上一节中,我们知道有29400个锚框,但是经过初步筛选后,假定有1000个锚框是正样本锚框。
根据位置,可以将网络预测的候选检测框位置bboxes_preds、前景背景目标分数obj_preds、类别分数cls_preds等信息,提取出来。
bboxes_preds_per_image = bboxes_preds_per_image[fg_mask] # [1000, 4]
cls_preds_ = cls_preds[batch_idx][fg_mask] # [1000, 1]
obj_preds_ = obj_preds[batch_idx][fg_mask] # [1000, 1]
num_in_boxes_anchor = bboxes_preds_per_image.shape[0] # 1000
针对筛选出的1000个候选检测框,和3个groundtruth计算Loss函数。
pair_wise_ious_loss
[3,1000]pair_wise_ious = bboxes_iou(gt_bboxes_per_image, bboxes_preds_per_image, False) # [gt_num, matched_anchor]
gt_cls_per_image = ( # [gt_num, matched_anchor, class_num]
F.one_hot(gt_classes.to(torch.int64), self.num_classes)
.float().unsqueeze(1)
.repeat(1, num_in_boxes_anchor, 1)
)
pair_wise_ious_loss = -torch.log(pair_wise_ious + 1e-8)
pair_wise_cls_loss
[3,1000]cls_preds_ = ( # [gt_num, matched_anchor, 1]
cls_preds_.float().unsqueeze(0).repeat(num_gt, 1, 1).sigmoid_() # [gt_num, matched_anchor, 1]
* obj_preds_.float().unsqueeze(0).repeat(num_gt, 1, 1).sigmoid_() # [gt_num, matched_anchor, 1]
)
pair_wise_cls_loss = F.binary_cross_entropy( # [gt_num, matched_anchor]
cls_preds_.sqrt_(), gt_cls_per_image, reduction="none"
).sum(-1)
有了reg_loss和cls_loss,就可以将两个损失函数加权相加,计算cost成本函数了。
cost = (
pair_wise_cls_loss
+ 3.0 * pair_wise_ious_loss
+ 100000.0 * (~is_in_boxes_and_center)
)
采用一种简化版的SimOTA方法,求解近似最优解。这里对应的函数,是get_assignments函数中的self.dynamic_k_matching
:
num_fg,gt_matched_classes, gt_matched_ids, pred_ious_this_matching, matched_gt_inds,)
= self.dynamic_k_matching(cost, pair_wise_ious, gt_classes, gt_ids, num_gt, fg_mask)
loss_iou = (self.iou_loss(bbox_preds.view(-1, 4)[fg_masks], reg_targets) # [matched_anchor, 4]
).sum() / num_fg
loss_obj = (self.bcewithlog_loss(obj_preds.view(-1, 1), obj_targets) # [all_anchor, 1]
).sum() / num_fg
loss_cls = (self.bcewithlog_loss(
cls_preds.view(-1, self.num_classes)[fg_masks], cls_targets # [matched_anchor, 1] )
).sum() / num_fg
在前面精细化筛选中,使用了reg_loss和cls_loss,筛选出和目标框所对应的预测框。
因此这里的iou_loss和cls_loss,只针对目标框和筛选出的正样本预测框进行计算。
而obj_loss,则还是针对29400个预测框。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。