当前位置: 首页 > article >正文

使用PyQt5的图形用户界面(GUI)开发教程

文章目录

  • 写在前面
  • 一、PyQt5的安装
    • 1.1 使用Conda管理环境
      • 1.1.1 新建环境
      • 1.1.2 `conda list`和`pip list`的区别
      • 1.1.3 `conda install`和`pip install`的区别
    • 1.2 安装PyQt5和Qt Designer
    • 1.3 VsCode中配置Qt Designer
  • 二、PyQt5的UI设计
    • 2.1 `.ui`文件设计
    • 2.2 `.qrc`文件建立
    • 2.3 `qss`设计
  • 三、PyQt5的逻辑设计
    • 3.1 新建.py逻辑文件
      • 3.1.1 主窗口初始化
      • 3.1.2 初始化界面
      • 3.1.3 绑定ui事件处理机制
      • 3.1.4 初始化标志位
      • 3.1.5 检查标志位状态
      • 3.1.6 读取数据输入
      • 3.1.7 根据不同的滤波方法显示不同的原理公式
      • 3.1.8 为程序设置icon
      • 3.1.9 执行仿真
      • 3.1.10 保存数据
      • 3.1.11 分辨率自适应
    • 3.2 最终成品
  • 四、总结
  • 参考文献

写在前面

