fix(scanner): re-land the badge-dot prune and the dead adaptive thresholds - #1309
Merged
bmc08gt merged 2 commits intoAug 22, 2026
Merged
Conversation
…ipse A code is located by its centre badge, but the badge is a disc with the Flipcash "F" knocked out of it, and the glyph's round dot is itself a near-perfect ellipse sitting ~0.08D from the badge centre. Once the code is drawn large enough in frame, that dot clears the candidate filters and the two ellipses end up in the same prune bucket. The prune dropped the wrong one. Its rule was "discard this candidate if a nearby one is at most twice its area", which is order-dependent and, for a big/small pair, always discards the big one. The badge disappeared and the scan was left searching from the dot, which yields one finder point instead of nine. Measured on a 1920x1080 frame: a code occupying 540px decodes, 756 and 972 do not. Prune only true near-duplicates -- the same physical circle fitted twice from the inner and outer edge of its stroke, which are comparable in area. A much smaller neighbour is a different feature nested inside, so keep both and let the search decide; the badge sorts first and still hits on the first iteration, so the happy path is unchanged. The sweep harness masked this by substituting a plain white oval for the badge. It now renders the real artwork, which is what put the dot in frame in the first place.
…eads
detectKikCode opened with a pair of 21x21 Gaussian adaptive thresholds into
`whitish` and `blackish`. Neither result is ever read:
- `whitish` is unconditionally overwritten a few dozen lines later by the
global `threshold(greyscale, whitish, 170, 255, THRESH_BINARY)` before
anything touches it.
- `blackish` is only read on the inverted-code path (`!check_high`), and that
path recomputes it itself, lazily, behind `blackish_created` -- with a
different algorithm (ADAPTIVE_THRESH_MEAN_C at the quality-dependent width).
So the block was two full-frame adaptive thresholds per analyzed frame, thrown
away every time. Measured on an S25 Ultra over 30 iterations per case:
1280x720 code present 10.44ms -> 4.99ms
1280x720 no code 14.30ms -> 8.90ms
1920x1080 code present 8.27ms -> 2.79ms
1920x1080 no code 10.01ms -> 4.47ms
That is ~5.5ms off every frame the analyzer processes, hit or miss -- larger
than the luminance-plane fast path it stacks on. The instrumented sweep still
decodes 18/18 and stride parity is unchanged, which is what you would expect
from deleting values nothing consumes.
iOS never carried this block.
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.
Re-lands the two scanner commits that never reached
code/cash.#1306 and #1307 were stacked on each other's head branches rather than on
code/cash. #1303 merged first, which retired the bottom of the stack, and thetwo above it then merged into branches that were already spent. Both PRs show as
merged; neither one's content is on
code/cash.So
code/cashtoday has the luminance fast path and nothing else from thatseries. This puts the rest back, cherry-picked onto current
code/cashratherthan by merging the old branch — that branch predates #1304 and #1305, and
merging it would revert them.
fix: the badge glyph's dot steals the centre ellipse
The detector prunes ellipse candidates that sit within 50px of each other,
keeping whichever it reaches first. The badge is a disc with the "F" knocked out
under the even-odd rule, and the glyph's dot is itself a near-perfect ellipse
about 0.08·D from the badge centre. Past a certain on-screen size both land in
the same prune bucket, and the forward-only
2 * area(i) > area(j)rule alwaysdiscards the larger candidate — the badge, which is the only one that can
yield nine finder points. The search then runs from the dot and finds one.
The prune now fires only when the two candidates are comparable in area, i.e.
genuine near-duplicates: the same physical circle fitted twice from the inner and
outer edge of its stroke. A much smaller neighbour is a different feature nested
inside, not a duplicate.
This is a hard failure band, not a slowdown — hold the phone too close and the
code does not decode until you back off.
The harness could not have caught it
KikCodeScanTestoncode/cashrenders the badge asShapeDrawable(OvalShape()).A featureless oval has no knocked-out dot, so the sweep passes with the bug fully
present. This restores the real
ic_logo_round_whiteartwork, and adds thedrawable, which is absent from
code/cashentirely.perf: two full-frame adaptive thresholds nothing reads
detectKikCodeopened with a pair of 21x21 Gaussian adaptive thresholds.whitishis unconditionally overwritten by the global threshold before anythingreads it;
blackishis only read on the inverted-code path, which recomputes ititself behind
blackish_createdwith a different algorithm. Measured on an S25Ultra, 30 iterations per case, native detect only:
About 5.5 ms off every analyzed frame, hit or miss — larger than the luminance
fast path it stacks on.
Both changes are already on iOS (code-ios-app #630); iOS never carried the
adaptive-threshold block.