feat: judge a basketball by what a basketball looks like - #43
Merged
Conversation
"the ball is hardly ever identified. we need to work on that."
Measured before changing anything, on 20s of the reported game at the shipped
2x2 tile grid. The obvious lever turned out to be the wrong one:
3x3 tiling ball 13 tracks / 127 positions, rim 100 — against 15 / 173 and
316 for 2x2, at twice the runtime.
Tiles get smaller, but the whole-frame pass has to downsample further to feed
them, and the rim loses more than the ball gains. So the grid stays at two, and
the comment in the preset now says why, because 3x3 is the first thing anyone
will reach for next.
The lever that does work is the confidence floor, swept over the same clip:
floor ball tracks / positions rim positions player tracks
0.25 15 / 173 316 279
0.18 16 / 206 351 294
0.12 17 / 253 379 285
0.08 17 / 293 403 313
0.05 16 / 330 427 294
Ball *track* count stays flat while positions nearly double. That is the shape
of better recall rather than more noise: a looser detector would invent short
spurious tracks, not lengthen the ones already there.
Lowering it globally would have loosened people too, so the floor is now
per-class. A basketball is a handful of pixels and the model is right to be
unsure about it; a player fills a fifth of the frame and a 0.08 "person" is
junk. The model runs once at the most permissive floor any class asks for and
each detection is then held to its own class's standard — the decoder cannot do
this itself, since it works in class indices before the sport's mapping exists.
ByteTrack's low-score pass follows the same floor, because re-attaching exactly
these faint detections is what that pass is for.
Verified through the real worker CLI on the same clip:
before ball 15/173 | hoop 5/316 | player 279/8457 | 90s
after ball 17/293 | hoop 6/359 | player 279/8457 | 91s
+69% ball positions and +14% rim, with the player numbers identical to the
byte, at the same cost.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I measured before changing anything, on 20s of the reported game at the shipped 2×2 grid. The obvious lever is the wrong one.
3×3 tiling makes it worse
Tiles get smaller, but the whole-frame pass has to downsample further to feed them, and the rim loses more than the ball gains — for twice the runtime. The grid stays at two, and the preset now carries a comment saying why, because 3×3 is the first thing anyone will reach for next.
The confidence floor is the lever
Swept over the same clip:
Ball track count stays flat while positions nearly double. That's the shape of better recall, not more noise — a looser detector invents short spurious tracks, it doesn't lengthen the ones already there.
Per-class, not global
Lowering it globally would loosen people too. A basketball is a handful of pixels and the model is right to be unsure; a player fills a fifth of the frame and a 0.08 "person" is junk.
The model runs once at the most permissive floor any class asks for, and each detection is then held to its own class's standard. The decoder can't do this itself — it works in class indices, before the sport's mapping exists. ByteTrack's low-score pass follows the same floor, since re-attaching exactly these faint detections is what that pass is for.
Defaults:
ball/puck0.08,hoop/net0.15, everything else unchanged.Verified through the real worker CLI
+69% ball positions, +14% rim, players identical to the byte, same cost.
584 tests pass, 8 skipped. Lint and build clean. Merged with current master.
3 of 3 from the report — #40/#41 (duplicate athletes, live UI) are already in; #42 is jersey/team.
🤖 Generated with Claude Code