Skip to content

Latest commit

 

History

74 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RimPlane

Ball tracking, shot outcomes and metre-accurate shot charts from a single camera.

RimPlane turns an ordinary video recording of a basketball training session into per-player shooting analytics: who took the shot, where they took it from in real court metres, whether it went in, and how that breaks down across thirteen zones of the floor.

No sensors. No multi-camera rig. No instrumented ball or rim. One camera on a tripod at the side of the hall, and a short calibration the coach performs once per session.

The system is built and refined against real training sessions at grassroots basketball clubs in London — sessions with four to six players on the floor and several basketballs live at the same time.


Table of Contents


The Problem

Basketball analytics is a solved problem at the professional level and an unsolved one everywhere else.

Elite systems work, but they work by throwing hardware at the problem: synchronised multi-camera arrays mounted in the roof, calibrated optical rigs, sensor-embedded balls, or wearable tracking pods. That approach assumes a permanent, single-purpose venue and a budget to match. A grassroots club training in a hired sports hall on a Tuesday evening has neither.

Meanwhile, most accessible basketball computer-vision projects quietly solve a much easier problem than the one clubs actually have. They assume one player and one ball, cleanly visible, on a clean, correctly marked court. A real training session breaks every one of those assumptions at once:

  • Four to six players on the floor simultaneously, crossing paths and occluding each other
  • Several basketballs live at the same time — one player shooting while another dribbles and a third rebounds
  • UK sports halls that share floor space with netball, badminton and five-a-side, so the basketball markings are one set of lines among many overlapping sets
  • Multiple hoops visible inside a single camera frame, only one of which is the target
  • Mixed ceiling lighting, shadow, and motion blur on a fast-moving orange ball

RimPlane is built specifically for that environment. Every design decision in the system — the coloured-bib identity cue, the rim-aware tracking gate, the dual-signal shot verdict, the line-based court calibration — exists because a simpler approach was tried against real footage and failed on one of the conditions above.


What the System Produces

For each registered player in a session:

Output Description
Shot log Every attempt, attributed to a player, with the court position it was taken from in metres
Make / miss verdict Outcome per attempt, decided by two independent signals rather than one
Zone breakdown Makes, attempts and percentage across 13 court zones (paint, mid-range, three-point — each split corner / wing / top)
Shot chart A rendered half-court chart with per-zone heat colouring and individual shot markers
Session totals Total attempts, total makes, overall field-goal percentage
Annotated video The source footage with detections, track identities, possession and shot verdicts drawn on

Demo Video

A full end-to-end walkthrough of the system running on a real training session — calibration, live detection and tracking, shot detection, make/miss classification, and the generated shot charts:

▶️ https://www.youtube.com/watch?v=tfaVmRRfAWo

The video is the best single reference for how the system behaves in practice, because the static images below can only show one moment each. It covers the complete session lifecycle: the coach performing the one-time calibration through the OpenCV window, the processing run with overlays live, and the analytics output at the end.


Detection and Tracking

Detection Example

What you are looking at. This is a single unmodified frame from a real training session, with every one of the system's internal states drawn on top of it. It is worth reading carefully, because almost every subsystem in RimPlane is visible in this one image.

Top-left: Frame 4007/18284. The session is a continuous, un-cut recording of just over 18,000 frames. Nothing has been trimmed to a favourable clip — the system processes the full session including warm-up, standing around, and rebounding.

Green boxes with names — the players. Four players are tracked simultaneously: ayse, faridtolu, iman and one further player at the right. Each carries a name registered at the start of the session and a live state label. TRACKED means the player was matched to a detection in this frame.

The small blue dot at each player's feet. This is the single most important marker in the frame and the easiest one to miss. It is the player's floor-contact point — the pixel that gets pushed through the court homography to produce a real position in metres. It sits at the feet rather than at the centre of the box on purpose: a planar homography is only exact for points that actually lie on the floor plane, so measuring from the box centre would place every player metres away from where they are standing. Every court coordinate and every shot-chart position in the system traces back to this dot.

