Promote Quadrilateral to default

The library now uses the `Quadrilateral` struct when handling sets of four points in `GridSampler` and `PerspectiveTransform`
This commit is contained in:
Henry Schimke
2023-03-08 11:17:43 -06:00
parent d5e6a5d0a7
commit 2b7d053646
13 changed files with 348 additions and 422 deletions

View File

@@ -18,7 +18,7 @@ use crate::{
common::{ common::{
detector::WhiteRectangleDetector, detector::WhiteRectangleDetector,
reedsolomon::{self, ReedSolomonDecoder}, reedsolomon::{self, ReedSolomonDecoder},
BitMatrix, DefaultGridSampler, GridSampler, Result, BitMatrix, DefaultGridSampler, GridSampler, Result, Quadrilateral,
}, },
exceptions::Exceptions, exceptions::Exceptions,
point, Point, point, Point,
@@ -89,13 +89,15 @@ impl<'a> Detector<'_> {
// 3. Get the size of the matrix and other parameters from the bull's eye // 3. Get the size of the matrix and other parameters from the bull's eye
self.extractParameters(&bulls_eye_corners)?; self.extractParameters(&bulls_eye_corners)?;
let src_quad = Quadrilateral::new(bulls_eye_corners[self.shift as usize % 4],
bulls_eye_corners[(self.shift as usize + 1) % 4],
bulls_eye_corners[(self.shift as usize + 2) % 4],
bulls_eye_corners[(self.shift as usize + 3) % 4],);
// 4. Sample the grid // 4. Sample the grid
let bits = self.sample_grid( let bits = self.sample_grid(
self.image, self.image,
bulls_eye_corners[self.shift as usize % 4], src_quad
bulls_eye_corners[(self.shift as usize + 1) % 4],
bulls_eye_corners[(self.shift as usize + 2) % 4],
bulls_eye_corners[(self.shift as usize + 3) % 4],
)?; )?;
// 5. Get the corners of the matrix. // 5. Get the corners of the matrix.
@@ -457,10 +459,7 @@ impl<'a> Detector<'_> {
fn sample_grid( fn sample_grid(
&self, &self,
image: &BitMatrix, image: &BitMatrix,
top_left: Point, quad: Quadrilateral,
top_right: Point,
bottom_right: Point,
bottom_left: Point,
) -> Result<BitMatrix> { ) -> Result<BitMatrix> {
let sampler = DefaultGridSampler::default(); let sampler = DefaultGridSampler::default();
let dimension = self.get_dimension(); let dimension = self.get_dimension();
@@ -468,26 +467,13 @@ impl<'a> Detector<'_> {
let low = dimension as f32 / 2.0 - self.nb_center_layers as f32; let low = dimension as f32 / 2.0 - self.nb_center_layers as f32;
let high = dimension as f32 / 2.0 + self.nb_center_layers as f32; let high = dimension as f32 / 2.0 + self.nb_center_layers as f32;
let dst = Quadrilateral::new(point(low,low), point(high,low), point(high,high), point(low,high));
sampler.sample_grid_detailed( sampler.sample_grid_detailed(
image, image,
dimension, dimension,
dimension, dimension,
low, dst, quad
low, // topleft
high,
low, // topright
high,
high, // bottomright
low,
high, // bottomleft
top_left.x,
top_left.y,
top_right.x,
top_right.y,
bottom_right.x,
bottom_right.y,
bottom_left.x,
bottom_left.y,
) )
} }

View File

@@ -19,17 +19,20 @@
// import org.junit.Assert; // import org.junit.Assert;
// import org.junit.Test; // import org.junit.Test;
use crate::point;
/** /**
* @author Sean Owen * @author Sean Owen
*/ */
// public final class PerspectiveTransformTestCase extends Assert { // public final class PerspectiveTransformTestCase extends Assert {
use super::PerspectiveTransform; use super::{PerspectiveTransform, Quadrilateral};
static EPSILON: f32 = 1.0E-4f32; static EPSILON: f32 = 1.0E-4f32;
#[test] #[test]
fn test_square_to_quadrilateral() { fn test_square_to_quadrilateral() {
let pt = PerspectiveTransform::squareToQuadrilateral(2.0, 3.0, 10.0, 4.0, 16.0, 15.0, 4.0, 9.0); let square = Quadrilateral::new(point(2.0, 3.0), point(10.0, 3.0), point(16.0, 15.0), point(4.0,9.0));
let pt = PerspectiveTransform::squareToQuadrilateral(square);
assert_point_equals(2.0, 3.0, 0.0, 0.0, &pt); assert_point_equals(2.0, 3.0, 0.0, 0.0, &pt);
assert_point_equals(10.0, 4.0, 1.0, 0.0, &pt); assert_point_equals(10.0, 4.0, 1.0, 0.0, &pt);
assert_point_equals(4.0, 9.0, 0.0, 1.0, &pt); assert_point_equals(4.0, 9.0, 0.0, 1.0, &pt);
@@ -40,10 +43,10 @@ fn test_square_to_quadrilateral() {
#[test] #[test]
fn test_quadrilateral_to_quadrilateral() { fn test_quadrilateral_to_quadrilateral() {
let pt = PerspectiveTransform::quadrilateralToQuadrilateral( let quad1 = Quadrilateral::new(point(2.0, 3.0), point(10.0, 4.0), point(16.0, 15.0), point(4.0, 9.0));
2.0, 3.0, 10.0, 4.0, 16.0, 15.0, 4.0, 9.0, 103.0, 110.0, 300.0, 120.0, 290.0, 270.0, 150.0, let quad2 = Quadrilateral::new(point(103.0, 110.0), point(300.0, 120.0), point(290.0, 270.0), point(150.0,280.0));
280.0, let pt = PerspectiveTransform::quadrilateralToQuadrilateral(quad1, quad2);
);
assert_point_equals(103.0, 110.0, 2.0, 3.0, &pt); assert_point_equals(103.0, 110.0, 2.0, 3.0, &pt);
assert_point_equals(300.0, 120.0, 10.0, 4.0, &pt); assert_point_equals(300.0, 120.0, 10.0, 4.0, &pt);
assert_point_equals(290.0, 270.0, 16.0, 15.0, &pt); assert_point_equals(290.0, 270.0, 16.0, 15.0, &pt);

View File

@@ -18,10 +18,10 @@
// import com.google.zxing.NotFoundException; // import com.google.zxing.NotFoundException;
use crate::common::Result; use crate::{common::Result};
use crate::Exceptions; use crate::Exceptions;
use super::{BitMatrix, GridSampler, PerspectiveTransform}; use super::{BitMatrix, GridSampler, PerspectiveTransform, Quadrilateral, SamplerControl};
/** /**
* @author Sean Owen * @author Sean Owen
@@ -35,29 +35,14 @@ impl GridSampler for DefaultGridSampler {
image: &BitMatrix, image: &BitMatrix,
dimensionX: u32, dimensionX: u32,
dimensionY: u32, dimensionY: u32,
p1ToX: f32, dst: Quadrilateral,
p1ToY: f32, src:Quadrilateral
p2ToX: f32,
p2ToY: f32,
p3ToX: f32,
p3ToY: f32,
p4ToX: f32,
p4ToY: f32,
p1FromX: f32,
p1FromY: f32,
p2FromX: f32,
p2FromY: f32,
p3FromX: f32,
p3FromY: f32,
p4FromX: f32,
p4FromY: f32,
) -> Result<BitMatrix> { ) -> Result<BitMatrix> {
let transform = PerspectiveTransform::quadrilateralToQuadrilateral( let transform = PerspectiveTransform::quadrilateralToQuadrilateral(
p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1FromX, p1FromY, p2FromX, dst, src,
p2FromY, p3FromX, p3FromY, p4FromX, p4FromY,
); );
self.sample_grid(image, dimensionX, dimensionY, &transform) self.sample_grid(image, dimensionX, dimensionY, &[SamplerControl::new(dimensionX, dimensionY, transform)])
} }
fn sample_grid( fn sample_grid(
@@ -65,7 +50,7 @@ impl GridSampler for DefaultGridSampler {
image: &BitMatrix, image: &BitMatrix,
dimensionX: u32, dimensionX: u32,
dimensionY: u32, dimensionY: u32,
transform: &PerspectiveTransform, controls: &[SamplerControl]
) -> Result<BitMatrix> { ) -> Result<BitMatrix> {
if dimensionX == 0 || dimensionY == 0 { if dimensionX == 0 || dimensionY == 0 {
return Err(Exceptions::NOT_FOUND); return Err(Exceptions::NOT_FOUND);
@@ -83,7 +68,7 @@ impl GridSampler for DefaultGridSampler {
points[x + 1] = i_value; points[x + 1] = i_value;
x += 2; x += 2;
} }
transform.transform_points_single(&mut points); controls.first().unwrap().transform.transform_points_single(&mut points);
// Quick check to see if points transformed to something inside the image; // Quick check to see if points transformed to something inside the image;
// sufficient to check the endpoints // sufficient to check the endpoints
self.checkAndNudgePoints(image, &mut points)?; self.checkAndNudgePoints(image, &mut points)?;

View File

@@ -18,10 +18,10 @@
// import com.google.zxing.NotFoundException; // import com.google.zxing.NotFoundException;
use crate::common::Result; use crate::{common::Result, Point};
use crate::Exceptions; use crate::{Exceptions, point};
use super::{BitMatrix, PerspectiveTransform}; use super::{BitMatrix, PerspectiveTransform, Quadrilateral};
/** /**
* Implementations of this class can, given locations of finder patterns for a QR code in an * Implementations of this class can, given locations of finder patterns for a QR code in an
@@ -93,22 +93,8 @@ pub trait GridSampler {
image: &BitMatrix, image: &BitMatrix,
dimensionX: u32, dimensionX: u32,
dimensionY: u32, dimensionY: u32,
p1ToX: f32, dst: Quadrilateral,
p1ToY: f32, src:Quadrilateral
p2ToX: f32,
p2ToY: f32,
p3ToX: f32,
p3ToY: f32,
p4ToX: f32,
p4ToY: f32,
p1FromX: f32,
p1FromY: f32,
p2FromX: f32,
p2FromY: f32,
p3FromX: f32,
p3FromY: f32,
p4FromX: f32,
p4FromY: f32,
) -> Result<BitMatrix>; ) -> Result<BitMatrix>;
fn sample_grid( fn sample_grid(
@@ -116,7 +102,7 @@ pub trait GridSampler {
image: &BitMatrix, image: &BitMatrix,
dimensionX: u32, dimensionX: u32,
dimensionY: u32, dimensionY: u32,
transform: &PerspectiveTransform, controls: &[SamplerControl]
) -> Result<BitMatrix>; ) -> Result<BitMatrix>;
/** /**
@@ -195,3 +181,15 @@ pub trait GridSampler {
Ok(()) Ok(())
} }
} }
pub struct SamplerControl {
pub p0: Point,
pub p1: Point,
pub transform: PerspectiveTransform
}
impl SamplerControl {
pub fn new( width: u32, height: u32, transform: PerspectiveTransform ) -> Self {
Self{ p0: point(0.0, 0.0), p1: point(width as f32, height as f32), transform }
}
}

View File

@@ -109,6 +109,9 @@ pub use hybrid_binarizer::*;
mod eci; mod eci;
pub use eci::*; pub use eci::*;
mod quad;
pub use quad::*;
#[cfg(feature = "otsu_level")] #[cfg(feature = "otsu_level")]
mod otsu_level_binarizer; mod otsu_level_binarizer;
#[cfg(feature = "otsu_level")] #[cfg(feature = "otsu_level")]

View File

@@ -16,6 +16,12 @@
// package com.google.zxing.common; // package com.google.zxing.common;
use std::ops::Mul;
use crate::point;
use super::Quadrilateral;
/** /**
* <p>This class implements a perspective transform in two dimensions. Given four source and four * <p>This class implements a perspective transform in two dimensions. Given four source and four
* destination points, it will compute the transformation implied between them. The code is based * destination points, it will compute the transformation implied between them. The code is based
@@ -63,27 +69,18 @@ impl PerspectiveTransform {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn quadrilateralToQuadrilateral( pub fn quadrilateralToQuadrilateral(
x0: f32, dst: Quadrilateral,
y0: f32, src: Quadrilateral
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
x0p: f32,
y0p: f32,
x1p: f32,
y1p: f32,
x2p: f32,
y2p: f32,
x3p: f32,
y3p: f32,
) -> Self { ) -> Self {
let q_to_s = PerspectiveTransform::quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3);
// let q_to_s = PerspectiveTransform::quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3);
// let s_to_q =
// PerspectiveTransform::squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p);
let q_to_s = PerspectiveTransform::quadrilateralToSquare(dst);
let s_to_q = let s_to_q =
PerspectiveTransform::squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p); PerspectiveTransform::squareToQuadrilateral(src);
s_to_q.times(&q_to_s) s_to_q * q_to_s
} }
pub fn transform_points_single(&self, points: &mut [f32]) { pub fn transform_points_single(&self, points: &mut [f32]) {
@@ -122,35 +119,29 @@ impl PerspectiveTransform {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn squareToQuadrilateral( pub fn squareToQuadrilateral(
x0: f32, square: Quadrilateral
y0: f32,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
) -> Self { ) -> Self {
let dx3 = x0 - x1 + x2 - x3; let [p0, p1, p2, p3 ] = square.0;
let dy3 = y0 - y1 + y2 - y3;
if dx3 == 0.0 && dy3 == 0.0 { let d3 = p0 - p1 + p2 - p3;
if d3 == point(0.0,0.0) {
// Affine // Affine
PerspectiveTransform::new(x1 - x0, x2 - x1, x0, y1 - y0, y2 - y1, y0, 0.0, 0.0, 1.0) PerspectiveTransform::new(p1.x - p0.x, p2.x - p1.x, p0.x, p1.y - p0.y, p2.y - p1.y, p0.y, 0.0, 0.0, 1.0)
} else { } else {
let dx1 = x1 - x2;
let dx2 = x3 - x2; let d1 = p1 - p2;
let dy1 = y1 - y2; let d2 = p3 - p2;
let dy2 = y3 - y2;
let denominator = dx1 * dy2 - dx2 * dy1; let denominator = d1.cross(d2);
let a13 = (dx3 * dy2 - dx2 * dy3) / denominator; let a13 = (d3.x * d2.y - d2.x * d3.y) / denominator;
let a23 = (dx1 * dy3 - dx3 * dy1) / denominator; let a23 = (d1.x * d3.y - d3.x * d1.y) / denominator;
PerspectiveTransform::new( PerspectiveTransform::new(
x1 - x0 + a13 * x1, p1.x - p0.x + a13 * p1.x,
x3 - x0 + a23 * x3, p3.x - p0.x + a23 * p3.x,
x0, p0.x,
y1 - y0 + a13 * y1, p1.y - p0.y + a13 * p1.y,
y3 - y0 + a23 * y3, p3.y - p0.y + a23 * p3.y,
y0, p0.y,
a13, a13,
a23, a23,
1.0, 1.0,
@@ -160,17 +151,10 @@ impl PerspectiveTransform {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn quadrilateralToSquare( pub fn quadrilateralToSquare(
x0: f32, quad: Quadrilateral
y0: f32,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
) -> Self { ) -> Self {
// Here, the adjoint serves as the inverse // Here, the adjoint serves as the inverse
PerspectiveTransform::squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint() PerspectiveTransform::squareToQuadrilateral(quad).buildAdjoint()
} }
fn buildAdjoint(&self) -> Self { fn buildAdjoint(&self) -> Self {
@@ -187,18 +171,22 @@ impl PerspectiveTransform {
self.a11 * self.a22 - self.a12 * self.a21, self.a11 * self.a22 - self.a12 * self.a21,
) )
} }
}
fn times(&self, other: &Self) -> Self { impl Mul for PerspectiveTransform {
type Output = PerspectiveTransform;
fn mul(self, rhs: Self) -> Self::Output {
PerspectiveTransform::new( PerspectiveTransform::new(
self.a11 * other.a11 + self.a21 * other.a12 + self.a31 * other.a13, self.a11 * rhs.a11 + self.a21 * rhs.a12 + self.a31 * rhs.a13,
self.a11 * other.a21 + self.a21 * other.a22 + self.a31 * other.a23, self.a11 * rhs.a21 + self.a21 * rhs.a22 + self.a31 * rhs.a23,
self.a11 * other.a31 + self.a21 * other.a32 + self.a31 * other.a33, self.a11 * rhs.a31 + self.a21 * rhs.a32 + self.a31 * rhs.a33,
self.a12 * other.a11 + self.a22 * other.a12 + self.a32 * other.a13, self.a12 * rhs.a11 + self.a22 * rhs.a12 + self.a32 * rhs.a13,
self.a12 * other.a21 + self.a22 * other.a22 + self.a32 * other.a23, self.a12 * rhs.a21 + self.a22 * rhs.a22 + self.a32 * rhs.a23,
self.a12 * other.a31 + self.a22 * other.a32 + self.a32 * other.a33, self.a12 * rhs.a31 + self.a22 * rhs.a32 + self.a32 * rhs.a33,
self.a13 * other.a11 + self.a23 * other.a12 + self.a33 * other.a13, self.a13 * rhs.a11 + self.a23 * rhs.a12 + self.a33 * rhs.a13,
self.a13 * other.a21 + self.a23 * other.a22 + self.a33 * other.a23, self.a13 * rhs.a21 + self.a23 * rhs.a22 + self.a33 * rhs.a23,
self.a13 * other.a31 + self.a23 * other.a32 + self.a33 * other.a33, self.a13 * rhs.a31 + self.a23 * rhs.a32 + self.a33 * rhs.a33,
) )
} }
} }

217
src/common/quad.rs Normal file
View File

@@ -0,0 +1,217 @@
use crate::Point;
#[derive(Clone, Copy, Debug)]
pub struct Quadrilateral(pub [Point; 4]);
impl Quadrilateral {
// using Base = std::array<T, 4>;
// using Base::at;
// public:
// using Point = T;
#[allow(dead_code)]
pub fn new(tl:Point, tr: Point, br: Point, bl: Point) -> Self {
Self([tl,tr,br,bl])
}
// pub fn with_f32( tl:f32, tr:f32, br:f32, bl:f32) -> Self {
// Self([tl, tr,br, bl ])
// }
pub fn with_points(tl: Point, tr: Point, br: Point, bl: Point) -> Self {
Self([tl, tr, br, bl])
}
pub fn top_left(&self) -> &Point {
&self.0[0]
} //const noexcept { return at(0); }
pub fn top_right(&self) -> &Point {
&self.0[1]
} //const noexcept { return at(1); }
pub fn bottom_right(&self) -> &Point {
&self.0[2]
} //const noexcept { return at(2); }
pub fn bottom_left(&self) -> &Point {
&self.0[3]
} //const noexcept { return at(3); }
#[allow(dead_code)]
pub fn orientation(&self) -> f64 {
let centerLine =
(*self.top_right() + *self.bottom_right()) - (*self.top_left() + *self.bottom_left());
if (centerLine == Point { x: 0.0, y: 0.0 }) {
return 0.0;
}
let centerLineF = Point::normalized(centerLine);
f32::atan2(centerLineF.y, centerLineF.x).into()
}
pub fn points(&self) -> &[Point] {
&self.0
}
}
impl Quadrilateral {
#[allow(dead_code)]
pub fn rectangle(width: i32, height: i32, margin: Option<i32>) -> Quadrilateral {
let margin = margin.unwrap_or(0);
Quadrilateral([
Point {
x: margin as f32,
y: margin as f32,
},
Point {
x: width as f32 - margin as f32,
y: margin as f32,
},
Point {
x: width as f32 - margin as f32,
y: height as f32 - margin as f32,
},
Point {
x: margin as f32,
y: height as f32 - margin as f32,
},
])
}
#[allow(dead_code)]
pub fn centered_square(size: i32) -> Quadrilateral {
Self::scale(
&Quadrilateral([
Point { x: -1.0, y: -1.0 },
Point { x: 1.0, y: -1.0 },
Point { x: 1.0, y: 1.0 },
Point { x: -1.0, y: 1.0 },
]),
size / 2,
)
}
#[allow(dead_code)]
pub fn line(y: i32, xStart: i32, xStop: i32) -> Quadrilateral {
Quadrilateral([
Point {
x: xStart as f32,
y: y as f32,
},
Point {
x: xStop as f32,
y: y as f32,
},
Point {
x: xStop as f32,
y: y as f32,
},
Point {
x: xStart as f32,
y: y as f32,
},
])
}
#[allow(dead_code)]
pub fn is_convex(&self) -> bool {
let N = self.0.len();
let mut sign = false;
let mut m = f32::INFINITY;
let mut M = 0.0_f32;
for i in 0..N
// for(int i = 0; i < N; i++)
{
let d1 = self.0[(i + 2) % N] - self.0[(i + 1) % N];
let d2 = self.0[i] - self.0[(i + 1) % N];
let cp = d1.cross(d2);
m = if m.abs() > cp { cp } else { m.abs() };
M = if M.abs() > cp { M.abs() } else { cp };
// m = std::cmp::min((m).abs(), cp);
// M = std::cmp::max((M).abs(), cp);
if i == 0 {
sign = cp > 0.0;
} else if sign != (cp > 0.0) {
return false;
}
}
// It turns out being convex is not enough to prevent a "numerical instability"
// that can cause the corners being projected inside the image boundaries but
// some points near the corners being projected outside. This has been observed
// where one corner is almost in line with two others. The M/m ratio is below 2
// for the complete existing sample set. For very "skewed" QRCodes a value of
// around 3 is realistic. A value of 14 has been observed to trigger the
// instability.
M / m < 4.0
}
#[allow(dead_code)]
pub fn scale(&self, factor: i32) -> Quadrilateral {
Quadrilateral([
self.0[0] * factor as f32,
self.0[1] * factor as f32,
self.0[2] * factor as f32,
self.0[3] * factor as f32,
])
}
#[allow(dead_code)]
pub fn center(&self) -> Point {
let reduced: Point = self.0.iter().sum();
let size = self.0.len() as f32;
reduced / size
// return Reduce(q) / Size(q);
}
#[allow(dead_code)]
pub fn rotated_corners(&self, n: Option<i32>, mirror: Option<bool>) -> Quadrilateral {
let n = if let Some(n) = n { n } else { 1 };
let mirror = if let Some(m) = mirror { m } else { false };
let mut res = self.clone();
res.0.rotate_left(((n + 4) % 4) as usize);
// std::rotate_copy(q.begin(), q.begin() + ((n + 4) % 4), q.end(), res.begin());
if mirror {
res.0.swap(1, 3);
}
// {std::swap(res[1], res[3]);}
res
}
#[allow(dead_code)]
pub fn is_inside(&self, p: Point) -> bool {
// Test if p is on the same side (right or left) of all polygon segments
let mut pos = 0;
let mut neg = 0;
for i in 0..self.0.len()
// for (int i = 0; i < Size(q); ++i)
{
if Point::cross(p - self.0[i], self.0[(i + 1) % self.0.len()] - self.0[i]) < 0.0 {
neg += 1;
} else {
pos += 1;
}
// (cross(p - q[i], q[(i + 1) % Size(q)] - q[i]) < 0 ? neg : pos)++;
}
pos == 0 || neg == 0
}
#[allow(dead_code)]
pub fn have_intersecting_bounding_boxes(&self, b: &Quadrilateral) -> bool {
// TODO: this is only a quick and dirty approximation that works for the trivial standard cases
let x = b.top_right().x < self.top_left().x || b.top_left().x > self.top_right().x;
let y = b.bottom_left().y < self.top_left().y || b.top_left().y > self.bottom_left().y;
!(x || y)
}
}
impl Default for Quadrilateral {
fn default() -> Self {
Self([Point { x: 0.0, y: 0.0 }; 4])
}
}

View File

@@ -16,7 +16,7 @@
use crate::{ use crate::{
common::{ common::{
detector::WhiteRectangleDetector, BitMatrix, DefaultGridSampler, GridSampler, Result, detector::WhiteRectangleDetector, BitMatrix, DefaultGridSampler, GridSampler, Result, Quadrilateral,
}, },
point, Exceptions, Point, point, Exceptions, Point,
}; };
@@ -334,26 +334,14 @@ impl<'a> Detector<'_> {
) -> Result<BitMatrix> { ) -> Result<BitMatrix> {
let sampler = DefaultGridSampler::default(); let sampler = DefaultGridSampler::default();
let dst = Quadrilateral::new(point(0.5, 0.5), point(dimensionX as f32 - 0.5, 0.5), point(dimensionX as f32 - 0.5,dimensionY as f32 - 0.5), point(0.5, dimensionY as f32 - 0.5));
let src = Quadrilateral::new(topRight, topLeft,bottomRight, bottomLeft);
sampler.sample_grid_detailed( sampler.sample_grid_detailed(
image, image,
dimensionX, dimensionX,
dimensionY, dimensionY,
0.5, dst, src
0.5,
dimensionX as f32 - 0.5,
0.5,
dimensionX as f32 - 0.5,
dimensionY as f32 - 0.5,
0.5,
dimensionY as f32 - 0.5,
topLeft.x,
topLeft.y,
topRight.x,
topRight.y,
bottomRight.x,
bottomRight.y,
bottomLeft.x,
bottomLeft.y,
) )
} }

View File

@@ -14,13 +14,13 @@ macro_rules! CHECK {
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
use crate::{ use crate::{
common::{BitMatrix, DefaultGridSampler, GridSampler, Result}, common::{BitMatrix, DefaultGridSampler, GridSampler, Result, Quadrilateral},
datamatrix::detector::{ datamatrix::detector::{
zxing_cpp_detector::{util::intersect, BitMatrixCursor, Quadrilateral, RegressionLine}, zxing_cpp_detector::{util::intersect, BitMatrixCursor, RegressionLine},
DatamatrixDetectorResult, DatamatrixDetectorResult,
}, },
qrcode::encoder::ByteMatrix, qrcode::encoder::ByteMatrix,
Exceptions, Point, Exceptions, Point, point,
}; };
use super::{DMRegressionLine, EdgeTracer}; use super::{DMRegressionLine, EdgeTracer};
@@ -212,27 +212,14 @@ fn Scan(
let grid_sampler = DefaultGridSampler::default(); let grid_sampler = DefaultGridSampler::default();
// let transform = PerspectiveTransform::quadrilateralToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3, x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p); // let transform = PerspectiveTransform::quadrilateralToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3, x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p);
let dst = Quadrilateral::new(point(0.0,0.0), point(dimT as f32, 0.0), point(dimT as f32, dimR as f32), point(0.0, dimR as f32));
let src = sourcePoints;
let res = grid_sampler.sample_grid_detailed( let res = grid_sampler.sample_grid_detailed(
startTracer.img, startTracer.img,
dimT as u32, dimT as u32,
dimR as u32, dimR as u32,
0.0, dst, src
0.0,
dimT as f32,
0.0,
dimT as f32,
dimR as f32,
0.0,
dimR as f32,
sourcePoints.topLeft().x,
sourcePoints.topLeft().y,
sourcePoints.topRight().x,
sourcePoints.topRight().y,
sourcePoints.bottomRight().x,
sourcePoints.bottomRight().y,
sourcePoints.bottomLeft().x,
sourcePoints.bottomLeft().y,
); );
// let res = grid_sampler.sample_grid(startTracer.img, dimT as u32, dimR as u32, &transform); // let res = grid_sampler.sample_grid(startTracer.img, dimT as u32, dimR as u32, &transform);

View File

@@ -3,7 +3,6 @@ mod cpp_new_detector;
mod direction; mod direction;
mod dm_regression_line; mod dm_regression_line;
mod edge_tracer; mod edge_tracer;
mod quad;
mod regression_line; mod regression_line;
mod step_result; mod step_result;
pub(self) mod util; pub(self) mod util;
@@ -14,7 +13,6 @@ pub use cpp_new_detector::detect;
pub(self) use direction::*; pub(self) use direction::*;
pub(self) use dm_regression_line::*; pub(self) use dm_regression_line::*;
pub(self) use edge_tracer::*; pub(self) use edge_tracer::*;
pub(self) use quad::*;
pub(self) use regression_line::*; pub(self) use regression_line::*;
pub(self) use step_result::*; pub(self) use step_result::*;
pub(self) use value::*; pub(self) use value::*;

View File

@@ -1,209 +0,0 @@
use crate::Point;
#[derive(Clone, Debug)]
pub struct Quadrilateral([Point; 4]);
impl Quadrilateral {
// using Base = std::array<T, 4>;
// using Base::at;
// public:
// using Point = T;
#[allow(dead_code)]
pub fn new() -> Self {
Self([Point { x: 0.0, y: 0.0 }; 4])
}
// pub fn with_f32( tl:f32, tr:f32, br:f32, bl:f32) -> Self {
// Self([tl, tr,br, bl ])
// }
pub fn with_points(tl: Point, tr: Point, br: Point, bl: Point) -> Self {
Self([tl, tr, br, bl])
}
pub fn topLeft(&self) -> &Point {
&self.0[0]
} //const noexcept { return at(0); }
pub fn topRight(&self) -> &Point {
&self.0[1]
} //const noexcept { return at(1); }
pub fn bottomRight(&self) -> &Point {
&self.0[2]
} //const noexcept { return at(2); }
pub fn bottomLeft(&self) -> &Point {
&self.0[3]
} //const noexcept { return at(3); }
#[allow(dead_code)]
pub fn orientation(&self) -> f64 {
let centerLine =
(*self.topRight() + *self.bottomRight()) - (*self.topLeft() + *self.bottomLeft());
if (centerLine == Point { x: 0.0, y: 0.0 }) {
return 0.0;
}
let centerLineF = Point::normalized(centerLine);
f32::atan2(centerLineF.y, centerLineF.x).into()
}
pub fn points(&self) -> &[Point] {
&self.0
}
}
#[allow(dead_code)]
pub fn Rectangle(width: i32, height: i32, margin: Option<i32>) -> Quadrilateral {
let margin = if let Some(m) = margin { m } else { 0 };
Quadrilateral([
Point {
x: margin as f32,
y: margin as f32,
},
Point {
x: width as f32 - margin as f32,
y: margin as f32,
},
Point {
x: width as f32 - margin as f32,
y: height as f32 - margin as f32,
},
Point {
x: margin as f32,
y: height as f32 - margin as f32,
},
])
}
#[allow(dead_code)]
pub fn CenteredSquare(size: i32) -> Quadrilateral {
Scale(
&Quadrilateral([
Point { x: -1.0, y: -1.0 },
Point { x: 1.0, y: -1.0 },
Point { x: 1.0, y: 1.0 },
Point { x: -1.0, y: 1.0 },
]),
size / 2,
)
}
#[allow(dead_code)]
pub fn Line(y: i32, xStart: i32, xStop: i32) -> Quadrilateral {
Quadrilateral([
Point {
x: xStart as f32,
y: y as f32,
},
Point {
x: xStop as f32,
y: y as f32,
},
Point {
x: xStop as f32,
y: y as f32,
},
Point {
x: xStart as f32,
y: y as f32,
},
])
}
#[allow(dead_code)]
pub fn IsConvex(poly: &Quadrilateral) -> bool {
let N = poly.0.len();
let mut sign = false;
let mut m = f32::INFINITY;
let mut M = 0.0_f32;
for i in 0..N
// for(int i = 0; i < N; i++)
{
let d1 = poly.0[(i + 2) % N] - poly.0[(i + 1) % N];
let d2 = poly.0[i] - poly.0[(i + 1) % N];
let cp = d1.cross(d2);
m = if m.abs() > cp { cp } else { m.abs() };
M = if M.abs() > cp { M.abs() } else { cp };
// m = std::cmp::min((m).abs(), cp);
// M = std::cmp::max((M).abs(), cp);
if i == 0 {
sign = cp > 0.0;
} else if sign != (cp > 0.0) {
return false;
}
}
// It turns out being convex is not enough to prevent a "numerical instability"
// that can cause the corners being projected inside the image boundaries but
// some points near the corners being projected outside. This has been observed
// where one corner is almost in line with two others. The M/m ratio is below 2
// for the complete existing sample set. For very "skewed" QRCodes a value of
// around 3 is realistic. A value of 14 has been observed to trigger the
// instability.
M / m < 4.0
}
#[allow(dead_code)]
pub fn Scale(q: &Quadrilateral, factor: i32) -> Quadrilateral {
Quadrilateral([
q.0[0] * factor as f32,
q.0[1] * factor as f32,
q.0[2] * factor as f32,
q.0[3] * factor as f32,
])
}
#[allow(dead_code)]
pub fn Center(q: &Quadrilateral) -> Point {
let reduced: Point = q.0.iter().sum();
let size = q.0.len() as f32;
reduced / size
// return Reduce(q) / Size(q);
}
#[allow(dead_code)]
pub fn RotatedCorners(q: &Quadrilateral, n: Option<i32>, mirror: Option<bool>) -> Quadrilateral {
let n = if let Some(n) = n { n } else { 1 };
let mirror = if let Some(m) = mirror { m } else { false };
let mut res = q.clone();
res.0.rotate_left(((n + 4) % 4) as usize);
// std::rotate_copy(q.begin(), q.begin() + ((n + 4) % 4), q.end(), res.begin());
if mirror {
res.0.swap(1, 3);
}
// {std::swap(res[1], res[3]);}
res
}
#[allow(dead_code)]
pub fn IsInside(p: Point, q: &Quadrilateral) -> bool {
// Test if p is on the same side (right or left) of all polygon segments
let mut pos = 0;
let mut neg = 0;
for i in 0..q.0.len()
// for (int i = 0; i < Size(q); ++i)
{
if Point::cross(p - q.0[i], q.0[(i + 1) % q.0.len()] - q.0[i]) < 0.0 {
neg += 1;
} else {
pos += 1;
}
// (cross(p - q[i], q[(i + 1) % Size(q)] - q[i]) < 0 ? neg : pos)++;
}
pos == 0 || neg == 0
}
#[allow(dead_code)]
pub fn HaveIntersectingBoundingBoxes(a: &Quadrilateral, b: &Quadrilateral) -> bool {
// TODO: this is only a quick and dirty approximation that works for the trivial standard cases
let x = b.topRight().x < a.topLeft().x || b.topLeft().x > a.topRight().x;
let y = b.bottomLeft().y < a.topLeft().y || b.topLeft().y > a.bottomLeft().y;
!(x || y)
}

View File

@@ -2,7 +2,7 @@
use num::integer::Roots; use num::integer::Roots;
use crate::{ use crate::{
common::{BitMatrix, DefaultGridSampler, DetectorRXingResult, GridSampler, Result}, common::{BitMatrix, DefaultGridSampler, DetectorRXingResult, GridSampler, Result, Quadrilateral},
point, Exceptions, Point, point, Exceptions, Point,
}; };
@@ -346,26 +346,16 @@ pub fn detect(image: &BitMatrix, try_harder: bool) -> Result<MaxicodeDetectionRe
// let target_width = (tr.0 - tl.0).round().abs() as u32; // let target_width = (tr.0 - tl.0).round().abs() as u32;
// let target_height = (br.1 - tr.1).round().abs() as u32; // let target_height = (br.1 - tr.1).round().abs() as u32;
let dst = Quadrilateral::new(point(0.0,0.0), point(target_width,0.0), point(target_width,
target_height), point(0.0,
target_height));
let src = Quadrilateral::new(tl, tr, br, bl);
let Ok(bits) = grid_sampler.sample_grid_detailed( let Ok(bits) = grid_sampler.sample_grid_detailed(
image, image,
target_width.round() as u32, target_width.round() as u32,
target_height.round() as u32, target_height.round() as u32,
0.0, dst, src
0.0,
target_width ,
0.0,
target_width,
target_height,
0.0,
target_height,
tl.x,
tl.y,
tr.x,
tr.y,
br.x,
br.y,
bl.x,
bl.y,
) else { ) else {
if try_harder { if try_harder {
continue; continue;

View File

@@ -17,7 +17,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::{ use crate::{
common::{BitMatrix, DefaultGridSampler, GridSampler, PerspectiveTransform, Result}, common::{BitMatrix, DefaultGridSampler, GridSampler, PerspectiveTransform, Result, Quadrilateral, SamplerControl},
point, point,
qrcode::decoder::Version, qrcode::decoder::Version,
DecodeHintType, DecodeHintValue, DecodingHintDictionary, Exceptions, Point, PointCallback, DecodeHintType, DecodeHintValue, DecodingHintDictionary, Exceptions, Point, PointCallback,
@@ -147,7 +147,7 @@ impl<'a> Detector<'_> {
) )
.ok_or(Exceptions::NOT_FOUND)?; .ok_or(Exceptions::NOT_FOUND)?;
let bits = Detector::sampleGrid(self.image, &transform, dimension)?; let bits = Detector::sampleGrid(self.image, transform, dimension)?;
let mut points = vec![ let mut points = vec![
Point::from(bottomLeft), Point::from(bottomLeft),
@@ -193,33 +193,25 @@ impl<'a> Detector<'_> {
sourceBottomRightY = dimMinusThree; sourceBottomRightY = dimMinusThree;
} }
let dst = Quadrilateral::new(point(3.5,
3.5), point(dimMinusThree,
3.5), point(sourceBottomRightX,
sourceBottomRightY), point(3.5,
dimMinusThree));
let src = Quadrilateral::new( topLeft, topRight, point(bottomRightX, bottomRightY), bottomLeft);
Some(PerspectiveTransform::quadrilateralToQuadrilateral( Some(PerspectiveTransform::quadrilateralToQuadrilateral(
3.5, dst, src
3.5,
dimMinusThree,
3.5,
sourceBottomRightX,
sourceBottomRightY,
3.5,
dimMinusThree,
topLeft.x,
topLeft.y,
topRight.x,
topRight.y,
bottomRightX,
bottomRightY,
bottomLeft.x,
bottomLeft.y,
)) ))
} }
fn sampleGrid( fn sampleGrid(
image: &BitMatrix, image: &BitMatrix,
transform: &PerspectiveTransform, transform: PerspectiveTransform,
dimension: u32, dimension: u32,
) -> Result<BitMatrix> { ) -> Result<BitMatrix> {
let sampler = DefaultGridSampler::default(); let sampler = DefaultGridSampler::default();
sampler.sample_grid(image, dimension, dimension, transform) sampler.sample_grid(image, dimension, dimension, &[SamplerControl::new(dimension, dimension, transform)])
} }
/** /**