之前的博客文章“图形用户界面(GUI)开发教程”(https://leilie.top/2024-01-20/Study-GUI)里介绍了使用MATLAB设计GUI,但使用MATLAB设计GUI存在三个问题:

  • 移植性差。比如开发这个GUI的版本是MATLAB 2018,那么在其他电脑上打开该程序将无法正常运行。一般都会在其他电脑上安装与开发该GUI的版本相同的MATLAB才能较好地避免该问题。
  • 风格化差。使用MATLAB设计GUI时,可以自由发挥创意地设计界面的空间小,只能在MATLAB既有框架下进行设计,所以开发出来的GUI不符合现代审美。(补丁:我用的是MATLAB 2018版本,或许最新的MATLAB 2025已经很好地解决了这个问题?)
  • 可读性差。GUI中各部件的逻辑均依赖回调函数实现,在复杂的GUI里,往往会迷失在浩如烟海的代码中;而且因为逻辑是依靠回调函数实现的,所以参数传递十分麻烦,增加了开发难度。

所以选择使用Python开发GUI。

为什么选择Python呢?因为我对于C/C++的使用不太熟练,用Python用的更加顺手。目前在想,现今阶段是不是精通MATLAB和Python就可以了(?)。科学计算的任务交给MATLAB,其他剩下的所有事情全部使用Python搞定。因为是个人开发者,对编译速度的要求不那么紧迫,加上Python的简便性,所以目前来说,使用Python已经足够满足我的需求。

再者写这篇博客的目的是防止自己忘记。或者说,很久之后因为某些需求得让我重新捡起来这项技能,因为有这些文章在,能够让我快速地回忆起以前掌握的知识。

所以,这篇博客的预备知识就是需要你已经掌握了如下技能:

  • MATLAB GUI的设计,熟悉MATLAB GUI各个部件的功能;
  • Python的安装和使用;
  • Conda的安装和使用;
  • VsCode的安装和使用。

那么开始这篇博客。

一、PyQt5的安装

1.1 使用Conda管理环境

1.1.1 新建环境

管理环境的方式有很多种,比如venvpyenvconda,我用conda来管理环境。因为conda可以创建独立的环境,并且给每个独立的环境安装不同的包。调出conda终端界面,新建环境,输入代码:

conda create -n [your_env_name] python==3.12.1

这里我新建的环境名为helloworld,并且指定了python的版本是3.12.1,因为之前的项目里使用的是3.12.1,为了保证以前的代码不出错,所以延续了这个python版本。

创建好新环境后,使用下面代码查看环境是否创建成功,为

conda env list

出现如下图所示的界面,即说明安装成功。

在这里插入图片描述
激活新创建的环境,代码为

conda activate helloworld

1.1.2 conda listpip list的区别

这里我遇见了一个问题。使用pip list查看该环境安装的包,发现了很多没有安装在这个环境里的包,在网络上搜索之后发现是电脑本地python安装的包污染了conda创建的新环境的pip list索引,实际上新环境里没有这些包的存在。

不过这个问题很影响查看新环境到底安装了哪些包。比如说,使用pip list查看安装的包,显示有pandas;再使用conda list查看安装的包,显示没有pandas。那么在写代码的时候,就很容易迷惑:到底pandas安装上没有?

在网络上查询了很多博客,解决无果,最后选择了掩耳盗铃,使用conda list来查看新环境的包。这种方式得到的环境所安装的包信息是最可信的。

1.1.3 conda installpip install的区别

引申一下,这里就涉及到使用conda install安装python包和pip install安装python包的区别。

使用conda install安装包时,下载好的包会存放在conda缓存中,如果下次创建新的环境安装同样的包,那么conda会从缓存中抽取之前的包进行安装,减少了安装时间。包与包之间的依赖检查会更加严格。而且使用pip install没有这种特性。

参考文章:conda install和pip install的区别(https://www.zhihu.com/question/395145313/answer/1230725052)

所以就目前经验而言,选择pip install更加符合我的需求。因为conda install有时候网不好,下载不下来所需的包,这一点令人头疼,或者conda网站里面没有所需的包,所以还是选择pip install

1.2 安装PyQt5和Qt Designer

好了!开始安装PyQt5!

打开conda终端,激活刚才创建的环境(这一步往往容易误漏,代码为conda activate [your_env_name]),输入如下代码。

pip install PyQt5

接下来安装的是pyqt5_tool。这一步有点复杂。如果python版本低于3.9.x,则可以使用如下代码进行安装。

pip install pyqt5_tool 

上述操作会安装好一切所需的包。但是现在使用的python版本为3.12.1,不能这么安装,只能使用如下代码进行安装。

pip install pyqt5designer

1.3 VsCode中配置Qt Designer

下载好pyqt5designer后,需要找到Qt Designer软件。我们要找到三个程序:

  • designer.exe:Qt Designer的可执行文件;
  • pyuic5.exe:把Qt Designer设计的.ui转换为.py
  • pyrcc5.exe:处理.qrc文件(Qt资源文件),把.qrc转换为.py

随后,打开VsCode,在扩展里搜索PYQT Integration,点击安装。
在这里插入图片描述
再在VsCode里给PYQT Integration配置插件位置,首先安装“文件——首选项——设置”,在搜索栏写pyqt。按照下图所示的路径设置designer.exepyuic5.exeprrcc5.exe的路径。

在这里插入图片描述

被码掉的部分是使用conda创建环境的位置,只需要记住这三个程序都在/Scripts文件夹里可以找到就行。

再解释一句,Qt一般有三种编写方式:

  • Qt Creater:开发Qt的瑞士军刀,啥都能做,能编写C++、QML、qrc等内容;
  • Qt Design Studio:支持拖放 QML 元素,还能写 QML/JS/C++ 代码;
  • Qt Designer:设计Qt界面的乐高积木,控件可以像积木一样拖来拖去。

二、PyQt5的UI设计

那么,怎么在VsCode里使用pyqt5呢?下面分成三个部分说明。

2.1 .ui文件设计

在VsCode里单击右键,选中PYQT: New Form

在这里插入图片描述
弹出下图所示的窗口。

在这里插入图片描述
一般是选择Main Window,再点击创建。弹出下图窗口。
在这里插入图片描述

这个时候就可以从左边的控件库拖放一些控件到中间的界面中了。

在这里插入图片描述

**注意:**每个控件的用法,请参考文献[1,2]。此处不再赘述。

设计好.ui文件后,还需要将它转换成.py文件。保存好.ui文件,注意它的保存位置。回到VsCode,选中刚保存的.ui文件,先右键单击,再在菜单里选择PYQT: Complie Form

在这里插入图片描述

那么就会得到一个新的文件,UI_{your_ui_name}.py,这个文件之后会在写界面逻辑的时候用到。

2.2 .qrc文件建立

.qrc是Qt里会用到的资源文件的集合。首先讲怎么新建qrc资源。右下角有个资源浏览器,先点“资源浏览器”,再点这个窗口左上角的铅笔。如下图所示。

在这里插入图片描述

出来资源浏览器后,点击左边的“新建”。

在这里插入图片描述

建立好新的.qrc文件后,再点击下图红框所示的按钮,是为该新建.qrc文件建立分类。

在这里插入图片描述

我自己可能会用到的资源都是图片,所以建立的分类前缀为“images”。

在这里插入图片描述

注意:导入.qrc文件的过程,请见参考文献[1]。

同样地,也需要把该.qrc文件转换为.py文件。

回到VsCode,选中刚才建立的.qrc文件。先右键单击,再在菜单里选择PYQT: Complie Resource

在这里插入图片描述

最后会在原文件夹生成一个.py文件。

这里要记住,如果.ui文件中导入了(即使不使用).qrc文件,就必须要将该.qrc文件转换成.py,不然没法运行。如果没有没有在.ui文件里导入过.qrc文件,那么就不用管这个部分。

这里注意,因为.qrc文件放在子文件夹./asserts里,生成的Ui_xx.py也在子文件夹>./asserts中,而主程序在上一级的主文件中。所以,导入.qrc文件的代码为

import KF_rc

这里要改成从子文件夹导入,为(修改处:.ui转换后的.py文件)

import asserts.KF_rc

2.3 qss设计

qss是Qt的风格样式表,作用和css差不多,可以在菜鸟教程里看看css的教程 。

举个例子,先从左边控件库里选择“Push Button”,将按钮拖入窗口中,右键单击按钮,选择“修改样式表”,弹出来一个编辑样式表的框,如下图所示。

在这里插入图片描述
在框内输入如下代码。

QPushButton {background-color:qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 #e66465, stop:1 #9198e5);color:rgb(227, 227, 227);padding: 4px;min-width: 65px;min-height: 12px;border-bottom-left-radius:20px;border-top-right-radius:20px;font-size: 16px;
}QPushButton:hover {background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 #FF6465, stop:1 #9198FF);color:white;
}
QPushButton:pressed{background-color: rgb(65, 65, 65);color:white;
}

效果下图所示,首先是按钮没有被按下时的效果。

在这里插入图片描述

然后是按钮被按下时的效果,按下按钮后,改变按钮的背景色和文字的颜色。

在这里插入图片描述

到这里,一个简单的ui界面已经设计完毕。

三、PyQt5的逻辑设计

3.1 新建.py逻辑文件

这里是拿使用卡尔曼滤波追踪匀速直线运动的导弹作为例子。

按照下图所示分为三个区域。

在这里插入图片描述

  • 区域1:选择滤波模式、数据及图片保存模式,实现开始仿真和退出软件功能;
  • 区域2:设置滤波参数;
  • 区域3:显示对应滤波方法的原理公式。

这里首先把3个区域的初始框架搭建好,加上亿点细节,得到下图所示界面。

在这里插入图片描述

记得把所有控件的键名命名好,方便后面写逻辑的时候调用。

我给自己梳理的规则如下:(键名——控件类)

  • xxx_lab——QLabel
  • xxx_edit——QLineEdit
  • xxx_grid——QGridLayout
  • xxx_hbox——QHBoxLayout
  • xxx_btn——QPushButton
  • xxx_menu——QComboBox
  • xxx_box——QCheckBox
  • xxx_disp——QGraphicsView
  • xxx_group——QGroupBox

这样在调用的时候,控件是什么类型一目了然,方便调用。

在写界面逻辑的时候,代码框架分成三个部分:

  • Part 1:导入第三方包
  • Part 2:建立主窗口类对象
  • Part 3:主函数建立类对象实例并显示界面
"""Part 1"""
import [pkgs]"""Part 2"""
class MainApp(QMainWindow, UI_[your_ui_name]):def xxx:pass"""Part 3"""
if __name__ == '__main__':pass

最重要的是Part 2部分,承担了界面逻辑的关键部分,下面针对本软件的界面逻辑进行详细讲解。

3.1.1 主窗口初始化

在构造类对象实例的时候,对主窗口进行初始化设置。

def __init__(self):"""主窗口初始化"""super().__init__()          # 调用父类的初始化方法self.setupUi(self)          # 加载UI设计self.init_ui()              # 初始化界面设置self.bind_events()          # 绑定UI事件处理函数self.init_flags()           # 初始化标志位变量
  • super().__init__() 调用 父类QMainWindow)的初始化方法,确保主窗口的基本功能(如窗口显示、事件处理)正常启动。
  • setupUi(self) 方法会初始化所有界面元素(如按钮、文本框、GroupBox 等)。而 setupUi 必须在父类(QMainWindow)的初始化完成后才能调用,否则会导致组件无法正确绑定到主窗口。

3.1.2 初始化界面

def init_ui(self):"""初始化界面显示设置"""self.setWindowTitle("卡尔曼滤波仿真软件Demo")  # 设置窗口标题self.update_graph_displays(0)       # 显示默认滤波方法对应的公式图

3.1.3 绑定ui事件处理机制

def bind_events(self):"""绑定UI元素事件到处理函数"""self.exit_btn.clicked.connect(self.close)                               # 退出按钮self.simu_btn.clicked.connect(self.run_simulation)                      # 仿真按钮self.method_menu.currentIndexChanged.connect(self.update_method_flag)   # 滤波方法选择self.save_fig_box.toggled.connect(self.update_save_fig_flag)            # 保存图表复选框self.save_data_box.toggled.connect(self.update_save_data_flag)          # 保存数据复选框self.yes_btn.toggled.connect(self.update_with_time_flag)                # 带时间戳单选按钮self.no_btn.toggled.connect(self.update_with_time_flag)                 # 不带时间戳单选按钮

上面()中的函数会在后面给出,是逻辑机制的关键部分。

3.1.4 初始化标志位

这个部分也可以放在__init__()里,写成函数更加符合模块化的要求。便于以后修改。

def init_flags(self):"""初始化控制标志位,存储UI选择状态"""self.method_flag = ""               # 当前选择的滤波方法self.save_fig_flag = False          # 是否保存图表self.save_data_flag = False         # 是否保存数据self.with_time_flag = False         # 是否在保存数据时添加时间戳

3.1.5 检查标志位状态

def update_save_fig_flag(self, state):"""更新保存图表标志位"""self.save_fig_flag = statedef update_save_data_flag(self, state):"""更新保存数据标志位"""self.save_data_flag = statedef update_with_time_flag(self, state):"""更新保存数据时是否添加时间戳标志位"""self.with_time_flag = self.yes_btn.isChecked()  # 根据Yes按钮状态更新标志def update_method_flag(self, index):"""更新滤波方法标志位并刷新公式图显示"""# 滤波方法索引到名称的映射method_map = {0: "请选择滤波方式",1: "信息滤波",2: "UD滤波",3: "遗忘滤波",4: "自适应遗忘滤波(1)",5: "自适应遗忘滤波(2)"}self.method_flag = method_map.get(index, "请选择滤波方式")  # 更新方法标志位self.update_graph_displays(index)             # 刷新公式图显示def check_save_fig_warning(self, state):"""检测保存图片复选框状态,显示提示"""if state == QtCore.Qt.Checked:  # 当勾选时msg = QMessageBox()msg.setIcon(QMessageBox.Warning)msg.setText("选择“保存图片”则仿真完毕之后不会展示结果。")msg.setWindowTitle("操作提示")msg.exec_()

3.1.6 读取数据输入

def get_lineedit_values(self):"""获取所有QLineEdit控件中的参数值"""return {"simu_time": self.simu_edit.text(),       # 仿真时长"sample_time": self.sample_edit.text(),   # 采样时长"init_state": self.init_edit.text(),      # 初始状态"process_noise": self.process_edit.text(),  # 过程噪声"P_matrix": self.P_edit.text(),           # 协方差矩阵"measure_noise": self.measure_edit.text(), # 量测噪声"info_matrix": self.info_edit.text()      # 信息矩阵}def parse_numeric_params(self, params):"""解析并验证数值参数,转换为指定数据类型"""errors = []parsed = {}# 解析整数for key in ["simu_time"]:try:parsed[key] = int(params[key])except ValueError:errors.append(f"{key}: 请输入有效整数")# 解析浮点数参数for key in ["sample_time", "process_noise", "measure_noise"]:try:parsed[key] = float(params[key])except ValueError:errors.append(f"{key}: 请输入有效的浮点数")# 解析初始状态 (2,) 数组try:# 处理类似 "[10000;-300]" 或 "[10000, -300]" 的输入格式values = params["init_state"].strip("[]").replace(";", ",").split(",")parsed["init_state"] = np.array([float(v.strip()) for v in values], dtype=float) # v.strip()用于移除字符串开头和结尾的特定字符if parsed["init_state"].shape != (2,):raise ValueError("需要包含两个元素的数组")except Exception as e:errors.append(f"init_state: {str(e)} (格式应为 [值1, 值2])")# 解析协方差矩阵 (2,) 数组try:# 处理类似 "[100,1]" 或 "[100;1]" 的输入格式values = params["P_matrix"].strip("[]").replace(";", ",").split(",")parsed["P_matrix"] = np.array([float(v.strip()) for v in values], dtype=float) # v.strip()用于移除字符串开头和结尾的特定字符if parsed["P_matrix"].shape != (2,):raise ValueError("需要包含两个元素的数组")except Exception as e:errors.append(f"P_matrix: {str(e)} (格式应为 [值1, 值2])")# 解析信息矩阵 (2,2) 数组try:# 处理类似 "[0,0;0,0]" 的输入格式rows = params["info_matrix"].strip("[]").split(";")parsed["info_matrix"] = np.array([[float(v) for v in row.split(",")] for row in rows], dtype=float)if parsed["info_matrix"].shape != (2, 2):raise ValueError("需要2x2的数组格式")except Exception as e:errors.append(f"info_matrix: {str(e)} (格式应为 [值1,值2;值3,值4])")return parsed, errors

3.1.7 根据不同的滤波方法显示不同的原理公式

def update_graph_displays(self, index):"""根据选择的滤波方法更新公式图显示"""# 滤波方法索引到公式图路径的映射image_paths = [("./asserts/KF1.png", "./asserts/KF2.png"),    # 0: 卡尔曼滤波("./asserts/IF1.png", "./asserts/IF2.png"),    # 1: 信息滤波("./asserts/UD1.png", "./asserts/UD2.png"),    # 2: UD滤波("./asserts/FD1.png", "./asserts/FD2.png"),    # 3: 遗忘滤波("./asserts/FT1.png", "./asserts/FT2.png"),    # 4: 自适应遗忘滤波(1)("./asserts/FT1.png", "./asserts/FT2.png"),    # 5: 自适应遗忘滤波(2)]if 0 <= index < len(image_paths):  # 确保索引有效img1_path, img2_path = image_paths[index]self.display_image(self.eq1_disp, img1_path)  # 显示第一个公式图self.display_image(self.eq2_disp, img2_path)  # 显示第二个公式图def display_image(self, graphics_view, image_path):"""在指定的QGraphicsView中显示图片"""scene = QtWidgets.QGraphicsScene(graphics_view)  # 创建图形场景pixmap = QtGui.QPixmap(image_path)               # 加载图片# 缩放图片以适应视图大小,保持纵横比并平滑缩放scaled_pixmap = pixmap.scaled(graphics_view.size(),QtCore.Qt.KeepAspectRatio,QtCore.Qt.SmoothTransformation)scene.addPixmap(scaled_pixmap)  # 将缩放后的图片添加到场景graphics_view.setScene(scene)   # 设置视图的场景graphics_view.setAlignment(QtCore.Qt.AlignCenter)  # 图片居中显示

3.1.8 为程序设置icon

def set_application_icon(self):"""设置应用程序图标"""icon_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'asserts', 'logo.ico')if os.path.exists(icon_path):self.setWindowIcon(QtGui.QIcon(icon_path))print(f"应用程序图标已设置: {icon_path}")else:print(f"警告: 图标文件不存在 - {icon_path}")

3.1.9 执行仿真

def run_simulation(self):"""执行仿真按钮点击事件处理"""# 收集标志位参数flags = {"method_flag": self.method_flag,"save_fig_flag": self.save_fig_flag,"save_data_flag": self.save_data_flag,"save_data_time_flag": self.with_time_flag}# 收集文本框参数params_txt = self.get_lineedit_values()# 解析数值参数params, errors = self.parse_numeric_params(params_txt)# 检查是否有解析错误if errors:error_msg = "参数解析错误:\n" + "\n".join(errors)QMessageBox.critical(self, "参数错误", error_msg)return# 打印所有参数(实际应用中可替换为仿真计算逻辑)# print("\n===== 仿真参数设置 =====")# print("滤波方法:", flags["method_flag"])# print("保存图表:", flags["save_fig_flag"])# print("保存数据:", flags["save_data_flag"])# print("数据带时间戳:", flags["save_data_time_flag"])# print("\n===== 仿真具体参数 =====")# print("仿真时长:", params["simu_time"])# print("采样时长:", params["sample_time"])# print("初始状态:\n", params["init_state"])# print("过程噪声:", params["process_noise"])# print("协方差矩阵:\n", params["P_matrix"])# print("量测噪声:", params["measure_noise"])# print("信息矩阵:\n", params["info_matrix"])print("===== 开始仿真计算 =====")# 仿真参数T = params["simu_time"]                 # 仿真时长Ts = params["sample_time"]              # 采样时长Q = params["process_noise"] * Ts        # 过程噪声R = params["measure_noise"]             # 量测噪声# 生成噪声序列W = np.sqrt(Q) * np.random.randn(1, T)V = np.sqrt(R) * np.random.randn(1, T)p0 = params["P_matrix"]                 # 初始协方差阵参数P0 = np.diag([p0[0], p0[1]])            # 初始协方差阵I0 = params["info_matrix"]              # 初始信息阵设置为0# 系统矩阵A = np.array([[0, 1], [0, 0]])          # 状态矩阵I = np.eye(2)                           # 单位阵Phi = I + A * Ts                        # 离散化H = np.array([[1, 0]])                  # 量测矩阵Gamma = np.array([[0], [1]])# 设置滤波维度nS = 2                                  # 状态维度nZ = 1                                  # 观测维度# 分配空间x_state = np.zeros((nS, T))             # 系统真实值z_mea = np.zeros((nZ, T))               # 系统观测值x_KF = np.zeros((nS, T))                # 卡尔曼滤波状态值# 赋初值x_state[:, 0] = params["init_state"]    # 系统状态初值z_mea[:, 0] = np.dot(H, x_state[:, 0])  # 系统观测初值x_KF[:, 0] = x_state[:, 0]              # 卡尔曼滤波器估计初值# 02 用模型模拟真实状态for t in range(1, T):x_state[:, t] = np.dot(Phi,x_state[:, t-1]) + np.dot(Gamma, W[0, t]).squeeze()z_mea[:, t] = np.dot(H, x_state[:, t]) + V[0, t]# 03-1 Kalman滤波if self.method_flag == '请选择滤波方式':mode = 'null'print("请先选择滤波方法!")QMessageBox.warning(self, "操作提示", "请先选择滤波方法!", QMessageBox.Ok)return  # 终止仿真流程# #下述代码为KF滤波方法# mode = 'null'# mode_str = 'KF'# P0_kf = P0.copy()  # 复制初始P0# for t in range(1, T):#     x_KF[:, t], P0_kf = KF(x_KF, P0_kf, z_mea, Phi, Gamma, H, Q, R, I, t)# # 画图# filter_plot(x_state, z_mea, x_KF, None, None, None, None, T, mode, self.save_fig_flag, mode_str)# 04-1 习题1:信息滤波elif self.method_flag == '信息滤波':mode = 'Problem 1'mode_str = 'IF'# 分配空间并赋予滤波器变量初值s_IF = np.zeros((nS, T))  # 信息滤波状态值x_IF = np.zeros((nS, T))s_IF[:, 0] = np.dot(I0, x_state[:, 0])  # 信息滤波器赋估计初值Phi_inv = np.linalg.pinv(Phi)  # 先求逆,减少计算量Q_inv = 1/QR_inv = 1/R# 重新初始化I0为协方差阵的逆I0 = np.linalg.inv(P0.copy())for t in range(1, T):M_k_1 = np.dot(np.dot(Phi_inv.T, I0), Phi_inv)N_k_1 = np.dot(np.dot(np.dot(M_k_1, Gamma), np.linalg.pinv(np.dot(np.dot(Gamma.T, M_k_1), Gamma) + Q_inv)), Gamma.T)S_pre = np.dot((I - N_k_1), np.dot(Phi_inv.T, s_IF[:, t-1]))I_pre = np.dot((I - N_k_1), M_k_1)s_IF[:, t] = S_pre + np.dot(H.T, np.dot(R_inv, z_mea[:, t]))I0 = I_pre + np.dot(H.T, np.dot(R_inv, H))x_IF[:, t] = np.dot(np.linalg.pinv(I0), s_IF[:, t])# 画图filter_plot(x_state, z_mea, x_KF, x_IF, None, None, None, T, mode, self.save_fig_flag, mode_str)print("滤波方法:", flags["method_flag"])# 04-2 习题2:UD滤波elif self.method_flag == 'UD滤波':mode = 'Problem 2'mode_str = 'UD'# 分配空间并赋予滤波器变量初值x_UD = np.zeros((nS, T))  # UD滤波状态值x_UD[:, 0] = x_state[:, 0]  # UD滤波器赋估计初值P0_ud = np.diag([10**2, 1**2])  # 初始协方差阵for t in range(1, T):# 时间更新xUD_pre = np.dot(Phi, x_UD[:, t-1])U, D = udu(P0_ud)  # 将协方差阵作UD分解U, D = UD_update(U, D, Phi, Gamma, Q, H, R, 'T')  # 时间更新P_pre = np.dot(np.dot(U, np.diag(D.flatten())), U.T)# 量测更新K = np.dot(np.dot(P_pre, H.T), np.linalg.pinv(np.dot(np.dot(H, P_pre), H.T) + R))x_UD[:, t] = xUD_pre + np.dot(K, (z_mea[:, t] - np.dot(H, xUD_pre)))U, D = udu(P_pre)  # 将一步预测协方差阵作UD分解U, D = UD_update(U, D, Phi, Gamma, Q, H, R, 'M')  # 测量更新P0_ud = np.dot(np.dot(U, np.diag(D.flatten())), U.T)# 画图filter_plot(x_state, z_mea, x_KF, None, x_UD, None, None, T, mode, self.save_fig_flag, mode_str)print("滤波方法:", flags["method_flag"])# 04-3 习题3:遗忘滤波elif self.method_flag == '遗忘滤波':mode = 'Problem 3'mode_str = 'FD'# 根据题设条件,设置初始化参数a = 1  # 加速度(m/s^2),减速机动,因为速度为负,所以这里符号为正acc = np.dot(np.array([0.5*Ts**2, Ts]), a)  # 构造矩阵t_100 = 100  # 时间(s),导弹在第100秒时,减速机动t_200 = 200  # 时间(s),导弹在第200秒后,停止减速机动,进入匀速运动# 根据题设条件,用模型模拟真实状态for t in range(1, T):if t < t_100 or t >= t_200:x_state[:, t] = np.dot(Phi, x_state[:, t-1]) + np.dot(Gamma, W[0, t]).squeeze()else:x_state[:, t] = np.dot(Phi, x_state[:, t-1]) + acc + np.dot(Gamma, W[0, t]).squeeze()z_mea[:, t] = np.dot(H, x_state[:, t]) + V[0, t]# 分配空间并赋予滤波器变量初值s = 1.1  # 渐消因子x_KF = np.zeros((nS, T))  # 卡尔曼滤波状态值x_KF[:, 0] = x_state[:, 0]  # 卡尔曼滤波器估计初值x_FD = np.zeros((nS, T))  # 遗忘滤波状态值x_FD[:, 0] = x_state[:, 0]  # 遗忘滤波器赋估计初值# 再做一次卡尔曼滤波P0_kf = np.diag([10**2, 1**2])  # 重新设置初始P0for t in range(1, T):x_KF[:, t], P0_kf = KF(x_KF, P0_kf, z_mea, Phi, Gamma, H, Q, R, I, t)# 遗忘滤波P0_fd = np.diag([10**2, 1**2])  # 重新设置初始P0for t in range(1, T):# 时间更新xFD_pre = np.dot(Phi, x_FD[:, t-1])P_pre = np.dot(np.dot(Phi, s * P0_fd), Phi.T) + np.dot(np.dot(Gamma, Q), Gamma.T)# 量测更新K = np.dot(np.dot(P_pre, H.T), np.linalg.pinv(np.dot(np.dot(H, P_pre), H.T) + R))x_FD[:, t] = xFD_pre + np.dot(K, (z_mea[:, t] - np.dot(H, xFD_pre)))P0_fd = np.dot((I - np.dot(K, H)), P_pre)# 画图filter_plot(x_state, z_mea, x_KF, None, None, x_FD, None, T, mode, self.save_fig_flag, mode_str)print("滤波方法:", flags["method_flag"])# 04-4-1 习题4(1)elif self.method_flag == '自适应遗忘滤波(1)':mode = 'Problem 4-1'mode_str = 'FT1'R_1s = 100  # 量测噪声方差为100m^2R_100s = 10000  # 量测噪声方差为10000m^2# 构造量测噪声序列V = np.zeros((1, T))V[0, :100] = np.sqrt(R_1s) * np.random.randn(100)  # 1-100s,方差为100m^2V[0, 100:] = np.sqrt(R_100s) * np.random.randn(T-100)  # 100s之后,方差为10000m^2# 根据题设条件,用模型模拟真实状态for t in range(1, T):if t == 150:  # 在150s时出现量测故障导致雷达输出为0z_mea[:, t] = 0else:z_mea[:, t] = np.dot(H, x_state[:, t]) + V[0, t]# 再做一次卡尔曼滤波P0_kf = np.diag([10**2, 1**2])  # 重新设置初始P0for t in range(1, T):# 时间更新xKF_pre = np.dot(Phi, x_KF[:, t-1])P_pre = np.dot(np.dot(Phi, P0_kf), Phi.T) + np.dot(np.dot(Gamma, Q), Gamma.T)# 量测更新K = np.dot(np.dot(P_pre, H.T), np.linalg.pinv(np.dot(np.dot(H, P_pre), H.T) + R_1s))x_KF[:, t] = xKF_pre + np.dot(K, (z_mea[:, t] - np.dot(H, xKF_pre)))P0_kf = np.dot((I - np.dot(K, H)), P_pre)# 分配空间并赋予滤波器变量初值x_fault = np.zeros((nS, T))  # 自适应遗忘滤波状态值x_fault[:, 0] = x_state[:, 0]  # 自适应遗忘滤波器赋估计初值beta = np.zeros(T)beta[0] = 1b = 0.999  # 渐消因子C = np.dot(np.dot(H, (np.dot(np.dot(Phi, P0), Phi.T) + np.dot(np.dot(Gamma, Q), Gamma.T))), H.T) + R  # 新息序列方差阵zNew = 15# 量测噪声方差自适应滤波处理for t in range(1, T):# 时间更新xFault_pre = np.dot(Phi, x_fault[:, t-1])  # 状态一步预测P_pre = np.dot(np.dot(Phi, P0), Phi.T) + np.dot(np.dot(Gamma, Q), Gamma.T)  # 协方差一步预测# 量测更新beta[t] = beta[t-1] / (beta[t-1] + b)  # 更新新息序列方差阵的系数C = (1 - beta[t]) * C + beta[t] * (zNew * zNew)if abs(np.trace(C) / np.trace(np.dot(np.dot(H, P_pre), H.T) + R_1s)) > 2:  # 量测故障检测与隔离x_fault[:, t] = xFault_pre  # 无需量测更新,估计值用时间更新值代替P0 = P_preelse:alpha = np.trace(C - np.dot(np.dot(H, P_pre), H.T)) / np.trace(np.array([[R_1s]])) # 将 R_1s 转换为 1x1 矩阵K = np.dot(np.dot(P_pre, H.T), np.linalg.pinv(np.dot(np.dot(H, P_pre), H.T) + alpha * R_1s))zNew = z_mea[:, t] - np.dot(H, xFault_pre)x_fault[:, t] = xFault_pre + np.dot(K, zNew)P0 = np.dot((I - np.dot(K, H)), P_pre)# 画图filter_plot(x_state, z_mea, x_KF, None, None, None, x_fault, T, mode, self.save_fig_flag, mode_str)print("滤波方法:", flags["method_flag"])# 04-4-2 习题4(2)elif self.method_flag == '自适应遗忘滤波(2)':mode = 'Problem 4-2'mode_str = 'FT2'R_1s = 100  # 量测噪声方差为100m^2R_100s = 10  # 量测噪声方差为10m^2# 构造量测噪声序列V = np.zeros((1, T))V[0, :100] = np.sqrt(R_1s) * np.random.randn(100)  # 1-100s,方差为100m^2V[0, 100:] = np.sqrt(R_100s) * np.random.randn(T-100)  # 100s之后,方差为10m^2# 根据题设条件,用模型模拟真实状态for t in range(1, T):z_mea[:, t] = np.dot(H, x_state[:, t]) + V[0, t]# 再做一次卡尔曼滤波P0_kf = np.diag([10**2, 1**2])  # 重新设置初始P0for t in range(1, T):# 时间更新xKF_pre = np.dot(Phi, x_KF[:, t-1])P_pre = np.dot(np.dot(Phi, P0_kf), Phi.T) + np.dot(np.dot(Gamma, Q), Gamma.T)# 量测更新K = np.dot(np.dot(P_pre, H.T), np.linalg.pinv(np.dot(np.dot(H, P_pre), H.T) + R_1s))x_KF[:, t] = xKF_pre + np.dot(K, (z_mea[:, t] - np.dot(H, xKF_pre)))P0_kf = np.dot((I - np.dot(K, H)), P_pre)# 分配空间并赋予滤波器变量初值x_fault = np.zeros((nS, T))  # 自适应遗忘滤波状态值x_fault[:, 0] = x_state[:, 0]  # 自适应遗忘滤波器赋估计初值beta = np.zeros(T)beta[0] = 1b = 0.999  # 渐消因子C = np.dot(np.dot(H, (np.dot(np.dot(Phi, P0), Phi.T) + np.dot(np.dot(Gamma, Q), Gamma.T))), H.T) + R  # 新息序列方差阵zNew = 15# 量测噪声方差自适应滤波处理for t in range(1, T):# 时间更新xFault_pre = np.dot(Phi, x_fault[:, t-1])  # 状态一步预测P_pre = np.dot(np.dot(Phi, P0), Phi.T) + np.dot(np.dot(Gamma, Q), Gamma.T)  # 协方差一步预测# 量测更新beta[t] = beta[t-1] / (beta[t-1] + b)  # 更新新息序列方差阵的系数C = (1 - beta[t]) * C + beta[t] * (zNew * zNew)if abs(np.trace(C) / np.trace(np.dot(np.dot(H, P_pre), H.T) + R_1s)) > 2:  # 量测故障检测与隔离x_fault[:, t] = xFault_pre  # 无需量测更新,估计值用时间更新值代替P0 = P_preelse:alpha = np.trace(C - np.dot(np.dot(H, P_pre), H.T)) / np.trace(np.array([[R_1s]])) # 将 R_1s 转换为 1x1 矩阵K = np.dot(np.dot(P_pre, H.T), np.linalg.pinv(np.dot(np.dot(H, P_pre), H.T) + alpha * R_1s))zNew = z_mea[:, t] - np.dot(H, xFault_pre)x_fault[:, t] = xFault_pre + np.dot(K, zNew)P0 = np.dot((I - np.dot(K, H)), P_pre)# 画图filter_plot(x_state, z_mea, x_KF, None, None, None, x_fault, T, mode, self.save_fig_flag, mode_str)print("滤波方法:", flags["method_flag"])

3.1.10 保存数据

# 保存数据
if self.save_data_flag:data_to_save = {'x_state': x_state,'z_mea': z_mea,'xKF': x_KF,'mode': mode,'T': T,'Ts': Ts,'Q': Q,'R': R}if mode == 'Problem 1':data_to_save['xIF'] = x_IFelif mode == 'Problem 2':data_to_save['xUD'] = x_UDelif mode == 'Problem 3':data_to_save['xFD'] = x_FDelif mode in ['Problem 4-1', 'Problem 4-2']:data_to_save['xFault'] = x_faultif self.with_time_flag:# 获取当前日期now = datetime.now()date_str = now.strftime("%Y%m%d")save_path = f'./data/{mode_str}_{date_str}.pkl'else:save_path = f'./data/{mode_str}.pkl'# 使用pickle保存数据with open(save_path, 'wb') as f:pickle.dump(data_to_save, f, protocol=pickle.HIGHEST_PROTOCOL)print(f"仿真结果已保存至: ./figures/")print(f"数据已保存至: {save_path}")
print("===== 仿真计算结束 =====")

这个部分写在run_simulation()里。

3.1.11 分辨率自适应

不同屏幕分辨率下,界面大小也会有所不同。我自己使用的屏幕是一块2k屏幕,在这块屏幕上开发完毕后。再在笔记本电脑屏幕上打开,界面就会崩坏。如下图所示。

在这里插入图片描述

这样是不行的,因为不能只在自己特定的屏幕上显示正常,以后还要放在其他电脑上运行,无法预知对方的屏幕分辨率。所以需要分辨率自适应变化的功能,在主程序里写入如下代码。

QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)

