wikillm

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.

  1. 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.
  2. 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.

Pseudocode
# 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.

Pseudocode
# 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.

Pseudocode
# 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 loss

Inference 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.

Pseudocode
# 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.

Pseudocode
# 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, counts

Quick 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.

NameValueWhereNotes
Tile size (input to the encoder)384 x 384§4.1Each resized image gives six non-overlapping tiles §4.1
Resized frame1152 x 768§4.1Code: landscape to 1152 wide and 768 tall; portrait to 768 wide and 1152 tall, with each axis scaled separately predataset_sh.py
Patch size K16§4.1Gives N = 576 patches per tile §4.1
Encoder layers L12§4.1Encoder only, no decoder §3.3
Heads per attention block m12§4.1Head dimension follows from D and m, which is not stated
Hidden width DNot stated§3.3Code: 768 models.py
MLP expansion4 x D§3.3Code: mlp_ratio = 4 models.py
InitialisationImageNet pretrained encoder§4.1Code loads a DeiT-B 384 checkpoint models.py
OptimiserAdam§4.1Code: torch Adam with weight decay train.py
Learning rate1e-5§4.1Code default 1e-5 config.py. No warmup stated. Code: multi-step decay by 0.1 at epoch 300 train.py
Weight decay1e-4§4.1Code default is 5e-4 config.py; the README training commands do not override it README.md
Batch size24§4.1Counts tiles, not images. Code default 8 config.py
Training epochsNot statednoneCode default 20000 config.py
LR scheduleNot statednoneCode: MultiStepLR, milestone 300, gamma 0.1 train.py
AugmentationRandom horizontal flip; random grayscaling§4.1Code: horizontal flip with probability 0.5 dataset.py. No grayscale transform in the code
Input normalisationNot statednoneCode: ImageNet mean and std train.py
LossL1 averaged over the batcheq. 7Code sums over the batch train.py
Head dropoutNot statednoneCode: 0.5 models.py models.py
Random seedNot statednoneCode default 1 config.py
Test batchNot statednoneCode: one image per step, six tiles each dataset.py
Training hardwareOne V100 GPU§4.1Run-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.

MethodYearLabelsQNRF MAEQNRF MSEA MAEA MSEB MAEB MSE
MCNNCVPR 16point + count277.0426.0110.2173.226.441.3
CLECCV 18point + count132.0191.0––––
CSRNetCVPR 18point + count––68.2115.010.616.0
L2RTPAMI 19point + count124.0196.073.6112.013.721.4
CFFICCV 19point + count––65.2109.47.212.2
PGCNetICCV 19point + count––57.086.08.813.7
TEDnetCVPR 19point + count113.0188.064.2109.18.212.8
BLICCV 19point + count88.7154.862.8101.87.712.7
ASNetCVPR 20point + count91.5159.757.790.1––
LibraNetECCV 20point + count88.1143.755.997.17.311.3
NoisyCCNeurIPS 20point + count85.8150.661.999.67.411.3
DM-CountNeurIPS 20point + count85.6148.359.795.77.411.8
Method in [ma2020learning]MM 20point + count84.7147.258.191.76.510.1
S3IJCAI 21point + count80.6139.857.096.06.310.6
UOTAAAI 21point + count83.3142.358.195.96.510.2
Method in [yang2020weakly]ECCV 20count––104.6145.212.321.2
MATTPR 21count (some point labels)––80.1129.411.717.5
TransCrowd-Token–count98.9176.169.0116.510.619.7
TransCrowd-GAP–count97.2168.566.1105.19.316.1

Table 2 tab. 2: JHU-Crowd++ validation split. Low, medium and high are the count bins [0, 50], (50, 500] and above 500.

