C++ 曲线平滑

最近需要将数据通过图表展示出来,以便观察其变化趋势。直接展示出来的效果不是很好,故在网上查找了很久曲线平滑的方法,最终找到了了一套比较适合的方法,思路比较简单,对于现在的项目来说效果也还不错,故记录一下。

原始数据大小分别为881、490和1711,直接展示如下图所示:

原始曲线

由于采集过程中传感器数据变化比较敏感,所以有比较明显的锯齿,故第一步需要去差值。这里采用的是“五点去差值法”,即选周围五个点,去掉最大值和最小值求剩余三个点的平均,使其相邻点变得平滑。这里作了SIZE/20次去差值,去差值后如下图所示:

原始曲线+去差值

经过多次反复去差值后,可以看到曲线明显变得平滑了许多,也没有明显的锯齿了。但是我们只关心曲线的大致趋势,不需要那么多细节。由于前面已经将曲线相邻点变得比较平滑了,故下一步就直接减少点的数量,这里将数据量减少了十倍,减少数据量后效果如下:

原始曲线+去差值+减少采样点​​​​

去差值+减少数据量后看起来比原始数据展示好看多了,更加简洁明了。但是还是比较生硬,需要对其平滑一下。这里采用也是最简单的“线性五点平滑”法,选取周围五点点的数据取均值。这里做了20次平滑,平滑后的效果如下:

曲线平滑效果

 

 

下面是完整代码:

头文件:

#ifndef _CHART_WIDGET_H_
#define _CHART_WIDGET_H_

#include <QWidget>
#include <QtCharts>
#include <QChartView>
#include <QChart>
#include <QLineSeries>
#include <QValueAxis>

class ChartWidget : public QChartView
{
	Q_OBJECT

public:
	ChartWidget(QWidget *parent = 0);
	~ChartWidget();

	enum MMSmoothType
	{
		SMOOTH_NULL = -1,
		//拟合SIZE/200次 数据量减少10倍 平滑处理20次
		SMOOTH_200_10_20,

		//拟合SIZE/200次 数据量减少10倍 平滑处理20次
		SMOOTH_200_10_10,

		//拟合SIZE/50次 数据量减少20倍 平滑处理20次
		SMOOTH_50_20_20
	};
	void AddLineSeries(QString Name, QColor color, const QList<QPointF> &Datas, MMSmoothType SmoothType = SMOOTH_NULL);

	void SetAxisXRange(float Min, float Max);

	void SetAxisYRange(float Min, float Max);

	void SetAxisXTitle(QString Title);

	void SetAxisYTitle(QString Title);

	void SetTitle(QString Title);

	void SetLegendVisible(bool IsVisible);

	void SetBackgroundColor(QColor color);

	void ClearDatas();
private:
	
	void InitChartData();

	//线性五点拟合去差值  临近五个点去掉最大值和最小值取平均
	QList<QPointF> LinearFitted5(const QList<QPointF>& Datas);

	//线性五点平滑 临近五个点取平均
	QList<QPointF> LinearSmooth5(const QList<QPointF>& Datas);

		
	QList<QPointF> SmoothData(const QList<QPointF>& Datas, MMSmoothType SmoothType);
private:

	QValueAxis* m_AxisX;
	QValueAxis* m_AxisY;

	double m_MinX;
	double m_MinY;
	double m_MaxX;
	double m_MaxY;

	bool m_HasData;
};

#endif //_CHART_WIDGET_H_

 

 

源文件:

#include "ChartWidget.h"

#pragma execution_character_set("utf-8")

ChartWidget::ChartWidget(QWidget *parent)
	: QChartView(parent),
	m_MinX(0),
	m_MaxX(0),
	m_MinY(0),
	m_MaxY(0),
	m_AxisX(new QValueAxis),//sin的X轴
	m_AxisY(new QValueAxis),//sin的Y轴
	m_HasData(false)

{
	QChart* chart = this->chart();

	QFont font;
	font.setFamily("Microsoft Yahei");
	font.setPixelSize(10);
	m_AxisX->setTitleFont(font);
	m_AxisX->setLabelsFont(font);
	m_AxisX->setTitleText("时间/m");
	//m_AxisX->setLabelFormat("%0.1f");
	m_AxisX->setLabelsColor(QColor(30, 30, 30));

	//m_AxisY->setRange(-2, 2);
	m_AxisY->setTitleText("值/m");
	m_AxisY->setTitleFont(font);
	m_AxisY->setLabelsFont(font);
	m_AxisY->setLabelsColor(QColor(30, 30, 30));

	//m_AxisY->setLabelFormat("%0.1f");

	chart->legend()->setFont(font);
	chart->setTitleBrush(QColor(30, 30, 30));
	QPen p;
	p.setColor(QColor(255, 255, 255));
	chart->legend()->setPen(p);

	setRenderHint(QPainter::Antialiasing);//抗锯齿
	chart->layout()->setContentsMargins(0, 0, 0, 0);//设置外边界全部为0
	chart->setMargins(QMargins(0, 0, 0, 0));//设置内边界全部为0
	chart->setBackgroundRoundness(0);//设置背景区域无圆角

	InitChartData();

}