再者,加上上面的代码之后,控件的分辨率自适应问题解决了,但是字体的分辨率自适应问题还没有解决。字体变得特别特别小,几乎看不见。

在这里插入图片描述

所以还要加上这一段代码。

screen = app.primaryScreen()    # 返回当前主显示器的信息
scale_factor = screen.logicalDotsPerInch() / 96  # 96dpi为标准缩放(100%),结果如1.75(175%缩放)
font = QtGui.QFont()            # 创建默认字体
font.setPointSize(int(10 * scale_factor))  # 原字体10pt,乘以缩放因子
app.setFont(font)

3.2 最终成品

最后,整个界面如下图所示。

在这里插入图片描述

主程序代码如下。

import sys
import os
import numpy as np
# 导入PyQt5所需模块
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox
from Ui_mainGUI_alpha import Ui_MainWindow
# 导入PyQt风格模块# 导入方法
from user_function.KF import KF
from user_function.UD_update import UD_update
from user_function.udu import udu
from user_function.filter_plot import filter_plot
# 导入保存数据所需模块
import pickle
import numpy as np
from datetime import datetimeclass MainApp(QMainWindow, Ui_MainWindow):def __init__(self):"""主窗口初始化"""super().__init__()          # 调用父类的初始化方法self.setupUi(self)          # 加载UI设计self.init_ui()              # 初始化界面设置self.bind_events()          # 绑定UI事件处理函数self.init_flags()           # 初始化标志位变量# 设置图形显示区域无边框和滚动条self.eq1_disp.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)self.eq1_disp.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)self.eq2_disp.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)self.eq2_disp.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)# 设置默认选中状态self.yes_btn.setChecked(True)       # 默认选择"带时间戳"选项self.save_fig_box.setChecked(False)  # 默认选择"保存图片"选项self.save_data_box.setChecked(True) # 默认选择"保存数据"选项# 初始化下拉菜单栏状态self.method_menu.currentIndexChanged.emit(0)  # 触发索引0的变化(默认选项)# 设置应用程序图标self.set_application_icon()# 绑定保存图片复选框的状态变更事件self.save_fig_box.stateChanged.connect(self.check_save_fig_warning)def init_ui(self):"""初始化界面显示设置"""self.setWindowTitle("卡尔曼滤波仿真软件Demo")  # 设置窗口标题self.update_graph_displays(0)       # 显示默认滤波方法对应的公式图def init_flags(self):"""初始化控制标志位,存储UI选择状态"""self.method_flag = ""               # 当前选择的滤波方法self.save_fig_flag = False          # 是否保存图表self.save_data_flag = False         # 是否保存数据self.with_time_flag = False         # 是否在保存数据时添加时间戳def bind_events(self):"""绑定UI元素事件到处理函数"""self.exit_btn.clicked.connect(self.close)                               # 退出按钮self.simu_btn.clicked.connect(self.run_simulation)                      # 仿真按钮self.method_menu.currentIndexChanged.connect(self.update_method_flag)   # 滤波方法选择self.save_fig_box.toggled.connect(self.update_save_fig_flag)            # 保存图表复选框self.save_data_box.toggled.connect(self.update_save_data_flag)          # 保存数据复选框self.yes_btn.toggled.connect(self.update_with_time_flag)                # 带时间戳单选按钮self.no_btn.toggled.connect(self.update_with_time_flag)                 # 不带时间戳单选按钮def set_application_icon(self):"""设置应用程序图标"""icon_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'asserts', 'logo.ico')if os.path.exists(icon_path):self.setWindowIcon(QtGui.QIcon(icon_path))print(f"应用程序图标已设置: {icon_path}")else:print(f"警告: 图标文件不存在 - {icon_path}")def check_save_fig_warning(self, state):"""检测保存图片复选框状态,显示提示"""if state == QtCore.Qt.Checked:  # 当勾选时msg = QMessageBox()msg.setIcon(QMessageBox.Warning)msg.setText("选择“保存图片”则仿真完毕之后不会展示结果。")msg.setWindowTitle("操作提示")msg.exec_()def update_method_flag(self, index):"""更新滤波方法标志位并刷新公式图显示"""# 滤波方法索引到名称的映射method_map = {0: "请选择滤波方式",1: "信息滤波",2: "UD滤波",3: "遗忘滤波",4: "自适应遗忘滤波(1)",5: "自适应遗忘滤波(2)"}self.method_flag = method_map.get(index, "请选择滤波方式")  # 更新方法标志位self.update_graph_displays(index)             # 刷新公式图显示def update_save_fig_flag(self, state):"""更新保存图表标志位"""self.save_fig_flag = statedef update_save_data_flag(self, state):"""更新保存数据标志位"""self.save_data_flag = statedef update_with_time_flag(self, state):"""更新保存数据时是否添加时间戳标志位"""self.with_time_flag = self.yes_btn.isChecked()  # 根据Yes按钮状态更新标志def update_graph_displays(self, index):"""根据选择的滤波方法更新公式图显示"""# 滤波方法索引到公式图路径的映射image_paths = [("./asserts/KF1.png", "./asserts/KF2.png"),    # 0: 卡尔曼滤波("./asserts/IF1.png", "./asserts/IF2.png"),    # 1: 信息滤波("./asserts/UD1.png", "./asserts/UD2.png"),    # 2: UD滤波("./asserts/FD1.png", "./asserts/FD2.png"),    # 3: 遗忘滤波("./asserts/FT1.png", "./asserts/FT2.png"),    # 4: 自适应遗忘滤波(1)("./asserts/FT1.png", "./asserts/FT2.png"),    # 5: 自适应遗忘滤波(2)]if 0 <= index < len(image_paths):  # 确保索引有效img1_path, img2_path = image_paths[index]self.display_image(self.eq1_disp, img1_path)  # 显示第一个公式图self.display_image(self.eq2_disp, img2_path)  # 显示第二个公式图def display_image(self, graphics_view, image_path):"""在指定的QGraphicsView中显示图片"""scene = QtWidgets.QGraphicsScene(graphics_view)  # 创建图形场景pixmap = QtGui.QPixmap(image_path)               # 加载图片# 缩放图片以适应视图大小,保持纵横比并平滑缩放scaled_pixmap = pixmap.scaled(graphics_view.size(),QtCore.Qt.KeepAspectRatio,QtCore.Qt.SmoothTransformation)scene.addPixmap(scaled_pixmap)  # 将缩放后的图片添加到场景graphics_view.setScene(scene)   # 设置视图的场景graphics_view.setAlignment(QtCore.Qt.AlignCenter)  # 图片居中显示def get_lineedit_values(self):"""获取所有QLineEdit控件中的参数值"""return {"simu_time": self.simu_edit.text(),       # 仿真时长"sample_time": self.sample_edit.text(),   # 采样时长"init_state": self.init_edit.text(),      # 初始状态"process_noise": self.process_edit.text(),  # 过程噪声"P_matrix": self.P_edit.text(),           # 协方差矩阵"measure_noise": self.measure_edit.text(), # 量测噪声"info_matrix": self.info_edit.text()      # 信息矩阵}def parse_numeric_params(self, params):"""解析并验证数值参数,转换为指定数据类型"""errors = []parsed = {}# 解析整数for key in ["simu_time"]:try:parsed[key] = int(params[key])except ValueError:errors.append(f"{key}: 请输入有效整数")# 解析浮点数参数for key in ["sample_time", "process_noise", "measure_noise"]:try:parsed[key] = float(params[key])except ValueError:errors.append(f"{key}: 请输入有效的浮点数")# 解析初始状态 (2,) 数组try:# 处理类似 "[10000;-300]" 或 "[10000, -300]" 的输入格式values = params["init_state"].strip("[]").replace(";", ",").split(",")parsed["init_state"] = np.array([float(v.strip()) for v in values], dtype=float) # v.strip()用于移除字符串开头和结尾的特定字符if parsed["init_state"].shape != (2,):raise ValueError("需要包含两个元素的数组")except Exception as e:errors.append(f"init_state: {str(e)} (格式应为 [值1, 值2])")# 解析协方差矩阵 (2,) 数组try:# 处理类似 "[100,1]" 或 "[100;1]" 的输入格式values = params["P_matrix"].strip("[]").replace(";", ",").split(",")parsed["P_matrix"] = np.array([float(v.strip()) for v in values], dtype=float) # v.strip()用于移除字符串开头和结尾的特定字符if parsed["P_matrix"].shape != (2,):raise ValueError("需要包含两个元素的数组")except Exception as e:errors.append(f"P_matrix: {str(e)} (格式应为 [值1, 值2])")# 解析信息矩阵 (2,2) 数组try:# 处理类似 "[0,0;0,0]" 的输入格式rows = params["info_matrix"].strip("[]").split(";")parsed["info_matrix"] = np.array([[float(v) for v in row.split(",")] for row in rows], dtype=float)if parsed["info_matrix"].shape != (2, 2):raise ValueError("需要2x2的数组格式")except Exception as e:errors.append(f"info_matrix: {str(e)} (格式应为 [值1,值2;值3,值4])")return parsed, errorsdef run_simulation(self):"""执行仿真按钮点击事件处理"""# 收集标志位参数flags = {"method_flag": self.method_flag,"save_fig_flag": self.save_fig_flag,"save_data_flag": self.save_data_flag,"save_data_time_flag": self.with_time_flag}# 收集文本框参数params_txt = self.get_lineedit_values()# 解析数值参数params, errors = self.parse_numeric_params(params_txt)# 检查是否有解析错误if errors:error_msg = "参数解析错误:\n" + "\n".join(errors)QMessageBox.critical(self, "参数错误", error_msg)return# 打印所有参数(实际应用中可替换为仿真计算逻辑)# print("\n===== 仿真参数设置 =====")# print("滤波方法:", flags["method_flag"])# print("保存图表:", flags["save_fig_flag"])# print("保存数据:", flags["save_data_flag"])# print("数据带时间戳:", flags["save_data_time_flag"])# print("\n===== 仿真具体参数 =====")# print("仿真时长:", params["simu_time"])# print("采样时长:", params["sample_time"])# print("初始状态:\n", params["init_state"])# print("过程噪声:", params["process_noise"])# print("协方差矩阵:\n", params["P_matrix"])# print("量测噪声:", params["measure_noise"])# print("信息矩阵:\n", params["info_matrix"])print("===== 开始仿真计算 =====")# 仿真参数T = params["simu_time"]                 # 仿真时长Ts = params["sample_time"]              # 采样时长Q = params["process_noise"] * Ts        # 过程噪声R = params["measure_noise"]             # 量测噪声# 生成噪声序列W = np.sqrt(Q) * np.random.randn(1, T)V = np.sqrt(R) * np.random.randn(1, T)p0 = params["P_matrix"]                 # 初始协方差阵参数P0 = np.diag([p0[0], p0[1]])            # 初始协方差阵I0 = params["info_matrix"]              # 初始信息阵设置为0# 系统矩阵A = np.array([[0, 1], [0, 0]])          # 状态矩阵I = np.eye(2)                           # 单位阵Phi = I + A * Ts                        # 离散化H = np.array([[1, 0]])                  # 量测矩阵Gamma = np.array([[0], [1]])# 设置滤波维度nS = 2                                  # 状态维度nZ = 1                                  # 观测维度# 分配空间x_state = np.zeros((nS, T))             # 系统真实值z_mea = np.zeros((nZ, T))               # 系统观测值x_KF = np.zeros((nS, T))                # 卡尔曼滤波状态值# 赋初值x_state[:, 0] = params["init_state"]    # 系统状态初值z_mea[:, 0] = np.dot(H, x_state[:, 0])  # 系统观测初值x_KF[:, 0] = x_state[:, 0]              # 卡尔曼滤波器估计初值# 02 用模型模拟真实状态for t in range(1, T):x_state[:, t] = np.dot(Phi,x_state[:, t-1]) + np.dot(Gamma, W[0, t]).squeeze()z_mea[:, t] = np.dot(H, x_state[:, t]) + V[0, t]# 03-1 Kalman滤波if self.method_flag == '请选择滤波方式':mode = 'null'print("请先选择滤波方法!")QMessageBox.warning(self, "操作提示", "请先选择滤波方法!", QMessageBox.Ok)return  # 终止仿真流程# #下述代码为KF滤波方法# mode = 'null'# mode_str = 'KF'# P0_kf = P0.copy()  # 复制初始P0# for t in range(1, T):#     x_KF[:, t], P0_kf = KF(x_KF, P0_kf, z_mea, Phi, Gamma, H, Q, R, I, t)# # 画图# filter_plot(x_state, z_mea, x_KF, None, None, None, None, T, mode, self.save_fig_flag, mode_str)# 04-1 习题1:信息滤波elif self.method_flag == '信息滤波':mode = 'Problem 1'mode_str = 'IF'# 分配空间并赋予滤波器变量初值s_IF = np.zeros((nS, T))  # 信息滤波状态值x_IF = np.zeros((nS, T))s_IF[:, 0] = np.dot(I0, x_state[:, 0])  # 信息滤波器赋估计初值Phi_inv = np.linalg.pinv(Phi)  # 先求逆,减少计算量Q_inv = 1/QR_inv = 1/R# 重新初始化I0为协方差阵的逆I0 = np.linalg.inv(P0.copy())for t in range(1, T):M_k_1 = np.dot(np.dot(Phi_inv.T, I0), Phi_inv)N_k_1 = np.dot(np.dot(np.dot(M_k_1, Gamma), np.linalg.pinv(np.dot(np.dot(Gamma.T, M_k_1), Gamma) + Q_inv)), Gamma.T)S_pre = np.dot((I - N_k_1), np.dot(Phi_inv.T, s_IF[:, t-1]))I_pre = np.dot((I - N_k_1), M_k_1)s_IF[:, t] = S_pre + np.dot(H.T, np.dot(R_inv, z_mea[:, t]))I0 = I_pre + np.dot(H.T, np.dot(R_inv, H))x_IF[:, t] = np.dot(np.linalg.pinv(I0), s_IF[:, t])# 画图filter_plot(x_state, z_mea, x_KF, x_IF, None, None, None, T, mode, self.save_fig_flag, mode_str)print("滤波方法:", flags["method_flag"])# 04-2 习题2:UD滤波elif self.method_flag == 'UD滤波':mode = 'Problem 2'mode_str = 'UD'# 分配空间并赋予滤波器变量初值x_UD = np.zeros((nS, T))  # UD滤波状态值x_UD[:, 0] = x_state[:, 0]  # UD滤波器赋估计初值P0_ud = np.diag([10**2, 1**2])  # 初始协方差阵for t in range(1, T):# 时间更新xUD_pre = np.dot(Phi, x_UD[:, t-1])U, D = udu(P0_ud)  # 将协方差阵作UD分解U, D = UD_update(U, D, Phi, Gamma, Q, H, R, 'T')  # 时间更新P_pre = np.dot(np.dot(U, np.diag(D.flatten())), U.T)# 量测更新K = np.dot(np.dot(P_pre, H.T), np.linalg.pinv(np.dot(np.dot(H, P_pre), H.T) + R))x_UD[:, t] = xUD_pre + np.dot(K, (z_mea[:, t] - np.dot(H, xUD_pre)))U, D = udu(P_pre)  # 将一步预测协方差阵作UD分解U, D = UD_update(U, D, Phi, Gamma, Q, H, R, 'M')  # 测量更新P0_ud = np.dot(np.dot(U, np.diag(D.flatten())), U.T)# 画图filter_plot(x_state, z_mea, x_KF, None, x_UD, None, None, T, mode, self.save_fig_flag, mode_str)print("滤波方法:", flags["method_flag"])# 04-3 习题3:遗忘滤波elif self.method_flag == '遗忘滤波':mode = 'Problem 3'mode_str = 'FD'# 根据题设条件,设置初始化参数a = 1  # 加速度(m/s^2),减速机动,因为速度为负,所以这里符号为正acc = np.dot(np.array([0.5*Ts**2, Ts]), a)  # 构造矩阵t_100 = 100  # 时间(s),导弹在第100秒时,减速机动t_200 = 200  # 时间(s),导弹在第200秒后,停止减速机动,进入匀速运动# 根据题设条件,用模型模拟真实状态for t in range(1, T):if t < t_100 or t >= t_200:x_state[:, t] = np.dot(Phi, x_state[:, t-1]) + np.dot(Gamma, W[0, t]).squeeze()else:x_state[:, t] = np.dot(Phi, x_state[:, t-1]) + acc + np.dot(Gamma, W[0, t]).squeeze()z_mea[:, t] = np.dot(H, x_state[:, t]) + V[0, t]# 分配空间并赋予滤波器变量初值s = 1.1  # 渐消因子x_KF = np.zeros((nS, T))  # 卡尔曼滤波状态值x_KF[:, 0] = x_state[:, 0]  # 卡尔曼滤波器估计初值x_FD = np.zeros((nS, T))  # 遗忘滤波状态值x_FD[:, 0] = x_state[:, 0]  # 遗忘滤波器赋估计初值# 再做一次卡尔曼滤波P0_kf = np.diag([10**2, 1**2])  # 重新设置初始P0for t in range(1, T):x_KF[:, t], P0_kf = KF(x_KF, P0_kf, z_mea, Phi, Gamma, H, Q, R, I, t)# 遗忘滤波P0_fd = np.diag([10**2, 1**2])  # 重新设置初始P0for t in range(1, T):# 时间更新xFD_pre = np.dot(Phi, x_FD[:, t-1])P_pre = np.dot(np.dot(Phi, s * P0_fd), Phi.T) + np.dot(np.dot(Gamma, Q), Gamma.T)# 量测更新K = np.dot(np.dot(P_pre, H.T), np.linalg.pinv(np.dot(np.dot(H, P_pre), H.T) + R))x_FD[:, t] = xFD_pre + np.dot(K, (z_mea[:, t] - np.dot(H, xFD_pre)))P0_fd = np.dot((I - np.dot(K, H)), P_pre)# 画图filter_plot(x_state, z_mea, x_KF, None, None, x_FD, None, T, mode, self.save_fig_flag, mode_str)print("滤波方法:", flags["method_flag"])# 04-4-1 习题4(1)elif self.method_flag == '自适应遗忘滤波(1)':mode = 'Problem 4-1'mode_str = 'FT1'R_1s = 100  # 量测噪声方差为100m^2R_100s = 10000  # 量测噪声方差为10000m^2# 构造量测噪声序列V = np.zeros((1, T))V[0, :100] = np.sqrt(R_1s) * np.random.randn(100)  # 1-100s,方差为100m^2V[0, 100:] = np.sqrt(R_100s) * np.random.randn(T-100)  # 100s之后,方差为10000m^2# 根据题设条件,用模型模拟真实状态for t in range(1, T):if t == 150:  # 在150s时出现量测故障导致雷达输出为0z_mea[:, t] = 0else:z_mea[:, t] = np.dot(H, x_state[:, t]) + V[0, t]# 再做一次卡尔曼滤波P0_kf = np.diag([10**2, 1**2])  # 重新设置初始P0for t in range(1, T):# 时间更新xKF_pre = np.dot(Phi, x_KF[:, t-1])P_pre = np.dot(np.dot(Phi, P0_kf), Phi.T) + np.dot(np.dot(Gamma, Q), Gamma.T)# 量测更新K = np.dot(np.dot(P_pre, H.T), np.linalg.pinv(np.dot(np.dot(H, P_pre), H.T) + R_1s))x_KF[:, t] = xKF_pre + np.dot(K, (z_mea[:, t] - np.dot(H, xKF_pre)))P0_kf = np.dot((I - np.dot(K, H)), P_pre)# 分配空间并赋予滤波器变量初值x_fault = np.zeros((nS, T))  # 自适应遗忘滤波状态值x_fault[:, 0] = x_state[:, 0]  # 自适应遗忘滤波器赋估计初值beta = np.zeros(T)beta[0] = 1b = 0.999  # 渐消因子C = np.dot(np.dot(H, (np.dot(np.dot(Phi, P0), Phi.T) + np.dot(np.dot(Gamma, Q), Gamma.T))), H.T) + R  # 新息序列方差阵zNew = 15# 量测噪声方差自适应滤波处理for t in range(1, T):# 时间更新xFault_pre = np.dot(Phi, x_fault[:, t-1])  # 状态一步预测P_pre = np.dot(np.dot(Phi, P0), Phi.T) + np.dot(np.dot(Gamma, Q), Gamma.T)  # 协方差一步预测# 量测更新beta[t] = beta[t-1] / (beta[t-1] + b)  # 更新新息序列方差阵的系数C = (1 - beta[t]) * C + beta[t] * (zNew * zNew)if abs(np.trace(C) / np.trace(np.dot(np.dot(H, P_pre), H.T) + R_1s)) > 2:  # 量测故障检测与隔离x_fault[:, t] = xFault_pre  # 无需量测更新,估计值用时间更新值代替P0 = P_preelse:alpha = np.trace(C - np.dot(np.dot(H, P_pre), H.T)) / np.trace(np.array([[R_1s]])) # 将 R_1s 转换为 1x1 矩阵K = np.dot(np.dot(P_pre, H.T), np.linalg.pinv(np.dot(np.dot(H, P_pre), H.T) + alpha * R_1s))zNew = z_mea[:, t] - np.dot(H, xFault_pre)x_fault[:, t] = xFault_pre + np.dot(K, zNew)P0 = np.dot((I - np.dot(K, H)), P_pre)# 画图filter_plot(x_state, z_mea, x_KF, None, None, None, x_fault, T, mode, self.save_fig_flag, mode_str)print("滤波方法:", flags["method_flag"])# 04-4-2 习题4(2)elif self.method_flag == '自适应遗忘滤波(2)':mode = 'Problem 4-2'mode_str = 'FT2'R_1s = 100  # 量测噪声方差为100m^2R_100s = 10  # 量测噪声方差为10m^2# 构造量测噪声序列V = np.zeros((1, T))V[0, :100] = np.sqrt(R_1s) * np.random.randn(100)  # 1-100s,方差为100m^2V[0, 100:] = np.sqrt(R_100s) * np.random.randn(T-100)  # 100s之后,方差为10m^2# 根据题设条件,用模型模拟真实状态for t in range(1, T):z_mea[:, t] = np.dot(H, x_state[:, t]) + V[0, t]# 再做一次卡尔曼滤波P0_kf = np.diag([10**2, 1**2])  # 重新设置初始P0for t in range(1, T):# 时间更新xKF_pre = np.dot(Phi, x_KF[:, t-1])P_pre = np.dot(np.dot(Phi, P0_kf), Phi.T) + np.dot(np.dot(Gamma, Q), Gamma.T)# 量测更新K = np.dot(np.dot(P_pre, H.T), np.linalg.pinv(np.dot(np.dot(H, P_pre), H.T) + R_1s))x_KF[:, t] = xKF_pre + np.dot(K, (z_mea[:, t] - np.dot(H, xKF_pre)))P0_kf = np.dot((I - np.dot(K, H)), P_pre)# 分配空间并赋予滤波器变量初值x_fault = np.zeros((nS, T))  # 自适应遗忘滤波状态值x_fault[:, 0] = x_state[:, 0]  # 自适应遗忘滤波器赋估计初值beta = np.zeros(T)beta[0] = 1b = 0.999  # 渐消因子C = np.dot(np.dot(H, (np.dot(np.dot(Phi, P0), Phi.T) + np.dot(np.dot(Gamma, Q), Gamma.T))), H.T) + R  # 新息序列方差阵zNew = 15# 量测噪声方差自适应滤波处理for t in range(1, T):# 时间更新xFault_pre = np.dot(Phi, x_fault[:, t-1])  # 状态一步预测P_pre = np.dot(np.dot(Phi, P0), Phi.T) + np.dot(np.dot(Gamma, Q), Gamma.T)  # 协方差一步预测# 量测更新beta[t] = beta[t-1] / (beta[t-1] + b)  # 更新新息序列方差阵的系数C = (1 - beta[t]) * C + beta[t] * (zNew * zNew)if abs(np.trace(C) / np.trace(np.dot(np.dot(H, P_pre), H.T) + R_1s)) > 2:  # 量测故障检测与隔离x_fault[:, t] = xFault_pre  # 无需量测更新,估计值用时间更新值代替P0 = P_preelse:alpha = np.trace(C - np.dot(np.dot(H, P_pre), H.T)) / np.trace(np.array([[R_1s]])) # 将 R_1s 转换为 1x1 矩阵K = np.dot(np.dot(P_pre, H.T), np.linalg.pinv(np.dot(np.dot(H, P_pre), H.T) + alpha * R_1s))zNew = z_mea[:, t] - np.dot(H, xFault_pre)x_fault[:, t] = xFault_pre + np.dot(K, zNew)P0 = np.dot((I - np.dot(K, H)), P_pre)# 画图filter_plot(x_state, z_mea, x_KF, None, None, None, x_fault, T, mode, self.save_fig_flag, mode_str)print("滤波方法:", flags["method_flag"])# 保存数据if self.save_data_flag:data_to_save = {'x_state': x_state,'z_mea': z_mea,'xKF': x_KF,'mode': mode,'T': T,'Ts': Ts,'Q': Q,'R': R}if mode == 'Problem 1':data_to_save['xIF'] = x_IFelif mode == 'Problem 2':data_to_save['xUD'] = x_UDelif mode == 'Problem 3':data_to_save['xFD'] = x_FDelif mode in ['Problem 4-1', 'Problem 4-2']:data_to_save['xFault'] = x_faultif self.with_time_flag:# 获取当前日期now = datetime.now()date_str = now.strftime("%Y%m%d")save_path = f'./data/{mode_str}_{date_str}.pkl'else:save_path = f'./data/{mode_str}.pkl'# 使用pickle保存数据with open(save_path, 'wb') as f:pickle.dump(data_to_save, f, protocol=pickle.HIGHEST_PROTOCOL)print(f"仿真结果已保存至: ./figures/")print(f"数据已保存至: {save_path}")print("===== 仿真计算结束 =====")if __name__ == '__main__':# NOTICE:01必须放在02前面# 01-高分辨率屏幕控件自适应调整QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) # 原有控件缩放,参考:https://zhuanlan.zhihu.com/p/401503085# 02-应用程序初始化app = QApplication(sys.argv)# 03-高分辨率屏幕字体自适应调整screen = app.primaryScreen()    # 返回当前主显示器的信息scale_factor = screen.logicalDotsPerInch() / 96  # 96dpi为标准缩放(100%),结果如1.75(175%缩放)font = QtGui.QFont()            # 创建默认字体font.setPointSize(int(10 * scale_factor))  # 原字体10pt,乘以缩放因子app.setFont(font)# 04-创建并显示应用窗口window = MainApp()window.show()sys.exit(app.exec_())

四、总结

代码我放在了Github上,网址是:https://github.com/luwin1127/PyQt-main-GUI-KF.git。也打包了一个.exe文件,解压就可以用。

在这里插入图片描述

点击上图中红框的位置。进入下一个页面之后,选择v0.1.1-release.zip下载。

在这里插入图片描述


不足的地方:

  • 没有使用.qrc文件,因为还没学会;
  • 编辑qss会让GUI更好看,使用了qt-material,不好看;
  • 使用pyinstaller打包程序占用空间很大;
  • 考虑使用Qt Fluent Design把各个控件美化一下。

参考文献

[1] 图形用户界面(GUI)开发教程[EB/OL]. (2024-01-20)[2025-05-30]https://leilie.top/2024-01-20/Study-GUI

[2] 王硕, 孙洋洋. PyQt5快速开发与实战电子书 [M]. 北京: 电子工业出版社, 2017.

相关文章:

使用PyQt5的图形用户界面(GUI)开发教程

文章目录 写在前面一、PyQt5的安装1.1 使用Conda管理环境1.1.1 新建环境1.1.2 conda list和pip list的区别1.1.3 conda install和pip install的区别 1.2 安装PyQt5和Qt Designer1.3 VsCode中配置Qt Designer 二、PyQt5的UI设计2.1 .ui文件设计2.2 .qrc文件建立2.3 qss设计 三、…...

