1use image::{GrayImage, ImageBuffer, Luma, Pixel};
2
3use crate::features::reserve_total;
4use crate::float_image::FloatImageView;
5use crate::pyramid::{
6 build_float_pyramid_into, build_pyramid_into, level_dimensions, pyramid_level_count,
7 reserve_pyramid,
8};
9use crate::utils::fast_gradients::compute_gradients_into;
10use crate::utils::float_gradients::scharr_f32;
11
12mod fixed;
13
14pub type FloatPyramidLevel = ImageBuffer<Luma<f32>, Vec<f32>>;
16
17pub const DEFAULT_MIN_EIGEN_THRESHOLD: f32 = 1e-3;
29
30pub const CONVERGENCE_EPSILON: f32 = 0.01;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum TrackStatus {
45 Tracked,
47 OutOfBounds,
49 Diverged,
54 LowTexture,
57 FbInconsistent,
61}
62
63pub const DEFAULT_FB_THRESHOLD: f32 = 0.7;
66
67#[derive(Clone, Copy, Debug, PartialEq)]
82pub struct TrackParams {
83 pub window_size: usize,
85 pub max_iterations: usize,
87 pub min_eigen_threshold: f32,
93}
94
95#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
110#[non_exhaustive]
111pub enum TrackKernel {
112 #[default]
117 Auto,
118 FixedPoint,
121 Reference,
124}
125
126#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
139#[non_exhaustive]
140pub struct TrackOptions {
141 pub start_level: Option<usize>,
151 pub fb_levels: Option<usize>,
163 pub kernel: TrackKernel,
165}
166
167impl TrackOptions {
168 pub const fn new() -> Self {
170 TrackOptions {
171 start_level: None,
172 fb_levels: None,
173 kernel: TrackKernel::Auto,
174 }
175 }
176
177 pub const fn with_start_level(mut self, level: usize) -> Self {
180 self.start_level = Some(level);
181 self
182 }
183
184 pub const fn with_fb_levels(mut self, levels: usize) -> Self {
187 self.fb_levels = Some(levels);
188 self
189 }
190
191 pub const fn with_kernel(mut self, kernel: TrackKernel) -> Self {
193 self.kernel = kernel;
194 self
195 }
196}
197
198#[derive(Debug, Clone, Copy, PartialEq)]
206pub struct TrackResult {
207 pub pos: (f32, f32),
209 pub status: TrackStatus,
211 pub error: f32,
219}
220
221pub fn calc_optical_flow_ex(
245 prev_pyramid: &[GrayImage],
246 curr_pyramid: &[GrayImage],
247 prev_points: &[(f32, f32)],
248 predicted: Option<&[(f32, f32)]>,
249 params: TrackParams,
250) -> Vec<TrackResult> {
251 let mut scratch = Scratch::default();
252 let mut out = Vec::new();
253 track_into(
254 prev_pyramid,
255 &mut Gradients::default(),
256 curr_pyramid,
257 prev_points,
258 predicted,
259 params,
260 None,
261 GrayImage::select(TrackKernel::Auto, params.window_size),
262 &mut scratch,
263 &mut out,
264 );
265 out
266}
267
268#[derive(Default)]
272struct Scratch {
273 float_patch: [Vec<f32>; 3],
275 fixed_patch: [Vec<i16>; 3],
277 displacements: Vec<(f32, f32)>,
278}
279
280impl Scratch {
281 fn reserve(&mut self, capacity: &TrackerCapacity) {
282 let size = capacity.window_size;
283 for buffer in &mut self.float_patch {
284 reserve_total(buffer, size * size);
285 }
286 for buffer in &mut self.fixed_patch {
287 reserve_total(buffer, size * fixed::stride(size));
288 }
289 reserve_total(&mut self.displacements, capacity.points);
290 }
291}
292
293#[derive(Default)]
298struct Gradients<G> {
299 levels: Vec<GradientLevel<G>>,
300}
301
302#[derive(Default)]
303struct GradientLevel<G> {
304 x: Vec<G>,
305 y: Vec<G>,
306 ready: bool,
308}
309
310impl<G: Copy + Default> Gradients<G> {
311 fn invalidate(&mut self) {
313 for level in &mut self.levels {
314 level.ready = false;
315 }
316 }
317
318 fn reserve(&mut self, width: u32, height: u32, levels: usize) {
321 for (index, (w, h)) in level_dimensions(width, height, levels).enumerate() {
322 if index == self.levels.len() {
323 self.levels.push(GradientLevel::default());
324 }
325 let pixels = w as usize * h as usize;
326 reserve_total(&mut self.levels[index].x, pixels);
327 reserve_total(&mut self.levels[index].y, pixels);
328 }
329 }
330
331 fn level<I: TrackImage<Gradient = G>>(&mut self, pyramid: &[I], level: usize) -> (&[G], &[G]) {
334 if self.levels.len() < pyramid.len() {
335 self.levels
336 .resize_with(pyramid.len(), GradientLevel::default);
337 }
338 let image = &pyramid[level];
339 let slot = &mut self.levels[level];
340 if !slot.ready {
341 let (w, h) = image.dimensions();
342 let pixels = w as usize * h as usize;
343 slot.x.resize(pixels, G::default());
344 slot.y.resize(pixels, G::default());
345 image.gradients_into(&mut slot.x, &mut slot.y);
346 slot.ready = true;
347 }
348 (&slot.x, &slot.y)
349 }
350}
351
352#[allow(clippy::too_many_arguments)]
358fn track_into<I: TrackImage>(
359 prev_pyramid: &[I],
360 prev_gradients: &mut Gradients<I::Gradient>,
361 curr_pyramid: &[I],
362 prev_points: &[(f32, f32)],
363 predicted: Option<&[(f32, f32)]>,
364 params: TrackParams,
365 start_level: Option<usize>,
366 kernel: I::Kernel,
367 scratch: &mut Scratch,
368 out: &mut Vec<TrackResult>,
369) {
370 assert_eq!(prev_pyramid.len(), curr_pyramid.len());
371 assert!(
372 !prev_pyramid.is_empty(),
373 "pyramid must have at least 1 level"
374 );
375 assert!(
376 prev_pyramid
377 .iter()
378 .zip(curr_pyramid)
379 .all(|(prev, curr)| prev.dimensions() == curr.dimensions()),
380 "both pyramids must have the same level sizes"
381 );
382 let TrackParams {
383 window_size,
384 max_iterations,
385 min_eigen_threshold,
386 } = params;
387 assert!(window_size % 2 == 1, "Window size must be odd");
388 if let Some(predicted) = predicted {
389 assert_eq!(
390 predicted.len(),
391 prev_points.len(),
392 "predicted must have one entry per prev_point"
393 );
394 }
395
396 let top = prev_pyramid.len() - 1;
397 let start = start_level.map_or(top, |level| level.min(top));
398 let radius = window_size / 2;
399
400 let displacements = &mut scratch.displacements;
404 displacements.clear();
405 match predicted {
406 Some(predicted) => displacements.extend(
407 prev_points
408 .iter()
409 .zip(predicted.iter())
410 .map(|((px, py), (gx, gy))| (gx - px, gy - py)),
411 ),
412 None => displacements.resize(prev_points.len(), (0.0, 0.0)),
413 }
414
415 out.clear();
417 out.extend(prev_points.iter().map(|&(x, y)| TrackResult {
418 pos: (x, y),
419 status: TrackStatus::Tracked,
420 error: f32::INFINITY,
421 }));
422
423 for level in (0..=start).rev() {
425 let prev_img = &prev_pyramid[level];
426 let curr_img = &curr_pyramid[level];
427 let (lw, lh) = prev_img.dimensions();
428 let (grad_x, grad_y) = prev_gradients.level(prev_pyramid, level);
429
430 let level = Level::<I> {
431 prev: prev_img.pixels(),
432 curr: curr_img.pixels(),
433 grad_x,
434 grad_y,
435 width: lw as usize,
436 height: lh as usize,
437 scale: 2f32.powi(level as i32),
438 is_finest: level == 0,
439 window_size,
440 radius,
441 max_iterations,
442 min_eigen_threshold,
443 };
444
445 I::track_level(kernel, &level, prev_points, scratch, out);
446 }
447
448 for (idx, (x, y)) in prev_points.iter().enumerate() {
450 let (dx, dy) = scratch.displacements[idx];
451 out[idx].pos = (x + dx, y + dy);
452 }
453}
454
455struct Level<'a, I: TrackImage> {
457 prev: &'a [I::Pixel],
458 curr: &'a [I::Pixel],
459 grad_x: &'a [I::Gradient],
460 grad_y: &'a [I::Gradient],
461 width: usize,
462 height: usize,
463 scale: f32,
464 is_finest: bool,
465 window_size: usize,
466 radius: usize,
467 max_iterations: usize,
468 min_eigen_threshold: f32,
469}
470
471#[derive(Clone, Copy, Debug)]
476enum Kernel {
477 Portable,
478 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
479 Avx2,
480}
481
482impl Kernel {
483 fn detect() -> Self {
484 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
485 if is_x86_feature_detected!("avx2") {
486 return Kernel::Avx2;
487 }
488 Kernel::Portable
489 }
490
491 fn track_level<I: TrackImage>(
493 self,
494 level: &Level<I>,
495 prev_points: &[(f32, f32)],
496 scratch: &mut Scratch,
497 out: &mut [TrackResult],
498 ) {
499 let n_pixels = level.window_size * level.window_size;
500 let Scratch {
501 float_patch: [prev, ix, iy],
502 displacements,
503 ..
504 } = scratch;
505 for patch in [&mut *prev, &mut *ix, &mut *iy] {
506 patch.resize(n_pixels, 0.0);
507 }
508 let windows = FloatWindows { prev, ix, iy };
509 match self {
510 Kernel::Portable => track_level_impl(level, prev_points, displacements, windows, out),
511 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
512 Kernel::Avx2 => unsafe {
514 track_level_avx2(level, prev_points, displacements, windows, out)
515 },
516 }
517 }
518}
519
520#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
521#[target_feature(enable = "avx2")]
522unsafe fn track_level_avx2<I: TrackImage, W: Windows<I>>(
523 level: &Level<I>,
524 prev_points: &[(f32, f32)],
525 displacements: &mut [(f32, f32)],
526 windows: W,
527 out: &mut [TrackResult],
528) {
529 track_level_impl(level, prev_points, displacements, windows, out)
530}
531
532#[derive(Clone, Copy, Debug)]
536enum FixedKernel {
537 #[cfg_attr(
539 any(
540 all(
541 any(target_arch = "x86", target_arch = "x86_64"),
542 target_feature = "sse2"
543 ),
544 all(target_arch = "wasm32", target_feature = "simd128")
545 ),
546 allow(dead_code)
547 )]
548 Scalar,
549 #[cfg(all(
550 any(target_arch = "x86", target_arch = "x86_64"),
551 target_feature = "sse2"
552 ))]
553 Sse2,
554 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
555 Avx2(fixed::Avx2),
556 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
557 Simd128,
558}
559
560impl FixedKernel {
561 #[cfg(all(
563 any(target_arch = "x86", target_arch = "x86_64"),
564 target_feature = "sse2"
565 ))]
566 const BASELINE: Self = FixedKernel::Sse2;
567 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
568 const BASELINE: Self = FixedKernel::Simd128;
569 #[cfg(not(any(
570 all(
571 any(target_arch = "x86", target_arch = "x86_64"),
572 target_feature = "sse2"
573 ),
574 all(target_arch = "wasm32", target_feature = "simd128")
575 )))]
576 const BASELINE: Self = FixedKernel::Scalar;
577
578 fn detect() -> Self {
580 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
581 if let Some(avx2) = fixed::Avx2::detect() {
582 return FixedKernel::Avx2(avx2);
583 }
584 Self::BASELINE
585 }
586
587 fn track_level(
589 self,
590 level: &Level<GrayImage>,
591 prev_points: &[(f32, f32)],
592 scratch: &mut Scratch,
593 out: &mut [TrackResult],
594 ) {
595 let size = level.window_size;
596 let stride = fixed::stride(size);
597 let Scratch {
598 fixed_patch: [pixels, grad_x, grad_y],
599 displacements,
600 ..
601 } = scratch;
602 for patch in [&mut *pixels, &mut *grad_x, &mut *grad_y] {
603 patch.resize(size * stride, 0);
604 }
605 let patch = fixed::Patch {
606 size,
607 stride,
608 pixels,
609 grad_x,
610 grad_y,
611 };
612 fn run<K: fixed::Ops>(
613 level: &Level<GrayImage>,
614 prev_points: &[(f32, f32)],
615 displacements: &mut [(f32, f32)],
616 patch: fixed::Patch<'_>,
617 ops: K,
618 out: &mut [TrackResult],
619 ) {
620 let windows = FixedWindows { patch, ops };
621 track_level_impl(level, prev_points, displacements, windows, out)
622 }
623 match self {
624 FixedKernel::Scalar => {
625 run(level, prev_points, displacements, patch, fixed::Scalar, out)
626 }
627 #[cfg(all(
628 any(target_arch = "x86", target_arch = "x86_64"),
629 target_feature = "sse2"
630 ))]
631 FixedKernel::Sse2 => run(level, prev_points, displacements, patch, fixed::Sse2, out),
632 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
633 FixedKernel::Avx2(avx2) => run(level, prev_points, displacements, patch, avx2, out),
634 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
635 FixedKernel::Simd128 => run(
636 level,
637 prev_points,
638 displacements,
639 patch,
640 fixed::Simd128,
641 out,
642 ),
643 }
644 }
645}
646
647#[derive(Clone, Copy, Debug)]
649enum ByteKernel {
650 Reference(Kernel),
651 Fixed(FixedKernel),
652}
653
654trait Windows<I: TrackImage> {
657 fn build(&mut self, lv: &Level<I>, win: &Window) -> (f32, f32, f32);
660
661 fn step(&self, lv: &Level<I>, win: &Window) -> (f32, f32);
664
665 fn error(&self, lv: &Level<I>, win: &Window) -> f32;
667}
668
669struct FloatWindows<'a> {
671 prev: &'a mut [f32],
672 ix: &'a mut [f32],
673 iy: &'a mut [f32],
674}
675
676impl<I: TrackImage> Windows<I> for FloatWindows<'_> {
677 #[inline(always)]
678 fn build(&mut self, lv: &Level<I>, win: &Window) -> (f32, f32, f32) {
679 build_patch(lv, win, self.prev, self.ix, self.iy)
680 }
681
682 #[inline(always)]
683 fn step(&self, lv: &Level<I>, win: &Window) -> (f32, f32) {
684 lk_step(lv, win, self.prev, self.ix, self.iy)
685 }
686
687 #[inline(always)]
688 fn error(&self, lv: &Level<I>, win: &Window) -> f32 {
689 window_error(lv, win, self.prev)
690 }
691}
692
693struct FixedWindows<'a, K> {
695 patch: fixed::Patch<'a>,
696 ops: K,
697}
698
699impl<K: fixed::Ops> Windows<GrayImage> for FixedWindows<'_, K> {
700 #[inline(always)]
701 fn build(&mut self, lv: &Level<GrayImage>, win: &Window) -> (f32, f32, f32) {
702 let source = fixed::Source {
703 pixels: fixed::Plane {
704 data: lv.prev,
705 width: lv.width,
706 },
707 grad_x: fixed::Plane {
708 data: lv.grad_x,
709 width: lv.width,
710 },
711 grad_y: fixed::Plane {
712 data: lv.grad_y,
713 width: lv.width,
714 },
715 };
716 let weights = fixed::Weights::of(win);
717 let [xx, xy, yy] = self
718 .ops
719 .build(source, win.x0, win.y0, weights, &mut self.patch);
720 let scale = |sum: i64| sum as f32 * fixed::SPATIAL_SCALE;
721 (scale(xx), scale(xy), scale(yy))
722 }
723
724 #[inline(always)]
725 fn step(&self, lv: &Level<GrayImage>, win: &Window) -> (f32, f32) {
726 let next = fixed::Plane {
727 data: lv.curr,
728 width: lv.width,
729 };
730 let weights = fixed::Weights::of(win);
731 let [bx, by] = self.ops.step(next, win.x0, win.y0, weights, &self.patch);
732 (
733 bx as f32 * fixed::RESIDUAL_SCALE,
734 by as f32 * fixed::RESIDUAL_SCALE,
735 )
736 }
737
738 #[inline(always)]
739 fn error(&self, lv: &Level<GrayImage>, win: &Window) -> f32 {
740 let next = fixed::Plane {
741 data: lv.curr,
742 width: lv.width,
743 };
744 let weights = fixed::Weights::of(win);
745 let sum = self.ops.error(next, win.x0, win.y0, weights, &self.patch);
746 sum as f32 * fixed::ERROR_SCALE / (lv.window_size * lv.window_size) as f32
747 }
748}
749
750#[inline(always)]
752fn track_level_impl<I: TrackImage, W: Windows<I>>(
753 lv: &Level<I>,
754 prev_points: &[(f32, f32)],
755 displacements: &mut [(f32, f32)],
756 mut windows: W,
757 out: &mut [TrackResult],
758) {
759 let n_pixels = lv.window_size * lv.window_size;
760 let window_limit = lv.window_size as f32;
761 let final_error = |windows: &W, cx: f32, cy: f32| {
764 Window::new(cx, cy, lv.radius, lv.width, lv.height)
765 .map_or(f32::INFINITY, |win| windows.error(lv, &win))
766 };
767
768 for (idx, &(prev_x, prev_y)) in prev_points.iter().enumerate() {
769 let x = prev_x / lv.scale;
771 let y = prev_y / lv.scale;
772
773 let mut dx = displacements[idx].0 / lv.scale;
775 let mut dy = displacements[idx].1 / lv.scale;
776
777 let Some(prev_win) = Window::new(x, y, lv.radius, lv.width, lv.height) else {
779 out[idx].status = TrackStatus::OutOfBounds;
780 continue;
781 };
782
783 let (gxx, gxy, gyy) = windows.build(lv, &prev_win);
785
786 let (inv_h00, inv_h01, inv_h11) =
789 match I::spatial_inverse(gxx, gxy, gyy, n_pixels, lv.min_eigen_threshold) {
790 Spatial::Invertible(inverse) => inverse,
791 Spatial::LowTexture { measure_error } => {
792 out[idx].status = TrackStatus::LowTexture;
793 if measure_error && lv.is_finest {
794 out[idx].error = final_error(&windows, x + dx, y + dy);
795 }
796 continue;
797 }
798 };
799
800 let mut converged = false;
802 let mut out_of_bounds = false;
803 let mut diverged = false;
804 let mut prev_step = (0.0f32, 0.0f32);
805 for iteration in 0..lv.max_iterations {
806 let Some(win) = Window::new(x + dx, y + dy, lv.radius, lv.width, lv.height) else {
807 out_of_bounds = true;
808 break;
809 };
810
811 let (bx, by) = windows.step(lv, &win);
812
813 let ddx = inv_h00 * bx + inv_h01 * by;
814 let ddy = inv_h01 * bx + inv_h11 * by;
815 dx += ddx;
816 dy += ddy;
817
818 if !dx.is_finite()
820 || !dy.is_finite()
821 || ddx.abs() > window_limit
822 || ddy.abs() > window_limit
823 {
824 diverged = true;
825 break;
826 }
827
828 if ddx.abs() < CONVERGENCE_EPSILON && ddy.abs() < CONVERGENCE_EPSILON {
829 converged = true;
830 break;
831 }
832
833 if iteration > 0
836 && (ddx + prev_step.0).abs() < CONVERGENCE_EPSILON
837 && (ddy + prev_step.1).abs() < CONVERGENCE_EPSILON
838 {
839 dx -= ddx * 0.5;
840 dy -= ddy * 0.5;
841 converged = true;
842 break;
843 }
844 prev_step = (ddx, ddy);
845 }
846
847 out[idx].status = if out_of_bounds {
848 TrackStatus::OutOfBounds
849 } else if diverged || !converged {
850 TrackStatus::Diverged
851 } else {
852 TrackStatus::Tracked
853 };
854
855 displacements[idx] = (dx * lv.scale, dy * lv.scale);
857
858 if lv.is_finest {
859 out[idx].error = if out_of_bounds {
860 f32::INFINITY
861 } else {
862 final_error(&windows, x + dx, y + dy)
863 };
864 }
865 }
866}
867
868struct Window {
873 x0: usize,
874 y0: usize,
875 w00: f32,
876 w10: f32,
877 w01: f32,
878 w11: f32,
879}
880
881impl Window {
882 #[inline(always)]
885 fn new(cx: f32, cy: f32, radius: usize, width: usize, height: usize) -> Option<Window> {
886 let r = radius as f32;
887 if !(cx >= r && cy >= r && cx < width as f32 - r - 1.0 && cy < height as f32 - r - 1.0) {
889 return None;
890 }
891 let fx0 = cx.floor();
892 let fy0 = cy.floor();
893 let fx = cx - fx0;
894 let fy = cy - fy0;
895 Some(Window {
896 x0: fx0 as usize - radius,
897 y0: fy0 as usize - radius,
898 w00: (1.0 - fx) * (1.0 - fy),
899 w10: fx * (1.0 - fy),
900 w01: (1.0 - fx) * fy,
901 w11: fx * fy,
902 })
903 }
904
905 #[inline(always)]
907 fn scaled(&self, factor: f32) -> Window {
908 Window {
909 x0: self.x0,
910 y0: self.y0,
911 w00: self.w00 * factor,
912 w10: self.w10 * factor,
913 w01: self.w01 * factor,
914 w11: self.w11 * factor,
915 }
916 }
917
918 #[inline(always)]
920 fn rows<'a, T>(
921 &self,
922 data: &'a [T],
923 width: usize,
924 j: usize,
925 size: usize,
926 ) -> (&'a [T], &'a [T]) {
927 let start = (self.y0 + j) * width + self.x0;
928 (
929 &data[start..start + size + 1],
930 &data[start + width..start + width + size + 1],
931 )
932 }
933}
934
935#[inline(always)]
937fn sample_lanes<T: Copy + Into<f32>, const L: usize>(win: &Window, a: &[T], b: &[T]) -> [f32; L] {
938 let a0: &[T; L] = a[..L].try_into().unwrap();
939 let a1: &[T; L] = a[1..=L].try_into().unwrap();
940 let b0: &[T; L] = b[..L].try_into().unwrap();
941 let b1: &[T; L] = b[1..=L].try_into().unwrap();
942 let mut s = [0f32; L];
943 for k in 0..L {
944 s[k] = win.w00 * a0[k].into()
945 + win.w10 * a1[k].into()
946 + win.w01 * b0[k].into()
947 + win.w11 * b1[k].into();
948 }
949 s
950}
951
952#[inline(always)]
953fn total<const L: usize>(acc: &[f32; L]) -> f32 {
954 acc.iter().sum()
955}
956
957#[inline(always)]
960fn build_patch<I: TrackImage>(
961 lv: &Level<I>,
962 win: &Window,
963 prev_patch: &mut [f32],
964 ix_patch: &mut [f32],
965 iy_patch: &mut [f32],
966) -> (f32, f32, f32) {
967 #[inline(always)]
968 #[allow(clippy::too_many_arguments)]
969 fn lanes<P: Copy + Into<f32>, G: Copy + Into<f32>, const L: usize>(
970 win: &Window,
971 gwin: &Window,
972 (pa, pb): (&[P], &[P]),
973 (xa, xb): (&[G], &[G]),
974 (ya, yb): (&[G], &[G]),
975 p: &mut [f32],
976 ox: &mut [f32],
977 oy: &mut [f32],
978 acc: &mut [[f32; L]; 3],
979 ) {
980 let s = sample_lanes::<P, L>(win, pa, pb);
981 let sx = sample_lanes::<G, L>(gwin, xa, xb);
982 let sy = sample_lanes::<G, L>(gwin, ya, yb);
983 let p: &mut [f32; L] = (&mut p[..L]).try_into().unwrap();
984 let ox: &mut [f32; L] = (&mut ox[..L]).try_into().unwrap();
985 let oy: &mut [f32; L] = (&mut oy[..L]).try_into().unwrap();
986 *p = s;
987 *ox = sx;
988 *oy = sy;
989 for k in 0..L {
990 acc[0][k] += sx[k] * sx[k];
991 acc[1][k] += sx[k] * sy[k];
992 acc[2][k] += sy[k] * sy[k];
993 }
994 }
995
996 let size = lv.window_size;
997 let gwin = win.scaled(1.0 / 32.0);
999 let mut acc8 = [[0f32; 8]; 3];
1000 let mut acc4 = [[0f32; 4]; 3];
1001 let mut acc1 = [[0f32; 1]; 3];
1002
1003 for j in 0..size {
1004 let (pa, pb) = win.rows(lv.prev, lv.width, j, size);
1005 let (xa, xb) = win.rows(lv.grad_x, lv.width, j, size);
1006 let (ya, yb) = win.rows(lv.grad_y, lv.width, j, size);
1007 let p = &mut prev_patch[j * size..][..size];
1008 let ox = &mut ix_patch[j * size..][..size];
1009 let oy = &mut iy_patch[j * size..][..size];
1010
1011 let mut i = 0;
1012 while i + 8 <= size {
1013 lanes::<_, _, 8>(
1014 win,
1015 &gwin,
1016 (&pa[i..], &pb[i..]),
1017 (&xa[i..], &xb[i..]),
1018 (&ya[i..], &yb[i..]),
1019 &mut p[i..],
1020 &mut ox[i..],
1021 &mut oy[i..],
1022 &mut acc8,
1023 );
1024 i += 8;
1025 }
1026 if i + 4 <= size {
1027 lanes::<_, _, 4>(
1028 win,
1029 &gwin,
1030 (&pa[i..], &pb[i..]),
1031 (&xa[i..], &xb[i..]),
1032 (&ya[i..], &yb[i..]),
1033 &mut p[i..],
1034 &mut ox[i..],
1035 &mut oy[i..],
1036 &mut acc4,
1037 );
1038 i += 4;
1039 }
1040 while i < size {
1041 lanes::<_, _, 1>(
1042 win,
1043 &gwin,
1044 (&pa[i..], &pb[i..]),
1045 (&xa[i..], &xb[i..]),
1046 (&ya[i..], &yb[i..]),
1047 &mut p[i..],
1048 &mut ox[i..],
1049 &mut oy[i..],
1050 &mut acc1,
1051 );
1052 i += 1;
1053 }
1054 }
1055
1056 (
1057 total(&acc8[0]) + total(&acc4[0]) + total(&acc1[0]),
1058 total(&acc8[1]) + total(&acc4[1]) + total(&acc1[1]),
1059 total(&acc8[2]) + total(&acc4[2]) + total(&acc1[2]),
1060 )
1061}
1062
1063#[inline(always)]
1066fn lk_step<I: TrackImage>(
1067 lv: &Level<I>,
1068 win: &Window,
1069 prev_patch: &[f32],
1070 ix_patch: &[f32],
1071 iy_patch: &[f32],
1072) -> (f32, f32) {
1073 #[inline(always)]
1074 fn lanes<P: Copy + Into<f32>, const L: usize>(
1075 win: &Window,
1076 (a, b): (&[P], &[P]),
1077 p: &[f32],
1078 gx: &[f32],
1079 gy: &[f32],
1080 acc: &mut [[f32; L]; 2],
1081 ) {
1082 let s = sample_lanes::<P, L>(win, a, b);
1083 let p: &[f32; L] = p[..L].try_into().unwrap();
1084 let gx: &[f32; L] = gx[..L].try_into().unwrap();
1085 let gy: &[f32; L] = gy[..L].try_into().unwrap();
1086 for k in 0..L {
1087 let e = p[k] - s[k];
1088 acc[0][k] += gx[k] * e;
1089 acc[1][k] += gy[k] * e;
1090 }
1091 }
1092
1093 let size = lv.window_size;
1094 let mut acc8 = [[0f32; 8]; 2];
1095 let mut acc4 = [[0f32; 4]; 2];
1096 let mut acc1 = [[0f32; 1]; 2];
1097
1098 for j in 0..size {
1099 let (a, b) = win.rows(lv.curr, lv.width, j, size);
1100 let p = &prev_patch[j * size..][..size];
1101 let gx = &ix_patch[j * size..][..size];
1102 let gy = &iy_patch[j * size..][..size];
1103
1104 let mut i = 0;
1105 while i + 8 <= size {
1106 lanes::<_, 8>(
1107 win,
1108 (&a[i..], &b[i..]),
1109 &p[i..],
1110 &gx[i..],
1111 &gy[i..],
1112 &mut acc8,
1113 );
1114 i += 8;
1115 }
1116 if i + 4 <= size {
1117 lanes::<_, 4>(
1118 win,
1119 (&a[i..], &b[i..]),
1120 &p[i..],
1121 &gx[i..],
1122 &gy[i..],
1123 &mut acc4,
1124 );
1125 i += 4;
1126 }
1127 while i < size {
1128 lanes::<_, 1>(
1129 win,
1130 (&a[i..], &b[i..]),
1131 &p[i..],
1132 &gx[i..],
1133 &gy[i..],
1134 &mut acc1,
1135 );
1136 i += 1;
1137 }
1138 }
1139
1140 (
1141 total(&acc8[0]) + total(&acc4[0]) + total(&acc1[0]),
1142 total(&acc8[1]) + total(&acc4[1]) + total(&acc1[1]),
1143 )
1144}
1145
1146#[inline(always)]
1149fn window_error<I: TrackImage>(lv: &Level<I>, win: &Window, prev_patch: &[f32]) -> f32 {
1150 #[inline(always)]
1151 fn lanes<P: Copy + Into<f32>, const L: usize>(
1152 win: &Window,
1153 (a, b): (&[P], &[P]),
1154 p: &[f32],
1155 acc: &mut [f32; L],
1156 ) {
1157 let s = sample_lanes::<P, L>(win, a, b);
1158 let p: &[f32; L] = p[..L].try_into().unwrap();
1159 for k in 0..L {
1160 acc[k] += (p[k] - s[k]).abs();
1161 }
1162 }
1163
1164 let size = lv.window_size;
1165 let mut acc8 = [0f32; 8];
1166 let mut acc4 = [0f32; 4];
1167 let mut acc1 = [0f32; 1];
1168
1169 for j in 0..size {
1170 let (a, b) = win.rows(lv.curr, lv.width, j, size);
1171 let p = &prev_patch[j * size..][..size];
1172 let mut i = 0;
1173 while i + 8 <= size {
1174 lanes::<_, 8>(win, (&a[i..], &b[i..]), &p[i..], &mut acc8);
1175 i += 8;
1176 }
1177 if i + 4 <= size {
1178 lanes::<_, 4>(win, (&a[i..], &b[i..]), &p[i..], &mut acc4);
1179 i += 4;
1180 }
1181 while i < size {
1182 lanes::<_, 1>(win, (&a[i..], &b[i..]), &p[i..], &mut acc1);
1183 i += 1;
1184 }
1185 }
1186
1187 (total(&acc8) + total(&acc4) + total(&acc1)) / (size * size) as f32
1188}
1189
1190pub fn calc_optical_flow_fb(
1215 prev_pyramid: &[GrayImage],
1216 next_pyramid: &[GrayImage],
1217 prev_points: &[(f32, f32)],
1218 predicted: Option<&[(f32, f32)]>,
1219 params: TrackParams,
1220 fb_threshold: f32,
1221) -> Vec<TrackResult> {
1222 let mut scratch = Scratch::default();
1223 let mut forward = Vec::new();
1224 let kernel = GrayImage::select(TrackKernel::Auto, params.window_size);
1225 track_into(
1226 prev_pyramid,
1227 &mut Gradients::default(),
1228 next_pyramid,
1229 prev_points,
1230 predicted,
1231 params,
1232 None,
1233 kernel,
1234 &mut scratch,
1235 &mut forward,
1236 );
1237
1238 let forward_pos: Vec<(f32, f32)> = forward.iter().map(|r| r.pos).collect();
1239 let mut backward = Vec::new();
1240 track_into(
1245 next_pyramid,
1246 &mut Gradients::default(),
1247 prev_pyramid,
1248 &forward_pos,
1249 Some(prev_points),
1250 params,
1251 None,
1252 kernel,
1253 &mut scratch,
1254 &mut backward,
1255 );
1256
1257 mark_fb_inconsistent(&mut forward, &backward, prev_points, fb_threshold);
1258 forward
1259}
1260
1261fn mark_fb_inconsistent(
1264 forward: &mut [TrackResult],
1265 backward: &[TrackResult],
1266 prev_points: &[(f32, f32)],
1267 fb_threshold: f32,
1268) {
1269 let threshold_sq = fb_threshold * fb_threshold;
1270 for (idx, result) in forward.iter_mut().enumerate() {
1271 if result.status != TrackStatus::Tracked {
1273 continue;
1274 }
1275
1276 let back = &backward[idx];
1277 let dx = back.pos.0 - prev_points[idx].0;
1278 let dy = back.pos.1 - prev_points[idx].1;
1279 if back.status != TrackStatus::Tracked || dx * dx + dy * dy > threshold_sq {
1280 result.status = TrackStatus::FbInconsistent;
1281 }
1282 }
1283}
1284
1285fn min_eigenvalue(a: f32, b: f32, c: f32) -> f32 {
1287 let trace = a + c;
1288 let det = a * c - b * b;
1289 let disc = (trace * trace - 4.0 * det).max(0.0).sqrt();
1290 (trace - disc) / 2.0
1291}
1292
1293fn invert_2x2(a00: f32, a01: f32, a11: f32, det_epsilon: f32) -> Option<(f32, f32, f32)> {
1294 let det = a00 * a11 - a01 * a01;
1295 if det.abs() <= det_epsilon {
1296 return None;
1297 }
1298
1299 let inv_det = 1.0 / det;
1300 Some((a11 * inv_det, -a01 * inv_det, a00 * inv_det))
1301}
1302
1303enum Spatial {
1305 Invertible((f32, f32, f32)),
1307 LowTexture { measure_error: bool },
1310}
1311
1312trait TrackImage: Sized {
1315 type Pixel: Copy + Into<f32>;
1316 type Gradient: Copy + Into<f32> + Default;
1317 type Kernel: Copy;
1319
1320 fn dimensions(&self) -> (u32, u32);
1321
1322 fn pixels(&self) -> &[Self::Pixel];
1323
1324 fn gradients_into(&self, grad_x: &mut [Self::Gradient], grad_y: &mut [Self::Gradient]);
1326
1327 fn spatial_inverse(gxx: f32, gxy: f32, gyy: f32, n_pixels: usize, threshold: f32) -> Spatial;
1330
1331 fn select(choice: TrackKernel, window_size: usize) -> Self::Kernel;
1333
1334 fn track_level(
1336 kernel: Self::Kernel,
1337 level: &Level<Self>,
1338 prev_points: &[(f32, f32)],
1339 scratch: &mut Scratch,
1340 out: &mut [TrackResult],
1341 );
1342}
1343
1344impl TrackImage for GrayImage {
1345 type Pixel = u8;
1346 type Gradient = i16;
1347 type Kernel = ByteKernel;
1348
1349 fn dimensions(&self) -> (u32, u32) {
1350 ImageBuffer::dimensions(self)
1351 }
1352
1353 fn select(choice: TrackKernel, window_size: usize) -> ByteKernel {
1354 let fixed = FixedKernel::detect();
1355 let fixed_point = window_size <= fixed::MAX_WINDOW
1356 && match choice {
1357 TrackKernel::Auto => !matches!(fixed, FixedKernel::Scalar),
1358 TrackKernel::FixedPoint => true,
1359 TrackKernel::Reference => false,
1360 };
1361 if fixed_point {
1362 ByteKernel::Fixed(fixed)
1363 } else {
1364 ByteKernel::Reference(Kernel::detect())
1365 }
1366 }
1367
1368 fn track_level(
1369 kernel: ByteKernel,
1370 level: &Level<Self>,
1371 prev_points: &[(f32, f32)],
1372 scratch: &mut Scratch,
1373 out: &mut [TrackResult],
1374 ) {
1375 match kernel {
1376 ByteKernel::Reference(kernel) => kernel.track_level(level, prev_points, scratch, out),
1377 ByteKernel::Fixed(kernel) => kernel.track_level(level, prev_points, scratch, out),
1378 }
1379 }
1380
1381 fn pixels(&self) -> &[u8] {
1382 self.as_raw()
1383 }
1384
1385 fn gradients_into(&self, grad_x: &mut [i16], grad_y: &mut [i16]) {
1386 compute_gradients_into(self, grad_x, grad_y);
1387 }
1388
1389 #[inline(always)]
1390 fn spatial_inverse(gxx: f32, gxy: f32, gyy: f32, n_pixels: usize, threshold: f32) -> Spatial {
1391 let min_eig = min_eigenvalue(gxx, gxy, gyy) / n_pixels as f32;
1403 if min_eig < threshold {
1404 return Spatial::LowTexture {
1405 measure_error: true,
1406 };
1407 }
1408 match invert_2x2(gxx, gxy, gyy, 1e-6) {
1409 Some(inverse) => Spatial::Invertible(inverse),
1410 None => Spatial::LowTexture {
1411 measure_error: false,
1412 },
1413 }
1414 }
1415}
1416
1417impl TrackImage for FloatPyramidLevel {
1418 type Pixel = f32;
1419 type Gradient = f32;
1420 type Kernel = Kernel;
1421
1422 fn dimensions(&self) -> (u32, u32) {
1423 ImageBuffer::dimensions(self)
1424 }
1425
1426 fn select(_: TrackKernel, _: usize) -> Kernel {
1427 Kernel::detect()
1428 }
1429
1430 fn track_level(
1431 kernel: Kernel,
1432 level: &Level<Self>,
1433 prev_points: &[(f32, f32)],
1434 scratch: &mut Scratch,
1435 out: &mut [TrackResult],
1436 ) {
1437 kernel.track_level(level, prev_points, scratch, out)
1438 }
1439
1440 fn pixels(&self) -> &[f32] {
1441 self.as_raw()
1442 }
1443
1444 fn gradients_into(&self, grad_x: &mut [f32], grad_y: &mut [f32]) {
1445 let (width, height) = ImageBuffer::dimensions(self);
1446 scharr_f32(
1447 self.as_raw(),
1448 width as usize,
1449 height as usize,
1450 grad_x,
1451 grad_y,
1452 );
1453 }
1454
1455 #[inline(always)]
1456 fn spatial_inverse(gxx: f32, gxy: f32, gyy: f32, n_pixels: usize, threshold: f32) -> Spatial {
1457 let (a, b, c) = (gxx as f64, gyy as f64, gxy as f64);
1462 let determinant = a * b - c * c;
1463 let largest = (a + b + ((a - b).powi(2) + 4.0 * c * c).sqrt()) * 0.5;
1464 let smallest = if determinant > 0.0 {
1465 determinant / largest
1466 } else {
1467 0.0
1468 };
1469 let resolvable = smallest > largest * f32::EPSILON as f64;
1470 if !resolvable || smallest / (n_pixels as f64) < threshold as f64 {
1471 return Spatial::LowTexture {
1472 measure_error: true,
1473 };
1474 }
1475 let inverse = 1.0 / determinant;
1476 Spatial::Invertible((
1477 (b * inverse) as f32,
1478 (-c * inverse) as f32,
1479 (a * inverse) as f32,
1480 ))
1481 }
1482}
1483
1484#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1488pub struct TrackerCapacity {
1489 pub width: u32,
1491 pub height: u32,
1493 pub levels: usize,
1495 pub window_size: usize,
1497 pub points: usize,
1499}
1500
1501#[derive(Default)]
1504struct Tracker<I: TrackImage> {
1505 prev_pyramid: Vec<I>,
1506 next_pyramid: Vec<I>,
1507 prev_gradients: Gradients<I::Gradient>,
1509 next_gradients: Gradients<I::Gradient>,
1512 next_levels: Option<usize>,
1516 scratch: Scratch,
1517 results: Vec<TrackResult>,
1518 forward_pos: Vec<(f32, f32)>,
1519 backward: Vec<TrackResult>,
1520}
1521
1522impl<P> Tracker<ImageBuffer<P, Vec<P::Subpixel>>>
1523where
1524 P: Pixel,
1525 P::Subpixel: Default,
1526 ImageBuffer<P, Vec<P::Subpixel>>: TrackImage,
1527{
1528 fn reserve(&mut self, capacity: &TrackerCapacity) {
1530 let TrackerCapacity {
1531 width,
1532 height,
1533 levels,
1534 points,
1535 ..
1536 } = *capacity;
1537 reserve_pyramid(&mut self.prev_pyramid, width, height, levels);
1538 let base = self.next_pyramid.first().map(|level| level.dimensions());
1539 reserve_pyramid(&mut self.next_pyramid, width, height, levels);
1540 if self.next_pyramid.first().map(|level| level.dimensions()) != base {
1541 self.next_levels = None;
1543 }
1544 for gradients in [&mut self.prev_gradients, &mut self.next_gradients] {
1547 gradients.reserve(width, height, levels);
1548 gradients.invalidate();
1549 }
1550 self.scratch.reserve(capacity);
1551 reserve_total(&mut self.results, points);
1552 reserve_total(&mut self.forward_pos, points);
1553 reserve_total(&mut self.backward, points);
1554 }
1555}
1556
1557impl<I: TrackImage> Tracker<I> {
1558 fn built_both(&mut self) {
1560 self.prev_gradients.invalidate();
1561 self.next_gradients.invalidate();
1562 self.built_next();
1563 }
1564
1565 fn built_next(&mut self) {
1567 self.next_gradients.invalidate();
1568 self.next_levels = Some(self.next_pyramid.len());
1569 }
1570
1571 fn advance(
1575 &mut self,
1576 dimensions: (u32, u32),
1577 levels: usize,
1578 build: impl Fn(&mut Vec<I>),
1579 rebuild: impl FnOnce(&I, &mut Vec<I>),
1580 ) {
1581 let last_frame = self
1583 .next_levels
1584 .filter(|_| self.next_pyramid.first().map(I::dimensions) == Some(dimensions));
1585 match last_frame {
1586 Some(built)
1587 if built == self.next_pyramid.len()
1588 && built == pyramid_level_count(dimensions.0, dimensions.1, levels) =>
1589 {
1590 std::mem::swap(&mut self.prev_pyramid, &mut self.next_pyramid);
1592 std::mem::swap(&mut self.prev_gradients, &mut self.next_gradients);
1593 }
1594 Some(_) => {
1595 rebuild(&self.next_pyramid[0], &mut self.prev_pyramid);
1596 self.prev_gradients.invalidate();
1597 }
1598 None => {
1599 build(&mut self.prev_pyramid);
1600 self.prev_gradients.invalidate();
1601 }
1602 }
1603 build(&mut self.next_pyramid);
1604 self.built_next();
1605 }
1606
1607 fn track(
1608 &mut self,
1609 prev_points: &[(f32, f32)],
1610 predicted: Option<&[(f32, f32)]>,
1611 params: TrackParams,
1612 options: TrackOptions,
1613 ) -> &[TrackResult] {
1614 track_into(
1615 &self.prev_pyramid,
1616 &mut self.prev_gradients,
1617 &self.next_pyramid,
1618 prev_points,
1619 predicted,
1620 params,
1621 options.start_level,
1622 I::select(options.kernel, params.window_size),
1623 &mut self.scratch,
1624 &mut self.results,
1625 );
1626 &self.results
1627 }
1628
1629 fn track_fb(
1630 &mut self,
1631 prev_points: &[(f32, f32)],
1632 predicted: Option<&[(f32, f32)]>,
1633 params: TrackParams,
1634 fb_threshold: f32,
1635 options: TrackOptions,
1636 ) -> &[TrackResult] {
1637 let backward_start = options.fb_levels.map(|levels| {
1638 assert!(levels >= 1, "fb_levels must be at least 1");
1639 levels - 1
1640 });
1641 let kernel = I::select(options.kernel, params.window_size);
1642 track_into(
1643 &self.prev_pyramid,
1644 &mut self.prev_gradients,
1645 &self.next_pyramid,
1646 prev_points,
1647 predicted,
1648 params,
1649 options.start_level,
1650 kernel,
1651 &mut self.scratch,
1652 &mut self.results,
1653 );
1654
1655 self.forward_pos.clear();
1656 self.forward_pos.extend(self.results.iter().map(|r| r.pos));
1657
1658 track_into(
1660 &self.next_pyramid,
1661 &mut self.next_gradients,
1662 &self.prev_pyramid,
1663 &self.forward_pos,
1664 Some(prev_points),
1665 params,
1666 backward_start,
1667 kernel,
1668 &mut self.scratch,
1669 &mut self.backward,
1670 );
1671
1672 mark_fb_inconsistent(&mut self.results, &self.backward, prev_points, fb_threshold);
1673 &self.results
1674 }
1675}
1676
1677#[derive(Default)]
1694pub struct TrackerContext {
1695 tracker: Tracker<GrayImage>,
1696}
1697
1698impl TrackerContext {
1699 pub fn new() -> Self {
1703 Self::default()
1704 }
1705
1706 pub fn with_capacity(capacity: TrackerCapacity) -> Self {
1708 let mut context = Self::default();
1709 context.reserve(capacity);
1710 context
1711 }
1712
1713 pub fn reserve(&mut self, capacity: TrackerCapacity) {
1716 self.tracker.reserve(&capacity);
1717 }
1718
1719 pub fn prepare(&mut self, prev: &GrayImage, next: &GrayImage, levels: usize) {
1725 assert_eq!(
1726 prev.dimensions(),
1727 next.dimensions(),
1728 "frames must have the same size"
1729 );
1730 build_pyramid_into(prev, levels, &mut self.tracker.prev_pyramid);
1731 build_pyramid_into(next, levels, &mut self.tracker.next_pyramid);
1732 self.tracker.built_both();
1733 }
1734
1735 pub fn advance(&mut self, frame: &GrayImage, levels: usize) {
1776 self.tracker.advance(
1777 frame.dimensions(),
1778 levels,
1779 |pyramid| build_pyramid_into(frame, levels, pyramid),
1780 |base, pyramid| build_pyramid_into(base, levels, pyramid),
1781 );
1782 }
1783
1784 pub fn prev_pyramid(&self) -> &[GrayImage] {
1787 &self.tracker.prev_pyramid
1788 }
1789
1790 pub fn next_pyramid(&self) -> &[GrayImage] {
1793 &self.tracker.next_pyramid
1794 }
1795
1796 pub fn track(
1804 &mut self,
1805 prev_points: &[(f32, f32)],
1806 predicted: Option<&[(f32, f32)]>,
1807 params: TrackParams,
1808 ) -> &[TrackResult] {
1809 self.track_with(prev_points, predicted, params, TrackOptions::new())
1810 }
1811
1812 pub fn track_with(
1815 &mut self,
1816 prev_points: &[(f32, f32)],
1817 predicted: Option<&[(f32, f32)]>,
1818 params: TrackParams,
1819 options: TrackOptions,
1820 ) -> &[TrackResult] {
1821 self.tracker.track(prev_points, predicted, params, options)
1822 }
1823
1824 pub fn track_fb(
1828 &mut self,
1829 prev_points: &[(f32, f32)],
1830 predicted: Option<&[(f32, f32)]>,
1831 params: TrackParams,
1832 fb_threshold: f32,
1833 ) -> &[TrackResult] {
1834 self.track_fb_with(
1835 prev_points,
1836 predicted,
1837 params,
1838 fb_threshold,
1839 TrackOptions::new(),
1840 )
1841 }
1842
1843 pub fn track_fb_with(
1866 &mut self,
1867 prev_points: &[(f32, f32)],
1868 predicted: Option<&[(f32, f32)]>,
1869 params: TrackParams,
1870 fb_threshold: f32,
1871 options: TrackOptions,
1872 ) -> &[TrackResult] {
1873 self.tracker
1874 .track_fb(prev_points, predicted, params, fb_threshold, options)
1875 }
1876}
1877
1878#[derive(Default)]
1905pub struct FloatTrackerContext {
1906 tracker: Tracker<FloatPyramidLevel>,
1907}
1908
1909impl FloatTrackerContext {
1910 pub fn new() -> Self {
1913 Self::default()
1914 }
1915
1916 pub fn with_capacity(capacity: TrackerCapacity) -> Self {
1918 let mut context = Self::default();
1919 context.reserve(capacity);
1920 context
1921 }
1922
1923 pub fn reserve(&mut self, capacity: TrackerCapacity) {
1926 self.tracker.reserve(&capacity);
1927 }
1928
1929 pub fn prepare(&mut self, prev: FloatImageView<'_>, next: FloatImageView<'_>, levels: usize) {
1935 assert_eq!(
1936 prev.dimensions(),
1937 next.dimensions(),
1938 "frames must have the same size"
1939 );
1940 build_float_pyramid_into(prev, levels, &mut self.tracker.prev_pyramid);
1941 build_float_pyramid_into(next, levels, &mut self.tracker.next_pyramid);
1942 self.tracker.built_both();
1943 }
1944
1945 pub fn advance(&mut self, frame: FloatImageView<'_>, levels: usize) {
1953 self.tracker.advance(
1954 frame.dimensions(),
1955 levels,
1956 |pyramid| build_float_pyramid_into(frame, levels, pyramid),
1957 |base, pyramid| {
1958 let (width, height) = base.dimensions();
1959 let base = FloatImageView::new(base.as_raw(), width, height)
1960 .expect("the base level is a copy of a validated frame");
1961 build_float_pyramid_into(base, levels, pyramid);
1962 },
1963 );
1964 }
1965
1966 pub fn prev_pyramid(&self) -> &[FloatPyramidLevel] {
1969 &self.tracker.prev_pyramid
1970 }
1971
1972 pub fn next_pyramid(&self) -> &[FloatPyramidLevel] {
1975 &self.tracker.next_pyramid
1976 }
1977
1978 pub fn track(
1987 &mut self,
1988 prev_points: &[(f32, f32)],
1989 predicted: Option<&[(f32, f32)]>,
1990 params: TrackParams,
1991 ) -> &[TrackResult] {
1992 self.track_with(prev_points, predicted, params, TrackOptions::new())
1993 }
1994
1995 pub fn track_with(
1998 &mut self,
1999 prev_points: &[(f32, f32)],
2000 predicted: Option<&[(f32, f32)]>,
2001 params: TrackParams,
2002 options: TrackOptions,
2003 ) -> &[TrackResult] {
2004 self.tracker.track(prev_points, predicted, params, options)
2005 }
2006
2007 pub fn track_fb(
2013 &mut self,
2014 prev_points: &[(f32, f32)],
2015 predicted: Option<&[(f32, f32)]>,
2016 params: TrackParams,
2017 fb_threshold: f32,
2018 ) -> &[TrackResult] {
2019 self.track_fb_with(
2020 prev_points,
2021 predicted,
2022 params,
2023 fb_threshold,
2024 TrackOptions::new(),
2025 )
2026 }
2027
2028 pub fn track_fb_with(
2034 &mut self,
2035 prev_points: &[(f32, f32)],
2036 predicted: Option<&[(f32, f32)]>,
2037 params: TrackParams,
2038 fb_threshold: f32,
2039 options: TrackOptions,
2040 ) -> &[TrackResult] {
2041 self.tracker
2042 .track_fb(prev_points, predicted, params, fb_threshold, options)
2043 }
2044}
2045
2046#[cfg(test)]
2047mod tests {
2048 use super::*;
2049 use crate::pyramid::build_pyramid;
2050 use image::Luma;
2051
2052 #[test]
2053 fn invert_2x2_returns_inverse_components() {
2054 let (inv00, inv01, inv11) = invert_2x2(4.0, 1.0, 3.0, 1e-6).unwrap();
2055
2056 assert!((inv00 - 3.0 / 11.0).abs() < 1e-6);
2057 assert!((inv01 + 1.0 / 11.0).abs() < 1e-6);
2058 assert!((inv11 - 4.0 / 11.0).abs() < 1e-6);
2059 }
2060
2061 #[test]
2062 fn invert_2x2_rejects_singular_matrix() {
2063 assert!(invert_2x2(1.0, 2.0, 4.0, 1e-6).is_none());
2064 }
2065
2066 fn bilinear(img: &GrayImage, x: f32, y: f32) -> f32 {
2068 let (x0, y0) = (x.floor(), y.floor());
2069 let (fx, fy) = (x - x0, y - y0);
2070 let px = |dx: u32, dy: u32| img.get_pixel(x0 as u32 + dx, y0 as u32 + dy)[0] as f32;
2071 px(0, 0) * (1.0 - fx) * (1.0 - fy)
2072 + px(1, 0) * fx * (1.0 - fy)
2073 + px(0, 1) * (1.0 - fx) * fy
2074 + px(1, 1) * fx * fy
2075 }
2076
2077 #[test]
2078 fn window_sampling_matches_per_pixel_bilinear_reference() {
2079 let img = GrayImage::from_fn(40, 30, |x, y| Luma([((x * 37 + y * 11) ^ (x * y)) as u8]));
2080 let mut grad_x = vec![0i16; 40 * 30];
2081 let mut grad_y = vec![0i16; 40 * 30];
2082 compute_gradients_into(&img, &mut grad_x, &mut grad_y);
2083
2084 for &size in &[1usize, 5, 7, 11, 21] {
2085 let radius = size / 2;
2086 let lv = Level::<GrayImage> {
2087 prev: img.as_raw(),
2088 curr: img.as_raw(),
2089 grad_x: &grad_x,
2090 grad_y: &grad_y,
2091 width: 40,
2092 height: 30,
2093 scale: 1.0,
2094 is_finest: true,
2095 window_size: size,
2096 radius,
2097 max_iterations: 1,
2098 min_eigen_threshold: 0.0,
2099 };
2100 let (cx, cy) = (17.3f32, 14.6f32);
2101 let win = Window::new(cx, cy, radius, 40, 30).unwrap();
2102 let mut patch = vec![0f32; size * size];
2103 let mut ix = vec![0f32; size * size];
2104 let mut iy = vec![0f32; size * size];
2105 build_patch(&lv, &win, &mut patch, &mut ix, &mut iy);
2106
2107 for j in 0..size {
2108 for i in 0..size {
2109 let (sx, sy) = (
2110 cx + (i as f32 - radius as f32),
2111 cy + (j as f32 - radius as f32),
2112 );
2113 let expected = bilinear(&img, sx, sy);
2114 let got = patch[j * size + i];
2115 assert!(
2116 (got - expected).abs() < 1e-3,
2117 "size {size} at ({i},{j}): {got} vs {expected}"
2118 );
2119 }
2120 }
2121 assert!(window_error(&lv, &win, &patch) < 1e-4);
2124 let (bx, by) = lk_step(&lv, &win, &patch, &ix, &iy);
2125 assert!(
2126 bx.abs() < 1e-2 && by.abs() < 1e-2,
2127 "size {size}: ({bx}, {by})"
2128 );
2129 }
2130 }
2131
2132 fn fixed_kernels() -> Vec<FixedKernel> {
2134 #[allow(unused_mut)]
2135 let mut kernels = vec![FixedKernel::Scalar, FixedKernel::BASELINE];
2136 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
2137 if let Some(avx2) = fixed::Avx2::detect() {
2138 kernels.push(FixedKernel::Avx2(avx2));
2139 }
2140 kernels
2141 }
2142
2143 fn texture(w: u32, h: u32, (sx, sy): (f32, f32)) -> GrayImage {
2145 let noise = |x: i32, y: i32| {
2146 let mut s = (y.rem_euclid(1024) as u32 * 1024 + x.rem_euclid(1024) as u32)
2147 .wrapping_mul(2654435761);
2148 s ^= s >> 15;
2149 s = s.wrapping_mul(0x85ebca6b);
2150 (s >> 24) as f32
2151 };
2152 let smooth = |x: f32, y: f32| {
2153 let (x0, y0) = (x.floor(), y.floor());
2154 let (fx, fy) = (x - x0, y - y0);
2155 let mut total = 0.0;
2156 for dy in -2..=3 {
2157 for dx in -2..=3 {
2158 let wx = if dx == -2 {
2159 1.0 - fx
2160 } else if dx == 3 {
2161 fx
2162 } else {
2163 1.0
2164 };
2165 let wy = if dy == -2 {
2166 1.0 - fy
2167 } else if dy == 3 {
2168 fy
2169 } else {
2170 1.0
2171 };
2172 total += wx * wy * noise(x0 as i32 + dx, y0 as i32 + dy);
2173 }
2174 }
2175 total / 25.0
2176 };
2177 GrayImage::from_fn(w, h, |x, y| {
2178 Luma([smooth(x as f32 - sx, y as f32 - sy) as u8])
2179 })
2180 }
2181
2182 #[test]
2183 fn fixed_point_kernels_track_identically() {
2184 for (w, h, shift) in [(97u32, 71u32, (1.7f32, -0.6f32)), (160, 120, (4.25, 3.5))] {
2185 let prev = build_pyramid(&texture(w, h, (0.0, 0.0)), 3);
2186 let next = build_pyramid(&texture(w, h, shift), 3);
2187 let points: Vec<(f32, f32)> = (0..90)
2190 .map(|i| {
2191 (
2192 1.5 + (i % 10) as f32 * w as f32 / 9.6,
2193 2.0 + (i / 10) as f32 * h as f32 / 8.7,
2194 )
2195 })
2196 .collect();
2197 let predicted: Vec<(f32, f32)> = points
2198 .iter()
2199 .map(|&(x, y)| (x + shift.0, y + shift.1))
2200 .collect();
2201 for window_size in [1, 7, 13, 21, 31] {
2202 let params = TrackParams {
2203 window_size,
2204 max_iterations: 20,
2205 min_eigen_threshold: DEFAULT_MIN_EIGEN_THRESHOLD,
2206 };
2207 for guess in [None, Some(predicted.as_slice())] {
2208 let run = |kernel| {
2209 let mut out = Vec::new();
2210 track_into(
2211 &prev,
2212 &mut Gradients::default(),
2213 &next,
2214 &points,
2215 guess,
2216 params,
2217 None,
2218 ByteKernel::Fixed(kernel),
2219 &mut Scratch::default(),
2220 &mut out,
2221 );
2222 out.iter()
2223 .map(|r| {
2224 (
2225 r.pos.0.to_bits(),
2226 r.pos.1.to_bits(),
2227 r.status,
2228 r.error.to_bits(),
2229 )
2230 })
2231 .collect::<Vec<_>>()
2232 };
2233 let kernels = fixed_kernels();
2234 let scalar = run(kernels[0]);
2235 let tracked = scalar
2236 .iter()
2237 .filter(|r| r.2 == TrackStatus::Tracked)
2238 .count();
2239 assert!(window_size == 1 || tracked > 20, "{tracked} tracked");
2240 for &kernel in &kernels[1..] {
2241 assert_eq!(
2242 run(kernel),
2243 scalar,
2244 "{kernel:?}, {w}x{h}, window {window_size}"
2245 );
2246 }
2247 }
2248 }
2249 }
2250 }
2251
2252 #[test]
2253 fn window_rejects_footprints_touching_the_border() {
2254 assert!(Window::new(10.0, 10.0, 10, 40, 40).is_some());
2255 assert!(Window::new(9.9, 10.0, 10, 40, 40).is_none());
2256 assert!(Window::new(28.9, 10.0, 10, 40, 40).is_some());
2257 assert!(Window::new(29.0, 10.0, 10, 40, 40).is_none());
2258 assert!(Window::new(f32::NAN, 10.0, 10, 40, 40).is_none());
2259 }
2260}