Implement basic contour tracing with Suzuki-Abe algorithm
This commit is contained in:
1 parent
a0c06ab573
commit
88a25f3812
2 files changed
+245
No files matched your search
+244
@@ -0,0 +1,244 @@
|
|||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum Kind {
|
||||||
|
Outer,
|
||||||
|
Inner,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Contour {
|
||||||
|
pub trace: Vec<(usize, usize)>,
|
||||||
|
pub children: Vec<usize>,
|
||||||
|
pub parent: Option<usize>,
|
||||||
|
pub kind: Kind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
enum Pixel {
|
||||||
|
Foreground,
|
||||||
|
Background,
|
||||||
|
Traced(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
enum Dir {
|
||||||
|
N,
|
||||||
|
NE,
|
||||||
|
E,
|
||||||
|
SE,
|
||||||
|
S,
|
||||||
|
SW,
|
||||||
|
W,
|
||||||
|
NW,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Coordinate {
|
||||||
|
fn prev_x(&self) -> Self;
|
||||||
|
fn next_x(&self) -> Self;
|
||||||
|
fn prev_y(&self) -> Self;
|
||||||
|
fn next_y(&self) -> Self;
|
||||||
|
|
||||||
|
fn in_bounds(&self, bounds: image::Extent) -> bool;
|
||||||
|
fn get<P: Copy>(&self, img: &image::Image<P>) -> Option<P>;
|
||||||
|
fn set<P>(&self, img: &mut image::Image<P>, value: P);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find(img: image::Image<image::Luminance>) -> Vec<Contour> {
|
||||||
|
let mut map = img.map(|p| {
|
||||||
|
if p.y == 1.0 {
|
||||||
|
Pixel::Background
|
||||||
|
} else {
|
||||||
|
Pixel::Foreground
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut contours = Vec::new();
|
||||||
|
for y in 0..map.extent.y {
|
||||||
|
let mut parent = None;
|
||||||
|
for x in 0..map.extent.x {
|
||||||
|
let q = (x, y);
|
||||||
|
if foreground(&map, q) {
|
||||||
|
let prev_bg = background(&map, q.prev_x());
|
||||||
|
let next_bg = background(&map, q.next_x());
|
||||||
|
if prev_bg || next_bg {
|
||||||
|
let (dir, kind) = if prev_bg {
|
||||||
|
(Dir::W, Kind::Outer)
|
||||||
|
} else {
|
||||||
|
(Dir::E, Kind::Inner)
|
||||||
|
};
|
||||||
|
let label = contours.len();
|
||||||
|
let trace = trace(&mut map, q, dir, label);
|
||||||
|
contours.push(Contour::new(parent, kind, trace));
|
||||||
|
if let Some(index) = parent {
|
||||||
|
contours[index].add_child(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(label) = traced(&map, q) {
|
||||||
|
if background(&map, q.prev_x()) {
|
||||||
|
parent = Some(label);
|
||||||
|
} else if background(&map, q.next_x()) {
|
||||||
|
parent = contours[label].parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contours
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trace(
|
||||||
|
img: &mut image::Image<Pixel>,
|
||||||
|
start_q: (usize, usize),
|
||||||
|
mut dir: Dir,
|
||||||
|
label: usize,
|
||||||
|
) -> Vec<(usize, usize)> {
|
||||||
|
let mut q = start_q;
|
||||||
|
dir = dir.next();
|
||||||
|
|
||||||
|
let mut path = Vec::new();
|
||||||
|
'outer: loop {
|
||||||
|
if !q.in_bounds(img.extent) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
q.set(img, Pixel::Traced(label));
|
||||||
|
path.push(q);
|
||||||
|
|
||||||
|
let start = dir;
|
||||||
|
while background(img, dir.from(q)) {
|
||||||
|
dir = dir.next();
|
||||||
|
if dir == start {
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
q = dir.from(q);
|
||||||
|
dir = dir.flip().next();
|
||||||
|
|
||||||
|
if q == start_q {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foreground(img: &image::Image<Pixel>, q: (usize, usize)) -> bool {
|
||||||
|
match q.get(img) {
|
||||||
|
Some(Pixel::Foreground) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background(img: &image::Image<Pixel>, q: (usize, usize)) -> bool {
|
||||||
|
match q.get(img) {
|
||||||
|
Some(Pixel::Background) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn traced(img: &image::Image<Pixel>, q: (usize, usize)) -> Option<usize> {
|
||||||
|
match q.get(img) {
|
||||||
|
Some(Pixel::Traced(label)) => Some(label),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dir {
|
||||||
|
fn next(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::N => Self::NE,
|
||||||
|
Self::NE => Self::E,
|
||||||
|
Self::E => Self::SE,
|
||||||
|
Self::SE => Self::S,
|
||||||
|
Self::S => Self::SW,
|
||||||
|
Self::SW => Self::W,
|
||||||
|
Self::W => Self::NW,
|
||||||
|
Self::NW => Self::N,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flip(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::N => Self::S,
|
||||||
|
Self::NE => Self::SW,
|
||||||
|
Self::E => Self::W,
|
||||||
|
Self::SE => Self::NW,
|
||||||
|
Self::S => Self::N,
|
||||||
|
Self::SW => Self::NE,
|
||||||
|
Self::W => Self::E,
|
||||||
|
Self::NW => Self::SE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from<C: Coordinate>(&self, q: C) -> C {
|
||||||
|
match self {
|
||||||
|
Self::N => q.prev_y(),
|
||||||
|
Self::NE => q.prev_y().next_x(),
|
||||||
|
Self::E => q.next_x(),
|
||||||
|
Self::SE => q.next_y().next_x(),
|
||||||
|
Self::S => q.next_y(),
|
||||||
|
Self::SW => q.next_y().prev_x(),
|
||||||
|
Self::W => q.prev_x(),
|
||||||
|
Self::NW => q.prev_y().prev_x(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Coordinate for (usize, usize) {
|
||||||
|
fn prev_x(&self) -> Self {
|
||||||
|
(self.0.wrapping_sub(1), self.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_x(&self) -> Self {
|
||||||
|
(self.0 + 1, self.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prev_y(&self) -> Self {
|
||||||
|
(self.0, self.1.wrapping_sub(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_y(&self) -> Self {
|
||||||
|
(self.0, self.1 + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn in_bounds(&self, bounds: image::Extent) -> bool {
|
||||||
|
self.0 < bounds.x && self.1 < bounds.y
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get<P: Copy>(&self, img: &image::Image<P>) -> Option<P> {
|
||||||
|
if self.in_bounds(img.extent) {
|
||||||
|
Some(img.at(self.0, self.1))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set<P>(&self, img: &mut image::Image<P>, value: P) {
|
||||||
|
if self.in_bounds(img.extent) {
|
||||||
|
*img.at_mut(self.0, self.1) = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Contour {
|
||||||
|
fn new(
|
||||||
|
parent: Option<usize>,
|
||||||
|
kind: Kind,
|
||||||
|
trace: Vec<(usize, usize)>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
trace,
|
||||||
|
children: Vec::new(),
|
||||||
|
parent,
|
||||||
|
kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_child(&mut self, child: usize) {
|
||||||
|
self.children.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Pixel {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Background
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod contour;
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
pub mod hough;
|
pub mod hough;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
|
|||||||
Reference in new issue
Block a user