diff --git a/src/common/cpp_essentials/concentric_finder.rs b/src/common/cpp_essentials/concentric_finder.rs index c0479c6..2e2a887 100644 --- a/src/common/cpp_essentials/concentric_finder.rs +++ b/src/common/cpp_essentials/concentric_finder.rs @@ -79,7 +79,7 @@ pub fn ReadSymmetricPattern( // default for RELAXED_THRESHOLD should be false pub fn CheckSymmetricPattern< - const RELAXED_THRESHOLD: bool, + const E2E: bool, const LEN: usize, const SUM: usize, T: BitMatrixCursorTrait, @@ -125,13 +125,12 @@ pub fn CheckSymmetricPattern< } } - if IsPattern( + if IsPattern::( &PatternView::new(&res), &FixedPattern::::with_reference(pattern), None, 0.0, 0.0, - Some(RELAXED_THRESHOLD), ) == 0.0 { return 0; @@ -258,46 +257,28 @@ pub fn CenterOfRings( range: i32, numOfRings: u32, ) -> Option { - let mut n = numOfRings; - let mut sum = numOfRings * center; - for i in 1..numOfRings { + let mut n = 1; + let mut sum = center; + for i in 2..(numOfRings + 1) { // for (int i = 1; i < numOfRings; ++i) { - let c = CenterOfRing(image, center, range, i as i32 + 1, false)?; + let c = CenterOfRing(image, center.floor(), range, i as i32, false)?; - // TODO: decide whether this wheighting depending on distance to the center is worth it - let weight = numOfRings - i; - sum += weight * c; - n += weight; + if !(c == Point::default()) { + if n == 1 { + return None; + } else { + return Some(sum / n as f32); + } + } else if Point::distance(c, center) > range as f32 / numOfRings as f32 / 2.0 { + return None; + } + + sum += c; + n += 1; } Some(sum / n as f32) } -pub fn FinetuneConcentricPatternCenter( - image: &BitMatrix, - center: Point, - range: i32, - finderPatternSize: u32, -) -> Option { - // make sure we have at least one path of white around the center - let res = CenterOfRing(image, center, range, 1, false)?; - - let center = res; - - let mut res = CenterOfRings(image, center, range, finderPatternSize / 2); - - if res.is_none() || !image.get_point(res?) { - res = CenterOfDoubleCross(image, center, range, finderPatternSize / 2 + 1); - } - if res.is_none() || !image.get_point(res?) { - res = Some(center); - } - if res.is_none() || !image.get_point(res?) { - return None; - } - - res -} - pub fn CollectRingPoints( image: &BitMatrix, center: Point, @@ -419,12 +400,6 @@ pub fn FitQadrilateralToPoints(center: Point, points: &mut [Point]) -> Option Option 3.0 + && (lines[i].distance_single(*p) as f64) > f64::max(1.0, f64::min(8.0, len / 8.0))) + { + // #ifdef PRINT_DEBUG + // printf("%d: %.2f > %.2f @ %.fx%.f\n", i, lines[i].distance(*p), std::distance(beg[i], end[i]) / 1., p->x, p->y); + // #endif + return None; + } + } + } + let mut res = Quadrilateral::default(); for i in 0..4 { // for (int i = 0; i < 4; ++i) { @@ -446,44 +451,6 @@ pub fn FitQadrilateralToPoints(center: Point, points: &mut [Point]) -> Option) -> Option { -// let dist2center = |a, b| Point::distance(a, center) < Point::distance(b, center); - -// // Rotate points such that the first one is the furthest away from the center (hence, a corner) -// let mut points = points; -// points.rotate_left(points.iter().position(|p| dist2center(p, center)).unwrap()); - -// let mut corners = [&points[0]; 4]; -// corners[0] = &points[0]; - -// // Find the opposite corner by looking for the farthest point near the opposite point -// let opposite_corner = points.iter().take(points.len() / 2).max_by(|p| dist2center(p, corners[0])); -// corners[2] = opposite_corner; - -// // Find the two in between corners by looking for the points farthest from the long diagonal -// let dist2diagonal = |l: &RegressionLine, a| l.distance(a); -// corners[1] = points.iter().take(points.len() / 2).max_by(|p| dist2diagonal(&RegressionLine(*corners[0], *corners[2]), *p)); -// corners[3] = points.iter().skip(points.len() / 2).max_by(|p| dist2diagonal(&RegressionLine(*corners[0], *corners[2]), *p)); - -// let lines = [ -// RegressionLine::new(corners[0], corners[1]), -// RegressionLine::new(corners[1], corners[2]), -// RegressionLine::new(corners[2], corners[3]), -// RegressionLine::new(corners[3], corners[0]), -// ]; - -// if lines.iter().any(|l| !l.is_valid()) { -// return None; -// } - -// let mut res = QuadrilateralF::new(); -// for i in 0..4 { -// res[i] = intersect(lines[i], lines[(i + 1) % 4]); -// } - -// Some(res) -// } - pub fn QuadrilateralIsPlausibleSquare(q: &Quadrilateral, lineIndex: usize) -> bool { let mut m; @@ -593,11 +560,7 @@ impl ConcentricPattern { } } -pub fn LocateConcentricPattern< - const RELAXED_THRESHOLD: bool, - const LEN: usize, - const SUM: usize, ->( +pub fn LocateConcentricPattern( image: &BitMatrix, pattern: &Pattern, center: Point, @@ -606,31 +569,38 @@ pub fn LocateConcentricPattern< let mut cur = EdgeTracer::new(image, center, Point::default()); let mut minSpread = image.getWidth() as i32; let mut maxSpread = 0_i32; + + // TODO: setting maxError to 1 can subtantially help with detecting symbols with low print quality resulting in damaged + // finder patterns, but it sutantially increases the runtime (approx. 20% slower for the falsepositive images). + let mut maxError = 0; for d in [point(0.0, 1.0), point(1.0, 0.0)] { // for (auto d : {PointI{0, 1}, {1, 0}}) { cur.setDirection(d); // THIS COULD POSSIBLY BE WRONG, WE MIGHT MEAN TO CLONE cur EACH RUN? - let spread = - CheckSymmetricPattern::(&mut cur, pattern, range, true); - if spread == 0 { - return None; + + let spread = CheckSymmetricPattern::(&mut cur, pattern, range, true); + if spread != 0 { + UpdateMinMax(&mut minSpread, &mut maxSpread, spread); + } else { + maxError -= 1; + if maxError < 0 { + return None; + } } - UpdateMinMax(&mut minSpread, &mut maxSpread, spread); } //#if 1 for d in [point(1.0, 1.0), point(1.0, -1.0)] { // for (auto d : {PointI{1, 1}, {1, -1}}) { cur.setDirection(d); // THIS COULD POSSIBLY BE WRONG, WE MIGHT MEAN TO CLONE cur EACH RUN? - let spread = CheckSymmetricPattern::( - &mut cur, - pattern, - range * 2, - false, - ); - if spread == 0 { - return None; + let spread = CheckSymmetricPattern::(&mut cur, pattern, range * 2, false); + if spread != 0 { + UpdateMinMax(&mut minSpread, &mut maxSpread, spread); + } else { + maxError -= 1; + if maxError < 0 { + return None; + } } - UpdateMinMax(&mut minSpread, &mut maxSpread, spread); } //#endif @@ -645,3 +615,51 @@ pub fn LocateConcentricPattern< size: (maxSpread + minSpread) / 2, }) } + +pub fn FinetuneConcentricPatternCenter( + image: &BitMatrix, + center: Point, + range: i32, + finderPatternSize: u32, +) -> Option { + // make sure we have at least one path of white around the center + if let Some(res1) = CenterOfRing(image, center.floor(), range, 1, true) { + if !image.get_point(res1) { + return None; + } + // and then either at least one more ring around that + if let Some(res2) = CenterOfRings(image, res1, range, finderPatternSize / 2) { + return Some(res2); + } + // or the center can be approximated by a square + if (FitSquareToPoints(image, res1, range, 1, false).is_some()) { + return Some(res1); + } + // TODO: this is currently only keeping #258 alive, evaluate if still worth it + if let Some(res2) = + CenterOfDoubleCross(image, res1.floor(), range, finderPatternSize / 2 + 1) + { + return Some(res2); + } + } + None + + // // make sure we have at least one path of white around the center + // let res = CenterOfRing(image, center, range, 1, false)?; + + // let center = res; + + // let mut res = CenterOfRings(image, center, range, finderPatternSize / 2); + + // if res.is_none() || !image.get_point(res?) { + // res = CenterOfDoubleCross(image, center, range, finderPatternSize / 2 + 1); + // } + // if res.is_none() || !image.get_point(res?) { + // res = Some(center); + // } + // if res.is_none() || !image.get_point(res?) { + // return None; + // } + + // res +} diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index 969d805..704c07a 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -11,6 +11,21 @@ use crate::{ pub type PatternType = u16; pub type Pattern = [PatternType; N]; +fn BarAndSpaceSum< + const LEN: usize, + T: Into + Copy, + RT: Default + std::cmp::PartialEq + std::ops::AddAssign, +>( + view: &[T], +) -> BarAndSpace { + let mut res = BarAndSpace::default(); + for i in 0..LEN { + // for (int i = 0; i < LEN; ++i) + res[i] += view[i].into(); + } + res +} + #[derive(Default, Debug)] pub struct PatternRow(Vec); @@ -341,6 +356,7 @@ impl<'a> std::ops::Index for PatternView<'_> { * * The operator[](int) can be used in combination with a PatternView */ +#[derive(Default)] struct BarAndSpace { bar: T, space: T, @@ -355,7 +371,7 @@ impl std::ops::Index for BarAndSpace type Output = T; fn index(&self, index: usize) -> &Self::Output { - match index { + match index & 1 { 0 => &self.bar, 1 => &self.space, _ => panic!("Index out of range for BarAndSpace"), @@ -365,7 +381,7 @@ impl std::ops::Index for BarAndSpace impl std::ops::IndexMut for BarAndSpace { fn index_mut(&mut self, index: usize) -> &mut Self::Output { - match index { + match index & 1 { 0 => &mut self.bar, 1 => &mut self.space, _ => panic!("Index out of range for BarAndSpace"), @@ -418,6 +434,10 @@ impl FixedPattern usize { N } + + fn sums(&self) -> BarAndSpace { + return BarAndSpaceSum::(&self.data); + } } impl std::ops::Index @@ -445,17 +465,58 @@ pub type FixedSparcePattern = FixedPattern // using FixedSparcePattern = FixedPattern; -pub fn IsPattern( +pub fn IsPattern( view: &PatternView, pattern: &FixedPattern, space_in_pixel: Option, min_quiet_zone: f32, module_size_ref: f32, - relaxed_threshold: Option, + // e2e: Option, ) -> f32 { - let relaxed_threshold = relaxed_threshold.unwrap_or(false); + let e2e = E2E; //e2e.unwrap_or(false); let mut module_size_ref = module_size_ref; + if (e2e) { + //using float_t = double; + + let widths = BarAndSpaceSum::(&view.data().0); + let sums = pattern.sums(); + let modSize: BarAndSpace = BarAndSpace { + bar: widths[0] / sums[0] as f64, + space: widths[1] / sums[1] as f64, + }; + + let [m, M] = [ + f64::min(modSize[0], modSize[1]), + f64::max(modSize[0], modSize[1]), + ]; + if (M > 4.0 * m) { + // make sure module sizes of bars and spaces are not too far away from each other + return 0.0; + } + + if (min_quiet_zone != 0.0 + && (space_in_pixel.unwrap_or_default()) < min_quiet_zone * modSize.space as f32) + { + return 0.0; + } + + let thr: BarAndSpace = BarAndSpace { + bar: modSize[0] * 0.75 + 0.5, + space: modSize[1] / (2.0 + f64::from(LEN < 6)) + 0.5, + }; + + for x in 0..LEN { + // for (int x = 0; x < LEN; ++x){ + if (view[x] as f64 - pattern[x] as f64 * modSize[x]).abs() > thr[x] { + return 0.0; + } + } + + let moduleSize: f64 = (modSize[0] + modSize[1]) / 2.0; + return moduleSize as f32; + } + let width = view.sum(Some(LEN)); if SUM > LEN && Into::::into(width) < SUM { return 0.0; @@ -473,7 +534,7 @@ pub fn IsPattern( module_size_ref = module_size; } - let threshold = module_size_ref * (0.5 + (relaxed_threshold as u8) as f32 * 0.25) + 0.5; + let threshold = module_size_ref * (0.5 + (e2e as u8) as f32 * 0.25) + 0.5; // the offset of 0.5 is to make the code less sensitive to quantization errors for small (near 1) module sizes. // TODO: review once we have upsampling in the binarizer in place. @@ -501,13 +562,15 @@ pub fn IsRightGuard( Some(view.end().unwrap().into()) }; - IsPattern( + const E2E: bool = false; + + IsPattern::( view, pattern, spaceInPixel, minQuietZone, moduleSizeRef, - None, + // None, ) != 0.0 } @@ -558,7 +621,13 @@ pub fn FindLeftGuard<'a, const LEN: usize, const SUM: usize, const IS_SPARCE: bo { return false; } - return IsPattern(window, pattern, spaceInPixel, minQuietZone, 0.0, None) != 0.0; + return IsPattern::( + window, + pattern, + spaceInPixel, + minQuietZone, + 0.0, + ) != 0.0; }) } diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index 37250cc..618b541 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -36,6 +36,17 @@ pub type FinderPatterns = Vec; pub type FinderPatternSets = Vec; const PATTERN: FixedPattern<5, 7, false> = FixedPattern::new([1, 1, 3, 1, 1]); +const E2E: bool = true; + +// pub fn FindPattern( view: &PatternView) -> PatternView +// { +// return FindLeftGuard(view, PATTERN.size(), [](const PatternView& view, int spaceInPixel) { +// // perform a fast plausability test for 1:1:3:1:1 pattern +// if (view[2] < 2 * std::max(view[0], view[4]) || view[2] < std::max(view[1], view[3])) +// return 0.f; +// return IsPattern(view, PATTERN, spaceInPixel, 0.5); +// }); +// } /// Locate the finder patterns for the symbol. /// This function can panic @@ -85,7 +96,7 @@ pub fn FindFinderPatterns(image: &BitMatrix, tryHarder: bool) -> FinderPatterns .is_none() { // if (FindIf(res, [p](const auto& old) { return distance(p, old) < old.size / 2; }) == res.end()) { - let pattern = LocateConcentricPattern::( + let pattern = LocateConcentricPattern::( image, &PATTERN.into(), p, @@ -240,7 +251,9 @@ pub fn GenerateFinderPatternSets(patterns: &mut FinderPatterns) -> FinderPattern pub fn EstimateModuleSize(image: &BitMatrix, a: ConcentricPattern, b: ConcentricPattern) -> f64 { let mut cur = EdgeTracer::new(image, a.p, b.p - a.p); - if !cur.isBlack() { return -1.0; } + if !cur.isBlack() { + return -1.0; + } assert!(cur.isBlack()); let pattern = ReadSymmetricPattern::<5, _>(&mut cur, a.size * 2); @@ -251,13 +264,12 @@ pub fn EstimateModuleSize(image: &BitMatrix, a: ConcentricPattern, b: Concentric let pattern = pattern.unwrap(); - if (!(IsPattern( + if (!(IsPattern::( &PatternView::new(&PatternRow::new(pattern.to_vec())), &PATTERN, None, 0.0, 0.0, - Some(true), ) != 0.0)) { return -1.0; @@ -800,7 +812,7 @@ pub fn DetectPureQR(image: &BitMatrix) -> Result { // diagonal = BitMatrixCursorI(image, p, d).readPatternFromBlack(1, width / 3 + 1); let diag_hld = diagonal.to_vec().into(); let view = PatternView::new(&diag_hld); - if (!(IsPattern(&view, &PATTERN, None, 0.0, 0.0, None) != 0.0)) { + if (!(IsPattern::(&view, &PATTERN, None, 0.0, 0.0) != 0.0)) { return Err(Exceptions::NOT_FOUND); } } @@ -880,7 +892,7 @@ pub fn DetectPureMQR(image: &BitMatrix) -> Result { .ok_or(Exceptions::ILLEGAL_STATE)?; let diag_hld = diagonal.to_vec().into(); let view = PatternView::new(&diag_hld); - if (!(IsPattern(&view, &PATTERN, None, 0.0, 0.0, None) != 0.0)) { + if (!(IsPattern::(&view, &PATTERN, None, 0.0, 0.0) != 0.0)) { return Err(Exceptions::NOT_FOUND); } diff --git a/tests/cpp_qr_code_blackbox_tests.rs b/tests/cpp_qr_code_blackbox_tests.rs index 519643b..0360ce3 100644 --- a/tests/cpp_qr_code_blackbox_tests.rs +++ b/tests/cpp_qr_code_blackbox_tests.rs @@ -14,10 +14,7 @@ * limitations under the License. */ -use rxing::{ - qrcode::{cpp_port::QrReader}, - BarcodeFormat, -}; +use rxing::{qrcode::cpp_port::QrReader, BarcodeFormat}; mod common;