From 5a9bcfc4716aef6ea7d15d1d898e48ea5c05e601 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Wed, 25 Jan 2023 09:30:08 -0600 Subject: [PATCH] update maxicode detector with experimental features --- src/common/bit_matrix.rs | 8 +- src/maxicode/detector.rs | 307 +++++++++++++++++++++++++++++++-------- 2 files changed, 252 insertions(+), 63 deletions(-) diff --git a/src/common/bit_matrix.rs b/src/common/bit_matrix.rs index 568ad9b..bb190b3 100644 --- a/src/common/bit_matrix.rs +++ b/src/common/bit_matrix.rs @@ -218,12 +218,12 @@ impl BitMatrix { y as usize * self.row_size + (x as usize / 32) } - pub fn try_get(&self, x: u32, y: u32) -> Result { + pub fn try_get(&self, x: u32, y: u32) -> Option { let offset = self.get_offset(y, x); - if offset > self.bits.len() { - return Err(Exceptions::IndexOutOfBoundsException(None)); + if offset >= self.bits.len() { + return None; } - Ok(((self.bits[offset] >> (x & 0x1f)) & 1) != 0) + Some(((self.bits[offset] >> (x & 0x1f)) & 1) != 0) } /// Confusingly returns true if the requested element is out of bounds diff --git a/src/maxicode/detector.rs b/src/maxicode/detector.rs index 7c443de..ab56d8d 100644 --- a/src/maxicode/detector.rs +++ b/src/maxicode/detector.rs @@ -1,3 +1,5 @@ +use num::integer::Roots; + use crate::{ common::{BitMatrix, DefaultGridSampler, DetectorRXingResult, GridSampler}, Exceptions, RXingResultPoint, @@ -5,7 +7,7 @@ use crate::{ use super::MaxiCodeReader; -const ROW_SCAN_SKIP: u32 = 5; +const ROW_SCAN_SKIP: u32 = 2; #[derive(Debug)] pub struct MaxicodeDetectionResult { @@ -23,14 +25,15 @@ impl DetectorRXingResult for MaxicodeDetectionResult { } } -struct Circle { +struct Circle<'a> { center: (u32, u32), radius: u32, horizontal_buckets: [u32; 11], vertical_buckets: [u32; 11], + image: &'a BitMatrix, } -impl Circle { +impl<'a> Circle<'_> { pub fn calculate_circle_variance(&self) -> f32 { let total_width_even = self .horizontal_buckets @@ -90,7 +93,7 @@ impl Circle { let circle_area_variance = (expected_area_horizontal - circle_area_average).abs() + (expected_area_vertical - circle_area_average).abs(); - total_variance_even + total_variance_odd + circle_area_variance + (total_variance_even + total_variance_odd + circle_area_variance) / 3.0 } pub fn calculate_center_point_std_dev(circles: &[Self]) -> ((u32, u32), (u32, u32)) { @@ -116,6 +119,222 @@ impl Circle { (x_mean as u32, y_mean as u32), ) } + + /// detect a higher accuracy center point for a circle + pub fn calculate_high_accuracy_center(&mut self) { + let [point_1, point_2] = self.find_width_at_degree(7.0).1; + let point_3 = self.find_width_at_degree(97.0).1[0]; + let guessed_center_point = Self::find_center(point_1, point_2, point_3); + self.center = ( + guessed_center_point.0.round() as u32, + guessed_center_point.1.round() as u32, + ) + } + + /// detect an ellipse, and try to find defining points of it. + pub fn detect_ellipse(&self) -> ((f32, f32), ((f32, f32), (f32, f32), (f32, f32), (f32, f32))) { + if (self.image.get(self.center.0 + self.radius, self.center.1) + && self.image.get(self.center.0, self.center.1 + self.radius) + && self.image.get(self.center.0 - self.radius, self.center.1) + && self.image.get(self.center.0, self.center.1 - self.radius)) + || self.horizontal_buckets[5] == self.vertical_buckets[5] + { + // probably allready a circle, or we already have true center + + //self.center + todo!() + } else { + // this looks like an ellipse, do ellipse magic + // find semi-major and semi-minor axi + let mut lengths = [(0, 0.0, [(0.0_f32, 0.0); 2]); 72]; + let mut circle_points = Vec::new(); + for i_rotation in 0..72 { + let rotation = i_rotation as f32 * 5.0; + let (length, points) = self.find_width_at_degree(rotation); + circle_points.extend_from_slice(&points); + lengths[i_rotation] = (length, rotation, points); + } + lengths.sort_by_key(|e| e.0); + let major_axis = lengths.last().unwrap(); + let minor_axis = lengths.first().unwrap(); + + // // find foci + let linear_eccentricity = + ((major_axis.0 / 2).pow(2) - (minor_axis.0 / 2).pow(2)).sqrt(); + + if linear_eccentricity == 0 { + // it's a circle afterall, and we're probably at the center of it + + //self.center + todo!() + } else { + //it's an elipse, or we're off center, so we need to fix that problem + // let mut good_points = 0; + // let mut bad_points = 0; + let mut found_all_on_ellipse = true; + for point in &circle_points { + let check_result = Self::check_ellipse_point( + self.center, + point, + major_axis.0 / 2, + minor_axis.0 / 2, + ); + if check_result > 1.0 { + //&& check_result - Self::ALLOWABLE_ELLIPSE_SLIP > 1.0 { + // a point is off the ellipse + // bad_points += 1; + found_all_on_ellipse = false; + break; + } /*else { + good_points += 1; + }*/ + } + if !found_all_on_ellipse { + // probably a circle that we wrongly accused of being an ellipse, + // try to find the center of that circle given two points on circumference + // let point_1 = major_axis.2[0]; + // let point_2 = minor_axis.2[1]; + // let point_3 = circle_points[((major_axis.0 + 3 + minor_axis.0 + 7) / 2) as usize]; + let [point_1, point_2] = self.find_width_at_degree(0.0).1; + let point_3 = self.find_width_at_degree(90.0).1[0]; + let guessed_center_point = Self::find_center(point_1, point_2, point_3); + // ( + // guessed_center_point.0.round() as u32, + // guessed_center_point.1.round() as u32, + // ) + todo!() + } else { + // maybe an actual ellipse + todo!() + } + } + } + } + + fn find_center(p1: (f32, f32), p2: (f32, f32), p3: (f32, f32)) -> (f32, f32) { + let (x1, y1) = p1; + let (x2, y2) = p2; + let (x3, y3) = p3; + + let a = x1 * (y2 - y3) - y1 * (x2 - x3) + (x2 * y3 - x3 * y2); + let bx = (x1 * x1 + y1 * y1) * (y3 - y2) + + (x2 * x2 + y2 * y2) * (y1 - y3) + + (x3 * x3 + y3 * y3) * (y2 - y1); + let by = (x1 * x1 + y1 * y1) * (x2 - x3) + + (x2 * x2 + y2 * y2) * (x3 - x1) + + (x3 * x3 + y3 * y3) * (x1 - x2); + + let x = bx / (2.0 * a); + let y = by / (2.0 * a); + + (x.abs(), y.abs()) + } + + fn check_ellipse_point( + center: (u32, u32), + point: &(f32, f32), + semi_major_axis: u32, + semi_minor_axis: u32, + ) -> f64 { + ((point.0 as f64 - center.0 as f64).powf(2.0) / (semi_major_axis as f64).powf(2.0)) + + ((point.1 as f64 - center.1 as f64).powf(2.0) / (semi_minor_axis as f64).powf(2.0)) + } + + fn find_width_at_degree(&self, rotation: f32) -> (u32, [(f32, f32); 2]) { + let mut x = self.center.0; + let y = self.center.1; + let mut length = 0; + + // count left + while { + let point = get_point(self.center, (x, y), rotation); + !self.image.get(point.0 as u32, point.1 as u32) + } { + x -= 1; + length += 1; + } + + let x_left = x; + x = self.center.0 + 1; + + // count right + while { + let point = get_point(self.center, (x, y), rotation); + !self.image.get(point.0 as u32, point.1 as u32) + } { + x += 1; + length += 1; + } + + ( + length, + [ + get_point(self.center, (x_left, y), rotation), + get_point(self.center, (x, y), rotation), + ], + ) + } + + // fn find_cercumference(&self) -> (u32, Vec<(u32, u32)>) { + // let mut x = self.center.0; + // let mut y = self.center.1; + // let mut points = Vec::new(); + // let mut circumference = 0; + + // // back up to the left wall + // while !self.image.get(x, y) { + // x -= 1; + // } + // x -= 1; + + // // this is our first point + // points.push((x, y)); + + // let start_x = x; + // let start_y = y; + + // loop { + + // if x == start_x && y == start_y { + // break; + // } + // circumference += 1; + // points.push((x, y)); + // } + + // (circumference, points) + // } + // fn find_area(&self) -> u32 { + // let mut pixel_area = 0; + + // let mut x = self.center.0; + // let mut y = self.center.1; + + // // move to one end + // while !self.image.get(x, y) { + // y -= 1; + // } + + // // work our way to the opposite side + // while !self.image.get(x, y) { + // // count left + // x = self.center.0; + // while !self.image.get(x, y) { + // x -= 1; + // pixel_area += 1; + // } + // // count right + // x = self.center.0 + 1; + // while !self.image.get(x, y) { + // x += 1; + // pixel_area += 1; + // } + + // y += 1; + // } + + // pixel_area + // } } pub fn detect(image: &BitMatrix, try_harder: bool) -> Result { @@ -138,7 +357,7 @@ pub fn detect(image: &BitMatrix, try_harder: bool) -> Result Option> { radius, horizontal_buckets, vertical_buckets, + image, }); // update the search to the next possible location @@ -506,7 +726,7 @@ const LEFT_SHIFT_PERCENT_ADJUST: f32 = 0.0; const RIGHT_SHIFT_PERCENT_ADJUST: f32 = 0.03; const ACCEPTED_SCALES: [f64; 5] = [0.065, 0.069, 0.07, 0.075, 0.08]; -fn box_symbol(image: &BitMatrix, circle: &Circle) -> Result<[(f32, f32); 4], Exceptions> { +fn box_symbol(image: &BitMatrix, circle: &mut Circle) -> Result<[(f32, f32); 4], Exceptions> { let (left_boundary, right_boundary, top_boundary, bottom_boundary) = calculate_simple_boundary(circle, Some(image), None); @@ -519,7 +739,7 @@ fn box_symbol(image: &BitMatrix, circle: &Circle) -> Result<[(f32, f32); 4], Exc let mut result_box = naive_box; - #[cfg(feature="experimental_features")] + #[cfg(feature = "experimental_features")] for scale in ACCEPTED_SCALES { if let Some(found_rotation) = attempt_rotation_box(image, circle, &naive_box, scale) { result_box = found_rotation; @@ -591,17 +811,11 @@ const BOTTOM_RIGHT_ORIENTATION_POS: ((u32, u32), (u32, u32), (u32, u32)) = fn attempt_rotation_box( image: &BitMatrix, - circle: &Circle, + circle: &mut Circle, naive_box: &[RXingResultPoint; 4], center_scale: f64, ) -> Option<[RXingResultPoint; 4]> { - // use the expected symbol syze to calculate the x,y module size (best guess) - // let (symbol_width, symbol_height) = - // guess_barcode_size_general(circle, 0.03, center_scale, 0.97); //guess_barcode_size_tighter(circle); - // let x_module_size = (symbol_width as f32 / MaxiCodeReader::MATRIX_WIDTH as f32).round() as u32; - // let y_module_size = - // (symbol_height as f32 / MaxiCodeReader::MATRIX_HEIGHT as f32).round() as u32; - + circle.calculate_high_accuracy_center(); // we know that the locator symbols should appear at 60 degree increments around the circle // top left @@ -637,9 +851,9 @@ fn attempt_rotation_box( let p1_rot = get_point(circle.center, topl_p1, rotation); let p2_rot = get_point(circle.center, topl_p2, rotation); let p3_rot = get_point(circle.center, topl_p3, rotation); - let found_tl = image.get(p1_rot.0 as u32, p1_rot.1 as u32) - && image.get(p2_rot.0 as u32, p2_rot.1 as u32) - && image.get(p3_rot.0 as u32, p3_rot.1 as u32); + let found_tl = image.try_get(p1_rot.0 as u32, p1_rot.1 as u32)? + && image.try_get(p2_rot.0 as u32, p2_rot.1 as u32)? + && image.try_get(p3_rot.0 as u32, p3_rot.1 as u32)?; if !found_tl { continue; } @@ -650,9 +864,9 @@ fn attempt_rotation_box( let p1_rot = get_point(circle.center, topr_p1, rotation); let p2_rot = get_point(circle.center, topr_p2, rotation); let p3_rot = get_point(circle.center, topr_p3, rotation); - let found_tr = !image.get(p1_rot.0 as u32, p1_rot.1 as u32) - && !image.get(p2_rot.0 as u32, p2_rot.1 as u32) - && !image.get(p3_rot.0 as u32, p3_rot.1 as u32); + let found_tr = !image.try_get(p1_rot.0 as u32, p1_rot.1 as u32)? + && !image.try_get(p2_rot.0 as u32, p2_rot.1 as u32)? + && !image.try_get(p3_rot.0 as u32, p3_rot.1 as u32)?; if !found_tr { continue; } @@ -663,9 +877,9 @@ fn attempt_rotation_box( let p1_rot = get_point(circle.center, l_p1, rotation); let p2_rot = get_point(circle.center, l_p2, rotation); let p3_rot = get_point(circle.center, l_p3, rotation); - let found_l = image.get(p1_rot.0 as u32, p1_rot.1 as u32) - && !image.get(p2_rot.0 as u32, p2_rot.1 as u32) - && image.get(p3_rot.0 as u32, p3_rot.1 as u32); + let found_l = image.try_get(p1_rot.0 as u32, p1_rot.1 as u32)? + && !image.try_get(p2_rot.0 as u32, p2_rot.1 as u32)? + && image.try_get(p3_rot.0 as u32, p3_rot.1 as u32)?; if !found_l { continue; } @@ -676,9 +890,9 @@ fn attempt_rotation_box( let p1_rot = get_point(circle.center, r_p1, rotation); let p2_rot = get_point(circle.center, r_p2, rotation); let p3_rot = get_point(circle.center, r_p3, rotation); - let found_r = image.get(p1_rot.0 as u32, p1_rot.1 as u32) - && !image.get(p2_rot.0 as u32, p2_rot.1 as u32) - && image.get(p3_rot.0 as u32, p3_rot.1 as u32); + let found_r = image.try_get(p1_rot.0 as u32, p1_rot.1 as u32)? + && !image.try_get(p2_rot.0 as u32, p2_rot.1 as u32)? + && image.try_get(p3_rot.0 as u32, p3_rot.1 as u32)?; if !found_r { continue; } @@ -689,9 +903,9 @@ fn attempt_rotation_box( let p1_rot = get_point(circle.center, bottoml_p1, rotation); let p2_rot = get_point(circle.center, bottoml_p2, rotation); let p3_rot = get_point(circle.center, bottoml_p3, rotation); - let found_bl = image.get(p1_rot.0 as u32, p1_rot.1 as u32) - && !image.get(p2_rot.0 as u32, p2_rot.1 as u32) - && image.get(p3_rot.0 as u32, p3_rot.1 as u32); + let found_bl = image.try_get(p1_rot.0 as u32, p1_rot.1 as u32)? + && !image.try_get(p2_rot.0 as u32, p2_rot.1 as u32)? + && image.try_get(p3_rot.0 as u32, p3_rot.1 as u32)?; if !found_bl { continue; } @@ -702,9 +916,9 @@ fn attempt_rotation_box( let p1_rot = get_point(circle.center, bottomr_p1, rotation); let p2_rot = get_point(circle.center, bottomr_p2, rotation); let p3_rot = get_point(circle.center, bottomr_p3, rotation); - let found_br = image.get(p1_rot.0 as u32, p1_rot.1 as u32) - && !image.get(p2_rot.0 as u32, p2_rot.1 as u32) - && image.get(p3_rot.0 as u32, p3_rot.1 as u32); + let found_br = image.try_get(p1_rot.0 as u32, p1_rot.1 as u32)? + && !image.try_get(p2_rot.0 as u32, p2_rot.1 as u32)? + && image.try_get(p3_rot.0 as u32, p3_rot.1 as u32)?; if !found_br { continue; } @@ -798,13 +1012,6 @@ fn adjust_point_alternate(point: (u32, u32), circle: &Circle, center_scale: f64) /// calculate a likely size for the barcode. /// returns (width, height) fn guess_barcode_size(circle: &Circle) -> (u32, u32) { - // let circle_area = std::f64::consts::PI * circle.radius.pow(2) as f64; - // let ideal_symbol_area = (circle_area / 0.065) / 0.97; - // let ideal_symbol_side = ideal_symbol_area.sqrt(); - // ( - // ideal_symbol_side.round() as u32, - // (ideal_symbol_side).round() as u32, - // ) guess_barcode_size_general(circle, 0.03, 0.066, 1.0) } @@ -812,11 +1019,6 @@ fn guess_barcode_size_tighter(circle: &Circle) -> (u32, u32) { guess_barcode_size_general(circle, 0.03, 0.0695, 0.97) } -// fn guess_barcode_size(circle: &Circle) -> (u32, u32) { -// let diameter = circle.horizontal_buckets.iter().sum::() as f32; -// ((diameter / 0.29) as u32, ((diameter / 0.29) * 0.97) as u32) -// } - fn guess_barcode_size_general( circle: &Circle, height_adjust_percent: f64, @@ -827,10 +1029,6 @@ fn guess_barcode_size_general( let ideal_symbol_area = (circle_area / circle_area_percent) / (1.0 - height_adjust_percent); let ideal_symbol_side = ideal_symbol_area.sqrt(); - // let estimated_module_x = ideal_symbol_side / MaxiCodeReader::MATRIX_WIDTH as f64; - // let estimated_module_y = - // (ideal_symbol_side * height_final_adjust_percent) / MaxiCodeReader::MATRIX_HEIGHT as f64; - ( ideal_symbol_side.round() as u32, (ideal_symbol_side * height_final_adjust_percent).round() as u32, @@ -842,15 +1040,7 @@ fn compare_circle(a: &Circle, b: &Circle) -> std::cmp::Ordering { let a_var = a.calculate_circle_variance(); let b_var = b.calculate_circle_variance(); - // a_var.partial_cmp(&b_var).unwrap() - - if a_var < b_var { - std::cmp::Ordering::Greater - } else if a_var > b_var { - std::cmp::Ordering::Less - } else { - std::cmp::Ordering::Equal - } + a_var.partial_cmp(&b_var).unwrap() } /// Read appropriate bits from a bitmatrix for the maxicode decoder @@ -868,7 +1058,6 @@ pub fn read_bits(image: &BitMatrix) -> Result { // for (int y = 0; y < MATRIX_HEIGHT; y++) { let iy = (top + (y * height + height / 2) / MaxiCodeReader::MATRIX_HEIGHT).min(height - 1); for x in 0..MaxiCodeReader::MATRIX_WIDTH { - // for (int x = 0; x < MATRIX_WIDTH; x++) { // srowen: I don't quite understand why the formula below is necessary, but it // can walk off the image if left + width = the right boundary. So cap it. let ix = left