ChartWidget::~ChartWidget()
{
}

void ChartWidget::AddLineSeries(QString Name, QColor color, const QList<QPointF> &Datas, MMSmoothType SmoothType)
{
	if (Datas.size() <= 0)
	{
		return;
	}

	QList<QPointF> tmpData = SmoothData(Datas, SmoothType);

	QLineSeries *newSeries = new QLineSeries;
	newSeries->setColor(color);
	newSeries->setName(Name);
	QPen tmpPen(QColor(color), 1);
	newSeries->setPen(tmpPen);
	//newSeries->replace(Datas);

	for (int i = 0; i < tmpData.size(); i++)
	{
		m_MinX = m_MinX < tmpData[i].x() ? m_MinX : tmpData[i].x();
		m_MaxX = m_MaxX > tmpData[i].x() ? m_MaxX : tmpData[i].x();
		m_MinY = m_MinY < tmpData[i].y() ? m_MinY : tmpData[i].y();
		m_MaxY = m_MaxY > tmpData[i].y() ? m_MaxY : tmpData[i].y();

		newSeries->append(tmpData[i]);
	}

	QChart* chart = this->chart();
	chart->addSeries(newSeries);
	chart->setAxisX(m_AxisX, newSeries);
	chart->setAxisY(m_AxisY, newSeries);
	
	m_AxisX->setRange(m_MinX, m_MaxX);
	m_AxisY->setRange(m_MinY, m_MaxY);
}

void ChartWidget::SetAxisXRange(float Min, float Max)
{
	if (Min == Max)
	{
		Max++;
	}

	//取整
	int tmpMin = Min;
	if (tmpMin < Min && Min > 0)
	{
		tmpMin = Min - 1;

	}
	else if (tmpMin > Min && Min < 0)
	{
		tmpMin = Min - 1;

	}

	int tmpMax = Max;
	if (tmpMax < Max && Max > 0)
	{
		tmpMax = Max + 1;

	}
	else if (tmpMax > Max && Max < 0)
	{
		tmpMax = Max + 1;

	}

	m_MinX = tmpMin;
	m_MaxX = tmpMax;

	m_AxisX->setRange(tmpMin, tmpMax);

}

void ChartWidget::SetAxisYRange(float Min, float Max)
{
	if (Min == Max)
	{
		Max++;
	}
	//取整
	int tmpMin = Min;
	if (tmpMin < Min && Min > 0)
	{
		tmpMin = Min - 1;

	}
	else if (tmpMin > Min && Min < 0)
	{
		tmpMin = Min - 1;

	}

	int tmpMax = Max;
	if (tmpMax < Max && Max > 0)
	{
		tmpMax = Max + 1;

	}
	else if (tmpMax > Max && Max < 0)
	{
		tmpMax = Max + 1;

	}
	m_MinY = tmpMin;
	m_MaxY = tmpMax;
	m_AxisY->setRange(tmpMin, tmpMax);

}

void ChartWidget::SetAxisXTitle(QString Title)
{
	m_AxisX->setTitleText(Title);
}

void ChartWidget::SetAxisYTitle(QString Title)
{
	m_AxisY->setTitleText(Title);
}

void ChartWidget::SetTitle(QString Title)
{
	QChart* chart = this->chart();
	chart->setTitle(Title);
}

void ChartWidget::SetLegendVisible(bool IsVisible)
{
	QChart* chart = this->chart(); 
	chart->legend()->setVisible(IsVisible);
}

void ChartWidget::SetBackgroundColor(QColor color)
{
	QChart* chart = this->chart();
	chart->setBackgroundBrush(QBrush(color));

}

void ChartWidget::ClearDatas()
{
	QList<QAbstractSeries *> series = this->chart()->series();
	for each (QAbstractSeries* var in series)
	{
		this->chart()->removeSeries(var);
	}
	
	InitChartData();
}

void ChartWidget::InitChartData()
{
	
	QList<QPointF> Datas;
	for (int i = 0; i < 3; i++)
	{
		Datas.push_back(QPointF(i, 0));
	}

	AddLineSeries("", QColor(230, 230, 230), Datas);
	m_AxisX->setRange(0, 3);
	m_AxisY->setRange(0, 1);

	m_HasData = false;
}

//自定义排序函数  
bool sortByY(const QPointF &p1, const QPointF &p2)
{
	return p1.y() < p2.y();//升序排列  
}