Yellow and green boxes with BALL nn — the basketballs. Three separate basketballs are being tracked at once here, each with a persistent numeric identity (ID 25, ID 26, ID 27). The colour encodes track confidence state:

  • CANDIDATE (yellow) — a new track that has been seen too briefly to be trusted yet. It has not earned the right to generate a shot attempt.
  • CONFIRMED (green) — a track that has survived enough consecutive frames to be treated as a real basketball.

This two-stage promotion is a deliberate defence. On a busy court the expensive failure is not losing a ball, it is inventing one: a phantom track from a duplicate detection would be treated downstream as a real basketball and could manufacture a shot attempt that never happened, silently corrupting a coach's shot chart.

shooter:iman / shooter:hanna. Confirmed ball tracks carry an attribution label binding the ball in flight to the player who released it. This is what makes the analytics per-player rather than merely per-session.

The magenta box on the rim. This is the net region of interest — a fixed rectangle around the net, established once during calibration, inside which the system measures net movement. Its role is explained in the make/miss section below.

Note the second hoop at the far left of the frame, and the floor. Both are the reason this system does manual calibration rather than automatic detection. There are multiple hoops in view, so automatic rim detection cannot know which one is the target. And the floor carries several overlapping sets of painted markings from other sports — court-detection methods trained on clean basketball courts fail outright in a shared UK sports hall.


The Ball Detection Model

A generic off-the-shelf object detector was tried first and was not good enough. A basketball in real gym footage is small, fast, frequently motion-blurred to an orange smear, routinely occluded by hands and bodies, and — critically — easily confused with other orange objects in a sports hall. The detector was therefore trained specifically for this problem.

Dataset

  • 1,550 images, personally collected and manually labelled
  • Drawn from real gym conditions: varied ceiling lighting, different player densities, partial occlusion, and changing ball visibility
  • Built through an iterative pipeline — collect → label → train → evaluate → refine — over multiple cycles, with new examples targeted at the model's remaining failure cases after each round rather than added at random
  • Train / validation split: 80 / 20

Model

  • Architecture: YOLOv8n (Ultralytics), fine-tuned from the base weights
  • Deliberately the smallest model in the family — 73 fused layers, ~3.0M parameters, 8.1 GFLOPs — so the system runs on ordinary hardware a club already owns rather than requiring a dedicated GPU box

Validation results — held-out set of 310 images containing 474 labelled ball instances:

Metric Value
Precision 0.90
Recall 0.895
mAP@0.5 0.925
mAP@0.5:0.95 0.434

Basketball Detection Model Performance

Reading this output. This is the raw Ultralytics validation summary, shown unedited rather than transcribed into a prettier table, so the numbers can be checked against their source. The first line is the fused model summary — 73 layers, 3,005,843 parameters, 0 gradients, 8.1 GFLOPs — confirming this is the lightweight nano architecture with training-time gradients discarded for inference. The second line is the per-class result row: class all, 310 validation images, 474 ball instances, then precision 0.9, recall 0.895, mAP50 0.925 and mAP50-95 0.434.

On the gap between mAP@0.5 and mAP@0.5:0.95. It is worth addressing directly rather than leaving a reader to wonder. mAP@0.5 of 0.925 says the model reliably finds the ball. mAP@0.5:0.95 of 0.434 says the boxes it draws are not pixel-tight at strict IoU thresholds. That gap is expected and largely irreducible for a small, fast, motion-blurred object: when the ball smears across several pixels of travel in one frame, the "correct" box is genuinely ambiguous. It also matters far less here than it would elsewhere, because everything downstream consumes the centre of the box, not its edges — tracking, rim-relative distance and shot location all depend on centroid accuracy, which degrades much more gracefully than box tightness.

These figures come from real, varied training footage, not from a clean benchmark set.


Shot Outcome: Dual-Signal Make/Miss

Net Motion Demo

Why this is the hardest part of the system. A single camera has no depth. From the side of a hall, a ball that passes cleanly through the hoop and a ball that sails a metre in front of it or behind it can trace nearly identical paths across the image. Any classifier built purely on where the ball appears relative to the rim will therefore call some misses as makes — and during shooting drills, where attempts come in rapid succession and rebounds fly back through the same region, those false positives accumulate fast.

RimPlane resolves this by requiring two independent signals to agree:

  1. Rim-relative trajectory. The system records the last position at which the ball was seen above the rim line and the first position at which it was seen below it, and sums those two distances from the rim centre. A ball that truly passed through the hoop is close to the rim centre on both sides of the crossing; a ball that missed is not.

  2. Net motion. After the ball crosses below the rim line, the system measures pixel-level movement inside the net region — the magenta box — using frame differencing. A ball that went through the hoop physically whips the net; a ball that missed does not.

A shot is classified MAKE only when both conditions hold.

The two signals are chosen specifically because they fail in different ways. The trajectory signal is fooled by depth ambiguity. The net signal is not, because a ball a metre in front of the hoop cannot move the net. Requiring agreement means a false positive needs both to fail simultaneously, which is far rarer than either failing alone.

One detail makes the second signal real rather than decorative: the ball's own bounding box is masked out of the net region before motion is measured. The ball is the largest, highest-contrast moving object in that box. Without masking it, the ball's mere presence would generate the "the net moved" evidence, the second signal would collapse into a restatement of the first, and the whole dual-check would be theatre.

Reading the demo clip

The clip above is a real made shot, frame by frame, with the internal state drawn on. Watching the labels change is the clearest explanation of how the classifier works:

  • BALL 38 | CONFIRMED | shooter:ayse — the ball is a trusted track, in flight, and already attributed to the player who released it. Attribution happens at release, not at the rim, so a rebound cannot steal credit for the shot.
  • BALL 38 | NET CHECK | ayse | 4 — the ball has crossed below the rim line and the classifier has latched into its verification window. The trailing number is a live countdown of frames remaining before the verdict is issued. Net movement is measured on every frame of this window and the system keeps the peak value, not the average: net whip is a brief impulse, and averaging it across the window would dilute a real spike below the detection threshold.
  • BALL 38 | owner:None — after the verdict, the ball is released back to the unowned pool, free to be picked up by whoever rebounds it.
  • BALL 40 | owner:iman — meanwhile, an entirely separate basketball is being tracked and possessed by a different player, throughout. The shot pipeline is not a single-ball state machine.

The player labels tell the parallel story of the tracker under stress. Watch them cycle through TRACKED (green — matched to a detection this frame), PREDICTED (yellow — detection lost, position being carried forward by the motion model) and LOST (red — track has aged out). Players drop into PREDICTED exactly when they cross behind one another, and recover their original identity on the other side. That recovery is what the coloured bibs buy: colour gives the tracker an identity cue that survives occlusion, so farida comes back as farida rather than as a new anonymous track.

This behaviour has been field-tested across real training sessions in London.


Court Mapping and Spatial Analytics

Every spatial number the system reports depends on one thing: converting a pixel in the camera view into a real position on the court, in metres. RimPlane does this with a four-point planar homography.

The four reference correspondences are not gathered the obvious way, and the reason is the most instructive piece of engineering in the project.

The four points that best condition a homography are the ones spread widest across the floor — which, on a half-court, means the two lane elbows near the camera and the two far baseline corners. But those far corners are precisely the worst points in the frame to ask a human to click: distant, foreshortened, blurred, often behind a wall pad or a player, and frequently outside the frame entirely. A click there carries several pixels of error, and the homography amplifies that error across the entire court.

So the system asks for the measurement a person can make accurately, and derives the one they cannot:

  • The two lane elbows are clicked directly — they are near the camera, sharply defined as the intersection of two painted lines, and have exact known court coordinates.
  • The two baseline corners are never clicked. The operator instead draws three long lines — left sideline, right sideline, baseline — along clearly visible, well-resolved stretches of floor. The system then computes each corner analytically, as the intersection of two of those lines.

Two clicks placed anywhere along a long painted line produce a tiny angular error, so intersecting two such lines yields a far more precise corner than clicking it ever could — and, unlike a mouse click, a computed intersection is allowed to land outside the image.