STM32实战:智能环境监测站设计方案

下面是一个基于STM32的智能环境监测站设计方案&#xff0c;使用Keil MDK-ARM开发环境。这个系统集成了多种传感器&#xff0c;并通过OLED显示数据&#xff0c;同时具备数据存储和报警功能。 [STM32F4系列MCU] ├── I2C总线 │ ├── SHT30温湿度传感器 │ ├──…...

猎板硬金镀层厚度:新能源汽车高压系统的可靠性基石

在新能源汽车的电池管理系统&#xff08;BMS&#xff09;和电机控制器中&#xff0c;硬金镀层厚度直接关系到高压环境下的电气稳定性与使用寿命。猎板针对车载场景开发的耐电迁移方案&#xff08;金层 2.5μm&#xff0c;镍层 8μm&#xff09;&#xff0c;经 150℃/85% RH 高压…...

KEYSIGHT是德科技 E5063A 18G ENA系列网络分析仪

KEYSIGHT是德科技 E5063A 18G ENA系列网络分析仪 E5063A ENA 矢量网络分析仪 18GHz 2端口 降低无源射频元器件的测试成本 Keysight E5063A ENA 是一款经济适用的台式矢量网络分析仪&#xff0c;可用于测试简单的无源元器件&#xff0c;例如频率最高达到 18 GHz 的天线、滤…...

