Skip to content

Commit 7d77356

Browse files
MagicalTuxclaude
andcommitted
detect: gate scene texture out of the locator and stop fragmenting codes
Real captures drowned the locator in noise: dense print flood-filled into near-frame-sized 'matrix' blobs, text rows/columns passed as linear regions, and one physical code was reported as a pile of overlapping fragments — while a close-up code filling the frame produced no candidate at all (huge modules leave the texture pass blind, and the size cap killed its region). - cap regions at max_region_frac of the frame unless a confirmed finder backs them (a close-up code legitimately fills the frame) - rank finder-backed regions first, then largest - suppress a candidate overlapping an accepted one by >50% of the smaller box - reject linear regions that are not scannable (reading extent < 0.75x bar extent) or whose dark mask breaks up along the bar axis like text glyphs - band finderless matrix guesses to code-like ink coverage (22..85% dark) - synthesize candidates from unclaimed finder hits (count >= 2) - keep Region as the un-grown core box; grow linear quiet zones at emission - micro-opts on the hot path: direct row slices in the tile pass, reused run buffer in the finder scan (~12% faster 1080p locate) Real scenes: EAN-amid-text 16 -> 2 candidates with the barcode ranked first; close-up QR 7 -> 1 (exact box); pure text areas now yield zero candidates. Synthetic recall stays 100% in the bench and tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 55e57be commit 7d77356

6 files changed

Lines changed: 448 additions & 64 deletions

File tree

‎examples/locdebug.rs‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ fn main() {
1111
.collect();
1212
let frame = anyd::GrayFrame::new(&luma, w, h).unwrap();
1313
let cands = locate(&frame, &LocateOptions::default());
14-
println!("frame {w}x{h}: {} candidates", cands.len());
14+
let thr = anyd::imgproc::threshold::otsu_threshold(&frame);
15+
println!("frame {w}x{h}: {} candidates (otsu {thr})", cands.len());
1516
for c in &cands {
1617
let cr = c.location.outline.corners;
1718
let (xs, ys): (Vec<f32>, Vec<f32>) = (
@@ -30,14 +31,28 @@ fn main() {
3031
.symbology
3132
.map(|s| format!("{:?}", s.dimension()))
3233
.unwrap_or("?".into());
34+
// Dark fraction of the candidate box under the global Otsu split — the cheap
35+
// "is this barcode-like ink coverage?" signal the locator gates on.
36+
let (bx0, by0) = (x0.max(0.0) as usize, y0.max(0.0) as usize);
37+
let (bx1, by1) = ((x1 as usize).min(w), (y1 as usize).min(h));
38+
let mut dark = 0u64;
39+
for yy in by0..by1 {
40+
for xx in bx0..bx1 {
41+
if luma[yy * w + xx] <= thr {
42+
dark += 1;
43+
}
44+
}
45+
}
46+
let area = ((bx1 - bx0) * (by1 - by0)).max(1) as f64;
3347
println!(
34-
" {fam:8} ({:.0},{:.0})-({:.0},{:.0}) {:.0}x{:.0}",
48+
" {fam:8} ({:.0},{:.0})-({:.0},{:.0}) {:.0}x{:.0} dark {:.2}",
3549
x0,
3650
y0,
3751
x1,
3852
y1,
3953
x1 - x0,
40-
y1 - y0
54+
y1 - y0,
55+
dark as f64 / area
4156
);
4257
}
4358
}

‎src/detect/finder.rs‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,14 @@ pub(crate) fn find(grid: &DownGrid) -> Vec<FinderHit> {
122122
let mut centers: Vec<FinderHit> = Vec::new();
123123
let w = grid.width;
124124
let h = grid.height;
125+
let mut runs: Vec<(bool, usize, i32)> = Vec::new();
125126
for y in 0..h {
126-
// Run-length encode this row as (dark, start, len).
127-
let mut runs: Vec<(bool, usize, i32)> = Vec::new();
128-
let mut cur = grid.dark(0, y);
127+
// Run-length encode this row as (dark, start, len), reusing one buffer.
128+
runs.clear();
129+
let row = &grid.dark[y * w..(y + 1) * w];
130+
let mut cur = row[0];
129131
let mut start = 0usize;
130-
for x in 1..w {
131-
let d = grid.dark(x, y);
132+
for (x, &d) in row.iter().enumerate().skip(1) {
132133
if d != cur {
133134
runs.push((cur, start, (x - start) as i32));
134135
cur = d;

0 commit comments

Comments
 (0)