Jay Y. Lee

Baseball pitch tracker

Recovering pitch velocity, spin rate and spin axis from a stereo camera and a marked ball, at a price a youth programme can actually reach.

RunningBaseball · Imaging · Data

Reference

There is no mature open-source computer-vision pitch tracker, and the smart-ball side of the market is entirely commercial. That gap is the whole reason this exists.

The price structure is the interesting part. Below about $400 you get velocity and nothing else. Above about $3,000 you get Rapsodo or Trackman and full metrics. The middle is close to empty, and that is where a 13U programme lives. Diamond Kinetics discontinued the PitchTracker smart baseball in late 2024, which vacated part of the low end and left an installed base with no forward path.

13U is the initial target for a practical reason: at roughly 50 to 70 mph and around 1,800 RPM, the ball is meaningfully easier to instrument than an adult arm, and the price sensitivity in that market is what makes the gap real.

Three tracks

TrackApproachWhere it is
ABLE decode of the discontinued Diamond Kinetics ballProtocol identified, decoding not started
BDIY smart ball, IMU inside a printed shellHardware chosen, no firmware yet
COpenPitch — stereo camera plus a marked ballPrimary. Algorithm settled, awaiting a feasibility test

A and B are upstream research that fed into C. Both are still live, neither is the main line of work.

How OpenPitch recovers orientation

A stereo OV9281 monochrome camera watches a baseball with its leather C-panel boundary marked in black. Per-frame orientation comes from matching the observed silhouette against a table of poses rendered in advance.

StepWhat happens
Ball modelUV sphere with the painted panel as a binary texture
Pose gridSO(3) sampled at about 5° via Hopf fibration, roughly 50K poses
RenderEach pose to a 128×128 binary mask, offline
DescriptorOrder-8 Zernike moments, 36 rotation-invariant coefficients
Indexscipy.spatial.cKDTree, serialised to a pose table
QueryFour nearest neighbours per frame, then barycentric quaternion averaging

Spin axis then comes from fitting the sequence of inter-frame rotations, rather than from any single frame.

What it is not

Rapsodo and Trackman are not camera-only systems. They fuse radar with infrared illumination and multiple cameras. A camera-only build is not going to match them and it would be dishonest to imply otherwise. The claim here is price and openness in a segment those products do not serve, not accuracy parity.

Hardware

PartRole
SVPRO OV9281 stereo USB module, 6mm lensMono stereo pair, no IR-cut filter
Raspberry Pi 5 4GBCapture and processing
Posca PC-5M black markerPanel marking on natural leather
Seeed XIAO ESP32-C3 + ICM-20948Track B smart ball
Diamond Kinetics PitchTracker ballTrack A subject, Renesas DA14695

Alan Nathan's trajectory calculator at baseball.physics.illinois.edu and Driveline's OpenBiomechanics data are the two references I keep going back to.

Log

The blur budget, and why moving closer makes it worse

Ran an adversarial review over the whole Track C plan, looking for the thing that kills it rather than the things that are merely hard. The answer was not resolution and not algorithm complexity. It is motion blur.

At 70 mph, holding blur under about two pixels needs an exposure in the region of 75 µs. That is a bright-outdoor-sun exposure. It constrains where the system can be used far more than any code decision does.

The non-obvious part came out of tabulating blur against side distance:

Moving the camera closer makes blur worse. Image-plane angular velocity rises as you approach, so absolute smear in pixels grows even though the percentage smear stays roughly constant. Closer buys more pixels on the ball and a tighter exposure budget at the same time. Those two pull against each other, and the exposure side is the one that bites.

Consequence for the public numbers: headline spin-axis accuracy came down from an optimistic 1–2° to a realistic 3–5°, once the synthetic-to-real domain gap and symmetry aliasing were priced in. The lower figure was wishful and should not appear anywhere.

The answer to all of this is probably infrared. The OV9281 has no IR-cut filter and is natively IR-sensitive, so adding IR illumination decouples the system from ambient light entirely. That is also the first honest step toward the multi-modal fusion the professional systems already do.

Three tests, one weekend, all pass or fail

Everything downstream is now gated on one weekend of measurement. Three tests, each deliberately binary, because a test that returns "sort of" is a test that gets argued about for a month:

TestPasses if
Motion blurBall elongation at 70 mph stays under 2 px
Ball size28–32 px diameter at 15 ft on the 6mm lens
Panel segmentationOtsu threshold gives a clean chiral silhouette matching the synthetic renders on at least 15 of 20 frames

The one real decision here was to test at 10 ft rather than the 15 ft production spec, which looks like cheating and is not.

At 10 ft the ball is about 47 px across, comfortably above the descriptor quality threshold. If segmentation fails at 47 px then the problem is fundamental, not marginal resolution, and I have learned something worth knowing. Testing at 15 ft conflates two failure modes: I would not know whether segmentation broke because the approach is wrong or because the ball was only 32 px wide. Two pixels of blur also matter far less on a 47 px ball, which isolates blur from resolution instead of tangling them.

If 10 ft passes, pushing back out to 15 ft becomes a known-margin decision instead of a gamble.

The distance policy for the product itself falls out of the same reasoning. A dedicated bullpen with a backstop can run 8 to 10 ft and get 40 to 50 px with eight to ten frames in view. General practice with a batter possibly present takes 12 to 15 ft as a hard safety floor. Below 8 ft you are fighting both safety and the exposure budget for diminishing returns.