VR 虚拟仿真工器具:开启医学新视界的智慧钥匙​

VR 虚拟仿真工器具在医疗领域的应用&#xff0c;为医疗行业的发展带来了新的机遇。在手术模拟训练中&#xff0c;它让医生提前熟悉手术流程和操作技巧。对于一些复杂的手术&#xff0c;如心脏搭桥手术、神经外科手术等&#xff0c;手术难度大、风险高&#xff0c;对医生的操作技…...

webshell管理工具、C2远控服务器流量分析

文章目录 一、Webshell管理工具流量分析1. 蚁剑&#xff08;AntSword&#xff09;2. 冰蝎&#xff08;Behinder&#xff09;3. 哥斯拉&#xff08;Godzilla&#xff09;二、常见C2远控服务器流量分析1. Metasploit2. CobaltStrike 三、防御对抗策略总结 一、Webshell管理工具流…...

JavaWeb:前端工程化-TS(TypeScript)

概述 快速入门 常用类型 基础类型 联合类型 函数类型 对象类型 接口Interface Interface和type区别 典型推论...

unity+ spine切换武器不换皮肤解决方案

1.在spine编辑中获取到角色武器插槽名称 这里的武器插槽名称为“zj_22”。角色的spine正常导出到unity中。 2.将需要替换的武器图片单独放在一个spine项目里面&#xff0c;并为每个武器单独建立一个插槽。 而且全部放在根骨骼Root下。 3.将武器的spine动画导出&#xff0c;会…...

