Volleyball person + ball detector (D-FINE-S fine-tune)
A two-class real-time object detector for volleyball footage: person and ball.
It is a fine-tune of D-FINE-S (10 M parameters, Apache-2.0)
trained on frames from youth club matches filmed with a wide-angle camera mounted on the referee stand, plus beach
and grass sessions from the same camera rig.
The model was built to power automatic highlight detection in a volleyball recording app: it finds every person in the frame and the ball, and the app decides who is a player from the court region.
What it detects
| class | what it means |
|---|---|
| person | any person: players, referees, coaches, spectators |
| ball | a volleyball, in the air or on the ground |
It does not distinguish players from spectators. Filter by court region downstream if you need players only.
Intended use and tiling
The ball is small: in 4K footage from a ref-stand camera it is often 20 to 40 pixels across. The model was trained on 640×640 tiles cut from 720 to 1040 px windows of 4K frames, so it should be run the same way:
- For 1080p or smaller images, run on the whole image.
- For 4K frames, slice into ~860 px tiles with 25 % overlap, run each tile, add one full-frame pass, then merge with NMS.
predict.py in this repo does exactly that.
from transformers import AutoImageProcessor, AutoModelForObjectDetection
from PIL import Image
import torch
repo = "grdesignbuild/volleyball-person-ball-detector"
processor = AutoImageProcessor.from_pretrained(repo)
model = AutoModelForObjectDetection.from_pretrained(repo).eval()
img = Image.open("frame.jpg").convert("RGB") # a tile or a small image
inputs = processor(images=img, return_tensors="pt")
with torch.no_grad():
out = model(**inputs)
res = processor.post_process_object_detection(out, threshold=0.4, target_sizes=[img.size[::-1]])[0]
for score, label, box in zip(res["scores"], res["labels"], res["boxes"]):
print(model.config.id2label[label.item()], round(score.item(), 2), [round(v) for v in box.tolist()])
Training data
- Source: 37 recordings (about 880 minutes) from one club season: indoor multi-court convention halls (AAU-style tournaments), a local gym, beach courts and grass courts. 4K at 30 or 60 fps, one 1080p sideline video.
- Frames: 3,061 keyframes sampled evenly across each recording, skipping warm-ups — 2,940 from the ref-stand rig plus, new in this release, 121 from an end-line camera (behind the baseline, 2000×1124 H.264) so the model sees players close and large and the ball from behind the court.
- Labels: pseudo-labels from D-FINE-X Objects365
run on overlapping tiles of each frame (classes
PersonandVolleyball), with every accepted ball box on the ref-stand frames reviewed by hand (4,077 of 6,086: 910 removed as ceiling lights, coolers, cups, bags and logos; 456 blurred out as ignore regions) and every accepted ball box on the end-line frames reviewed by hand (136: 77 were three ceiling-light fixtures repeated across frames, 44 real balls kept, 15 far-court specks blurred out). Person boxes are the teacher's, unreviewed. Tiles with people but no ball are sampled at 55% so shoes, knee pads and hair are seen as background often; end-line tiles are repeated three times so they are not drowned by the ref-stand ones. - Split by recording: 31,023 training records (30,043 tiles, end-line ones ×3), 4,181 validation tiles from 5 held-out ref-stand recordings covering each venue type.
- The training images contain minors and are not published.
Evaluation
Validation is against the teacher's pseudo-labels on held-out ref-stand recordings, with the ball-label cleaning applied to the validation side. These numbers are comparable to the 2026-09-08 release (same validation labels), not to the first release (0.759 mAP@0.5 against uncleaned labels).
| metric | value |
|---|---|
| mAP@0.5 | 0.776 |
| mAP@0.5:0.95 | 0.611 |
| AP@0.5:0.95 person | 0.674 |
| AP@0.5:0.95 ball | 0.547 |
| mAP small objects | 0.427 |
Training: fine-tuned from the 2026-09-08 release for 1 epoch, batch 8, AdamW (lr 5e-5, backbone 5e-6), cosine schedule, on an Apple M5 GPU.
Held-out match footage, ref-stand camera (24 frames, 4K, no release trained on them)
| release | ball claims | of which real balls | person detections |
|---|---|---|---|
| 2026-09-03 | 74 | 7 | 1,950 |
| 2026-09-08 | 7 | 7 | 1,975 |
| 2026-09-08 end-line (this one) | 7 | 7 | 2,083 |
End-line camera: nine hand-labelled contact frames, 640 px tiles
| threshold | 2026-09-08 real balls / light-fixture hits | this release |
|---|---|---|
| 0.40 | 4 / 8 | 4 / 5 |
| 0.50 | 3 / 5 | 4 / 0 |
| 0.60 | 1 / 0 | 3 / 0 |
Every real ball scores higher than under the previous release; the ceiling-light false positive on that venue's pillar disappears above 0.47. Run this model with tiles of ~640 px on frames around 2000 px wide, not the 4K recipe above.
Limitations
- Trained on one camera rig and one age group; expect weaker results on broadcast TV angles.
- Balls on adjacent courts and balls in ball carts are detected too. That is correct behaviour, but you may need to filter.
- Fast-moving, heavily motion-blurred balls are the most common miss, and since the 2026-09-08 release the model is a little more conservative at the exact instant a blurred ball meets a player's hands — the trade for the false-positive reduction above.
- Shadows and mannequin-like objects occasionally score as
personat low confidence; a 0.4 threshold is a good default.
Version history
- 2026-09-08, end-line release — 121 end-line frames added (ball labels hand-cleaned), end-line tiles oversampled ×3, 1-epoch fine-tune from the 2026-09-08 release. Finds the ball from an end-line camera where the previous release mostly found a ceiling light; ref-stand behaviour unchanged.
- 2026-09-08 — ball labels hand-cleaned, background sampling raised, 1-epoch fine-tune from the first release. 91% fewer false ball claims on held-out match footage; person detection unchanged.
- 2026-09-03 — first release, teacher pseudo-labels only.
License
Apache-2.0, same as the base model. Built with 🤗 Transformers.