Automated archery scoring using computer vision and YOLO26 object detection.
This project transitions archery scoring from manual logging to a high-precision, automated pipeline. It captures and analyzes target face images to detect arrow placements and calculate scores in real-time, bridging traditional sport with modern data analytics for both high-volume training sessions and professional tournaments.
For full methodology, architecture details, and complete breakdowns → read the blog post.
- Project Vision and Scope
- Data Engineering and Methodology
- Model Architecture: YOLO26
- Training Hyperparameters
- Performance Results and Analysis
- Scoring Logic and Integration Pipeline
- Limitations and Known Issues
The core objective is automation of the scoring pipeline. Traditionally, archers must walk to the target and manually record scores, a process prone to human error and fatigue. This system allows users to simply photograph the target; the AI identifies the arrows and computes their values instantly.
The MVP focuses on World Archery (WA) outdoor target faces (122cm and 80cm) using the standard 10-zone scoring system. However, the architectural logic is target-agnostic: by identifying universal structural markers rather than predicting static scores, the system is designed for seamless adaptation to:
- Indoor Archery: Triple-spot or 40cm faces
- Custom Targets: Proprietary club or training faces
I aggregated a raw pool of 3,700 images from four specialized repositories on Roboflow:
archery-skmsa/archeryuni-oidi4/archeryarchery-scoring/Archery Scoringshrutiharshil/DJS Phoenix
After a rigorous filtering phase (discarding low-resolution frames, redundant images, and poorly labeled samples) I produced a refined set of 250 high-variance images for fine-tuning.
Several classes (notably arrow_shaft) were originally labeled as polygons (segmentation) rather than bounding boxes. A custom Python script converts these by calculating the extreme polygon coordinates
Applied via Roboflow, expanding the training set to 525 images (70/20/10 split):
| Augmentation | Value | Purpose |
|---|---|---|
| Horizontal Flip | 50% probability | Handles different approach angles |
| Brightness | +/-25% | Simulates outdoor lighting variation |
| Blur and Noise | Applied | Mimics low-quality sensors / motion blur |
| Class | Role |
|---|---|
arrow_shaft |
Identifies arrow body; assists in distinguishing arrows from target lines |
arrow_tip |
Primary scoring key -- precise point of impact |
target_center |
Defines the absolute origin (0,0) of the target coordinate system |
target_face |
4-point detection for perspective correction |
Class Distribution:
| Class | Count |
|---|---|
| arrow_shaft | 2,742 |
| arrow_tip | 2,577 |
| target_center | 519 |
| target_face | 519 |
Dataset hosted on Roboflow under archeryxpert-score-detection.
This project uses YOLO26, the state-of-the-art in the YOLO family as of early 2026.
| Feature | Detail |
|---|---|
| NMS-Free | Direct end-to-end prediction -- no Non-Maximum Suppression latency |
| CPU Optimization | Nano variant (YOLO26n) shows ~43% speed increase over YOLO11 on mobile CPUs |
| MuSGD Optimizer | Muon-style optimization (adapted from LLMs) for stable convergence on small datasets |
/images -- image files
/labels -- paired .txt files: class_id | cx | cy | w | h
data.yaml -- paths + class names: ['arrow_shaft', 'arrow_tip', 'target_center', 'target_face']
epochs: 100
imgsz: 640
batch: 16
optimizer: AdamW
lr0: 0.001
lrf: 0.01
warmup_epochs: 3
weight_decay: 0.0005
hsv_h: 0.015
hsv_s: 0.7
hsv_v: 0.4
translate: 0.1
scale: 0.5
fliplr: 0.5
mosaic: 1.0
degrees: 0.0
dropout: 0.1
patience: 20
device: 0
workers: 2
cache: true- AdamW over SGD: Better for small datasets; adapts learning rate per parameter automatically.
scale=0.5: Critical for archery -- archers photograph from varying distances, so an arrow tip can change dramatically in pixel size.degrees=0.0: Rotation disabled because axis-aligned bounding boxes would become inaccurate for rotated objects.mosaic=1.0: Combines 4 images into a 2x2 grid per training step, effectively quadrupling training variety.patience=20: Early stopping prevents overfitting; training may stop well before epoch 100.
YOLO26s summary (fused): 122 layers, 9,466,728 parameters, 0 gradients, 20.5 GFLOPs
Speed: 0.3ms preprocess, 5.3ms inference, 0.0ms loss, 1.0ms postprocess per image
| Metric | YOLO26s | YOLOv12s |
|---|---|---|
| Overall mAP50 | 0.913 | 0.924 |
| Overall mAP50-95 | 0.698 | 0.704 |
| Precision | 0.910 | 0.911 |
| Recall | 0.871 | 0.890 |
Per-Class mAP50:
| Class | YOLO26s | YOLOv12s |
|---|---|---|
| arrow_shaft | 0.914 | 0.948 |
| arrow_tip | 0.776 | 0.785 |
| target_center | 0.975 | 0.972 |
| target_face | 0.989 | 0.993 |
| Metric | YOLO26s | YOLOv12s |
|---|---|---|
| Overall mAP50 | 0.875 | 0.872 |
| Overall mAP50-95 | 0.653 | 0.659 |
| Precision | 0.878 | 0.879 |
| Recall | 0.844 | 0.862 |
Per-Class mAP50:
| Class | YOLO26s | YOLOv12s |
|---|---|---|
| arrow_shaft | 0.909 | 0.901 |
| arrow_tip | 0.673 | 0.608 |
| target_center | 0.951 | 0.985 |
| target_face | 0.969 | 0.992 |
| Factor | YOLO26s | YOLOv12s | Winner |
|---|---|---|---|
| Val->Test mAP50 gap | 0.038 | 0.053 | YOLO26s (less overfitting) |
| Arrow tip mAP50 (test) | 0.673 | 0.608 | YOLO26s |
| Target center mAP50 (test) | 0.951 | 0.985 | YOLOv12s |
| Total inference time | 6.3ms | 13.4ms | YOLO26s |
Arrow tip detection is the critical failure mode for scoring accuracy. YOLO26s outperforms YOLOv12s on this class by +6.5 mAP50 points and is approximately 2x faster at inference. It was selected for production.
YOLO26s: 9/25 test images had missed arrow tips
YOLOv12s: 7/25 test images had missed arrow tips
Worst cases:
| GT Arrows | Detected | Missed |
|---|---|---|
| 5 | 1 | 4 |
| 6 | 4 | 2 |
| 6 | 4 | 2 |
The primary failure mode is dense clustering in the gold zone (5+ arrows). Images with fewer than 4 arrows showed near-perfect detection.
Image -> Perspective Correction -> YOLO Detection -> Geometry Resolution -> Score Calculation -> Visualization
archery/
├── models/
│ └── best.pt
├── src/
│ ├── visualize_inference.py # drawing results for single inference
│ ├── detection.py # YOLO inference
│ ├── perspective.py # HSV + ellipse + affine warp
│ ├── geometry.py # distance calc + score lookup
│ └── visualize.py # drawing results
├── main.py # entry point
├── labeling.py # auto-label new images using model
└── config.py # all constants and paths
The entire pipeline operates in pixel coordinates. Every module produces and consumes pixel-space values, eliminating the normalization mismatch common when mixing YOLO's 0-1 normalized outputs with pixel-space geometry.
Photographs are rarely taken head-on. Camera tilt causes the circular target to appear as an ellipse, compressing radial distances along the tilt axis. This stage warps the image to restore a circular target and geometrically accurate distances.
HSV Red Zone Isolation
The red scoring zone (rings 7-8) is isolated using two HSV hue ranges (red wraps the hue cylinder):
RED_HSV_LOWER_1 = (0, 80, 80) # hue 0-10 degrees
RED_HSV_UPPER_1 = (10, 255, 255)
RED_HSV_LOWER_2 = (170, 80, 80) # hue 170-180 degrees
RED_HSV_UPPER_2 = (180, 255, 255)Morphological Cleanup: MORPH_CLOSE fills holes from arrow shafts crossing the red zone; MORPH_OPEN removes stray exterior pixels. Order matters: close first, then open.
Ellipse Fitting: The largest contour is selected and fitted via cv2.fitEllipse, returning center
Affine Warp: Three point correspondences map the detected ellipse to a circle using the parametric ellipse boundary:
Extrema are computed via:
| Point | Source | Destination |
|---|---|---|
| Top | Ellipse top extremum | |
| Right | Ellipse right extremum | |
| Bottom | Ellipse bottom extremum |
Full Target Radius: The red zone covers 40% of the full target radius on WA 122cm targets:
The model runs on the corrected image with conf=0.25, iou=0.45, max_det=20. All coordinates use box.xywh (pixel space). For target_face and target_center, only the highest-confidence detection is kept.
Center and radius are resolved by fusing the HSV-based and YOLO-based sources:
Center priority: YOLO target_center -> ellipse center -> YOLO target_face center
Radius priority: Ellipse-based (scaled by RED_ZONE_RATIO) -> YOLO target_face width / 2
A cross-validation check warns if the two center sources diverge by more than 30 pixels.
Tip Refinement: Each tip is refined using its nearest shaft (within 50px). A weighted blend uses detection confidence as the weight:
Distance Ratio:
WA 122cm Ring Table:
| Ring | Outer Edge Ratio | Score |
|---|---|---|
| X | 0.05 | 10 |
| 10 | 0.10 | 10 |
| 9 | 0.20 | 9 |
| 8 | 0.30 | 8 |
| 7 | 0.40 | 7 |
| 6 | 0.50 | 6 |
| 5 | 0.60 | 5 |
| 4 | 0.70 | 4 |
| 3 | 0.80 | 3 |
| 2 | 0.90 | 2 |
| 1 | 1.00 | 1 |
| Miss | > 1.0 | 0 |
Boundary Detection: Arrows within +/-0.015 of any ring edge are flagged is_boundary: true and highlighted in visualization for manual verification.
- Concentric ring overlays aligned to the resolved center
- Arrow shaft bounding boxes (orange)
- Scored tip markers colored by zone (gold -> red -> blue -> black -> white -> gray)
- Cyan border on boundary-flagged arrows
{
'image': 'filename.jpg',
'arrows':[
{'score': 10, 'ratio': 0.031, 'angle': 45.2, 'is_boundary': False,
'x': 412.3, 'y': 438.1, 'confidence': 0.87},
...
],
'total': 58,
'arrow_count': 6,
'boundaries': [3] # indices of arrows near ring edges
}- Dense clustering: Arrow tip detection degrades significantly when 5+ arrows cluster in the high-scoring zones.
- Localization precision: mAP50-95 of 19.5% on arrow tips is insufficient for tournament-grade scoring without user confirmation on boundary arrows.
- Dataset size: ~500 training images. Performance is expected to improve substantially with more diverse data, particularly close-up images of clustered arrows.
- Perspective correction fragility: Small radius estimation errors from the red-zone ellipse propagate outward through each ring. The pipeline compensates by preferring the YOLO-detected
target_centerover the ellipse center, but full homography-based correction remains an open problem.





