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();
}
}
}