#[derive(Debug, Clone)]
pub struct FrameDiffSubtractor {
threshold: u8,
previous: Option<Vec<u8>>,
}
impl FrameDiffSubtractor {
#[must_use]
pub fn new(threshold: u8) -> Self {
Self {
threshold,
previous: None,
}
}
pub fn update(&mut self, frame: &[u8], w: u32, h: u32) -> Vec<bool> {
let n = (w as usize).saturating_mul(h as usize);
match &self.previous {
None => {
self.previous = Some(frame[..frame.len().min(n)].to_vec());
vec![false; n]
}
Some(prev) if prev.len() != n => {
self.previous = Some(frame[..frame.len().min(n)].to_vec());
vec![false; n]
}
Some(prev) => {
let len = n.min(frame.len()).min(prev.len());
let mut mask = vec![false; n];
for i in 0..len {
let diff = (frame[i] as i16 - prev[i] as i16).unsigned_abs() as u8;
mask[i] = diff > self.threshold;
}
self.previous = Some(frame[..frame.len().min(n)].to_vec());
mask
}
}
}
pub fn reset(&mut self) {
self.previous = None;
}
#[must_use]
pub fn threshold(&self) -> u8 {
self.threshold
}
#[must_use]
pub fn has_background(&self) -> bool {
self.previous.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_first_frame_all_background() {
let mut sub = FrameDiffSubtractor::new(10);
let frame = vec![100u8; 16];
let mask = sub.update(&frame, 4, 4);
assert_eq!(mask.len(), 16);
assert!(mask.iter().all(|&fg| !fg));
}
#[test]
fn test_identical_frames_all_background() {
let frame = vec![50u8; 9];
let mut sub = FrameDiffSubtractor::new(5);
sub.update(&frame, 3, 3);
let mask = sub.update(&frame, 3, 3);
assert!(mask.iter().all(|&fg| !fg));
}
#[test]
fn test_changed_pixel_becomes_foreground() {
let frame1 = vec![0u8; 4];
let mut frame2 = vec![0u8; 4];
frame2[2] = 100;
let mut sub = FrameDiffSubtractor::new(20);
sub.update(&frame1, 2, 2);
let mask = sub.update(&frame2, 2, 2);
assert!(!mask[0]);
assert!(!mask[1]);
assert!(mask[2]); assert!(!mask[3]);
}
#[test]
fn test_threshold_boundary() {
let frame1 = vec![0u8; 4];
let mut frame2 = vec![0u8; 4];
frame2[0] = 30;
let mut sub_below = FrameDiffSubtractor::new(30); sub_below.update(&frame1, 2, 2);
let mask_below = sub_below.update(&frame2, 2, 2);
assert!(!mask_below[0]);
let mut sub_above = FrameDiffSubtractor::new(29); sub_above.update(&frame1, 2, 2);
let mask_above = sub_above.update(&frame2, 2, 2);
assert!(mask_above[0]);
}
#[test]
fn test_dimension_change_reseeds() {
let frame_4x4 = vec![128u8; 16];
let frame_2x2 = vec![255u8; 4];
let mut sub = FrameDiffSubtractor::new(10);
sub.update(&frame_4x4, 4, 4);
let mask = sub.update(&frame_2x2, 2, 2);
assert_eq!(mask.len(), 4);
assert!(mask.iter().all(|&fg| !fg));
}
#[test]
fn test_reset_reseeds() {
let frame1 = vec![0u8; 4];
let frame2 = vec![255u8; 4];
let mut sub = FrameDiffSubtractor::new(10);
sub.update(&frame1, 2, 2);
sub.reset();
let mask = sub.update(&frame2, 2, 2);
assert!(mask.iter().all(|&fg| !fg));
}
#[test]
fn test_has_background() {
let mut sub = FrameDiffSubtractor::new(5);
assert!(!sub.has_background());
sub.update(&[0u8; 4], 2, 2);
assert!(sub.has_background());
sub.reset();
assert!(!sub.has_background());
}
}