← Back to writing
28 July 2026 · 3 min read

Why I labelled it a shot candidate and not a goal

Building event detection from ball trajectory on uncalibrated footage, and why naming the limitation in the output was cheaper than pretending it was not there.

I am building a pipeline that takes a single wide-angle recording of an amateur football match and returns the thing players actually want: a fifteen-second clip of their own goal, cut automatically.

The vision part is not the hard part. YOLO finds players and the ball. ByteTrack keeps identities stable across frames so a player who is occluded for half a second does not come back as a new person. KMeans on jersey colour separates the two teams. None of that is novel and all of it works.

The hard part is deciding what counts as an event.

Events from trajectory, not from appearance

A goal has no visual signature. There is no pixel pattern that means “goal”. What there is, reliably, is a change in the ball’s motion: it accelerates sharply when struck, travels in a relatively straight line, and then either stops, changes direction, or leaves the frame.

So the detector is not looking at the ball. It is looking at the ball’s velocity over time, computed from the tracked centroid across frames, and flagging sudden increases above a threshold. That gives you shots. It gives you clearances too, and hard passes, and the occasional bad throw, which is exactly the problem.

To separate a shot from a clearance you need to know where the goal is. Direction of travel relative to the goal mouth is the discriminator, and it is a cheap one once you have the geometry.

The geometry I did not have

The sample footage was recorded handheld and on a gimbal. The camera moves. There is no fixed homography between image coordinates and pitch coordinates, and there is no stable region of the frame I can call “the goal”.

I could have estimated it. Detect the goalposts, fit a plane, track the camera motion, warp per frame. That is a real project on its own, and it would have been solving a problem that does not exist in production, because in production the camera is bolted to a wall and never moves.

So I did the other thing. The event detection function takes a goal-region parameter. On the sample footage that parameter is absent, and the function emits its output labelled shot candidate. On a fixed rig you pass the region once, and the same code emits goal.

Why the label matters more than the code

The code change here is trivial: one optional argument and a different string in the output. The decision underneath it is not.

The alternative was to tune the velocity threshold until the demo clips happened to be mostly goals, call the output goal, and ship it. That version demos better. It also has a failure mode that surfaces at the worst possible moment, in front of a customer, on footage the tuning never saw. And you cannot debug your way out of it, because the information required to make the distinction was never in the input.

Naming the limitation in the output does three things. It stops anyone downstream from treating the number as something it is not. It documents exactly what has to change for the feature to be complete. And when the fixed camera arrives, the work is a config value rather than a rewrite.

What I would do differently

I would have written the goal-region parameter into the signature on day one instead of adding it once the limitation became obvious. Leaving a hole where you know a constraint belongs is cheaper than carving one out later, and it costs nothing while the hole is empty.

The rest of the pipeline I would keep: detect, track, infer events from motion, cut around events with ffmpeg, encode to browser-friendly H.264, put a thin Streamlit front end on it so a non-technical person can drop in a video and get clips back.

The honest summary

Most of the engineering in an applied vision system is not in the model. It is in deciding what your output is allowed to claim given what your input actually contains. A shot candidate that is honest is worth more than a goal that is sometimes a throw-in.

computer visiontrackingByteTracksports analytics