using System; using System.Collections.Generic; using UnityEngine; namespace TensorFlowLite { /// /// RelativeVelocityFilter from MediaPipe /// mediapipe/graphs/pose_tracking/calculators/relative_velocity_filter.h /// /// This filter keeps track (on a window of specified size) of /// value changes over time, which as result gives us velocity of how value /// changes over time. With higher velocity it weights new values higher. /// /// Use @window_size and @velocity_scale to tweak this filter for your use case. /// /// - higher @window_size adds to lag and to stability /// - lower @velocity_scale adds to lag and to stability /// public class RelativeVelocityFilter { public enum DistanceEstimationMode { // When the value scale changes, uses a heuristic // that is not translation invariant (see the implementation for details). LegacyTransition, // The current (i.e. last) value scale is always used for scale estimation. // When using this mode, the filter is translation invariant, i.e. // Filter(Data + Offset) = Filter(Data) + Offset. ForceCurrentScale, } private struct WindowElement { public float distance; public double duration; public WindowElement(float distance, double duration) { this.distance = distance; this.duration = duration; } }; private float lastValue = 0.0f; private float lastValueScale = 1.0f; private double lastTimestamp = -1; private uint maxWindowSize; private Queue windows; private LowPassFilter lowPassFilter; private float velocityScale; private DistanceEstimationMode distanceMode; public float VelocitySacle { get => velocityScale; set => velocityScale = value; } public RelativeVelocityFilter( uint windowSize, float velocityScale, DistanceEstimationMode distanceMode) { maxWindowSize = windowSize; this.velocityScale = velocityScale; this.distanceMode = distanceMode; lowPassFilter = new LowPassFilter() { alpha = 1f, }; windows = new Queue(); } public float Apply(double newTimestamp, float valueScale, float value) { if (lastTimestamp >= newTimestamp) { // Results are unpredictable in this case, so nothing to do but // return same value Debug.LogWarning("New timestamp is equal or less than the last one."); return value; } double alpha; if (lastTimestamp == -1) { alpha = 1.0; } else { float distance = distanceMode == DistanceEstimationMode.LegacyTransition ? value * valueScale - lastValue * lastValueScale // Original. : valueScale * (value - lastValue); // Translation invariant. double duration = newTimestamp - lastTimestamp; float cumulative_distance = distance; double cumulative_duration = duration; // Define max cumulative duration assuming // 30 frames per second is a good frame rate, so assuming 30 values // per second or 1 / 30 of a second is a good duration per window element const double kAssumedMaxDuration = 1.0 / 30.0; double max_cumulative_duration = (1 + windows.Count) * kAssumedMaxDuration; foreach (var windows in windows) { if (cumulative_duration + windows.duration > max_cumulative_duration) { // This helps in cases when durations are large and outdated // window elements have bad impact on filtering results break; } cumulative_distance += windows.distance; cumulative_duration += windows.duration; } double velocity = cumulative_distance / cumulative_duration; alpha = 1.0 - 1.0 / (1.0 + velocityScale * Math.Abs(velocity)); windows.Enqueue(new WindowElement(distance, duration)); if (windows.Count > maxWindowSize) { windows.Dequeue(); } } lastValue = value; lastValueScale = valueScale; lastTimestamp = newTimestamp; // Debug.Log($"alpha: {alpha}"); return lowPassFilter.Apply(value, (float)alpha); } } public class RelativeVelocityFilter2D { private RelativeVelocityFilter x; private RelativeVelocityFilter y; public Vector2 VelocityScale { get { return new Vector2( x.VelocitySacle, y.VelocitySacle); } set { x.VelocitySacle = value.x; y.VelocitySacle = value.y; } } public RelativeVelocityFilter2D( uint windowSize, float velocityScale, RelativeVelocityFilter.DistanceEstimationMode distanceMode) { x = new RelativeVelocityFilter(windowSize, velocityScale, distanceMode); y = new RelativeVelocityFilter(windowSize, velocityScale, distanceMode); } public Vector2 Apply(double newTimestamp, float valueScale, Vector2 value) { return new Vector2( x.Apply(newTimestamp, valueScale, value.x), y.Apply(newTimestamp, valueScale, value.y) ); } } public class RelativeVelocityFilter3D { private RelativeVelocityFilter x; private RelativeVelocityFilter y; private RelativeVelocityFilter z; public Vector3 VelocityScale { get { return new Vector3( x.VelocitySacle, y.VelocitySacle, z.VelocitySacle); } set { x.VelocitySacle = value.x; y.VelocitySacle = value.y; z.VelocitySacle = value.z; } } public RelativeVelocityFilter3D( uint windowSize, float velocityScale, RelativeVelocityFilter.DistanceEstimationMode distanceMode) { x = new RelativeVelocityFilter(windowSize, velocityScale, distanceMode); y = new RelativeVelocityFilter(windowSize, velocityScale, distanceMode); z = new RelativeVelocityFilter(windowSize, velocityScale, distanceMode); } public Vector3 Apply(double newTimestamp, float valueScale, Vector3 value) { return new Vector3( x.Apply(newTimestamp, valueScale, value.x), y.Apply(newTimestamp, valueScale, value.y), z.Apply(newTimestamp, valueScale, value.z) ); } } }