1. 目标检测的“灵魂拷问”:到底该让谁去学哪个目标?
大家好,我是老张,在AI和计算机视觉这行摸爬滚打了十来年,做过不少目标检测的项目。今天想和大家聊聊一个听起来有点“玄学”,但实际上至关重要的问题:在训练一个目标检测模型时,我们怎么决定图片里成千上万个“候选框”(也叫锚点或预测框)里,哪些是“好学生”需要认真学习某个真实目标,哪些是“坏学生”需要被惩罚,哪些又是“背景板”直接忽略?
这个问题,就是标签分配,或者叫正负样本分配。它就像是给一群学生分配导师,分配得好,学生们(模型)学得快、学得准;分配得不好,要么是“好苗子”没被发掘,要么是“坏学生”带偏了节奏,整个班级(模型)的成绩(检测精度)就上不去。
早期的方法很简单粗暴,比如YOLOv1到v3,或者Faster R-CNN,基本就是看“长相”——计算候选框和真实目标框的重叠面积(IoU)。如果重叠度超过一个固定阈值(比如0.5),就认为这个候选框是正样本,让它去学习这个目标;低于另一个阈值(比如0.4),就判定为负样本(背景);夹在中间的?不好意思,直接忽略,当作“模糊样本”不参与训练。这种方法我们称之为静态分配或者基于规则的分配。
静态分配有什么问题呢?我举个实际的例子。一张图里有个很小的行人,旁边有个很大的汽车。对于小行人,可能只有一两个候选框能和它的IoU超过0.5,导致正样本极少,模型学到的特征不充分,小目标检测效果就差。对于大汽车,可能几十个候选框都满足条件,但其中有些框虽然IoU达标,位置却偏得离谱,让它们去学习,反而会引入噪声。更头疼的是拥挤场景,两个目标挨得很近,它们的候选框大面积重叠,这个框到底该学哪个目标?静态规则无法做出全局最优的判断。
所以,研究者们开始思考:能不能让分配策略“聪明”一点,能根据每张图片、每个目标的实际情况,动态地、自适应地决定谁该学谁?这就是动态标签分配技术的由来。今天,我们就来深入聊聊这个领域里两个里程碑式的工作:OTA和它的高效改进版SimOTA。我会用最直白的话,结合我实际调参、踩坑的经验,帮你把它们的核心思想、实现细节和工程价值讲明白。
2. OTA:把分配问题变成一道“最优运输”的数学题
OTA的全称是Optimal Transport Assignment,翻译过来就是“最优传输分配”。这篇论文在2021年的CVPR上发表,它的核心思想非常巧妙:不再用死板的规则,而是把标签分配看作一个全局优化问题。
2.1 从“快递配送”理解OTA思想
想象一下,你是一个物流中心的总调度。你有:
- 供应商:几家工厂(好比图片中的真实目标,GT),每个工厂有一批特定型号的货物(正样本标签)。另外,还有一个特殊的“背景”供应商,它有海量的“空包裹”(负样本标签)。
- 需求方:成千上万个配送点(好比图片上的所有锚点或预测框),每个点都需要且只需要一个包裹(一个标签)。
- 运输成本:从某个工厂送一个特定型号的货物到某个配送点,是有成本的。这个成本取决于货物和需求是否匹配。
你的目标是:找到一种配送方案,让总的运输成本最低。
在目标检测里,这个“运输成本”怎么定义呢?非常直观:
- 如果一个预测框被分配去学习某个真实目标,那么成本就是这个框预测该目标的分类损失加上预测框位置的回归损失的加权和。预测得越准(分类置信度高、框的位置准),损失越小,成本就越低。
- 如果一个预测框被分配为背景,那么成本就仅仅是它预测为背景的分类损失(通常我们希望它预测为“背景”类的概率很高)。
OTA的聪明之处在于,它通过求解这个“最优运输”问题,一次性为所有预测框和所有目标找到了全局最优的匹配关系。它天然就能处理那些棘手的“模糊框”:一个框如果同时和两个目标都重叠,OTA会计算它分配给不同目标所产生的成本,最终选择那个使得全局总成本最低的分配方式,而不是武断地按某个规则(比如谁IoU大就归谁)处理。
2.2 OTA的求解:Sinkhorn-Knopp算法
那么,这个“最优运输”问题怎么解呢?这涉及到经典的最优传输理论。OTA论文里使用了Sinkhorn-Knopp迭代算法来求解。你不需要完全理解这个算法的数学细节,可以把它想象成一个高效的“智能匹配算法”,它能考虑到所有工厂和所有配送点之间的两两成本,最终算出一个最优的配送矩阵。
这个矩阵的每个元素值,代表了从一个GT(或背景)分配给一个预测框的“标签量”。在OTA的设置里,每个GT假设可以提供k个正标签(k是一个超参数,比如通常设为5或10),每个预测框需要一个标签。算法最终会输出一个稀疏的矩阵,其中非零的位置就代表了正样本的分配关系。
OTA带来的提升是显著的。在FCOS、RetinaNet等经典检测器上,直接换上OTA策略,往往能带来超过1个点的AP(平均精度)提升,而且这种提升是“无痛”的——不改变模型结构,只改变训练时的样本分配策略。
但是,OTA有个明显的缺点:慢。Sinkhorn-Knopp迭代虽然高效,但相比简单的规则计算,它仍然增加了可观的计算开销。根据YOLOX团队的实测,在COCO数据集上训练300个epoch,OTA带来的额外计算会使总训练时间增加约25%。这对于动辄需要训练几天的工业级模型来说,是一个不小的负担。
3. SimOTA:化繁为简,在精度和效率间找到黄金平衡点
正是看到了OTA的精度优势和效率瓶颈,旷视科技在推出YOLOX时,提出了一个简化版的OTA——SimOTA。SimOTA的核心思想是:保留OTA全局优化和动态分配的精髓,但用一套更直观、更高效的启发式规则,来近似替代复杂的Sinkhorn-Knopp求解过程。
SimOTA的流程,我们可以把它拆解成五个清晰的步骤,我结合代码和实际项目中的理解来给大家捋一捋。
3.1 第一步:初筛——划定“候选人”范围
如果对全图上所有的预测框(动辄上万个)都进行两两成本计算,那开销太大了。SimOTA的第一步是做一个快速的空间先验筛选,大大缩小候选框的数量。
具体做法是,对于每一个真实目标框(GT):
- 中心区域筛选:找出所有中心点落在这个GT框内部的预测框。这是最基本的要求,一个框的中心都不在目标内部,它去学习这个目标显然不合适。
- 固定中心区域筛选:以GT框的中心点为中心,画一个边长为5(对应特征图尺度)的正方形区域(称为
fixed center area)。只保留那些同时满足条件1且中心点落在这个小正方形内的预测框。
经过这两步,我们得到的候选框,可以认为是与当前GT在空间位置上“强相关”的框。这一步过滤掉了大量明显不相关的背景框,将计算复杂度降了下来。下图直观展示了这个过程,只有被橙色勾选中的预测框才会进入下一轮计算。
注意:这个5x5的中心先验区域大小是个经验值,在YOLOX中是一个可调参数。对于小目标,这个区域可以适当缩小;对于大目标,可以适当放大。
3.2 第二步:计算“成本矩阵”
对于每一个GT和它对应的那一小撮候选框,我们计算一个成本矩阵。这个成本的定义和OTA类似,但形式上更简洁:
Cost = 分类损失 + λ * 回归损失 + 超大惩罚项 * (不在中心区域)
我们来拆解一下:
- 分类损失:通常用交叉熵损失,计算候选框预测为当前GT类别的置信度损失。预测越准,损失越小。
- 回归损失:通常用IoU Loss或GIoU Loss,计算预测框和GT框的位置差异。框越准,损失越小。
- λ:一个平衡系数,用来调节分类和回归的重要性。在YOLOX中,这个值通常设为3.0或5.0。
- 超大惩罚项:这是SimOTA实现空间筛选的关键。对于在第一步中,那些中心点在GT框内但不在
fixed center area内的候选框(即上图黑色勾的框),我们给它加上一个巨大的数(比如100000.0)。这样,在后续选择成本最小的框时,这些框因为成本极高,会自然被排除在外。这相当于用“成本惩罚”的软约束,替代了严格的“中心区域”硬约束,让分配更灵活。
3.3 第三步:动态决定“录取名额”——Dynamic k
这是SimOTA最精彩的设计之一。传统的静态分配,每个GT分配的正样本数k是固定的。但显然,一个大目标需要的正样本(学习它的预测框)应该比一个小目标多;一个清晰、孤立的目标比一个被严重遮挡的目标需要的正样本也可能不同。
SimOTA如何动态决定每个GT的k值呢?
- 计算该GT与所有候选框的IoU。
- 对这些IoU值进行排序,选出前
n_candidate_k个最大的(n_candidate_k通常取10和候选框数量的最小值)。 - 将这前
n_candidate_k个IoU值求和,然后取整,得到的结果就是这个GT的dynamic_k。
这个操作非常巧妙。如果一个GT有很多高质量的候选框(IoU都很大),那么这些IoU求和后值就大,dynamic_k也大,意味着它“录取”更多的正样本。反之,如果一个GT质量不高,或者候选框质量都一般(IoU小),那么dynamic_k就小,可能只分配一两个正样本,避免引入噪声。
3.4 第四步:执行分配——成本最小化
现在,对于每个GT,我们有了:
- 一小批候选框。
- 一个对应的成本矩阵(每个候选框对应一个成本值)。
- 一个动态的录取名额
dynamic_k。
分配就变得非常简单直接:对这个GT,从它的候选框中,挑选出成本最小的前dynamic_k个,作为它的正样本。
3.5 第五步:处理冲突——一对一的承诺
上一步分配完,可能会出现一个预测框被多个GT“看中”的情况(冲突)。SimOTA的处理规则是:这个预测框只归属于让它成本最低的那个GT。这保证了分配的一对一关系,一个预测框最终只负责学习一个目标(或背景)。
经过这五步,我们就完成了SimOTA的标签分配。所有被选中的预测框成为正样本,其余的全部作为负样本。
3.6 SimOTA的优势:为什么它是“工程利器”
从我实际在YOLOX和其他自定义模型上使用SimOTA的经验来看,它的优势非常突出:
- 精度无损:在COCO等标准数据集上,SimOTA达到的检测精度与完整的OTA几乎持平,显著优于ATSS等之前的动态分配方法。
- 速度极快:完全避免了Sinkhorn-Knopp迭代。整个分配过程主要由矩阵计算和Top-k选择构成,计算开销极小,相比OTA节省了约25%的训练时间,几乎与静态分配策略的速度相当。
- 超参数少且鲁棒:核心超参数主要是中心先验区域的大小和成本平衡系数λ。这些参数在不同数据集上表现稳定,调参压力小。
- 直觉性强:整个流程(初筛->算成本->动态k->选最小->解冲突)符合人的直觉,易于理解和调试。
可以说,SimOTA找到了动态标签分配中“精度”和“效率”的一个完美平衡点,这也是它能被YOLOX采纳并随之广泛流行的根本原因。
4. 横向对比:ATSS、OTA、SimOTA与TAL
为了让大家更清晰地理解动态标签分配的演进脉络,我们把这几个主流方法放在一起做个对比。我整理了一个表格,从核心思想、关键操作、优缺点和适用场景来剖析它们。
| 方法 | 全称与核心思想 | 关键操作/规则 | 优点 | 缺点 | 典型应用/场景 |
|---|---|---|---|---|---|
| ATSS | Adaptive Training Sample Selection 自适应阈值:根据GT的统计特性(IoU均值+标准差)动态设定IoU阈值来筛选正样本。 | 1. 为每个GT在每层FPN找中心最近的k个候选锚。 2. 计算候选锚与GT的IoU,求均值m和标准差v。 3. 设定阈值 t = m + v。 4. 选择IoU >= t 且中心在GT内的锚为正样本。 | 1. 几乎无需调参(仅k一个参数)。 2. 自适应不同目标尺度。 3. 显著提升Anchor-based和Anchor-free模型性能。 | 1. 仍是局部分配,未考虑全局模糊样本冲突。 2. 依赖IoU,对极端形状目标可能不友好。 | RetinaNet, FCOS的改进,作为轻量级动态分配 baseline。 |
| OTA | Optimal Transport Assignment 全局最优:将分配建模为最优传输问题,寻求全局成本最低的分配方案。 | 1. 定义GT和背景为“供应商”,锚点为“需求方”。 2. 构建分类+回归损失的成本矩阵。 3. 使用Sinkhorn-Knopp算法求解全局最优传输计划。 | 1. 全局优化,能最佳处理模糊样本冲突。 2. 理论优雅,分配结果全局最优。 3. 精度提升显著。 | 1. 计算开销大,训练速度慢。 2. 需要设置每个GT的初始标签数k等超参数。 | 对精度要求极高,且不计较训练成本的学术研究或离线场景。 |
| SimOTA | Simplified OTA 高效近似:用启发式规则(动态k + Top-k成本选择)近似OTA的全局优化。 | 1. 中心先验初筛候选锚。 2. 计算成本矩阵(Cls Loss + λ*Reg Loss)。 3. 为每个GT计算dynamic_k(基于IoU Top10求和)。 4. 为每个GT选取成本最小的前dynamic_k个锚。 5. 解决冲突(一个锚只归成本最小的GT)。 | 1. 在几乎不损失精度的前提下,速度极快。 2. 流程直观,易于实现和调试。 3. 超参数少,鲁棒性强。 | 1. 是OTA的近似,理论上非全局最优解。 2. 中心先验区域大小需要调节(但通常使用默认值即可)。 | YOLOX的标配,工业部署和实际项目的首选,平衡精度与效率的典范。 |
| TAL | Task-Aligned Learning 任务对齐:设计一个同时反映分类和定位质量的指标,并基于此分配样本。 | 1. 定义任务对齐指标 t = s^α * u^β,其中s是分类得分,u是IoU。 2. 根据t值排名,为每个GT选择top-m的锚作为正样本。 3. 使用任务对齐损失,让分类和定位任务相互促进。 | 1. 显式地对齐分类和定位任务,缓解了任务不一致问题。 2. 分配和损失函数协同设计,提升整体性能。 | 1. 需要与特定的T-head结构配合使用,耦合性较强。 2. 指标中的指数α、β需要调节。 | TOOD检测器,适用于希望显式加强分类-定位一致性的场景。 |
简单总结一下演进路线:ATSS开启了动态自适应分配的大门;OTA引入了全局最优的视角,将问题理论化,但计算复杂;SimOTA抓住了OTA的精髓,用一套巧妙的工程实现大幅提升了效率,成为落地实践的主流选择;而TAL则从另一个角度出发,关注分类和定位任务本身的对齐问题。
在实际项目中,我的选择策略通常是:如果追求极致的部署速度和代码简洁,并且数据集目标不算特别拥挤,ATSS是一个不错的起点。如果追求更高的精度,并且训练资源相对充足,SimOTA几乎是当前默认的最佳选择,尤其是在YOLO系列模型中。只有在一些非常特殊的、分类和定位难度都极高的场景下,我才会去考虑结合TAL思想进行更定制化的设计。
5. 动手实践:在自定义数据集上实现SimOTA
理论说得再多,不如动手试一下。这里我以在PyTorch框架下,为一个简单的单阶段检测器(例如,一个简化版的RetinaNet或FCOS)集成SimOTA为例,勾勒出核心代码逻辑和关键注意事项。请注意,以下代码是示意性的,聚焦于SimOTA分配流程本身。
import torch
import torch.nn.functional as F
def simota_assign(gt_bboxes, gt_labels, anchors, pred_scores, pred_bboxes, center_radius=2.5, topk_candidate=10):
"""
SimOTA 标签分配核心函数
Args:
gt_bboxes: (Tensor) 真实框,形状 [num_gts, 4] (x1, y1, x2, y2)
gt_labels: (Tensor) 真实标签,形状 [num_gts]
anchors: (Tensor) 锚框/预测点,形状 [num_anchors, 4] 或 [num_anchors, 2](如果是anchor-free的中心点)
pred_scores: (Tensor) 网络预测的分类得分,形状 [num_anchors, num_classes]
pred_bboxes: (Tensor) 网络预测的边界框,形状 [num_anchors, 4]
center_radius: (float) 固定中心区域的半径(在特征图尺度上)
topk_candidate: (int) 计算dynamic_k时考虑的top k IoU
Returns:
assigned_gt_inds: (Tensor) 每个anchor分配到的GT索引(-1表示背景,0~num_gts-1表示正样本)
assigned_labels: (Tensor) 每个anchor分配到的类别标签
"""
num_gts = gt_bboxes.size(0)
num_anchors = anchors.size(0)
# 1. 初始筛选:中心点落在GT框内
# 假设anchors是anchor-free的中心点格式 [x_center, y_center]
anchor_centers = anchors[:, :2]
# 计算每个anchor中心是否在每个gt框内 [num_gts, num_anchors]
inside_gt_bbox_mask = (anchor_centers[None, :, 0] >= gt_bboxes[:, 0:1]) & \
(anchor_centers[None, :, 0] <= gt_bboxes[:, 2:3]) & \
(anchor_centers[None, :, 1] >= gt_bboxes[:, 1:2]) & \
(anchor_centers[None, :, 1] <= gt_bboxes[:, 3:4])
# 2. 固定中心区域筛选
gt_centers = (gt_bboxes[:, :2] + gt_bboxes[:, 2:]) / 2
# 计算anchor中心到每个gt中心的距离 [num_gts, num_anchors, 2]
dist = anchor_centers[None, :, :] - gt_centers[:, None, :]
# 判断是否在中心半径内
inside_center_mask = (dist.abs() <= center_radius).all(dim=-1)
# 最终候选区域:既在GT框内,又在中心区域内
candidate_mask = inside_gt_bbox_mask & inside_center_mask # [num_gts, num_anchors]
# 如果没有GT,全部设为背景
if num_gts == 0:
return torch.full((num_anchors,), -1, dtype=torch.long, device=anchors.device), \
torch.full((num_anchors,), -1, dtype=torch.long, device=anchors.device)
# 3. 计算成本矩阵
# 计算pair-wise的分类损失 (Focal Loss或CE)
# pred_scores: [num_anchors, num_classes], gt_labels: [num_gts]
# 我们需要每个anchor对每个gt的类别预测概率
gt_labels_expanded = gt_labels.view(-1, 1).expand(-1, num_anchors) # [num_gts, num_anchors]
# 假设使用sigmoid分类,计算二值交叉熵损失作为分类成本
# 这里简化处理,实际可能用Focal Loss
pair_wise_cls_loss = F.binary_cross_entropy_with_logits(
pred_scores[:, gt_labels].permute(1, 0).sigmoid(), # 取对应gt类别的分数
torch.ones_like(pred_scores[:, gt_labels].permute(1, 0)),
reduction='none'
).sum(dim=-1) # [num_gts, num_anchors]
# 计算pair-wise的回归损失 (IoU Loss)
# pred_bboxes: [num_anchors, 4], gt_bboxes: [num_gts, 4]
# 需要扩展维度进行计算
pred_bboxes_expanded = pred_bboxes[None, :, :].expand(num_gts, -1, -1) # [num_gts, num_anchors, 4]
gt_bboxes_expanded = gt_bboxes[:, None, :].expand(-1, num_anchors, -1) # [num_gts, num_anchors, 4]
pair_wise_iou = bbox_iou(pred_bboxes_expanded, gt_bboxes_expanded, xyxy=True) # [num_gts, num_anchors]
pair_wise_reg_loss = -torch.log(pair_wise_iou + 1e-8) # 使用IoU loss
# 总成本
cost = pair_wise_cls_loss + 3.0 * pair_wise_reg_loss # lambda通常取3.0
# 对不在候选区域的anchor施加巨大惩罚
INF = 1e9
cost = cost.masked_fill(~candidate_mask, INF)
# 4. 计算每个GT的dynamic_k
# 计算每个GT与所有anchor的IoU (用于计算k,这里用到了所有anchor,不仅仅是候选区域)
iou_matrix = bbox_iou(gt_bboxes, anchors) # 这里假设anchors是anchor-based的框,简化计算
# 对每个GT,取IoU最高的topk_candidate个值
topk_ious, _ = torch.topk(iou_matrix, min(topk_candidate, iou_matrix.size(1)), dim=1)
# dynamic_k = 前topk个IoU之和,并取整,同时保证至少为1
dynamic_ks = torch.clamp(topk_ious.sum(dim=1).int(), min=1) # [num_gts]
# 5. 为每个GT分配正样本
matching_matrix = torch.zeros_like(cost) # [num_gts, num_anchors]
for gt_idx in range(num_gts):
# 取出当前GT的成本
gt_cost = cost[gt_idx]
# 选择成本最小的前dynamic_k个
_, pos_idx = torch.topk(gt_cost, dynamic_ks[gt_idx].item(), largest=False)
matching_matrix[gt_idx, pos_idx] = 1
# 6. 处理冲突:一个anchor只分配给成本最小的那个GT
# 如果一个anchor被多个GT选中,我们比较它被不同GT选中的成本,保留成本最小的那个
anchor_matching_gt_cost = cost * matching_matrix # 只保留被选中位置的cost
# 对于每个anchor,找出分配了它的GT中,成本最小的那个
min_cost_per_anchor, matched_gt_idx = anchor_matching_gt_cost.min(dim=0) # [num_anchors]
# 创建最终的分配索引:matched_gt_idx中值非INF的索引表示该anchor被分配
assigned_gt_inds = torch.full((num_anchors,), -1, dtype=torch.long, device=cost.device)
valid_mask = min_cost_per_anchor < INF / 2 # 成本不是无穷大的,说明被分配了
assigned_gt_inds[valid_mask] = matched_gt_idx[valid_mask]
# 获取对应的标签
assigned_labels = torch.full((num_anchors,), -1, dtype=torch.long, device=cost.device)
assigned_labels[valid_mask] = gt_labels[assigned_gt_inds[valid_mask]]
return assigned_gt_inds, assigned_labels
# 辅助函数:计算IoU
def bbox_iou(box1, box2, xyxy=True):
# 简化的IoU计算,实际需考虑各种格式和维度
pass
关键实现细节与踩坑点:
- 成本计算:分类损失通常使用Focal Loss,但在成本计算时,我们只计算对应GT类别的二值损失。回归损失常用IoU Loss或GIoU Loss。平衡系数
λ(代码中的3.0)需要根据任务调整,一般回归损失的权重会高一些。 - 中心先验:
center_radius参数控制筛选的严格程度。在YOLOX中,对于不同尺度的特征图(P3, P4, P5...),这个半径是固定的(如2.5)。在实际应用中,对于小目标居多的数据集,可以适当减小半径,让正样本更集中在目标中心;对于大目标,可以适当增大。 - Dynamic k的计算:代码中
topk_candidate通常取10。这是一个经验值,在5到15之间调整对最终结果影响不大。求和后取整保证了k是整数,并且用torch.clamp(..., min=1)确保每个GT至少有一个正样本,这对于防止某些目标“漏学”很重要。 - 冲突解决:步骤6是保证分配一对一的关键。我们不是简单地将一个anchor分配给第一个选中它的GT,而是比较所有选中它的GT所带来的成本,选择成本最小的那个。这模拟了全局最优的思想。
- 负样本处理:所有未被分配为正样本的anchor,在
assigned_gt_inds中索引为-1,即为负样本。在计算损失时,正样本参与分类和回归损失计算,负样本只参与分类损失(学习预测为背景)。 - 效率优化:上述代码为了清晰展示了每一步,实际实现时需要进行向量化优化,避免for循环。例如,步骤5的循环可以用
torch.topk的批处理版本来替代。
将SimOTA集成到你的检测器训练循环中后,通常能观察到模型收敛更稳定,尤其是在目标尺度变化大、遮挡严重的场景下,AP(特别是AP_s, AP_m, AP_l)可能会有比较均衡的提升。我第一次在自定义的无人机航拍数据集上使用SimOTA替换掉ATSS后,mAP提升了大约1.2%,其中小目标检测的精度提升最为明显。
6. 总结与展望:动态分配的未来
从基于IoU阈值的静态规则,到ATSS的自适应阈值,再到OTA的全局最优传输,最后到SimOTA的高效工程实现,动态标签分配策略的演进清晰地反映了一个趋势:让模型的训练过程越来越“智能”,让样本分配这一关键环节从依赖于人工先验,转向依赖于数据本身和模型当前的状态。
SimOTA的成功,证明了在工程实践中,一个设计精巧的启发式方法,往往比一个复杂但完美的数学优化更具生命力。它用可接受的计算代价,获得了逼近理论最优的性能,这正是工业界最需要的特质。
未来,标签分配的研究可能会朝着几个方向发展:一是完全端到端化,让分配策略本身也成为模型可学习的一部分,比如一些基于匈牙利算法的可微分分配模块;二是多任务协同,像TAL那样,不仅仅考虑分配本身,还考虑分类、回归甚至分割等多个任务之间的对齐与平衡;三是面向特定场景的定制化,例如在极度拥挤的行人检测、长尾分布的目标检测中,设计更精细化的分配策略。
对于大多数开发者和研究者而言,掌握SimOTA的原理并能在自己的项目中熟练应用,已经足以解决绝大部分目标检测中的样本分配难题。它的思想——动态、自适应、以损失为指引——也值得我们迁移到其他密集预测任务中,如实例分割、关键点检测等。下次当你训练检测模型遇到瓶颈时,不妨检查一下你的标签分配策略,试试SimOTA,说不定会有意想不到的收获。
282



被折叠的 条评论
为什么被折叠?



