Acoustic UAV Detection in Battlefield Scenarios
Summary
This article presents a GitHub repository for reproducing a paper on acoustic UAV detection in battlefield scenarios, addressing challenges like noise, domain shift, and weak labels. The repository provides code for the method and evaluation protocol, with synthetic data for testing.
View Cached Full Text
Cached at: 08/18/26, 10:26 PM
PositiveLoss/acoustic-uav-detector
Source: https://github.com/PositiveLoss/acoustic-uav-detector
Acoustic UAV Detection in Battlefield Scenarios — reproduction
An open reproduction of
Acoustic UAV Detection in Battlefield Scenarios: Handling Noise, Domain Shift, and Weak Labels Vadym Vilhurin, Volodymyr Sydorskyi, Andrii Shevtsov — ICMCIS 2026 · arXiv:2608.14287v1
The paper detects small UAVs from sound alone under three simultaneous problems: battlefield noise, a sensor domain shift between two very different microphones, and weak, extremely imbalanced labels (the target class is <1% of the training data). Its answer is a PCEN frontend, a ConvNeXt-Tiny SED model whose frequency pooling is replaced by a learnable projector, and a domain-aware training recipe built on auxiliary classes, noise-injection mixup and curriculum RMS masking.
This repository implements all of it, end to end, plus the paper’s evaluation protocol (Tables III–V, the detection-range study, and Figures 3–8).
The honest caveat, up front
The paper’s data is not public. It is 300k+ recordings collected by Zvook from Ukrainian frontlines and active anti-aircraft sensors. Without it, no one can reproduce the paper’s numbers. What can be reproduced is the method and the protocol, and that is what this repository is: a faithful implementation, plus a procedurally generated two-domain corpus that reproduces the paper’s structure — Mic-1 as a large auxiliary domain, Mic-2 as a tiny target domain, a sensor gap between them, a clean-to-combat train→val→test shift, and a target class under 1% of training data.
Numbers produced here are numbers on synthetic audio. They are used to check that the ablation ladder behaves the way the paper reports, not to claim the paper’s results. Point the manifest at real recordings and the same code trains on them unchanged.
Quickstart
uv sync
Run the full Table V ablation ladder on the synthetic corpus (~100 min on an M1 laptop):
uv run uav-detector ablation --preset demo --out-dir runs/demo --manifest data/manifest_demo.csv
Or check the plumbing (all ten stages, two epochs each) in about ten minutes:
uv run uav-detector ablation --preset smoke --out-dir runs/smoke --manifest data/manifest_smoke.csv
Then the analyses and figures:
uv run uav-detector analysis --out-dir runs/demo --manifest data/manifest_demo.csv
uv run uav-detector figures --out-dir runs/demo --manifest data/manifest_demo.csv
uv run uav-detector report --out-dir runs/demo --manifest data/manifest_demo.csv
Presets:
| preset | what it is | for |
|---|---|---|
paper | ConvNeXt-Tiny, 100 epochs, batch 64, hop 512, full Table I counts | a manifest of real recordings on a real GPU |
demo | ConvNeXt-Atto, 20 epochs, batch 16, hop 1024, 3% of the counts | the whole ladder on a laptop |
smoke | 2 epochs, 4 steps each, a few hundred clips | checking the plumbing |
paper with no manifest would render ~350,000 synthetic clips on the fly, which is not what you
want — it is meant to be pointed at real audio (see Training on real recordings).
The method, mapped to the code
| Paper | Where |
|---|---|
| PCEN frontend, s=0.015, α=0.8, δ=2.0, r=0.5, power 2.0 (§VII-A) | features.py — PCEN, Frontend |
| ConvNeXt-Tiny + SED attention head (§VII) | model.py — UAVDetector |
| GeM frequency pooling → learnable frequency projector (§VII) | FrequencyProjector (freq_head="projector" vs "gem") |
| tanh removed from attention; sigmoid → class-wise softmax (§VII) | AttBlock(use_tanh=False), aggregate="softmax" |
| Noise-injection mixup, λ≤0.3, Mic-1/Mic-2 with equal probability (§VII-B) | augment.py — Augmenter.noise_injection_mixup |
| Curriculum RMS masking, 2 s max-energy window, 0.3→1.0 s from epoch 5 (§VII-B, §VIII-B) | Augmenter.curriculum_rms_mask, rms_mask_interval |
| Time shift ±100 ms, sinusoidal warp, gain ±3 dB, reversal p=0.5, SpecAugment (§VIII-B) | Augmenter.__call__, SpecAugment |
| Square-root sampling with replacement, noise sampled per device (§VIII-B) | sampling.py |
| Focal loss α=1, γ=2 (§VIII-B) | losses.py |
| AdamW 1e-4, cosine → 1e-6, 100 epochs, batch 64, SWA, top-5 checkpoint averaging (§VIII-B) | engine.py — Trainer |
| Macro-F1 checkpoint selection over auxiliary classes, binary Small-Drone reporting (§V) | metrics.py |
| Silhouette / k-NN purity / adversarial validation / t-SNE (§VI, Tables III–IV) | domain_shift.py, analysis.py |
| Detection range subgroups (§VIII-D) | analysis.detection_range_table |
| Table V ablation ladder and ensembles (§VIII-C) | ablation.py |
Parameter counts land where the paper says they should: 28.42M with GeM pooling (paper: 28.22M) and 31.08M with the frequency projector (paper: 30.97M), i.e. the projector adds 2.66M against the paper’s 2.75M.
The two-domain corpus
synth.py renders every clip procedurally from its seed — 9 s at
32 kHz, no downloads, no disk, bit-reproducible. corpus.py lays
those clips out with the exact per-device, per-class counts of Tables I and II, scaled by
--scale, so the ratios that make the task hard survive.
| Mic-1 (auxiliary) | Mic-2 (target) | |
|---|---|---|
| Placement | tower, 10–40 m | ground, ~1 m, frontline |
| Classes | Drone, Jet, Helicopter, Propeller, Noise | Small Drone, Noise |
| Hardware | 60 Hz high-pass, 3 kHz presence peak, low self-noise | LF boost, 1.2 kHz notch, early HF rolloff, AGC with soft clipping, high self-noise |
| Noise | wind, mains hum, structural knocks, animals | speech, music, machinery, impacts, distant blasts |
Three deliberate design choices carry the paper’s difficulty:
- Drone-lookalike confusers. Generators, mopeds and fans produce harmonic stacks with strong AM in the same 60–260 Hz band as a quadcopter. They are almost absent from the clean field-test training split (p=0.06) and common in the combat-zone validation and test splits (p=0.34/0.42). This is the main driver of the validation→test gap, mirroring the paper’s own gap.
- Genuinely weak labels. A source is audible for only part of its clip (the fly-by envelope can fall to near zero), so a positive clip is mostly negative frames — which is exactly the regime attention pooling and RMS masking exist for.
- A nonlinear sensor gap. Mic-2 applies level-dependent AGC with soft clipping. A linear frequency response would be undone by the per-sample standardisation; this is not, so the domain shift survives into the embeddings and Tables III–IV measure something real.
SNRs are low on purpose: near 0–8 dB, medium −8–1 dB, far −17–−6 dB, with +3/0/−3 dB for the field/winter/spring splits. Listen to the corpus with:
uv run uav-detector synth --scale 0.01 --materialise data/examples --limit 2
Figures

