Fix inner/outer detection in contour tracing

This commit is contained in:
cdo committed 2026-11-02 11:15:27 +00:00
1 parent f25e9a00a8
commit f68fa6f0b7
2 files changed
+187 -36

No files matched your search

+21 -13
View File
@@ -1,30 +1,38 @@
mod common; mod common;
use ccvl::kernel::Kernel;
use ccvl::*; use ccvl::*;
fn draw(contour: &contour::Contour, img: &mut image::Image<image::SRGBA>) { fn draw(
let n = contour.vertices.len(); img: &mut image::Image<image::SRGBA>,
path: &Vec<(usize, usize)>,
colour: image::SRGBA,
) {
let n = path.len();
for i in 0..n { for i in 0..n {
let (x0, y0) = contour.vertices[i]; let (x0, y0) = path[i];
let (x1, y1) = contour.vertices[(i + 1) % n]; let (x1, y1) = path[(i + 1) % n];
let colour = match contour.kind {
contour::Kind::Outer => colours::GREEN,
contour::Kind::Inner => colours::BLUE,
};
draw::line(x0, y0, x1, y1, img, colour); draw::line(x0, y0, x1, y1, img, colour);
} }
} }
fn approx(a: f32, b: f32, epsilon: f32) -> bool {
(a - b).abs() <= epsilon
}
fn main() -> std::process::ExitCode { fn main() -> std::process::ExitCode {
common::process_image(|mut img| { common::process_image(|mut img| {
let greyscale = img.map(|p| image::Luminance::from(p)); let greyscale = img.map(|p| image::Luminance::from(p));
let thresholded = filter::threshold_auto(greyscale); let blurred = kernel::gaussian::<5>(1.2).convolve_sat(&greyscale);
let thresholded = filter::threshold_auto(blurred);
let contours = contour::find(thresholded); let contours =
contour::find(&thresholded, image::Luminance { y: 0.0 });
for contour in contours { for contour in contours {
draw(&contour, &mut img); draw(&mut img, &contour.vertices, match contour.kind {
contour::Kind::Outer => colours::GREEN,
contour::Kind::Inner => colours::BLUE,
});
} }
img img
}) })
} }
+166 -23
View File
@@ -45,12 +45,15 @@ trait Coordinate {
fn set<P>(&self, img: &mut image::Image<P>, value: P); fn set<P>(&self, img: &mut image::Image<P>, value: P);
} }
pub fn find(img: image::Image<image::Luminance>) -> Vec<Contour> { pub fn find<P>(img: &image::Image<P>, fg: P) -> Vec<Contour>
where
P: PartialEq + Copy + Send + Sync,
{
let mut map = img.map(|p| { let mut map = img.map(|p| {
if p.y == 1.0 { if p == fg {
Pixel::Background
} else {
Pixel::Foreground Pixel::Foreground
} else {
Pixel::Background
} }
}); });
@@ -67,7 +70,7 @@ pub fn find(img: image::Image<image::Luminance>) -> Vec<Contour> {
add(q, Kind::Inner, parent, &mut contours, &mut map); add(q, Kind::Inner, parent, &mut contours, &mut map);
} else if let Some(label) = traced_up(&map, q) { } else if let Some(label) = traced_up(&map, q) {
parent = Some(label); parent = Some(label);
} else if let Some(label) = traced_down(&map, q) { } else if let Some(label) = traced_down(&map, q) {
parent = contours[label].parent; parent = contours[label].parent;
} }
} }
@@ -83,7 +86,8 @@ fn add(
map: &mut image::Image<Pixel>, map: &mut image::Image<Pixel>,
) { ) {
let label = contours.len(); let label = contours.len();
let vertices = trace(map, start, kind.start_dir(), label); let dir = kind.find_start_dir(start, map);
let vertices = trace(map, start, dir, label);
contours.push(Contour::new(parent, kind, vertices)); contours.push(Contour::new(parent, kind, vertices));
if let Some(index) = parent { if let Some(index) = parent {
contours[index].add_child(label); contours[index].add_child(label);
@@ -98,15 +102,12 @@ fn trace(
) -> Vec<(usize, usize)> { ) -> Vec<(usize, usize)> {
let mut q = start_q; let mut q = start_q;
let mut dir = start_dir; let mut dir = start_dir;
while background(img, dir.from(q)) {
dir.next();
}
let mut vertices = Vec::new(); let mut vertices = Vec::new();
loop { loop {
if foreground(img, q) { if foreground(img, q) {
q.set(img, Pixel::TracedUp(label)); q.set(img, Pixel::TracedUp(label));
} }
let prev_dir = dir; let prev_dir = dir;
dir = dir.flip().next(); dir = dir.flip().next();
@@ -124,20 +125,17 @@ fn trace(
dir = dir.next(); dir = dir.next();
}; };
if q == start_q || !q.in_bounds(img.extent) { if q == start_q && dir == start_dir || !q.in_bounds(img.extent) {
if dir != start_dir {
vertices.push(q);
}
break; break;
} }
} }
// println!();
// for y in 0..img.extent.y { // for y in 0..img.extent.y {
// print!(" ");
// for x in 0..img.extent.x { // for x in 0..img.extent.x {
// if let Some(label) = traced_up(img, (x, y)) { // if let Some(label) = traced_up(img, (x, y)) {
// print!("{:1}", label); // print!("{:1}", label);
// } else if let Some(label) = traced_down(img, (x, y)) { // } else if let Some(_) = traced_down(img, (x, y)) {
// print!("-") // print!("-")
// } else if foreground(img, (x, y)) { // } else if foreground(img, (x, y)) {
// print!("."); // print!(".");
@@ -148,7 +146,7 @@ fn trace(
// println!(); // println!();
// } // }
vertices vertices
} }
fn foreground(img: &image::Image<Pixel>, q: (usize, usize)) -> bool { fn foreground(img: &image::Image<Pixel>, q: (usize, usize)) -> bool {
@@ -183,11 +181,19 @@ fn traced_down(
} }
impl Kind { impl Kind {
fn start_dir(self) -> Dir { fn find_start_dir(
match self { self,
Kind::Outer => Dir::E, q: (usize, usize),
Kind::Inner => Dir::W, img: &image::Image<Pixel>,
) -> Dir {
let mut dir = match self {
Kind::Outer => Dir::W,
Kind::Inner => Dir::E,
};
while background(img, dir.from(q)) {
dir = dir.prev()
} }
dir.flip()
} }
} }
@@ -205,6 +211,19 @@ impl Dir {
} }
} }
fn prev(self) -> Self {
match self {
Self::N => Self::NW,
Self::NE => Self::N,
Self::E => Self::NE,
Self::SE => Self::E,
Self::S => Self::SE,
Self::SW => Self::S,
Self::W => Self::SW,
Self::NW => Self::W,
}
}
fn flip(self) -> Self { fn flip(self) -> Self {
match self { match self {
Self::N => Self::S, Self::N => Self::S,
@@ -292,3 +311,127 @@ impl Default for Pixel {
Self::Background Self::Background
} }
} }
#[cfg(test)]
#[rustfmt::skip]
mod tests {
use super::*;
#[test]
fn simple_block() {
let img = image::Image::<usize> {
extent: image::Extent { x: 4, y: 4 },
pixels: vec![
0, 0, 0, 0,
0, 1, 1, 0,
0, 1, 1, 0,
0, 0, 0, 0,
]
};
let contours = find(&img, 1);
assert_eq!(contours.len(), 1);
let contour = &contours[0];
assert_eq!(contour.kind, Kind::Outer);
assert_eq!(contour.vertices, vec![
(1, 1), (2, 1), (2, 2), (1, 2)
]);
}
#[test]
fn simple_block_with_hole() {
let img = image::Image::<usize> {
extent: image::Extent { x: 8, y: 8 },
pixels: vec![
0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 0, 0, 1, 1, 0,
0, 1, 1, 0, 0, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0,
]
};
let contours = find(&img, 1);
assert_eq!(contours.len(), 2);
let contour = &contours[0];
assert_eq!(contour.parent, None);
assert_eq!(contour.kind, Kind::Outer);
assert_eq!(contour.vertices, vec![
(1, 1), (6, 1), (6, 6), (1, 6)
]);
assert_eq!(contour.children, vec![ 1 ]);
let contour = &contours[1];
assert_eq!(contour.parent, Some(0));
assert_eq!(contour.kind, Kind::Inner);
assert_eq!(contour.vertices, vec![
(2, 3), (2, 4), (3, 5), (4, 5),
(5, 4), (5, 3), (4, 2), (3, 2),
]);
}
#[test]
fn diagonally_connected_block_with_hole() {
let img = image::Image::<usize> {
extent: image::Extent { x: 8, y: 8 },
pixels: vec![
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 0,
0, 0, 0, 1, 1, 1, 1, 0,
0, 1, 1, 0, 0, 1, 1, 0,
0, 1, 1, 0, 0, 1, 1, 0,
0, 0, 0, 1, 1, 1, 1, 0,
0, 0, 0, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0,
]
};
let contours = find(&img, 1);
assert_eq!(contours.len(), 2);
let contour = &contours[0];
assert_eq!(contour.parent, None);
assert_eq!(contour.kind, Kind::Outer);
assert_eq!(contour.vertices, vec![
(3, 1), (6, 1), (6, 6), (3, 6), (3, 5),
(2, 4), (1, 4), (1, 3), (2, 3), (3, 2),
]);
assert_eq!(contour.children, vec![ 1 ]);
let contour = &contours[1];
assert_eq!(contour.parent, Some(0));
assert_eq!(contour.kind, Kind::Inner);
assert_eq!(contour.vertices, vec![
(2, 3), (2, 4), (3, 5), (4, 5),
(5, 4), (5, 3), (4, 2), (3, 2),
]);
}
#[test]
fn contour_passes_through_start() {
let img = image::Image::<usize> {
extent: image::Extent { x: 5, y: 5 },
pixels: vec![
0, 0, 0, 0, 0,
0, 0, 1, 1, 0,
0, 1, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 0, 0, 0,
]
};
let contours = find(&img, 1);
assert_eq!(contours.len(), 1);
let contour = &contours[0];
assert_eq!(contour.kind, Kind::Outer);
assert_eq!(contour.vertices, vec![
(2, 1), (3, 1), (2, 1), (1, 2), (1, 3), (1, 2)
]);
}
}