pico_detect/detect/
multiscale.rs

1use imageproc::rect::Rect;
2use thiserror::Error;
3
4use crate::geometry::Square;
5
6/// Multiscale object detection parameters.
7#[derive(Copy, Clone, Debug, PartialEq)]
8pub struct Multiscaler {
9    min_size: u32,
10    max_size: u32,
11    shift_factor: f32,
12    scale_factor: f32,
13}
14
15#[derive(Debug, Error)]
16pub enum MultiscalerError {
17    #[error("`min_size` should be non zero")]
18    MinSizeIsZero,
19    #[error("`max_size` should be greater than `min_size`")]
20    MaxSizeLessThanMinSize,
21    #[error("`shift_factor` should be in `(0, 1]` range")]
22    ShiftFactorOutOfRange,
23    #[error("`scale_factor` should be greater than 1")]
24    ScaleFactorLessThanOne,
25}
26
27impl Multiscaler {
28    /// Create a new multiscale detector with specified parameters.
29    ///
30    /// ### Arguments
31    ///
32    /// * `min_size` -- minimum size of the square region to detect;
33    /// * `max_size` -- maximum size of the square region to detect;
34    /// * `shift_factor` -- factor to shift the detection window;
35    /// * `scale_factor` -- factor to scale the detection window.
36    #[inline]
37    pub fn new(
38        min_size: u32,
39        max_size: u32,
40        shift_factor: f32,
41        scale_factor: f32,
42    ) -> Result<Self, MultiscalerError> {
43        if min_size == 0 {
44            return Err(MultiscalerError::MinSizeIsZero);
45        }
46
47        if min_size > max_size {
48            return Err(MultiscalerError::MaxSizeLessThanMinSize);
49        }
50
51        if !(0.0..=1.0).contains(&shift_factor) {
52            return Err(MultiscalerError::ShiftFactorOutOfRange);
53        }
54
55        if scale_factor < 1.0 {
56            return Err(MultiscalerError::ScaleFactorLessThanOne);
57        }
58
59        Ok(Self {
60            min_size,
61            max_size,
62            shift_factor,
63            scale_factor,
64        })
65    }
66
67    /// Returns the minimum size of the square region to detect.
68    pub fn min_size(&self) -> u32 {
69        self.min_size
70    }
71
72    /// Returns the maximum size of the square region to detect.
73    pub fn max_size(&self) -> u32 {
74        self.max_size
75    }
76
77    /// Returns the factor to shift the detection window.
78    pub fn shift_factor(&self) -> f32 {
79        self.shift_factor
80    }
81
82    /// Returns the factor to scale the detection window.
83    pub fn scale_factor(&self) -> f32 {
84        self.scale_factor
85    }
86
87    /// Run multiscale detection on the specified rectangle.
88    ///
89    /// ### Arguments
90    ///
91    /// * `rect` -- rectangle to run detection on;
92    /// * `f` -- function to call for each generated square region.
93    #[inline]
94    pub fn run<F>(&self, rect: Rect, f: F)
95    where
96        F: FnMut(Square),
97    {
98        multiscale(
99            self.min_size,
100            self.max_size,
101            self.shift_factor,
102            self.scale_factor,
103            rect,
104            f,
105        )
106    }
107
108    /// Count the number of square regions that would be generated
109    /// by running multiscale detection on the specified rectangle.
110    #[inline]
111    pub fn count(&self, rect: Rect) -> usize {
112        let mut count = 0;
113        self.run(rect, |_| count += 1);
114        count
115    }
116
117    /// Collect all square regions generated by running multiscale detection
118    /// on the specified rectangle.
119    #[inline]
120    pub fn collect(&self, rect: Rect) -> Vec<Square> {
121        let mut result = Vec::with_capacity(self.count(rect));
122        self.run(rect, |s| result.push(s));
123        result
124    }
125}
126
127/// Run multiscale detection on the specified rectangle.
128///
129/// ### Arguments
130///
131/// * `min_size` -- minimum size of the square region to detect;
132/// * `max_size` -- maximum size of the square region to detect;
133/// * `shift_factor` -- factor to shift the detection window;
134/// * `scale_factor` -- factor to scale the detection window;
135/// * `rect` -- boundary rectangle to run detection on;
136/// * `f` -- function to call for each generated square region.
137#[inline]
138pub fn multiscale<F>(
139    min_size: u32,
140    max_size: u32,
141    shift_factor: f32,
142    scale_factor: f32,
143    rect: Rect,
144    mut f: F,
145) where
146    F: FnMut(Square),
147{
148    let mut size = min_size;
149
150    let start_x = rect.left();
151    let start_y = rect.top();
152
153    let right = start_x + rect.width() as i32;
154    let bottom = start_y + rect.height() as i32;
155
156    while size <= max_size {
157        let sizef = size as f32;
158        let step: usize = 1.max((sizef * shift_factor) as usize);
159
160        let end_x = right - size as i32;
161        let end_y = bottom - size as i32;
162
163        for y in (start_y..=end_y).step_by(step) {
164            for x in (start_x..=end_x).step_by(step) {
165                f(Square::new(x, y, size))
166            }
167        }
168        size = (sizef * scale_factor) as u32;
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn test_multiscale_run() {
178        let ms = Multiscaler::new(1, 4, 1.0, 2.0).unwrap();
179        ms.run(Rect::at(0, 0).of_size(4, 4), |s| println!("{:?}", s));
180    }
181}