Paper · 2021-04-19 · cs.CV
TransCrowd: weakly-supervised crowd counting with transformers
TransCrowd casts crowd counting as sequence-to-count: a pure transformer encoder reads 16x16 patches of 384x384 tiles, and a small head regresses one count per tile, trained with L1 loss on count labels only. Author-reported: on ShanghaiTech Part A the GAP head reaches MAE 66.1, against 80.1 for the best count-only baseline in its table.
Vision Transformer (ViT) backbonesSelf-attentionWeakly-supervised learningDensity map regressioncrowd-countingweak-supervisiontransformervision
arXivPDFCodeMarkdown21 min read
AI-compiled by claude-haiku-5-5 on 2026-11-06, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.
In plain words
Counting the people in a photo of a crowd is usually learned from pictures in which someone has put a dot on every head. Those dots are slow to add. The final answer, though, is just one number per photo. TransCrowd asks whether a model can learn to count from that number alone.
Older counters of this kind look at the picture through small windows, so each part of the model sees only a little of the scene at a time. TransCrowd cuts the picture into small squares, turns each square into a token, and lets a transformer let every token look at every other token. A final part of the network reads one number from these tokens, which is the count.
Because the model sees the whole scene at once, it can use context from across the crowd. The authors train it on counts alone and compare it with methods that used dot labels, and they note that it can match some of them.
TL;DR
TransCrowd is a crowd counter built from a pure transformer encoder. It splits each 384 x 384 tile into 16 x 16 patches, encodes them, and regresses one count per tile §3.1-§3.4. It is trained only on count labels with an L1 loss §4.1 eq. 7. The authors report that it beats the count-only methods they compare with, and that it comes close to some fully supervised ones §5 tab. 1. Every number is author-reported. The official code's regression heads differ from the paper's description, so check the Paper vs code section before implementing.
Problem
Density-map counters need a point annotation on every person to build their training targets §1. The authors argue that these point labels are expensive and are not used when the count is evaluated §1. Count-only methods so far are CNNs that regress a total count from the whole image. Their receptive field is limited, and the authors name this as the main weakness of that family §1 §2.2. The authors say these count-only methods still do not reach the results of fully supervised ones §2.2.
The authors' transformer needs pretraining. Its encoder is initialised from ImageNet weights §4.1. The paper states that CNNs outperform transformers on small datasets, and that transformers overtake them on larger ones §5. Without pretraining, the CNN baseline is better on ShanghaiTech Part A §6.5 tab. 9.
Method
Overview §3. An image is cut into fixed-size patches, and each patch is linearly projected and given a learned position embedding eq. 1 eq. 2. The sequence goes through a standard transformer encoder with no decoder §3.3. A regression head then maps the output to one number, the count §3.4. Training uses the count only, with an L1 loss over the batch eq. 7.
Two variants of the head are described §3.4.
- TransCrowd-Token: a learned regression token is prepended to the patch sequence, and its output feeds the head. This follows the class token of BERT and ViT §3.4.
- TransCrowd-GAP: the head takes the average over all output tokens, with no regression token §3.4 fig. 2(c).
Both heads use two linear layers. The paper does not state the hidden width or the activation §3.4. The official code does not match this description; see Paper vs code.
Encoder §3.3. The encoder has L layers. Each layer applies layer normalisation and multi-head self-attention, with a residual connection eq. 3. It then applies layer normalisation and an MLP with a residual connection eq. 4. The MLP has a GELU activation and expands the width from D to 4D, then back to D §3.3. The attention module has m independent heads whose outputs are concatenated and projected by a D x D matrix §3.3. The paper's attention equations are eq. 5 and eq. 6.
Patch and position embeddings §3.1 §3.2. The image is reshaped into N patches of K x K pixels, with N = (H/K) x (W/K) §3.1. Each patch is mapped to a D-dimensional vector by a learned matrix eq. 1. A learned position embedding is added eq. 2.
Equations (as written in the paper; the MSE in eq. 8 is a root mean square):
e = [x^1_p E;\, x^2_p E;\, \dots;\, x^N_p E], \qquad E \in \mathbb{R}^{(K^2 \cdot 3) \times D} eq. 1
Z_0 = [e_1 + p_1;\, e_2 + p_2;\, \dots;\, e_N + p_N] eq. 2
Z'_l = \mathrm{MSA}(\mathrm{LN}(Z_{l-1})) + Z_{l-1} eq. 3
Z_l = \mathrm{MLP}(\mathrm{LN}(Z'_l)) + Z'_l eq. 4
Q = Z_{l-1} W_Q, \quad K = Z_{l-1} W_K, \quad V = Z_{l-1} W_V eq. 5
\mathrm{SA}(Z_l) = \mathrm{softmax}\!\left(\frac{Q K^{\top}}{\sqrt{D}}\right) V eq. 6
L_1 = \frac{1}{M} \sum_{i=1}^{M} \left| P_i - G_i \right| eq. 7
\mathrm{MAE} = \frac{1}{N} \sum_{i=1}^{N} \left| P_i - G_i \right|, \qquad \mathrm{MSE} = \sqrt{\frac{1}{N} \sum_{i=1}^{N} \left| P_i - G_i \right|^2} eq. 8
In eq. 7, M is the batch size §3.4. In eq. 8, N is the number of test images §4.3.
The forward pass, paper version. Pseudocode is in our own words; comments cite the source.
# TransCrowd forward pass [§3.1] [§3.2] [§3.3] [§3.4]
# Input: tiles (B, 3, 384, 384)
# Output: one predicted count per tile (B, 1)
def forward(tiles):
# Cut each tile into non-overlapping 16 x 16 patches [§3.1] [§4.1]
patches = flatten_patches(tiles, size=16) # (B, 576, 768)
# Learned linear map from patch pixels to D channels [eq. 1]
embedded = patches @ projection # (B, 576, D)
# Add a learned position embedding per patch [eq. 2]
tokens = embedded + position_embedding # (B, 576, D)
# Token variant only: prepend a learned regression token [§3.4]
if variant == "token":
tokens = concat(regression_token, tokens) # (B, 577, D)
# L pre-norm encoder layers with residual connections [§4.1] [§3.3]
for layer in encoder_layers:
# Self-attention sub-block [eq. 3] [eq. 5] [eq. 6]
tokens = tokens + attention(layer_norm(tokens))
# MLP sub-block, width D -> 4D -> D, GELU [eq. 4] [§3.3]
tokens = tokens + mlp(layer_norm(tokens))
# Token variant: read the regression token [§3.4]
if variant == "token":
pooled = tokens[:, 0] # (B, D)
# GAP variant: average over all output tokens [§3.4] [fig. 2(c)]
else:
pooled = mean(tokens, axis=1) # (B, D)
# Two linear layers; hidden width not stated [§3.4]
hidden = linear_1(pooled) # (B, H), H not stated
return linear_2(hidden) # (B, 1)The heads as the official code builds them. This differs from the block above; see Paper vs code.
# Heads in the official code [code: Networks/models.py#L21-L26]
# [code: Networks/models.py#L44-L50] [code: Networks/models.py#L63-L69]
# [code: Networks/models.py#L87] [code: Networks/models.py#L91-L98]
def token_head_as_coded(output_tokens):
# Backbone classifier, D -> 1000; loaded from the DeiT checkpoint
logits = backbone_head(output_tokens[:, 0]) # (B, 1000)
# ReLU, dropout 0.5, then 1000 -> 1 linear [code: models.py#L22-L24]
hidden = dropout(relu(logits), p=0.5) # (B, 1000)
return linear_1000_to_1(hidden) # (B, 1)
def gap_head_as_coded(output_tokens):
# output_tokens: (B, 576, D) after the final norm; class token dropped
# Average-pool the channel axis of each token from D to 48 [code: #L94]
reduced = adaptive_avg_pool(output_tokens, size=48) # (B, 576, 48)
flat = reshape(reduced, (batch, 576 * 48)) # (B, 27648)
# ReLU, linear 27648 -> 128, ReLU, dropout 0.5, linear 128 -> 1
hidden = relu(linear_27648_to_128(relu(flat))) # (B, 128)
hidden = dropout(hidden, p=0.5) # (B, 128)
return linear_128_to_1(hidden) # (B, 1)Training step, count labels only. The loss below is the paper's (mean over the batch). The official code uses the sum; see Paper vs code.
# Training step with count labels only [§4.1] [eq. 7]
# Input: tiles (B, 3, 384, 384); one integer count per tile (B,)
# Output: scalar loss
def training_step(tiles, counts):
# Predict one count per tile [§3.4]
predictions = model(tiles) # (B, 1)
# Mean absolute error over the batch [eq. 7]
loss = mean(abs(predictions - counts)) # scalar
# The official code sums over the batch instead [code: train.py#L60]
return lossInference for one image. The paper says six sub-images per image §4.1; the official code tiles the resized frame and sums the tile outputs.
# Count one image at test time [§4.1] [code: dataset.py#L69-L82]
# Input: image resized to 1152 x 768 (or 768 x 1152 if portrait)
# Output: predicted count (float)
def predict_count(image):
# Cut the frame into six non-overlapping 384 x 384 tiles [§4.1]
tiles = split_into_tiles(image, size=384) # (6, 3, 384, 384)
# The image count is the sum of the tile outputs [code: train.py#L219-L227]
return sum(model(tiles))Building training tiles and count labels. The point annotations are used only here, to derive the count of each tile.
# Build tiles and count labels [§4.1] [code: data/predataset_sh.py#L72-L107]
# Input: an image and its point annotations, one (x, y) per person
# Output: six 384 x 384 tiles and one integer count per tile
def make_training_tiles(image, points):
# Resize to 1152 x 768, not keeping the aspect ratio [§4.1]
# [code: data/predataset_sh.py#L72-L81]
frame = resize(image, width=1152, height=768)
# Scale the point coordinates by the same factors [code: #L73-L81]
points = scale_points(points, frame_factors)
# Three columns by two rows of tiles [§4.1] [code: #L101-L107]
for tile in grid(frame, size=384):
# Count the points in the tile to get its label [code: #L107]
counts = count_points_inside(points, tile)
yield tile, countsQuick reference for the paper's own statements: the image is reshaped into K x K patches with K = 16 §4.1; N = 576 tokens per 384 x 384 tile §4.1; the encoder has 12 layers with 12 heads §4.1.
Hyperparameters
Values from the paper unless the "Where" column says code. "Not stated" means the paper does not give the value.
| Name | Value | Where | Notes |
|---|---|---|---|
| Tile size (input to the encoder) | 384 x 384 | §4.1 | Each resized image gives six non-overlapping tiles §4.1 |
| Resized frame | 1152 x 768 | §4.1 | Code: landscape to 1152 wide and 768 tall; portrait to 768 wide and 1152 tall, with each axis scaled separately predataset_sh.py |
| Patch size K | 16 | §4.1 | Gives N = 576 patches per tile §4.1 |
| Encoder layers L | 12 | §4.1 | Encoder only, no decoder §3.3 |
| Heads per attention block m | 12 | §4.1 | Head dimension follows from D and m, which is not stated |
| Hidden width D | Not stated | §3.3 | Code: 768 models.py |
| MLP expansion | 4 x D | §3.3 | Code: mlp_ratio = 4 models.py |
| Initialisation | ImageNet pretrained encoder | §4.1 | Code loads a DeiT-B 384 checkpoint models.py |
| Optimiser | Adam | §4.1 | Code: torch Adam with weight decay train.py |
| Learning rate | 1e-5 | §4.1 | Code default 1e-5 config.py. No warmup stated. Code: multi-step decay by 0.1 at epoch 300 train.py |
| Weight decay | 1e-4 | §4.1 | Code default is 5e-4 config.py; the README training commands do not override it README.md |
| Batch size | 24 | §4.1 | Counts tiles, not images. Code default 8 config.py |
| Training epochs | Not stated | none | Code default 20000 config.py |
| LR schedule | Not stated | none | Code: MultiStepLR, milestone 300, gamma 0.1 train.py |
| Augmentation | Random horizontal flip; random grayscaling | §4.1 | Code: horizontal flip with probability 0.5 dataset.py. No grayscale transform in the code |
| Input normalisation | Not stated | none | Code: ImageNet mean and std train.py |
| Loss | L1 averaged over the batch | eq. 7 | Code sums over the batch train.py |
| Head dropout | Not stated | none | Code: 0.5 models.py models.py |
| Random seed | Not stated | none | Code default 1 config.py |
| Test batch | Not stated | none | Code: one image per step, six tiles each dataset.py |
| Training hardware | One V100 GPU | §4.1 | Run-time comparison on one Titan XP §6.4 tab. 8 |
Results
Author-reported. Each dataset is compared with fully supervised methods (point labels) and count-only methods §5. Dataset facts are from §4.2. The paper does not describe Trancos, and the table for it reports only MAE and MSE tab. 7.
Headline comparisons, from the tables below:
- ShanghaiTech Part A tab. 1: TransCrowd-GAP reports MAE 66.1 and MSE 105.1. The count-only baselines in the same table are MATT (80.1, 129.4) and the soft-label network of Yang et al. (104.6, 145.2). MATT uses some point labels §5.
- ShanghaiTech Part B tab. 1: TransCrowd-GAP reports 9.3 and 16.1. The count-only baselines are MATT (11.7, 17.5) and Yang et al. (12.3, 21.2).
- UCF-QNRF tab. 1: TransCrowd-GAP reports 97.2 and 168.5. No count-only baseline reports UCF-QNRF in this table.
- JHU-Crowd++ test tab. 3: TransCrowd-GAP reports overall 74.9 and 295.6, against BL (75.0, 299.9), and the authors report CSRNet at 85.9 and 309.2.
- NWPU-Crowd test tab. 4: TransCrowd-GAP reports overall 117.7 and 451.0. UOT reports 83.5 and 346.9 in the same table.
- UCF_CC_50 tab. 5: TransCrowd-GAP reports 272.2 MAE, with ADCrowdNet at 257.1 MAE and MATT at 355.0.
- WorldExpo'10 tab. 6: TransCrowd-GAP averages 8.5 MAE over five scenes; LSC-CNN reports 8.0.
- Trancos tab. 7: TransCrowd-GAP reports MAE 3.23 and MSE 4.66; ADCrowdNet reports MAE 2.44.
Full tables, as printed (a dash means not reported in the paper).
Table 1 tab. 1: UCF-QNRF, ShanghaiTech Part A and Part B. "Point + count" means the method used point and count labels; "count" means count labels only.
| Method | Year | Labels | QNRF MAE | QNRF MSE | A MAE | A MSE | B MAE | B MSE |
|---|---|---|---|---|---|---|---|---|
| MCNN | CVPR 16 | point + count | 277.0 | 426.0 | 110.2 | 173.2 | 26.4 | 41.3 |
| CL | ECCV 18 | point + count | 132.0 | 191.0 | – | – | – | – |
| CSRNet | CVPR 18 | point + count | – | – | 68.2 | 115.0 | 10.6 | 16.0 |
| L2R | TPAMI 19 | point + count | 124.0 | 196.0 | 73.6 | 112.0 | 13.7 | 21.4 |
| CFF | ICCV 19 | point + count | – | – | 65.2 | 109.4 | 7.2 | 12.2 |
| PGCNet | ICCV 19 | point + count | – | – | 57.0 | 86.0 | 8.8 | 13.7 |
| TEDnet | CVPR 19 | point + count | 113.0 | 188.0 | 64.2 | 109.1 | 8.2 | 12.8 |
| BL | ICCV 19 | point + count | 88.7 | 154.8 | 62.8 | 101.8 | 7.7 | 12.7 |
| ASNet | CVPR 20 | point + count | 91.5 | 159.7 | 57.7 | 90.1 | – | – |
| LibraNet | ECCV 20 | point + count | 88.1 | 143.7 | 55.9 | 97.1 | 7.3 | 11.3 |
| NoisyCC | NeurIPS 20 | point + count | 85.8 | 150.6 | 61.9 | 99.6 | 7.4 | 11.3 |
| DM-Count | NeurIPS 20 | point + count | 85.6 | 148.3 | 59.7 | 95.7 | 7.4 | 11.8 |
| Method in [ma2020learning] | MM 20 | point + count | 84.7 | 147.2 | 58.1 | 91.7 | 6.5 | 10.1 |
| S3 | IJCAI 21 | point + count | 80.6 | 139.8 | 57.0 | 96.0 | 6.3 | 10.6 |
| UOT | AAAI 21 | point + count | 83.3 | 142.3 | 58.1 | 95.9 | 6.5 | 10.2 |
| Method in [yang2020weakly] | ECCV 20 | count | – | – | 104.6 | 145.2 | 12.3 | 21.2 |
| MATT | PR 21 | count (some point labels) | – | – | 80.1 | 129.4 | 11.7 | 17.5 |
| TransCrowd-Token | – | count | 98.9 | 176.1 | 69.0 | 116.5 | 10.6 | 19.7 |
| TransCrowd-GAP | – | count | 97.2 | 168.5 | 66.1 | 105.1 | 9.3 | 16.1 |
Table 2 tab. 2: JHU-Crowd++ validation split. Low, medium and high are the count bins [0, 50], (50, 500] and above 500.
| Method | Year | Labels | Low MAE | Low MSE | Med MAE | Med MSE | High MAE | High MSE | Overall MAE | Overall MSE |
|---|---|---|---|---|---|---|---|---|---|---|
| MCNN | CVPR 16 | point + count | 90.6 | 202.9 | 125.3 | 259.5 | 494.9 | 856.0 | 160.6 | 377.7 |
| CMTL | AVSS 17 | point + count | 50.2 | 129.2 | 88.1 | 170.7 | 583.1 | 986.5 | 138.1 | 379.5 |
| DSSI-Net | ICCV 19 | point + count | 50.3 | 85.9 | 82.4 | 164.5 | 436.6 | 814.0 | 116.6 | 317.4 |
| CAN | CVPR 19 | point + count | 34.2 | 69.5 | 65.6 | 115.3 | 336.4 | 619.7 | 89.5 | 239.3 |
| SANet | ECCV 18 | point + count | 13.6 | 26.8 | 50.4 | 78.0 | 397.8 | 749.2 | 82.1 | 272.6 |
| CSRNet | CVPR 18 | point + count | 22.2 | 40.0 | 49.0 | 99.5 | 302.5 | 669.5 | 72.2 | 249.9 |
| CG-DRCN | PAMI 20 | point + count | 17.1 | 44.7 | 40.8 | 71.2 | 317.4 | 719.8 | 67.9 | 262.1 |
| MBTTBF | ICCV 19 | point + count | 23.3 | 48.5 | 53.2 | 119.9 | 294.5 | 674.5 | 73.8 | 256.8 |
| SFCN | CVPR 19 | point + count | 11.8 | 19.8 | 39.3 | 73.4 | 297.3 | 679.4 | 62.9 | 247.5 |
| BL | ICCV 19 | point + count | 6.9 | 10.3 | 39.7 | 85.2 | 279.8 | 620.4 | 59.3 | 229.2 |
| TransCrowd-Token | – | count | 7.1 | 10.7 | 33.3 | 54.6 | 302.5 | 557.4 | 58.4 | 201.1 |
| TransCrowd-GAP | – | count | 6.7 | 9.5 | 34.5 | 55.8 | 285.9 | 532.8 | 56.8 | 193.6 |
Table 3 tab. 3: JHU-Crowd++ test split, same bins.
| Method | Year | Labels | Low MAE | Low MSE | Med MAE | Med MSE | High MAE | High MSE | Overall MAE | Overall MSE |
|---|---|---|---|---|---|---|---|---|---|---|
| MCNN | CVPR 16 | point + count | 97.1 | 192.3 | 121.4 | 191.3 | 618.6 | 1166.7 | 188.9 | 483.4 |
| CMTL | AVSS 17 | point + count | 58.5 | 136.4 | 81.7 | 144.7 | 635.3 | 1225.3 | 157.8 | 490.4 |
| DSSI-Net | ICCV 19 | point + count | 53.6 | 112.8 | 70.3 | 108.6 | 525.5 | 1047.4 | 133.5 | 416.5 |
| CAN | CVPR 19 | point + count | 37.6 | 78.8 | 56.4 | 86.2 | 384.2 | 789.0 | 100.1 | 314.0 |
| SANet | ECCV 18 | point + count | 17.3 | 37.9 | 46.8 | 69.1 | 397.9 | 817.7 | 91.1 | 320.4 |
| CSRNet | CVPR 18 | point + count | 27.1 | 64.9 | 43.9 | 71.2 | 356.2 | 784.4 | 85.9 | 309.2 |
| CG-DRCN | PAMI 20 | point + count | 19.5 | 58.7 | 38.4 | 62.7 | 367.3 | 837.5 | 82.3 | 328.0 |
| MBTTBF | ICCV 19 | point + count | 19.2 | 58.8 | 41.6 | 66.0 | 352.2 | 760.4 | 81.8 | 299.1 |
| SFCN | CVPR 19 | point + count | 16.5 | 55.7 | 38.1 | 59.8 | 341.8 | 758.8 | 77.5 | 297.6 |
| BL | ICCV 19 | point + count | 10.1 | 32.7 | 34.2 | 54.5 | 352.0 | 768.7 | 75.0 | 299.9 |
| UOT | AAAI 21 | point + count | 11.2 | 26.2 | 28.7 | 45.3 | 274.1 | 648.2 | 60.5 | 252.7 |
| S3 | IJCAI 21 | point + count | – | – | – | – | – | – | 59.4 | 244.0 |
| TransCrowd-Token | – | count | 8.5 | 23.2 | 33.3 | 71.5 | 368.3 | 816.4 | 76.4 | 319.8 |
| TransCrowd-GAP | – | count | 7.6 | 16.7 | 34.8 | 73.6 | 354.8 | 752.8 | 74.9 | 295.6 |
Table 4 tab. 4: NWPU-Crowd. Validation overall, test overall, and test scene-level MAE by count range: S0 is zero, S1 is (0, 100], S2 is (100, 500], S3 is (500, 5000] and S4 is above 5000 tab. 4 caption.
| Method | Year | Labels | Val MAE | Val MSE | Test MAE | Test MSE | Test avg (scene MAE) | S0 / S1 / S2 / S3 / S4 |
|---|---|---|---|---|---|---|---|---|
| C3F-VGG | Tech 19 | point + count | 105.79 | 504.39 | 127.0 | 439.6 | 666.9 | 140.9 / 26.5 / 58.0 / 307.1 / 2801.8 |
| CSRNet | CVPR 18 | point + count | 104.89 | 433.48 | 121.3 | 387.8 | 522.7 | 176.0 / 35.8 / 59.8 / 285.8 / 2055.8 |
| PCC-Net-VGG | CVPR 19 | point + count | 100.77 | 573.19 | 112.3 | 457.0 | 777.6 | 103.9 / 13.7 / 42.0 / 259.5 / 3469.1 |
| CAN | CVPR 19 | point + count | 93.58 | 489.90 | 106.3 | 386.5 | 612.2 | 82.6 / 14.7 / 46.6 / 269.7 / 2647.0 |
| SFCN (dagger) | CVPR 19 | point + count | 95.46 | 608.32 | 105.7 | 424.1 | 712.7 | 54.2 / 14.8 / 44.4 / 249.6 / 3200.5 |
| BL | ICCV 19 | point + count | 93.64 | 470.38 | 105.4 | 454.2 | 750.5 | 66.5 / 8.7 / 41.2 / 249.9 / 3386.4 |
| KDMG | PAMI 20 | point + count | – | – | 100.5 | 415.5 | 632.7 | 77.3 / 10.3 / 38.5 / 259.4 / 2777.9 |
| NoisyCC | NeurIPS 20 | point + count | – | – | 96.9 | 534.2 | 608.1 | 218.7 / 10.7 / 35.2 / 203.2 / 2572.8 |
| DM-Count | NeurIPS 20 | point + count | 70.5 | 357.6 | 88.4 | 388.6 | 498.0 | 146.6 / 7.6 / 31.2 / 228.7 / 2075.8 |
| S3 | IJCAI 21 | point + count | – | – | 87.8 | 387.5 | 566.5 | 80.7 / 7.9 / 36.3 / 212.0 / 2495.4 |
| UOT | AAAI 21 | point + count | – | – | 83.5 | 346.9 | – | – |
| TransCrowd-Token | – | count | 88.2 | 446.9 | 119.6 | 463.9 | 736.0 | 88.0 / 12.7 / 47.2 / 311.2 / 3216.1 |
| TransCrowd-GAP | – | count | 88.4 | 400.5 | 117.7 | 451.0 | 737.8 | 69.3 / 12.8 / 46.0 / 309.0 / 3252.2 |
Table 5 tab. 5: UCF_CC_50.
| Method | Labels | MAE | MSE |
|---|---|---|---|
| MCNN | point + count | 377.6 | 509.1 |
| CSRNet | point + count | 266.1 | 397.5 |
| ADCrowdNet | point + count | 257.1 | 363.5 |
| MATT | count (some point labels) | 355.0 | 550.2 |
| TransCrowd-Token | count | 288.9 | 407.6 |
| TransCrowd-GAP | count | 272.2 | 395.3 |
Table 6 tab. 6: WorldExpo'10, MAE per scene (S1 to S5) and the average.
| Method | Labels | S1 | S2 | S3 | S4 | S5 | Average |
|---|---|---|---|---|---|---|---|
| M-CNN | point + count | 3.4 | 20.6 | 12.9 | 13.0 | 8.1 | 11.6 |
| CP-CNN | point + count | 2.9 | 14.7 | 10.5 | 10.4 | 5.8 | 8.8 |
| Liu et al. (DecideNet) | point + count | 2.0 | 13.1 | 8.9 | 17.4 | 4.8 | 9.2 |
| IC-CNN | point + count | 17.0 | 12.3 | 9.2 | 8.1 | 4.7 | 10.3 |
| CSR-Net | point + count | 2.9 | 11.5 | 8.6 | 16.6 | 3.4 | 8.6 |
| SA-Net | point + count | 2.6 | 13.2 | 9.0 | 13.3 | 3.0 | 8.2 |
| LSC-CNN | point + count | 2.9 | 11.3 | 9.4 | 12.3 | 4.3 | 8.0 |
| MATT | count (some point labels) | 3.8 | 13.1 | 10.4 | 15.9 | 5.3 | 9.7 |
| TransCrowd-Token | count | 2.3 | 14.2 | 9.9 | 14.0 | 4.3 | 8.9 |
| TransCrowd-GAP | count | 2.1 | 13.3 | 8.9 | 13.8 | 4.4 | 8.5 |
Table 7 tab. 7: Trancos. Only the TransCrowd rows are count-only; the paper gives no dataset description for Trancos §4.2.
| Method | Labels | MAE | MSE |
|---|---|---|---|
| FCN-HA | point + count | 4.21 | – |
| CSRNet | point + count | 3.56 | – |
| ADCrowdNet | point + count | 2.44 | – |
| TransCrowd-Token | count | 3.28 | 4.80 |
| TransCrowd-GAP | count | 3.23 | 4.66 |
Table 8 tab. 8: run-time on a Titan XP at 384 x 384 input (FPS as printed).
| Method | Input | Params | Backbone | FPS |
|---|---|---|---|---|
| CSRNet | 384 x 384 | 16.2 M | VGG16 | 21.67 |
| BL | 384 x 384 | 21.6 M | VGG19 | 45.66 |
| TransCrowd-Token | 384 x 384 | 86.8 M | Transformer | 46.41 |
| TransCrowd-GAP | 384 x 384 | 90.4 M | Transformer | 46.73 |
Table 9 tab. 9: ShanghaiTech Part A with three initialisations. None means the model is trained directly on Part A; Pre-ImgNet fine-tunes ImageNet weights; Pre-GCC fine-tunes weights pretrained on the synthetic GCC dataset §6.5.
| Method | Year | Labels | None MAE | None MSE | Pre-ImgNet MAE | Pre-ImgNet MSE | Pre-GCC MAE | Pre-GCC MSE |
|---|---|---|---|---|---|---|---|---|
| CSRNet | CVPR 18 | point + count | 120.0 | 179.4 | 68.2 | 115.0 | 67.4 | 112.3 |
| TransCrowd-Token | – | count | 142.0 | 212.5 | 69.0 | 116.5 | 67.2 | 111.9 |
| TransCrowd-GAP | – | count | 139.9 | 231.0 | 66.1 | 105.1 | 63.8 | 102.3 |
Table 10 tab. 10: cross-dataset evaluation, trained on the source without fine-tuning §6.6. Columns are source to target.
| Method | Year | Labels | B to A MAE | B to A MSE | A to B MAE | A to B MSE | QNRF to A MAE | QNRF to A MSE | QNRF to B MAE | QNRF to B MSE |
|---|---|---|---|---|---|---|---|---|---|---|
| MCNN | CVPR 16 | point + count | 221.4 | 357.8 | 85.2 | 142.3 | – | – | – | – |
| D-ConvNet | ECCV 18 | point + count | 140.4 | 226.1 | 49.1 | 99.2 | – | – | – | – |
| RRSP | CVPR 19 | point + count | – | – | 40.0 | 68.5 | – | – | – | – |
| BL | ICCV 19 | point + count | – | – | – | – | 69.8 | 123.8 | 15.3 | 26.5 |
| TransCrowd-GAP | – | count | 141.3 | 258.9 | 18.9 | 31.1 | 78.7 | 122.5 | 13.5 | 21.9 |
Paper vs code
Official code: https://github.com/dk-liang/TransCrowd at commit
e4fe7ec3138f66bbeb6c3926cff6f04b03c5af58, MIT licence. The repository has no
separate documentation beyond the README. The attention and block code come from
the timm library and is not in this repository.
- GAP head differs from the paper's description. The paper averages over the output tokens §3.4 fig. 2(c). The code averages over the channel axis: each of the 576 tokens is reduced from 768 to 48 values, then all 576 x 48 values are flattened into one 27,648-long vector [code: Networks/models.py#L94] models.py. The first linear layer of the head is 27,648 x 128 models.py.
- GAP sequence keeps a class token. The GAP model still prepends a class token to the input sequence and drops its output before the head [code: Networks/models.py#L76-L77] models.py. The paper's GAP description has no such token §3.4.
- Token head has more than the paper describes. The head is the backbone's
classifier (768 to 1000), then ReLU, dropout 0.5, then a 1000 to 1 linear
layer models.py models.py.
The paper gives two linear layers only §3.4. The 1000-wide classifier is
the timm default; the constructor does not set
num_classes, and the checkpoint is loaded withstrict=Falsemodels.py [inferred from timm defaults]. - Loss is a sum, not a mean. The paper's loss averages over the batch
eq. 7. The code builds
nn.L1Loss(size_average=False)train.py, which sums over the batch. Adam makes updates roughly insensitive to a constant gradient scale, but the L2 weight-decay term is added to the gradient, so its relative strength drops by the batch size [inferred from the code; not checked by running it]. - Weight decay. The paper states 1e-4 §4.1. The default in the code is 5e-4 config.py, and the README training commands do not change it README.md.
- Grayscale augmentation is absent. The paper lists random grayscaling §4.1. The training transform has only horizontal flips dataset.py and normalisation train.py.
- Model selection uses the evaluation split. The code evaluates on the test list every 5 epochs from epoch 10 and keeps the checkpoint with the lowest MAE train.py. The test lists are the ones used for reporting train.py. The paper does not say how the reported checkpoint was chosen.
- Training counts come from point annotations. The preprocessing counts the annotated points inside each tile and stores that count as the label [code: data/predataset_sh.py#L86-L107]. Training then uses only the count [code: train.py#L168-L172]. Anyone reproducing the count labels from a count-only source needs the per-tile counts, which the repository does not provide.
- Points outside the frame are dropped. A point is kept only if its integer coordinates fall inside the resized frame predataset_sh.py.
- Aspect ratio is not kept. Each landscape image is resized to 1152 x 768 and each portrait image to 768 x 1152, with each axis scaled separately predataset_sh.py. The paper states the target size only §4.1.
- Batch unit. The data loader's batch size counts tiles, so batch size 24 is 24 tiles train.py. The paper does not say whether it counts images or tiles §4.1.
- Test output is not clipped. The final layer is linear with no activation models.py models.py, so a tile output can be negative, and the sum over six tiles is not clipped [code: train.py#L219-L227].
- Scheduler. The learning rate drops by 10 at epoch 300 [code: train.py#L67]. The paper states no schedule §4.1.
- Parameter counts fit the GAP head. Table 8 reports 90.4 M parameters for GAP and 86.8 M for Token tab. 8. The difference is about 3.6 M, which is close to the 27,648 x 128 first linear layer in the code (about 3.5 M weights) [inferred from the code's layer sizes; not counted by running it].
Pitfalls
- Two GAP heads. Implement the paper's averaged-token head or the code's channel-pooled head, not a mix. The reported GAP numbers cannot be checked against the paper text alone, and the paper does not say which head produced them. §3.4 models.py
- Equation 6 and the head width. The attention equation divides by sqrt(D) eq. 6. Each head works on width D/m §3.3. The conventional scale for each head uses the head width, not D. The paper does not say which is used. The attention code comes from timm, which is not in this repository.
- Notation in equations 5 and 6. Equation 6 writes SA(Z_l) on the left but computes Q, K and V from Z_{l-1} eq. 5 eq. 6. Read the right-hand side as the input to layer l.
- "MSE" is a root mean square. Equation 8 takes the square root, and the code does the same train.py. Tables label it MSE tab. 1. Do not compare it with an unrooted MSE from other papers.
- Text and table disagree. §6.1 says TransCrowd-GAP beats TransCrowd-Token by 2.8 MAE on Part A §6.1. Table 1 gives 69.0 and 66.1, a difference of 2.9 tab. 1. The MSE difference of 11.4 matches.
- NWPU image count. §4.2 gives 5,109 images, with splits of 3,109, 500 and 1,500 §4.2. §6.1 gives 5,190 §6.1. The splits sum to 5,109.
- Epoch count is not stated. The convergence claim is "1.9 times fewer training epochs" than CSRNet §6.3. The paper does not state the number of epochs for either method, so the claim cannot be checked.
- Run-time is one comparison. FPS is measured on one Titan XP at 384 x 384 §6.4 tab. 8. The batch size and precision are not stated.
- Results come from one run per setting. The paper reports no seeds or error bars [not stated].
- Count-only is not always pure. MATT is marked as count-level in the tables but uses some point labels §5 tab. 1. Compare with that in mind.
- Comparisons across label types. The authors call comparisons with fully supervised methods unfair §5. Their own results are close to some of them, not ahead of all of them: NWPU-Crowd tab. 4, UCF_CC_50 tab. 5, WorldExpo tab. 6 and Trancos tab. 7 each have a fully supervised method ahead.
- Tile boundaries. The six tiles do not overlap. A person whose head crosses a tile border is counted in whichever tile holds the annotated point [code: data/predataset_sh.py#L101-L107]. Each tile is predicted on its own, so a person cut by a border is counted at most once.
- Cost. Each tile gives 576 patch tokens, plus one for the token variant §4.1. Self-attention cost grows with the square of the token count §3.3. The GAP head adds a 27,648 x 128 layer, about 3.5 M weights [code: Networks/models.py#L65].
- Qualitative attention. The attention maps are shown only in one figure and are not measured fig. 4 §6.2. The paper's claim that the GAP attention is more reasonable is therefore not quantified.
- Cross-dataset results are without fine-tuning. Models are trained on the source and tested on the target directly §6.6.
Lineage
- Builds on: the Transformer encoder §3.3 §1; the ViT encoder-only design §3.3 §2.3; the class-token idea from BERT and ViT, used as the regression token §3.4; and ImageNet pretraining §4.1.
- Compared against, count-only: MATT §2.2 tab. 1; Yang et al., soft-label sorting network §2.2 tab. 1.
- Compared against, point labels: CSRNet, BL, MCNN, DM-Count, UOT, S3, ADCrowdNet and others tab. 1 to tab. 7.
- Superseded by: not stated.
Source and license
Adapted from “TransCrowd: weakly-supervised crowd counting with transformers” by Dingkang Liang, Xiwu Chen, Wei Xu, Yu Zhou, Xiang Bai (arXiv:2104.09116), 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/dk-liang/TransCrowd at commit e4fe7ec3138f (MIT). Any quoted code is shown under that license.