Fix inner/outer detection in contour tracing
This commit is contained in:
1 parent
f25e9a00a8
commit
f68fa6f0b7
2 files changed
+182
-31
No files matched your search
+21
-13
@@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
+161
-18
@@ -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
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -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,9 +102,6 @@ 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 {
|
||||||
@@ -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!(".");
|
||||||
@@ -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)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user