scan_core/cv/
threshold.rs1use image::GrayImage;
2
3pub fn adaptive_threshold(img: &GrayImage, kernel_size: usize, threshold: i32) -> GrayImage {
9 let (width, height) = img.dimensions();
10 let mut blurred = stack_box_blur(img, kernel_size);
11 let src = img.as_raw();
12 let dst = blurred.as_mut();
13
14 let mut tab = [0u8; 768];
17 for i in 0..768 {
18 tab[i] = if (i as i32 - 255) <= -threshold { 255 } else { 0 };
19 }
20
21 for i in 0..src.len() {
23 let idx = src[i] as i32 - dst[i] as i32 + 255;
24 dst[i] = tab[idx as usize];
25 }
26
27 GrayImage::from_raw(width, height, dst.to_vec()).unwrap()
28}
29
30fn stack_box_blur(img: &GrayImage, kernel_size: usize) -> GrayImage {
33 let (width, height) = img.dimensions();
34 let w = width as usize;
35 let h = height as usize;
36 let src = img.as_raw();
37 let mut dst = src.clone();
38
39 let mult_table: [u32; 16] = [1, 171, 205, 293, 57, 373, 79, 137, 241, 27, 391, 357, 41, 19, 283, 265];
41 let shift_table: [u32; 16] = [0, 9, 10, 11, 9, 12, 10, 11, 12, 9, 13, 13, 10, 9, 13, 13];
42
43 let ks = kernel_size.min(15);
44 let mult = mult_table[ks];
45 let shift = shift_table[ks];
46 let size = ks + ks + 1;
47 let radius = ks + 1;
48
49 let mut stack = vec![0u32; size];
51
52 for y in 0..h {
54 let row_start = y * w;
55
56 let first_color = dst[row_start] as u32;
58 let mut sum = radius as u32 * first_color;
59
60 for i in 0..radius {
62 stack[i] = first_color;
63 }
64 for i in 1..radius {
66 let px = if i < w { dst[row_start + i] as u32 } else { dst[row_start + w - 1] as u32 };
67 stack[radius - 1 + i] = px;
68 sum += px;
69 }
70
71 let mut stack_idx = 0;
72
73 for x in 0..w {
74 dst[row_start + x] = ((sum * mult) >> shift) as u8;
75
76 let p = x + radius;
78 let p = if p < w { row_start + p } else { row_start + w - 1 };
79 let incoming = src[p] as u32;
80
81 sum -= stack[stack_idx];
82 sum += incoming;
83 stack[stack_idx] = incoming;
84
85 stack_idx += 1;
86 if stack_idx >= size { stack_idx = 0; }
87 }
88 }
89
90 let src_v = dst.clone();
92 for x in 0..w {
93 let first_color = src_v[x] as u32;
94 let mut sum = radius as u32 * first_color;
95
96 for i in 0..radius {
97 stack[i] = first_color;
98 }
99 for i in 1..radius {
100 let py = if i < h { i } else { h - 1 };
101 let px = src_v[py * w + x] as u32;
102 stack[radius - 1 + i] = px;
103 sum += px;
104 }
105
106 let mut stack_idx = 0;
107
108 for y in 0..h {
109 dst[y * w + x] = ((sum * mult) >> shift) as u8;
110
111 let p = y + radius;
112 let py = if p < h { p } else { h - 1 };
113 let incoming = src_v[py * w + x] as u32;
114
115 sum -= stack[stack_idx];
116 sum += incoming;
117 stack[stack_idx] = incoming;
118
119 stack_idx += 1;
120 if stack_idx >= size { stack_idx = 0; }
121 }
122 }
123
124 GrayImage::from_raw(width, height, dst).unwrap()
125}
126
127pub fn global_threshold(img: &GrayImage, thresh: u8) -> GrayImage {
130 let mut out = GrayImage::new(img.width(), img.height());
131 for (x, y, p) in img.enumerate_pixels() {
132 let val = if p[0] > thresh { 255 } else { 0 };
133 out.put_pixel(x, y, image::Luma([val]));
134 }
135 out
136}
137
138pub fn otsu_threshold(img: &GrayImage) -> u8 {
140 let mut hist = [0u32; 256];
141 for p in img.pixels() {
142 hist[p[0] as usize] += 1;
143 }
144 let total = img.len() as f32;
145
146 let mut sum = 0.0f32;
147 for i in 0..256 {
148 sum += i as f32 * hist[i] as f32;
149 }
150
151 let mut sum_b = 0.0f32;
152 let mut w_b = 0u32;
153 let mut max = 0.0f32;
154 let mut threshold = 0u8;
155
156 for i in 0..256 {
157 w_b += hist[i];
158 if w_b == 0 { continue; }
159 let w_f = total as u32 - w_b;
160 if w_f == 0 { break; }
161
162 sum_b += i as f32 * hist[i] as f32;
163
164 let m_b = sum_b / w_b as f32;
165 let m_f = (sum - sum_b) / w_f as f32;
166
167 let between = w_b as f32 * w_f as f32 * (m_b - m_f).powi(2);
168 if between > max {
169 max = between;
170 threshold = i as u8;
171 }
172 }
173 threshold
174}