Supporting decisions in the same module:

  • Near-parallel line pairs are rejected outright rather than divided through. An ill-conditioned intersection flies off toward infinity and produces a homography that maps the whole session to garbage coordinates which still look like plausible numbers — the worst kind of failure, because nothing crashes.
  • The derived corners are drawn live before the calibration is accepted, so a non-technical operator can visually confirm the two computed points landed on the real floor corners, and redo the lines if not.
  • Drawn lines are extended to the frame borders in the preview, so the operator can check the line stays glued to the painted court marking along its whole length. In a hall crowded with netball and badminton lines, this is what stops someone calibrating against the wrong painted line.
  • Clicks are stored in native sensor coordinates, not display coordinates. The preview is downscaled to fit the screen, and every mouse event is converted back out before storage. Skipping this produces a pure scale mismatch between calibration and detection — a plausible-looking but systematically wrong court map, and one of the hardest bugs in this domain to notice.

Once solved, the homography converts any floor-plane pixel to court metres. Shot positions are then classified into 13 zones — paint, mid-range and three-point, each split into corner, wing and top sectors — and aggregated per player.


Shot Charts

Shot Chart Example

What this chart shows. This is real output from a real session: 105 shot attempts by a single player, 59 made, 56.2% from the field — every one of them detected, located, attributed and classified automatically from a single camera recording, with no manual tagging.

Reading the chart:

  • Axes are in real metres, not pixels. The origin sits at the hoop, the floor spans roughly −7.5 m to +7.5 m across and out to 12.4 m from the basket. This is the homography output, and it is what makes the chart comparable across sessions, camera positions and venues.
  • Each zone label reads made/attempts and a percentage. In this session: 73% from the free-throw area (11/15), 80% from the right of the paint (8/10), 70% from the left (7/10), tailing off to 47% and 53% from the mid-range wings and 27% from the left mid-range corner.
  • Zone fill colour is a performance heat map, banded so a coach can read it at a glance without reading a single number: red below 30%, orange below 45%, yellow below 65%, green at 65% and above.
  • Zones with no attempts are filled a neutral grey, deliberately distinct from red. "Nobody shot from here" and "everybody missed from here" are completely different coaching problems, and the chart must never confuse them. In this session every three-point zone is grey — this was an inside-shooting drill, and the chart says so honestly rather than showing five alarming red zones.
  • Individual shots are plotted as markers on top of the heat map — circles for makes, crosses for misses — so the underlying distribution stays visible. The tight clusters here are exactly what you would expect from a structured drill: repeated attempts from a handful of spots rather than shots scattered at random.

Why this is actionable rather than decorative. The clustering pattern shows the player is drilling specific spots. The colour pattern shows the drill is working close to the basket and degrading with distance and toward the left. That left-side weakness — 27% from the left mid-range corner against 40% from the right — is a concrete, measured asymmetry a coach can build next week's session around, and it is the kind of pattern that is essentially invisible to unaided observation across 105 repetitions.

The chart is generated from exactly the same geometric constants the classifier uses to assign zones, so the boundaries a coach sees drawn can never drift out of agreement with the statistics printed on them.


Session Setup

Before processing, the coach performs a one-time interactive calibration through an OpenCV window. It takes a couple of minutes and requires no technical knowledge.

Step 1 — Player Registration

The number of players is specified and each player is named. The operator then draws a box around each player on the first frame, giving each track its initial position.

Step 2 — Bib Colour Reference

For each player, the operator navigates to a frame where that player is clearly visible (N / B jump forward and back by 50 frames) and draws a box around their bib.

The system builds an HSV colour reference from that region and uses it to re-identify the player throughout the session. Coloured training bibs are standard kit at most clubs and inexpensive where they are not, and using bib colour as an identity cue makes multi-player tracking dramatically more robust — it is what lets a track survive players crossing paths and occluding one another.

Step 3 — Rim Calibration

Two clicks on the target rim define the rim line; its midpoint becomes the rim centre, the spatial anchor for shot detection and outcome classification.

This is manual because UK sports halls commonly have several hoops visible in one frame, and automatic rim detection cannot know which one the session is using.

Step 4 — Net Region

