1use super::contour::{self, Bitmap};
6use super::geometry::{self, P};
7use crate::Quad;
8
9#[derive(Debug, Clone)]
11pub struct DbParams {
12 pub thresh: f32,
14 pub box_thresh: f32,
16 pub max_candidates: usize,
18 pub min_size: f64,
20 pub unclip_ratio: f64,
22}
23
24impl Default for DbParams {
25 fn default() -> Self {
26 DbParams {
29 thresh: 0.3,
30 box_thresh: 0.6,
31 max_candidates: 1000,
32 min_size: 3.0,
33 unclip_ratio: 1.5,
34 }
35 }
36}
37
38pub struct ProbMap<'a> {
40 pub data: &'a [f32],
41 pub width: usize,
42 pub height: usize,
43}
44
45pub fn box_score_fast(prob: &ProbMap, quad: &[P; 4]) -> f32 {
48 let (w, h) = (prob.width as i64, prob.height as i64);
49 let xs = quad.iter().map(|p| p.0);
50 let ys = quad.iter().map(|p| p.1);
51 let clamp = |v: f64, hi: i64| (v as i64).clamp(0, hi - 1);
52 let xmin = clamp(xs.clone().fold(f64::MAX, f64::min).floor(), w);
53 let xmax = clamp(xs.fold(f64::MIN, f64::max).ceil(), w);
54 let ymin = clamp(ys.clone().fold(f64::MAX, f64::min).floor(), h);
55 let ymax = clamp(ys.fold(f64::MIN, f64::max).ceil(), h);
56 let poly: Vec<(i32, i32)> = quad
58 .iter()
59 .map(|p| ((p.0 - xmin as f64) as i32, (p.1 - ymin as f64) as i32))
60 .collect();
61 let bw = (xmax - xmin + 1) as usize;
62 let bh = (ymax - ymin + 1) as usize;
63 let mut sum = 0f64;
64 let mut n = 0usize;
65 for (y, x0, x1) in geometry::fill_convex_rows(&poly, bw, bh) {
66 let row = (ymin as usize + y) * prob.width + xmin as usize;
67 sum += prob.data[row + x0..=row + x1]
68 .iter()
69 .map(|&v| v as f64)
70 .sum::<f64>();
71 n += x1 - x0 + 1;
72 }
73 if n == 0 { 0.0 } else { (sum / n as f64) as f32 }
75}
76
77pub fn boxes_from_prob(
79 prob: &ProbMap,
80 dest_w: usize,
81 dest_h: usize,
82 p: &DbParams,
83) -> Vec<(Quad, f32)> {
84 let bits: Vec<bool> = prob.data.iter().map(|&v| v > p.thresh).collect();
85 let contours = contour::outer_contours(&Bitmap {
86 data: &bits,
87 width: prob.width,
88 height: prob.height,
89 });
90 let sx = dest_w as f64 / prob.width as f64;
91 let sy = dest_h as f64 / prob.height as f64;
92 let mut out = Vec::new();
93 for c in contours.iter().take(p.max_candidates) {
94 let pts: Vec<P> = c.iter().map(|&(x, y)| (x as f64, y as f64)).collect();
95 let (quad, short) = geometry::mini_box(&pts);
96 if short < p.min_size {
97 continue;
98 }
99 let score = box_score_fast(prob, &quad);
100 if score < p.box_thresh {
101 continue;
102 }
103 let Some(expanded) = geometry::unclip(&quad, p.unclip_ratio) else {
104 continue;
105 };
106 let (quad, short) = geometry::mini_box(&expanded);
107 if short < p.min_size + 2.0 {
109 continue;
110 }
111 let map = |(x, y): P| {
113 (
114 (x * sx).round_ties_even().clamp(0.0, dest_w as f64) as f32,
115 (y * sy).round_ties_even().clamp(0.0, dest_h as f64) as f32,
116 )
117 };
118 out.push((quad.map(map), score));
119 }
120 out
121}
122
123pub fn sort_reading_order(boxes: &mut [(Quad, f32)], line_tol: f32) {
126 boxes.sort_by(|a, b| {
127 let (pa, pb) = (a.0[0], b.0[0]);
128 pa.1.total_cmp(&pb.1).then(pa.0.total_cmp(&pb.0))
129 });
130 for i in 0..boxes.len().saturating_sub(1) {
131 for j in (0..=i).rev() {
132 let (a, b) = (boxes[j].0[0], boxes[j + 1].0[0]);
133 if (b.1 - a.1).abs() < line_tol && b.0 < a.0 {
134 boxes.swap(j, j + 1);
135 } else {
136 break;
137 }
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn 框内平均分() {
148 let mut data = vec![0f32; 16];
150 for (x, y) in [(0, 0), (1, 0), (0, 1), (1, 1)] {
151 data[y * 4 + x] = 1.0;
152 }
153 let pm = ProbMap {
154 data: &data,
155 width: 4,
156 height: 4,
157 };
158 let q = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)];
159 assert!((box_score_fast(&pm, &q) - 1.0).abs() < 1e-6);
160 let q = [(0.0, 0.0), (3.0, 0.0), (3.0, 1.0), (0.0, 1.0)];
161 assert!((box_score_fast(&pm, &q) - 0.5).abs() < 1e-6);
162 }
163
164 #[test]
165 fn 一个矩形块变成外扩后的框() {
166 let (w, h) = (40, 20);
168 let mut data = vec![0f32; w * h];
169 for y in 6..=11 {
170 for x in 5..=24 {
171 data[y * w + x] = 0.9;
172 }
173 }
174 let pm = ProbMap {
175 data: &data,
176 width: w,
177 height: h,
178 };
179 let r = boxes_from_prob(&pm, 2 * w, 2 * h, &DbParams::default());
181 assert_eq!(r.len(), 1);
182 let (q, s) = r[0];
183 assert!((s - 0.9).abs() < 1e-6);
184 let d = 95.0 * 1.5 / 48.0;
186 let want = [
187 (5.0 - d, 6.0 - d),
188 (24.0 + d, 6.0 - d),
189 (24.0 + d, 11.0 + d),
190 (5.0 - d, 11.0 + d),
191 ];
192 for (got, (x, y)) in q.iter().zip(want) {
193 let (ex, ey) = (
194 (2.0f64 * x).round_ties_even(),
195 (2.0f64 * y).round_ties_even(),
196 );
197 assert_eq!((got.0 as f64, got.1 as f64), (ex, ey), "{q:?}");
198 }
199 }
200
201 #[test]
202 fn 太小或分低的块不要() {
203 let (w, h) = (20, 10);
204 let mut data = vec![0f32; w * h];
205 for x in 2..15 {
207 data[2 * w + x] = 0.9;
208 data[3 * w + x] = 0.9;
209 }
210 for y in 5..10 {
212 for x in 2..15 {
213 data[y * w + x] = 0.4;
214 }
215 }
216 let pm = ProbMap {
217 data: &data,
218 width: w,
219 height: h,
220 };
221 assert!(boxes_from_prob(&pm, w, h, &DbParams::default()).is_empty());
222 }
223
224 #[test]
225 fn 阅读顺序() {
226 let b = |x: f32, y: f32| -> (Quad, f32) {
227 (
228 [(x, y), (x + 5.0, y), (x + 5.0, y + 3.0), (x, y + 3.0)],
229 1.0,
230 )
231 };
232 let mut v = vec![b(50.0, 100.0), b(10.0, 104.0), b(10.0, 10.0), b(60.0, 8.0)];
233 sort_reading_order(&mut v, 10.0);
234 let xs: Vec<(f32, f32)> = v.iter().map(|q| q.0[0]).collect();
235 assert_eq!(
236 xs,
237 vec![(10.0, 10.0), (60.0, 8.0), (10.0, 104.0), (50.0, 100.0)]
238 );
239 }
240}