Implement basic contour tracing with Suzuki-Abe algorithm

This commit is contained in:
cdo committed 2026-10-29 10:28:53 +00:00
1 parent a0c06ab573
commit 88a25f3812
2 files changed
+245

No files matched your search

+244
View File
@@ -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
View File
@@ -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;