The paper’s Figure 3, reproduced on a synthetic Mic-2 Small Drone clip: the Log-Mel panel is dominated by the stationary noise floor, while PCEN suppresses it and exposes the rotor’s modulated harmonic stack and the fly-by envelope.

uav-detector figures writes, into <out-dir>/figures/:
fig3_mel_vs_pcen.pngandcorpus_overview.png— the two figures above.tsne_<stage>.png— Figures 4–7 in four panels: the embedding space by class, by microphone and season, the target class alone across seasons, and the noise class split by microphone — the panel where the Mic-2 cluster stands apart.fig8_range_roc.png— Figure 8: ROC curves for near/medium versus far flybys.
Results
Full tables are in RESULTS.md, regenerated by uav-detector report. The demo
ladder takes about 100 minutes on an M1 laptop. Test F1 for the Small Drone class, alongside the
paper’s reported values:
| stage | this repro (val) | this repro (test) | paper (test) |
|---|---|---|---|
| Baseline on Mic2 | 29.9 | 22.3 | 17.4 |
| Baseline on Mic2 + Mic1 | 88.5 | 72.5 | 49.8 |
| + SWA | 88.5 | 73.5 | 59.7 |
| + Mixup | 92.0 | 74.9 | 75.6 |
| + RMS based masking | 90.2 | 74.9 | 75.2 |
| + Projector | 90.2 | 71.5 | 77.8 |
| + Using full dataset | 88.9 | 71.7 | 77.1 |
| + PCEN | 85.1 | 83.2 | 74.6 |
| + Projector (PCEN) | 88.9 | 82.1 | 77.9 |
| + Using full dataset (PCEN) | 89.8 | 80.5 | 78.6 |
| Ensemble (small) | 92.0 | 76.4 | 81.1 |
| Ensemble (full) | 90.2 | 74.3 | 82.2 |
What reproduces. The paper’s central claim does, and strongly. Training on the target domain alone collapses (22.3 test F1, precision 12.6% — the model fires on everything). Adding the Mic-1 auxiliary domain and its four aircraft classes is worth +50 F1, which is contribution 4 of the paper: auxiliary UAV classes from a different domain improve target-domain generalisation. SWA adds a little (+1.0), noise-injection mixup adds more (+1.3, and it is the step that finally buys precision: 58.6% → 65.0%), and RMS masking is flat — the same shape as the paper’s ladder, where those steps go +9.9, +15.9, −0.4.
PCEN is the largest single win here (+8.4 test F1) and it nearly closes the validation-to-test gap: every Log-Mel stage sits around 90 val / 72 test, while PCEN lands at 85/83. That is a stronger effect than the paper reports (they see PCEN trade a little test F1 for robustness), and it is easy to explain: the synthetic Mic-2 chain ends in a level-dependent AGC with soft clipping, and adaptive gain control is exactly the right inverse for that. Real hardware variation is messier.
What does not reproduce. Two things, and both are informative:
- The frequency projector hurts (−3.4 F1 on the Log-Mel branch, −1.1 on the PCEN branch) where the paper gains +2.6. Its stated purpose is to recover harmonic ratios when low-frequency noise masks the fundamental. In this corpus the fundamental is usually intact, so the extra capacity has nothing to buy and 20 epochs on a small pool is enough to overfit it. This is a property of the synthetic data, not evidence against the paper’s design.
- Adversarial validation does not fall (Table IV: the paper drops from 97.1% to 69.1% test ROC AUC; here it stays near 97% for every setup). The synthetic sensor gap is a deterministic filter plus a fixed AGC applied to every Mic-2 clip, so a linear probe on the embeddings recovers it perfectly no matter what the augmentations do. Real microphones vary unit to unit and session to session, which is what gives noise-injection mixup something to blur. Modelling per-recording hardware variation would be the first thing to add to the simulator.
Table III lands in between: the direction and the ordering of the global sensor shift reproduce (the enhanced models separate the microphones more than the baseline, and PCEN separates them less than enhanced Log-Mel — 0.79 vs 0.73 silhouette, against the paper’s 0.208 vs 0.170), and PCEN gives the lowest Mic-2 noise seasonal silhouette (0.100 vs 0.147 for the baseline), matching the paper’s 0.259 vs 0.385. The absolute values are much larger than the paper’s because the synthetic domains are cleanly separable.
The detection-range study runs but is not meaningful at demo scale: only 6 far-distance test clips survive the 3% subsampling, against the paper’s 241.