Black marker, not fluorescent pink

Reversed an earlier decision. Fluorescent pink had been the recommendation for marking the panel, on the reasoning that fluorescent colours pop.

They do, to your eye. The OV9281 is a monochrome sensor. Integrated across its response, a fluorescent marking reflects nearly as much total light as bare leather. The pop of fluorescence is a colour phenomenon, and a mono sensor never sees colour. Black Posca PC-5M it is, because black is what actually maximises luminance contrast against leather.

The generalisable version, which is the bit worth keeping: the sensor type dictates the marker strategy. Any future variant that uses a colour sensor reopens this decision from scratch rather than inheriting the answer.

Two-colour panel splits and asymmetric dot patterns were also explored and not adopted.

A pre-rendered pose table, and the tiebreaker that picked it

Four ways to get orientation out of a frame, compared properly. The compute and accuracy columns are estimates from working through it, not measurements:

ApproachPer frameSpin axisComplexity
Moments, centroid plus PCAunder 1 ms~3–5°~150 lines
Pre-rendered plus Zernike lookup~2 ms~1–2° before real-world penalties~600 lines plus offline pipeline
KLT on the stitches~20 ms~1–2°~400 lines
Live render plus gradient descent~50–150 ms~0.5–1°~400 lines plus a GPU

The lookup table is second-best on accuracy, best on runtime, and needs no GPU. The expensive work sits offline, outside the latency budget entirely.

Descriptor choice took its own pass. Hu moments are too few coefficients, and visually distinct poses collide. Shape context and HOG on the boundary discriminate well at 60 to 100 dimensions but cost too much per query. A learned CNN embedding would discriminate best and needs training infrastructure, which is overkill for a v0.1 that has not proven it can see the ball yet.

The decisive argument was not in the table. It is that the failure modes are knowable in advance. With moments-based tracking you do not find out how bad the chirality artifacts are until week six, on real pitches, with a season half gone. With the lookup table the offline pipeline either builds cleanly or it does not, and you know in week one.

Accepted costs, stated plainly: 600 lines is meaningfully more than 150; the offline pipeline is a real several-day piece of work; and per-ball calibration adds field procedure, which is a half-step toward the "you must use our balls" model I would rather not copy.

The C-panel is not an ellipse

The original plan was to paint one hemisphere of the ball. A great circle projects to an ellipse, so you fit an ellipse per frame, track how it evolves, and read off the spin axis. Clean, and there is a closed-form fit.

It was wrong, and wrong in a way worth recording. The assumption was geometric convenience rather than the actual object. The real leather C-panel boundary follows the chiral tennis-ball curve, and its silhouette is not an ellipse. Ellipse-fitting was modelling a ball that does not exist.

The correction turned out to be an upgrade rather than a setback. A great circle is mirror-symmetric, which produces genuine pose ambiguity: two orientations give you the same ellipse and nothing in the frame distinguishes them. The chiral C-panel curve does not have that symmetry. Using the real geometry eliminates the mirror ambiguity that the convenient geometry would have handed me.

Chirality went from being an inconvenience to being the feature.

Ellipse-fitting is abandoned. Writing that down here so it does not get resurrected in six months as a simplification.

It is not a total escape: the panel keeps an approximate two-fold rotational symmetry about certain axes, so ambiguity is reduced rather than deleted, and some of it has moved rather than vanished. The mitigation is to track pose sequences and reject transitions that imply unphysical instantaneous changes in spin rate, a 180° jump being the obvious case. At 13U spin rates, around 30 rev/s, the ball turns about 3° between frames at 120 fps, so physical continuity is a very strong constraint and should catch these cleanly.

How Diamond Kinetics got around gyro saturation

The obvious way to measure spin rate is to put an IMU in a ball and read the gyroscope. This fails, and it fails exactly where it matters.

Standard MEMS gyroscopes saturate somewhere around ±2,000 °/s. A 1,800 RPM pitch is 10,800 °/s. Real pitching spin rates are not near the ceiling, they are five times past it. Any naive "put an IMU in a ball" design produces good data right up until the speeds you actually care about, then clips.

Diamond Kinetics' answer, from US Patent 11,083,951 and the academic lineage behind it (McGinnis and Perkins, Sensors, 2012, University of Michigan, work DK licensed), is to not use the gyro at all. They take an FFT of the magnetometer signal instead. As the ball spins, the Earth's magnetic field sweeps across the sensor periodically, and the dominant frequency of that oscillation is the spin rate. A magnetometer has no equivalent rate ceiling, so the saturation problem does not exist rather than being worked around.

This is the most elegant idea I came across in the whole survey, and it is the reference solution for any smart-ball work here. It also explains why the camera track is the primary one: the camera never had a saturation problem to solve.

Track A is the other side of this. The discontinued ball is a Renesas DA14695 with a proprietary BLE service, and the working assumption is that it emits one small fixed-layout struct per pitch rather than a raw sensor stream. That follows from the product behaviour: DK's balls store pitches while disconnected and sync later, which is incompatible with raw streaming. The ball must be computing metrics onboard. That assumption turns the job from "reconstruct a sensor pipeline" into "find the field offsets in a small struct", which is a much smaller problem. It is not verified against captured packets yet.

Spin rate is the field to pin down first, because it is derivable from the gyro vector magnitude with no external equipment. Velocity needs a radar gun as ground truth to correlate against.