Skip to content

Commit

Permalink
Merge pull request #30 from logic-switch/incremental_grid_detection
Browse files Browse the repository at this point in the history
Incremental Grid detection
  • Loading branch information
WanzenBug authored Jan 7, 2025
2 parents 9399e42 + 31eb398 commit 9ce073c
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 88 deletions.
119 changes: 34 additions & 85 deletions src/identify/match_capstones.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,63 +9,44 @@ struct Neighbor {
distance: f64,
}

/// Find CapStones that form a grid
///
/// By trying to match up the relative perspective of 3
/// [CapStones](struct.CapStone.html) we can find those that corner the same QR
/// code.
pub fn find_groupings(mut capstones: Vec<CapStone>) -> Vec<CapStoneGroup> {
let mut idx = 0;
let mut groups = Vec::new();
while idx < capstones.len() {
let (hlist, vlist) = find_possible_neighbors(&capstones, idx);
match test_neighbours(&hlist, &vlist) {
None => idx += 1,
Some((h_idx, v_idx)) => {
let group = remove_capstones_in_order(&mut capstones, h_idx, idx, v_idx);
groups.push(group);

// Update index for items removed
let sub = [h_idx, v_idx].iter().filter(|&&i| i < idx).count();
idx -= sub;
/// Return each pair Capstone indexes that are likely to be from a QR code
/// Ordered from most symmetric to least symmetric
pub fn find_and_rank_possible_neighbors(capstones: &[CapStone], idx: usize) -> Vec<(usize, usize)> {
const VIABILITY_THRESHOLD: f64 = 0.25;

let (hlist, vlist) = find_possible_neighbors(capstones, idx);
let mut res = Vec::new();
struct NeighborSet {
score: f64,
h_index: usize,
v_index: usize,
}
/* Test each possible grouping */
for hn in hlist {
for vn in vlist.iter() {
let score = {
if hn.distance < vn.distance {
(1.0f64 - hn.distance / vn.distance).abs()
} else {
(1.0f64 - vn.distance / hn.distance).abs()
}
};
if score < VIABILITY_THRESHOLD {
res.push(NeighborSet {
score,
h_index: hn.index,
v_index: vn.index,
});
}
}
}

groups
}

fn remove_capstones_in_order(
caps: &mut Vec<CapStone>,
first: usize,
second: usize,
third: usize,
) -> CapStoneGroup {
assert_ne!(first, second);
assert_ne!(first, third);
assert_ne!(second, third);

let idx0 = first;
let mut idx1 = second;
let mut idx2 = third;

if second > first {
idx1 -= 1;
}

if third > first {
idx2 -= 1;
}

if third > second {
idx2 -= 1;
}

let first_cap = caps.remove(idx0);
let second_cap = caps.remove(idx1);
let third_cap = caps.remove(idx2);

CapStoneGroup(first_cap, second_cap, third_cap)
res.sort_unstable_by(|a, b| {
(a.score)
.partial_cmp(&(b.score))
.expect("Neighbor distance should never cause a div by 0")
});
res.iter().map(|n| (n.h_index, n.v_index)).collect()
}

fn find_possible_neighbors(capstones: &[CapStone], idx: usize) -> (Vec<Neighbor>, Vec<Neighbor>) {
Expand Down Expand Up @@ -105,35 +86,3 @@ fn find_possible_neighbors(capstones: &[CapStone], idx: usize) -> (Vec<Neighbor>

(hlist, vlist)
}

fn test_neighbours(hlist: &[Neighbor], vlist: &[Neighbor]) -> Option<(usize, usize)> {
// Worse scores will be ignored anyway
let mut best_score = 2.5;
let mut best_h = None;
let mut best_v = None;
/* Test each possible grouping */
for hn in hlist {
for vn in vlist {
let score = (1.0f64 - hn.distance / vn.distance).abs();

if score > 2.5 {
continue;
}

let new = match (best_h, score) {
(None, _) => (Some(hn.index), Some(vn.index), score),
(Some(_), b) if b < best_score => (Some(hn.index), Some(vn.index), b),
_ => (best_h, best_v, best_score),
};

best_h = new.0;
best_v = new.1;
best_score = new.2;
}
}

match (best_h, best_v) {
(None, _) | (_, None) => None,
(Some(h), Some(v)) => Some((h, v)),
}
}
2 changes: 1 addition & 1 deletion src/identify/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub use self::grid::SkewedGridLocation;
pub use self::match_capstones::find_groupings;
pub use self::match_capstones::find_and_rank_possible_neighbors;

pub mod grid;
pub mod match_capstones;
Expand Down
56 changes: 54 additions & 2 deletions src/prepare.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{cmp, num::NonZeroUsize};

use crate::identify::match_capstones::CapStoneGroup;
use crate::identify::Point;
use lru::LruCache;

Expand Down Expand Up @@ -223,10 +224,17 @@ where
}
}

pub fn detect_grids(&mut self) -> Vec<crate::Grid<crate::identify::grid::RefGridImage<S>>> {
/// Group [CapStones](struct.CapStone.html) into [Grids](struct.Grid.html)
/// that are likely QR codes
///
/// Return a vector of Grids
pub fn detect_grids(&mut self) -> Vec<crate::Grid<crate::identify::grid::RefGridImage<S>>>
where
S: Clone,
{
let mut res = Vec::new();
let stones = crate::capstones_from_image(self);
let groups = crate::identify::find_groupings(stones);
let groups = self.find_groupings(stones);
let locations: Vec<_> = groups
.into_iter()
.filter_map(|v| crate::SkewedGridLocation::from_group(self, v))
Expand All @@ -252,6 +260,50 @@ where
res
}

/// Find CapStones that form a grid
///
/// By trying to match up the relative perspective of 3
/// [CapStones](struct.CapStone.html) along with other criteria we can find the
/// CapStones that corner the same QR code.
fn find_groupings(&mut self, capstones: Vec<crate::CapStone>) -> Vec<CapStoneGroup>
where
S: Clone,
{
let mut used_capstones = Vec::new();
let mut groups = Vec::new();
for idx in 0..capstones.len() {
if used_capstones.contains(&idx) {
continue;
}
let pairs = crate::identify::find_and_rank_possible_neighbors(&capstones, idx);
for pair in pairs {
if used_capstones.contains(&pair.0) || used_capstones.contains(&pair.1) {
continue;
}
let group_under_test = CapStoneGroup(
capstones[pair.0].clone(),
capstones[idx].clone(),
capstones[pair.1].clone(),
);
// Confirm that this group has the other requirements of a QR code.
// A copy of the input image is used to not contaminate the
// original on an incorrect set of CapStones
let mut image_copy = self.clone();
if crate::SkewedGridLocation::from_group(&mut image_copy, group_under_test.clone())
.is_none()
{
continue;
}
// This is a viable set, save this grouping
groups.push(group_under_test);
used_capstones.push(pair.0);
used_capstones.push(idx);
used_capstones.push(pair.1);
}
}
groups
}

pub fn without_preparation(buf: S) -> Self {
for y in 0..buf.height() {
for x in 0..buf.width() {
Expand Down
Binary file added tests/data/full/multiple.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/data/full/multiple_rotated.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 46 additions & 0 deletions tests/test_full.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,49 @@ fn test_full_large_version() {
assert_eq!(meta.version, rqrr::Version(14));
assert_eq!(content, "superlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdatasuperlongdata");
}

#[test]
fn test_full_multi() {
let png = image::open("tests/data/full/multiple.png")
.unwrap()
.to_luma8();

let mut search_img = rqrr::PreparedImage::prepare(png);
let grids = search_img.detect_grids();
assert_eq!(grids.len(), 3);

let (meta, content) = grids[0].decode().unwrap();
assert_eq!(meta.version, rqrr::Version(13));
assert_eq!(content, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariat");

let (meta, content) = grids[1].decode().unwrap();
assert_eq!(meta.version, rqrr::Version(13));
assert_eq!(content, "ur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea comm");

let (meta, content) = grids[2].decode().unwrap();
assert_eq!(meta.version, rqrr::Version(11));
assert_eq!(content, "odo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.");
}

#[test]
fn test_full_multi_rotated() {
let png = image::open("tests/data/full/multiple_rotated.png")
.unwrap()
.to_luma8();

let mut search_img = rqrr::PreparedImage::prepare(png);
let grids = search_img.detect_grids();
assert_eq!(grids.len(), 3);

let (meta, content) = grids[0].decode().unwrap();
assert_eq!(meta.version, rqrr::Version(13));
assert_eq!(content, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariat");

let (meta, content) = grids[1].decode().unwrap();
assert_eq!(meta.version, rqrr::Version(13));
assert_eq!(content, "ur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea comm");

let (meta, content) = grids[2].decode().unwrap();
assert_eq!(meta.version, rqrr::Version(11));
assert_eq!(content, "odo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.");
}

0 comments on commit 9ce073c

Please sign in to comment.