use super::contour::{self, Bitmap};
use super::geometry::{self, P};
use crate::Quad;
#[derive(Debug, Clone)]
pub struct DbParams {
pub thresh: f32,
pub box_thresh: f32,
pub max_candidates: usize,
pub min_size: f64,
pub unclip_ratio: f64,
}
impl Default for DbParams {
fn default() -> Self {
DbParams {
thresh: 0.3,
box_thresh: 0.6,
max_candidates: 1000,
min_size: 3.0,
unclip_ratio: 1.5,
}
}
}
pub struct ProbMap<'a> {
pub data: &'a [f32],
pub width: usize,
pub height: usize,
}
pub fn box_score_fast(prob: &ProbMap, quad: &[P; 4]) -> f32 {
let (w, h) = (prob.width as i64, prob.height as i64);
let xs = quad.iter().map(|p| p.0);
let ys = quad.iter().map(|p| p.1);
let clamp = |v: f64, hi: i64| (v as i64).clamp(0, hi - 1);
let xmin = clamp(xs.clone().fold(f64::MAX, f64::min).floor(), w);
let xmax = clamp(xs.fold(f64::MIN, f64::max).ceil(), w);
let ymin = clamp(ys.clone().fold(f64::MAX, f64::min).floor(), h);
let ymax = clamp(ys.fold(f64::MIN, f64::max).ceil(), h);
let poly: Vec<(i32, i32)> = quad
.iter()
.map(|p| ((p.0 - xmin as f64) as i32, (p.1 - ymin as f64) as i32))
.collect();
let bw = (xmax - xmin + 1) as usize;
let bh = (ymax - ymin + 1) as usize;
let mut sum = 0f64;
let mut n = 0usize;
for (y, x0, x1) in geometry::fill_convex_rows(&poly, bw, bh) {
let row = (ymin as usize + y) * prob.width + xmin as usize;
sum += prob.data[row + x0..=row + x1]
.iter()
.map(|&v| v as f64)
.sum::<f64>();
n += x1 - x0 + 1;
}
if n == 0 { 0.0 } else { (sum / n as f64) as f32 }
}
pub fn boxes_from_prob(
prob: &ProbMap,
dest_w: usize,
dest_h: usize,
p: &DbParams,
) -> Vec<(Quad, f32)> {
let bits: Vec<bool> = prob.data.iter().map(|&v| v > p.thresh).collect();
let contours = contour::outer_contours(&Bitmap {
data: &bits,
width: prob.width,
height: prob.height,
});
let sx = dest_w as f64 / prob.width as f64;
let sy = dest_h as f64 / prob.height as f64;
let mut out = Vec::new();
for c in contours.iter().take(p.max_candidates) {
let pts: Vec<P> = c.iter().map(|&(x, y)| (x as f64, y as f64)).collect();
let (quad, short) = geometry::mini_box(&pts);
if short < p.min_size {
continue;
}
let score = box_score_fast(prob, &quad);
if score < p.box_thresh {
continue;
}
let Some(expanded) = geometry::unclip(&quad, p.unclip_ratio) else {
continue;
};
let (quad, short) = geometry::mini_box(&expanded);
if short < p.min_size + 2.0 {
continue;
}
let map = |(x, y): P| {
(
(x * sx).round_ties_even().clamp(0.0, dest_w as f64) as f32,
(y * sy).round_ties_even().clamp(0.0, dest_h as f64) as f32,
)
};
out.push((quad.map(map), score));
}
out
}
pub fn sort_reading_order(boxes: &mut [(Quad, f32)], line_tol: f32) {
boxes.sort_by(|a, b| {
let (pa, pb) = (a.0[0], b.0[0]);
pa.1.total_cmp(&pb.1).then(pa.0.total_cmp(&pb.0))
});
for i in 0..boxes.len().saturating_sub(1) {
for j in (0..=i).rev() {
let (a, b) = (boxes[j].0[0], boxes[j + 1].0[0]);
if (b.1 - a.1).abs() < line_tol && b.0 < a.0 {
boxes.swap(j, j + 1);
} else {
break;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn 框内平均分() {
let mut data = vec![0f32; 16];
for (x, y) in [(0, 0), (1, 0), (0, 1), (1, 1)] {
data[y * 4 + x] = 1.0;
}
let pm = ProbMap {
data: &data,
width: 4,
height: 4,
};
let q = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)];
assert!((box_score_fast(&pm, &q) - 1.0).abs() < 1e-6);
let q = [(0.0, 0.0), (3.0, 0.0), (3.0, 1.0), (0.0, 1.0)];
assert!((box_score_fast(&pm, &q) - 0.5).abs() < 1e-6);
}
#[test]
fn 一个矩形块变成外扩后的框() {
let (w, h) = (40, 20);
let mut data = vec![0f32; w * h];
for y in 6..=11 {
for x in 5..=24 {
data[y * w + x] = 0.9;
}
}
let pm = ProbMap {
data: &data,
width: w,
height: h,
};
let r = boxes_from_prob(&pm, 2 * w, 2 * h, &DbParams::default());
assert_eq!(r.len(), 1);
let (q, s) = r[0];
assert!((s - 0.9).abs() < 1e-6);
let d = 95.0 * 1.5 / 48.0;
let want = [
(5.0 - d, 6.0 - d),
(24.0 + d, 6.0 - d),
(24.0 + d, 11.0 + d),
(5.0 - d, 11.0 + d),
];
for (got, (x, y)) in q.iter().zip(want) {
let (ex, ey) = (
(2.0f64 * x).round_ties_even(),
(2.0f64 * y).round_ties_even(),
);
assert_eq!((got.0 as f64, got.1 as f64), (ex, ey), "{q:?}");
}
}
#[test]
fn 太小或分低的块不要() {
let (w, h) = (20, 10);
let mut data = vec![0f32; w * h];
for x in 2..15 {
data[2 * w + x] = 0.9;
data[3 * w + x] = 0.9;
}
for y in 5..10 {
for x in 2..15 {
data[y * w + x] = 0.4;
}
}
let pm = ProbMap {
data: &data,
width: w,
height: h,
};
assert!(boxes_from_prob(&pm, w, h, &DbParams::default()).is_empty());
}
#[test]
fn 阅读顺序() {
let b = |x: f32, y: f32| -> (Quad, f32) {
(
[(x, y), (x + 5.0, y), (x + 5.0, y + 3.0), (x, y + 3.0)],
1.0,
)
};
let mut v = vec![b(50.0, 100.0), b(10.0, 104.0), b(10.0, 10.0), b(60.0, 8.0)];
sort_reading_order(&mut v, 10.0);
let xs: Vec<(f32, f32)> = v.iter().map(|q| q.0[0]).collect();
assert_eq!(
xs,
vec![(10.0, 10.0), (60.0, 8.0), (10.0, 104.0), (50.0, 100.0)]
);
}
}