One click sets the net height. The system computes the net box from that click combined with the rim centre and rim line already established. This region is used only for net-motion analysis, and the ball's own box is dynamically excluded from it during measurement.

Manual for the same reason as the rim: with multiple hoops in frame, automatic net detection cannot isolate the correct one from a single view.

Step 5 — Court Homography

Five inputs: 2 clicks (left and right lane elbows) and 3 drawn lines (left sideline, right sideline, baseline). The baseline corners are computed from the line intersections as described above, and the four resulting correspondences are solved into the homography.


Engineering Capability Demonstrated

This section is for readers evaluating the engineering rather than the product. Each item below is a specific, checkable decision in the published code, chosen because the obvious alternative is subtly wrong.

Multi-object data association

Ball tracking does not match per-track. It builds a flat list of every admissible (cost, track, detection) triple across the whole frame, sorts it globally, and consumes it greedily with mutual-exclusion sets — a cheap, dependency-free approximation to Hungarian assignment on a sparse cost matrix.

Why it matters: the naive alternative — loop over tracks, each grabs its nearest detection — is exactly what produces identity swaps when two basketballs pass close together. Whichever track happens to be iterated first steals a detection that belonged more strongly to another, and the result changes if you reorder the list. Sorting globally commits the most confident pairing in the frame first and resolves ambiguity against what remains.

Motion modelling with a deliberate fallback hypothesis

Each track is predicted forward by constant velocity, but candidates are scored against two hypotheses at once — 60% weight on the predicted position, 40% on the last known position — and gated on whether either is within range.

Why it matters: prediction-only gating fails precisely when constant velocity breaks — a rim bounce, a floor bounce, a catch, a release. Those are the highest-value frames in the entire system. Keeping a last-known-position term means the tracker degrades gracefully into plain nearest-neighbour at impact instead of losing the ball. It is also a considered refusal to reach for a Kalman filter: for a ballistic object at 30 fps with a reliable detector, a two-state predictor delivers most of the benefit with no covariance matrices to retune at every new venue.

Context-adaptive gating

The association radius is not uniform across the image. Inside the rim neighbourhood it widens by 2.2×.

Why it matters: the gate radius is the single knob trading identity swaps against track fragmentation, and its optimum is not constant. At the rim the ball has its largest per-frame displacement and its worst occlusion, so a globally tight gate would fragment tracks during exactly the frames the make/miss verdict depends on. A globally loose gate would fix that but license the tracker to jump between different basketballs everywhere else on a busy court. Making the radius state-dependent buys robustness only where it is needed.

Circular statistics for an angular quantity

Bib hue is never averaged arithmetically. Hue values are mapped onto the unit circle, sine and cosine are averaged independently, and the mean direction is recovered by arctan2. The same wrap-aware distance is used on the scoring side.

Why it matters: hue is an angle, not a scalar, and it wraps. Averaging hues of 175 and 5 arithmetically gives 90 — which in OpenCV's scale is cyan, the near-opposite of the actual colour. The reference becomes the inverse of the bib and that player's re-identification is broken for the whole session. This is the most common defect in HSV colour-ID code, and it bites hardest here: red and orange are the two most common bib colours and both sit on the wrap point. Many implementations fix this in the reference and forget it in the distance metric; both are handled here.

Chromaticity gating before measurement

The colour mask thresholds on saturation and value only — never on hue — then conditions the binary mask with an open followed by a close.

Why it matters: hue is numerically meaningless where saturation or value is low. A shadowed pixel and a blown-out highlight both carry a hue number that is pure noise, and under mixed gym lighting those are a large fraction of any player crop. Discarding them first is what lets a bib match survive a player running from under a light into shadow. The open-then-close order is deliberate: opening first removes speckle so the close cannot grow it into a blob; closing second fills the pinholes punched by bib numbers and fabric folds.

Score fusion with normalisation and soft margins

Colour matching fuses three terms — hue similarity (0.70), histogram correlation (0.20) and mask coverage (0.10) — each normalised into a common [0,1] range first. Hue similarity is a soft-margin curve, flat at 1.0 within 10°, quadratic through the shoulder, zero beyond 28°.

