checkerboard_calibrate/chessboard/
binarize.rs1fn saturate_u8(x: f64) -> u8 {
11 x.round_ties_even().clamp(0.0, 255.0) as u8
12}
13
14pub fn equalize_hist(src: &[u8]) -> Vec<u8> {
20 let mut hist = [0u64; 256];
21 for &v in src {
22 hist[v as usize] += 1;
23 }
24 let total = src.len() as u64;
25
26 let mut first = 0usize;
28 while first < 256 && hist[first] == 0 {
29 first += 1;
30 }
31 if first == 256 {
32 return Vec::new();
34 }
35 if hist[first] == total {
36 return vec![first as u8; src.len()];
38 }
39
40 let scale = 255.0 / (total - hist[first]) as f64;
41 let mut lut = [0u8; 256];
42 lut[first] = 0;
43 let mut sum = 0u64;
44 for (i, &h) in hist.iter().enumerate().skip(first + 1) {
45 sum += h;
46 lut[i] = saturate_u8(sum as f64 * scale);
47 }
48
49 src.iter().map(|&v| lut[v as usize]).collect()
50}
51
52pub fn adaptive_threshold_mean(
63 src: &[u8],
64 width: usize,
65 height: usize,
66 block_size: usize,
67 c: f64,
68) -> Vec<u8> {
69 assert_eq!(src.len(), width * height, "src length must be width*height");
70 assert!(
71 block_size >= 3 && block_size % 2 == 1,
72 "block_size must be odd and >= 3"
73 );
74
75 let r = block_size / 2;
76 let pw = width + 2 * r;
77 let ph = height + 2 * r;
78
79 let stride = pw + 1;
82 let mut integral = vec![0i64; stride * (ph + 1)];
83 for py in 0..ph {
84 let sy = py.saturating_sub(r).min(height - 1);
86 let mut row_sum = 0i64;
87 for px in 0..pw {
88 let sx = px.saturating_sub(r).min(width - 1);
89 row_sum += src[sy * width + sx] as i64;
90 integral[(py + 1) * stride + (px + 1)] = integral[py * stride + (px + 1)] + row_sum;
91 }
92 }
93
94 let area = (block_size * block_size) as f64;
95 let idelta = c.ceil() as i64; let mut dst = vec![0u8; src.len()];
98 for y in 0..height {
99 for x in 0..width {
100 let y0 = y;
102 let y1 = y + block_size;
103 let x0 = x;
104 let x1 = x + block_size;
105 let sum = integral[y1 * stride + x1]
106 - integral[y0 * stride + x1]
107 - integral[y1 * stride + x0]
108 + integral[y0 * stride + x0];
109 let mean = saturate_u8(sum as f64 / area) as i64;
110 let v = src[y * width + x] as i64;
111 dst[y * width + x] = if v > mean - idelta { 255 } else { 0 };
112 }
113 }
114 dst
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn equalize_hist_known_lut() {
123 let src = [0u8, 1, 2, 3];
127 assert_eq!(equalize_hist(&src), vec![0, 85, 170, 255]);
128 }
129
130 #[test]
131 fn equalize_hist_constant_image() {
132 let src = [42u8; 10];
133 assert_eq!(equalize_hist(&src), vec![42u8; 10]);
134 }
135
136 #[test]
137 fn equalize_hist_spreads_to_full_range() {
138 let src = [100u8, 100, 100, 150, 200];
140 let out = equalize_hist(&src);
141 assert_eq!(out[0], 0);
142 assert_eq!(*out.last().unwrap(), 255);
143 assert!(out[3] <= out[4]);
145 }
146
147 #[test]
148 fn adaptive_threshold_constant_image() {
149 let src = [100u8; 9];
150 assert_eq!(adaptive_threshold_mean(&src, 3, 3, 3, 0.0), vec![0u8; 9]);
152 assert_eq!(adaptive_threshold_mean(&src, 3, 3, 3, 1.0), vec![255u8; 9]);
154 }
155
156 #[test]
157 fn adaptive_threshold_bright_pixel() {
158 #[rustfmt::skip]
161 let src = [
162 10, 10, 10,
163 10, 250, 10,
164 10, 10, 10u8,
165 ];
166 let out = adaptive_threshold_mean(&src, 3, 3, 3, 0.0);
167 assert_eq!(out[4], 255);
170 assert_eq!(out[0], 0);
173 }
174}