imageproc/
morphology.rs

1//! Functions for computing [morphological operators].
2//!
3//! [morphological operators]: https://homepages.inf.ed.ac.uk/rbf/HIPR2/morops.htm
4
5use crate::distance_transform::{
6    distance_transform_impl, distance_transform_mut, DistanceFrom, Norm,
7};
8use image::GrayImage;
9use std::u8;
10
11/// Sets all pixels within distance `k` of a foreground pixel to white.
12///
13/// A pixel is treated as belonging to the foreground if it has non-zero intensity.
14///
15/// # Examples
16/// ```
17/// # extern crate image;
18/// # #[macro_use]
19/// # extern crate imageproc;
20/// # fn main() {
21/// use image::GrayImage;
22/// use imageproc::morphology::dilate;
23/// use imageproc::distance_transform::Norm;
24///
25/// let image = gray_image!(
26///     0,   0,   0,   0,   0;
27///     0,   0,   0,   0,   0;
28///     0,   0, 255,   0,   0;
29///     0,   0,   0,   0,   0;
30///     0,   0,   0,   0,   0
31/// );
32///
33/// // L1 norm
34/// let l1_dilated = gray_image!(
35///     0,   0,   0,   0,   0;
36///     0,   0, 255,   0,   0;
37///     0, 255, 255, 255,   0;
38///     0,   0, 255,   0,   0;
39///     0,   0,   0,   0,   0
40/// );
41///
42/// assert_pixels_eq!(dilate(&image, Norm::L1, 1), l1_dilated);
43///
44/// // LInf norm
45/// let linf_dilated = gray_image!(
46///    0,   0,   0,   0,   0;
47///    0, 255, 255, 255,   0;
48///    0, 255, 255, 255,   0;
49///    0, 255, 255, 255,   0;
50///    0,   0,   0,   0,   0
51/// );
52///
53/// assert_pixels_eq!(dilate(&image, Norm::LInf, 1), linf_dilated);
54/// # }
55/// ```
56pub fn dilate(image: &GrayImage, norm: Norm, k: u8) -> GrayImage {
57    let mut out = image.clone();
58    dilate_mut(&mut out, norm, k);
59    out
60}
61
62/// Sets all pixels within distance `k` of a foreground pixel to white.
63///
64/// A pixel is treated as belonging to the foreground if it has non-zero intensity.
65///
66/// See the [`dilate`](fn.dilate.html) documentation for examples.
67pub fn dilate_mut(image: &mut GrayImage, norm: Norm, k: u8) {
68    distance_transform_mut(image, norm);
69    for p in image.iter_mut() {
70        *p = if *p <= k { 255 } else { 0 };
71    }
72}
73
74/// Sets all pixels within distance `k` of a background pixel to black.
75///
76/// A pixel is treated as belonging to the foreground if it has non-zero intensity.
77///
78/// # Examples
79/// ```
80/// # extern crate image;
81/// # #[macro_use]
82/// # extern crate imageproc;
83/// # fn main() {
84/// use image::GrayImage;
85/// use imageproc::morphology::erode;
86/// use imageproc::distance_transform::Norm;
87///
88/// let image = gray_image!(
89///     0,   0,   0,   0,   0,   0,   0,   0,  0;
90///     0, 255, 255, 255, 255, 255, 255, 255,  0;
91///     0, 255, 255, 255, 255, 255, 255, 255,  0;
92///     0, 255, 255, 255, 255, 255, 255, 255,  0;
93///     0, 255, 255, 255,   0, 255, 255, 255,  0;
94///     0, 255, 255, 255, 255, 255, 255, 255,  0;
95///     0, 255, 255, 255, 255, 255, 255, 255,  0;
96///     0, 255, 255, 255, 255, 255, 255, 255,  0;
97///     0,   0,   0,   0,   0,   0,   0,   0,  0
98/// );
99///
100/// // L1 norm - the outermost foreground pixels are eroded,
101/// // as well as those horizontally and vertically adjacent
102/// // to the centre background pixel.
103/// let l1_eroded = gray_image!(
104///     0,   0,   0,   0,   0,   0,   0,   0,  0;
105///     0,   0,   0,   0,   0,   0,   0,   0,  0;
106///     0,   0, 255, 255, 255, 255, 255,   0,  0;
107///     0,   0, 255, 255,   0, 255, 255,   0,  0;
108///     0,   0, 255,   0,   0,   0, 255,   0,  0;
109///     0,   0, 255, 255,   0, 255, 255,   0,  0;
110///     0,   0, 255, 255, 255, 255, 255,   0,  0;
111///     0,   0,   0,   0,   0,   0,   0,   0,  0;
112///     0,   0,   0,   0,   0,   0,   0,   0,  0
113/// );
114///
115/// assert_pixels_eq!(erode(&image, Norm::L1, 1), l1_eroded);
116///
117/// // LInf norm - all pixels eroded using the L1 norm are eroded,
118/// // as well as the pixels diagonally adjacent to the centre pixel.
119/// let linf_eroded = gray_image!(
120///     0,   0,   0,   0,   0,   0,   0,   0,  0;
121///     0,   0,   0,   0,   0,   0,   0,   0,  0;
122///     0,   0, 255, 255, 255, 255, 255,   0,  0;
123///     0,   0, 255,   0,   0,   0, 255,   0,  0;
124///     0,   0, 255,   0,   0,   0, 255,   0,  0;
125///     0,   0, 255,   0,   0,   0, 255,   0,  0;
126///     0,   0, 255, 255, 255, 255, 255,   0,  0;
127///     0,   0,   0,   0,   0,   0,   0,   0,  0;
128///     0,   0,   0,   0,   0,   0,   0,   0,  0
129/// );
130///
131/// assert_pixels_eq!(erode(&image, Norm::LInf, 1), linf_eroded);
132/// # }
133/// ```
134pub fn erode(image: &GrayImage, norm: Norm, k: u8) -> GrayImage {
135    let mut out = image.clone();
136    erode_mut(&mut out, norm, k);
137    out
138}
139
140/// Sets all pixels within distance `k` of a background pixel to black.
141///
142/// A pixel is treated as belonging to the foreground if it has non-zero intensity.
143///
144/// See the [`erode`](fn.erode.html) documentation for examples.
145pub fn erode_mut(image: &mut GrayImage, norm: Norm, k: u8) {
146    distance_transform_impl(image, norm, DistanceFrom::Background);
147    for p in image.iter_mut() {
148        *p = if *p <= k { 0 } else { 255 };
149    }
150}
151
152/// Erosion followed by dilation.
153///
154/// See the [`erode`](fn.erode.html) and [`dilate`](fn.dilate.html)
155/// documentation for definitions of dilation and erosion.
156///
157/// # Examples
158/// ```
159/// # extern crate image;
160/// # #[macro_use]
161/// # extern crate imageproc;
162/// # fn main() {
163/// use imageproc::morphology::open;
164/// use imageproc::distance_transform::Norm;
165///
166/// // Isolated regions of foreground pixels are removed.
167/// let cross = gray_image!(
168///       0,   0,   0,   0,   0;
169///       0,   0, 255,   0,   0;
170///       0, 255, 255, 255,   0;
171///       0,   0, 255,   0,   0;
172///       0,   0,   0,   0,   0
173/// );
174///
175/// let opened_cross = gray_image!(
176///       0,   0,   0,   0,   0;
177///       0,   0,   0,   0,   0;
178///       0,   0,   0,   0,   0;
179///       0,   0,   0,   0,   0;
180///       0,   0,   0,   0,   0
181/// );
182///
183/// assert_pixels_eq!(
184///     open(&cross, Norm::LInf, 1),
185///     opened_cross
186/// );
187///
188/// // Large blocks survive unchanged.
189/// let blob = gray_image!(
190///       0,   0,   0,   0,   0;
191///       0, 255, 255, 255,   0;
192///       0, 255, 255, 255,   0;
193///       0, 255, 255, 255,   0;
194///       0,   0,   0,   0,   0
195/// );
196///
197/// assert_pixels_eq!(
198///     open(&blob, Norm::LInf, 1),
199///     blob
200/// );
201/// # }
202/// ```
203pub fn open(image: &GrayImage, norm: Norm, k: u8) -> GrayImage {
204    let mut out = image.clone();
205    open_mut(&mut out, norm, k);
206    out
207}
208
209/// Erosion followed by dilation.
210///
211/// See the [`open`](fn.open.html) documentation for examples,
212/// and the [`erode`](fn.erode.html) and [`dilate`](fn.dilate.html)
213/// documentation for definitions of dilation and erosion.
214pub fn open_mut(image: &mut GrayImage, norm: Norm, k: u8) {
215    erode_mut(image, norm, k);
216    dilate_mut(image, norm, k);
217}
218
219/// Dilation followed by erosion.
220///
221/// See the [`erode`](fn.erode.html) and [`dilate`](fn.dilate.html)
222/// documentation for definitions of dilation and erosion.
223///
224/// # Examples
225/// ```
226/// # extern crate image;
227/// # #[macro_use]
228/// # extern crate imageproc;
229/// # fn main() {
230/// use imageproc::morphology::close;
231/// use imageproc::distance_transform::Norm;
232///
233/// // Small holes are closed - hence the name.
234/// let small_hole = gray_image!(
235///     255, 255, 255, 255;
236///     255,   0,   0, 255;
237///     255,   0,   0, 255;
238///     255, 255, 255, 255
239/// );
240///
241/// let closed_small_hole = gray_image!(
242///     255, 255, 255, 255;
243///     255, 255, 255, 255;
244///     255, 255, 255, 255;
245///     255, 255, 255, 255
246/// );
247///
248/// assert_pixels_eq!(
249///     close(&small_hole, Norm::LInf, 1),
250///     closed_small_hole
251/// );
252///
253/// // Large holes survive unchanged.
254/// let large_hole = gray_image!(
255///     255, 255, 255, 255, 255;
256///     255,   0,   0,   0, 255;
257///     255,   0,   0,   0, 255;
258///     255,   0,   0,   0, 255;
259///     255, 255, 255, 255, 255
260/// );
261///
262/// assert_pixels_eq!(
263///     close(&large_hole, Norm::LInf, 1),
264///     large_hole
265/// );
266///
267/// // A dot gains a layer of foreground pixels
268/// // when dilated and loses them again when eroded,
269/// // resulting in no change.
270/// let dot = gray_image!(
271///       0,   0,   0,   0,   0;
272///       0,   0,   0,   0,   0;
273///       0,   0, 255,   0,   0;
274///       0,   0,   0,   0,   0;
275///       0,   0,   0,   0,   0
276/// );
277///
278/// assert_pixels_eq!(
279///     close(&dot, Norm::LInf, 1),
280///     dot
281/// );
282///
283/// // A dot near the boundary gains pixels in the top-left
284/// // of the image which are not within distance 1 of any
285/// // background pixels, so are not removed by erosion.
286/// let dot_near_boundary = gray_image!(
287///       0,   0,   0,   0,   0;
288///       0, 255,   0,   0,   0;
289///       0,   0,   0,   0,   0;
290///       0,   0,   0,   0,   0;
291///       0,   0,   0,   0,   0
292/// );
293///
294/// let closed_dot_near_boundary = gray_image!(
295///     255, 255,   0,   0,   0;
296///     255, 255,   0,   0,   0;
297///       0,   0,   0,   0,   0;
298///       0,   0,   0,   0,   0;
299///       0,   0,   0,   0,   0
300/// );
301///
302/// assert_pixels_eq!(
303///     close(&dot_near_boundary, Norm::LInf, 1),
304///     closed_dot_near_boundary
305/// );
306/// # }
307/// ```
308pub fn close(image: &GrayImage, norm: Norm, k: u8) -> GrayImage {
309    let mut out = image.clone();
310    close_mut(&mut out, norm, k);
311    out
312}
313
314/// Dilation followed by erosion.
315///
316/// See the [`close`](fn.close.html) documentation for examples,
317/// and the [`erode`](fn.erode.html) and [`dilate`](fn.dilate.html)
318/// documentation for definitions of dilation and erosion.
319pub fn close_mut(image: &mut GrayImage, norm: Norm, k: u8) {
320    dilate_mut(image, norm, k);
321    erode_mut(image, norm, k);
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327    use ::test::*;
328    use image::{GrayImage, Luma};
329    use std::cmp::{max, min};
330
331    #[test]
332    fn test_dilate_point_l1_1() {
333        let image = gray_image!(
334              0,   0,   0,   0,   0;
335              0,   0,   0,   0,   0;
336              0,   0, 255,   0,   0;
337              0,   0,   0,   0,   0;
338              0,   0,   0,   0,   0
339        );
340        let dilated = dilate(&image, Norm::L1, 1);
341
342        let expected = gray_image!(
343              0,   0,   0,   0,   0;
344              0,   0, 255,   0,   0;
345              0, 255, 255, 255,   0;
346              0,   0, 255,   0,   0;
347              0,   0,   0,   0,   0
348        );
349
350        assert_pixels_eq!(dilated, expected);
351    }
352
353    #[test]
354    fn test_dilate_point_l1_2() {
355        let image = gray_image!(
356              0,   0,   0,   0,   0;
357              0,   0,   0,   0,   0;
358              0,   0, 255,   0,   0;
359              0,   0,   0,   0,   0;
360              0,   0,   0,   0,   0
361        );
362        let dilated = dilate(&image, Norm::L1, 2);
363
364        let expected = gray_image!(
365              0,   0, 255,   0,   0;
366              0, 255, 255, 255,   0;
367            255, 255, 255, 255, 255;
368              0, 255, 255, 255,   0;
369              0,   0, 255,   0,   0
370        );
371
372        assert_pixels_eq!(dilated, expected);
373    }
374
375    #[test]
376    fn test_dilate_point_linf_1() {
377        let image = gray_image!(
378              0,   0,   0,   0,   0;
379              0,   0,   0,   0,   0;
380              0,   0, 255,   0,   0;
381              0,   0,   0,   0,   0;
382              0,   0,   0,   0,   0
383        );
384        let dilated = dilate(&image, Norm::LInf, 1);
385
386        let expected = gray_image!(
387              0,   0,   0,   0,   0;
388              0, 255, 255, 255,   0;
389              0, 255, 255, 255,   0;
390              0, 255, 255, 255,   0;
391              0,   0,   0,   0,   0
392        );
393
394        assert_pixels_eq!(dilated, expected);
395    }
396
397    #[test]
398    fn test_dilate_point_linf_2() {
399        let image = gray_image!(
400              0,   0,   0,   0,   0;
401              0,   0,   0,   0,   0;
402              0,   0, 255,   0,   0;
403              0,   0,   0,   0,   0;
404              0,   0,   0,   0,   0
405        );
406        let dilated = dilate(&image, Norm::LInf, 2);
407
408        let expected = gray_image!(
409            255, 255, 255, 255, 255;
410            255, 255, 255, 255, 255;
411            255, 255, 255, 255, 255;
412            255, 255, 255, 255, 255;
413            255, 255, 255, 255, 255
414        );
415
416        assert_pixels_eq!(dilated, expected);
417    }
418
419    #[test]
420    fn test_erode_point_l1_1() {
421        let image = gray_image!(
422              0,   0,   0,   0,   0;
423              0,   0,   0,   0,   0;
424              0,   0, 255,   0,   0;
425              0,   0,   0,   0,   0;
426              0,   0,   0,   0,   0
427        );
428        let eroded = erode(&image, Norm::L1, 1);
429
430        let expected = gray_image!(
431              0,   0,   0,   0,   0;
432              0,   0,   0,   0,   0;
433              0,   0,   0,   0,   0;
434              0,   0,   0,   0,   0;
435              0,   0,   0,   0,   0
436        );
437
438        assert_pixels_eq!(eroded, expected);
439    }
440
441    #[test]
442    fn test_erode_point_linf_1() {
443        let image = gray_image!(
444              0,   0,   0,   0,   0;
445              0,   0,   0,   0,   0;
446              0,   0, 255,   0,   0;
447              0,   0,   0,   0,   0;
448              0,   0,   0,   0,   0
449        );
450        let eroded = erode(&image, Norm::LInf, 1);
451
452        let expected = gray_image!(
453              0,   0,   0,   0,   0;
454              0,   0,   0,   0,   0;
455              0,   0,   0,   0,   0;
456              0,   0,   0,   0,   0;
457              0,   0,   0,   0,   0
458        );
459
460        assert_pixels_eq!(eroded, expected);
461    }
462
463    fn square() -> GrayImage {
464        GrayImage::from_fn(500, 500, |x, y| {
465            if min(x, y) > 100 && max(x, y) < 300 {
466                Luma([255u8])
467            } else {
468                Luma([0u8])
469            }
470        })
471    }
472
473    #[bench]
474    fn bench_dilate_l1_5(b: &mut Bencher) {
475        let image = square();
476        b.iter(|| {
477            let dilated = dilate(&image, Norm::L1, 5);
478            black_box(dilated);
479        })
480    }
481
482    #[bench]
483    fn bench_dilate_linf_5(b: &mut Bencher) {
484        let image = square();
485        b.iter(|| {
486            let dilated = dilate(&image, Norm::LInf, 5);
487            black_box(dilated);
488        })
489    }
490}