#![allow(dead_code)]
#![allow(clippy::cast_precision_loss)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FlowVector {
pub dx: f32,
pub dy: f32,
}
impl FlowVector {
#[must_use]
pub fn new(dx: f32, dy: f32) -> Self {
Self { dx, dy }
}
#[must_use]
pub fn magnitude(&self) -> f32 {
(self.dx * self.dx + self.dy * self.dy).sqrt()
}
#[must_use]
pub fn angle_rad(&self) -> f32 {
self.dy.atan2(self.dx)
}
#[must_use]
pub fn is_zero(&self) -> bool {
self.dx == 0.0 && self.dy == 0.0
}
}
impl Default for FlowVector {
fn default() -> Self {
Self::new(0.0, 0.0)
}
}
#[derive(Debug, Clone)]
pub struct FlowField {
pub vectors: Vec<FlowVector>,
pub width: usize,
pub height: usize,
}
impl FlowField {
#[must_use]
pub fn new(width: usize, height: usize, block_size: usize) -> Self {
let cols = width.div_ceil(block_size.max(1));
let rows = height.div_ceil(block_size.max(1));
Self {
vectors: vec![FlowVector::default(); cols * rows],
width,
height,
}
}
#[must_use]
pub fn block_cols(&self, block_size: usize) -> usize {
self.width.div_ceil(block_size.max(1))
}
#[must_use]
pub fn block_rows(&self, block_size: usize) -> usize {
self.height.div_ceil(block_size.max(1))
}
#[must_use]
pub fn at(&self, x: usize, y: usize) -> &FlowVector {
let cols = self.width.div_ceil(1); &self.vectors[y * cols + x]
}
#[must_use]
pub fn average_magnitude(&self) -> f32 {
if self.vectors.is_empty() {
return 0.0;
}
let sum: f32 = self.vectors.iter().map(FlowVector::magnitude).sum();
sum / self.vectors.len() as f32
}
#[must_use]
pub fn dominant_direction(&self) -> FlowVector {
if self.vectors.is_empty() {
return FlowVector::default();
}
let n = self.vectors.len() as f32;
let sum_dx: f32 = self.vectors.iter().map(|v| v.dx).sum();
let sum_dy: f32 = self.vectors.iter().map(|v| v.dy).sum();
FlowVector::new(sum_dx / n, sum_dy / n)
}
}
#[must_use]
pub fn lucas_kanade_block(prev_block: &[f32], curr_block: &[f32], block_size: usize) -> FlowVector {
if prev_block.is_empty() || curr_block.is_empty() || block_size == 0 {
return FlowVector::default();
}
let n = (block_size * block_size) as f32;
let mut sum_ix2 = 0.0_f32;
let mut sum_iy2 = 0.0_f32;
let mut sum_ix_iy = 0.0_f32;
let mut sum_ix_it = 0.0_f32;
let mut sum_iy_it = 0.0_f32;
let len = prev_block.len().min(curr_block.len());
for i in 0..len {
let it = curr_block[i] - prev_block[i];
let ix = if i + 1 < len {
curr_block[i + 1] - curr_block[i]
} else {
0.0
};
let iy = if i + block_size < len {
curr_block[i + block_size] - curr_block[i]
} else {
0.0
};
sum_ix2 += ix * ix;
sum_iy2 += iy * iy;
sum_ix_iy += ix * iy;
sum_ix_it += ix * it;
sum_iy_it += iy * it;
}
let a = sum_ix2 / n;
let b = sum_ix_iy / n;
let c = sum_iy2 / n;
let d = -sum_ix_it / n;
let e = -sum_iy_it / n;
let det = a * c - b * b;
if det.abs() < 1e-8 {
return FlowVector::default();
}
let vx = (d * c - e * b) / det;
let vy = (a * e - b * d) / det;
FlowVector::new(vx, vy)
}
#[must_use]
pub fn compute_flow_field(
prev_frame: &[f32],
curr_frame: &[f32],
width: usize,
height: usize,
block_size: usize,
) -> FlowField {
let bs = block_size.max(1);
let block_cols = width.div_ceil(bs);
let block_rows = height.div_ceil(bs);
let mut vectors = Vec::with_capacity(block_cols * block_rows);
for by in 0..block_rows {
for bx in 0..block_cols {
let x0 = bx * bs;
let y0 = by * bs;
let mut prev_block = Vec::with_capacity(bs * bs);
let mut curr_block = Vec::with_capacity(bs * bs);
for row in 0..bs {
let y = y0 + row;
if y >= height {
break;
}
for col in 0..bs {
let x = x0 + col;
if x >= width {
break;
}
let idx = y * width + x;
if idx < prev_frame.len() {
prev_block.push(prev_frame[idx]);
}
if idx < curr_frame.len() {
curr_block.push(curr_frame[idx]);
}
}
}
let fv = lucas_kanade_block(&prev_block, &curr_block, bs);
vectors.push(fv);
}
}
FlowField {
vectors,
width,
height,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::PI;
#[test]
fn test_flow_vector_magnitude_zero() {
let v = FlowVector::new(0.0, 0.0);
assert_eq!(v.magnitude(), 0.0);
}
#[test]
fn test_flow_vector_magnitude_345() {
let v = FlowVector::new(3.0, 4.0);
assert!((v.magnitude() - 5.0).abs() < 1e-5);
}
#[test]
fn test_flow_vector_angle_right() {
let v = FlowVector::new(1.0, 0.0);
assert!(v.angle_rad().abs() < 1e-5);
}
#[test]
fn test_flow_vector_angle_up() {
let v = FlowVector::new(0.0, 1.0);
assert!((v.angle_rad() - PI / 2.0).abs() < 1e-5);
}
#[test]
fn test_flow_vector_is_zero_true() {
let v = FlowVector::new(0.0, 0.0);
assert!(v.is_zero());
}
#[test]
fn test_flow_vector_is_zero_false() {
let v = FlowVector::new(0.0, 0.001);
assert!(!v.is_zero());
}
#[test]
fn test_flow_field_average_magnitude_empty_vectors() {
let field = FlowField {
vectors: vec![FlowVector::default(); 4],
width: 8,
height: 8,
};
assert_eq!(field.average_magnitude(), 0.0);
}
#[test]
fn test_flow_field_average_magnitude_uniform() {
let v = FlowVector::new(3.0, 4.0); let field = FlowField {
vectors: vec![v; 4],
width: 8,
height: 8,
};
assert!((field.average_magnitude() - 5.0).abs() < 1e-5);
}
#[test]
fn test_flow_field_dominant_direction_uniform() {
let field = FlowField {
vectors: vec![FlowVector::new(2.0, -1.0); 6],
width: 12,
height: 8,
};
let d = field.dominant_direction();
assert!((d.dx - 2.0).abs() < 1e-5);
assert!((d.dy + 1.0).abs() < 1e-5);
}
#[test]
fn test_flow_field_dominant_direction_cancels() {
let field = FlowField {
vectors: vec![FlowVector::new(1.0, 0.0), FlowVector::new(-1.0, 0.0)],
width: 4,
height: 4,
};
let d = field.dominant_direction();
assert!(d.dx.abs() < 1e-5);
}
#[test]
fn test_lucas_kanade_block_empty_returns_zero() {
let fv = lucas_kanade_block(&[], &[], 8);
assert!(fv.is_zero());
}
#[test]
fn test_lucas_kanade_block_constant_frames_returns_zero() {
let prev: Vec<f32> = vec![128.0; 64];
let curr = prev.clone();
let fv = lucas_kanade_block(&prev, &curr, 8);
assert!(fv.is_zero());
}
#[test]
fn test_compute_flow_field_dimensions() {
let prev = vec![0.0_f32; 64 * 48];
let curr = prev.clone();
let field = compute_flow_field(&prev, &curr, 64, 48, 8);
assert_eq!(field.vectors.len(), 48);
}
#[test]
fn test_compute_flow_field_constant_frames() {
let frame = vec![100.0_f32; 16 * 16];
let field = compute_flow_field(&frame, &frame, 16, 16, 4);
for v in &field.vectors {
assert!(v.is_zero());
}
}
}