多目标跟踪(Multiple Object Tracking,MOT)
多目标跟踪MOT(Multiple Object Tracking),也有一些文献称作MTT(Multiple Target Tracking)。对视频中的行人、汽车、动物等多个目标进行检测并赋予ID进行轨迹跟踪。不同的目标拥有不同的ID,以便实现后续的轨迹预测、精准查找等工作。
目前多目标跟踪领域的重要基准是MOTChallenge, OpenDataLab平台已经上架了多目标跟踪(MOT)系列数据集https://opendatalab.com/?keywords=MOT
SORT(Simple Online and Realtime Tracking, ICIP 2016)
SORT是采用Tracking-by-Detection策略的目标跟踪算法,在SORT之前很少有算法能做到实时多目标跟踪。SORT算法的核心为卡尔曼滤波和匈牙利匹配。
卡尔曼滤波详见卡尔曼滤波,卡尔曼滤波的算法程序有两个步骤,分别是估计、更新:
- 在估计步骤中,卡尔曼滤波会产生有关目前状态的估计,其中也包括不确定性;
- 在更新步骤中,只要观察到下一个量测(其中一定含有某种程度的误差,包括随机噪声),会通过加权平均来修正估计值,而确定性越高的量测加权比重也越高。
匈牙利算法是一种解决二分图最大匹配问题的经典算法,详见匈牙利算法
将该帧目标检测的框和上一帧通过卡尔曼滤波预测的框一一进行IOU匹配,再通过IOU匹配的结果计算其代价矩阵(cost matrix,其计算方式是-IOU)。而代价矩阵作为匈牙利算法的输入,得到线性的匹配的结果。
python中可直接使用scipy.optimize.linear_sum_assignment完成匈牙利算法的分配。
from scipy.optimize import linear_sum_assignment
import numpy as np
# 代价矩阵
cost =np.array([[0.9,0.6,0,0],[0,0.3,0.9,0],[0.5,0.9,0,0],[0,0,0.2,0]])
# 匹配结果:该⽅法的⽬的是代价最⼩,这⾥是求最⼤匹配,所以将cost取负数
row_ind,col_ind=linear_sum_assignment(-cost)
#对应的⾏索引
print("⾏索引:\n{}".format(row_ind))
#对应⾏索引的最优指派的列索引
print("列索引:\n{}".format(col_ind))
#提取每个⾏索引的最优指派列索引所在的元素,形成数组
print("匹配度:\n{}".format(cost[row_ind,col_ind]))
如上图中,左侧的0与右侧的1匹配,左侧的1与右侧的2匹配,左侧的2与右侧的1匹配,左侧的3与右侧的3匹配。此时完成匈牙利算法。
但是我们会发现,左侧的3和右侧的3匹配度是0,不应该匹配的。所以SORT算法会对此种情况作出处理,通过设置阈值保留匹配度高的分配,将左侧剩余的目标列作Unmatched Tracks不在跟踪,将右侧剩余的目标列作Umatched Detections生成新的轨迹。接着对处理后的结果更新卡尔曼滤波器。
SORT算法的基本流程如下:
- 检测:在每一个时刻,系统接收到一组目标检测输入,通常这些检测来自于对象检测器,如Faster R-CNN,YOLO或SSD,提供了目标在当前帧中的位置和尺寸(通常是边界框)。
- 预测:对每个已跟踪的目标,使用卡尔曼滤波器基于先前的状态(位置和速度)来预测它们在当前帧中的新位置。
- 数据关联:将预测位置与当前帧中检测到的对象位置进行匹配。SORT使用匈牙利算法根据预测和检测之间的IoU(交并比)来进行最优匹配。
- 更新:基于数据关联的结果,更新卡尔曼滤波器状态。对于已匹配的检测,用它们来校正预测的位置,从而获得一个更加精确的估计。对于没有匹配的检测项,将它们作为新目标的候选。
- 创建和删除跟踪:对于每个没有与现有跟踪关联的检测,初始化一个新的卡尔曼滤波器。如果一个目标在多个连续帧中未被检测到,将其从跟踪列表中移除。
- 输出:最终输出包括目标的ID和修正后的位置。
SORT算法匹配核心代码如下
if track_indices is None:
track_indices = np.arange(len(tracks))
if detection_indices is None:
detection_indices = np.arange(len(detections))
if len(detection_indices) == 0 or len(track_indices) == 0:
return [], track_indices, detection_indices # Nothing to match.
cost_matrix = distance_metric(
tracks, detections, track_indices, detection_indices)
cost_matrix[cost_matrix > max_distance] = max_distance + 1e-5
row_indices, col_indices = linear_assignment(cost_matrix)
matches, unmatched_tracks, unmatched_detections = [], [], []
for col, detection_idx in enumerate(detection_indices):
if col not in col_indices:
unmatched_detections.append(detection_idx)
for row, track_idx in enumerate(track_indices):
if row not in row_indices:
unmatched_tracks.append(track_idx)
for row, col in zip(row_indices, col_indices):
track_idx = track_indices[row]
detection_idx = detection_indices[col]
if cost_matrix[row, col] > max_distance:
unmatched_tracks.append(track_idx)
unmatched_detections.append(detection_idx)
else:
matches.append((track_idx, detection_idx))
SORT算法的一个关键特点是它相对较简单,计算效率较高,因此它可以应用于实时视频跟踪任务。然而,SORT算法在处理遮挡、交互和快速运动时的性能会下降,因为它不考虑外观信息,仅依赖于运动连续性。为了克服这些限制,后续研究提出了DeepSORT算法,它通过整合深度学习生成的外观信息来改进跟踪性能。
Deep-SORT(ICIP 2017)
DeepSORT(Deep Learning based Object SORTing)算法在SORT算法的基础上引入了深度学习来提取目标的外观特征,增强了算法在处理目标遮挡和复杂交互时的鲁棒性。DeepSORT的优化主要体现在以下几个方面:
- 外观信息(ReID):DeepSORT使用深度学习模型(如CNN)来提取目标的外观特征。这些特征与运动信息一起被用来进行数据关联,使得算法在目标发生遮挡或者相互交叠时表现得更加稳定。
- 二次确认策略:DeepSORT为每个跟踪器实现了一个年龄计数器,并且引入了一个“未确认”状态。一个新的跟踪目标在成为“确认”跟踪之前,需要在多个连续帧中被检测到。这有助于避免由于误检或瞬时噪声产生虚假跟踪。
- 连续跟丢处理:若目标暂时从视野中消失(例如被遮挡),DeepSORT会保持目标的跟踪状态一段时间,而不是立即将其删除。这样一来,目标一旦重新出现时,算法能够重新关联目标,保持ID的一致性。
- 跟踪器管理:DeepSORT对跟踪器的创建和删除采取了更为保守的策略。它只有在新检测到的目标在数个帧中持续出现时才创建新的跟踪器,若跟踪目标在较长时间内未被检测到,才将其删除。
ReID
卡尔曼滤波器更新的时候会使用ReID,DeepSORT的ReID通过构建CNN训练Market-1501数据集来实现行人重识别,这也是DeepSORT名为deep的原因。
Market-1501数据集在清华大学校园中采集,夏天拍摄,在 2015 年构建并公开。它包括由 6 个摄像头(其中 5 个高清摄像头和 1 个低清摄像头)拍摄到的 1501 个行人、32668 个检测到的行人矩形框。每个行人至少由 2 个摄像头捕获到,并且在一个摄像头中可能具有多张图像。训练集有 751 人,包含 12,936 张图像,平均每个人有 17.2 张训练数据;测试集有 750 人,包含 19,732 张图像,平均每个人有 26.3 张测试数据。
class Net(nn.Module):
def __init__(self, num_classes=751 ,reid=False):
super(Net,self).__init__()
# 3 128 64
self.conv = nn.Sequential(
nn.Conv2d(3,64,3,stride=1,padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
# nn.Conv2d(32,32,3,stride=1,padding=1),
# nn.BatchNorm2d(32),
# nn.ReLU(inplace=True),
nn.MaxPool2d(3,2,padding=1),
)
# 32 64 32
self.layer1 = make_layers(64,64,2,False)
# 32 64 32
self.layer2 = make_layers(64,128,2,True)
# 64 32 16
self.layer3 = make_layers(128,256,2,True)
# 128 16 8
self.layer4 = make_layers(256,512,2,True)
# 256 8 4
self.avgpool = nn.AvgPool2d((8,4),1)
# 256 1 1
self.reid = reid
self.classifier = nn.Sequential(
nn.Linear(512, 256),
nn.BatchNorm1d(256),
nn.ReLU(inplace=True),
nn.Dropout(),
nn.Linear(256, num_classes),
)
def forward(self, x):
x = self.conv(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.avgpool(x)
x = x.view(x.size(0),-1)
# B x 128
if self.reid:
x = x.div(x.norm(p=2,dim=1,keepdim=True))
return x
# classifier
x = self.classifier(x)
return x
ByteTrack(ECCV2022 )
ByteTrack是字节跳动被ECCV2022 接收的算法,详见https://blog.csdn.net/chl666888/article/details/123475954,https://zhuanlan.zhihu.com/p/554889181。
其主要工作流程为:
- BYTE 会将每个检测框根据得分分成两类,高分框和低分框,总共进行两次匹配。
- 第一次使用高分框和之前的跟踪轨迹进行匹配。
- 第二次使用低分框和第一次没有匹配上高分框的跟踪轨迹(例如在当前帧受到严重遮挡导致得分下降的物体)进行匹配。
- 对于没有匹配上跟踪轨迹,得分又足够高的检测框,对其新建一个跟踪轨迹。对于没有匹配
上检测框的跟踪轨迹,会保留 30 帧,在其再次出现时再进行匹配。
在数据关联的过程中,和 SORT 一样,只使用卡尔曼滤波来预测当前帧的跟踪轨迹在下一帧的位置,预测的框和实际的检测框之间的 IoU 作为两次匹配时的相似度,通过匈牙利算法完成匹配。
这里值得注意的是ByteTrack没有使用 ReID 特征来计算外观相似度。第一点是为了尽可能做到简单高速。第二点是作者发现在检测结果足够好的情况下,卡尔曼滤波的预测准确性非常高,能够代替 ReID 进行物体间的长时刻关联。
BOT-SORT(2022)
创新点:
- 在ByteTrack的基础上,添加了一个相机运动补偿,使得基于检测器的跟踪器性能得以提升。
- 更有效地IOU和Re-ID的融合的方法,使得检测和轨迹之间的关联更健壮。
(TODO)
FairMOT(IJCV 2021)
FairMOT属于JDE(Jointly learns the Detector and Embedding model )的一种。作者来自华中科技大学和微软亚洲研究院。
作者提到目前JDE的检测方式同时提取检测框和检测框内物体的Re-ID信息(低维的向量信息)。而基于Anchor-Based 检测器产生出来的anchor并不适合去学习合适的Re-ID信息。
-
一个物体可能被多个anchor负责并进行检测,这会导致严重的网络模糊性(ambiguities for the network)。
-
实际物体的中心可能与负责对该物体进行检测的anchor中心有偏差。
作者在encoder-decoder阶段选择了一个叫做DLA( Deep Layer Aggregation)的网络进行特征提取,这个网络的最大特点就是多层融合(恰好符合Re-ID信息需要多层信息融合的特点)。这种形式类似于目标检测的FPN。DLA输出四个有效特征层,其中三个被用来检测物体(Detection),一个被用来输出物体的Re-ID信息(Re-ID)。
网络的最后输出为:
- heatmap,负责估计对象中心的位置,形状为(1,H,W)。使用变形的focal loss进行预测的heatmap和实际真实的heatmap损失函数的求解。
- center offset,形状为(2,H,W),负责估计对象中心位置由于下采样产生的轻微的偏差,使用L1损失求解。
- bbox size,形状为(2,H,W),负责计算中心点对应检测框的宽高,使用L1损失求解。
- Re-ID Embedding,形状为(128,H,W),也就是每个物体用一个128维向量表示。使用Identity Embedding Loss求解。
至此,通过读取数据、正向传播、计算损失、反向传播便可以完成FairMOT的训练部分。
FairMOT的预测部分:
- 通过网络正向传播获取物体的位置和Re-ID Embedding
- 经卡尔曼滤波预测位置
- 与Re-ID Embedding计算距离构造代价矩阵
- 匈牙利算法进行分配
- 计算matches, unmatched_tracks, unmatched_detections
STrack.multi_predict(strack_pool)
dists = matching.embedding_distance(strack_pool, detections)
#dists = matching.iou_distance(strack_pool, detections)
dists = matching.fuse_motion(self.kalman_filter, dists, strack_pool, detections)
matches, u_track, u_detection = matching.linear_assignment(dists, thresh=0.4)
for itracked, idet in matches:
track = strack_pool[itracked]
det = detections[idet]
if track.state == TrackState.Tracked:
track.update(detections[idet], self.frame_id)
activated_starcks.append(track)
else:
track.re_activate(det, self.frame_id, new_id=False)
refind_stracks.append(track)
单目标跟踪(Visual Object Tracking(VOT),Single Object Tracking(SOT))
对于一段视频序列,在视频开始时,给定跟踪目标的位置,通过设计算法得到后续帧中目标的位置和尺度信息。只关注一个目标,并且可以跟踪任意类别的目标,无类别限制。
单目标检测的数据集通常使用VOTChallenge(https://votchallenge.net),OpenDataLab平台已经上架了单目标跟踪(VOT,SOT)系列数据集https://opendatalab.com/?keywords=VOT
Pysot(https://github.com/STVIR/pysot)是由商汤视频智能研究小组上传在Github的一个开源项目,由pytorch深度学习框架提供支持,这个项目还包含一个评估跟踪器的python工具包端口。Pysot 的目标是为视觉跟踪研究提供高质量、高性能的代码库,十分灵活,便于支持新研究的快速实施和评估。Pysot 包括以下视觉跟踪算法的实现:SiamRPN、DaSiamRPN、SiamRPN++、SiamMask,使用以下骨干网络架构:AlexNet、MobileNetV2、ResNet{18, 34, 50},评价工具包可以支持的数据集有OTB2015、VOT16/18/19、VOT18-LT、LaSOT、UAV123。
SiamFC(Fully-Convolutional Siamese Networks for Object Tracking, 2016)
SiamFC使用全卷积孪生网络进行相似性学习,来解决跟踪任意对象的问题。在孪生网络之前,相关滤波算法使用从视频本身提取的示例以在线方式学习目标外观的模型,但是这些算法的明显缺陷是只能学习相对简单的模型。
训练过程:
- 从同一视频序列中随机抽取两帧z和x,z在x前。
- 将z的目标提取,并缩放至指定尺寸,例如127*127
- 将x完成图像调整为指定尺寸,例如255*255
- 用同一CNN网络对z和x做卷积,根据网络下采样倍率进行特征提取。
- 用z经过CNN后的特征层对x经过CNN后的特征层做卷积,经激活函数sigmoid得到结果scores。
训练时,label也就是GT会被处理为如下形式,以中心点最为最大响应的地方,并以半径R的范围设置正样本,即值为1,其余位置为0。这样对scores和GT做BCELoss即可(一般会带weight)。
预测过程:
- 选取视频第一帧作为z,在追踪过程中z一直为第一帧
- 指定z的目标框,将目标提取并缩放到指定尺寸
- 对于后续帧,分别拆帧选为x,经网络正向传播,获得结果scores。
- 找出scores最大值对应的索引
- 将索引位置还原到图像,完成预测。
在实际过程中,预测过程也会对x做中心随机裁剪变成3个(作者称作多尺度变换),求得三个map的最大值,再对三个值求最大值,得到索引。如果真的考虑目标尺寸不一致的问题,可以就借鉴目标检测的多尺度变换技术,例如FPN。
@torch.no_grad() #全局过程中不求导,禁止使用上下文管理器
#传入后续帧,然后根据SiamFC跟踪流程返回目标的box坐标
def update(self, img):
self.model.eval()
"""----------------正向推导得到response map最大值位置---------------------"""
#三种patch边长 patch*3scales
x = x_to3s255(img,self.center,self.x_sz,self.scale_factors,cfg.instance_sz,self.avg_color)
#numpy转为float的torch型张量
x = torch.from_numpy(x).to(self.device).permute(0, 3, 1, 2).float()
#[3,255,22,22]
x = self.model(x)
#得到三种尺度下的response map
responses = self.corr(self.kernel, x) * cfg.out_reduce #[3,1,17,17]
responses = responses.squeeze(1).cpu().numpy() #压缩为[3,17,17]并转为numpy作后续计算处理
#将17x17大小的response map->[3,272,272]
responses = map_to272(responses,out_size=self.response_upsz)
#找到最大值属于哪个response map,并把该response map赋给response
scale_id = np.argmax(np.amax(responses, axis=(1, 2))) #里面求得三个map的最大值 再对三个值求最大值 得到索引
def x_to3s255(img,center,patch_size,three_scales,out_size,border_value):
x = [utils.crop_and_resize(
img, center, patch_size * scale,
out_size=out_size,
border_value=border_value) for scale in three_scales]
x = np.stack(x, axis=0) # [3,255,255,3]第一个三代表三种尺度
return x
SiamRPN(CVPR2018)
SiamRPN借鉴了目标检测中的区域候选网络(Regeon Proposal Network,RPN),最早由Faster-RCNN提出。RPN由两个小分支组成,分别用于分类和回归。分类分支对输入图像中生成的anchors进行分类,判断哪些anchors属于前景foreground,哪些anchors属于背景background,并从正例中选出top的K个anchors作为候选的proposals。回归分支对anchors进行微调和校正,使其能够更精确地框出物体的形状,最终得到合适的bounding box。SiamRPN把这种策略应用到了目标跟踪领域中。
区别为,Faster-RCNN的RPN设置的K=3,尺度为【0.5, 1,2】,SiamRPN取K=5,ratio设定为【0.33,0.5,1,2,3】,即每个grid内生成5个anchors。
在SiamRPN中,网络由孪生网络,分类网络,回归网络三个部分组成。三个部分分别使用相同网络对z和x进行推理。
训练的过程与anchor-base的过程类似,详见另一篇文章以YOLOV4为例详解anchor_based目标检测训练过程。损失分别是分类损失(CE、BCE)和回归损失(smooth L1)。
预测过程与SiamFC类似,选取第一帧,给出第一帧的目标框,然后对后续所有帧经过网络推理。得到cls和reg两个计算结果,经偏移项计算得分,取得分最大值对应的框,还原到原始图像,即为预测结果。
outputs = self.model.track(x_crop)
score = self._convert_score(outputs['cls'])
pred_bbox = self._convert_bbox(outputs['loc'], self.anchors)
def change(r):
return np.maximum(r, 1. / r)
def sz(w, h):
pad = (w + h) * 0.5
return np.sqrt((w + pad) * (h + pad))
# scale penalty
s_c = change(sz(pred_bbox[2, :], pred_bbox[3, :]) /
(sz(self.size[0]*scale_z, self.size[1]*scale_z)))
# aspect ratio penalty
r_c = change((self.size[0]/self.size[1]) /
(pred_bbox[2, :]/pred_bbox[3, :]))
penalty = np.exp(-(r_c * s_c - 1) * cfg.TRACK.PENALTY_K)
pscore = penalty * score
# window penalty
pscore = pscore * (1 - cfg.TRACK.WINDOW_INFLUENCE) + \
self.window * cfg.TRACK.WINDOW_INFLUENCE
best_idx = np.argmax(pscore)
bbox = pred_bbox[:, best_idx] / scale_z
lr = penalty[best_idx] * score[best_idx] * cfg.TRACK.LR
cx = bbox[0] + self.center_pos[0]
cy = bbox[1] + self.center_pos[1]
# smooth bbox
width = self.size[0] * (1 - lr) + bbox[2] * lr
height = self.size[1] * (1 - lr) + bbox[3] * lr
# clip boundary
cx, cy, width, height = self._bbox_clip(cx, cy, width,
height, img.shape[:2])
# udpate state
self.center_pos = np.array([cx, cy])
self.size = np.array([width, height])
bbox = [cx - width / 2,
cy - height / 2,
width,
height]
best_score = score[best_idx]
return {
'bbox': bbox,
'best_score': best_score
}
SiamRPN++(CVPR 2019)
商汤智能视频团队在孪生网络上做了一系列工作,包括将检测引入跟踪后实现第一个高性能孪生网络跟踪算法的 SiamRPN(CVPR 18),更好地利用训练数据增强判别能力的 DaSiamRPN(ECCV 18),以及最新的解决跟踪无法利用到深网络问题的 SiamRPN++(CVPR 19)。其中 SiamRPN++在多个数据集上都完成了 10% 以上的超越,并且达到了 SOTA 水平。
DaSiamRPN
在SiamRPN和SiamRPN++中间存在一个DaSiamRPN网络,其主要作用如下:
- SiamRPN的训练集使用VID和YouTube-BB(物体种类较少,分别只包含20和30个类。作者引入现有的检测数据集来充实正样本数据,将COCO 和 ImageNet Det 引入了训练,通过数据增强生成可用于训练的图片对,极大地丰富了训练集中的类别信息。
- 构造有语义的负样本,充实了困难负样本数据,让网络学习判别能力。
SiamRPN++做出的创新:
- 在此之前,孪生网络对于单目标跟踪任务中,SiamRPN修改了算法,DaSiamRPN增大了训练集。SiamRPN++将对于孪生网络本身做出优化。为了解决深网络这个 Siamese 跟踪器的痛点,商汤智能视频团队基于之前 ECCV2018 的工作(DaSiamRPN),通过分析孪生神经网络训练过程,发现孪生网络在使用现代化深度神经网络存在位置偏见问题,而这一问题是由于卷积的 padding 会破坏严格的平移不变性。然而深网络并不能去掉 padding,为了缓解这一问题,让深网络能够在跟踪提升性能,SiamRPN++中提出在训练过程中加入位置均衡的采样策略。通过修改采样策略来缓解网络在训练过程中的存在的位置偏见问题,让深网络能够发挥出应有的效果。(论文做了试验去论述,其实解决方法就是数据增强阶段进行-64~64的随机平移)
- 使用深层网络ResNet
解决了位置偏见问题,就可以使用深层网络实现目标跟踪了。
SiamRPN++分别在conv3_3、conv4_6、conv5_3使用RPN网络计算分类和回归,并在最后完成堆叠。
在深度卷积神经网路中,随着网络层数的变化,不同层所代表的语义信息是不一样的。取三个阶段的信息进行堆叠,可以有效利用网络各阶段信息。
class SiamRPN_plus_plus(nn.Module):
def __init__(self):
super(SiamRPN_plus_plus, self).__init__()
self.examplar_branch = resnet50()
self.search_region_branch = resnet50()
self.conv3_3_RPN = RPN()
self.conv4_6_RPN = RPN()
self.conv5_3_RPN = RPN()
self.weighted_sum_layer_alpha = nn.Conv2d(
30, 10, kernel_size=1, padding=0, groups=10)
self.weighted_sum_layer_beta = nn.Conv2d(
60, 20, kernel_size=1, padding=0, groups=20)
def forward(self, examplar, search_region):
_, examplar_conv_3_output, examplar_conv_4_output, examplar_conv_5_output = self.examplar_branch(
examplar)
_, search_region_conv_3_output, search_region_conv_4_output, search_region_conv_5_output = self.search_region_branch(search_region)
conv3_3_cls_prediction, conv3_3_bbox_regression_prediction = self.conv3_3_RPN(
examplar_conv_3_output, search_region_conv_3_output, examplar.size()[0])
conv4_6_cls_prediction, conv4_6_bbox_regression_prediction = self.conv4_6_RPN(
examplar_conv_4_output, search_region_conv_4_output, examplar.size()[0])
conv5_3_cls_prediction, conv5_3_bbox_regression_prediction = self.conv5_3_RPN(
examplar_conv_5_output, search_region_conv_5_output, examplar.size()[0])
# ipdb.set_trace()
stacked_cls_prediction = torch.cat((conv3_3_cls_prediction, conv4_6_cls_prediction, conv5_3_cls_prediction), 2).reshape(
examplar.size()[0], 10, -1, 25, 25).reshape(examplar.size()[0], -1, 25, 25)
stacked_regression_prediction = torch.cat(
(conv3_3_bbox_regression_prediction, conv4_6_bbox_regression_prediction, conv5_3_bbox_regression_prediction), 2).reshape(examplar.size()[0], 20, -1, 25, 25).reshape(examplar.size()[0], -1, 25, 25)
# ipdb.set_trace()
fused_cls_prediction = self.weighted_sum_layer_alpha(
stacked_cls_prediction)
fused_regression_prediction = self.weighted_sum_layer_beta(
stacked_regression_prediction)
return fused_cls_prediction, fused_regression_prediction
其中,30 = 5 个anchor x 2(前景、背景) x 3个有效特征层堆叠。60 = 5个anchor × 4个位置信息(x,y,w,h)×3个有效特征层堆叠。