The embedding space reproduces the paper’s Figures 4–7 qualitatively: classes form distinct clusters, Mic-2 environmental noise forms its own cluster well away from Mic-1 noise (the domain shift the paper describes), the Small Drone cluster sits right next to it — which is where the false positives come from — and Small Drone validation and test clips overlap, matching the paper’s finding that there is little seasonal shift for the signal classes.
Training on real recordings
docs/DATASETS.md surveys the public datasets that can stand in for the paper’s corpus, which ones fit the Mic-1 and Mic-2 roles, and the four traps (sample rate, clip length, session leakage, corpus-provenance leakage) that will otherwise produce numbers that mean nothing.
Everything is driven by a manifest CSV:
uid,path,device,label,split,season,distance,seed
rec-0001,/data/mic2/2026-03-01T09-12-33.wav,mic2,small_drone,train_small,spring,medium,0
path— a real audio file (any formatsoundfilereads; resampled to 32 kHz and cropped or padded to 9 s) orsynth://<uid>to render procedurally.device—mic1ormic2.label—small_drone,drone,jet_aircraft,helicopter,propeller_aircraft,noise.split—train_small,train_full,val,test. Keep every clip cut from one continuous stream inside a single split, as the paper does, or the evaluation leaks.season,distance— only used by the synthesiser and the range study;distanceshould be the human “near/medium/far” annotation if you have it.
Then:
uv run uav-detector train --config configs/pcen_full.yaml --preset paper --manifest /data/manifest.csv
Tracking and resuming
Training writes last.pt after every epoch — model, optimiser, epoch, history, the top-k
bookkeeping and the RNG state — so an interrupted run continues where it stopped:
uv run uav-detector ablation --preset demo --resume
Resume is at epoch granularity: a weighted sampler cannot be rewound mid-epoch, so an
interrupted epoch is redone rather than continued. last.pt is written atomically, so a crash
during the write cannot corrupt it, and --force deletes it because retraining is not resuming.
checkpoint_every trades write cost against how much work an interruption can lose.
Metrics can go to trackio, a local-first tracker:
uv sync --extra track
uv run uav-detector ablation --preset demo --track --project uav-detector
just dashboard
Per epoch it logs loss, accuracy, learning rate, validation macro/binary F1 and per-class F1;
at the end, the test metrics, parameter count and wall time. Every stage of the ladder lands in
one project grouped by output directory, so the ten Table V runs line up in a single dashboard.
Without --track the tracker is a no-op object, so trackio never becomes a hard dependency.
Exporting to ONNX
uv sync --extra onnx
uv run uav-detector export --run runs/demo/pcen_projector --input waveform
Two input contracts, both emitting probs, logits and embedding with a dynamic batch
dimension:
--input | takes | for |
|---|---|---|
waveform (default) | (batch, 288000) raw 32 kHz audio | deployment — STFT, Mel, PCEN and the model all live inside the graph |
spectrogram | (batch, 1, n_mels, frames) | pipelines that already compute features, or when a smaller graph matters |
Every export is verified against eager PyTorch at a batch size it never traced, and the run fails
loudly if the outputs disagree by more than float32 noise (typically ~5e-06) or if a shape drifts.
The audio contract — sample rate, clip length, hop, mel count, frontend, class order — is written
into the model’s metadata_props, so a consumer needs the file and nothing else:
import onnx, onnxruntime as ort, numpy as np
meta = {p.key: p.value for p in onnx.load("model.onnx").metadata_props}
classes = meta["classes"].split(",") # small_drone is index 0
session = ort.InferenceSession("model.onnx")
probs = session.run(None, {"waveform": audio_batch})[0] # (batch, 6), float32
On an M1 CPU the PCEN waveform graph runs a 9-second clip in ~45 ms, roughly 3-6x faster than eager PyTorch. Two things are worth knowing:
- PCEN unrolls. Its smoother is a sequential recursion over time, so tracing emits one step per
frame — 563 of them at hop 512, giving a ~22 MB graph. It is still fast; if the size matters,
export with
--input spectrogramand run the frontend outside, or use the Log-Mel frontend, which has no recursion. - Opset 18. That is what the dynamo exporter emits natively. Asking for less triggers a down-conversion that can silently fail, so the reported opset is read back from the written file rather than assumed.
Deviations from the paper
- Data. Synthetic, as explained above. The single most important limitation.
- Framework. Plain PyTorch instead of PyTorch Lightning, so checkpoint averaging and the augmentation curriculum stay explicit and inspectable.
- SWA + top-5 selection. The paper states both are used but not how they combine. Here SWA
averages the weights of the final phase, the SWA model is evaluated on validation, and it joins
the top-5 Macro-F1 checkpoints in a uniform weight average if it scores at least as well as the
worst of them. BatchNorm statistics are recomputed after every averaging step. The learning rate
also switches from the cosine schedule to a constant
swa_lronce the SWA phase starts, which is standard SWA practice but not something the paper specifies. - Binary decision rule. Table V reports binary Precision/Recall/F1 without stating the
operating point. Here the reported decision is
argmax == small_drone, the natural read-out of the class-wise softmax; the threshold-swept best F1 and ROC AUC are recorded alongside it in everysummary.json. - Head modifications are treated as part of the base setup. Removing tanh and switching to a class-wise softmax are multiclass adaptations, so every row uses them; the ablation rows vary exactly what Table V varies (SWA, mixup, RMS masking, projector, dataset size, PCEN).
- Spectrogram resolution is ambiguous in the paper. §VIII-B gives 2048/128 for “the Log-Mel
Spectrogram baselines” and 4096/256 for “the PCEN/Log-Mel configuration”. That second phrase can
be read as the enhanced setup, whichever frontend it uses. Here the Log-Mel branch of the
ladder stays at 2048/128 and only the PCEN branch moves to 4096/256; if you read it the other
way, set
audio.n_fft: 4096andaudio.n_mels: 256on the Log-Mel stages. - Adversarial validation (Table IV) uses a logistic-regression probe on frozen embeddings rather than training two more full models — same question, far cheaper.
- Fixed step budget in the reduced presets.
demoandsmokesetsteps_per_epoch, so every stage gets the same number of optimiser updates. The “+ Using full dataset” rows therefore change the diversity of the sampled pool, not the amount of training. Thepaperpreset leavessteps_per_epochunset and makes one pass per epoch, as the paper does. - Not reproduced: the Zvook proprietary baseline, SAM-Audio, AST-Drone and TRIDENT rows of
Table V. Those are external checkpoints evaluated as-is; their published numbers are kept in
ablation.PAPER_TABLE_Vfor comparison. - Sequential/stream-level inference is out of scope here, as it is in the paper (listed as future work).
Layout
src/uav_detector/
labels.py class taxonomy, device domains, sampling groups
config.py dataclass configs + YAML loading (paper values are the defaults)
features.py Log-Mel and PCEN frontends
model.py ConvNeXt-Tiny + frequency projector + SED attention head
losses.py focal loss
augment.py waveform augmentations, noise-injection mixup, RMS curriculum
sampling.py square-root sampling with replacement
synth.py procedural two-domain acoustic simulator
corpus.py manifest with the paper's class distribution
data.py datasets, noise bank, dataloaders
engine.py training loop, SWA, top-k checkpoint averaging
metrics.py binary / macro / per-class metrics
domain_shift.py silhouette, k-NN purity, adversarial probe, t-SNE
analysis.py Tables III/IV and the detection-range study
ablation.py the Table V ladder, ensembles, the paper's reported tables
figures.py Figures 3-8
tracking.py metric sinks: no-op by default, trackio when asked
export.py ONNX export, with metadata and a parity check
cli.py command line interface (synth / train / ablation / table / analysis / figures / report / export)
configs/ YAML configs (paper defaults + ablation variants)
tests/ unit tests for the frontend, model, augmentations, sampler, metrics
justfile dev tasks: check, fix, smoke, ablation, publish
just check
runs ruff (lint + format), pyright and the test suite. just fix applies the autofixes, and
just --list shows the rest — just smoke, just ablation, just publish and the individual
report/figure steps. Without just, everything is a plain uv run away:
uv run ruff check . && uv run ruff format --check . && uv run pyright && uv run pytest
Citation
@inproceedings{vilhurin2026acoustic,
title = {Acoustic UAV Detection in Battlefield Scenarios: Handling Noise, Domain Shift, and Weak Labels},
author = {Vilhurin, Vadym and Sydorskyi, Volodymyr and Shevtsov, Andrii},
booktitle = {International Conference on Military Communication and Information Systems (ICMCIS)},
year = {2026},
eprint = {2608.14287},
archivePrefix = {arXiv},
primaryClass = {cs.SD}
}
The dataset behind the original paper was collected and provided by Zvook.
Similar Articles
@BenjaminDEKR: Sound-based drone interception This project (Duke University) could probably be sold to the U.S. gov at any price they …
SonicFly, a research project from Duke University, enables a UAV to intercept another drone by passively listening to its flight sound using aeroacoustic perception.
Classification and detection of multiple UAVs using rational Gaussian wavelet neural networks
This paper proposes a cost-effective UAV detection and classification system using sound signals processed by rational Gaussian wavelet neural networks, achieving interpretable and robust performance for single and multiple UAVs including swarms, outperforming traditional methods.
SULAND v2: A Refined RGB Dataset and Deep Learning Object Detection Benchmark for UAV/UGV-Based SUrface LANDmine Detection Under Domain Shift
This paper introduces SULAND v2, a refined RGB surface landmine detection dataset and benchmark for UAV/UGV-based surveys, addressing annotation errors and domain-shift evaluation in object detection.
Human-in-the-Loop Signature Bootstrapping for UAV Hyperspectral PFM-1 Mine Detection
This paper presents a human-in-the-loop bootstrapping method for detecting PFM-1 mines in UAV hyperspectral imagery, showing that ACE with bootstrapping can find all targets in 2 rounds of inspection, while aggregate ROC-AUC scores hide large operational differences between detectors.
Edge-Aware Thermal Infrared UAV Swarm Tracking
This paper proposes an edge-aware online tracking pipeline for thermal infrared UAV swarm tracking, featuring the Adaptive Kinematic Kalman Filter (AKKF) that balances efficiency and robustness under challenging conditions.