101*_*101 5 c# xamarin xamarin.forms skiasharp
当我从 GPS 传感器读取数据时,有轻微的延迟。你不会得到像 0,1 0,2 0,3 0,4 0,5 等这样的值,但它们会像 1 一样突然变成 5 或 9 或 12。在这种情况下,针会来回跳动。有人知道如何使针顺利移动吗?我想需要某种延迟?
类似的东西,取自另一个控件:
async void animateProgress(int progress)
{
sweepAngle = 1;
// Looping at data interval of 5
for (int i = 0; i < progress; i=i+5)
{
sweepAngle = i;
await Task.Delay(3);
}
}
Run Code Online (Sandbox Code Playgroud)
但是我有点困惑如何实现它。
以下是在画布上绘制针的代码:
private void OnDrawNeedle()
{
using (var needlePath = new SKPath())
{
//first set up needle pointing towards 0 degrees (or 6 o'clock)
var widthOffset = ScaleToSize(NeedleWidth / 2.0f);
var needleOffset = ScaleToSize(NeedleOffset);
var needleStart = _center.Y - needleOffset;
var needleLength = ScaleToSize(NeedleLength);
needlePath.MoveTo(_center.X - widthOffset, needleStart);
needlePath.LineTo(_center.X + widthOffset, needleStart);
needlePath.LineTo(_center.X, needleStart + needleLength);
needlePath.LineTo(_center.X - widthOffset, needleStart);
needlePath.Close();
//then calculate needle position in degrees
var needlePosition = StartAngle + ((Value - RangeStart) / (RangeEnd - RangeStart) * SweepAngle);
//finally rotate needle to actual value
needlePath.Transform(SKMatrix.CreateRotationDegrees(needlePosition, _center.X, _center.Y));
using (var needlePaint = new SKPaint())
{
needlePaint.IsAntialias = true;
needlePaint.Color = NeedleColor.ToSKColor();
needlePaint.Style = SKPaintStyle.Fill;
_canvas.DrawPath(needlePath, needlePaint);
}
}
}
Run Code Online (Sandbox Code Playgroud)
编辑:
仍然很难理解这个过程。
假设我不想应用此过滤器进行控制,而是将其放在 ViewModel 中来过滤值。我有一个从中获取数据的类,例如 GPSTracker。GPSTracker 提供速度值,然后我在 HomeViewModel 中订阅 EventListener 并想要过滤传入值。
基于亚当斯的回答:
来自控制背景,为了模仿模拟设备的行为,您可以使用指数(也称为低通)滤波器。
您可以使用两种类型的低通滤波器,具体取决于您想要看到的行为类型:一阶滤波器或二阶滤波器。简而言之,如果您的读数稳定在 0,然后突然变为 10,并稳定在 10(阶跃变化),则第一个订单将慢慢变为 10,永远不会超过它,然后保持在 10,而第二个订单订单会加速其进度向 10 前进,超过它,然后向 10 振荡。
指数滤波器的函数很简单:
public void Exp_Filt(ref double filtered_value, double source_value, double time_passed, double time_constant)
{
if (time_passed > 0.0)
{
if (time_constant > 0.0)
{
source_value += (filtered_value - source_value) * Math.Exp(-time_passed / time_constant);
}
filtered_value = source_value;
}
}
Run Code Online (Sandbox Code Playgroud)
filtered_value是源的过滤版本source_value,time_passed是从上次调用此函数到过滤器经过的时间filtered_value,time_constant是过滤器的时间常数(仅供参考,对阶跃变化做出反应,filtered_value将获得63%的时间source_value)time_constant时间已过,5x 已过时为 99%)。的单位filtered_value将与 相同source_value。time_passed和的单位time_constant必须相同,无论是秒、微秒还是 jiffy。此外,time_passed应明显小于time_constant任何时候,否则过滤器行为将变得不确定。有多种方法可以获取time_passed,例如Stopwatch,请参阅如何计算已经过去了多少时间?
在使用过滤器函数之前,您需要初始化filtered_value以及用于获取 的任何内容time_passed。对于这个例子,我将使用stopwatch.
var stopwatch = new System.Diagnostics.Stopwatch();
double filtered_value, filtered_dot_value;
...
filtered_value = source_value;
filtered_dot_value = 0.0;
stopwatch.Start();
Run Code Online (Sandbox Code Playgroud)
要将此函数用于一阶滤波器,您可以使用计时器或类似的东西循环以下内容
double time_passed = stopwatch.ElapsedMilliseconds;
stopwatch.Restart();
Exp_Filt(ref filtered_value, source_value, time_passed, time_constant);
Run Code Online (Sandbox Code Playgroud)
要将此函数用于二阶滤波器,您可以使用计时器或类似的东西循环以下内容
double time_passed = stopwatch.ElapsedMilliseconds;
stopwatch.Restart();
if (time_passed > 0.0)
{
double last_value = filtered_value;
filtered_value += filtered_dot_value * time_passed;
Exp_Filt(ref filtered_value, source_value, time_passed, time_constant);
Exp_Filt(ref filtered_dot_value, (filtered_value - last_value) / time_passed, time_passed, dot_time_constant);
}
Run Code Online (Sandbox Code Playgroud)
二阶滤波器的工作原理是考虑一阶滤波值的一阶导数。另外,我建议制作time_constant < dot_time_constant- 首先,我会设置dot_time_constant = 2 * time_constant
就个人而言,我会在由线程计时器控制的后台线程中调用此过滤器,并有time_passed一个等于计时器周期的常量,但我将把实现细节留给您。
编辑:
下面是创建一阶和二阶滤波器的示例类。为了操作过滤器,我使用了一个线程计时器,设置为每 100 毫秒处理一次。由于该计时器相当一致,使得 time_passed 恒定,因此我通过预先计算Math.Exp(-time_passed / time_constant)而不是将“点”项除以/乘以 time_passed 来优化滤波器方程。
对于一阶滤波器,使用var filter = new ExpFilter(initial_value, time_constant). 对于二阶滤波器,使用var filter = new ExpFilter(initial_value, time_constant, dot_time_constant)。然后,要读取最新的过滤值,请调用double value = filter.Value. 要设置要过滤的值,请调用filter.Value = value。
public class ExpFilter : IDisposable
{
private double _input, _output, _dot;
private readonly double _tc, _tc_dot;
private System.Threading.Timer _timer;
/// <summary>
/// Initializes first-order filter
/// </summary>
/// <param name="value">initial value of filter</param>
/// <param name="time_constant">time constant of filter, in seconds</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="time_constant"/> must be positive</exception>
public ExpFilter(double value, double time_constant)
{
// time constant must be positive
if (time_constant <= 0.0) throw new ArgumentOutOfRangeException(nameof(time_constant));
// initialize filter
_output = _input = value;
_dot = 0.0;
// calculate gain from time constant
_tc = CalcTC(time_constant);
// disable second-order
_tc_dot = -1.0;
// start filter timer
StartTimer();
}
/// <summary>
/// Initializes second-order filter
/// </summary>
/// <param name="value">initial value of filter</param>
/// <param name="time_constant">time constant of primary filter, in seconds</param>
/// <param name="dot_time_constant">time constant of secondary filter, in seconds</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="time_constant"/> and <paramref name="dot_time_constant"/> must be positive</exception>
public ExpFilter(double value, double time_constant, double dot_time_constant)
{
// time constant must be positive
if (time_constant <= 0.0) throw new ArgumentOutOfRangeException(nameof(time_constant));
if (dot_time_constant <= 0.0) throw new ArgumentOutOfRangeException(nameof(dot_time_constant));
// initialize filter
_output = _input = value;
_dot = 0.0;
// calculate gains from time constants
_tc = CalcTC(time_constant);
_tc_dot = CalcTC(dot_time_constant);
// start filter timer
StartTimer();
}
// the following two functions must share the same time period
private double CalcTC(double time_constant)
{
// time period = 0.1 s (100 ms)
return Math.Exp(-0.1 / time_constant);
}
private void StartTimer()
{
// time period = 100 ms
_timer = new System.Threading.Timer(Filter_Timer, this, 100, 100);
}
~ExpFilter()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_timer.Dispose();
}
}
/// <summary>
/// Get/Set filter value
/// </summary>
public double Value
{
get => _output;
set => _input = value;
}
private static void Filter_Timer(object stateInfo)
{
var _filter = (ExpFilter)stateInfo;
// get values
double _input = _filter._input;
double _output = _filter._output;
double _dot = _filter._dot;
// if second-order, adjust _output (no change if first-order as _dot = 0)
// then use filter function to calculate new filter value
_input += (_output + _dot - _input) * _filter._tc;
_filter._output = _input;
if (_filter._tc_dot >= 0.0)
{
// calculate second-order portion of filter
_output = _input - _output;
_output += (_dot - _output) * _filter._tc_dot;
_filter._dot = _output;
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
306 次 |
| 最近记录: |