1use crate::{
2 base_net::BaseNet,
3 ocr_error::OcrError,
4 ocr_result::{self, TextBox},
5 ocr_utils::OcrUtils,
6 scale_param::ScaleParam,
7};
8use geo_clipper::{Clipper, EndType, JoinType};
9use geo_types::{Coord, LineString, Polygon};
10use ort::{inputs, session::SessionOutputs};
11use ort::{session::Session, value::Tensor};
12use std::cmp::Ordering;
13
14const MEAN_VALUES: [f32; 3] = [
15 0.485_f32 * 255_f32,
16 0.456_f32 * 255_f32,
17 0.406_f32 * 255_f32,
18];
19const NORM_VALUES: [f32; 3] = [
20 1.0_f32 / 0.229_f32 / 255.0_f32,
21 1.0_f32 / 0.224_f32 / 255.0_f32,
22 1.0_f32 / 0.225_f32 / 255.0_f32,
23];
24
25#[derive(Debug)]
26pub struct DbNet {
27 session: Option<Session>,
28 input_names: Vec<String>,
29}
30
31impl BaseNet for DbNet {
32 fn new() -> Self {
33 Self {
34 session: None,
35 input_names: Vec::new(),
36 }
37 }
38
39 fn set_input_names(&mut self, input_names: Vec<String>) {
40 self.input_names = input_names;
41 }
42
43 fn set_session(&mut self, session: Option<Session>) {
44 self.session = session;
45 }
46}
47
48impl DbNet {
49 pub fn get_text_boxes(
50 &mut self,
51 img_src: &image::RgbImage,
52 scale: &ScaleParam,
53 box_score_thresh: f32,
54 box_thresh: f32,
55 un_clip_ratio: f32,
56 ) -> Result<Vec<TextBox>, OcrError> {
57 let Some(session) = &mut self.session else {
58 return Err(OcrError::SessionNotInitialized);
59 };
60
61 let src_resize = image::imageops::resize(
62 img_src,
63 scale.dst_width,
64 scale.dst_height,
65 image::imageops::FilterType::Triangle,
66 );
67
68 let input_tensors =
69 OcrUtils::substract_mean_normalize(&src_resize, &MEAN_VALUES, &NORM_VALUES);
70
71 let tensor = Tensor::from_array(input_tensors)?;
72
73 let outputs = session.run(inputs![self.input_names[0].clone() => tensor]?)?;
77
78 let text_boxes = Self::get_text_boxes_core(
79 &outputs,
80 src_resize.height(),
81 src_resize.width(),
82 &ScaleParam::new(
83 scale.src_width,
84 scale.src_height,
85 scale.dst_width,
86 scale.dst_height,
87 scale.scale_width,
88 scale.scale_height,
89 ),
90 box_score_thresh,
91 box_thresh,
92 un_clip_ratio,
93 )?;
94
95 Ok(text_boxes)
96 }
97
98 fn get_text_boxes_core(
99 output_tensor: &SessionOutputs,
100 rows: u32,
101 cols: u32,
102 s: &ScaleParam,
103 box_score_thresh: f32,
104 box_thresh: f32,
105 un_clip_ratio: f32,
106 ) -> Result<Vec<TextBox>, OcrError> {
107 let max_side_thresh = 3.0;
108 let mut rs_boxes = Vec::new();
109
110 let (_, red_data) = output_tensor.iter().next().unwrap();
111
112 let pred_data: Vec<f32> = crate::compat::tensor_to_vec_f32(&red_data)?;
115
116 let cbuf_data: Vec<u8> = pred_data
117 .iter()
118 .map(|pixel| (pixel * 255.0) as u8)
119 .collect();
120
121 let pred_img: image::ImageBuffer<image::Luma<f32>, Vec<f32>> =
122 image::ImageBuffer::from_vec(cols, rows, pred_data).unwrap();
123
124 let cbuf_img = image::GrayImage::from_vec(cols, rows, cbuf_data).unwrap();
125
126 let threshold_img = imageproc::contrast::threshold(
127 &cbuf_img,
128 (box_thresh * 255.0) as u8,
129 imageproc::contrast::ThresholdType::Binary,
130 );
131
132 let dilate_img = imageproc::morphology::dilate(
133 &threshold_img,
134 imageproc::distance_transform::Norm::LInf,
135 1,
136 );
137
138 let img_contours: Vec<imageproc::contours::Contour<i32>> =
139 imageproc::contours::find_contours(&dilate_img);
140
141 for contour in img_contours {
142 if contour.points.len() <= 2 {
143 continue;
144 }
145
146 let mut max_side = 0.0;
147 let min_box = Self::get_mini_box(&contour.points, &mut max_side)?;
148 if max_side < max_side_thresh {
149 continue;
150 }
151
152 let score = Self::get_score(&contour, &pred_img)?;
153 if score < box_score_thresh {
154 continue;
155 }
156
157 let clip_box = Self::unclip(&min_box, un_clip_ratio)?;
158 if clip_box.is_empty() {
159 continue;
160 }
161
162 let mut clip_contour = Vec::new();
163 for point in &clip_box {
164 clip_contour.push(*point);
165 }
166
167 let mut max_side_clip = 0.0;
168 let clip_min_box = Self::get_mini_box(&clip_contour, &mut max_side_clip)?;
169 if max_side_clip < max_side_thresh + 2.0 {
170 continue;
171 }
172
173 let mut final_points = Vec::new();
174 for item in clip_min_box {
175 let x = (item.x / s.scale_width) as u32;
176 let ptx = x.min(s.src_width);
177
178 let y = (item.y / s.scale_height) as u32;
179 let pty = y.min(s.src_height);
180
181 final_points.push(ocr_result::Point { x: ptx, y: pty });
182 }
183
184 let text_box = TextBox {
185 score,
186 points: final_points,
187 };
188
189 rs_boxes.push(text_box);
190 }
191
192 Ok(rs_boxes)
193 }
194
195 fn get_mini_box(
196 contour_points: &[imageproc::point::Point<i32>],
197 min_edge_size: &mut f32,
198 ) -> Result<Vec<imageproc::point::Point<f32>>, OcrError> {
199 let rect = imageproc::geometry::min_area_rect(contour_points);
200
201 let mut rect_points: Vec<imageproc::point::Point<f32>> = rect
202 .iter()
203 .map(|p| imageproc::point::Point::new(p.x as f32, p.y as f32))
204 .collect();
205
206 let width = ((rect_points[0].x - rect_points[1].x).powi(2)
207 + (rect_points[0].y - rect_points[1].y).powi(2))
208 .sqrt();
209 let height = ((rect_points[1].x - rect_points[2].x).powi(2)
210 + (rect_points[1].y - rect_points[2].y).powi(2))
211 .sqrt();
212
213 *min_edge_size = width.min(height);
214
215 rect_points.sort_by(|a, b| {
216 if a.x > b.x {
217 return Ordering::Greater;
218 }
219 if a.x == b.x {
220 return Ordering::Equal;
221 }
222 Ordering::Less
223 });
224
225 let mut box_points = Vec::new();
226 let index_1;
227 let index_4;
228 if rect_points[1].y > rect_points[0].y {
229 index_1 = 0;
230 index_4 = 1;
231 } else {
232 index_1 = 1;
233 index_4 = 0;
234 }
235
236 let index_2;
237 let index_3;
238 if rect_points[3].y > rect_points[2].y {
239 index_2 = 2;
240 index_3 = 3;
241 } else {
242 index_2 = 3;
243 index_3 = 2;
244 }
245
246 box_points.push(rect_points[index_1]);
247 box_points.push(rect_points[index_2]);
248 box_points.push(rect_points[index_3]);
249 box_points.push(rect_points[index_4]);
250
251 Ok(box_points)
252 }
253
254 fn get_score(
255 contour: &imageproc::contours::Contour<i32>,
256 f_map_mat: &image::ImageBuffer<image::Luma<f32>, Vec<f32>>,
257 ) -> Result<f32, OcrError> {
258 let mut xmin = i32::MAX;
260 let mut xmax = i32::MIN;
261 let mut ymin = i32::MAX;
262 let mut ymax = i32::MIN;
263
264 for point in contour.points.iter() {
266 let x = point.x;
267 let y = point.y;
268
269 if x < xmin {
270 xmin = x;
271 }
272 if x > xmax {
273 xmax = x;
274 }
275 if y < ymin {
276 ymin = y;
277 }
278 if y > ymax {
279 ymax = y;
280 }
281 }
282
283 let width = f_map_mat.width() as i32;
284 let height = f_map_mat.height() as i32;
285
286 xmin = xmin.max(0).min(width - 1);
287 xmax = xmax.max(0).min(width - 1);
288 ymin = ymin.max(0).min(height - 1);
289 ymax = ymax.max(0).min(height - 1);
290
291 let roi_width = xmax - xmin + 1;
292 let roi_height = ymax - ymin + 1;
293
294 if roi_width <= 0 || roi_height <= 0 {
295 return Ok(0.0);
296 }
297
298 let mut mask = image::GrayImage::new(roi_width as u32, roi_height as u32);
299
300 let mut pts = Vec::<imageproc::point::Point<i32>>::new();
301 for point in contour.points.iter() {
302 pts.push(imageproc::point::Point::new(point.x - xmin, point.y - ymin));
303 }
304
305 imageproc::drawing::draw_polygon_mut(&mut mask, pts.as_slice(), image::Luma([255]));
306
307 let cropped_img = image::imageops::crop_imm(
308 f_map_mat,
309 xmin as u32,
310 ymin as u32,
311 roi_width as u32,
312 roi_height as u32,
313 )
314 .to_image();
315
316 let mean = OcrUtils::calculate_mean_with_mask(&cropped_img, &mask);
317
318 Ok(mean)
319 }
320
321 fn unclip(
322 box_points: &[imageproc::point::Point<f32>],
323 unclip_ratio: f32,
324 ) -> Result<Vec<imageproc::point::Point<i32>>, OcrError> {
325 let points_arr = box_points.to_vec();
326
327 let clip_rect_width = ((points_arr[0].x - points_arr[1].x).powi(2)
328 + (points_arr[0].y - points_arr[1].y).powi(2))
329 .sqrt();
330 let clip_rect_height = ((points_arr[1].x - points_arr[2].x).powi(2)
331 + (points_arr[1].y - points_arr[2].y).powi(2))
332 .sqrt();
333
334 if clip_rect_height < 1.001 && clip_rect_width < 1.001 {
335 return Ok(Vec::new());
336 }
337
338 let mut the_cliper_pts = Vec::new();
339 for pt in box_points {
340 let a1 = Coord {
341 x: pt.x as f64,
342 y: pt.y as f64,
343 };
344 the_cliper_pts.push(a1);
345 }
346
347 let area = Self::signed_polygon_area(box_points).abs();
348 let length = Self::length_of_points(box_points);
349 let distance = area * unclip_ratio / length as f32;
350
351 let co = Polygon::new(LineString::new(the_cliper_pts), vec![]);
352 let solution = co
353 .offset(
354 distance as f64,
355 JoinType::Round(2.0),
356 EndType::ClosedPolygon,
357 1.0,
358 )
359 .0;
360
361 if solution.is_empty() {
362 return Ok(Vec::new());
363 }
364
365 let mut ret_pts = Vec::new();
366 for ip in solution.first().unwrap().exterior().points() {
367 ret_pts.push(imageproc::point::Point::new(ip.x() as i32, ip.y() as i32));
368 }
369
370 Ok(ret_pts)
371 }
372
373 fn signed_polygon_area(points: &[imageproc::point::Point<f32>]) -> f32 {
374 let num_points = points.len();
375 let mut pts = Vec::with_capacity(num_points + 1);
376 pts.extend_from_slice(points);
377 pts.push(points[0]);
378
379 let mut area = 0.0;
380 for i in 0..num_points {
381 area += (pts[i + 1].x - pts[i].x) * (pts[i + 1].y + pts[i].y) / 2.0;
382 }
383
384 area
385 }
386
387 fn length_of_points(box_points: &[imageproc::point::Point<f32>]) -> f64 {
388 if box_points.is_empty() {
389 return 0.0;
390 }
391
392 let mut length = 0.0;
393 let pt = box_points[0];
394 let mut x0 = pt.x as f64;
395 let mut y0 = pt.y as f64;
396
397 let mut box_with_first = Vec::from(box_points);
398 box_with_first.push(pt);
399
400 (1..box_with_first.len()).for_each(|idx| {
401 let pts = box_with_first[idx];
402 let x1 = pts.x as f64;
403 let y1 = pts.y as f64;
404 let dx = x1 - x0;
405 let dy = y1 - y0;
406
407 length += (dx * dx + dy * dy).sqrt();
408
409 x0 = x1;
410 y0 = y1;
411 });
412
413 length
414 }
415}