1#![allow(clippy::cast_precision_loss)]
25
26use crate::{AlignError, AlignResult};
27
28#[derive(Debug, Clone)]
30pub struct FarnebackConfig {
31 pub pyramid_levels: usize,
33 pub poly_n: usize,
35 pub poly_sigma: f64,
37 pub iterations: usize,
39 pub win_size: usize,
41}
42
43impl Default for FarnebackConfig {
44 fn default() -> Self {
45 Self {
46 pyramid_levels: 3,
47 poly_n: 3,
48 poly_sigma: 1.2,
49 iterations: 5,
50 win_size: 5,
51 }
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct DenseFlowField {
58 pub dx: Vec<f32>,
60 pub dy: Vec<f32>,
62 pub width: usize,
64 pub height: usize,
66}
67
68impl DenseFlowField {
69 #[must_use]
71 pub fn zeros(width: usize, height: usize) -> Self {
72 let n = width * height;
73 Self {
74 dx: vec![0.0; n],
75 dy: vec![0.0; n],
76 width,
77 height,
78 }
79 }
80
81 #[must_use]
83 pub fn at(&self, x: usize, y: usize) -> Option<(f32, f32)> {
84 if x < self.width && y < self.height {
85 let idx = y * self.width + x;
86 Some((self.dx[idx], self.dy[idx]))
87 } else {
88 None
89 }
90 }
91
92 #[must_use]
94 pub fn avg_magnitude(&self) -> f32 {
95 let n = self.dx.len();
96 if n == 0 {
97 return 0.0;
98 }
99 let sum: f64 = self
100 .dx
101 .iter()
102 .zip(self.dy.iter())
103 .map(|(&dx, &dy)| f64::from((dx * dx + dy * dy).sqrt()))
104 .sum();
105 (sum / n as f64) as f32
106 }
107
108 #[must_use]
110 pub fn max_magnitude(&self) -> f32 {
111 self.dx
112 .iter()
113 .zip(self.dy.iter())
114 .map(|(&dx, &dy)| (dx * dx + dy * dy).sqrt())
115 .fold(0.0_f32, f32::max)
116 }
117
118 #[must_use]
120 pub fn upsample_2x(&self) -> Self {
121 let nw = self.width * 2;
122 let nh = self.height * 2;
123 let mut out = DenseFlowField::zeros(nw, nh);
124
125 for y in 0..nh {
126 for x in 0..nw {
127 let sx = x as f32 / 2.0;
128 let sy = y as f32 / 2.0;
129
130 let x0 = (sx.floor() as usize).min(self.width.saturating_sub(1));
131 let y0 = (sy.floor() as usize).min(self.height.saturating_sub(1));
132 let x1 = (x0 + 1).min(self.width.saturating_sub(1));
133 let y1 = (y0 + 1).min(self.height.saturating_sub(1));
134
135 let fx = sx - x0 as f32;
136 let fy = sy - y0 as f32;
137
138 let idx = y * nw + x;
139
140 let d00 = self.dx[y0 * self.width + x0];
142 let d10 = self.dx[y0 * self.width + x1];
143 let d01 = self.dx[y1 * self.width + x0];
144 let d11 = self.dx[y1 * self.width + x1];
145 out.dx[idx] = (d00 * (1.0 - fx) * (1.0 - fy)
146 + d10 * fx * (1.0 - fy)
147 + d01 * (1.0 - fx) * fy
148 + d11 * fx * fy)
149 * 2.0; let d00 = self.dy[y0 * self.width + x0];
152 let d10 = self.dy[y0 * self.width + x1];
153 let d01 = self.dy[y1 * self.width + x0];
154 let d11 = self.dy[y1 * self.width + x1];
155 out.dy[idx] = (d00 * (1.0 - fx) * (1.0 - fy)
156 + d10 * fx * (1.0 - fy)
157 + d01 * (1.0 - fx) * fy
158 + d11 * fx * fy)
159 * 2.0;
160 }
161 }
162
163 out
164 }
165}
166
167pub fn compute_farneback_flow(
176 prev: &[u8],
177 curr: &[u8],
178 width: usize,
179 height: usize,
180 config: &FarnebackConfig,
181) -> AlignResult<DenseFlowField> {
182 if prev.len() != width * height || curr.len() != width * height {
183 return Err(AlignError::InvalidConfig(
184 "Image size does not match width*height".to_string(),
185 ));
186 }
187 if width < 8 || height < 8 {
188 return Err(AlignError::InvalidConfig(
189 "Image must be at least 8x8".to_string(),
190 ));
191 }
192
193 let prev_pyr = build_f32_pyramid(prev, width, height, config.pyramid_levels);
195 let curr_pyr = build_f32_pyramid(curr, width, height, config.pyramid_levels);
196
197 let coarsest = prev_pyr.len() - 1;
199 let mut flow = DenseFlowField::zeros(prev_pyr[coarsest].1, prev_pyr[coarsest].2);
200
201 for level in (0..prev_pyr.len()).rev() {
203 let (ref prev_img, pw, ph) = prev_pyr[level];
204 let (ref curr_img, _, _) = curr_pyr[level];
205
206 if level < coarsest {
208 flow = flow.upsample_2x();
209 flow = trim_flow(&flow, pw, ph);
211 }
212
213 let poly_prev = polynomial_expansion(prev_img, pw, ph, config.poly_n, config.poly_sigma);
215 let poly_curr = polynomial_expansion(curr_img, pw, ph, config.poly_n, config.poly_sigma);
216
217 for _iter in 0..config.iterations {
219 flow = update_flow(&poly_prev, &poly_curr, &flow, pw, ph, config.win_size);
220 }
221 }
222
223 Ok(flow)
224}
225
226#[derive(Debug, Clone, Copy, Default)]
230struct PolyCoeff {
231 a11: f32, a22: f32, a12: f32, b1: f32, b2: f32, }
237
238fn polynomial_expansion(
240 image: &[f32],
241 width: usize,
242 height: usize,
243 poly_n: usize,
244 poly_sigma: f64,
245) -> Vec<PolyCoeff> {
246 let n = width * height;
247 let mut coeffs = vec![PolyCoeff::default(); n];
248
249 let ksize = 2 * poly_n + 1;
251 let mut kernel = vec![0.0_f64; ksize * ksize];
252 let sigma2 = poly_sigma * poly_sigma;
253 let pn = poly_n as isize;
254
255 for ky in 0..ksize {
256 for kx in 0..ksize {
257 let dx = kx as f64 - poly_n as f64;
258 let dy = ky as f64 - poly_n as f64;
259 kernel[ky * ksize + kx] = (-0.5 * (dx * dx + dy * dy) / sigma2).exp();
260 }
261 }
262
263 for y in poly_n..height.saturating_sub(poly_n) {
265 for x in poly_n..width.saturating_sub(poly_n) {
266 let idx = y * width + x;
267
268 let mut s_xx_xx = 0.0_f64;
271 let mut s_yy_yy = 0.0_f64;
272 let mut s_xy_xy = 0.0_f64;
273 let mut s_x_x = 0.0_f64;
274 let mut s_y_y = 0.0_f64;
275 let mut s_xx_f = 0.0_f64;
276 let mut s_yy_f = 0.0_f64;
277 let mut s_xy_f = 0.0_f64;
278 let mut s_x_f = 0.0_f64;
279 let mut s_y_f = 0.0_f64;
280
281 for ky in 0..ksize {
282 for kx in 0..ksize {
283 let dx = kx as f64 - poly_n as f64;
284 let dy = ky as f64 - poly_n as f64;
285 let w = kernel[ky * ksize + kx];
286
287 let nx = (x as isize + kx as isize - pn) as usize;
288 let ny = (y as isize + ky as isize - pn) as usize;
289 let val = f64::from(image[ny * width + nx]);
290
291 let xx = dx * dx;
292 let yy = dy * dy;
293 let xy = dx * dy;
294
295 s_xx_xx += w * xx * xx;
296 s_yy_yy += w * yy * yy;
297 s_xy_xy += w * xy * xy;
298 s_x_x += w * dx * dx;
299 s_y_y += w * dy * dy;
300 s_xx_f += w * xx * val;
301 s_yy_f += w * yy * val;
302 s_xy_f += w * xy * val;
303 s_x_f += w * dx * val;
304 s_y_f += w * dy * val;
305 }
306 }
307
308 let a11 = if s_xx_xx > 1e-10 {
312 s_xx_f / s_xx_xx
313 } else {
314 0.0
315 };
316 let a22 = if s_yy_yy > 1e-10 {
317 s_yy_f / s_yy_yy
318 } else {
319 0.0
320 };
321 let a12 = if s_xy_xy > 1e-10 {
322 s_xy_f / s_xy_xy
323 } else {
324 0.0
325 };
326 let b1 = if s_x_x > 1e-10 { s_x_f / s_x_x } else { 0.0 };
327 let b2 = if s_y_y > 1e-10 { s_y_f / s_y_y } else { 0.0 };
328
329 coeffs[idx] = PolyCoeff {
330 a11: a11 as f32,
331 a22: a22 as f32,
332 a12: a12 as f32,
333 b1: b1 as f32,
334 b2: b2 as f32,
335 };
336 }
337 }
338
339 coeffs
340}
341
342fn update_flow(
344 poly_prev: &[PolyCoeff],
345 poly_curr: &[PolyCoeff],
346 flow: &DenseFlowField,
347 width: usize,
348 height: usize,
349 win_size: usize,
350) -> DenseFlowField {
351 let mut new_flow = DenseFlowField::zeros(width, height);
352 let half = (win_size / 2) as isize;
353
354 for y in 0..height {
355 for x in 0..width {
356 let idx = y * width + x;
357
358 let mut h11 = 0.0_f32;
360 let mut h22 = 0.0_f32;
361 let mut h12 = 0.0_f32;
362 let mut g1 = 0.0_f32;
363 let mut g2 = 0.0_f32;
364
365 for dy in -half..=half {
366 for dx in -half..=half {
367 let nx = x as isize + dx;
368 let ny = y as isize + dy;
369
370 if nx < 0 || ny < 0 || nx >= width as isize || ny >= height as isize {
371 continue;
372 }
373
374 let nidx = ny as usize * width + nx as usize;
375 let pp = &poly_prev[nidx];
376 let pc = &poly_curr[nidx];
377
378 let a11 = (pp.a11 + pc.a11) * 0.5;
380 let a22 = (pp.a22 + pc.a22) * 0.5;
381 let a12 = (pp.a12 + pc.a12) * 0.5;
382
383 let fx = flow.dx[nidx];
385 let fy = flow.dy[nidx];
386
387 let db1 = pc.b1 - pp.b1 + 2.0 * a11 * fx + a12 * fy;
388 let db2 = pc.b2 - pp.b2 + a12 * fx + 2.0 * a22 * fy;
389
390 h11 += 4.0 * a11 * a11 + a12 * a12;
391 h22 += a12 * a12 + 4.0 * a22 * a22;
392 h12 += 2.0 * a11 * a12 + 2.0 * a12 * a22;
393 g1 += 2.0 * a11 * db1 + a12 * db2;
394 g2 += a12 * db1 + 2.0 * a22 * db2;
395 }
396 }
397
398 let det = h11 * h22 - h12 * h12;
400 if det.abs() > 1e-6 {
401 let inv_det = 1.0 / det;
402 new_flow.dx[idx] = flow.dx[idx] - (h22 * g1 - h12 * g2) * inv_det;
403 new_flow.dy[idx] = flow.dy[idx] - (-h12 * g1 + h11 * g2) * inv_det;
404 } else {
405 new_flow.dx[idx] = flow.dx[idx];
406 new_flow.dy[idx] = flow.dy[idx];
407 }
408 }
409 }
410
411 new_flow
412}
413
414fn build_f32_pyramid(
417 image: &[u8],
418 width: usize,
419 height: usize,
420 levels: usize,
421) -> Vec<(Vec<f32>, usize, usize)> {
422 let f32_img: Vec<f32> = image.iter().map(|&v| f32::from(v)).collect();
423 let mut pyr = Vec::with_capacity(levels);
424 pyr.push((f32_img, width, height));
425
426 for _ in 1..levels {
427 let (ref prev, pw, ph) = pyr[pyr.len() - 1];
428 let nw = pw / 2;
429 let nh = ph / 2;
430 if nw < 4 || nh < 4 {
431 break;
432 }
433 let down = downsample_f32(prev, pw, ph, nw, nh);
434 pyr.push((down, nw, nh));
435 }
436
437 pyr
438}
439
440fn downsample_f32(src: &[f32], sw: usize, sh: usize, dw: usize, dh: usize) -> Vec<f32> {
441 let mut dst = vec![0.0_f32; dw * dh];
442 for dy in 0..dh {
443 for dx in 0..dw {
444 let sx = dx * 2;
445 let sy = dy * 2;
446 let mut sum = 0.0_f32;
447 let mut count = 0u32;
448 for oy in 0..2 {
449 for ox in 0..2 {
450 let rx = sx + ox;
451 let ry = sy + oy;
452 if rx < sw && ry < sh {
453 sum += src[ry * sw + rx];
454 count += 1;
455 }
456 }
457 }
458 dst[dy * dw + dx] = if count > 0 { sum / count as f32 } else { 0.0 };
459 }
460 }
461 dst
462}
463
464fn trim_flow(flow: &DenseFlowField, target_w: usize, target_h: usize) -> DenseFlowField {
465 let mut out = DenseFlowField::zeros(target_w, target_h);
466 for y in 0..target_h.min(flow.height) {
467 for x in 0..target_w.min(flow.width) {
468 let src_idx = y * flow.width + x;
469 let dst_idx = y * target_w + x;
470 out.dx[dst_idx] = flow.dx[src_idx];
471 out.dy[dst_idx] = flow.dy[src_idx];
472 }
473 }
474 out
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480
481 #[test]
484 fn test_dense_flow_zeros() {
485 let f = DenseFlowField::zeros(10, 10);
486 assert_eq!(f.width, 10);
487 assert_eq!(f.height, 10);
488 assert_eq!(f.avg_magnitude(), 0.0);
489 assert_eq!(f.max_magnitude(), 0.0);
490 }
491
492 #[test]
493 fn test_dense_flow_at() {
494 let mut f = DenseFlowField::zeros(4, 4);
495 f.dx[5] = 3.0;
496 f.dy[5] = 4.0;
497 let (dx, dy) = f.at(1, 1).expect("should be in bounds");
498 assert!((dx - 3.0).abs() < 1e-6);
499 assert!((dy - 4.0).abs() < 1e-6);
500 }
501
502 #[test]
503 fn test_dense_flow_at_out_of_bounds() {
504 let f = DenseFlowField::zeros(4, 4);
505 assert!(f.at(10, 10).is_none());
506 }
507
508 #[test]
509 fn test_dense_flow_upsample() {
510 let mut f = DenseFlowField::zeros(4, 4);
511 for i in 0..16 {
512 f.dx[i] = 1.0;
513 f.dy[i] = -1.0;
514 }
515 let up = f.upsample_2x();
516 assert_eq!(up.width, 8);
517 assert_eq!(up.height, 8);
518 let (dx, dy) = up.at(4, 4).expect("should be in bounds");
520 assert!((dx - 2.0).abs() < 0.5, "dx={dx}");
521 assert!((dy + 2.0).abs() < 0.5, "dy={dy}");
522 }
523
524 #[test]
525 fn test_dense_flow_max_magnitude() {
526 let mut f = DenseFlowField::zeros(4, 4);
527 f.dx[0] = 3.0;
528 f.dy[0] = 4.0;
529 assert!((f.max_magnitude() - 5.0).abs() < 1e-5);
530 }
531
532 #[test]
535 fn test_farneback_identical_frames() {
536 let w = 64usize;
537 let h = 64usize;
538 let img = vec![128u8; w * h];
539
540 let config = FarnebackConfig {
541 pyramid_levels: 2,
542 poly_n: 2,
543 poly_sigma: 1.0,
544 iterations: 3,
545 win_size: 5,
546 };
547
548 let flow = compute_farneback_flow(&img, &img, w, h, &config).expect("should succeed");
549 assert_eq!(flow.width, w);
550 assert_eq!(flow.height, h);
551 assert!(
553 flow.avg_magnitude() < 1.0,
554 "identical frames avg_mag={}",
555 flow.avg_magnitude()
556 );
557 }
558
559 #[test]
560 fn test_farneback_shifted_image() {
561 let w = 64usize;
562 let h = 64usize;
563 let mut prev = vec![0u8; w * h];
565 let mut curr = vec![0u8; w * h];
566 for y in 0..h {
567 for x in 0..w {
568 prev[y * w + x] = if (x / 8) % 2 == 0 { 200 } else { 50 };
569 let sx = (x + 2).min(w - 1);
571 curr[y * w + x] = if (sx / 8) % 2 == 0 { 200 } else { 50 };
572 }
573 }
574
575 let config = FarnebackConfig {
576 pyramid_levels: 2,
577 poly_n: 3,
578 poly_sigma: 1.2,
579 iterations: 5,
580 win_size: 7,
581 };
582
583 let flow = compute_farneback_flow(&prev, &curr, w, h, &config).expect("should succeed");
584 assert!(flow.max_magnitude() > 0.0, "should detect some motion");
586 }
587
588 #[test]
589 fn test_farneback_image_mismatch() {
590 let config = FarnebackConfig::default();
591 let result = compute_farneback_flow(&[0u8; 100], &[0u8; 200], 10, 10, &config);
592 assert!(result.is_err());
593 }
594
595 #[test]
596 fn test_farneback_too_small() {
597 let config = FarnebackConfig::default();
598 let result = compute_farneback_flow(&[0u8; 4], &[0u8; 4], 2, 2, &config);
599 assert!(result.is_err());
600 }
601
602 #[test]
603 fn test_farneback_default_config() {
604 let config = FarnebackConfig::default();
605 assert_eq!(config.pyramid_levels, 3);
606 assert_eq!(config.poly_n, 3);
607 assert_eq!(config.iterations, 5);
608 }
609
610 #[test]
613 fn test_f32_pyramid_levels() {
614 let img = vec![100u8; 64 * 64];
615 let pyr = build_f32_pyramid(&img, 64, 64, 3);
616 assert_eq!(pyr.len(), 3);
617 assert_eq!(pyr[0].1, 64);
618 assert_eq!(pyr[1].1, 32);
619 assert_eq!(pyr[2].1, 16);
620 }
621
622 #[test]
623 fn test_f32_pyramid_constant_preserved() {
624 let img = vec![100u8; 32 * 32];
625 let pyr = build_f32_pyramid(&img, 32, 32, 2);
626 for &v in &pyr[1].0 {
627 assert!((v - 100.0).abs() < 1e-3);
628 }
629 }
630
631 #[test]
632 fn test_trim_flow_smaller() {
633 let f = DenseFlowField::zeros(8, 8);
634 let trimmed = trim_flow(&f, 4, 4);
635 assert_eq!(trimmed.width, 4);
636 assert_eq!(trimmed.height, 4);
637 }
638
639 #[test]
640 fn test_polynomial_expansion_constant() {
641 let img = vec![100.0_f32; 32 * 32];
642 let coeffs = polynomial_expansion(&img, 32, 32, 2, 1.0);
643 for c in &coeffs[3 * 32 + 3..29 * 32 + 29] {
645 assert!(c.b1.abs() < 1.0, "b1={}", c.b1);
646 assert!(c.b2.abs() < 1.0, "b2={}", c.b2);
647 }
648 }
649}