namespace OpenCvSharp.Demo { using System; using System.Collections.Generic; using OpenCvSharp; /// /// Data stabilizer general parameters /// public class DataStabilizerParams { /// /// Should this stabilizer just push data through (false value) or do some work before (true value)? /// public bool Enabled { get; set; } /// /// Maximum ignored point distance /// public double Threshold { get; set; } /// /// Threshold scale factor (should processing space be scaled) /// public double ThresholdFactor { get; set; } /// /// Accumulated samples count /// public int SamplesCount { get; set; } /// /// Returns scaled threshold /// /// public double GetScaledThreshold() { return Threshold * ThresholdFactor; } /// /// Default constructor /// public DataStabilizerParams() { Enabled = true; Threshold = 1.0; ThresholdFactor = 1.0; SamplesCount = 10; } } /// /// Base DataStabilizer generic interface /// /// Any data type interface IDataStabilizer { /// /// Parameters, see corresponding class /// DataStabilizerParams Params { get; set; } /// /// Stabilized data /// T Sample { get; set; } /// /// Signals whether last data chunk changed anything /// bool LastApplied { get; set; } } /// /// Basic data stabilizer abstract implementation, intended to be used on compact data sets /// abstract class DataStabilizerBase { protected T result; // computer output sample protected bool dirty = true; // flag signals whether "result" sample must be recomputed protected T[] samples = null; // whole samples set protected long inputSamples = 0; // processed samples count /// /// Parameters, see corresponding class /// public DataStabilizerParams Params { get; set; } /// /// Stabilized data /// public virtual T Sample { get { // requires update if (dirty) { // samples count changed if (samples.Length != Params.SamplesCount) { T[] data = new T[Params.SamplesCount]; Array.Copy(samples, data, Math.Min(samples.Length, Params.SamplesCount)); samples = data; // drop result result = DefaultValue(); } // prepare to compute LastApplied = true; // process samples if (Params.Enabled) LastApplied = PrepareStabilizedSample(); // stabilizer is disabled - simply grab the fresh-most sample else result = samples[0]; dirty = false; } return result; } set { ValidateSample(value); // shift and push new value to the top T[] data = new T[Params.SamplesCount]; Array.Copy(samples, 0, data, 1, Params.SamplesCount - 1); data[0] = value; samples = data; inputSamples++; // mark dirty = true; } } /// /// Signals whether last data chunk changed anything /// public bool LastApplied { get; private set; } /// /// Constructs base data stabilizer /// protected DataStabilizerBase(DataStabilizerParams parameters) { Params = parameters; samples = new T[Params.SamplesCount]; result = DefaultValue(); } /// /// Computes stabilized data sample /// /// True if data has been recomputed, false if nothing changed for returned sample protected abstract bool PrepareStabilizedSample(); /// /// Computes average data sample /// /// protected abstract T ComputeAverageSample(); /// /// Tests sample validity, must throw on unexpected value /// /// Sample to test protected abstract void ValidateSample(T sample); /// /// Gets default value for the output sample /// /// protected abstract T DefaultValue(); } /// /// On top of various OpenCV stabilizers for video itself (like optical flow) we might /// need a simpler one "stabilizer" for some data reacquired each frame like face rects, /// face landmarks etc. /// /// This class is designed to be fast, so it basically applies some threshold and simplest heuristics /// to decide whether to update it's data set with new data chunk /// class PointsDataStabilizer : DataStabilizerBase { /// /// Flag signaling whether data set is interpreted as whole (Triangle, Rectangle) or independent (independent points array) /// public bool PerPointProcessing { get; set; } /// /// Creates DataStabilizer instance /// /// Stabilizer general parameters public PointsDataStabilizer(DataStabilizerParams parameters) : base(parameters) { PerPointProcessing = true; } /// /// Validate sample /// /// protected override void ValidateSample(Point[] sample) { if (null == sample || sample.Length == 0) throw new ArgumentException("sample: is null or empty array."); foreach (Point[] data in samples) { if (data != null && data.Length != sample.Length) throw new ArgumentException("sample: invalid input data, length does not match."); } } /// /// Computes average data sample /// /// protected override Point[] ComputeAverageSample() { // we need full stack to run if (inputSamples < Params.SamplesCount) return null; // accumulate average int sampleSize = samples[0].Length; Point[] average = new Point[sampleSize]; for (int s = 0; s < Params.SamplesCount; ++s) { Point[] data = samples[s]; for (int i = 0; i < sampleSize; ++i) average[i] += data[i]; } // normalize double inv = 1.0 / Params.SamplesCount; for (int i = 0; i < sampleSize; ++i) average[i] = new Point(average[i].X * inv + 0.5, average[i].Y * inv); return average; } /// /// Computes data sample /// /// True if final sample changed, false if current frame is the same as the last one protected override bool PrepareStabilizedSample() { // get average Point[] average = ComputeAverageSample(); if (null == average) return false; // if we have no saved result at all - average will do if (DefaultValue() == result) { result = average; return true; } // we have new average and saved data as well - test it double dmin = double.MaxValue, dmax = double.MinValue, dmean = 0.0; double[] distance = new double[result.Length]; for (int i = 0; i < result.Length; ++i) { double d = Point.Distance(result[i], average[i]); dmean += d; dmax = Math.Max(dmax, d); dmin = Math.Min(dmin, d); distance[i] = d; } dmean /= result.Length; // check whether it's OK to apply double edge = Params.Threshold; if (dmean > edge) { result = average; return true; } // per-item process bool anyChanges = false; if (PerPointProcessing) { for (int i = 0; i < result.Length; ++i) { if (distance[i] > edge) { anyChanges = true; result[i] = average[i]; } } } return anyChanges; } /// /// Gets default value for the output sample /// /// protected override Point[] DefaultValue() { return null; } } /// /// Data stabilizer designed for OpenCv Rect (Object tracking, face detection etc.) /// class RectStabilizer : DataStabilizerBase { /// /// Constructs Rectangle stabilizer /// /// Data stabilizer general parameters public RectStabilizer(DataStabilizerParams parameters) : base(parameters) {} /// /// Computes average data sample /// /// protected override Rect ComputeAverageSample() { Rect average = new Rect(); if (inputSamples < Params.SamplesCount) return average; foreach (Rect rc in samples) average = average + rc; return average * (1.0 / Params.SamplesCount); } /// /// For Rect stabilizer any sample is valid /// /// Sample to test protected override void ValidateSample(Rect sample) {} /// /// Prepares stabilized sample (Rectangle) /// protected override bool PrepareStabilizedSample() { Rect average = ComputeAverageSample(); // quick check if (DefaultValue() == result) { result = average; return true; } // compute per-corner distance between the frame we have and new one double dmin = double.MaxValue, dmax = double.MinValue, dmean = 0.0; Point[] our = result.ToArray(), their = average.ToArray(); for (int i = 0; i < 4; ++i) { double distance = Point.Distance(our[i], their[i]); dmin = Math.Min(distance, dmin); dmax = Math.Max(distance, dmax); dmean += distance; } dmean /= their.Length; // apply conditions if (dmin > Params.GetScaledThreshold()) { result = average; return true; } return false; } /// /// Gets default value for the output sample /// /// protected override Rect DefaultValue() { return new Rect(); } } }