目录
间接平差法进行直线拟合,使用Qt、Qcustomplot、Eigen
仔细读一定会有所收获,不要着急,很快就学会啦
间接平差法直线拟合原理
间接平差是在确定多个未知量的最或然值时,选择它们之间不存在任何条件关系的独立量作为未知量组成用未知量表达测量的函数关系、列出误差方程式,按最小二乘法原理求得未知量的最或然值的平差方法。间接平差为平差计算最常用的方法,其数学模型比较简单,便于评定平差值及其函数的精度。
对于多个点也是类似的做法
工具介绍
Qt
Qt 是一种更快,更智能的方式来为多个屏幕创建创新的设备、现代用户界面和应用程序。它是一个跨平台的C++应用程序开发框架。它提供给开发者建立图形用户界面所需的功能,广泛用于开发图形用户界面程序,也可用于开发非图形用户界面(比如命令行界面)程序。Qt是完全面向对象的,很容易扩展,并且允许真正地组件编程。
基本上,Qt 同 X11上的GTK、Motif、Openwin和Windows上的MFC,OWL,VCL,ATL 是同类型的东西,但是 Qt 支持更多的平台(包括Windows、GNU/Linux、Mac OS X、Android、iOS、WinCE、Unix家族等),面向对象且模块化程度更高(Qt 提供了一种称为 signals/slots 的安全类型来替代 callback,这使得各个元件 之间的协同工作变得十分简单)。
丰富的API(Qt 包括多达 250 个以上的 C++ 类,还替供基于模板的 collections, serialization, file, I/O device, directory management, date/time 类。甚至还包括正则表达式的处理 功能),支持 2D/3D 图形渲染,支持 OpenGL、大量的开发文档、XML支持等。
这里给出Qt提供的各种类This is a list of all Qt classes. ,开发时按照官方文档走准没错。
Qcustomplot
QCustomPlot 是一个基于Qt的画图和数据可视化C++控件。QCustomPlot 致力于提供美观的界面,高质量的2D画图、图画和图表,同时为实时数据可视化应用提供良好的解决方案。
Eigen
Eigen 是一个线性算术的C++模板库,包括:vectors, matrices, 以及相关算法。功能强大、快速、优雅以及支持多平台。
准备工作
下载Eigen,下载最新的就行,然后放在你能找到的地方,在项目文件.pro里加入Eigen安装路径:
INCLUDEPATH += C:\Users\Documents\Qt\Line_fitting\eigen-3.3.9
下载Qcustomplot,同理下载下来,这个是两个文件qcustomplot.h和qcustomplot.cpp,直接导入项目就行,同时在第三行后面加入printsupport提供绘图支持:
在需要使用到两个功能的地方引入即可,例如:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QFileDialog"
#include <Eigen/Dense>
#include "qcustomplot.h"
using namespace Eigen;
我们需要做的是三件事:
- 读取坐标文件存入矩阵
- 矩阵运算求出结果
- 绘图
1.Qt读取文件存入矩阵
Qt提供了QFileDialog类,可以调用窗口选择文件
//定义文件对话框类
QFileDialog *fileDialog = new QFileDialog(this);
//定义文件对话框标题
fileDialog->setWindowTitle(QStringLiteral("选择数据文件"));
//设置默认文件路径
fileDialog->setDirectory("C:\\users");
//设置文件过滤器
fileDialog->setNameFilter(tr("Text files (*.txt)"));
//设置可以选择多个文件,默认为只能选择一个文件QFileDialog::ExistingFiles
fileDialog->setFileMode(QFileDialog::ExistingFiles);
//设置视图模式
fileDialog->setViewMode(QFileDialog::Detail);
if (fileDialog->exec()) {
//读取文件路径
QStringList filePaths;//可设为全局变量,存储文件路径名
filePaths = fileDialog->selectedFiles();
}
效果如下图:
下面要用QFile把刚才打开的txt文件内容读取到矩阵中,矩阵由Eigen提供
//读取坐标并存储
QVector<QString> XY;
QVector<QString> X;
QVector<QString> Y;
QFile file(filePaths.at(0));
if(!file.open(QIODevice::ReadOnly|QIODevice::Text))
{
return;
}
QTextStream in(&file);
while (!in.atEnd())//数据流in按行读取
{
QString line=in.readLine();
XY.append(line);
}
for (int i=0;i<XY.length();i++)//遍历一遍XY中的信息
{
QString line=XY.at(i);
line=line.trimmed();
QStringList linesplit=line.split(" ");//将数据以空格为间隔分开,以QString数组形式存储
X.append(linesplit.at(0));//X中存坐标第一列
Y.append(linesplit.at(1));//Y中存坐标第二列
}
//将坐标信息存入矩阵X、Y
MatrixXd B(X.length(),2);//行数按照数据的行数,列数为2
MatrixXd l(Y.length(),1);
for (int i=0;i<X.length() ;i++ ) {
B(i,0)=X.at(i).toDouble();
B(i,1)=1;
l(i,0)=Y.at(i).toDouble();
}
效果如下
现在我们算是得到了所需要的矩阵,就可以直接计算结果了
2.Eigen矩阵运算得到k、d
公式在上面,直接上代码,Eigen提供了矩阵的转置和求逆方法
由于每个点的权值都是1,所以这里的P直接取单位矩阵就行,否则需要进一步求P(不难)
MatrixXd I=MatrixXd::Identity(X.length(),Y.length());//单位矩阵
MatrixXd Z=((B.transpose()*I*B).inverse())*B.transpose()*I*l;//计算公式
MatrixXd V=B*Z-l;
double k=Z(0,0);//求出k、d值
double d=Z(1,0);
得到k、d就可以画图了
3.Qcustomplot绘图
没啥讲的,有k、d和那么多点,直接画就行,有个要点
我们在ui设计里面使用Qcustomplot需要将widget提升为qcustomplot
首先放置一个widget在需要的地方
右键该widget
然后将其提升,我是第二次,里面直接有Qcustomplot,第一次也没关系,直接在下面提升的类名称部分按下图填就行,然后点击添加、提升即可。
下面代码里面是ui->panel,这个就是上面的widget提升之后我改的名字
你可以自己改,网上查的大多代码是Qcustomplot->,你可以这样
Qcustomplot *Qcustomplot=ui->panel;
下面将进行绘图
//绘图
QString K=QString::number(k);
ui->kvalue->setText(K);
QString D=QString::number(d);
ui->dvalue->setText(D);
QVector<double> x,y,yt;//x,y分别为原始数据点,yt为拟合后的纵坐标,(x,yt)一定在拟合直线上
for (int i=0;i<X.length() ;i++ ) {
x.append(X.at(i).toDouble());
yt.append(k*X.at(i).toDouble()+d);
y.append(Y.at(i).toDouble());
}
//绘制拟合直线
ui->panel->addGraph();
ui->panel->graph(0)->setPen(QPen(Qt::black,2));
ui->panel->graph(0)->setData(x, yt);
//根据图像最高点最低点自动缩放坐标轴,下面同理
//X、Y轴
ui->panel->graph(0)->rescaleAxes(true);
// 为坐标轴添加标签
ui->panel->xAxis->setLabel("X");
ui->panel->yAxis->setLabel("Y");
//绘制拟合后的点,在直线上
ui->panel->addGraph();
ui->panel->graph(1)->setPen(QPen(Qt::red));
ui->panel->graph(1)->setLineStyle(QCPGraph::lsNone);
ui->panel->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 4));
ui->panel->graph(1)->setData(x, yt);
//X、Y轴
ui->panel->graph(1)->rescaleAxes(true);
//绘制原始数据散点,不一定在直线上
ui->panel->addGraph();
ui->panel->graph(2)->setPen(QPen(Qt::green));
ui->panel->graph(2)->setLineStyle(QCPGraph::lsNone);
ui->panel->graph(2)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 4));
ui->panel->graph(2)->setData(x, y);
//X、Y轴
ui->panel->graph(2)->rescaleAxes(true);
// 重画图像
ui->panel->replot();