scirs2_vision/segmentation/
mod.rs1pub mod grabcut;
7pub mod kmeans_seg;
8pub mod mean_shift;
9pub mod region_growing;
10pub mod semantic;
11pub mod slic;
12pub mod unified;
13pub mod watershed;
14
15pub use grabcut::{
16 apply_foreground_mask, grabcut_mask_to_image, grabcut_rect, grabcut_with_mask, GrabCutMask,
17 GrabCutParams, GrabCutResult,
18};
19pub use kmeans_seg::{
20 kmeans_labels_to_color, kmeans_labels_to_gray, kmeans_segment, KMeansSegParams, KMeansSegResult,
21};
22pub use mean_shift::{mean_shift, MeanShiftParams};
23pub use region_growing::{
24 adaptive_region_growing, region_growing, region_labels_to_color, RegionGrowingParams, SeedPoint,
25};
26pub use semantic::*;
27pub use slic::{draw_superpixel_boundaries, slic};
28pub use unified::{segment, SegmentMethod, SegmentResult};
29pub use watershed::{
30 compute_gradient_magnitude, labels_to_color_image, watershed, watershed_markers,
31};
32
33use crate::error::{Result, VisionError};
34use crate::feature::image_to_array;
35use image::{DynamicImage, GrayImage, ImageBuffer, Luma};
36#[derive(Debug, Clone, Copy)]
40pub enum AdaptiveMethod {
41 Mean,
43 Gaussian,
45}
46
47#[allow(dead_code)]
58pub fn threshold_binary(img: &DynamicImage, threshold: f32) -> Result<GrayImage> {
59 let array = image_to_array(img)?;
60 let (height, width) = array.dim();
61
62 let mut binary = ImageBuffer::new(width as u32, height as u32);
63
64 for y in 0..height {
65 for x in 0..width {
66 let value = if array[[y, x]] >= threshold { 255 } else { 0 };
67 binary.put_pixel(x as u32, y as u32, Luma([value]));
68 }
69 }
70
71 Ok(binary)
72}
73
74#[allow(dead_code)]
84pub fn otsu_threshold(img: &DynamicImage) -> Result<(GrayImage, f32)> {
85 let gray = img.to_luma8();
86 let (width, height) = gray.dimensions();
87 let total_pixels = (width * height) as usize;
88
89 let mut histogram = [0; 256];
91 for pixel in gray.pixels() {
92 histogram[pixel[0] as usize] += 1;
93 }
94
95 let mut sum = 0;
97 for (i, &count) in histogram.iter().enumerate() {
98 sum += i * count;
99 }
100
101 let mut sum_background = 0;
102 let mut weight_background = 0;
103 let mut max_variance = 0.0;
104 let mut threshold = 0;
105
106 for (i, &count) in histogram.iter().enumerate() {
107 weight_background += count;
109 if weight_background == 0 {
110 continue;
111 }
112
113 let weight_foreground = total_pixels - weight_background;
114 if weight_foreground == 0 {
115 break;
116 }
117
118 sum_background += i * histogram[i];
120
121 let mean_background = sum_background as f32 / weight_background as f32;
123 let mean_foreground = (sum - sum_background) as f32 / weight_foreground as f32;
124
125 let variance = weight_background as f32
127 * weight_foreground as f32
128 * (mean_background - mean_foreground).powi(2);
129
130 if variance > max_variance {
132 max_variance = variance;
133 threshold = i;
134 }
135 }
136
137 let threshold_f32 = threshold as f32 / 255.0;
139 let binary = threshold_binary(img, threshold_f32)?;
140
141 Ok((binary, threshold_f32))
142}
143
144#[allow(dead_code)]
157pub fn adaptive_threshold(
158 img: &DynamicImage,
159 block_size: usize,
160 c: f32,
161 method: AdaptiveMethod,
162) -> Result<GrayImage> {
163 if block_size.is_multiple_of(2) || block_size < 3 {
165 return Err(VisionError::InvalidParameter(
166 "block_size must be odd and at least 3".to_string(),
167 ));
168 }
169
170 let array = image_to_array(img)?;
171 let (height, width) = array.dim();
172 let radius = block_size / 2;
173
174 let mut binary = ImageBuffer::new(width as u32, height as u32);
175
176 for y in 0..height {
177 for x in 0..width {
178 let start_y = y.saturating_sub(radius);
180 let end_y = (y + radius + 1).min(height);
181 let start_x = x.saturating_sub(radius);
182 let end_x = (x + radius + 1).min(width);
183
184 let threshold = match method {
186 AdaptiveMethod::Mean => {
187 let mut sum = 0.0;
189 let mut count = 0;
190
191 for ny in start_y..end_y {
192 for nx in start_x..end_x {
193 sum += array[[ny, nx]];
194 count += 1;
195 }
196 }
197
198 sum / count as f32 - c
199 }
200 AdaptiveMethod::Gaussian => {
201 let mut weighted_sum = 0.0;
203 let mut weight_sum = 0.0;
204
205 for ny in start_y..end_y {
206 for nx in start_x..end_x {
207 let dy = (ny as isize - y as isize).pow(2) as f32;
208 let dx = (nx as isize - x as isize).pow(2) as f32;
209 let dist = (dy + dx).sqrt();
210
211 let sigma = radius as f32 / 2.0;
213 let weight = (-dist * dist / (2.0 * sigma * sigma)).exp();
214
215 weighted_sum += array[[ny, nx]] * weight;
216 weight_sum += weight;
217 }
218 }
219
220 weighted_sum / weight_sum - c
221 }
222 };
223
224 let value = if array[[y, x]] > threshold { 255 } else { 0 };
226 binary.put_pixel(x as u32, y as u32, Luma([value]));
227 }
228 }
229
230 Ok(binary)
231}
232
233pub type LabeledImage = ImageBuffer<Luma<u16>, Vec<u16>>;
245
246#[allow(dead_code)]
262pub fn connected_components(binary: &GrayImage) -> Result<(LabeledImage, u16)> {
263 let (width, height) = binary.dimensions();
264 let mut labels: ImageBuffer<Luma<u16>, Vec<u16>> = ImageBuffer::new(width, height);
265 let mut label_equiv = vec![0u16; 65536]; let mut next_label = 1u16;
267
268 for (i, val) in label_equiv.iter_mut().enumerate() {
271 *val = i as u16;
272 }
273
274 for y in 0..height {
276 for x in 0..width {
277 if binary.get_pixel(x, y)[0] == 0 {
279 labels.put_pixel(x, y, Luma([0]));
280 continue;
281 }
282
283 let mut neighbors = Vec::new();
285
286 if x > 0 && binary.get_pixel(x - 1, y)[0] > 0 {
287 neighbors.push(labels.get_pixel(x - 1, y)[0]);
288 }
289
290 if y > 0 && binary.get_pixel(x, y - 1)[0] > 0 {
291 neighbors.push(labels.get_pixel(x, y - 1)[0]);
292 }
293
294 if neighbors.is_empty() {
296 labels.put_pixel(x, y, Luma([next_label]));
297 next_label += 1;
298
299 if next_label == 0 {
301 return Err(VisionError::OperationError(
302 "Too many components (label overflow)".to_string(),
303 ));
304 }
305 } else {
306 let min_label = *neighbors.iter().min().expect("Operation failed");
308 labels.put_pixel(x, y, Luma([min_label]));
309
310 for &neighbor_label in &neighbors {
312 if neighbor_label != min_label {
313 union(&mut label_equiv, min_label, neighbor_label);
314 }
315 }
316 }
317 }
318 }
319
320 for y in 0..height {
322 for x in 0..width {
323 let label = labels.get_pixel(x, y)[0];
324 if label > 0 {
325 labels.put_pixel(x, y, Luma([find(&label_equiv, label)]));
326 }
327 }
328 }
329
330 let mut unique_labels = std::collections::HashSet::new();
332 for y in 0..height {
333 for x in 0..width {
334 let label = labels.get_pixel(x, y)[0];
335 if label > 0 {
336 unique_labels.insert(label);
337 }
338 }
339 }
340
341 Ok((labels, unique_labels.len() as u16))
342}
343
344#[allow(dead_code)]
346fn find(labels: &[u16], x: u16) -> u16 {
347 let mut y = x;
348 while y != labels[y as usize] {
349 y = labels[y as usize];
350 }
351 y
352}
353
354#[allow(dead_code)]
355fn union(labels: &mut [u16], x: u16, y: u16) {
356 let root_x = find(labels, x);
357 let root_y = find(labels, y);
358 if root_x <= root_y {
359 labels[root_y as usize] = root_x;
360 } else {
361 labels[root_x as usize] = root_y;
362 }
363}