第六周编程学习(4.3-4.6/4.14-4.16)

目录

第一天4.3  星期一  回归、胸径、经验模型

第二天4.4  星期二   回归指标

第三天4.5  星期三  Pillow库、点云格式、解析解和数值解

第四天4.6  星期四  两种点云叠加方法

第五天4.14  星期五   QPainter、QT基础操作

第六天4.15  星期六  QT折线图  时间轴

第七天4.16  星期日   横坐标更改   八叉树

本周总结:


第一天4.3  星期一  回归、胸径、经验模型

今天主要完成了回归方法的场景使用与区分,以及最小二乘法和完全最小二乘法等内容

一、主要回归方法及其适应场景

  1. 线性回归:适用于输入变量和输出变量之间存在线性关系的场景,通常用于建立数学模型、预测和探索数据之间的关系。其中最小二乘法和完全最小二乘法适用于存在测量误差的场景,可以通过对数据进行拟合来预测输出变量的值。

  2. 多项式回归:适用于输入变量和输出变量之间存在非线性关系的场景,通过添加高次项可以拟合更加复杂的关系。

  3. 岭回归:适用于存在多重共线性或者变量数目大于样本数的场景,通过对回归系数施加L2惩罚项可以控制模型复杂度。

  4. 最小角回归:适用于存在大量相关性变量的场景,通过引入弹性网络可以同时实现变量选择和模型优化。

  5. Lasso回归和Elastic Net回归:适用于需要进行特征选择和模型简化的场景,可以通过对回归系数施加L1惩罚项来实现。

  6. 主成分回归:适用于处理多重共线性问题和高维数据问题,可以通过主成分分析降维后再进行回归分析。

  7. 贝叶斯回归:适用于需要考虑回归系数的后验分布和不确定性的场景,可以通过贝叶斯统计推断得到回归系数的概率分布和置信区间。

  8. 决策树回归:适用于处理非线性关系和交互作用的场景,将输入空间划分成多个区域,并在每个区域内拟合一个局部线性回归模型。

  9. 随机森林回归:适用于处理过拟合和高维数据问题的场景,通过构建多个决策树,采用随机特征选择和自助重采样技术,从而可以得到更加鲁棒的回归模型。

  10. 梯度提升回归:适用于需要得到更加准确的回归模型的场景,通过迭代地加入新的回归树,使得每个回归树的预测结果越来越接近真实值。

二、回归方法的学习顺序和建议的学习方法:

  1. 线性回归:线性回归是回归分析的基础,是其他回归算法的基础。可以先学习线性代数和最小二乘法,然后再学习线性回归的概念和实现方法。

  2. 多项式回归:多项式回归是线性回归的扩展,可以处理非线性问题。建议学习完线性回归后再学习多项式回归。

  3. 岭回归和Lasso回归:这两种回归方法是正则化线性回归的扩展,可以防止过拟合。建议先学习线性回归和正则化的基本概念,再学习岭回归和Lasso回归的实现方法。

  4. 弹性网络回归:弹性网络回归是岭回归和Lasso回归的组合,可以同时处理线性和非线性关系。建议先掌握岭回归和Lasso回归,再学习弹性网络回归。

  5. 决策树回归和随机森林回归:这两种回归方法是基于决策树的算法,可以处理非线性问题和高维数据。建议先学习决策树的基本概念和实现方法,再学习随机森林回归。

  6. 支持向量回归:支持向量回归是一种非线性回归方法,可以处理复杂数据结构和高维数据。建议先学习支持向量机的基本概念和实现方法,再学习支持向量回归。

建议的学习方法包括:

  1. 先学习理论,再学习实践。先掌握回归方法的数学基础和理论知识,然后再通过实现代码来加深理解。

  2. 实践中不断调整参数和算法。在实践过程中,可以尝试不同的参数和算法来比较性能和效果,从而不断调整和优化。

  3. 研究应用场景和实际问题。回归方法的应用非常广泛,需要根据具体问题和数据集的特点来选择合适的算法和参数。因此,要结合实际应用场景和问题来研究回归方法。

三、最小二乘法(OLS)和完全最小二乘法(CLS)

二者都是用于估计线性回归模型参数的方法,但它们的计算方式有所不同。

最小二乘法是通过最小化残差平方和来估计模型参数的。它的基本思想是找到一组模型参数,使得模型预测值与真实值的残差平方和最小。最小二乘法是一种广泛使用的方法,因为它的计算过程简单,易于理解和实现。

与之不同的是,完全最小二乘法是一种更加复杂的方法。CLS可以处理包含多个自变量的模型,也可以处理存在测量误差的情况。它的主要思想是,将数据点投影到回归平面上,并找到一个回归平面,使得每个数据点在平面上的投影与其实际值之间的距离之和最小。CLS通常需要使用迭代算法来计算最佳回归平面,这使得它的计算成本比OLS更高。

因此,OLS是一种适用于简单线性回归的方法,而CLS则适用于更复杂的线性回归模型,尤其是在存在多个自变量和测量误差的情况下。在实践中,由于OLS的计算成本低,所以它仍然是最常用的线性回归方法之一。

四、胸径对于林业指标计算中的作用

胸径是用来计算森林蓄积量和生物量的指标。植被覆盖度是指在一定区域内植被的覆盖程度,通常使用遥感影像数据和图像处理算法来计算

五、经验模型和物理模型

经验模型是通过对现象进行观察、实验和数据分析,建立起一个基于经验和数据的数学模型,以描述和预测现象的行为和变化。它通常基于统计分析和机器学习技术,能够有效地预测数据,并具有较强的解释性。经验模型常常应用于自然、社会和经济现象的建模,如预测股票价格、气候变化趋势等。

物理模型则是通过对物理系统的物理规律进行建模,描述系统的结构和运动规律,以预测系统的行为和变化。它通常基于物理学原理,对物理量进行量化,用数学方程描述系统的运动、能量、力等物理量的变化,具有较高的准确性和可信度。物理模型常用于研究物理系统的运动、力学、热力学、电磁学等方面,如计算机仿真、天气预报等。

第二天4.4  星期二   回归指标

 今天主要研究了各个回归指标的特性与计算方法:

1.决定系数

决定系数(Coefficient of Determination, R^2)是一种衡量统计模型对数据拟合程度的常用指标。它表示因变量的变异中,能够由自变量解释的比例,其取值范围在0到1之间。R^2的定义如下:

R^2 = 1 - SSE/SST

其中,SSE表示残差平方和(Sum of Squares for Error),即模型拟合误差的总和,SST表示总平方和(Sum of Squares Total),即实际观测值与观测值平均数之差的平方和。R^2越接近1,说明模型对数据的拟合越好,即模型可以解释较大部分的观测值变异性;而R^2越接近0,则说明模型对数据的拟合越差,即模型无法解释大部分的观测值变异性。

R^2的优点在于,它不受样本大小和自变量个数的影响,同时具有直观性和易于解释的特点。因此,R^2常常被用于评估回归模型的预测精度和解释效果。但需要注意的是,R^2并不能完全代表模型的好坏,因为它只考虑了模型对数据拟合的效果,而未考虑其他因素,如模型的解释性、泛化能力等。因此,在评估模型时,除了R^2之外,还需要综合考虑其他指标和实际应用情况。

from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
import numpy as np
from sklearn.metrics import r2_score

# (1)导入数据
X, y = load_diabetes().data, load_diabetes().target

# (2)分割数据
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

# (3)训练
LR = LinearRegression()
performance = LR.fit(X_train, y_train)

# (4)预测(本例分必要)
y_pred_train = LR.predict(X_train)  # 在测试集合上预测
y_pred_test = LR.predict(X_test)  # 在测试集合上预测

# (5) 评估模型
print("训练集合上R^2 = {:.3f}".format(performance.score(X_train, y_train)))
print("测试集合上R^2 = {:.3f} ".format(performance.score(X_test, y_test)))

print("训练集合上R^2 = {:.3f}".format(r2_score(y_train, y_pred_train)))
print("测试集合上R^2 = {:.3f} ".format(r2_score(y_test, y_pred_test)))

np.set_printoptions(precision=3, suppress=True)
print('w0 = {0:.3f}'.format(LR.intercept_))
print('W = {}'.format(LR.coef_))

2.均方根误差

均方根误差(Root Mean Squared Error, RMSE)是衡量数值解和原始问题解析解之间差异的一种常用指标。RMSE是MSE的平方根,可以理解为预测误差的标准差,其计算公式如下:

RMSE = sqrt(1/n * sum((y_true - y_pred)^2))

其中,y_true表示原始问题的解析解,y_pred表示数值解,n为样本数。

与其他评价指标相比,RMSE具有以下优点:

  1. 对异常值比较敏感,能够反映出预测误差的分布情况。

  2. 相比平均绝对误差(MAE),均方误差(MSE)等指标,RMSE对预测误差较大的样本进行了更强的惩罚,因此更加重视大误差的样本。

  3. 与MSE和MAE等指标相比,RMSE的计算结果更加容易理解,其单位与原始数据的单位相同。

在实际应用中,RMSE常常被用于衡量回归模型的预测精度。一般来说,RMSE越小,表示模型的预测效果越好。

3.平均绝对误差

 4.均方误差(MSE)均方根误差(RMSE)

5.MAE:平均绝对误差;MAPE:平均绝对百分比误差

代码:

import numpy as np
from sklearn import metrics


# MAPE和SMAPE需要自己实现
def mape(y_true, y_pred):
    return np.mean(np.abs((y_pred - y_true) / y_true)) * 100


def smape(y_true, y_pred):
    return 2.0 * np.mean(np.abs(y_pred - y_true) / (np.abs(y_pred) + np.abs(y_true))) * 100


y_true = np.array([1.0, 5.0, 4.0, 3.0, 2.0, 5.0, -3.0])
y_pred = np.array([1.0, 4.5, 3.5, 5.0, 8.0, 4.5, 1.0])

# MSE
print(metrics.mean_squared_error(y_true, y_pred))  # 8.107142857142858
# RMSE
print(np.sqrt(metrics.mean_squared_error(y_true, y_pred)))  # 2.847304489713536
# MAE
print(metrics.mean_absolute_error(y_true, y_pred))  # 1.9285714285714286
# MAPE
print(mape(y_true, y_pred))  # 76.07142857142858,即76%
# SMAPE
print(smape(y_true, y_pred))  # 57.76942355889724,即58%

第三天4.5  星期三  Pillow库、点云格式、解析解和数值解

今天主要收获如下:

一、今天了解到了fls的数据属于扫描仪的原始数据,需要用法如的软件导出,因此数据处理通常使用las,e57,pcd等格式

二、解析解和数值解

解析解是指通过数学方法求出的一个方程或者函数的精确解。这种解法通常依赖于公式、运算符、函数、变量和初始条件等的组合和运用。求解得到的解是一个表达式,其在定义域上可以给出函数的精确值。

数值解是指使用数值方法通过计算机程序求出的一个方程或函数的近似解。数值解通常通过离散化(将问题离散为一组数字问题)和迭代求解(通过重复计算逐步逼近精确解)来得到。求解得到的解是一个数值的列表或数组,它代表了解的离散近似值。

在实际问题中,有些方程或函数的解析解很难甚至不可能找到,这时候数值解就变得尤为重要。通常情况下,数值解的精度可以通过适当的算法和调整参数进行控制。但是,与解析解相比,数值解的精度可能会受到舍入误差和算法本身的误差等因素的影响。

三、Pillow库

Pillow 是 Python 中一个非常流行的图像处理库,它可以用来处理各种图像格式,例如打开、保存、裁剪、旋转、缩放等。Pillow 是基于 Python Imaging Library (PIL) 开发的,但是比 PIL 更加易于使用和兼容性更好。

下面是一些常见的 Pillow 操作:

  1. 1打开和保存图像:
from PIL import Image

# 打开图像
image = Image.open("image.jpg")

# 保存图像
image.save("new_image.png")
  1. 2裁剪图像:
# 裁剪图像
cropped = image.crop((100, 100, 200, 200))

# 保存裁剪后的图像
cropped.save("cropped_image.jpg")
  1. 3旋转图像:
# 旋转图像
rotated = image.rotate(90)

# 保存旋转后的图像
rotated.save("rotated_image.jpg")
  1. 4缩放图像:
# 缩放图像
resized = image.resize((300, 300))

# 保存缩放后的图像
resized.save("resized_image.jpg")
  1. 5调整图像亮度和对比度:
# 调整图像亮度和对比度
from PIL import ImageEnhance

enhancer = ImageEnhance.Brightness(image)
brightened_image = enhancer.enhance(1.5)  # 亮度增强1.5倍
brightened_image.save("brightened_image.jpg")

enhancer = ImageEnhance.Contrast(image)
contrasted_image = enhancer.enhance(1.5)  # 对比度增强1.5倍
contrasted_image.save("contrasted_image.jpg")

这些只是 Pillow 提供的一部分功能,Pillow 还有很多其他功能,例如添加水印、调整颜色平衡、转换图像格式等等。

第四天4.6  星期四  两种点云叠加方法

 今天主要研究了两种点云叠加的方法以及一篇论文的阅读:

一、深度学习进行主被动遥感分类时有较高的难度,其中牵扯到太多知识点,了解可以,但深入学习需花费大量时间,因而要慎重考虑,若选择读博,深度学习毫无疑问要好好学习,但若只考虑毕业,我觉得还是另谋其他简单方向

二、两种点云叠加方法

1.包围盒的叠加

(1)假设有两组点云A,B,计算A的OBB包围盒
(2)寻找点云A的OBB包围盒内的点云B,这样就得到点云B相对A的重叠区域
(3)反之计算,得到点云A相对于B的重叠区域
(4)综上可以得到一组重叠区域的点云,可用于后续分析。

// 提取点云重叠区域.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <pcl/features/moment_of_inertia_estimation.h>
#include <vector>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>

#include<pcl/filters/crop_box.h>
#include <pcl/common/transforms.h>
#include<pcl/common/common.h>
#include<ctime>
//获取第二个点云相对第一个点云的重叠区域
void getOverlappedCloud(const pcl::PointCloud<pcl::PointXYZ>& cloud, const pcl::PointCloud<pcl::PointXYZ>& cloud2, pcl::PointCloud<pcl::PointXYZ>& overlapped_cloud2)
{
    pcl::MomentOfInertiaEstimation <pcl::PointXYZ> feature_extractor;
    feature_extractor.setInputCloud(cloud.makeShared());
    feature_extractor.compute();
    pcl::PointXYZ min_point_AABB;
    pcl::PointXYZ max_point_AABB;
    pcl::PointXYZ min_point_OBB;
    pcl::PointXYZ max_point_OBB;
    pcl::PointXYZ position_OBB;
    Eigen::Matrix3f rotational_matrix_OBB;
    feature_extractor.getAABB(min_point_AABB, max_point_AABB);
    feature_extractor.getOBB(min_point_OBB, max_point_OBB, position_OBB, rotational_matrix_OBB);
    //将点云旋转,使得第一个点云的OBB=AABB,以此获得第一个点云OBB区域内的第二个点云
    Eigen::Matrix4f tf = Eigen::Matrix4f::Identity();
    tf << (rotational_matrix_OBB.inverse());
    pcl::PointCloud<pcl::PointXYZ>cloud_tf, cloud2_tf;
    pcl::transformPointCloud(cloud, cloud_tf, tf);
    pcl::transformPointCloud(cloud2, cloud2_tf, tf);
    Eigen::Vector4f pmin, pmax;

    pcl::getMinMax3D(cloud_tf, pmin, pmax);
    pcl::PointCloud<pcl::PointXYZ> tf_overlapped_cloud2;
    std::vector<int> indices;
    //第二个点云旋转之后的点云重叠区域
    pcl::getPointsInBox(cloud2_tf, pmin, pmax, indices);
    pcl::copyPointCloud(cloud2_tf, indices, tf_overlapped_cloud2);
    //再变换到原始坐标系中
    pcl::transformPointCloud(tf_overlapped_cloud2, overlapped_cloud2, tf.inverse());

    std::cout << tf << std::endl;
    std::cout << rotational_matrix_OBB << std::endl;
}
int main(int argc, char** argv)
{
    auto start = std::clock();
    pcl::PointCloud<pcl::PointXYZ>cloud, overlapped_cloud;
    pcl::io::loadPCDFile("dem_transformed.pcd", cloud);

    pcl::PointCloud<pcl::PointXYZ>cloud2, overlapped_cloud2;
    pcl::io::loadPCDFile("feidimian.pcd", cloud2);

    getOverlappedCloud(cloud, cloud2, overlapped_cloud2);
    getOverlappedCloud(cloud2, cloud, overlapped_cloud);

    pcl::io::savePCDFile<pcl::PointXYZ>("重叠点云.pcd", overlapped_cloud);
    pcl::io::savePCDFile<pcl::PointXYZ>("重叠点云2.pcd", overlapped_cloud2);
    auto end = std::clock();
    std::cerr << "耗时" << std::difftime(end, start) << "ms" << std::endl;
    std::cout << "Hello World!\n";
}


2.八叉树方法

(1)假设有两组点云A,B,对A建立八叉树
(2)遍历B中所有点,查询其对应的A的体素是否存在点云(即同一体素中是否同时存在AB点云),若存在,则该点为B点云的重叠点云
(3)同理,得到A中重叠点云

// 提取点云重叠区域2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include<pcl/io/pcd_io.h>
#include<pcl/point_cloud.h>
#include<pcl/point_types.h>
#include<pcl/octree/octree.h>
#include<ctime>
using point = pcl::PointXYZ;
using cloud = pcl::PointCloud<point>;
void getOverlappedCloud(const pcl::PointCloud<pcl::PointXYZ>& cloud1, const pcl::PointCloud<pcl::PointXYZ>& cloud2, pcl::PointCloud<pcl::PointXYZ>& overlapped_cloud2)
{
    double radius = 3.0;
    pcl::octree::OctreePointCloudSearch<point> octree(radius);
    octree.setInputCloud(cloud1.makeShared());
    octree.addPointsFromInputCloud();

    //pcl::PointCloud<point> overlapped_2;
    for (size_t i = 0; i < cloud2.size(); ++i)
    {
        std::vector<int> indices;
        octree.voxelSearch(cloud2.points[i], indices);
        pcl::PointCloud<point> cloud_out;
        if (indices.size())
        {
            overlapped_cloud2.push_back(cloud2.points[i]);
        }

    }
}
int main()
{
    auto start = std::clock();
    pcl::PointCloud<point> cloud1, cloud2;
    pcl::io::loadPCDFile<point>("SHCSCloud.pcd", cloud1);
    pcl::io::loadPCDFile<point>("SHCSCloud副本.pcd", cloud2);
    cloud overlapped1, overlapped2;
    getOverlappedCloud(cloud1, cloud2, overlapped2);
    getOverlappedCloud(cloud2, cloud1, overlapped1);

    pcl::io::savePCDFile("重叠1.pcd", overlapped1);
    pcl::io::savePCDFile("重叠2.pcd", overlapped2);
    auto end = std::clock();
    std::cerr << "耗时" << std::difftime(end, start) << "ms" << std::endl;
    std::cout << "Hello World!\n";
}


第五天4.14  星期五   QPainter、QT基础操作

今天再一次进行QT的相关学习,主要是折线图的绘制,以及鼠标操作

详见项目文件untitled5(Qpaint鼠标操作)

一、painter.begin(&pixmap)、painter.end();代码理解

QPainter 是 Qt 框架中用于绘制图形的类。它提供了一系列的绘图函数,可以在图像上绘制各种形状、文字、图案等等。要在图像上绘制图形,必须先创建一个 QPainter 对象,然后使用它来进行绘图操作。

在这段代码中,首先创建了一个名为 painterQPainter 对象,然后使用 begin() 函数将其与 pixmap 对象关联起来。这意味着在接下来的绘图操作中,所有的绘图函数调用都将作用于 pixmap 图像上。

需要注意的是,为了保证正确的绘图操作,必须在绘图完成后及时结束 QPainter 的绘图操作,并释放相关资源。在这段代码中,使用 end() 函数结束了 painter 对象的绘图操作,以释放其所占用的资源。

二、当前对象的坐标  prePoint=event->pos()

当前对象的坐标指的是在当前对象的坐标系中描述的位置。例如,在一个窗口(QWidget)对象中,(0,0)通常是窗口的左上角。鼠标事件中的pos()函数返回了鼠标指针相对于当前对象坐标系的位置。因此,在Myplot对象的mousePressEvent()函数中,prePoint被设置为鼠标指针相对于Myplot对象坐标系的位置。

三、QPainter painter(this)QPainter painter 的关系

QPainter painter(this)QPainter painter 这两句代码都创建了一个 QPainter 对象,但是它们的作用略有不同。

QPainter painter(this) 创建的 QPainter 对象将绘制在当前控件上,也就是 this 所指代的控件上。例如,如果 this 是一个 QWidget 对象,那么这句代码将创建一个 QPainter 对象,用于在该 QWidget 控件上进行绘图操作。这种用法通常用于在控件上进行自定义绘图,例如在一个 QLabel 控件上绘制一个自定义的图片等等。

QPainter painter 创建的 QPainter 对象没有指定绘制目标,需要在绘图操作时使用 begin() 函数来指定。例如,可以在 begin() 函数中传入一个 QPixmap 对象,用于在该图像上进行绘图操作。这种用法通常用于在内存中创建一个图像,并在其中进行绘图操作,例如生成一个验证码图片等等。

需要注意的是,无论是哪种用法,都需要在绘图操作完成后使用 end() 函数结束 QPainter 的绘图操作,以释放相关资源。

四、头文件保护

#ifndef MYPLOT_H
#define MYPLOT_H

这两句是头文件的标准写法,称为“头文件保护(Header Guard)”,用于避免同一个头文件被多次包含。当一个头文件被多次包含时,头文件保护可以确保它只被编译一次。当第一次编译时,预处理器会定义 MYPLOT_H,后续再包含该头文件时,由于 MYPLOT_H 已经被定义,预处理器会忽略整个文件的内容,从而避免重复定义。

同时这段代码也是预处理器指令,用于告诉编译器要包含哪些文件。

#ifndef 是一个条件编译预处理器指令,意为“如果未定义”。如果该标识符已定义,则跳过后面的代码,否则就编译该代码,直到 #endif 指令。通常在头文件中使用此指令,以避免多次包含同一个头文件。

#define 是另一个预处理器指令,用于定义一个宏。在此情况下,宏为 MYPLOT_H。如果 MYPLOT_H 宏未定义,则定义它,然后编译后面的代码,最后在文件结尾处添加 #endif 来结束条件编译指令的区域。这样做的目的是防止同一文件的多次包含。

五、Q_OBJECT的含义

Q_OBJECT 是 Qt 框架提供的一个宏,用于声明一个类需要使用 Qt 的元对象系统(Meta-Object System),以支持信号(Signal)和槽(Slot)的机制。

在使用信号和槽的机制之前,需要在类声明中加入 Q_OBJECT 宏。在该宏被解析时,Qt 会自动创建一个内部的元对象系统,该系统可以在运行时通过反射机制动态地查询类的信息,以便实现信号和槽的连接、事件处理等机制。

需要注意的是,如果一个类中包含 Q_OBJECT 宏,则该类的源文件必须通过 MOC(Meta-Object Compiler)预处理器处理,以生成针对元对象系统的代码。这些代码会被编译成一个与源文件同名的 .moc 文件,并由编译器在链接阶段与其他目标文件一起编译成最终的可执行程序。

Q_OBJECT 是一个宏,它告诉编译器这个类是一个 Qt 的元对象类(Meta-Object Class),需要使用 MOC 预处理器处理。

在使用 Q_OBJECT 宏定义的类中,可以使用 Qt 的元对象系统(Meta-Object System)提供的功能,例如信号和槽机制、动态属性、反射等。

在使用 Q_OBJECT 宏定义的类中,MOC 预处理器会自动生成一些额外的 C++ 代码,其中包括了一些字符串、函数、变量和代码等,这些代码用于实现 Qt 的元对象系统提供的功能。

为了让 MOC 预处理器正确处理头文件中的 Q_OBJECT 宏,需要在项目的 .pro 文件中添加如下语句:

makefileCopy code

QT += core

这会告诉 qmake 工具链接 Qt 的核心库,其中包含了 MOC 预处理器。当编译时,MOC 预处理器会自动被调用,生成额外的 C++ 代码。

六、class Myplot : public QWidget含义

这句代码定义了一个名为Myplot的类,它是QWidget类的一个派生类。也就是说,Myplot类继承了QWidget类的所有成员函数和成员变量,并且可以在此基础上进行扩展。QWidget是QT中所有可视控件的基类,因此Myplot类可以被用于创建一个新的QT控件,可以在窗口中显示和交互。

七、Myplot(QWidget *parent = 0)的含义

在C++中,函数可以有参数。这里的参数是一个QWidget指针,它是一个名为“parent”的指针,指向一个QWidget对象。这个指针默认值为0,表示如果用户没有传递指向父QWidget对象的指针,则使用默认值0。

这个参数的作用是将父窗口传递给Myplot构造函数。这对于在子窗口中显示Myplot小部件很有用。如果没有传递父指针,则Myplot小部件将显示在屏幕上,而不是在父窗口中。

        7.1与QWidget* parent = nullptr的区别

QWidget *parent = 0 和 QWidget *parent = nullptr 都是表示将 parent 指针设置为 null 指针,也就是没有父对象。不过在 C++11 标准引入 nullptr 后,建议使用 QWidget *parent = nullptr,因为 nullptr 更加明确地表示一个空指针,而且避免了将 0 误解为一个整数值的可能性。

        7.2每一个构造函数的参数都应该是这个吗

不是每个构造函数都应该有这个参数。这个参数通常是用于实现父子关系的,即在创建一个对象时,需要告诉这个对象它的父对象是谁。如果这个对象是窗口部件(QWidget)的子类,那么这个参数通常会传递给QWidget的构造函数,用于实现窗口部件之间的父子关系。

在早期版本的Qt中,这个参数通常会被设置为0(也就是NULL),表示这个对象没有父对象。但是在C++11标准中,建议使用nullptr来代替0,因为nullptr具有更明确的语义,可以防止一些潜在的错误。

八、publicprotectedprivate的区别

在 C++ 中,类的成员变量可以被定义为 publicprotectedprivate。这些关键字的意义如下:

  • public:成员可以被类外的代码访问。
  • protected:成员可以被类外的代码访问,但只能通过类的继承体系来访问。
  • private:成员只能被类内的代码访问。

一般来说,我们应该尽可能将成员变量定义为 private,只在必要时使用 publicprotected。这是因为将变量定义为 public 会导致其暴露在类外,可能会对数据的封装性和类的可维护性造成影响。

通常情况下,将数据成员定义为 private,并提供公有的成员函数(getter 和 setter)来访问和修改数据成员。这种方式称为封装,可以有效地保护数据,同时也可以使代码更加可维护和易读。对于一些需要在类外部访问的成员,可以定义为 publicprotected,但需要考虑到成员的访问控制和类的封装性。

一些技巧:

  • 优先使用成员函数来访问和修改数据成员,而不是直接操作成员变量。
  • 将成员变量定义为 conststatic 可以提高代码的安全性和可维护性。
  • 对于一些需要在子类中访问的成员,可以定义为 protected
  • 对于一些不需要在子类或类外部访问的成员,应该定义为 private

需要根据具体情况来决定成员变量的访问控制和定义方式。

九、Widget::Widget(QWidget* parent) : QWidget(parent) , ui(new Ui::Widget)

这是 Widget 类的构造函数的实现。它接受一个 QWidget* 类型的参数 parent,并调用基类 QWidget 的构造函数并传入 parent,以初始化 Widget 对象。

其次,它创建了一个新的 Ui::Widget 对象,并将其存储在成员变量 ui 中。Ui::Widget 是一个自动生成的类,它包含了 UI 文件中所有部件的指针。我们可以使用它来访问 UI 文件中的各个部件,例如按钮、标签等。

十、问题汇总:

1.#include "ui_Text1.h"要与Ui::Text1Class ui;放在同一个文件下

2..ui文件修改后又再次自动变成修改前的代码:

在VS里直接编辑.ui用编译器打开,来直接改.ui

3.无法解析的外部符号 "__declspec(dllimport) public: __cdecl QApplication::QApplication(int &,char * *,int)" (__imp_??0QApplication@@QEAA@AEAHPEAPEADH@Z),函数 main 中引用了该符号 QtApplication1 E:\yan1\exercise\C_C++\QT\QtApplication1\QtApplication1\源.obj 1

原因:未连接模板,在VS的插件中添加即可

4.Pro转sln,用vs的插件打开,然后保存为sln即可

5.0x00007FFD4263ED6A (Qt5Cored.dll)处(位于 DynamicPlot.exe 中)引发的异常: 0xC0

原因:QLineSeries* series = new QLineSeries();要改成series = new QLineSeries();

QLineSeries*要在.h声明,若在cpp里会出野指针

除了main.cpp之外

第六天4.15  星期六  QT折线图  时间轴

今天主要学习了QT中折线图,时间轴的绘制、

一、使用QHBoxLayout添加布局控件

在Qt中,使用布局是管理窗口中小部件的定位和大小的一种常见方式。

在这种情况下,QChartView部件被添加到一个QHBoxLayout布局中,然后被设置为QMainWindow中央部件的布局。这使得图表视图可以在窗口调整大小或改变布局时自动调整大小和位置。

创建一个布局并将图表视图添加到其中还提供了更多的灵活性,以备你以后想在主窗口中添加其他部件。你可以将这些部件添加到同一个布局或不同的布局中,并通过调整布局属性来管理它们相对于图表视图的位置。

总的来说,使用布局可以更好地控制窗口中小部件的定位和大小,并具有灵活性,是Qt编程的最佳实践。

二、计时器startTimer与timerEvent的关系

当使用startTimer方法启动一个定时器后,Qt会在事件循环中安排一个QTimerEvent事件,在计时器到期时自动触发。此时,Qt会自动调用定时器所在对象的timerEvent方法来处理该事件。因此,在你的代码中,当你使用startTimer(300)时,Qt会安排一个计时器,并在计时器到期时调用timerEvent方法,即使你没有显式地调用该方法

三、用代码分别表示QObject和QTimer两种计时器的创建,开始,获取ID,结束

1.创建QObject计时器:

//创建计时器对象

QTimer *timer = new QTimer(this);

//开始计时,设置时间间隔为1000毫秒

timer->start(1000);

//获取计时器ID

int timerId = startTimer(1000);



//在计时器触发的槽函数中结束计时器

void MyClass::onTimeout() {

    //结束计时器

    timer->stop();

    //结束QObject计时器

    killTimer(timerId);

}

2.创建QTimer计时器:

//创建计时器对象

QTimer *timer = new QTimer(this);

//设置时间间隔为1000毫秒

timer->setInterval(1000);

//开始计时

timer->start();

//获取计时器ID

int timerId = timer->timerId();



//在计时器触发的槽函数中结束计时器

void MyClass::onTimeout() {

    //结束计时器

    timer->stop();

    //结束QTimer计时器

    timer->deleteLater();

}

注意:QObject计时器的开始和设置时间间隔是在startTimer()函数中完成的,而QTimer计时器是通过setInterval()函数设置时间间隔,然后调用start()函数开始计时。另外,QObject计时器在结束时使用killTimer()函数,而QTimer计时器使用deleteLater()函数。

四、QT_CHARTS_USE_NAMESPACE的含义

QT_CHARTS_USE_NAMESPACE 是一个在 Qt Charts 模块中定义的宏,用于访问 Qt Charts 命名空间中的所有类。它被用来避免在Qt Charts模块的每个类名前重复输入QtCharts::。

例如,你可以简单地在你的代码中包括QT_CHARTS_USE_NAMESPACE,然后直接使用QChart,而不是每次想使用QChart类时都写QtCharts::QChart。

注意,重要的是只在源文件而不是头文件中使用这个宏,以避免命名空间的碰撞。

五、QT_BEGIN_NAMESPACE 和 QT_END_NAMESPACE的含义

QT_BEGIN_NAMESPACE 和 QT_END_NAMESPACE 是 Qt 提供的用于定义命名空间的预处理器指令。这些宏用于确保所有的Qt类、函数和宏都被定义在Qt命名空间内。

QT_CHARTS_USE_NAMESPACE是另一个由Qt提供的预处理器指令,用于在你的代码中包含Qt图表命名空间。

在你提供的代码中,QT_BEGIN_NAMESPACE和QT_END_NAMESPACE被用来定义Ui命名空间,它包含Widget类。这是为了确保Widget类被定义在Qt命名空间中。

QT_CHARTS_USE_NAMESPACE用于在代码中包含Qt图表命名空间。

六、namespace Ui { class MainWindow; }含义

这个代码片段定义了一个名为"Ui"的命名空间,并在其中定义了一个名为"Widget"的类。通常,这段代码是由Qt的UI文件自动生成的,用于将UI文件中定义的界面与应用程序的逻辑代码分离。

在生成的代码中,"Ui::Widget"类是在主类中实例化和使用的,而实际的UI元素和布局信息则在UI文件中描述和存储。这种设计模式可以使开发者更轻松地维护和修改UI界面,同时也可以提高代码的可读性和可维护性。

七、dynamic_cast函数

dynamic_cast是一个C++操作符,可以用来对一个对象指针进行运行时类型信息(RTTI)检查,并在指针类型之间安全转换,包括多态类型。在代码中,

QValueAxis *axisY = dynamic_cast<QValueAxis*>(this->chart()->axisY()); 

它试图将this->chart()->axisY()返回的指针转换成QValueAxis类型的指针。

如果由this->chart()->axisY()返回的指针可以安全地转换为QValueAxis*,那么转换将成功,指针将被分配到axisY。否则,如果指针不是QValueAxis的一个实例,那么dynamic_cast 将返回一个空指针。

在这个特定的代码中,它假定axisY对象是QValueAxis类型的(而不是QCategoryAxis或其他类型的轴),并使用它来访问和修改图表Y轴上显示的数值范围。

八、setMouseTracking函数

this->setMouseTracking(false);

这段代码设置了QGraphicsView的mouseTracking属性为false。当mouseTracking属性为false时,QGraphicsView只会在鼠标按下、释放、拖动时触发mousePressEvent、mouseReleaseEvent和mouseMoveEvent事件。如果mouseTracking属性为true,则鼠标在移动时也会触发mouseMoveEvent事件。

因此,设置mouseTracking属性为false可以减少不必要的事件触发,提高应用程序的性能。但是,如果需要实现鼠标悬停的效果,就需要将mouseTracking属性设置为true。

九、QPen pen(0x000001)函数设置颜色

//设置pen的颜色 
pen.setWidth(1); 
splineSeries->setPen(pen); 
//设置画笔颜色和大小

这段代码用来设置QSplineSeries对象的画笔属性,QSplineSeries是一种基于样条曲线的折线图,通过对数据进行插值计算,使得折线更加平滑,画笔属性决定了折线的颜色和宽度。

在这里,首先创建了一个QPen对象,0x000001是一个16进制的颜色值,表示黑色(RGB值为(0, 0, 1))。然后通过setWidth()方法设置画笔的宽度为1,最后通过splineSeries对象的setPen()方法设置折线的画笔为该QPen对象。

注意,在使用QSplineSeries绘制折线图时,建议将QPen对象的宽度设置为1,以获得最佳的绘图效果。

如果使用的是QLineSeries是不是就不用设置了

是的,如果使用的是 QLineSeries,则不需要设置 QPen,因为 QLineSeries 默认的画笔就是黑色,线条宽度为 2。但如果需要自定义线条颜色、宽度等属性,也可以通过 setPen 方法来实现。

十、问题汇总:

1.在Qdesigner新增加的控件可能没有,要用VS重新打开项目,而不是重新打开VS

2.chart->setTitle("实时动态曲线");函数在使用VS编译时会报错,而使用Qcreator时不会

3.cmake要全英文路径

4.QVTKOpenGLNativeWidget代替QVTKWidget        

第七天4.16  星期日   横坐标更改   八叉树

今天主要学习了折线图横坐标的标签更改,以及八叉树的学习研究:

一、qobject_cast 函数:转化标签类型

设置垂直轴的标签格式,可以使用 QValueAxis 类的 setLabelFormat 方法。

chart->axes(Qt::Vertical).back() 返回的是一个 QAbstractAxis 类型的指针,它是 QValueAxis 类型的基类。因此,你需要将其转换为 QValueAxis 类型,然后才能使用 setLabelFormat 方法。

QValueAxis* verticalAxis = qobject_cast<QValueAxis*>(chart->axes(Qt::Horizontal).back());
    if (verticalAxis) {
        verticalAxis->setLabelFormat("%d"); // 设置标签格式为小数点后两位
    }

在这个例子中,我们首先使用 qobject_cast 函数将 QAbstractAxis 指针转换为 QValueAxis 指针,然后将其赋值给 verticalAxis 变量。如果转换成功,那么我们就可以使用 setLabelFormat 方法来设置标签格式了。这里的标签格式是 "%.2f",它表示显示小数点后两位。

请注意,如果 QAbstractAxis 指针指向的实际上是一个不同类型的轴,而不是 QValueAxis,那么转换操作将失败,verticalAxis 变量将为 nullptr,因此在使用 verticalAxis 指针之前应该进行检查。

二、static_cast<int>方法

要让 m_coordItem->setText() 输出整数,可以使用 QString::number() 方法将浮点数转换为整数。具体来说,可以将 curVal.x()curVal.y() 分别转换为整数,并将它们插入到字符串中。

//输出int
const QPoint curPos = event->pos();
QPointF curVal = this->chart()->mapToValue(QPointF(curPos));
int x = static_cast<int>(curVal.x());
int y = static_cast<int>(curVal.y());
QString coordStr = QString("X = %1, Y = %2").arg(x).arg(y);
m_coordItem->setText(coordStr);

//输出String
const QPoint curPos = event->pos();
QPointF curVal = this->chart()->mapToValue(QPointF(curPos));
QString coordStr = QString("X = %1, Y = %2").arg(curVal.x()).arg(curVal.y());
m_coordItem->setText(coordStr);

在这个例子中,我们首先将 curVal.x()curVal.y() 分别转换为整数,并将它们赋值给 xy 变量。这里使用 static_cast<int>(curVal.x()) 将浮点数转换为整数。然后,我们使用 QString::arg() 方法将 xy 插入到字符串中,这个字符串将被设置为 m_coordItem 的文本内容。这样,m_coordItem->setText() 就会输出整数。

三、now.addSecs(i * 60)

now.addSecs(i * 60)是将当前时间now增加i分钟后得到的时间。其中i * 60将i分钟转换为秒数。

四、八叉树和四叉树的关系

四叉树和八叉树都是常见的树形数据结构,用于分割二维和三维空间。

四叉树(Quadtree)是一种将二维空间划分为四个象限的树形结构。每个节点可以有最多四个子节点,分别代表左上、右上、左下和右下四个象限。通常情况下,四叉树的根节点表示整个空间,而子节点则表示空间的更小的区域。四叉树的应用包括图像处理、地理信息系统和计算机游戏等领域。

八叉树(Octree)是一种将三维空间划分为八个子空间的树形结构。每个节点可以有最多八个子节点,分别代表上、下、左、右、前、后六个方向。八叉树的根节点表示整个空间,而子节点则表示空间的更小的子空间。八叉树的应用包括三维图形学、计算机辅助设计、物理仿真和计算机视觉等领域。

四叉树和八叉树都是空间划分的有效工具,可以大大提高算法的效率。但是,在处理非均匀分布数据时,它们可能会出现不平衡的问题,需要特别的算法来解决。

也就是说四叉树应用与二维,八叉树应用与三维,四叉树主要应用于二维空间,而八叉树则主要应用于三维空间。这是因为在二维空间中,将空间划分为四个象限已经足够描述大部分情况,而在三维空间中,需要将空间划分为八个子空间才能充分描述空间中的各种情况。当然,这并不是说四叉树不能应用于三维空间,或者八叉树不能应用于二维空间,但它们的效率可能会受到一些限制。

五、八叉树的使用方法以及与点云数据的关系

Q:八叉树会增加运行速度吗

使用八叉树可以提高一些运行速度,但这取决于具体应用场景和算法实现。

一般来说,使用八叉树可以加速一些需要对点云数据进行查找、遍历或者近邻搜索等操作的算法。八叉树可以将点云数据按空间位置进行分割,以便更快地对其进行访问。例如,在点云配准和3D重建等应用中,使用八叉树可以提高匹配点对的计算速度,降低算法复杂度。

但是,在某些情况下,八叉树的使用可能会增加运行时间和空间复杂度。例如,如果点云数据非常密集或者八叉树的分辨率设置不合理,将会导致八叉树的节点数目急剧增加,从而降低算法的效率。

因此,是否使用八叉树需要根据具体应用场景和算法实现来评估其效果。

Q:如何完全掌握八叉树算法

要完全掌握八叉树算法,需要具备一些前置知识和掌握一定的技能。

首先,需要掌握C++编程语言和数据结构的基础知识,了解指针、数组、链表、树等基本数据结构的使用方法和原理。

其次,需要了解点云数据的基本概念和格式,熟悉点云数据处理的相关算法和库,如PCL、Open3D等。

接着,需要了解八叉树的原理和实现方法,包括八叉树的数据结构、节点的定义和属性、节点的访问和遍历等。

最后,需要进行实际的编程练习和项目实践,加深对八叉树算法的理解和掌握,比如实现点云配准、重建、聚类等算法。

以下是一些具体的建议,有助于学习和掌握八叉树算法:

  1. 学习八叉树的基本概念和原理,包括节点的定义和属性、节点的访问和遍历、八叉树的数据结构等。
  2. 基于现有的八叉树库或者代码实现,了解八叉树的具体应用场景和使用方法,比如点云配准、重建、聚类等。
  3. 实践项目,应用八叉树算法解决实际问题,比如建立场景地图、进行目标检测、分割等。
  4. 参考相关文献和学术论文,深入了解八叉树算法的理论和最新研究成果。

