Arrow Ballistics Study | 2026
This page picks up where the slow-motion capture and preparation page leaves off. By that point each surviving shot is a folder of high-speed frames plus a small set of per-shot calibration values (arrow length in pixels, tape reference line, per-build flex envelope).
What we do here is find the arrow in every frame, turn each frame into a yaw number (how far the nock has swung off the direction of travel) and a flex number (how much the shaft is bowing in the middle), filter out bad frames, summarize each shot down to one number at one bow-relative position, and then average across the three repeats at each (build, torque, distance) cell.
The end product is a per-(build, torque, distance) profile of how yaw and flex evolve in the first ~45 ft of flight. The last section, Limitations and Future Capture Changes, is the honest list of what currently leaks into those numbers.
For each frame in the shot, the detector produces tip and nock pixel coordinates, the angle of the line between them (the chord), 60 sample points along the shaft, and a valid-or-not flag with a reject reason. Everything below runs once per shot, frame by frame.
Almost everything in frame does not move. The tape measure, the table grid lines, dust on the lens, stuck sensor pixels: they sit on the same pixels in every frame. The arrow occupies any given pixel for only a few frames as it sweeps across the field of view.
That gap lets us recover the stationary background cheaply. For each pixel, the color it has across the majority of frames is the background; the arrow only ever shows up as the minority. Taking the median across all frames in the shot produces a clean background image. Every frame is then re-expressed as itself minus that background, recentered to a neutral gray. The transformed frames look like a uniform gray field with the moving arrow and any per-frame lighting noise still visible; the stationary clutter is gone.
On the background-subtracted frames, the arrow's position is found by subtracting each frame from the previous one (frame differencing) and keeping the pixels that changed the most. After a small noise-cleanup pass, any pixel whose change exceeds a threshold becomes part of the motion mask for that frame.
The motion mask is not a clean silhouette of the arrow. A long, uniform stretch of shaft looks almost identical frame to frame, so the only pixels that show up as motion are the tip leaving its previous position and entering a new one, the nock doing the same, and any colored bands along the shaft. The detector treats the motion mask as a sparse cloud of "arrow signal" pixels, not as a solid blob to be cropped.
Two whole-frame guards run before any geometry is attempted:
A straight line is then fit through the surviving pixels using a method that down-weights a handful of stray pixels (shadows, sensor blips) rather than letting them drag the line off course the way an ordinary best-fit line would. The result is the arrow's long axis in this frame. Pixels more than about 8 px perpendicular to that line are dropped as outliers; the rest are the inliers, the motion pixels actually attributable to the shaft. If the inliers do not span at least 40% of the calibrated arrow length, the frame is rejected. Shorter spans almost always mean the arrow is partly out of frame or the line locked onto a small cluster of noise.
Initial tip and nock estimates come from the extreme x-bands of the inlier cloud, with a slightly wider window on the nock side so the fletchings get averaged in rather than tugging the nock inward. Those estimates are biased backward, since frame differencing flags both the previous and current arrow positions as motion. A refinement step re-finds each end in the background-subtracted current frame alone, restricted to a narrow band around the fitted axis, where only the current position contributes.
The tip needs one more correction. The silver field points used in this dataset have low contrast against the table and reflect lights differently every frame, so the refined tip almost always lands at the shaft / field point boundary rather than at the actual front. With the nock trustworthy, the tip is slid outward along the fitted axis until the chord matches the calibrated arrow length: a small (~10 px, the visible field-point length) but systematic correction.
Three sanity checks reject the obviously-bad detections before the frame is marked valid:
Frames that fail any of these tests are written out with valid = False and a reject reason; the downstream aggregation skips them.
For frames that pass, the shape of the shaft itself is recovered. The inliers are rotated into a coordinate system where one axis runs along the chord (nock-to-tip) and the other is perpendicular. A gentle curve (a polynomial up to cubic, just enough to capture any bow in the shaft) is fit to the perpendicular offsets as a function of along-chord position. Sixty equally-spaced points are then read off that curve along the middle two-thirds of the chord, staying away from the fletching mass at the nock end and the field-point extrapolation at the tip end.
When a per-build flex envelope is available (from the capture page), a second smoother centerline is computed by fitting a single amplitude to that build's analytic flex shape rather than to a free polynomial, and clipped to the maximum flex the envelope predicts. That envelope-bounded centerline is what gets reported as the per-frame flex value and what shows up in the validation viewers.
The detector's tip and nock at each of the three hand-annotated frames are compared against the human-marked positions for the same frame. A shot is flagged okif the worst-frame residual is at most 4 px, and annotation_validation_failed otherwise. This catches the cases where the detector silently goes off the rails (a strong shadow, a stationary feature that the median did not subtract, and so on) without anyone having to scroll through the whole shot.
The per-frame detections from the previous section get turned into the two physical quantities of interest.
Yaw is how far off-line the nock has swung from the direction of travel. The direction of travel is taken from the tape reference line built during calibration (the long tape measure laid along the table). For each frame we draw an imaginary line through the tip parallel to the tape and measure the perpendicular distance from the nock to that line. The sign is preserved (positive on one side, negative on the other), so a build that consistently yaws one direction can be told apart from one whose yaw direction wanders:
The value is converted to inches using the per-shot pixels-per-inch ruler from the calibration step.
Flex is how much the shaft is bowing in the middle. We take the centerline points from the previous section, draw a straight line from the tip to the nock (the chord), and measure the largest perpendicular distance from that chord to any centerline point. The measurement is restricted to the middle of the shaft (t ∈ [0.2, 0.8]) so the fletching mass on the nock end and the tip extrapolation on the tip end do not bias it:
The signed value at the same point is also kept, so direction-of-bow is recoverable. Like yaw, flex is reported in pixels and inches.
Yaw is computed on every detector-valid frame; flex also requires at least half of the 60 centerline points to be accepted. Each shot therefore produces a list of per-frame yaw values and a list of per-frame flex values, indexed by the original cine frame number.
Within a single shot, the per-frame yaw and flex values are filtered to remove residual bad frames, tagged with the physical bow-relative position they correspond to, and summarized into one number per shot.
Four filters run in series. The first three are automatic; the fourth honors a manually-curated exclusion list described below.
The automatic filters catch obvious detector failures but leave a long tail of frames that pass every threshold and still measure something the detector should not have trusted: a flicker that survived background subtraction and pulled the chord angle off by a degree, a fletching reflection that biased the nock, a frame where one endpoint touched a stationary feature the median did not subtract. Each (build, torque, distance) cell was reviewed by hand against the underlying TIFFs in an internal viewer, and any such frames were added to the manual exclusions list that the filter above consumes on the next pipeline run.
The detector reports the tip's x in image pixels. The physical position of the tip relative to the bow at any frame is the camera's bow-to-left-frame-edge distance (from the shot log, in feet) plus the additional distance from the left frame edge to the tip, measured at the arrow's height:
The second term uses the per-shot pixel arrow length from the calibration step rather than a fixed pixels-per-foot scale. The arrow flies above the table and is therefore closer to the camera than the table is, so its on-screen scale is larger than the table's; using the table scale would underestimate the tip's actual bow-relative position by a few percent.
Visible tip travel within a single shot is usually about 4 ft, less than the 8 ft table width, because the camera was set up so the arrow enters and exits in roughly the time the buffer wants to record. Consecutive camera stations therefore leave small gaps in bow-distance coverage; those show up as missing points rather than interpolated values in the final plots.
The kept frames of each shot produce a per-shot yaw summary (mean, std, n, position_mean, position_min, position_max) and an analogous flex summary. The position range is recorded explicitly because it is the natural horizontal error bar when plotting; each shot covers a stretch of bow-relative distance, not a single point.
Three shots were fired at every (build, torque, camera station) combination, so the cross-shot step is a mean of those per-shot means within each cell, with the sample standard deviation across shots reported alongside. Two views of the aggregated data come out:
Both views feed an internal exploration app where build, torque, and distance subsets can be selected without re-running the aggregation, plus a peak- analysis tab that scatters per-shot peak yaw or peak flex against build properties (spine, FoC, point weight, insert weight, arrow weight, launch velocity) to look for cross-build trends.
The motion-based pipeline is the best version of this measurement we have to date, but it is not bias-free. Below is what currently leaks into the numbers, what survives the pipeline at the end, and what we would change about the capture setup next round.
The next capture pass would change several things about the setup, most aimed at making the detector's job easier rather than at the detector itself: