From ea656552e018ddf613a660de92cf23823dbee927 Mon Sep 17 00:00:00 2001 From: syneffort Date: Thu, 23 May 2024 16:11:31 +0900 Subject: [PATCH] FIR FIlter --- .../GettingStarted/Documentation/FIRFilter.md | 10 ++ .../GettingStarted/Filter/FIRFilter.cs | 88 +++++++++++++++++ .../GettingStarted/GettingStarted.csproj | 4 - OxyPlotExample/GettingStarted/MainWindow.xaml | 22 +++-- .../ViewModels/FIRFilterViewModel.cs | 98 +++++++++++++++++++ .../GettingStarted/Views/FIRFilterView.xaml | 39 ++++++++ .../Views/FIRFilterView.xaml.cs | 28 ++++++ 7 files changed, 277 insertions(+), 12 deletions(-) create mode 100644 OxyPlotExample/GettingStarted/Documentation/FIRFilter.md create mode 100644 OxyPlotExample/GettingStarted/Filter/FIRFilter.cs create mode 100644 OxyPlotExample/GettingStarted/ViewModels/FIRFilterViewModel.cs create mode 100644 OxyPlotExample/GettingStarted/Views/FIRFilterView.xaml create mode 100644 OxyPlotExample/GettingStarted/Views/FIRFilterView.xaml.cs diff --git a/OxyPlotExample/GettingStarted/Documentation/FIRFilter.md b/OxyPlotExample/GettingStarted/Documentation/FIRFilter.md new file mode 100644 index 0000000..07bbdfe --- /dev/null +++ b/OxyPlotExample/GettingStarted/Documentation/FIRFilter.md @@ -0,0 +1,10 @@ +# FIR Low Pass Filter 개요 + +- 샘플링 주파수 ($f_s$: Sampling Frequency) + - 신호를 샘플링 하는 속도 +- 컷오프 주파수 ($f_c$: Cutoff Frequency) + - 필터의 신호를 통과시키거나 차단하기 시작하는 주파수 +- 필터 계수 설계 + - 필터의 특성을 정의하는 계수. 해밍 창(Hamming Window)등을 이용해 설계 +- 페이즈 지연 보상 + - 필터로 인한 지연 보상 diff --git a/OxyPlotExample/GettingStarted/Filter/FIRFilter.cs b/OxyPlotExample/GettingStarted/Filter/FIRFilter.cs new file mode 100644 index 0000000..bd7ef2b --- /dev/null +++ b/OxyPlotExample/GettingStarted/Filter/FIRFilter.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GettingStarted.Filter +{ + internal class FIRFilter + { + private double[] _coefficients; + private double[] _buffer; + private int _bufferIndex; + private int _delay; + + public FIRFilter(double[] coefficients) + { + _coefficients = coefficients; + _buffer = new double[coefficients.Length]; + _bufferIndex = 0; + _delay = (_coefficients.Length - 1) / 2; + } + + public double Process(double input) + { + _buffer[_bufferIndex] = input; + double result = 0d; + + int index = _bufferIndex; + for (int i = 0; i < _coefficients.Length; i++) + { + result += _coefficients[i] * _buffer[index]; + index = (index > 0) ? index - 1 : _buffer.Length - 1; + } + + _bufferIndex = (_bufferIndex + 1) % _buffer.Length; + + return result; + } + + public double[] ProcessSignal(double[] inputSignal) + { + int signalLength = inputSignal.Length; + double[] outputSignal = new double[signalLength]; + + for (int i = 0; i < signalLength; i++) + { + outputSignal[i] = Process(inputSignal[i]); + } + + // 페이즈 지연 보상 + for (int i = 0; i < signalLength; i++) + { + if (i + _delay < signalLength) + { + outputSignal[i] = outputSignal[i + _delay]; + } + else + { + outputSignal[i] = 0; // 지연 보상 후 남은 부분은 0으로 채움 + } + } + + return outputSignal; + } + + public static double[] DesignLowPassFilter(int length, double cutoffFrequnecy, double sampleRate) + { + double[] coefficients = new double[length]; + double omega_c = 2 * Math.PI * cutoffFrequnecy / sampleRate; + int M = length - 1; + + for (int n = 0; n <= M; n++) + { + if (n - M / 2 == 0) + { + coefficients[n] = omega_c / Math.PI; + } + else + { + coefficients[n] = Math.Sin(omega_c * (n - M / 2)) / (Math.PI * (n - M / 2)) * (0.54 - 0.46 * Math.Cos(2 * Math.PI * n / M)); + } + } + + return coefficients; + } + } +} diff --git a/OxyPlotExample/GettingStarted/GettingStarted.csproj b/OxyPlotExample/GettingStarted/GettingStarted.csproj index fd94eef..9e580d5 100644 --- a/OxyPlotExample/GettingStarted/GettingStarted.csproj +++ b/OxyPlotExample/GettingStarted/GettingStarted.csproj @@ -14,8 +14,4 @@ - - - - diff --git a/OxyPlotExample/GettingStarted/MainWindow.xaml b/OxyPlotExample/GettingStarted/MainWindow.xaml index 87c43b9..30f2e34 100644 --- a/OxyPlotExample/GettingStarted/MainWindow.xaml +++ b/OxyPlotExample/GettingStarted/MainWindow.xaml @@ -8,17 +8,23 @@ Title="MainWindow" Width="800" Height="450" - mc:Ignorable="d" - Closing="Window_Closing"> + Closing="Window_Closing" + mc:Ignorable="d"> + + + + - - - + + + - - - + + + + + diff --git a/OxyPlotExample/GettingStarted/ViewModels/FIRFilterViewModel.cs b/OxyPlotExample/GettingStarted/ViewModels/FIRFilterViewModel.cs new file mode 100644 index 0000000..0a6b1e3 --- /dev/null +++ b/OxyPlotExample/GettingStarted/ViewModels/FIRFilterViewModel.cs @@ -0,0 +1,98 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using GettingStarted.Filter; +using OxyPlot; +using OxyPlot.Series; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GettingStarted.ViewModels +{ + internal partial class FIRFilterViewModel : ObservableObject + { + + [ObservableProperty] + private PlotModel _myModel; + + + [ObservableProperty] + private LineSeries _inputSeries; + + + [ObservableProperty] + private LineSeries _outputSeries; + + [ObservableProperty] + private double _sampleRate; + + + [ObservableProperty] + private double _cutoffFrequency; + + + [ObservableProperty] + private int _filterLength; + + double[] _coefficients; + + FIRFilter _filter; + + double[] _inputSignal; + + double _noiseLevel = 0.3; + public FIRFilterViewModel() + { + MyModel = new PlotModel() { Title = "FIR Low Pass Filter" }; + InputSeries = new LineSeries() { Title = "Input" }; + OutputSeries = new LineSeries() { Title = "Output" }; + MyModel.Series.Add(InputSeries); + MyModel.Series.Add(OutputSeries); + + SampleRate = 1000d; + CutoffFrequency = 100d; + FilterLength = 21; + + Random rand = new Random(); + List inputSimulate = new List(); + int x = 0; + for (int i = 0; i < 100; i++) + { + double noise = (rand.NextDouble() - 0.5) * _noiseLevel; + double value = Math.Sin(x++ * 0.1) + noise; + inputSimulate.Add(value); + } + + _inputSignal = inputSimulate.ToArray(); ; + + ProcessSignal(); + } + + [RelayCommand] + private void ProcessSignal() + { + _coefficients = FIRFilter.DesignLowPassFilter(FilterLength, CutoffFrequency, SampleRate); + _filter = new FIRFilter(_coefficients); + + double[] outputSignal = _filter.ProcessSignal(_inputSignal); + + { + InputSeries.Points.Clear(); + int x = 0; + DataPoint[] dataPoints = _inputSignal.Select(val => new DataPoint(++x, val)).ToArray(); + InputSeries.Points.AddRange(dataPoints); + } + + { + OutputSeries.Points.Clear(); + int x = 0; + DataPoint[] dataPoints = outputSignal.Select(val => new DataPoint(++x, val)).ToArray(); + OutputSeries.Points.AddRange(dataPoints); + } + + MyModel.InvalidatePlot(true); + } + } +} diff --git a/OxyPlotExample/GettingStarted/Views/FIRFilterView.xaml b/OxyPlotExample/GettingStarted/Views/FIRFilterView.xaml new file mode 100644 index 0000000..ad842b4 --- /dev/null +++ b/OxyPlotExample/GettingStarted/Views/FIRFilterView.xaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + +