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}