上次写了十大滤波的算法详解,今天处理数据时候突发奇想,做了一个小工具用来检验滤波成果。
#ifndef FILTER_H
#define FILTER_H
#include <vector>
#include <QObject>
using namespace std;
enum FilterType {
LimiteFilter, //限幅滤波器
MidValueFilter, //中位值滤波器
AverageFilter, //均值滤波器
MovingAverageFilter, //滑动平均滤波器
ImpulseNoiseFilter, //防脉冲干扰平均滤波器
LimitAverageFilter, //限幅平均滤波函数器
WeightedAverageFilter,//加权平均滤波器
LowPassFilter, //一阶低通滤波器
DeChatFilter, //消抖滤波器
CliDenFilter //限幅消抖滤波器
};
class DataAcquisitionAnalyzer : public QObject {
Q_OBJECT
public:
DataAcquisitionAnalyzer(QObject *parent = nullptr);
//应用滤波器
void applyFilter(const vector<double> &inputData, vector<double> &outputData);
//设置当前滤波器类型
void setCurrentFilterType(FilterType filterType);
private:
FilterType currentFilterType;
// 不同类型的滤波器函数
void limite_filter(vector<double> &old_value, vector<double> &rst, double limit); // 限幅滤波器
void mid_value(int N, vector<double> &data, vector<double> &rst); // 中位值滤波器
void avg(int n, vector<double> &data, vector<double> &rst); // 均值滤波器
void mv_avg_filter(int N, vector<double> &data, vector<double> &rst); // 滑动平均滤波器
void im_int_filter(vector<double> &data, int N, vector<double> &rst); // 防脉冲干扰平均滤波器
void limit_average_filter(vector<double> &data, int N, double A, vector<double> &rst); // 限幅平均滤波器
void wei_avg_fil(int N, vector<double> &data, vector<double> &weight, vector<double> &rst); // 加权平均滤波器
void low_pass_filtering(double alpha, vector<double> &old_value, vector<double> &rst); // 一阶低通滤波器
void De_chat_filter(int N, vector<double> &data, vector<double> &rst); // 消抖滤波器
void cli_den_filter(double N, double A, vector<double> &data, vector<double> &rst); // 限幅消抖滤波器
};
#endif // FILTER_H
#include <QObject>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include "filter.h"
//各种滤波器函数的实现
//实现限幅滤波器
void DataAcquisitionAnalyzer::limite_filter(vector<double> &old_value, vector<double> &rst, double limit){
rst.push_back(old_value[0]); //给rst一个初始参考值
for(std::vector<int>::size_type i = 1; i < old_value.size(); i++) //利用循环将数组遍历
if((abs(old_value[i] - rst[i-1])) >= limit) //判断相邻两数据差值是否超过幅值
{
rst.push_back(rst[i - 1]); //超过则用老数据覆盖新数据
}
else
{
rst.push_back(old_value[i]); //未超过则将新数据存入
}
}
//实现中位值滤波器
//从一个数周围取n个数据,将这n个数据排序后取得中位值,将该中位值作为这个点的滤波值
void DataAcquisitionAnalyzer::mid_value(int N, vector<double> &data, vector<double> &rst) {
int n = data.size(), count = 0;
while(count < n - N + 1) {
vector<double> temp(data.begin() + count, data.begin() + count + N);
sort(temp.begin(), temp.end());
if(N % 2 == 0)
rst.push_back((temp[N / 2] + temp[(N - 1) / 2]) / 2);
else
rst.push_back(temp[(N - 1) / 2]);
count++;
}
}
//实现均值滤波器
//均值滤波与滑动滤波区别:最主要的区别:取值位置的不同,均值滤波是取N个周期的相同位置的数据进行计算,得出一个均值认定这个值为对应值。
//而滑动平均滤波是采连续的N个数,在这连续的N个数中,每次取n个数据进行处理
//n为每次处理的数据量,data中存储了所有的传入数据,rst中存储平均值
void DataAcquisitionAnalyzer::avg(int n, vector<double> &data, vector<double> &rst) {
int size = data.size();
for(int i = 0; i < size - n + 1; i++) {
double sum = 0; // 每次计算平均值前需要将 sum 置零
for(int j = i; j < n + i; j++) {
sum += data[j];
}
rst.push_back(sum / n); // 将平均值添加到结果数组中
}
}
//实现滑动平均滤波器
//滑动平均滤波需要设置一个窗口值来确定每次进行取均值计算的数据个数,然后通过主函数中的循环调用,每次输入不同的n个数据来进行取均值处理
//N为窗口值,data为传入数据的数组,rst为存储结果的数组
void DataAcquisitionAnalyzer::mv_avg_filter(int N, vector<double> &data, vector<double> &rst) {//设置计数器,获得传入数组的大小
int count = 0, size = data.size();
while(count < size - N + 1) { //设置循环次数
double sum = 0; // 每次计算均值前需要将 sum 置零
for(int i = count; i < N + count; i++) {
sum += data[i]; //求和
}
double avg = sum / N; // 求均值
rst.push_back(avg); // 将求得的均值添加到 rst 数组中
count++;
}
}
//实现防脉冲干扰平均滤波器
//输入所有数据,每次取一定数量的数据,排序后去除最大值与最小值,将剩余数据进行取均值处理,最后返回均值
//传入数据data,设置窗口值N,存储结果rst
//sum数据和,temp做排序中间值,count计数器,size传入数组的大小,avg均值
void DataAcquisitionAnalyzer::im_int_filter(vector<double> &data, int N, vector<double> &rst) {
int sum = 0, count = 0, size = data.size();
double avg;
while(count < size - N + 1){ //设置循环次数
vector<double> temp_data(data.begin() + count, data.begin() + count + N); //使用data数组的指针来初始化temp_data
sort(temp_data.begin(), temp_data.end()); //使用标准库函数进行排序
for(int i = 1; i < N - 1; i++){ //去头去尾(去掉最大值与最小值),然后求和
sum += temp_data[i];
}
avg = sum / (N - 2); //取平均值
rst.push_back(avg); //将均值存入rst数组中,rst.push_back(avg)将avg插入到数组的尾端
sum = 0; // 每次计算均值后需要将 sum 置零
count++;
}
}
// 实现限幅平均滤波器
//data为传入数组,N为窗口值,A为限幅阈值,rst为存储结果的数组
void DataAcquisitionAnalyzer::limit_average_filter(vector<double> &data, int N, double A, vector<double> &rst) {
double sum = 0; //记录滤波后的数据总和来求出最后需要的均值
int count = 0; //计数器
double result = 0.0; //最终计算结果
int size = data.size(); //得到传入数组的大小
vector<double> arr; //用来存储限幅后的数据
arr.push_back(data[0]); //赋予初值
for(int i = count; i < count + N; i++) //利用循环进行限幅滤波
{
if( abs(arr[i] - data[i+1]) < A) // 若当前数据与上次滤波结果之差小于阈值 A,则将当前数据存入数组
{
arr.push_back(data[i+1]);
}else{ //否则将上次滤波结果传入数组
arr.push_back(arr[i]);
}
}
while(count < size - N + 1){ //设置循环次数,开始滑动均值滤波
for(int k = 0;k < N; k++){ //每次取N个值进行求均值处理
sum += arr[k];
}
result = sum / N;
rst.push_back(result); //将计算结果存储到数组中
count++;
sum = 0; //每次循环都将sum值清零
}
}
//实现加权平均滤波器
//设定一个窗口值N.传入数据数组data与权重数组weight,设置结果数组rst,最后的数据结果存储到rst中
void DataAcquisitionAnalyzer::wei_avg_fil(int N, vector<double> &data, vector<double> &weight, vector<double> &rst) {
double sum = 0; //数据总和
double sum_wei = 0,avg = 0; //权重总和,平均值
int count = 0,size = data.size(); //计数器,传入数组大小
while(count < size - N +1){ //设定循环条件
for(int i = count; i < N+count; i ++){ //求数据*权重的和与权重的和
sum += data[i] * weight[i];
//cout << sum <<endl;
sum_wei += weight[i];
}
avg = sum / sum_wei; //得到均值数据
rst.push_back(avg); //将均值数据传入rst数组的尾端
sum = 0; //结果存入后将sum值清零
sum_wei = 0;
count++; //计数器自加
}
}
/*
一阶低通滤波器:利用公式得出滤波系数a(a的计算方法如下:a = Ts/(Ts+RC)=Ts/(Ts+1/(2πfc))=2πfcTs/(2πfcTs+1)
a的范围为[0,1]),然后利用两个数据(一般一个数据是上次测量或计算得出数据,用old_value来表示,一个为本次试验得到的数据,
用new_value)来进行滤波后数值的计算,然后将数值存入old_value中,计算方法如下:old_value=a * old_value+(1-a)new_value或old_value=anew_value+(1-a)*old_value,
使用哪种算法需要根据a的具体值来进行判断,一般认为,old_value数据更为准确,所以该数据占比会大一些,因此,a与1-a中,占比大的一方来处理old_value。
*/
// 一阶低通滤波函数,alpha为一阶低通滤波系数,Fc为截止频率,Ts为采样周期
void DataAcquisitionAnalyzer::low_pass_filtering(double alpha, vector<double> &old_value, vector<double> &rst) {
int size = old_value.size();
rst.push_back(old_value[0]);
for(int i = 0; i < size - 1; i++) {
if(alpha > 0.5){
rst.push_back(alpha * old_value[i] + (1-alpha) * old_value[i+1]); //一般将旧数据所占比重设置的较大,故公式会根据滤波系数的大小而产生变化
}
else
{
rst.push_back(alpha * old_value[i+1] + (1-alpha) * old_value[i]);
}
}
}
//实现消抖滤波器:设置一个阈值N,将传入数据与存储数据进行对比,若传入数据与存储数据不一致,则计数器+1,否则将传入数据返回。
//当传入数据与存储数据不一致,且计数器计数大于阈值N,则将传入数据返回,将其作为新的存储数据。
//N为设置的消抖参数,data为传入的数据,rst存储结果
void DataAcquisitionAnalyzer::De_chat_filter(int N, vector<double> &data, vector<double> &rst) {
int count = 0,j = 0; //count定义计数器,j为rst的下标
rst.push_back(data[0]); //定义初始值(参考值)
for(std::vector<double>::size_type i = 1; i < data.size(); i++) { //利用循环进行消抖处理
if (data[i] != rst[j]) { //当传入数据与存入数据不匹配时,计数器自增,然后判断计数器是否大于设置的参数,大于参数,则将新数据传入结果数组,并将计数器置零。
count++; //计数器加一
if (count >= N) {
j++;
rst.push_back(data[i]);
count = 0;
}
}
else { //当传入数据与存入数据一致时,将传入数据存入结果数组,并将计数器置零
j++;
rst.push_back(data[i]);
count = 0;
}
}
}
//实现限幅消抖滤波器
//N为幅值,A为消抖参数,data为原数据,rst为结果数据
void DataAcquisitionAnalyzer::cli_den_filter(double N, double A, vector<double> &data, vector<double> &rst) {
int i = 0, size = data.size(), count = 0; //i,j为循环使用参数,size为data的数据大小,count为消抖计数器
vector<double> arr(size); //定义数组来存储限幅后的数据
arr[0] = data[0]; //定义初始值
for(i = 1; i < size; i++) //利用循环得到限幅后的数据
{
if(abs(data[i] - arr[i - 1]) >= N){ // 判断信号变化是否超过阈值N,进行限幅处理
arr[i] = arr[i-1];
}else{
arr[i] = data[i];
}
}
// cout << endl;
for(i = 0; i < size; i++){ //进行消抖滤波
if(arr[i] != arr[i-1]){ // 判断是否连续A次相同,输出信号
count ++;
if(count >= A){ //当计数器数值大于设置的参数时,将数据存入,并将计数器置零
rst.push_back(arr[i]);
count = 0;
}
}else{ //当新数据与老数据相等时,存入新数据,计数器置零
rst.push_back(arr[i]);
count = 0;
}
}
}
DataAcquisitionAnalyzer::DataAcquisitionAnalyzer(QObject *parent)
: QObject(parent), currentFilterType(FilterType::LimiteFilter) {}
void DataAcquisitionAnalyzer::setCurrentFilterType(FilterType filterType) {
currentFilterType = filterType;
}
void DataAcquisitionAnalyzer::applyFilter(const vector<double> &inputData, vector<double> &outputData) {
outputData.clear();
vector<double> old_value_double(inputData.begin(), inputData.end());
vector<double> rst_double;
switch (currentFilterType) {
case FilterType::LimiteFilter:
limite_filter(old_value_double, rst_double, 25.0);
outputData.assign(rst_double.begin(), rst_double.end());
break;
case FilterType::MidValueFilter:
mid_value(3, const_cast<vector<double>&>(inputData), outputData);
break;
case FilterType::LowPassFilter:
low_pass_filtering(0.5, const_cast<vector<double>&>(inputData), outputData);
break;
case FilterType::DeChatFilter:
De_chat_filter(2, const_cast<vector<double>&>(inputData), outputData);
break;
case FilterType::CliDenFilter:
cli_den_filter(50.0, 2.0, const_cast<vector<double>&>(inputData), outputData);
break;
case FilterType::AverageFilter:
avg(3, const_cast<vector<double>&>(inputData), outputData);
break;
case FilterType::MovingAverageFilter:
mv_avg_filter(3, const_cast<vector<double>&>(inputData), outputData);
break;
case FilterType::ImpulseNoiseFilter:
im_int_filter(const_cast<vector<double>&>(inputData), 3, outputData);
break;
case FilterType::LimitAverageFilter:
limit_average_filter(const_cast<vector<double>&>(inputData), 3, 20.0, outputData);
break;
case FilterType::WeightedAverageFilter:
vector<double> weight = {0.1, 0.2, 0.3, 0.4, 0.5};
wei_avg_fil(3, const_cast<vector<double>&>(inputData), weight, outputData);
break;
}
}
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLineSeries>
#include <QtCharts>
#include <QComboBox>
#include <QFileDialog>
#include <QPushButton>
#include <QLabel>
#include <QMouseEvent>
#include "filter.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
protected:
void mouseMoveEvent(QMouseEvent *event) override; // 鼠标移动事件
private slots:
void applyFilter(); // 应用滤波槽函数
void loadDataFromFile(); // 从文件加载数据槽函数
void exportFilteredData(); // 导出滤波后数据槽函数
private:
Ui::MainWindow *ui; // UI界面
DataAcquisitionAnalyzer *analyzer; // 数据采集分析器
QLineSeries *originalSeries; // 原始数据线序列
QLineSeries *filteredSeries; // 滤波后数据线序列
QChart *chart; // 图表
QChartView *chartView; // 图表视图
QComboBox *filterComboBox; // 滤波器选择框
QPushButton *loadFileButton; // 加载文件按钮
QPushButton *exportButton; // 导出数据按钮
QLabel *coordLabel; // 显示坐标的标签
};
#endif // MAINWINDOW_H
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QComboBox>
#include <QChartView>
#include <QLineSeries>
#include <QValueAxis>
#include <QtCharts>
#include <QFileDialog>
#include <QFile>
#include <QTextStream>
#include <QMouseEvent>
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
analyzer(new DataAcquisitionAnalyzer(this)),
originalSeries(new QLineSeries()),
filteredSeries(new QLineSeries()),
chart(new QChart()),
chartView(new QChartView(chart)),
loadFileButton(new QPushButton("加载文件", this)),
exportButton(new QPushButton("导出数据", this)),
coordLabel(new QLabel(this)),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 布局设置
QVBoxLayout *mainLayout = new QVBoxLayout(this);
QHBoxLayout *inputLayout = new QHBoxLayout();
QHBoxLayout *chartLayout = new QHBoxLayout();
// 按钮
QPushButton *filterButton = new QPushButton("应用滤波", this);
inputLayout->addWidget(loadFileButton);
inputLayout->addWidget(filterButton);
inputLayout->addWidget(exportButton);
// 滤波器选择框
filterComboBox = new QComboBox(this);
filterComboBox->addItem("限幅滤波器", FilterType::LimiteFilter);
filterComboBox->addItem("中位值滤波器", FilterType::MidValueFilter);
filterComboBox->addItem("均值滤波器", FilterType::AverageFilter);
filterComboBox->addItem("滑动平均滤波器", FilterType::MovingAverageFilter);
filterComboBox->addItem("防脉冲干扰平均滤波器", FilterType::ImpulseNoiseFilter);
filterComboBox->addItem("限幅平均滤波器", FilterType::LimitAverageFilter);
filterComboBox->addItem("加权平均滤波器", FilterType::WeightedAverageFilter);
filterComboBox->addItem("一阶低通滤波器", FilterType::LowPassFilter);
filterComboBox->addItem("消抖滤波器", FilterType::DeChatFilter);
filterComboBox->addItem("限幅消抖滤波器", FilterType::CliDenFilter);
inputLayout->addWidget(filterComboBox);
// 曲线图
chartView = new QChartView(chart);
chartLayout->addWidget(chartView);
// 数据处理
chart->addSeries(originalSeries);
chart->addSeries(filteredSeries);
QValueAxis *axisX = new QValueAxis;
axisX->setTickCount(10);
chart->addAxis(axisX, Qt::AlignBottom);
originalSeries->attachAxis(axisX);
filteredSeries->attachAxis(axisX);
QValueAxis *axisY = new QValueAxis;
chart->addAxis(axisY, Qt::AlignLeft);
originalSeries->attachAxis(axisY);
filteredSeries->attachAxis(axisY);
chart->legend()->setVisible(true);
chart->legend()->setAlignment(Qt::AlignBottom);
// 连接按钮的点击信号到槽函数
connect(filterButton, &QPushButton::clicked, this, &MainWindow::applyFilter);
connect(loadFileButton, &QPushButton::clicked, this, &MainWindow::loadDataFromFile);
connect(exportButton, &QPushButton::clicked, this, &MainWindow::exportFilteredData);
// 添加布局到主布局
mainLayout->addLayout(inputLayout);
mainLayout->addLayout(chartLayout);
mainLayout->addWidget(coordLabel);
// 设置主布局
QWidget *centralWidget = new QWidget();
centralWidget->setLayout(mainLayout);
setCentralWidget(centralWidget);
// 设置窗口可缩放
chartView->setRubberBand(QChartView::RectangleRubberBand);
chartView->setRenderHint(QPainter::Antialiasing);
// 启用 OpenGL 渲染以实现平滑曲线
//originalSeries->setUseOpenGL(true);
//filteredSeries->setUseOpenGL(true);
}
void MainWindow::applyFilter() {
originalSeries->clear();
filteredSeries->clear();
QString fileName = QFileDialog::getOpenFileName(this, "选择数据文件", "", "图表文件 (*.csv);;所有文件 (*.*)");
if (fileName.isEmpty()) {
return;
}
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return;
}
QTextStream in(&file);
std::vector<double> xData;
std::vector<double> yData;
while (!in.atEnd()) {
QString line = in.readLine();
QStringList values = line.split(",", QString::SkipEmptyParts);
if (values.size() == 2) {
xData.push_back(values[0].toDouble());
yData.push_back(values[1].toDouble());
}
}
// 原始数据绘制
QVector<QPointF> originalPoints;
originalPoints.reserve(xData.size());
for (std::size_t i = 0; i < xData.size(); ++i) {
originalPoints.append(QPointF(xData[i], yData[i]));
}
originalSeries->replace(originalPoints);
// 应用滤波器
std::vector<double> filteredData;
FilterType selectedFilter = static_cast<FilterType>(filterComboBox->currentData().toInt());
analyzer->setCurrentFilterType(selectedFilter);
analyzer->applyFilter(yData, filteredData);
// 滤波后数据绘制
QVector<QPointF> filteredPoints;
filteredPoints.reserve(xData.size());
for (std::size_t i = 0; i < xData.size(); ++i) {
filteredPoints.append(QPointF(xData[i], filteredData[i]));
}
filteredSeries->replace(filteredPoints);
file.close();
}
void MainWindow::loadDataFromFile() {
QString fileName = QFileDialog::getOpenFileName(this, "选择数据文件", "", "图表文件 (*.csv);;所有文件 (*.*)");
if (fileName.isEmpty()) {
return;
}
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return;
}
QTextStream in(&file);
std::vector<double> xData;
std::vector<double> yData;
while (!in.atEnd()) {
QString line = in.readLine();
QStringList values = line.split(",", QString::SkipEmptyParts);
if (values.size() == 2) {
xData.push_back(values[0].toDouble());
yData.push_back(values[1].toDouble());
}
}
// 原始数据绘制
QVector<QPointF> originalPoints;
originalPoints.reserve(xData.size());
for (std::size_t i = 0; i < xData.size(); ++i) {
originalPoints.append(QPointF(xData[i], yData[i]));
}
originalSeries->replace(originalPoints);
file.close();
}
void MainWindow::exportFilteredData() {
QString fileName = QFileDialog::getSaveFileName(this, "导出滤波后数据", "", "CSV 文件 (*.csv);;所有文件 (*.*)");
if (fileName.isEmpty()) {
return;
}
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return;
}
QTextStream out(&file);
for (int i = 0; i < filteredSeries->count(); ++i) {
QPointF point = filteredSeries->at(i);
out << point.x() << "," << point.y() << "\n";
}
file.close();
}
void MainWindow::mouseMoveEvent(QMouseEvent *event) {
QPointF point = chartView->mapToScene(event->pos());
QPointF value = chart->mapToValue(point, originalSeries);
coordLabel->setText(QString("X: %1, Y: %2").arg(value.x()).arg(value.y()));
}
MainWindow::~MainWindow() {
delete ui;
delete analyzer;
delete originalSeries;
delete filteredSeries;
delete chart;
delete chartView;
}
效果图如下(具体可以自己调,如限幅权值这些):