MethodYearLabelsLow MAELow MSEMed MAEMed MSEHigh MAEHigh MSEOverall MAEOverall MSE
MCNNCVPR 16point + count90.6202.9125.3259.5494.9856.0160.6377.7
CMTLAVSS 17point + count50.2129.288.1170.7583.1986.5138.1379.5
DSSI-NetICCV 19point + count50.385.982.4164.5436.6814.0116.6317.4
CANCVPR 19point + count34.269.565.6115.3336.4619.789.5239.3
SANetECCV 18point + count13.626.850.478.0397.8749.282.1272.6
CSRNetCVPR 18point + count22.240.049.099.5302.5669.572.2249.9
CG-DRCNPAMI 20point + count17.144.740.871.2317.4719.867.9262.1
MBTTBFICCV 19point + count23.348.553.2119.9294.5674.573.8256.8
SFCNCVPR 19point + count11.819.839.373.4297.3679.462.9247.5
BLICCV 19point + count6.910.339.785.2279.8620.459.3229.2
TransCrowd-Token–count7.110.733.354.6302.5557.458.4201.1
TransCrowd-GAP–count6.79.534.555.8285.9532.856.8193.6

Table 3 tab. 3: JHU-Crowd++ test split, same bins.

MethodYearLabelsLow MAELow MSEMed MAEMed MSEHigh MAEHigh MSEOverall MAEOverall MSE
MCNNCVPR 16point + count97.1192.3121.4191.3618.61166.7188.9483.4
CMTLAVSS 17point + count58.5136.481.7144.7635.31225.3157.8490.4
DSSI-NetICCV 19point + count53.6112.870.3108.6525.51047.4133.5416.5
CANCVPR 19point + count37.678.856.486.2384.2789.0100.1314.0
SANetECCV 18point + count17.337.946.869.1397.9817.791.1320.4
CSRNetCVPR 18point + count27.164.943.971.2356.2784.485.9309.2
CG-DRCNPAMI 20point + count19.558.738.462.7367.3837.582.3328.0
MBTTBFICCV 19point + count19.258.841.666.0352.2760.481.8299.1
SFCNCVPR 19point + count16.555.738.159.8341.8758.877.5297.6
BLICCV 19point + count10.132.734.254.5352.0768.775.0299.9
UOTAAAI 21point + count11.226.228.745.3274.1648.260.5252.7
S3IJCAI 21point + count––––––59.4244.0
TransCrowd-Token–count8.523.233.371.5368.3816.476.4319.8
TransCrowd-GAP–count7.616.734.873.6354.8752.874.9295.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.

MethodYearLabelsVal MAEVal MSETest MAETest MSETest avg (scene MAE)S0 / S1 / S2 / S3 / S4
C3F-VGGTech 19point + count105.79504.39127.0439.6666.9140.9 / 26.5 / 58.0 / 307.1 / 2801.8
CSRNetCVPR 18point + count104.89433.48121.3387.8522.7176.0 / 35.8 / 59.8 / 285.8 / 2055.8
PCC-Net-VGGCVPR 19point + count100.77573.19112.3457.0777.6103.9 / 13.7 / 42.0 / 259.5 / 3469.1
CANCVPR 19point + count93.58489.90106.3386.5612.282.6 / 14.7 / 46.6 / 269.7 / 2647.0
SFCN (dagger)CVPR 19point + count95.46608.32105.7424.1712.754.2 / 14.8 / 44.4 / 249.6 / 3200.5
BLICCV 19point + count93.64470.38105.4454.2750.566.5 / 8.7 / 41.2 / 249.9 / 3386.4
KDMGPAMI 20point + count––100.5415.5632.777.3 / 10.3 / 38.5 / 259.4 / 2777.9
NoisyCCNeurIPS 20point + count––96.9534.2608.1218.7 / 10.7 / 35.2 / 203.2 / 2572.8
DM-CountNeurIPS 20point + count70.5357.688.4388.6498.0146.6 / 7.6 / 31.2 / 228.7 / 2075.8
S3IJCAI 21point + count––87.8387.5566.580.7 / 7.9 / 36.3 / 212.0 / 2495.4
UOTAAAI 21point + count––83.5346.9––
TransCrowd-Token–count88.2446.9119.6463.9736.088.0 / 12.7 / 47.2 / 311.2 / 3216.1
TransCrowd-GAP–count88.4400.5117.7451.0737.869.3 / 12.8 / 46.0 / 309.0 / 3252.2

