UFC 308 · Topuria vs. Holloway · Round 2 — the model reading every five frames as it goes.
Strike detection in MMA broadcast video with a 5-frame temporal classifier on top of SAM2 silhouettes.
A three-stage pipeline — segment, annotate, classify — that calls strike or neutral on every 5-frame window. Trained on 38 hand-labeled windows and a Kinetics-400 prior. This page presents the system, the design decisions, and the failure modes that survive them.
- Author
- Thomas Ou
- Source
- UFC 308 · Topuria vs. Holloway
- Read time
- ≈ 7 min
- Status
- technical preview
Abstract
We fine-tune a Temporal Segment Network (ResNet-50, Kinetics-400 pretrain) for binary strike / neutral classification on 5-frame windows of MMA footage. SAM2 segmentation removes the broadcast (cage, crowd, overlays) before classification, reducing the domain-shift burden on a dataset of only 38 hand-labeled windows. The pipeline reaches 0.83 validation accuracy after under one minute of training on a free-tier Colab GPU. A from-scratch 3D CNN baseline never converges — we report both, and discuss where the small-data regime forces transfer learning to do almost all the work.
Detecting a strike in broadcast MMA video is, on the surface, a 1-class action-recognition problem. Three things make it specifically hard.
Temporal scale. A jab takes 100–200 ms from initiation to contact. At 30 fps that is five frames. Most action-recognition models read seconds of context; here the entire signal is shorter than a sneeze. Either the model reads all five frames at once, or it misses the action.
Domain noise. Broadcast footage carries cage geometry, crowd parallax, scorebugs, corporate overlays, and a camera that pans on contact. A naïve classifier will happily learn to predict strike whenever the Monster Energy logo enters frame.
No data. There is no public, labeled dataset for MMA strike detection. Every sample in this work was created from scratch.
The architecturally cleaner option came first: a small 3D CNN that ingests RGB and the SAM2 mask jointly as a 4-channel volume. The hypothesis was that explicit mask information, fed end-to-end, would let the network learn fighter-aware spatiotemporal features.
It never converged. With 38 training samples, no pretrain is available for 4-channel 3D convolutions; the model is initialized from scratch and asked to learn human-motion priors from a kitchen-table dataset. Validation accuracy flatlined at the majority baseline.
The pivot: trade architectural elegance for a strong prior. ResNet-50 has already seen ~300 K Kinetics-400 video clips covering human action. Fine-tuning only adapts the final layer. The backbone does the heavy lifting; the head learns the strike/neutral boundary. That move is the whole pipeline below.
The system is staged so each step is independently inspectable. Mask quality can be audited without re-running training; classifier output can be re-aggregated without re-running segmentation.
Segment
Promptable segmentation, prompted once per fighter on frame 0 of a clip. SAM2's streaming memory propagates masks forward through fast occlusion and overlapping bodies — exactly the failure modes that defeat simpler trackers.
- Input
- RGB frames
- Output
- per-frame binary mask
- Prompt
- 1 click / fighter
Annotate
38 five-frame windows hand-verified over one weekend at a kitchen table. Balanced 19 / 19. Without ML-assisted propagation (premium-only), every mask had to be inspected manually — the bottleneck is the eye, not the GPU.
- Windows
- 38 (19 strike · 19 neutral)
- Frames each
- 5 @ 30 fps
- Split
- 30 / 6 / 2
Classify
Temporal Segment Network. Each of the 5 frames passes through a shared ResNet-50; features are averaged into a single 2048-d vector; a fresh FC head produces the binary softmax. Backbone frozen, head fine-tuned.
- Pretrain
- tsn_r50_1x1x3_100e_k400
- Epochs
- 20
- Train time
- < 1 min · 1 GPU


The plot below is the model's softmax output, window-by-window, on the Knockdown clip. Hover anywhere to read the underlying numbers.
hover to scrub
Three failure modes recur across the test clips. They are not bugs; they are the load-bearing limits of a 38-sample classifier with no temporal context between windows.
Clinch break fires as strike
P(strike) = 0.82Sudden separation looks like a recoiling punch. The pretrain has no concept of clinch.
Slipped jab missed
P(strike) = 0.33Defender slips outside; striker's arm extends fully but never makes a connecting motion. The model splits the difference.
Strike crosses window boundary
P(strike) = 0.48 / 0.82Wind-up sits in w14, contact in w15. Non-overlapping windows fragment the action; only w15 fires.
The 0.83 number is a ceiling against the 38-window dataset, not against the task. The path from here is unglamorous: more annotation. Even 200–500 windows would be transformative. With ML-assisted propagation the same weekend yields ~10× the data.
Beyond volume, three structural changes are queued. Multi-class labels (jab, cross, hook, kick, elbow, knee, takedown) make the output useful for fight analytics, not just highlight detection. Overlapping sliding windows recover strikes that currently fragment across the boundary at w14/w15. Multi-fight, multi-fightertraining breaks the implicit overfit to Topuria's stance and Holloway's volume.
The annotation format is intentionally simple. Each line in train.txtis a folder, a frame count, and an integer class label — readable by MMAction2's RawframeDataset out of the box.
# annotations/train.txt — MMAction2 RawframeDataset format # <folder> <n_frames> <class_label> (0 = neutral, 1 = strike) strike/0 5 1 strike/1 5 1 neutral/0 5 0 neutral/1 5 0 ... # inference (per clip) from mmaction.apis import inference_recognizer, init_recognizer model = init_recognizer('configs/tsn_r50_strike.py', 'work_dirs/best.pth') result = inference_recognizer(model, 'window_w20.mp4') # result.pred_score → tensor([0.04, 0.96]) → STRIKE @ 0.96
| Setting | Value | Notes |
|---|---|---|
| backbone | ResNet-50 | ImageNet → Kinetics-400 → frozen during fine-tune |
| checkpoint | tsn_r50_1x1x3_100e_kinetics400_rgb | OpenMMLab model zoo |
| input | 5 × 224 × 224 RGB | center crop · ImageNet norm |
| head | FC 2048 → 2 · softmax | only learnable parameters |
| optimizer | SGD · lr 0.001 · momentum 0.9 | linear warmup · cosine decay |
| epochs | 20 | val acc converged by epoch 9 |
| data format | RawframeDataset | JPEG sequences, not video files |
The model, running on three held-out clips.
Three confident detections in 6.7s. Peaks at w3 (0.93), w20 (0.96), w35 (0.85) align with the visible left hook → follow-up → referee stoppage.
Run the pipeline on a clip you choose.
MP4 · ≤ 3 seconds · ≤ 10 MB. Frames are processed in five-frame windows; the page reports per-window confidence in line with the evaluation above.