总之,要完全掌握八叉树算法,需要不断进行实践和学习,提高编程技能和理论水平,并尝试将其应用到实际问题中去。

Q:八叉树和点云相关的常见应用领域

八叉树和点云相关的应用非常广泛,下面是其中几个常见的应用领域:

  1. 三维重建和建模:八叉树可以用于对点云进行采样、滤波和体素化,从而生成更加稠密、准确的三维重建和建模结果。例如,在三维重建中,可以使用八叉树对输入的点云进行体素化,生成稠密的体素网格,并应用网格重建算法对其进行表面重建。
  2. 点云配准:八叉树可以用于加速点云配准算法。通过对输入点云进行体素化,将匹配点对的计算复杂度从O(n^2)降低到O(nlogn)或者更低,提高算法的效率和精度。例如,在配准算法中,可以使用八叉树生成特征描述子,从而实现快速、准确的匹配。
  3. 目标检测和识别:八叉树可以用于对点云数据进行分割和聚类,从而实现目标检测和识别。例如,在无人驾驶汽车中,可以使用八叉树对点云数据进行分割和聚类,从而检测和识别道路、车辆、行人等物体。
  4. 点云处理和分析:八叉树可以用于对点云数据进行滤波、分割、拟合、分析等操作。例如,在计算机视觉中,可以使用八叉树对输入的点云进行滤波和分割,从而提高图像处理和分析的效率和精度。

总之,八叉树和点云相关的应用非常广泛,可以应用于多个领域和任务中,为研究者和工程师们提供了很多方便和创新的可能性。

六、八叉树的C++代码

#include <iostream>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/octree/octree.h>

int main(int argc, char** argv)
{
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);

    // Fill in the cloud data
    cloud->width    = 1000;
    cloud->height   = 1;
    cloud->is_dense = false;
    cloud->points.resize(cloud->width * cloud->height);

    for (size_t i = 0; i < cloud->points.size (); ++i)
    {
        cloud->points[i].x = 1024.0f * rand () / (RAND_MAX + 1.0f);
        cloud->points[i].y = 1024.0f * rand () / (RAND_MAX + 1.0f);
        cloud->points[i].z = 1024.0f * rand () / (RAND_MAX + 1.0f);
    }

    // Octree resolution - side length of octree voxels
    float resolution = 128.0f;

    // Instantiate octree-based point cloud change detection class
    pcl::octree::OctreePointCloudChangeDetector<pcl::PointXYZ> octree(resolution);

    // Add points to the octree
    octree.setInputCloud(cloud);
    octree.addPointsFromInputCloud();

    // Switch octree buffers: This resets octree but keeps previous tree structure in memory.
    octree.switchBuffers();

    // Generate output cloud with points that have differences between buffered and current octree state
    pcl::PointCloud<pcl::PointXYZ>::Ptr output(new pcl::PointCloud<pcl::PointXYZ>);
    octree.getPointIndicesFromNewVoxels(*output);

    std::cout << "Input cloud: " << cloud->size() << " points" << std::endl;
    std::cout << "Output cloud: " << output->size() << " points" << std::endl;

    return 0;
}

该示例使用了PCL库中的pcl::octree::OctreePointCloudChangeDetector类来实现八叉树。该类可以用于检测两个点云之间的差异,它包含两个主要步骤:添加点云数据和获取差异点云。

在该示例中,我们首先定义了一个pcl::PointCloud<pcl::PointXYZ>类型的指针cloud,并使用随机数填充了点云数据。然后,我们定义了一个resolution变量,用于设置八叉树的分辨率,即八叉树中每个体素的边长。接下来,我们创建了一个pcl::octree::OctreePointCloudChangeDetector对象octree,并将分辨率设置为resolution

然后,我们将点云数据设置为octree对象的输入数据,并使用addPointsFromInputCloud()方法将其添加到八叉树中。接着,我们调用switchBuffers()方法来切换八叉树的缓冲区,将当前的八叉树状态存储在内存中。

最后,我们使用getPointIndicesFromNewVoxels()方法获取差异点云数据,并将其存储在名为outputpcl::PointCloud<pcl::PointXYZ>类型的指针中。最后,我们输出输入点云和输出点云的大小。

需要注意的是,该示例中使用的是点云变化检测的例子,但是八叉树也可以用于其他类型的点云操作,比如聚类、重建等等。另外,在实际应用中,可以根据具体需求调整分辨率等参数,以获取更好的结果。

七、八叉树的Python代码

import open3d as o3d

# 读取点云数据
pcd = o3d.io.read_point_cloud("pointcloud.ply")

# 构建八叉树
octree = o3d.geometry.Octree(max_depth=5)
octree.convert_from_point_cloud(pcd)

# 保存八叉树
o3d.io.write_octree("octree.ot", octree)

在上述代码中,首先使用Open3D库的read_point_cloud函数读取点云数据,然后构建一个Octree对象,并指定最大深度为5。接着,调用convert_from_point_cloud方法将点云数据转换为八叉树,并将结果保存到octree对象中。最后,调用write_octree函数将八叉树保存到文件中。

需要注意的是,上述代码中使用的Octree类是Open3D库中的一种基于CPU的八叉树实现。如果需要使用基于GPU的八叉树实现,可以使用Open3D库的KDTreeFlann类。此外,构建八叉树时,需要根据实际情况调整最大深度参数,以保证八叉树的质量和效率。

八、问题汇总:

1.获取QchartView内置的chart

QChart* chart = ui->chartView->chart();

2.添加了横坐标显示时间以后,能正常运行,但出现报错问题

提示窗口:ASSERT failuer in QList<T>::at "index out of range"

chart->removeAxis(chart->axes(Qt::Horizontal).at(1));
//可能会是.at(1)的问题,list数组里没有1,最多就到0

本周总结:

由于各种原因,这两周公用一篇文档, 以后不出意外情况,每周都进行学习记录:

1.回归的概念,物理模型和经验模型

2.精度评价的各项指标:如均方根误差、决定系数

3.点云格式、解析解和数值解

4.点云叠加的两种方法、八叉树的概念

5.QPaint、Qchart的学习以及二者的区别和联系

6.时间轴的绘制,横坐标轴的变更

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值