C# -WPF ni:graph 一维信号滤波
1 WPF环境搭建
编译环境为VS2019和.NET Framework4.6.1
下载Measurement Studio2019 和 Telerik控件库并安装破解
控件库百度网盘链接:https://pan.baidu.com/s/1apY7x1cp8gdKAQKp3e_TQw
提取码:f5x3
2 WPF前端XML代码
其中用到NI的graph控件用于显示信号的波形图。
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:VideoCaptureAndSignalFilter"
xmlns:ni="http://schemas.ni.com/controls/2009/xaml/presentation"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" x:Class="VideoCaptureAndSignalFilter.NosieFilter"
mc:Ignorable="d"
Title="传感器信号滤波去噪" Height="530" Width="670" WindowStartupLocation="CenterScreen" ShowInTaskbar="False" ResizeMode="NoResize" Icon="图标/component_video_256.ico">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="230"/>
<RowDefinition/>
<RowDefinition Height="230"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="压力传感器" FontSize="13"/>
<MenuItem Header="湿度传感器" FontSize="13"/>
<MenuItem Header="温度传感器" FontSize="13"/>
<MenuItem Header="水流速传感器" FontSize="13"/>
</Menu>
<Menu Grid.Row="0" Grid.Column="1">
</Menu>
<ni:Graph x:Name="OriginalSignal" Grid.Row="1">
<ni:Graph.Axes>
<ni:AxisDouble x:Name="xScale" Range="0,100" Orientation="Horizontal" MinorDivisions="{x:Null}" Adjuster="ContinuousChart" InteractionMode="None"/>
<ni:AxisDouble x:Name="yScale" Range="0,100" Orientation="Vertical" MinorDivisions="{x:Null}" Adjuster="None" InteractionMode="None">
<ni:AxisDouble.MajorDivisions>
<ni:RangeLabeledDivisions/>
</ni:AxisDouble.MajorDivisions>
<ni:AxisDouble.MajorGridLines>
<ni:GridLines/>
</ni:AxisDouble.MajorGridLines>
</ni:AxisDouble>
</ni:Graph.Axes>
<ni:Graph.Children>
<ni:RangeCursor x:Name="upperLimitRangeCursor"
Label="Range cursor"
CrosshairBrush="#8008"
CrosshairThickness="1"
Cursor="Hand"
SnapToData="False"
FillBrush="#88FF0000"
VerticalRange="70,100"
ValuePresenter="Group:F0"
ValueVisibility="Collapsed"
/>
<ni:RangeCursor x:Name="lowerLimitRangeCursor"
Label="Range cursor"
CrosshairBrush="#8008"
CrosshairThickness="1"
Cursor="Hand"
SnapToData="False"
FillBrush="#88ADD8E6"
VerticalRange="0,30"
ValuePresenter="Group:F0"
ValueVisibility="Collapsed"
/>
</ni:Graph.Children>
</ni:Graph>
<ni:Graph x:Name="FilterSignal" Grid.Row="3">
<ni:Graph.Axes>
<ni:AxisDouble x:Name="xScale1" Range="0,100" Orientation="Horizontal" MinorDivisions="{x:Null}" Adjuster="ContinuousChart" InteractionMode="None"/>
<ni:AxisDouble x:Name="yScale1" Range="0,100" Orientation="Vertical" MinorDivisions="{x:Null}" Adjuster="None" InteractionMode="None">
<ni:AxisDouble.MajorDivisions>
<ni:RangeLabeledDivisions/>
</ni:AxisDouble.MajorDivisions>
<ni:AxisDouble.MajorGridLines>
<ni:GridLines/>
</ni:AxisDouble.MajorGridLines>
</ni:AxisDouble>
</ni:Graph.Axes>
<ni:Graph.Children>
<ni:RangeCursor x:Name="upperLimitRangeCursor1"
Label="Range cursor"
CrosshairBrush="#8008"
CrosshairThickness="1"
Cursor="Hand"
SnapToData="False"
FillBrush="#88FF0000"
VerticalRange="70,100"
ValuePresenter="Group:F0"
ValueVisibility="Collapsed"
/>
<ni:RangeCursor x:Name="lowerLimitRangeCursor1"
Label="Range cursor"
CrosshairBrush="#8008"
CrosshairThickness="1"
Cursor="Hand"
SnapToData="False"
FillBrush="#88ADD8E6"
VerticalRange="0,30"
ValuePresenter="Group:F0"
ValueVisibility="Collapsed"
/>
</ni:Graph.Children>
</ni:Graph>
<GroupBox Grid.Column="1" Grid.Row="1" Header="滤波方式及参数选择">
<Grid Name="spain">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<RadioButton Name="MeanFilter" HorizontalAlignment="Left" VerticalAlignment="Center" Content="均值滤波"/>
<RadioButton Name="GaussianFilter" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Center" Content="高斯滤波"/>
<RadioButton Name="MedianFilter" Grid.Row="2" HorizontalAlignment="Left" VerticalAlignment="Center" Content="中值滤波"/>
<RadioButton Name="KalmanFilter" Grid.Row="3" VerticalAlignment="Center" Content="卡尔曼滤波" HorizontalAlignment="Left" Checked="KalmanFilter_Checked"/>
<Grid Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="滤波核的大小"/>
<ComboBox Name="KernelSize" Grid.Column="1" Margin="10, 5, 5, 5" BorderBrush="Black">
<ComboBox.Background>
<LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFF0F0F0" Offset="0"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</ComboBox.Background>
</ComboBox>
</Grid>
<Grid Grid.Row="5">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="卡尔曼初始误差"/>
<ComboBox Name="KalmanError" Grid.Column="1" Margin="10, 5, 5, 5" BorderBrush="Black">
<ComboBox.Background>
<LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFF0F0F0" Offset="0"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</ComboBox.Background>
</ComboBox>
</Grid>
<Grid Grid.Row="6">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<telerik:RadButton Name="BeginFilter" Content="确认" Margin="10,2" Click="BeginFilter_Click"/>
<telerik:RadButton Name="CancelFilter" Content="取消" Margin="10,2" Grid.Column="1" Click="CancelFilter_Click"/>
</Grid>
</Grid>
</GroupBox>
<GroupBox Grid.Column="1" Grid.Row="3" Header="信号重要信息显示">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<GroupBox Header="原始信号">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="最大值" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Name="OriginalMaximum" Grid.Column="1" Margin="0, 13"/>
<Label Content="最小值" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Name="OriginalMinimum" Grid.Column="3" Margin="0, 13"/>
<Label Content="均值" HorizontalAlignment="Center" Grid.Row="1" VerticalAlignment="Center"/>
<TextBox Name="OriginalMean" Grid.Column="1" Margin="0, 13" Grid.Row="1"/>
<Label Content="标准差" Grid.Column="2" HorizontalAlignment="Center" Grid.Row="1" VerticalAlignment="Center"/>
<TextBox Name="OriginalVariance" Grid.Column="3" Margin="0, 13" Grid.Row="1"/>
</Grid>
</GroupBox>
<GroupBox Header="滤波信号" Grid.Row="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="最大值" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Name="FilterMaximum" Grid.Column="1" Margin="0, 13"/>
<Label Content="最小值" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Name="FilterMinimum" Grid.Column="3" Margin="0, 13"/>
<Label Content="均值" HorizontalAlignment="Center" Grid.Row="1" VerticalAlignment="Center"/>
<TextBox Name="FilterMean" Grid.Column="1" Margin="0, 13" Grid.Row="1"/>
<Label Content="标准差" Grid.Column="2" HorizontalAlignment="Center" Grid.Row="1" VerticalAlignment="Center"/>
<TextBox Name="FilterVarance" Grid.Column="3" Margin="0, 13" Grid.Row="1"/>
</Grid>
</GroupBox>
</Grid>
</GroupBox>
</Grid>
</Window>
3 后端代码实现
using NationalInstruments.Controls;
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Controls;
namespace VideoCaptureAndSignalFilter
{
/// <summary>
/// NosieFilter.xaml 的交互逻辑
/// </summary>
public partial class NosieFilter : Window
{
private ChartCollection<double> OriginalData = new ChartCollection<double>();
private ChartCollection<double> FilterData = new ChartCollection<double>();
private ChartCollection<double> chartCollection = new ChartCollection<double>();
private List<double> CalcMeanArr = new List<double>();
private System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
private int SignalCount = 1;
private double Accumulated_Error = 0.01;
public NosieFilter()
{
InitializeComponent();
ParamInit();
spain.AddHandler(RadioButton.CheckedEvent, new RoutedEventHandler(RadioCheck), true);
}
/// <summary>
/// 控件及其参数初始化
/// </summary>
private void ParamInit()
{
this.MeanFilter.IsChecked = true;
for(int i=1; i<6; i++)
{
this.KernelSize.Items.Add(2*i + 1);
}
this.KernelSize.SelectedIndex = 2;
for(int i=0; i<5; i++)
{
this.KalmanError.Items.Add(0.003 + 0.001 * i);
}
this.KalmanError.SelectedIndex = 2;
this.CancelFilter.IsEnabled = false;
this.KalmanError.IsEnabled = false;
this.OriginalSignal.DataSource = OriginalData;
this.FilterSignal.DataSource = FilterData;
chartCollection = GetTxtInfo();
timer.Interval = 100;
timer.Tick += OnMainTimerTick;
}
/// <summary>
/// 求均值
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
private double CalcMeanValue(List<double> data)
{
double num = 0;
for (int i = 0; i < data.Count; i++)
{
num += data[i];
}
return num / Convert.ToDouble(data.Count);
}
/// <summary>
/// 获取高斯核
/// </summary>
/// <param name="kernel_size"></param>
/// <returns></returns>
private List<double> KernelData(int kernel_size)
{
List<double> Kernel = new List<double>();
for (int i = kernel_size / 2; i > -1; i--)
{
Kernel.Add(System.Math.Exp((0 - System.Math.Pow(i, 2)) / 2));
}
for(int j = kernel_size / 2 - 1; j>-1 ;j--)
{
Kernel.Add(Kernel[j]);
}
double sum = 0;
for(int m=0; m<kernel_size; m++)
{
sum += Kernel[m];
}
for (int n= 0; n < kernel_size; n++)
{
Kernel[n] = Kernel[n]/sum;
}
return Kernel;
}
/// <summary>
/// 高斯滤波
/// </summary>
/// <param name="data"></param>
/// <param name="kernel"></param>
/// <returns></returns>
private double CalcGaussian(List<double> data, List<double> kernel)
{
double gaussian_result = 0;
for (int i = 0; i < data.Count; i++)
{
gaussian_result += data[i] * kernel[i];
}
return gaussian_result;
}
/// <summary>
/// 卡尔曼滤波
/// </summary>
/// <param name="previous_data">前一个数据</param>
/// <param name="current_data">当前数据</param>
/// <param name="Q">上一轮的预估误差</param>
/// <param name="R">这一轮的测量误差</param>
/// <param name="SCOPE">用以比较新的值核之前的值的差距</param>
/// <returns></returns>
public double KalmamFilterValue(double current_data, double previous_data, double Q, double R, double SCOPE)
{
double Old_Input = 0;
if (System.Math.Abs(current_data - previous_data) / SCOPE > 0.25)
{
Old_Input = current_data * 0.3 + previous_data * 0.7;
}
else
{
Old_Input = previous_data;
}
//上一轮的总误差=累计误差^2+预估误差^2
double Old_Error_All = System.Math.Sqrt(System.Math.Pow(this.Accumulated_Error, 2) + System.Math.Pow(Q, 2));
//H为利用均方差计算出来的双方的相信度
double H = System.Math.Pow(Old_Error_All, 2) / (System.Math.Pow(Old_Error_All, 2) + System.Math.Pow(R, 2));
//计算卡尔曼结果 旧值 + 1.00001/(1.00001+0.1) * (新值-旧值)
double kalman_result = Old_Input + H * (current_data - Old_Input);
//更新累计误差
this.Accumulated_Error = System.Math.Sqrt((1.0 - H) * System.Math.Pow(Old_Error_All, 2));
return kalman_result;
}
private void UpdateSignalValue()
{
double OriginalMaxValue=0, OriginalMinValue=100, OriginalMeanValue=0, OriginalVaranceValue = 0;
double FilterMaxValue=0, FilterMinValue=100, FilterMeanValue=0, FilterVaranceValue = 0;
int length = OriginalData.Count;
for (int i =0;i< length; i++)
{
//原始数据
if(OriginalMaxValue < OriginalData[i])
{
OriginalMaxValue = OriginalData[i];
}
if(OriginalMinValue > OriginalData[i])
{
OriginalMinValue = OriginalData[i];
}
//滤波数据
if (FilterMaxValue < FilterData[i])
{
FilterMaxValue = FilterData[i];
}
if (FilterMinValue > FilterData[i])
{
FilterMinValue = FilterData[i];
}
OriginalMeanValue += OriginalData[i];
FilterMeanValue += FilterData[i];
}
OriginalMeanValue = OriginalMeanValue / Convert.ToDouble(length);
FilterMeanValue = FilterMeanValue / Convert.ToDouble(length);
for (int j=0; j< length; j++)
{
OriginalVaranceValue += System.Math.Pow((OriginalData[j] - OriginalMeanValue), 2);
FilterVaranceValue += System.Math.Pow((FilterData[j] - FilterMeanValue), 2);
}
OriginalVaranceValue = System.Math.Sqrt(OriginalVaranceValue / Convert.ToDouble(length));
FilterVaranceValue = System.Math.Sqrt(FilterVaranceValue / Convert.ToDouble(length));
this.OriginalMaximum.Text = System.Math.Round(OriginalMaxValue, 3).ToString();
this.OriginalMinimum.Text = System.Math.Round(OriginalMinValue, 3).ToString();
this.OriginalMean.Text = System.Math.Round(OriginalMeanValue, 3).ToString();
this.OriginalVariance.Text = System.Math.Round(OriginalVaranceValue, 3).ToString();
this.FilterMaximum.Text = System.Math.Round(FilterMaxValue, 3).ToString();
this.FilterMinimum.Text = System.Math.Round(FilterMinValue, 3).ToString();
this.FilterMean.Text = System.Math.Round(FilterMeanValue, 3).ToString();
this.FilterVarance.Text = System.Math.Round(FilterVaranceValue, 3).ToString();
}
/// <summary>
/// 定时器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnMainTimerTick(object sender, EventArgs e)
{
if(Convert.ToInt32(this.KernelSize.SelectedValue) + SignalCount < chartCollection.Count)
{
if(MeanFilter.IsChecked==true)
{
if(SignalCount==1)
{
for (int i = 0; i < Convert.ToInt32(this.KernelSize.SelectedValue); i++)
{
CalcMeanArr.Add(chartCollection[i]);
}
}
FilterData.Append(CalcMeanValue(CalcMeanArr));
OriginalData.Append(chartCollection[Convert.ToInt32(this.KernelSize.SelectedValue) / 2 + SignalCount]);
CalcMeanArr.RemoveAt(0);
CalcMeanArr.Add(chartCollection[Convert.ToInt32(this.KernelSize.SelectedValue) + SignalCount]);
UpdateSignalValue();
this.SignalCount++;
}
else if(GaussianFilter.IsChecked==true)
{
if (SignalCount == 1)
{
for (int i = 0; i < Convert.ToInt32(this.KernelSize.SelectedValue); i++)
{
CalcMeanArr.Add(chartCollection[i]);
}
}
int kernel_size = Convert.ToInt32(this.KernelSize.SelectedValue);
List<double> Kernel = KernelData(kernel_size);
FilterData.Append(CalcGaussian(CalcMeanArr, Kernel));
OriginalData.Append(chartCollection[Convert.ToInt32(this.KernelSize.SelectedValue) / 2 + SignalCount]);
CalcMeanArr.RemoveAt(0);
CalcMeanArr.Add(chartCollection[Convert.ToInt32(this.KernelSize.SelectedValue) + SignalCount]);
UpdateSignalValue();
this.SignalCount++;
}
else if(MedianFilter.IsChecked==true)
{
if (SignalCount == 1)
{
for (int i = 0; i < Convert.ToInt32(this.KernelSize.SelectedValue); i++)
{
CalcMeanArr.Add(chartCollection[i]);
}
}
List<double> CalcMeanArr1 = new List<double>();
for(int j=0; j< CalcMeanArr.Count; j++)
{
CalcMeanArr1.Add(CalcMeanArr[j]);
}
CalcMeanArr1.Sort();
FilterData.Append(CalcMeanArr1[CalcMeanArr1.Count / 2]);
OriginalData.Append(chartCollection[Convert.ToInt32(this.KernelSize.SelectedValue) / 2 + SignalCount]);
CalcMeanArr.RemoveAt(0);
CalcMeanArr.Add(chartCollection[Convert.ToInt32(this.KernelSize.SelectedValue) + SignalCount]);
UpdateSignalValue();
this.SignalCount++;
}
else if(KalmanFilter.IsChecked==true)
{
if (SignalCount == 1)
{
FilterData.Append(chartCollection[0]);
OriginalData.Append(chartCollection[0]);
}
double kalman_result = KalmamFilterValue(chartCollection[SignalCount], FilterData[SignalCount - 1], Convert.ToDouble(this.KalmanError.SelectedValue), 0.05, 30);
FilterData.Append(kalman_result);
OriginalData.Append(chartCollection[SignalCount]);
UpdateSignalValue();
this.SignalCount++;
}
}
else
{
this.timer.Stop();
}
}
/// <summary>
/// 获取txt数据
/// </summary>
/// <returns></returns>
public ChartCollection<double> GetTxtInfo()
{
ChartCollection<double> chartCollection = null;
string filePath = "test.txt";
try
{
if (File.Exists(filePath))
{
string tmp = File.ReadAllText(filePath);
tmp = System.Text.RegularExpressions.Regex.Replace(tmp, @"\s+", ",");
string[] arr = tmp.Split(',');
chartCollection = new ChartCollection<double>(arr.Length - 1);
for (int i = 0; i < arr.Length - 1; i++)
{
chartCollection.Append(Convert.ToDouble(arr[i]));
}
}
else
{
MessageBox.Show("文件不存在");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return chartCollection;
}
private void BeginFilter_Click(object sender, RoutedEventArgs e)
{
timer.Start();
this.BeginFilter.IsEnabled = false;
this.CancelFilter.IsEnabled = true;
}
private void CancelFilter_Click(object sender, RoutedEventArgs e)
{
this.timer.Stop();
this.BeginFilter.IsEnabled = true;
this.CancelFilter.IsEnabled = false;
OriginalData.Clear();
FilterData.Clear();
CalcMeanArr.Clear();
this.SignalCount = 1;
this.OriginalMaximum.Text = "";
this.OriginalMinimum.Text = "";
this.OriginalMean.Text = "";
this.OriginalVariance.Text = "";
this.FilterMaximum.Text = "";
this.FilterMinimum.Text = "";
this.FilterMean.Text = "";
this.FilterVarance.Text = "";
}
private void KalmanFilter_Checked(object sender, RoutedEventArgs e)
{
if(KalmanFilter.IsChecked == true)
{
this.KalmanError.IsEnabled = true;
this.KernelSize.IsEnabled = false;
}
}
private void RadioCheck(object sender, RoutedEventArgs e)
{
if(e.Source==MeanFilter)
{
if (MeanFilter.IsChecked == true)
{
this.KalmanError.IsEnabled = false;
this.KernelSize.IsEnabled = true;
}
}
else if (e.Source == GaussianFilter)
{
if (GaussianFilter.IsChecked == true)
{
this.KalmanError.IsEnabled = false;
this.KernelSize.IsEnabled = true;
}
}
else if (e.Source == MedianFilter)
{
if (MedianFilter.IsChecked == true)
{
this.KalmanError.IsEnabled = false;
this.KernelSize.IsEnabled = true;
}
}
else if (e.Source == KalmanFilter)
{
if (KalmanFilter.IsChecked == true)
{
this.KalmanError.IsEnabled = true;
this.KernelSize.IsEnabled = false;
}
}
}
}
}