Why it matters: fusing a correlation on [-1,1] directly with a ratio on [0,1] lets one term dominate for reasons unrelated to its importance — normalising first is what makes the weights mean what they say. And a hard threshold would flip a player's identity discontinuously on a single frame of shadow, whereas a graded score can be weighed against positional evidence by the assignment stage rather than overriding it.

Estimator selection for the minimal case

The homography is solved with an exact four-point method. RANSAC is explicitly not used.

Why it matters: with exactly four correspondences, robust estimation is not merely unnecessary — it is meaningless. Four points are the minimal set for a homography, so there is no redundancy to vote with, every minimal sample is the same sample, and the only things robust fitting contributes are nondeterminism and a chance of returning nothing. Reaching for RANSAC here would be cargo cult.

Coordinate-system design

The metric frame is centred on the rim, and the polar convention deliberately swaps the usual atan2 argument order.

Why it matters: two choices collapse a great deal of downstream arithmetic into nothing. A rim-centred origin makes shot distance a plain hypot(x, y) with no offset anywhere, and makes left/right symmetry a pure sign flip. Swapping the atan2 arguments rotates the angular zero to point from the basket toward half court, which moves the branch cut behind the shooter instead of leaving it on the court. With the conventional convention, left-corner shots straddle the ±180° discontinuity — so every sector test would need wraparound handling, and the corner would be exactly where it breaks. Here the whole playing area maps monotonically with no wrap, which is why the zone classifier can be four naive comparisons and still be correct in the corners.

Correct three-point geometry

The three-point test is not a radius check. Below the point where the straight corner line meets the arc, a rectangle rule applies; above it, the arc rule; and the tangency band between them is handled explicitly.

Why it matters: a radius-only test is the obvious implementation and it is wrong in the corner, where the painted line is straight and closer to the hoop than the arc. A shot at 6.70 m out in the corner has a radius under the 6.75 m arc and would be scored a two — when the shooter's foot was 10 cm behind the painted line. The constant that defines the boundary is derived once and drives both the classifier and the rendered chart, so the picture and the statistics cannot disagree.

Peak-hold temporal integration on frame indices

Net motion is integrated by maximum, not mean, across a fixed window measured in frame indices rather than wall-clock time.

Why it matters: net whip is a brief impulse. Sampling one frame would miss it depending on phase; averaging would dilute it below threshold. Max-hold is the correct detector for a short transient. And using frame arithmetic rather than a timer makes the verdict bit-identical on a re-run of the same footage regardless of machine speed — a deterministic, replayable pipeline you can debug offline.

Defensive numerics and observability

Every scoring function degrades rather than throwing: empty crops return well-formed zero tuples, degenerate calibration regions fall back to the unmasked ROI, and every score is explicitly clamped. Functions return their component scores alongside the verdict, not a bare number.

Why it matters: these run once per player per frame on detector output, so clipped boxes at frame edges and near-black crops are certainties, not edge cases — and an exception there kills the session mid-recording, which in a paid deployment means a coach gets nothing. Returning components rather than a single number is a deliberate observability decision: it is the difference between "this match failed" and "this match failed because coverage collapsed, not because the colour was wrong", which is what lets thresholds be tuned against real footage instead of guessed at.

Deployment parameterisation

Every tunable lives in one configuration module, grouped by consuming file, with units in the identifier. The pixel-domain thresholds carry an explicit note that they are recalibrated per install, because camera distance, angle and net material change the response. The net signal sits behind a real feature flag with a clean fallback path.

Why it matters: this is the file that separates a demo from something a second gym can install. Naming a constant is boilerplate; recording which constants are venue-dependent and why the physics makes them so is a handover artefact — it tells the next engineer exactly which few numbers to re-measure at a new club and which to leave alone.


Repository Contents

This repository is a documentation and demonstration set, not a runnable application. The published modules are the self-contained, algorithmically interesting components; they import cleanly and can be read and reasoned about independently, but there is no entry point here and they do not compose into a working system on their own.