Table 5 tab. 5: UCF_CC_50.

MethodLabelsMAEMSE
MCNNpoint + count377.6509.1
CSRNetpoint + count266.1397.5
ADCrowdNetpoint + count257.1363.5
MATTcount (some point labels)355.0550.2
TransCrowd-Tokencount288.9407.6
TransCrowd-GAPcount272.2395.3

Table 6 tab. 6: WorldExpo'10, MAE per scene (S1 to S5) and the average.

MethodLabelsS1S2S3S4S5Average
M-CNNpoint + count3.420.612.913.08.111.6
CP-CNNpoint + count2.914.710.510.45.88.8
Liu et al. (DecideNet)point + count2.013.18.917.44.89.2
IC-CNNpoint + count17.012.39.28.14.710.3
CSR-Netpoint + count2.911.58.616.63.48.6
SA-Netpoint + count2.613.29.013.33.08.2
LSC-CNNpoint + count2.911.39.412.34.38.0
MATTcount (some point labels)3.813.110.415.95.39.7
TransCrowd-Tokencount2.314.29.914.04.38.9
TransCrowd-GAPcount2.113.38.913.84.48.5

Table 7 tab. 7: Trancos. Only the TransCrowd rows are count-only; the paper gives no dataset description for Trancos §4.2.

MethodLabelsMAEMSE
FCN-HApoint + count4.21–
CSRNetpoint + count3.56–
ADCrowdNetpoint + count2.44–
TransCrowd-Tokencount3.284.80
TransCrowd-GAPcount3.234.66

Table 8 tab. 8: run-time on a Titan XP at 384 x 384 input (FPS as printed).

MethodInputParamsBackboneFPS
CSRNet384 x 38416.2 MVGG1621.67
BL384 x 38421.6 MVGG1945.66
TransCrowd-Token384 x 38486.8 MTransformer46.41
TransCrowd-GAP384 x 38490.4 MTransformer46.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.

MethodYearLabelsNone MAENone MSEPre-ImgNet MAEPre-ImgNet MSEPre-GCC MAEPre-GCC MSE
CSRNetCVPR 18point + count120.0179.468.2115.067.4112.3
TransCrowd-Token–count142.0212.569.0116.567.2111.9
TransCrowd-GAP–count139.9231.066.1105.163.8102.3

Table 10 tab. 10: cross-dataset evaluation, trained on the source without fine-tuning §6.6. Columns are source to target.

MethodYearLabelsB to A MAEB to A MSEA to B MAEA to B MSEQNRF to A MAEQNRF to A MSEQNRF to B MAEQNRF to B MSE
MCNNCVPR 16point + count221.4357.885.2142.3––––
D-ConvNetECCV 18point + count140.4226.149.199.2––––
RRSPCVPR 19point + count––40.068.5––––
BLICCV 19point + count––––69.8123.815.326.5
TransCrowd-GAP–count141.3258.918.931.178.7122.513.521.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.

  1. 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.
  2. 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.
  3. 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 with strict=False models.py [inferred from timm defaults].
  4. 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].
  5. 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.
  6. Grayscale augmentation is absent. The paper lists random grayscaling §4.1. The training transform has only horizontal flips dataset.py and normalisation train.py.
  7. 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.
  8. 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.
  9. Points outside the frame are dropped. A point is kept only if its integer coordinates fall inside the resized frame predataset_sh.py.
  10. 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.
  11. 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.
  12. 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].
  13. Scheduler. The learning rate drops by 10 at epoch 300 [code: train.py#L67]. The paper states no schedule §4.1.
  14. 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.