[java八股文][MySQL面试篇]SQL基础

NOSQL和SQL的区别&#xff1f; SQL数据库&#xff0c;指关系型数据库 - 主要代表&#xff1a;SQL Server&#xff0c;Oracle&#xff0c;MySQL(开源)&#xff0c;PostgreSQL(开源)。 关系型数据库存储结构化数据。这些数据逻辑上以行列二维表的形式存在&#xff0c;每一列代表…...

Ubuntu中SSH服务器安装使用

SSH服务安装 1. 安装 OpenSSH 安装 SSH 服务端&#xff08;允许远程登录&#xff09; sudo apt update sudo apt install openssh-server安装 SSH 客户端&#xff08;用于连接其他服务器&#xff09; sudo apt install openssh-client2. 检查 SSH 服务状态 sudo systemctl…...

【AI论文】SWE-rebench:一个用于软件工程代理的任务收集和净化评估的自动化管道

摘要&#xff1a;基于LLM的代理在越来越多的软件工程&#xff08;SWE&#xff09;任务中显示出有前景的能力。 然而&#xff0c;推进这一领域面临着两个关键挑战。 首先&#xff0c;高质量的训练数据稀缺&#xff0c;尤其是反映现实世界软件工程场景的数据&#xff0c;在这些场…...

Flask文件处理全攻略:安全上传下载与异常处理实战

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storms…...

