Implement edge hysteresis for Canny
This commit is contained in:
1 parent
7e62163c26
commit
0c44ce6396
1 file changed
+189
@@ -0,0 +1,189 @@
|
|||||||
|
use super::double_threshold::Candidate;
|
||||||
|
use crate::image::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DisjointSet {
|
||||||
|
parent: Vec<usize>,
|
||||||
|
rank: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisjointSet {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
parent: Vec::new(),
|
||||||
|
rank: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_set(&mut self) -> usize {
|
||||||
|
let x = self.parent.len();
|
||||||
|
self.parent.push(x);
|
||||||
|
self.rank.push(0);
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find(&mut self, x: usize) -> usize {
|
||||||
|
if self.parent[x] != x {
|
||||||
|
self.parent[x] = self.find(self.parent[x]);
|
||||||
|
}
|
||||||
|
self.parent[x]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn union(&mut self, x: usize, y: usize) {
|
||||||
|
let root_x = self.find(x);
|
||||||
|
let root_y = self.find(y);
|
||||||
|
if root_x != root_y {
|
||||||
|
if self.rank[root_x] < self.rank[root_y] {
|
||||||
|
self.parent[root_x] = root_y;
|
||||||
|
} else {
|
||||||
|
self.parent[root_y] = root_x;
|
||||||
|
if self.rank[root_y] == self.rank[root_x] {
|
||||||
|
self.rank[root_x] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hysteresis(src: Image<Candidate>) -> Image<bool> {
|
||||||
|
let mut links = DisjointSet::new();
|
||||||
|
let mut map = Image::<Option<usize>>::with_extent(src.extent);
|
||||||
|
let mut neighbor_labels = std::collections::BTreeSet::<usize>::new();
|
||||||
|
|
||||||
|
for y in 0..src.extent.y {
|
||||||
|
for x in 0..src.extent.x {
|
||||||
|
if src.at(x, y) == Candidate::None {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_neighbor_labels(x, y, &src, &map, &mut neighbor_labels);
|
||||||
|
*map.at_mut(x, y) = Some(match neighbor_labels.first() {
|
||||||
|
None => links.make_set(),
|
||||||
|
Some(&label) => {
|
||||||
|
for neighbor in &neighbor_labels {
|
||||||
|
links.union(*neighbor, label);
|
||||||
|
}
|
||||||
|
label
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut alive = std::collections::BTreeSet::<usize>::new();
|
||||||
|
for y in 0..src.extent.y {
|
||||||
|
for x in 0..src.extent.x {
|
||||||
|
if let Some(label) = map.at(x, y) {
|
||||||
|
let final_label = links.find(label);
|
||||||
|
*map.at_mut(x, y) = Some(final_label);
|
||||||
|
if src.at(x, y) == Candidate::Strong {
|
||||||
|
alive.insert(final_label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map.map(|p| match p {
|
||||||
|
None => false,
|
||||||
|
Some(label) => alive.contains(&label),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_neighbor_labels(
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
src: &Image<Candidate>,
|
||||||
|
map: &Image<Option<usize>>,
|
||||||
|
out: &mut std::collections::BTreeSet<usize>,
|
||||||
|
) {
|
||||||
|
out.clear();
|
||||||
|
for (nx, ny) in neighbors(x, y, src.extent) {
|
||||||
|
if src.at(nx, ny) == Candidate::None {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(label) = map.at(nx, ny) {
|
||||||
|
out.insert(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn neighbors(x: usize, y: usize, extent: Extent) -> Vec<(usize, usize)> {
|
||||||
|
let start_x = if x <= 0 { 0 } else { x - 1 };
|
||||||
|
let start_y = if y <= 0 { 0 } else { y - 1 };
|
||||||
|
let end_x = if x + 1 >= extent.x { x + 1 } else { x + 2 };
|
||||||
|
let end_y = if y + 1 >= extent.y { y + 1 } else { y + 2 };
|
||||||
|
|
||||||
|
let mut res = Vec::new();
|
||||||
|
for i in start_y..end_y {
|
||||||
|
for j in start_x..end_x {
|
||||||
|
if i != y || j != x {
|
||||||
|
res.push((j, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_horiz_lines() {
|
||||||
|
let img = Image {
|
||||||
|
extent: Extent { x: 3, y: 3 },
|
||||||
|
pixels: vec![
|
||||||
|
Candidate::Weak, Candidate::Weak, Candidate::Weak,
|
||||||
|
Candidate::None, Candidate::None, Candidate::None,
|
||||||
|
Candidate::Weak, Candidate::Weak, Candidate::Strong,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
let exp = vec![
|
||||||
|
false, false, false,
|
||||||
|
false, false, false,
|
||||||
|
true, true, true,
|
||||||
|
];
|
||||||
|
assert_eq!(hysteresis(img).pixels, exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_diagonal() {
|
||||||
|
let img = Image {
|
||||||
|
extent: Extent { x: 3, y: 3 },
|
||||||
|
pixels: vec![
|
||||||
|
Candidate::Weak, Candidate::None, Candidate::None,
|
||||||
|
Candidate::None, Candidate::Weak, Candidate::None,
|
||||||
|
Candidate::None, Candidate::None, Candidate::Strong,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
let exp = vec![
|
||||||
|
true, false, false,
|
||||||
|
false, true, false,
|
||||||
|
false, false, true,
|
||||||
|
];
|
||||||
|
assert_eq!(hysteresis(img).pixels, exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn complex_single_group() {
|
||||||
|
let img = Image {
|
||||||
|
extent: Extent { x: 5, y: 5 },
|
||||||
|
pixels: vec![
|
||||||
|
Candidate::None, Candidate::Weak, Candidate::None, Candidate::None, Candidate::Weak,
|
||||||
|
Candidate::Weak, Candidate::None, Candidate::None, Candidate::Weak, Candidate::None,
|
||||||
|
Candidate::None, Candidate::Weak, Candidate::Weak, Candidate::None, Candidate::None,
|
||||||
|
Candidate::Weak, Candidate::None, Candidate::Weak, Candidate::None, Candidate::Strong,
|
||||||
|
Candidate::None, Candidate::None, Candidate::None, Candidate::Weak, Candidate::None,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
let exp = vec![
|
||||||
|
false, true, false, false, true,
|
||||||
|
true, false, false, true, false,
|
||||||
|
false, true, true, false, false,
|
||||||
|
true, false, true, false, true,
|
||||||
|
false, false, false, true, false,
|
||||||
|
];
|
||||||
|
assert_eq!(hysteresis(img).pixels, exp);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user