1use crate::{
2 ocr_error::OcrError,
3 ocr_result::{Point, TextBox},
4};
5use image::imageops;
6use imageproc::geometric_transformations::{Interpolation, Projection};
7use ndarray::{Array, Array4};
8
9pub struct OcrUtils;
10
11impl OcrUtils {
12 pub fn substract_mean_normalize(
13 img_src: &image::RgbImage,
14 mean_vals: &[f32],
15 norm_vals: &[f32],
16 ) -> Array4<f32> {
17 let cols = img_src.width();
18 let rows = img_src.height();
19 let channels = 3;
20
21 let mut input_tensor = Array::zeros((1, channels as usize, rows as usize, cols as usize));
22
23 unsafe {
25 for r in 0..rows {
26 for c in 0..cols {
27 for ch in 0..channels {
28 let idx = (r * cols * channels + c * channels + ch) as usize;
29 let value = img_src.get_unchecked(idx).to_owned();
30 let data = value as f32 * norm_vals[ch as usize]
31 - mean_vals[ch as usize] * norm_vals[ch as usize];
32 input_tensor[[0, ch as usize, r as usize, c as usize]] = data;
33 }
34 }
35 }
36 }
37
38 input_tensor
39 }
40
41 pub fn make_padding(
42 img_src: &image::RgbImage,
43 padding: u32,
44 ) -> Result<image::RgbImage, OcrError> {
45 if padding == 0 {
46 return Ok(img_src.clone());
47 }
48
49 let width = img_src.width();
50 let height = img_src.height();
51
52 let mut padding_src = image::RgbImage::new(width + 2 * padding, height + 2 * padding);
53 imageproc::drawing::draw_filled_rect_mut(
54 &mut padding_src,
55 imageproc::rect::Rect::at(0, 0).of_size(width + 2 * padding, height + 2 * padding),
56 image::Rgb([255, 255, 255]),
57 );
58
59 image::imageops::replace(&mut padding_src, img_src, padding as i64, padding as i64);
60
61 Ok(padding_src)
62 }
63
64 pub fn get_part_images(
65 img_src: &image::RgbImage,
66 text_boxes: &[TextBox],
67 ) -> Vec<image::RgbImage> {
68 text_boxes
69 .iter()
70 .map(|text_box| Self::get_rotate_crop_image(img_src, &text_box.points))
71 .collect()
72 }
73
74 pub fn get_rotate_crop_image(
75 img_src: &image::RgbImage,
76 box_points: &[Point],
77 ) -> image::RgbImage {
78 let mut points = box_points.to_vec();
79
80 let (min_x, min_y, max_x, max_y) = points.iter().fold(
82 (u32::MAX, u32::MAX, 0u32, 0u32),
83 |(min_x, min_y, max_x, max_y), point| {
84 (
85 min_x.min(point.x),
86 min_y.min(point.y),
87 max_x.max(point.x),
88 max_y.max(point.y),
89 )
90 },
91 );
92
93 let img_crop =
95 imageops::crop_imm(img_src, min_x, min_y, max_x - min_x, max_y - min_y).to_image();
96
97 for point in &mut points {
98 point.x -= min_x;
99 point.y -= min_y;
100 }
101
102 let img_crop_width = ((points[0].x as i32 - points[1].x as i32).pow(2) as f32
103 + (points[0].y as i32 - points[1].y as i32).pow(2) as f32)
104 .sqrt() as u32;
105 let img_crop_height = ((points[0].x as i32 - points[3].x as i32).pow(2) as f32
106 + (points[0].y as i32 - points[3].y as i32).pow(2) as f32)
107 .sqrt() as u32;
108
109 let src_points = [
110 (points[0].x as f32, points[0].y as f32),
111 (points[1].x as f32, points[1].y as f32),
112 (points[2].x as f32, points[2].y as f32),
113 (points[3].x as f32, points[3].y as f32),
114 ];
115
116 let dst_points = [
117 (0.0, 0.0),
118 (img_crop_width as f32, 0.0),
119 (img_crop_width as f32, img_crop_height as f32),
120 (0.0, img_crop_height as f32),
121 ];
122
123 let projection = Projection::from_control_points(src_points, dst_points)
124 .expect("Failed to create projection transformation");
125
126 let mut part_img = image::RgbImage::new(img_crop_width, img_crop_height);
127 imageproc::geometric_transformations::warp_into(
128 &img_crop,
129 &projection,
130 Interpolation::Bilinear,
131 image::Rgb([255, 255, 255]),
132 &mut part_img,
133 );
134
135 if part_img.height() >= part_img.width() * 3 / 2 {
137 let mut rotated = image::RgbImage::new(part_img.height(), part_img.width());
138
139 for (x, y, pixel) in part_img.enumerate_pixels() {
140 rotated.put_pixel(y, part_img.width() - 1 - x, *pixel);
141 }
142
143 rotated
144 } else {
145 part_img
146 }
147 }
148
149 pub fn mat_rotate_clock_wise_180(src: &mut image::RgbImage) {
150 imageops::rotate180_in_place(src);
151 }
152
153 pub fn calculate_mean_with_mask(
154 img: &image::ImageBuffer<image::Luma<f32>, Vec<f32>>,
155 mask: &image::ImageBuffer<image::Luma<u8>, Vec<u8>>,
156 ) -> f32 {
157 let mut sum: f32 = 0.0;
158 let mut mask_count = 0;
159
160 assert_eq!(img.width(), mask.width());
161 assert_eq!(img.height(), mask.height());
162
163 for y in 0..img.height() {
164 for x in 0..img.width() {
165 let mask_value = mask.get_pixel(x, y)[0];
166 if mask_value > 0 {
167 let pixel = img.get_pixel(x, y);
168 sum += pixel[0];
169 mask_count += 1;
170 }
171 }
172 }
173
174 if mask_count == 0 {
175 return 0.0;
176 }
177
178 sum / mask_count as f32
179 }
180
181 pub fn polygon_centroid(points: &[Point]) -> (u32, u32) {
185 if points.is_empty() { return (0, 0); }
186 let n = points.len() as u32;
187 let sx: u32 = points.iter().map(|p| p.x).sum();
188 let sy: u32 = points.iter().map(|p| p.y).sum();
189 (sx / n, sy / n)
190 }
191
192 pub fn inverse_warp_quad(
210 line_polygon: &[Point; 4],
211 crop_size: (u32, u32),
212 quad_in_crop: &[(f32, f32); 4],
213 ) -> Option<[Point; 4]> {
214 let (min_x, min_y, _, _) = line_polygon.iter().fold(
217 (u32::MAX, u32::MAX, 0u32, 0u32),
218 |(mn_x, mn_y, mx_x, mx_y), p| (mn_x.min(p.x), mn_y.min(p.y), mx_x.max(p.x), mx_y.max(p.y)),
219 );
220 let src_points: [(f32, f32); 4] = [
221 ((line_polygon[0].x - min_x) as f32, (line_polygon[0].y - min_y) as f32),
222 ((line_polygon[1].x - min_x) as f32, (line_polygon[1].y - min_y) as f32),
223 ((line_polygon[2].x - min_x) as f32, (line_polygon[2].y - min_y) as f32),
224 ((line_polygon[3].x - min_x) as f32, (line_polygon[3].y - min_y) as f32),
225 ];
226 let (cw, ch) = (crop_size.0 as f32, crop_size.1 as f32);
227 let dst_points: [(f32, f32); 4] = [
228 (0.0, 0.0),
229 (cw, 0.0),
230 (cw, ch),
231 (0.0, ch),
232 ];
233
234 let proj = imageproc::geometric_transformations::Projection::from_control_points(
237 src_points, dst_points,
238 )?.invert();
239
240 let mut out = [Point { x: 0, y: 0 }; 4];
241 for (i, &(qx, qy)) in quad_in_crop.iter().enumerate() {
242 let (mapped_x, mapped_y) = proj * (qx, qy);
243 let abs_x = (mapped_x.max(0.0) as u32).saturating_add(min_x);
246 let abs_y = (mapped_y.max(0.0) as u32).saturating_add(min_y);
247 out[i] = Point { x: abs_x, y: abs_y };
248 }
249 Some(out)
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn inverse_warp_axis_aligned_roundtrip() {
259 let poly = [
261 Point { x: 50, y: 200 },
262 Point { x: 150, y: 200 },
263 Point { x: 150, y: 230 },
264 Point { x: 50, y: 230 },
265 ];
266 let crop_size = (200u32, 60u32); let word_quad = [(40.0, 0.0), (80.0, 0.0), (80.0, 60.0), (40.0, 60.0)];
270 let result = OcrUtils::inverse_warp_quad(&poly, crop_size, &word_quad).unwrap();
271
272 assert_eq!(result[0].x, 70);
274 assert_eq!(result[0].y, 200);
275 assert_eq!(result[1].x, 90);
276 assert_eq!(result[1].y, 200);
277 assert_eq!(result[2].x, 90);
278 assert_eq!(result[2].y, 230);
279 assert_eq!(result[3].x, 70);
280 assert_eq!(result[3].y, 230);
281 }
282
283 #[test]
284 fn inverse_warp_corners_match_polygon() {
285 let poly = [
287 Point { x: 100, y: 50 },
288 Point { x: 300, y: 60 },
289 Point { x: 295, y: 90 },
290 Point { x: 95, y: 80 },
291 ];
292 let crop_size = (200u32, 30u32);
293 let full_crop = [(0.0, 0.0), (200.0, 0.0), (200.0, 30.0), (0.0, 30.0)];
294 let result = OcrUtils::inverse_warp_quad(&poly, crop_size, &full_crop).unwrap();
295
296 for (i, p) in result.iter().enumerate() {
298 let dx = (p.x as i32 - poly[i].x as i32).abs();
299 let dy = (p.y as i32 - poly[i].y as i32).abs();
300 assert!(dx <= 2, "corner {i} x: got {} expected {} (Δ={})", p.x, poly[i].x, dx);
301 assert!(dy <= 2, "corner {i} y: got {} expected {} (Δ={})", p.y, poly[i].y, dy);
302 }
303 }
304}