【算法深练】分组循环:“分”出条理,化繁为简

目录 引言 分组循环 2760. 最长奇偶子数组 1446. 连续字符 1869. 哪种连续子字符串更长 2414. 最长的字母序连续子字符串的长度 3456. 找出长度为 K 的特殊子字符串 1957. 删除字符使字符串变好 674. 最长连续递增序列 978. 最长湍流子数组 2110. 股票平滑下跌阶段的…...

焊缝缺陷焊接缺陷识别分割数据集labelme格式5543张4类别

数据集中有超过一半为增强图片&#xff0c;请认真观察图片预览 数据集格式&#xff1a;labelme格式(不包含mask文件&#xff0c;仅仅包含jpg图片和对应的json文件) 图片数量(jpg文件个数)&#xff1a;5543 标注数量(json文件个数)&#xff1a;5543 标注类别数&#xff1a;4…...

关于scrapy在pycharm中run可以运行,但是debug不行的问题

关于scrapy在pycharm中run模式可以运行&#xff0c;但是debug模式不行的问题 文章目录 关于scrapy在pycharm中run模式可以运行&#xff0c;但是debug模式不行的问题查了下原因 点击run就可以运行&#xff0c;但是debug就是运行不了 一点击debug就报这个错&#xff0c;也不知道啥…...

Java高级 | 【实验四】Springboot 获取前端数据与返回Json数据

隶属文章&#xff1a; Java高级 | &#xff08;二十二&#xff09;Java常用类库-CSDN博客 系列文章&#xff1a; Java高级 | 【实验一】Spring Boot安装及测试 最新-CSDN博客 Java高级 | 【实验二】Springboot 控制器类相关注解知识-CSDN博客 Java高级 | 【实验三】Springboot …...

云数据库选型指南:关系型 vs NoSQL vs NewSQL的企业决策

在云时代&#xff0c;数据库选型直接关系到企业应用性能和成本效益。本文深入分析三大数据库类型&#xff0c;助您做出明智决策。 目录概览 关系型数据库&#xff1a;经典之选NoSQL数据库&#xff1a;灵活应对非结构化数据NewSQL数据库&#xff1a;融合的优势三大数据库对比分…...

Prj08--8088单板机C语言8255读取按键码

1.验证结果 2.代码片 key_codeinp(PORT_8255_C)&0x0f;tiny_sprintf(buffer,"Key_code 0X%x \r\n",key_code);uart_str_send(buffer); 3.完整代码 #include "tiny_stdarg.h" // 使用自定义可变参数实现#define ADR_273 0x0200 #define ADR_244 0x…...

蜜獾算法(HBA,Honey Badger Algorithm)

2021年由Hashim等人提出&#xff08;论文&#xff1a;Honey Badger Algorithm: A New Metaheuristic Algorithm for Solving Optimization Problems&#xff09;。模拟蜜獾在自然界中的智能捕食行为&#xff0c;属于群体智能优化算法&#xff08;与粒子群PSO、遗传算法GA同属一…...

Modbus转Ethernet IP网关助力罗克韦尔PLC数据交互

在工业自动化领域&#xff0c;Modbus协议是一种广泛应用的串行通信协议&#xff0c;它定义了主站和从站之间的通信规则和数据格式。罗克韦尔PLC是一种可编程的逻辑控制器&#xff0c;通过Modbus协议实现与其他设备之间的数据交互。然而&#xff0c;随着以太网技术的普及和发展&…...

飞算JavaAI 炫技赛重磅回归!用智能编码攻克老项目重构难题

深夜还在排查十年前Hibernate框架埋下的N1查询隐患&#xff1f;跨语言迁移时发现SpringMVC控制器里的业务逻辑像一团乱麻&#xff1f;当企业数字化进入深水区&#xff0c;百万行代码的老系统就像一座随时可能崩塌的"技术债冰山"。近日&#xff0c;飞算科技发布JavaAI…...

青少年编程与数学 02-020 C#程序设计基础 15课题、异常处理

青少年编程与数学 02-020 C#程序设计基础 15课题、异常处理 一、异常1. 异常的分类2. 异常的作用小结 二、异常处理1. 异常处理的定义2. 异常处理的主要组成部分3. 异常处理的作用小结 三、C#异常处理1. 异常的基本概念2. 异常处理的关键字3. 异常处理的流程4. 自定义异常5. 异…...

Electron打包前端和后端为exe

文章目录 什么是Electron&#xff1f; 安装electron过程 其他git项目地址比较好的文章electron的替代品安装报错 npm ERR! request to https://registry.npm.taobao.org/electron failed, reason: certificate has expired安装提示 npm WARN deprecated boolean3.2.0: Package …...

unix/linux,sudo,一个强大且灵活的工具,允许一个被授权的用户以另一个用户(通常是root,即超级用户)的身份来执行命令

sudo:不仅仅是“用管理员权限运行” sudo 这个词,来源于 SuperUser DO (或者 Substitute User DO,后者的含义更为广阔和准确)。它是一个强大且灵活的工具,允许一个被授权的用户以另一个用户(通常是root,即超级用户)的身份来执行命令。 1. Unix/Linux 的权限哲学:最小…...

JavaScript 二维数组初始化:为什么 fill([]) 是个大坑?

JavaScript 二维数组初始化&#xff1a;为什么 fill([]) 是个大坑&#xff1f; 今天刷leetcode的时候&#xff0c;遇到一个神奇的bug。 当我修改数组中的一个元素&#xff0c;却意外影响了其他所有元素&#xff1f;&#xff1f;&#xff1f;。 问题重现&#xff1a;诡异的数组…...

项目任务,修改svip用户的存储空间。

修改存储空间 3GB->5GB&#xff0c;这是项目任务&#xff0c;首先有人任务就要去思考实现思路&#xff0c;首先存储空间&#xff0c;也就是说不只是前端样式3GB改一下就可以了&#xff0c;那用户实际还是3GB&#xff0c;所以我们去网站看后端谁返回给我们了3GB&#xff0c;我…...

TypeScript 全面学习指南 (2025最新版)

TypeScript 全面学习指南 目录 TypeScript 简介环境搭建与工具基础类型变量声明接口&#xff08;Interfaces&#xff09;类&#xff08;Classes&#xff09;函数&#xff08;Functions&#xff09;泛型&#xff08;Generics&#xff09;枚举&#xff08;Enums&#xff09;类型…...

【redis】过期策略 懒惰删除

过期删除&#xff1a; redis会将所有设置过期时间的key以及过期时间存储在字典里。 redis采取两个策略实现删除过期key&#xff1a; 1、定时删除&#xff1a;定期扫描字典&#xff0c;采用贪心的策略&#xff0c;从字典随机抽20个key&#xff0c;删除其中已经过期的key&#x…...

Docker或Docker-Compose时间时区配置

Docker或Docker-Compose配置时区&#xff0c;主要是为了使用容器内的时间和物理机操作系统的时间保持一致。以下是集中配置Docker或Docker-Compose环境时间时区的方式。 Dockerfile&#xff08;Docker&#xff09;中配置时区 在Dockerfile中&#xff0c;可以通过如下方式添加…...

如何在IDE中通过Spark操作Hive

在IDE中通过Spark操作Hive是一项常见的任务&#xff0c;特别是在大数据处理和分析的场景中。本文将详细介绍如何在集成开发环境&#xff08;IDE&#xff09;中使用Apache Spark与Hive进行交互&#xff0c;包括必要的设置、代码示例以及详细解释。 环境准备 在开始之前&#x…...