use crate::kernels::geometry::js_hypot;
use crate::kernels::pool::SyncPtr;
pub struct FieldMotion {
pub x: f64,
pub y: f64,
pub support: u32,
pub confidence: f64,
}
pub struct Field<'a> {
pub motions: &'a [FieldMotion],
pub labels: &'a [u8],
pub confidence: &'a [u8],
pub dynamic: &'a [u8],
pub cols: usize,
pub rows: usize,
pub difference: f64,
pub unknown: bool,
}
#[inline]
fn norm(m: &FieldMotion) -> f64 {
js_hypot(m.x, m.y)
}
pub const MOVING_COLUMN: f64 = 1.0;
pub fn informative(field: &Field<'_>) -> bool {
!field.unknown
&& field.difference >= 0.2
&& field
.motions
.iter()
.any(|m| norm(m) > 1.0 && m.confidence > 0.3)
}
pub struct Learner {
pub width: usize,
pub height: usize,
pub cell: usize,
pub cols: usize,
pub rows: usize,
pub split: Vec<f64>,
pub evidence: Vec<f64>,
pub activity: Vec<f64>,
pub observations: Vec<f64>,
pub row_fixed: Vec<f64>,
pub row_moving: Vec<f64>,
pub pixel_fixed: Vec<f64>,
pub pixel_moving: Vec<f64>,
pub informative_frames: u32,
pub row_change: Vec<f64>,
pub col_change: Vec<f64>,
pub col_moving: Vec<f64>,
pub col_mean: Vec<f64>,
pub col_gain: Vec<f64>,
pub horizontal_gain: Vec<f64>,
pub native_row_change: Vec<f64>,
pub native_col_change: Vec<f64>,
pub native_col_mean: Vec<f64>,
pub native_frames: u32,
pub native_width: usize,
pub native_height: usize,
}
impl Learner {
pub fn new(width: usize, height: usize) -> Learner {
let cell = 24;
let cols = width.div_ceil(cell);
let rows = height.div_ceil(cell);
let n = cols * rows;
Learner {
width,
height,
cell,
cols,
rows,
split: vec![0.0; n * 2],
evidence: vec![0.0; n * 2],
activity: vec![0.0; n],
observations: vec![0.0; n],
row_fixed: vec![0.0; height],
row_moving: vec![0.0; height],
pixel_fixed: vec![0.0; width * height],
pixel_moving: vec![0.0; width * height],
informative_frames: 0,
row_change: vec![0.0; height],
col_change: vec![0.0; width],
col_moving: vec![0.0; width],
col_mean: vec![0.0; width],
col_gain: vec![0.0; cols],
horizontal_gain: vec![0.0; rows],
native_row_change: Vec::new(),
native_col_change: Vec::new(),
native_col_mean: Vec::new(),
native_frames: 0,
native_width: 0,
native_height: 0,
}
}
pub fn add(
&mut self,
field: &Field<'_>,
prev: &[u8],
current: &[u8],
native: Option<(&[u8], &[u8], usize, usize)>,
) -> bool {
if !informative(field) {
return false;
}
self.informative_frames += 1;
if let Some((a, b, w, h)) = native {
self.add_native(a, b, w, h);
}
let (width, height, cols, rows) = (self.width, self.height, self.cols, self.rows);
for y in 0..height {
let (mut sum, mut n) = (0.0f64, 0u32);
let mut x = 0;
while x < width {
let i = y * width + x;
sum += (prev[i] as i32 - current[i] as i32).abs() as f64;
n += 1;
x += 3;
}
self.row_change[y] += sum / (n.max(1) as f64);
}
let (mut change, mut mean) = (vec![0.0f64; width], vec![0.0f64; width]);
let mut n = 0u32;
let mut y = 0;
while y < height {
let (p, c) = (
&prev[y * width..(y + 1) * width],
¤t[y * width..(y + 1) * width],
);
for x in 0..width {
change[x] += (p[x] as i32 - c[x] as i32).abs() as f64;
mean[x] += c[x] as f64;
}
n += 1;
y += 3;
}
for x in 0..width {
self.col_change[x] += change[x] / n as f64;
if change[x] / n as f64 > MOVING_COLUMN {
self.col_moving[x] += 1.0;
}
self.col_mean[x] += mean[x] / n as f64;
}
let models = field.motions.len();
let mut columns = vec![0.0f64; cols * models];
let mut lines = vec![0.0f64; rows * models];
let informative_frames = self.informative_frames as f64;
for y in 0..rows {
for x in 0..cols {
let i = y * cols + x;
let py = (height - 1).min(((y as f64 + 0.5) * self.cell as f64).floor() as usize);
let label = field.labels[i] as usize;
if field.confidence[i] <= 110
|| field.dynamic[i] != 0
|| norm(&field.motions[label]) <= 1.0
|| self.row_change[py] / informative_frames < 0.9
{
continue;
}
let w = field.confidence[i] as f64 / 255.0;
columns[x * models + label] += w;
lines[y * models + label] += w;
}
}
gain(&columns, models, &mut self.col_gain);
gain(&lines, models, &mut self.horizontal_gain);
for y in 0..rows {
for x in 0..cols {
let i = y * cols + x;
let m = &field.motions[field.labels[i] as usize];
let c = field.confidence[i] as f64 / 255.0;
if c > 0.25 && field.dynamic[i] == 0 {
self.activity[i] += norm(m) * c;
self.observations[i] += c;
}
let neighbours = [
if x + 1 < cols { Some(i + 1) } else { None },
if y + 1 < rows { Some(i + cols) } else { None },
];
for (k, j) in neighbours.iter().enumerate() {
let Some(j) = *j else {
continue;
};
if field.dynamic[i] != 0 || field.dynamic[j] != 0 {
continue;
}
let weight = c.min(field.confidence[j] as f64 / 255.0);
if weight < 0.25 {
continue;
}
let other = &field.motions[field.labels[j] as usize];
let disagreement = js_hypot(m.x - other.x, m.y - other.y);
self.evidence[i * 2 + k] += weight;
if disagreement > 1.8 {
self.split[i * 2 + k] += weight;
}
}
}
}
let mut moving: Option<&FieldMotion> = None;
for m in field.motions {
if norm(m) > 1.0 && m.support >= 4 && moving.is_none_or(|best| m.support > best.support)
{
moving = Some(m);
}
}
if let Some(moving) = moving {
let dx = crate::kernels::geometry::js_round(moving.x) as i64;
let dy = crate::kernels::geometry::js_round(moving.y) as i64;
let (w, h) = (width as i64, height as i64);
for y in 2..(h - 2).max(2) {
let mut x = 2i64;
while x < w - 2 {
if x + dx < 1 || x + dx >= w - 1 || y + dy < 1 || y + dy >= h - 1 {
x += 4;
continue;
}
let i = (y * w + x) as usize;
let stationary = (prev[i] as i32 - current[i] as i32).abs();
let motion =
(prev[((y + dy) * w + x + dx) as usize] as i32 - current[i] as i32).abs();
if stationary + 6 < motion {
self.row_fixed[y as usize] += 1.0;
self.pixel_fixed[i] += 1.0;
}
if motion + 6 < stationary {
self.row_moving[y as usize] += 1.0;
self.pixel_moving[i] += 1.0;
}
x += 4;
}
}
}
true
}
fn add_native(&mut self, a: &[u8], b: &[u8], w: usize, h: usize) {
if self.native_row_change.is_empty() {
self.native_row_change = vec![0.0; h];
self.native_col_change = vec![0.0; w];
self.native_col_mean = vec![0.0; w];
self.native_width = w;
self.native_height = h;
}
if w != self.native_width || h != self.native_height {
return;
}
self.native_frames += 1;
let step = (w / 480).max(1);
let vstep = (h / 300).max(1);
let sample = |i: usize| {
((a[i] as i32 - b[i] as i32).abs()
+ (a[i + 1] as i32 - b[i + 1] as i32).abs()
+ (a[i + 2] as i32 - b[i + 2] as i32).abs()) as f64
/ 3.0
};
let rows_out = SyncPtr(self.native_row_change.as_mut_ptr());
let chunks = crate::kernels::pool::chunks_for(h * w.div_ceil(step), 32 * 1024);
crate::kernels::pool::par_for(chunks, |c| {
for y in crate::kernels::pool::split(h, chunks, c)
..crate::kernels::pool::split(h, chunks, c + 1)
{
let (mut sum, mut n) = (0.0f64, 0u32);
let mut x = 0;
while x < w {
sum += sample((y * w + x) * 4);
n += 1;
x += step;
}
unsafe { *rows_out.get().add(y) += sum / (n.max(1) as f64) };
}
});
let samples = h.div_ceil(vstep);
let (mut change, mut mean) = (vec![0.0f64; w], vec![0.0f64; w]);
let (change_out, mean_out) = (SyncPtr(change.as_mut_ptr()), SyncPtr(mean.as_mut_ptr()));
let chunks = crate::kernels::pool::chunks_for(w * samples, 32 * 1024);
crate::kernels::pool::par_for(chunks, |c| {
let (x0, x1) = (
crate::kernels::pool::split(w, chunks, c),
crate::kernels::pool::split(w, chunks, c + 1),
);
let (change, mean) = unsafe {
(
std::slice::from_raw_parts_mut(change_out.get().add(x0), x1 - x0),
std::slice::from_raw_parts_mut(mean_out.get().add(x0), x1 - x0),
)
};
let mut y = 0;
while y < h {
for x in x0..x1 {
let i = (y * w + x) * 4;
change[x - x0] += sample(i);
mean[x - x0] += (b[i] as f64 + b[i + 1] as f64 + b[i + 2] as f64) / 3.0;
}
y += vstep;
}
});
let n = samples.max(1) as f64;
for x in 0..w {
self.native_col_change[x] += change[x] / n;
self.native_col_mean[x] += mean[x] / n;
}
}
}
fn gain(bins: &[f64], models: usize, output: &mut [f64]) {
let count = output.len();
let mut total = vec![0.0f64; models];
let mut left = vec![0.0f64; models];
let mut weight = 0.0f64;
for k in 0..count {
for m in 0..models {
total[m] += bins[k * models + m];
weight += bins[k * models + m];
}
}
if weight < 10.0 {
return;
}
let all = total.iter().copied().fold(f64::NEG_INFINITY, f64::max);
for k in 1..count {
let (mut a, mut b) = (0.0f64, 0.0f64);
for m in 0..models {
left[m] += bins[(k - 1) * models + m];
a = a.max(left[m]);
b = b.max(total[m] - left[m]);
}
output[k] += (a + b - all) / weight;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uninformative_fields_leave_every_accumulator_untouched() {
let mut learner = Learner::new(48, 24);
let motions = [FieldMotion {
x: 0.0,
y: 0.0,
support: 10,
confidence: 1.0,
}];
let cells = learner.cols * learner.rows;
let field = Field {
motions: &motions,
labels: &vec![0u8; cells],
confidence: &vec![255u8; cells],
dynamic: &vec![0u8; cells],
cols: learner.cols,
rows: learner.rows,
difference: 1.0,
unknown: false,
};
let gray = vec![7u8; 48 * 24];
assert!(!learner.add(&field, &gray, &gray, None));
assert_eq!(learner.informative_frames, 0);
assert!(learner.row_change.iter().all(|&v| v == 0.0));
}
}