Paper · 2021-07-07 · cs.CV
Disentangle Your Dense Object Detector
DDOD adds three decoupling steps to one-stage detectors: separate cost-based label assignment for classification and regression, a deformable first conv in each head branch, and per-FPN-level loss weights from positive counts. Author-reported: +2.2 mAP on ATSS (39.4 to 41.6, COCO val, ResNet-50, 1x); 55.0 mAP on test-dev with extra test-time methods.
Object detectionLabel assignmentDeformable convolutionMulti-scale feature fusionobject-detectionlabel-assignmentdeformable-convolutionfeature-pyramidcoco
arXivPDFCodeMarkdown19 min read
AI-compiled by claude-haiku-5-5 on 2026-10-19, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.
In plain words
A dense object detector looks at every spot of an image and asks two things at each spot: is there an object here, and where exactly is its box. The authors argue that the usual training recipe ties these two jobs together in three ways nobody had tested. Both jobs learn only from the same spots, both read the same features, and every level of the feature pyramid counts the same in the loss, even though the deep, coarse levels that handle large objects get far fewer training spots.
The fix separates the jobs. Each one picks its own training spots, each one learns its own view of the feature map, and the pyramid levels are weighted by how many training spots they receive. These changes slot into existing detectors. The gains the authors report are about two points of average precision on three popular detectors.
TL;DR
DDOD is a training recipe for one-stage detectors (RetinaNet, FCOS and ATSS). It gives classification and regression separate label assignment, puts a deformable convolution at the first layer of each head branch, and weights feature-pyramid levels by their positive-sample counts. Author-reported gains on COCO val are +2.0 to +2.4 mAP across the three detectors; on ATSS the ablation reaches 41.6 mAP with ResNet-50 at 1x. The repository has code for the ATSS and FCOS variants only, and the WIDER FACE model has no config there.
Problem
The authors call three shared assumptions in dense-detector training "conjunctions", which they say are simplifications from engineering practice rather than things that were tested §1:
- Label assignment. The regression head is trained only on the samples that the classification branch marks positive §3.1.
- Spatial features. Both heads use the same receptive fields. The authors argue that classification should attend to semantic regions and regression to object contours, and show with Grad-CAM that the two heads' sensitive regions differ. This is a visual check, not a measured result §3.2 fig. 2.
- Pyramid supervision. Predictions from all levels are flattened and concatenated into one loss. P3 gets about 256 times as many training samples as P7 §1, so large objects on the deep levels are under-trained §3.3.
Pilot evidence from the authors (author-reported):
- On RetinaNet, the best pair of IoU thresholds is 0.5 for classification and 0.4 for regression, giving 36.5 mAP. The matched 0.5/0.5 pair gives 36.1 tab. 1. This is a fixed-threshold test, not the cost-based assignment the method finally uses §3.1 §4.1.
- On the ATSS baseline (39.4 mAP), linearly increasing weights from 1.0 at P3 to 2.0 at P7 give 39.8. Constant weights give 39.4 and the reversed ramp 3.0-to-1.0 gives 37.6 tab. 2 §3.3.
Method
DDOD is a set of three changes to a standard one-stage FPN detector, plus an IoU-prediction branch that replaces the centerness branch §5.1 fig. 3:
- Classification and regression each get their own label assignment, based on a matching cost §4.1.
- The first convolution of each head branch is a deformable convolution, so each branch learns its own sampling positions §4.2.
- The loss of each pyramid level is scaled by a weight that depends on how many positive samples the level has collected so far §4.3.
Label assignment §4.1. For each prediction and each ground truth, the cost is the predicted probability of the ground-truth class raised to the power (1 - alpha), times the IoU between the predicted box and the ground truth, raised to the power alpha eq. 1. Candidates are restricted to predictions whose centres lie inside the ground-truth box. Per ground truth, the paper takes an adaptive threshold from the candidates' costs, and a candidate is positive if its cost reaches it. Classification uses alpha = 0.8 and regression uses alpha = 0.5 §5.4.1. The paper's candidate rule and the code's differ; see Paper vs code.
Deformable first layer §4.2. The paper writes the feature at each position as a weighted sum over sampling points whose offsets are learned eq. 2. In practice the first convolution of the classification tower and of the regression tower is replaced by a deformable convolution. The other convolutions stay regular, so the head keeps the same number of convolutions. Placing the deformable layer first was the best of four positions in the ablation §5.4.2 tab. 6.
Pyramid weights §4.3. For level i, the weight is 2 minus the level's positive count, normalised to 0 to 1 between the smallest and largest counts over all levels eq. 3 eq. 4. The classification and regression counts are kept separately. The total loss uses these weights on the classification term and the regression term eq. 5. Counts are accumulated over all iterations so far, which the paper describes as a moving average. The weight is 1.0 for the level with the most positives (P3 in practice) and 2.0 for the level with the fewest (P7 in practice) §4.3.
IoU branch §5.1. The centerness branch is replaced by a branch that predicts the IoU of each box. Its training target is the IoU between the predicted box and its matched ground truth. At inference the class score is multiplied by the predicted IoU ddod_head.py.
Pseudocode of the method as the code implements it. Comments name the paper section and, where they differ, the code location:
# Cost-based assignment for one head branch [§4.1] [eq. 1]
# Input: probs (P, C) class probabilities, boxes (P, 4) decoded boxes,
# gt_boxes (N, 4), gt_labels (N,), alpha, k, level_sizes
# Output: assigned (P,) gt index per prediction, -1 for background
def assign_labels(probs, boxes, gt_boxes, gt_labels, alpha, k,
level_sizes):
# Matching cost of every prediction for every ground truth [§4.1]
# Probability of the gt class, raised to (1 - alpha)
class_term = probs[:, gt_labels] ** (1 - alpha) # (P, N)
# IoU between each predicted box and each gt, raised to alpha
iou_term = box_iou(boxes, gt_boxes) ** alpha # (P, N)
cost = class_term * iou_term # (P, N)
# Candidates: per FPN level, the k anchors whose centres are closest
# to each gt centre. The paper ranks by cost instead (Paper vs code).
# [code: mmdet/core/bbox/assigners/atss_cost_assigner.py#L217-L230]
candidates = []
for level_slice in split_by_level(level_sizes):
distances = centre_distance(level_slice, gt_boxes) # (A_l, N)
candidates.append(smallest_k(distances, k)) # (k, N)
candidates = concatenate(candidates) # (k*L, N)
# Adaptive threshold per gt: mean + std of its candidates' costs
# [§4.1] [code: mmdet/core/bbox/assigners/atss_cost_assigner.py#L234-L239]
cand_cost = gather(cost, candidates) # (k*L, N)
threshold = mean(cand_cost, axis=0) + std(cand_cost, axis=0)
# Keep candidates at or above the threshold whose centre lies inside
# the gt [code: mmdet/core/bbox/assigners/atss_cost_assigner.py#L239-L257]
is_positive = cand_cost >= threshold
is_positive = is_positive & centre_inside(candidates, gt_boxes)
# A prediction claimed by several gts keeps the one with the highest
# cost. The code comment says "highest IoU", but the value compared
# is the cost.
# [code: mmdet/core/bbox/assigners/atss_cost_assigner.py#L259-L269]
assigned = resolve_by_max_cost(candidates, is_positive, cost)
return assigned# Per-level weights from cumulative positive counts [§4.3] [eq. 3]
# Fewest positives -> 2.0, most positives -> 1.0, linear in between
# [code: mmdet/models/dense_heads/ddod_head.py#L315-L321]
def level_weights(counts):
lowest = min(counts)
highest = max(counts)
# 1e-10 avoids division by zero when all levels have equal counts
interval = 1.0 / (highest - lowest + 1e-10)
return [2.0 - (count - lowest) * interval for count in counts]# Positives per level, summed over every iteration so far [§4.3]
# Five levels, P3 to P7 [code: mmdet/models/dense_heads/ddod_head.py#L309-L313]
def add_positives_per_level(assigned_levels, counts):
for level in range(5):
counts[level] += number_of_positives(assigned_levels[level])
return counts# Training loss: two assignment passes, then per-level weights [§4.1] [§4.3]
# [code: mmdet/models/dense_heads/ddod_head.py#L285-L400]
# Input: per level: cls_logits, box_deltas, iou_logits; gt_boxes, gt_labels
# counts_cls, counts_reg: per-level positive counts (kept across calls)
# Output: loss_cls, loss_box, loss_iou
def training_loss(levels, gt_boxes, gt_labels, counts_cls, counts_reg):
# Pass 1: classification targets from the classification assignment
# alpha = 0.8, top k = 9 [§5.4.1] [code: coco_cfg/ddod_r50_1x.py#L48]
cls_assigned = assign_per_level(levels, gt_boxes, gt_labels, 0.8, 9)
counts_cls = add_positives_per_level(cls_assigned, counts_cls)
weights_cls = level_weights(counts_cls) # [eq. 3]
loss_cls = 0
for level in range(5):
loss_cls += weights_cls[level] * focal_loss(levels[level],
cls_assigned[level])
# Pass 2: box and IoU losses from the regression assignment
# alpha = 0.5, top k = 9 [§5.4.1] [code: coco_cfg/ddod_r50_1x.py#L49]
reg_assigned = assign_per_level(levels, gt_boxes, gt_labels, 0.5, 9)
counts_reg = add_positives_per_level(reg_assigned, counts_reg)
weights_reg = level_weights(counts_reg) # [eq. 4]
loss_box = 0
loss_iou = 0
for level in range(5):
positives = select_positives(reg_assigned[level])
# IoU target: overlap of each decoded positive box with its gt,
# with no gradient through the target [code: ddod_head.py#L226-L227]
iou_target = iou(decode(positives.boxes).detach(), positives.gts)
# GIoU box loss with weight 2.0 [code: coco_cfg/ddod_r50_1x.py#L43]
loss_box += weights_reg[level] * 2.0 * giou_loss(positives)
# Cross-entropy on positives only, weight 1.0
# [code: ddod_head.py#L195] [code: coco_cfg/ddod_r50_1x.py#L44-L45]
loss_iou += weights_reg[level] * bce(levels[level].iou_logits,
iou_target, positives)
return loss_cls, loss_box, loss_iouHyperparameters
Values from the paper are cited to the paper. Values marked "code" appear only in the official repository at the commit in the frontmatter. "not stated" means neither the paper nor the code gives a value.
| Name | Value | Where | Notes |
|---|---|---|---|
| Backbone | ResNet-50 (1x schedule) | §5.1 ddod_r50_1x.py | Frozen first stage and BatchNorm in eval mode in the code; not stated in the paper |
| Feature pyramid | FPN, 256 channels, levels P3 to P7 | ddod_r50_1x.py ddod_head.py | Five levels hard-coded in the counters |
| Anchors | 1 per location; ratio 1.0; octave base scale 8; strides 8 to 128 | ddod_r50_1x.py | Paper says "predefined anchors" §4.1 without values |
| Head depth and width | 4 stacked convs per branch, 256 channels | ddod_r50_1x.py | |
| Head normalisation | GroupNorm, 32 groups | ddod_head.py | Default in the head; not overridden in the config; not stated in the paper |
| Per-level box scale | Learnable scalar per level, initialised to 1.0 | ddod_head.py ddod_head.py | Code only |
| Deformable layer | First conv of each branch (cls and reg); deform_groups 1 | §4.2 ddod_head.py ddod_head.py | Group count is code only |
| alpha, classification assignment | 0.8 | §5.4.1 atss_cost_assigner.py ddod_r50_1x.py | Used by the default of the assigner |
| alpha, regression assignment | 0.5 | §5.4.1 ddod_r50_1x.py | |
| Candidates per level, k | 9 | ddod_r50_1x.py | Paper refers to "top K" without a value §4.1 |
| Centre margin for "inside gt" | 0.01 pixels | atss_cost_assigner.py | Strict check in code only |
| Level weight range | 1.0 to 2.0 | §4.3 eq. 3 | Empirical: P3 gets 1.0, P7 gets 2.0 §4.3 |
| Count accumulation | Cumulative from the first iteration, per process | §4.3 ddod_head.py | Paper calls it a moving average |
| Focal loss | gamma 2.0, alpha 0.25, weight 1.0 | ddod_r50_1x.py | Paper gives no value in the method or in §5.1 |
| Box loss | GIoU, weight 2.0 | ddod_r50_1x.py | Paper gives no value |
| IoU-branch loss | Cross-entropy, weight 1.0, positives only | ddod_r50_1x.py ddod_head.py | Replaces centerness §5.1 |
| Box coder | Delta XYWH; target stds 0.1, 0.1, 0.2, 0.2 | ddod_r50_1x.py | Paper gives no value |
| Training images per GPU | 4 | ddod_r50_1x.py | GPU count not stated in the paper or the config |
| Optimiser | SGD, lr 0.02, momentum 0.9, weight decay 1e-4 | ddod_r50_1x.py | Paper says MMDetection defaults §5.1 |
| Warm-up | Linear, 500 iterations, starting ratio 0.001 | ddod_r50_1x.py | |
| Schedule | 12 epochs; lr steps at epochs 8 and 11 | ddod_r50_1x.py | Step factor not set in the config; standard "1x" §5.1 |
| Input scale | Resize to (1333, 800), keep ratio; horizontal flip 0.5 | ddod_r50_1x.py | Test at short side 800 §5.5 |
| Mixed precision | fp16 with loss scale 512 | ddod_r50_1x.py | Paper does not mention it |
| Test: score threshold, pre-NMS top-k, NMS IoU, max detections | 0.05; 1000; 0.6; 100 | ddod_r50_1x.py | |
| COCO data | Train on trainval35k (115K images); minval (5K) for ablations; test-dev (20K) via the server | §5.1 | |
| WIDER FACE training | Batch 24 on six Titan V100 GPUs; lr 7.5e-3 to 7.5e-5 with cosine decay, repeated every 30 epochs for 600 epochs total | §5.1 | No DDOD-Face config in the repository |
| WIDER FACE extras | Deformable convolution block, SSH module, DIoU loss | §5.1 §5.6 | Not in the repository |
Results
Author-reported throughout. Setup: COCO with ResNet-50 and the 1x schedule §5.1; ablations on the ATSS baseline with minval/val-2017 §5.3 tab. 4; FPS on a single Titan V100 with batch size 1 where possible tab. 8; WIDER FACE validation subset tab. 9.
Headline numbers:
- Each detector with DDOD, same backbone, COCO val: RetinaNet 36.5 to 38.5, FCOS 39.2 to 41.6, ATSS 39.4 to 41.6 tab. 3.
- Component ablation on ATSS: 39.4 baseline, 41.6 with all three tab. 4.
- COCO test-dev, single model and single scale: DDOD with ResNet-50 45.0; with Res2Net-101-DCN, 51.2; DDOD-X with Res2Net-101-DCN, 52.5. The 55.0 row adds multi-scale testing and other techniques, which the paper describes only loosely §5.5 tab. 8.
- WIDER FACE validation (easy, medium, hard): ATSS 0.964 / 0.958 / 0.930; DDOD-Face 0.970 / 0.964 / 0.935 tab. 9.
Table 3: three detectors with and without DDOD (COCO val-2017)
Source: tab. 3. FCOS uses the improved implementation in ATSS's codebase, per the table footnote.
| Model | DDOD | AP | AP50 | AP75 | APS | APM | APL |
|---|---|---|---|---|---|---|---|
| RetinaNet | 36.5 | 55.4 | 39.1 | 20.4 | 40.3 | 48.1 | |
| RetinaNet | ✓ | 38.5 | 58.1 | 41.2 | 20.7 | 41.4 | 51.6 |
| FCOS* | 39.2 | 57.3 | 42.4 | 22.7 | 43.1 | 51.5 | |
| FCOS* | ✓ | 41.6 | 59.7 | 45.3 | 24.3 | 45.0 | 55.1 |
| ATSS | 39.4 | 56.6 | 42.6 | 23.9 | 42.5 | 49.6 | |
| ATSS | ✓ | 41.6 | 59.9 | 45.2 | 23.9 | 44.9 | 54.4 |
Table 4: component ablation on ATSS (COCO val-2017)
Source: tab. 4. Columns: the three components, then AP and its parts.
| Assign | Spatial | Supervision | AP | AP50 | AP75 | APS | APM | APL |
|---|---|---|---|---|---|---|---|---|
| 39.4 | 56.6 | 42.6 | 23.9 | 42.5 | 49.6 | |||
| ✓ | 40.4 | 58.9 | 43.8 | 24.1 | 43.9 | 51.4 | ||
| ✓ | 40.6 | 58.6 | 44.1 | 24.0 | 44.1 | 52.5 | ||
| ✓ | 40.0 | 57.9 | 43.1 | 23.0 | 43.6 | 52.6 | ||
| ✓ | ✓ | ✓ | 41.6 | 59.9 | 45.2 | 23.9 | 44.9 | 54.4 |
Table 5: classification and regression alpha (ATSS, COCO val)
Source: tab. 5. Rows: alpha for classification. Columns: alpha for regression.
| alpha_cls \ alpha_reg | 0.5 | 0.6 | 0.7 | 0.8 |
|---|---|---|---|---|
| 0.5 | 39.4 | 39.3 | 39.0 | 39.0 |
| 0.6 | 39.6 | 39.4 | 39.5 | 39.5 |
| 0.7 | 39.9 | 40.1 | 39.7 | 39.6 |
| 0.8 | 40.4 | 40.0 | 40.0 | 39.9 |
Table 6: position of the deformable layer in the head
Source: tab. 6. Position 1 is the first conv of each branch.
| Position | AP | AP50 | AP75 | APS | APM | APL |
|---|---|---|---|---|---|---|
| 1 | 40.6 | 58.4 | 44.3 | 23.6 | 44.3 | 52.4 |
| 2 | 40.3 | 58.5 | 44.1 | 23.9 | 44.0 | 52.4 |
| 3 | 40.1 | 57.9 | 43.4 | 23.0 | 44.6 | 52.4 |
| 4 | 40.1 | 57.9 | 43.1 | 23.0 | 44.6 | 52.6 |
Table 7: pyramid re-weighting strategies
Source: tab. 7. Sum-Reweight uses weights proportional to the positive count per level. Linear-Reweight is the linear ramp from 1.0 to 2.0 used in QueryDet. Linear-Interpolate is the paper's method. An asterisk marks the moving-average version, which counts all valid samples up to the current iteration.
| Method | AP | AP50 | AP75 | APS | APM | APL |
|---|---|---|---|---|---|---|
| Sum-Reweight | 39.7 | 57.5 | 43.2 | 22.6 | 43.3 | 52.1 |
| Sum-Reweight* | 39.8 | 57.8 | 43.1 | 23.0 | 43.3 | 51.0 |
| Linear-Reweight | 39.8 | 57.8 | 42.9 | 22.9 | 43.5 | 51.8 |
| Linear-Reweight* | 39.8 | 57.7 | 42.9 | 23.0 | 43.5 | 52.0 |
| Linear-Interpolate | 39.8 | 57.9 | 43.1 | 23.1 | 43.2 | 51.1 |
| Linear-Interpolate* | 40.0 | 57.9 | 43.4 | 22.9 | 43.6 | 52.6 |
Table 8: COCO test-dev comparison
Source: tab. 8. Single model and single scale, except the last row. MS_train and MS_test mark multi-scale training and testing. FPS is on a single Titan V100 with batch size 1 where possible; "n/a" means no trained model or timing was available. Backbone abbreviations: R = ResNet, X = ResNeXt, HG = Hourglass, DCN = deformable convolution, R2 = Res2Net. FCOS* uses the improved implementation in ATSS's codebase. The table mixes backbones and schedules, so only rows with the same backbone are directly comparable.
| Method | Backbone | Epoch | MS_train | FPS | AP | AP50 | AP75 | APS | APM | APL | Reference |
|---|---|---|---|---|---|---|---|---|---|---|---|
| multi-stage | |||||||||||
| Faster R-CNN w/ FPN | R-101 | 24 | 14.2 | 36.2 | 59.1 | 39.0 | 18.2 | 39.0 | 48.2 | CVPR17 | |
| Cascade R-CNN | R-101 | 18 | 11.9 | 42.8 | 62.1 | 46.3 | 23.7 | 45.5 | 55.2 | CVPR18 | |
| Grid R-CNN | R-101 | 20 | 11.4 | 41.5 | 60.9 | 44.5 | 23.3 | 44.9 | 53.1 | CVPR19 | |
| Libra R-CNN | X-101-64x4d | 12 | 8.5 | 43.0 | 64.0 | 47.0 | 25.3 | 45.6 | 54.6 | CVPR19 | |
| RepPoints | R-101 | 24 | 13.3 | 41.0 | 62.9 | 44.3 | 23.6 | 44.1 | 51.7 | ICCV19 | |
| RepPoints | R-101-DCN | 24 | ✓ | 11.8 | 45.0 | 66.1 | 49.0 | 26.6 | 48.6 | 57.5 | ICCV19 |
| RepPointsV2 | R-101 | 24 | ✓ | 11.1 | 46.0 | 65.3 | 49.5 | 27.4 | 48.9 | 57.3 | NeurIPS20 |
| RepPointsV2 | R-101-DCN | 24 | ✓ | 10.0 | 48.1 | 67.5 | 51.8 | 28.7 | 50.9 | 60.8 | NeurIPS20 |
| TridentNet | R-101 | 24 | ✓ | 2.7 | 42.7 | 63.6 | 46.5 | 23.9 | 46.6 | 56.6 | ICCV19 |
| TridentNet | R-101-DCN | 36 | ✓ | 1.3 | 46.8 | 67.6 | 51.5 | 28.0 | 51.2 | 60.5 | ICCV19 |
| TSD | R-101 | 20 | 1.1 | 43.2 | 64.0 | 46.9 | 24.0 | 46.3 | 55.8 | CVPR20 | |
| BorderDet | R-101 | 24 | ✓ | 13.2 | 45.4 | 64.1 | 48.8 | 26.7 | 48.3 | 56.5 | ECCV20 |
| BorderDet | X-101-64x4d | 24 | ✓ | 8.1 | 47.2 | 66.1 | 51.0 | 28.1 | 50.2 | 59.9 | ECCV20 |
| BorderDet | X-101-64x4d-DCN | 24 | ✓ | 6.4 | 48.0 | 67.1 | 52.1 | 29.4 | 50.7 | 60.5 | ECCV20 |
| one-stage | |||||||||||
| TPNet320 | R-101 | 24 | 25.7 | 34.2 | 53.1 | 36.4 | 13.6 | 36.8 | 50.5 | ACM MM20 | |
| TPNet512 | R-101 | 24 | 13.9 | 39.6 | 58.5 | 42.8 | 20.5 | 45.3 | 53.3 | ACM MM20 | |
| CornerNet | HG-104 | 200 | ✓ | 3.1 | 40.6 | 56.4 | 43.2 | 19.1 | 42.8 | 54.3 | ECCV18 |
| CenterNet | HG-104 | 190 | ✓ | 3.3 | 44.9 | 62.4 | 48.1 | 25.6 | 47.4 | 57.4 | ICCV19 |
| CentripetalNet | HG-104 | 210 | ✓ | n/a | 45.8 | 63.0 | 49.3 | 25.0 | 48.2 | 58.7 | CVPR20 |
| RetinaNet | R-101 | 18 | 13.6 | 39.1 | 59.1 | 42.3 | 21.8 | 42.7 | 50.2 | ICCV17 | |
| FreeAnchor | R-101 | 24 | ✓ | 12.8 | 43.1 | 62.2 | 46.4 | 24.5 | 46.1 | 54.8 | NeurIPS19 |
| FreeAnchor | X-101-32x8d | 24 | ✓ | 8.2 | 44.9 | 64.3 | 48.5 | 26.8 | 48.3 | 55.9 | NeurIPS19 |
| FSAF | R-101 | 18 | ✓ | 15.1 | 40.9 | 61.5 | 44.0 | 24.0 | 44.2 | 51.3 | CVPR19 |
| FSAF | X-101-64x4d | 18 | ✓ | 9.1 | 42.9 | 63.8 | 46.3 | 26.6 | 46.2 | 52.7 | CVPR19 |
| FCOS | R-101 | 24 | ✓ | 14.7 | 41.5 | 60.7 | 45.0 | 24.4 | 44.8 | 51.6 | ICCV19 |
| FCOS | X-101-64x4d | 24 | ✓ | 8.9 | 44.7 | 64.1 | 48.4 | 27.6 | 47.5 | 55.6 | ICCV19 |
| SAPD | R-101 | 24 | ✓ | 13.2 | 43.5 | 63.6 | 46.5 | 24.9 | 46.8 | 54.6 | CVPR20 |
| SAPD | R-101-DCN | 24 | ✓ | 11.1 | 46.0 | 65.9 | 49.6 | 26.3 | 49.2 | 59.6 | CVPR20 |
| SAPD | X-101-32x4d-DCN | 24 | ✓ | 8.8 | 46.6 | 66.6 | 50.0 | 27.3 | 49.7 | 60.7 | CVPR20 |
| ATSS | R-101 | 24 | ✓ | 14.6 | 43.6 | 62.1 | 47.4 | 26.1 | 47.0 | 53.6 | CVPR20 |
| ATSS | R-101-DCN | 24 | ✓ | 12.7 | 46.3 | 64.7 | 50.4 | 27.7 | 49.8 | 58.4 | CVPR20 |
| ATSS | X-101-32x8d-DCN | 24 | ✓ | 6.9 | 47.7 | 66.6 | 52.1 | 29.3 | 50.8 | 59.7 | CVPR20 |
| PAA | R-101 | 24 | ✓ | 14.6 | 44.8 | 63.3 | 48.7 | 26.5 | 48.8 | 56.3 | ECCV20 |
| PAA | R-101-DCN | 24 | ✓ | 12.7 | 47.4 | 65.7 | 51.6 | 27.9 | 51.3 | 60.6 | ECCV20 |
| PAA | X-101-64x4d-DCN | 24 | ✓ | 6.9 | 49.0 | 67.8 | 53.3 | 30.2 | 52.8 | 62.2 | ECCV20 |
| GFL | R-50 | 24 | ✓ | 19.4 | 43.1 | 62.0 | 46.8 | 26.0 | 46.7 | 52.3 | NeurIPS20 |
| GFL | R-101 | 24 | ✓ | 14.6 | 45.0 | 63.7 | 48.9 | 27.2 | 48.8 | 54.5 | NeurIPS20 |
| GFL | R-101-DCN | 24 | ✓ | 12.7 | 47.3 | 66.3 | 51.4 | 28.0 | 51.1 | 59.2 | NeurIPS20 |
| GFL | X-101-32x4d-DCN | 24 | ✓ | 10.7 | 48.2 | 67.4 | 52.6 | 29.2 | 51.7 | 60.2 | NeurIPS20 |
| DDOD (ours) | R-50 | 24 | ✓ | 18.6 | 45.0 | 63.5 | 49.3 | 27.2 | 47.9 | 55.9 | -- |
| DDOD (ours) | R-101 | 24 | ✓ | 14.0 | 46.7 | 65.3 | 51.1 | 28.2 | 49.9 | 57.9 | -- |
| DDOD (ours) | R-101-DCN | 24 | ✓ | 12.0 | 49.0 | 67.7 | 53.6 | 29.6 | 52.2 | 62.3 | -- |
| DDOD (ours) | X-101-32x4d-DCN | 24 | ✓ | 10.3 | 50.1 | 69.0 | 54.8 | 30.8 | 53.5 | 63.2 | -- |
| DDOD (ours) | R2-101-DCN | 24 | ✓ | 10.4 | 51.2 | 69.9 | 55.9 | 32.2 | 54.4 | 64.9 | -- |
| DDOD-X (ours) | R2-101-DCN | 24 | ✓ | 8.4 | 52.5 | 71.0 | 57.2 | 33.9 | 56.8 | 65.9 | -- |
| DDOD-X (ours) + MS_test | R2-101-DCN | 24 | ✓ | - | 55.0 | 72.6 | 60.0 | 37.0 | 58.0 | 66.7 | -- |
Table 9: WIDER FACE validation subset
Source: tab. 9. The source file has a commented-out MogFace row, which is omitted here; the source footnote says it uses a model ensemble.
| Method | Backbone | Easy | Medium | Hard |
|---|---|---|---|---|
| PyramidBox | VGG-16 | 0.961 | 0.950 | 0.889 |
| PyramidBox++ | VGG-16 | 0.967 | 0.959 | 0.912 |
| AInnoFace | ResNet-152 | 0.970 | 0.961 | 0.918 |
| RetinaFace | ResNet-152 | 0.969 | 0.961 | 0.918 |
| RefineFace | ResNet-152 | 0.972 | 0.962 | 0.920 |
| DSFD | ResNet-152 | 0.966 | 0.957 | 0.904 |
| ASFD-D6 | ResNet-152 | 0.972 | 0.965 | 0.925 |
| HAMBox | ResNet-50 | 0.970 | 0.964 | 0.933 |
| TinaFace | ResNet-50 | 0.970 | 0.963 | 0.934 |
| ATSS | ResNet-50 | 0.964 | 0.958 | 0.930 |
| DDOD-Face (ours) | ResNet-50 | 0.970 | 0.964 | 0.935 |
Paper vs code
Repository: https://github.com/zehuichen123/DDOD (linked from the abstract),
commit ca2852432f4d2ab17cd3d7e90ab872dab31d4a1a. It is a fork of MMDetection
under Apache-2.0 LICENSE.
- Candidate selection. The paper says "top K predictions with the largest cost value are selected from each FPN level" §4.1. The code ranks anchors per level by distance from the anchor centre to the gt centre, keeping the nearest k. Only those candidates are then tested against the cost threshold atss_cost_assigner.py. The paper gives no value of K; the config uses k = 9 for both assigners [code: coco_cfg/ddod_r50_1x.py#L48-L49]. Implement the code's rule to match the reported numbers, or the paper's rule to match the text; the two are not equivalent.
- Threshold statistics. The paper says the threshold uses "batch statistics like ATSS" §4.1. The code computes mean plus standard deviation per ground truth, over that gt's own candidates [code: mmdet/core/bbox/assigners/atss_cost_assigner.py#L234-L239].
- Conflicts between ground truths. The code comment says a prediction claimed by several gts keeps the one with the highest IoU, but the value compared is the matching cost [code: mmdet/core/bbox/assigners/atss_cost_assigner.py#L259-L269]. The paper does not describe this case.
- Level weights. The paper describes the counts as accumulated up to the current iteration, in a moving-average manner §4.3. The code accumulates counts from the first iteration and never decays them [code: mmdet/models/dense_heads/ddod_head.py#L309-L313]. The code also applies the regression weight to the IoU loss as well as to the box loss [code: mmdet/models/dense_heads/ddod_head.py#L245] [code: mmdet/models/dense_heads/ddod_head.py#L377-L389]. The paper's loss equation shows only the classification and box terms.
- IoU branch. The paper says the IoU branch replaces centerness §5.1. The paper does not give the training target or the inference score. The code trains the IoU branch only on positives, with a target equal to the IoU of each decoded positive box ddod_head.py ddod_head.py, and scores each detection as class probability times the sigmoid of the IoU prediction [code: mmdet/models/dense_heads/ddod_head.py#L540-L582] [code: mmdet/core/post_processing/bbox_nms.py#L57-L62].
- Deformable layer. The paper says the first convolution of both branches is replaced §4.2. The code does this for the first tower layer only, with one deformable group ddod_head.py. This matches. The offset-prediction layer lives in the mmcv DCN module, which is not part of this repository.
- Extras not in the paper. The config enables fp16 with a loss scale of 512 ddod_r50_1x.py and a learnable per-level scale on box outputs ddod_head.py. Neither is mentioned in the paper.
- Missing code. The repository has configs only for ATSS-based DDOD (R-50) and FCOS-based DDOD (R-50) ddod_r50_1x.py [code: coco_cfg/ddod_r50_1x_fcos.py]. There is no RetinaNet variant, no ResNet-101, ResNeXt or Res2Net config for the test-dev rows, and no WIDER FACE DDOD config. Results for those settings cannot be checked against code here.
Pitfalls
- Assignment runs twice per iteration. Targets are computed once for classification and once for regression, each with its own assignment and sampling pass ddod_head.py. Budget for roughly double the assignment work. The abstract says the extra overhead is negligible, but the paper gives no matched speed comparison: the FPS column of tab. 8 compares different backbones.
- The name alpha is overloaded. Section 4.1 says samples with higher foreground probability should be prioritised for classification §4.1. The chosen classification alpha of 0.8 does the opposite in the cost: it weights the IoU term at 0.8 and the probability term at 0.2. The paper's own ablation says IoU is decisive for classification targets §5.4.1. Implement the formula with alpha = 0.8, not the stated intuition.
- Counts are cumulative and not saved. The counters grow for the whole run, so late in training the weights hardly change. They are plain Python lists on the head ddod_head.py, so they are not part of the model's state dict. Resuming from a checkpoint restarts them from zero, which the code does not record.
- Counts are per process. The increments use each process's own positives, with no synchronisation across GPUs [code: mmdet/models/dense_heads/ddod_head.py#L309-L313]. Under distributed training, each GPU may end up with different level weights. The paper does not discuss this.
- Five levels are hard-coded. The counters are created for exactly five levels ddod_head.py. A different number of pyramid levels needs changes there.
- Positives only for the IoU branch. Negatives get zero weight in the IoU loss ddod_head.py. If a batch has no positives, the IoU and box losses are zero [code: mmdet/models/dense_heads/ddod_head.py#L239-L241].
- Pilot numbers use other thresholds. The IoU-threshold grid in tab. 1 is a RetinaNet experiment with fixed thresholds. Do not read it as the final cost-based assignment.
- WIDER FACE figures are not checkable. There is no DDOD-Face config here. The text's "overall AP of 93.0" matches the hard-subset value of the ATSS row in tab. 9, not an average over the three subsets. The reported DDOD gain is about 0.5 to 0.6 points on each subset §5.6.
- The 55.0 test-dev row is not fully specified. The paper lists multi-scale testing, Soft-NMS and stronger augmentation as "techniques like" these §5.5. The exact recipe is not given, and no config for that row is in the repository.
- Equation numbers are inferred. Equations 3 to 5 were numbered from the order of the LaTeX source. The compiled PDF was not checked, so cite these numbers with care.
- Training recipe gaps. The paper gives no learning rate, batch size or GPU count for COCO, and defers to MMDetection defaults §5.1. The config supplies lr 0.02 with 4 images per GPU ddod_r50_1x.py but no GPU count.
Lineage
- Builds on: ATSS (Zhang et al., 2020), whose adaptive threshold and base detector code this head extends §4.1 [code: mmdet/models/dense_heads/ddod_head.py#L15-L24]. The cost formula is taken from DeFCN (Wang et al., 2020; arXiv:2012.03544) §4.1. Deformable convolution is from Dai et al. (2017) §4.2. The observation that classification and localisation want different features comes from TSD (Song et al., 2020) §3.2.
- The linear per-level ramp is the Linear-Reweight of QueryDet (Yang et al., 2021; arXiv:2103.09136), used as a baseline §5.4.3 tab. 7.
- The face model adds the SSH module (Najibi et al., 2017) and DIoU loss (Zheng et al., 2020) §5.6.
- Compared against: RetinaNet, FCOS and ATSS in the same detectors tab. 3; the COCO test-dev field, including GFL, PAA, RepPoints, TridentNet, CenterNet, SAPD and FreeAnchor tab. 8; WIDER FACE methods including PyramidBox, RetinaFace, TinaFace and HAMBox tab. 9. None of these works has a card on this wiki, so only their names are used here.
Source and license
Adapted from “Disentangle Your Dense Object Detector” by Zehui Chen, Chenhongyi Yang, Qiaofei Li, Feng Zhao, Zheng-Jun Zha, Feng Wu (arXiv:2107.02963), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/zehuichen123/DDOD at commit ca2852432f4d (Apache-2.0). Any quoted code is shown under that license.