block_motion_detector/
lib.rs

1//! Block subdivision motion detector.
2
3use ofps::prelude::v1::*;
4
5ofps::define_descriptor!(block_motion, Detector, |_| Ok(Box::new(
6    BlockMotionDetection::default()
7)));
8
9/// Block subdivision based motion detector.
10///
11/// This detector splits up each frame to blocks that are of area `min_size / (subdivide ^ 2)` and
12/// checks average motion within each block to be at least of `target_motion` magnitude.
13pub struct BlockMotionDetection {
14    pub min_size: f32,
15    pub subdivide: usize,
16    pub target_motion: f32,
17}
18
19impl Default for BlockMotionDetection {
20    fn default() -> Self {
21        Self {
22            min_size: 0.05,
23            subdivide: 3,
24            target_motion: 0.003,
25        }
26    }
27}
28
29impl Properties for BlockMotionDetection {
30    fn props_mut(&mut self) -> Vec<(&str, PropertyMut)> {
31        vec![
32            (
33                "Min size",
34                PropertyMut::float(&mut self.min_size, 0.01, 1.0),
35            ),
36            (
37                "Subdivisions",
38                PropertyMut::usize(&mut self.subdivide, 1, 16),
39            ),
40            (
41                "Target motion",
42                PropertyMut::float(&mut self.target_motion, 0.0001, 0.1),
43            ),
44        ]
45    }
46}
47
48impl Detector for BlockMotionDetection {
49    fn detect_motion(&self, motion: &[MotionEntry]) -> Option<(usize, MotionField)> {
50        let motion = motion.iter().copied();
51
52        // Calculate the motion field size, rounded up.
53        let block_width = self.min_size.sqrt() / self.subdivide as f32;
54        let block_dim = (1.0 / block_width).ceil() as usize;
55
56        // Add all the motion to the field densifier.
57        let mut mf = MotionFieldDensifier::new(block_dim, block_dim);
58        motion.for_each(|(pos, motion)| {
59            mf.add_vector(pos, motion);
60        });
61        let mf = MotionField::from(mf);
62
63        let mut map = vec![vec![false; block_dim]; block_dim];
64
65        // Compute which blocks have motion
66        mf.iter()
67            .filter(|(_, _, motion)| motion.magnitude() >= self.target_motion)
68            .for_each(|(x, y, _)| map[y][x] = true);
69
70        // Flood fill compute the area of the biggest motion.
71        let mut biggest_area = 0;
72        let mut biggest_area_mf = None;
73
74        for y in 0..block_dim {
75            for x in 0..block_dim {
76                if map[y][x] {
77                    let mut area = 0;
78                    let mut mf2 = MotionField::new(block_dim, block_dim);
79
80                    map[y][x] = false;
81                    let mut to_fill = vec![(x, y); 1];
82
83                    while let Some((x, y)) = to_fill.pop() {
84                        area += 1;
85
86                        let neighbor_offs = (-1..=1).flat_map(|x| (-1..=1).map(move |y| (x, y)));
87
88                        // Go through each neighbor and add any unvisited and over threshold
89                        // entries.
90                        for (x, y) in neighbor_offs
91                            .map(|(ox, oy)| (x as isize + ox, y as isize + oy))
92                            .filter(|&(ox, oy)| {
93                                (0..block_dim as isize).contains(&ox)
94                                    && (0..block_dim as isize).contains(&oy)
95                            })
96                            .map(|(x, y)| (x as usize, y as usize))
97                        {
98                            if map[y][x] {
99                                mf2.set_motion(x, y, mf.get_motion(x, y));
100                                to_fill.push((x, y));
101                                map[y][x] = false;
102                            }
103                        }
104                    }
105
106                    if area > biggest_area {
107                        biggest_area = area;
108                        biggest_area_mf = Some(mf2);
109                    }
110                }
111            }
112        }
113
114        if biggest_area as f32 / (block_dim * block_dim) as f32 >= self.min_size {
115            biggest_area_mf.map(|mf| (biggest_area, mf))
116        } else {
117            None
118        }
119    }
120}