Module Contents Status
modules/config.py Court geometry, tracking and classifier constants Public
modules/ball_track.py Multi-ball track lifecycle and data association Public
modules/bib_color.py HSV colour reference construction and match scoring Public
modules/make_miss_classifier.py Shot-outcome state machine and net-motion measurement Public
modules/homography_mapping.py Interactive calibration, 4-point homography, point transform Public
modules/court.py 13-zone classification and shot-chart rendering Public
Trained detector weights and dataset Private
Player detector and re-ID state machine Private
Shot-attempt detection Private
Shooter attribution logic Private
Video pipeline, inference loop, overlay renderer Private

The private components are withheld because the system is in active commercial deployment. Where a public module depends on a private one, its docstring says so explicitly and names what is missing — for example, the make/miss classifier documents that the heuristic deciding a shot is under way at all is excluded, and that it only ever runs after a private caller has made that decision.


Known Limitations and Assumptions

Stated plainly, because a system evaluated honestly is easier to trust than one evaluated optimistically.

  • The court model assumes FIBA dimensions. Court width, three-point radius, corner offset, lane width and hoop-to-baseline distance are fixed to the FIBA standard used by UK club courts. On an NBA-marked or undersized school court the homography still solves, but zone boundaries will be systematically offset. Per-venue court profiles are on the roadmap.

  • A planar homography is exact only on the floor plane. Court positions are therefore derived from floor-contact points — the blue dot at a player's feet in the annotated frame. An airborne ball mapped through the same homography would land metres from its true position, so ball-height pixels are never used as court coordinates.

  • A single camera resolves the rim as a line in the image, not a plane in space. A ball passing well in front of or behind the hoop crosses that same line in the image. This is not a defect being disclosed reluctantly — it is the entire reason the net-motion signal exists, and the dual-signal design is the answer to it.

  • The dual-signal guarantee is a default, not an invariant. The net-motion requirement sits behind a configuration flag. It is enabled by default, but a venue whose net region is unusable can disable it and fall back to trajectory-only classification, with correspondingly higher false-positive risk.

  • Pixel-domain thresholds are per-install. Tracking radii and net-motion thresholds are expressed in pixels, so they depend on camera resolution, distance and lens. A new venue requires a short commissioning pass, not just a calibration click-through.

  • Ball trajectory is a first-order linear model. Tracks carry a bounded rolling position history and predict forward with two-point constant velocity. There is no parabolic fit, no gravity term and no smoothing filter — a deliberate, cheap choice, not an approximation of something more sophisticated.

  • Calibration ordering is not yet validated. The homography solve assumes the lane elbows are clicked left-then-right. Reversing them yields a mirrored transform that solves without error. An orientation check is a known outstanding item.

  • Processing is offline. Sessions are analysed after recording, not live.


Roadmap

Active development, driven by what real sessions expose:

  • Per-venue court profiles, removing the fixed FIBA assumption
  • Calibration validation — orientation and reprojection-error checks so a mis-ordered calibration fails loudly instead of silently
  • Throughput optimisation toward near-real-time analysis
  • Player detection robustness under heavier occlusion and lower light
  • Expanded metrics beyond shooting — movement volume, spacing and possession statistics
  • Published make/miss accuracy figures measured against a hand-labelled session, to match the rigour of the detector's published metrics

Commercial Status

RimPlane is being developed and commercially deployed as an AI-powered sports analytics product for grassroots basketball, currently running across multiple clubs in London.

Selected architecture and calibration methods are documented here for demonstration and evaluation. Trained models, datasets, and proprietary analytics components remain private.


License

Copyright © 2025–2026. All Rights Reserved.

This repository is provided for portfolio, demonstration, and evaluation purposes only. You may view, clone, and reference the code for educational and non-commercial purposes. Commercial use, redistribution, sublicensing, resale, or deployment is prohibited without prior written permission.

See the full LICENSE for complete terms.

About

Single-camera computer-vision analytics for real basketball training sessions — custom-trained YOLOv8 ball detection, multi-ball tracking, dual-signal make/miss classification and homography-based court mapping that turns raw footage into per-player shot charts in real metres.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages