scirs2_vision/preprocessing/
morphology.rs1use crate::error::{Result, VisionError};
7use image::{DynamicImage, ImageBuffer, Luma};
8
9#[derive(Debug, Clone, Copy)]
11pub enum StructuringElement {
12 Rectangle(usize, usize),
14 Ellipse(usize, usize),
16 Cross(usize),
18}
19
20#[allow(dead_code)]
30fn create_structuring_element(shape: StructuringElement) -> Result<Vec<Vec<bool>>> {
31 match shape {
32 StructuringElement::Rectangle(width, height) => {
33 if width == 0 || height == 0 {
34 return Err(VisionError::InvalidParameter(
35 "Width and height must be positive".to_string(),
36 ));
37 }
38
39 let kernel = vec![vec![true; width]; height];
41 Ok(kernel)
42 }
43 StructuringElement::Ellipse(width, height) => {
44 if width == 0 || height == 0 {
45 return Err(VisionError::InvalidParameter(
46 "Width and height must be positive".to_string(),
47 ));
48 }
49
50 let center_x = width as f32 / 2.0;
51 let center_y = height as f32 / 2.0;
52 let radius_x = center_x;
53 let radius_y = center_y;
54
55 let mut kernel = vec![vec![false; width]; height];
57
58 for (y, row) in kernel.iter_mut().enumerate() {
59 for (x, cell) in row.iter_mut().enumerate() {
60 let dx = (x as f32 - center_x + 0.5) / radius_x;
61 let dy = (y as f32 - center_y + 0.5) / radius_y;
62
63 if dx * dx + dy * dy <= 1.0 {
65 *cell = true;
66 }
67 }
68 }
69
70 Ok(kernel)
71 }
72 StructuringElement::Cross(size) => {
73 if size == 0 {
74 return Err(VisionError::InvalidParameter(
75 "Size must be positive".to_string(),
76 ));
77 }
78
79 let size = if size % 2 == 0 { size + 1 } else { size };
81
82 let mut kernel = vec![vec![false; size]; size];
84 let center = size / 2;
85
86 for (y, row) in kernel.iter_mut().enumerate() {
87 for (x, cell) in row.iter_mut().enumerate() {
88 if x == center || y == center {
89 *cell = true;
90 }
91 }
92 }
93
94 Ok(kernel)
95 }
96 }
97}
98
99#[allow(dead_code)]
110pub fn erode(img: &DynamicImage, kernelshape: StructuringElement) -> Result<DynamicImage> {
111 let gray = img.to_luma8();
112 let (width, height) = gray.dimensions();
113 let kernel = create_structuring_element(kernelshape)?;
114
115 let kernel_height = kernel.len();
116 let kernel_width = kernel[0].len();
117
118 let anchor_x = kernel_width / 2;
120 let anchor_y = kernel_height / 2;
121
122 let mut result = ImageBuffer::new(width, height);
123
124 for y in 0..height {
125 for x in 0..width {
126 let mut min_val = 255u8;
128
129 for (ky, kernel_row) in kernel.iter().enumerate() {
131 for (kx, &is_active) in kernel_row.iter().enumerate() {
132 if !is_active {
134 continue;
135 }
136
137 let img_x = x as isize + (kx as isize - anchor_x as isize);
139 let img_y = y as isize + (ky as isize - anchor_y as isize);
140
141 if img_x < 0 || img_x >= width as isize || img_y < 0 || img_y >= height as isize
143 {
144 continue;
145 }
146
147 let pixel_val = gray.get_pixel(img_x as u32, img_y as u32)[0];
149 min_val = min_val.min(pixel_val);
150 }
151 }
152
153 result.put_pixel(x, y, Luma([min_val]));
155 }
156 }
157
158 Ok(DynamicImage::ImageLuma8(result))
159}
160
161#[allow(dead_code)]
172pub fn dilate(img: &DynamicImage, kernelshape: StructuringElement) -> Result<DynamicImage> {
173 let gray = img.to_luma8();
174 let (width, height) = gray.dimensions();
175 let kernel = create_structuring_element(kernelshape)?;
176
177 let kernel_height = kernel.len();
178 let kernel_width = kernel[0].len();
179
180 let anchor_x = kernel_width / 2;
182 let anchor_y = kernel_height / 2;
183
184 let mut result = ImageBuffer::new(width, height);
185
186 for y in 0..height {
187 for x in 0..width {
188 let mut max_val = 0u8;
190
191 for (ky, kernel_row) in kernel.iter().enumerate() {
193 for (kx, &is_active) in kernel_row.iter().enumerate() {
194 if !is_active {
196 continue;
197 }
198
199 let img_x = x as isize - (kx as isize - anchor_x as isize);
201 let img_y = y as isize - (ky as isize - anchor_y as isize);
202
203 if img_x < 0 || img_x >= width as isize || img_y < 0 || img_y >= height as isize
205 {
206 continue;
207 }
208
209 let pixel_val = gray.get_pixel(img_x as u32, img_y as u32)[0];
211 max_val = max_val.max(pixel_val);
212 }
213 }
214
215 result.put_pixel(x, y, Luma([max_val]));
217 }
218 }
219
220 Ok(DynamicImage::ImageLuma8(result))
221}
222
223#[allow(dead_code)]
234pub fn opening(img: &DynamicImage, kernelshape: StructuringElement) -> Result<DynamicImage> {
235 let eroded = erode(img, kernelshape)?;
236 dilate(&eroded, kernelshape)
237}
238
239#[allow(dead_code)]
250pub fn closing(img: &DynamicImage, kernelshape: StructuringElement) -> Result<DynamicImage> {
251 let dilated = dilate(img, kernelshape)?;
252 erode(&dilated, kernelshape)
253}
254
255#[allow(dead_code)]
266pub fn morphological_gradient(
267 img: &DynamicImage,
268 kernelshape: StructuringElement,
269) -> Result<DynamicImage> {
270 let dilated = dilate(img, kernelshape)?;
271 let eroded = erode(img, kernelshape)?;
272
273 let dilated_gray = dilated.to_luma8();
274 let eroded_gray = eroded.to_luma8();
275 let (width, height) = dilated_gray.dimensions();
276
277 let mut result = ImageBuffer::new(width, height);
278
279 for y in 0..height {
280 for x in 0..width {
281 let dilated_val = dilated_gray.get_pixel(x, y)[0];
282 let eroded_val = eroded_gray.get_pixel(x, y)[0];
283
284 let gradient = dilated_val.saturating_sub(eroded_val);
287
288 result.put_pixel(x, y, Luma([gradient]));
290 }
291 }
292
293 Ok(DynamicImage::ImageLuma8(result))
294}
295
296#[allow(dead_code)]
307pub fn top_hat(img: &DynamicImage, kernelshape: StructuringElement) -> Result<DynamicImage> {
308 let opened = opening(img, kernelshape)?;
309
310 let original = img.to_luma8();
311 let opened_gray = opened.to_luma8();
312 let (width, height) = original.dimensions();
313
314 let mut result = ImageBuffer::new(width, height);
315
316 for y in 0..height {
317 for x in 0..width {
318 let original_val = original.get_pixel(x, y)[0];
319 let opened_val = opened_gray.get_pixel(x, y)[0];
320
321 let top_hat_val = original_val.saturating_sub(opened_val);
324
325 result.put_pixel(x, y, Luma([top_hat_val]));
327 }
328 }
329
330 Ok(DynamicImage::ImageLuma8(result))
331}
332
333#[allow(dead_code)]
344pub fn black_hat(img: &DynamicImage, kernelshape: StructuringElement) -> Result<DynamicImage> {
345 let closed = closing(img, kernelshape)?;
346
347 let original = img.to_luma8();
348 let closed_gray = closed.to_luma8();
349 let (width, height) = original.dimensions();
350
351 let mut result = ImageBuffer::new(width, height);
352
353 for y in 0..height {
354 for x in 0..width {
355 let original_val = original.get_pixel(x, y)[0];
356 let closed_val = closed_gray.get_pixel(x, y)[0];
357
358 let black_hat_val = closed_val.saturating_sub(original_val);
361
362 result.put_pixel(x, y, Luma([black_hat_val]));
364 }
365 }
366
367 Ok(DynamicImage::ImageLuma8(result))
368}