QList<QPointF> ChartWidget::LinearFitted5(const QList<QPointF>& Datas)
{
	QList<QPointF> result;

	if (Datas.size() <= 5)
	{
		result = Datas;
		return result;
	}
	else
	{
		int N = Datas.size();
		double tmpY;

		std::vector<QPointF> tmpData(5);

		int i = 0;
		for (; i < N - 5; i++)
		{
			tmpData.clear();
			for (int j = i; j < i + 5; j++)
			{
				tmpData.push_back(Datas[j]);
			}
			std::sort(tmpData.begin(), tmpData.end(), sortByY);

			tmpY = (tmpData[1].y() + tmpData[2].y() + tmpData[3].y()) / 3;
			result.push_back(QPointF(Datas[i].x(), tmpY));			
		}

		result[result.size() - 1].setX(Datas[Datas.size() - 1].x());

	}

	return result;
}

QList<QPointF> ChartWidget::LinearSmooth5(const QList<QPointF>& Datas)
{
	QList<QPointF> result;

	if (Datas.size() <= 5)
	{
		result = Datas;
		return result;
	}
	else
	{
		int N = Datas.size();
		double tmpY;
		tmpY = (3.0 * Datas[0].y() + 2.0 * Datas[1].y() + Datas[2].y() - Datas[4].y()) / 5.0;
		if (tmpY <= 0)
		{
		tmpY = 0;
		}
		result.push_back(QPointF(Datas[0].x(), tmpY));

		tmpY = (4.0 * Datas[0].y() + 3.0 * Datas[1].y() + 2 * Datas[2].y() + Datas[3].y()) / 10.0;
		result.push_back(QPointF(Datas[1].x(), tmpY));


		for (int i = 2; i <= N - 3; i++)
		{
			tmpY = (Datas[i - 2].y() + Datas[i - 1].y() + Datas[i].y() + Datas[i + 1].y() + Datas[i + 2].y()) / 5.0;

			result.push_back(QPointF(Datas[i].x(), tmpY));

		}
		tmpY = (4.0 * Datas[N - 1].y() + 3.0 * Datas[N - 2].y() + 2 * Datas[N - 3].y() + Datas[N - 4].y()) / 10.0;
		result.push_back(QPointF(Datas[N - 2].x(), tmpY));

		tmpY  = (3.0 * Datas[N - 1].y() + 2.0 * Datas[N - 2].y() + Datas[N - 3].y() - Datas[N - 5].y()) / 5.0;
		result.push_back(QPointF(Datas[N - 1].x(), tmpY));

	}

	return result;
}

QList<QPointF> ChartWidget::SmoothData(const QList<QPointF>& Datas, MMSmoothType SmoothType)
{
	if (Datas.size() < 5)
	{
		return Datas;
	}

	QList<QPointF> tmpDatas = Datas;

	int param1, param2, param3;
	switch (SmoothType)
	{
	case ChartWidget::SMOOTH_NULL:
		return tmpDatas;
		break;
	case ChartWidget::SMOOTH_200_10_20:
		param1 = 200;
		param2 = 10;
		param3 = 20;
		break;
	case ChartWidget::SMOOTH_200_10_10:
		param1 = 200;
		param2 = 1;
		param3 = 10;
		break;
	case ChartWidget::SMOOTH_50_20_20:
		param1 = 50;
		param2 = 20;
		param3 = 20;

		break;
	default:
		break;
	}

	int smoothCount = Datas.size() / param1 + 1;
	for (int i = 0; i < smoothCount; i++)
	{
		tmpDatas = LinearFitted5(tmpDatas);
	}
	
	QList<QPointF>  result;
	for (int i = 0; i < tmpDatas.size(); )
	{
		result.push_back(tmpDatas.at(i));
		i += param2;

	}
	result.push_back(tmpDatas.at(tmpDatas.size() - 1));


	for (int i = 0; i < param3; i++)
	{
		result = LinearSmooth5(result);

	}
	return result;
}

调用方式:

	QList<QPointF> data0 = ReadDatas("data0.txt");
	QList<QPointF> data1 = ReadDatas("data1.txt");
	QList<QPointF> data2 = ReadDatas("data2.txt");

	ChartWidget* tmpChart = new ChartWidget(this);
	
	//QHBoxLayout* mainLayout = new QHBoxLayout(tmpChart);
	//this->setLayout(mainLayout);
	this->setCentralWidget(tmpChart);

	tmpChart->AddLineSeries("", QColor(255, 0, 0), data0, ChartWidget::SMOOTH_200_10_20);
	tmpChart->AddLineSeries("", QColor(0, 255, 0), data1, ChartWidget::SMOOTH_200_10_20);
	tmpChart->AddLineSeries("", QColor(0, 0, 255), data2, ChartWidget::SMOOTH_200_10_20);

完整代码  CSDN链接下载需要积分,不知道怎么取消积分。

用百度云:链接: https://pan.baidu.com/s/1lVjeWracw1z5NPl8-3FYIA 提取码: yak8 

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值