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
+182 -31

No files matched your search

+21 -13
View File
@@ -1,30 +1,38 @@
mod common;
use ccvl::kernel::Kernel;
use ccvl::*;
fn draw(contour: &contour::Contour, img: &mut image::Image<image::SRGBA>) {
let n = contour.vertices.len();
fn draw(
img: &mut image::Image<image::SRGBA>,
path: &Vec<(usize, usize)>,
colour: image::SRGBA,
) {
let n = path.len();
for i in 0..n {
let (x0, y0) = contour.vertices[i];
let (x1, y1) = contour.vertices[(i + 1) % n];
let colour = match contour.kind {
contour::Kind::Outer => colours::GREEN,
contour::Kind::Inner => colours::BLUE,
};
let (x0, y0) = path[i];
let (x1, y1) = path[(i + 1) % n];
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 {
common::process_image(|mut img| {
let greyscale = img.map(|p| image::Luminance::from(p));
let thresholded = filter::threshold_auto(greyscale);
let contours = contour::find(thresholded);
let blurred = kernel::gaussian::<5>(1.2).convolve_sat(&greyscale);
let thresholded = filter::threshold_auto(blurred);
let contours =
contour::find(&thresholded, image::Luminance { y: 0.0 });
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
})
}
+161 -18
View File
@@ -45,12 +45,15 @@ trait Coordinate {
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| {
if p.y == 1.0 {
Pixel::Background
} else {
if p == fg {
Pixel::Foreground
} else {
Pixel::Background
}
});
@@ -83,7 +86,8 @@ fn add(
map: &mut image::Image<Pixel>,
) {
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));
if let Some(index) = parent {
contours[index].add_child(label);
@@ -98,9 +102,6 @@ fn trace(
) -> Vec<(usize, usize)> {
let mut q = start_q;
let mut dir = start_dir;
while background(img, dir.from(q)) {
dir.next();
}
let mut vertices = Vec::new();
loop {
@@ -124,20 +125,17 @@ fn trace(
dir = dir.next();
};
if q == start_q || !q.in_bounds(img.extent) {
if dir != start_dir {
vertices.push(q);
}
if q == start_q && dir == start_dir || !q.in_bounds(img.extent) {
break;
}
}
// println!();
// for y in 0..img.extent.y {
// print!(" ");
// for x in 0..img.extent.x {
// if let Some(label) = traced_up(img, (x, y)) {
// print!("{:1}", label);
// } else if let Some(label) = traced_down(img, (x, y)) {
// } else if let Some(_) = traced_down(img, (x, y)) {
// print!("-")
// } else if foreground(img, (x, y)) {
// print!(".");
@@ -183,11 +181,19 @@ fn traced_down(
}
impl Kind {
fn start_dir(self) -> Dir {
match self {
Kind::Outer => Dir::E,
Kind::Inner => Dir::W,
fn find_start_dir(
self,
q: (usize, usize),
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 {
match self {
Self::N => Self::S,
@@ -292,3 +311,127 @@ impl Default for Pixel {
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)
]);
}
}