Qt PCL学习(三):点云滤波

news2024/9/21 4:35:26

注意事项

  • 版本一览:Qt 5.15.2 + PCL 1.12.1 + VTK 9.1.0
  • 前置内容:Qt PCL学习(一):环境搭建、Qt PCL学习(二):点云读取与保存

0. 效果演示

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1. pcl_open_save.pro

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

// 添加下行代码(根据自己安装目录进行修改)
include(D:/PCL1.12.1/pcl1.12.1.pri)

2. mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <voxel_filtering.h>
#include <vector>
#include <QMainWindow>
#include <QDebug>
#include <QColorDialog>
#include <QMessageBox>
#include <QFileDialog>
#include <QTime>
#include <QDir>
#include <QFile>
#include <QtMath>
#include <QWindow>
#include <QAction>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QIcon>
#include <QMenuBar>
#include <QMenu>
#include <QToolBar>
#include <QStatusBar>
#include <QFont>
#include <QString>
#include <QTextBrowser>
#include <QDirIterator>
#include <QStandardItemModel>
#include <QModelIndex>

#include "QVTKOpenGLNativeWidget.h"
#include <vtkSphereSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <vtkNamedColors.h>
#include <vtkProperty.h>
#include <vtkSmartPointer.h>
#include "vtkAutoInit.h"

VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);
VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2);
VTK_MODULE_INIT(vtkRenderingFreeType);

#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/filters/voxel_grid.h>

#ifndef TREE_ITEM_ICON_DataItem
#define TREE_ITEM_ICON_DataItem QStringLiteral("treeItem_folder")
#endif

typedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<PointT> PointCloudT;
typedef pcl::visualization::PCLVisualizer PCLViewer;
typedef std::shared_ptr<PointCloudT> PointCloudPtr;

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow {
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    PointCloudT::Ptr pcl_voxel_filter(PointCloudT::Ptr cloud_in, float leaf_size);
    void view_updata(std::vector<PointCloudT::Ptr> vector_cloud, std::vector<int> index);

private slots:
    void open_clicked();  // 打开文件
    void save_clicked();  // 保存文件

    void on_treeView_clicked(const QModelIndex &index);

    // 点云滤波
    void pressBtn_voxel();
    void voxel_clicked(QString data);

private:
    Ui::MainWindow *ui;

    QMap<QString, QIcon> m_publicIconMap;   // 存放公共图标

    QStandardItemModel* model;
    QStandardItem* itemFolder;

    QModelIndex index_cloud;
    std::vector<PointCloudT::Ptr> cloud_vec;
    std::vector<int> cloud_index;

    // 点云名称
    std::vector<std::string> cloud_name{"0", "1", "2"};
    int point_size = 1;

    PointCloudPtr cloudptr;
    PCLViewer::Ptr cloud_viewer;

    voxel_filtering *dialog_voxel;
};
#endif // MAINWINDOW_H

3. mainwindow.cpp

#pragma execution_character_set("utf-8")

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);
    // 设置窗口标题和 logo
    this->setWindowTitle("CloudOne");
    this->setWindowIcon(QIcon(":/resourse/icon.ico"));

    m_publicIconMap[TREE_ITEM_ICON_DataItem] = QIcon(QStringLiteral(":/resourse/folder.png"));
    model = new QStandardItemModel(ui->treeView);
    model->setHorizontalHeaderLabels(QStringList()<<QStringLiteral("--cloud--DB-Tree--"));
    ui->treeView->setHeaderHidden(true);

    ui->treeView->setModel(model);
    ui->treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);  // 设置多选

    cloudptr.reset(new PointCloudT);
    cloud_viewer.reset(new pcl::visualization::PCLVisualizer("viewer", false));
    vtkNew<vtkGenericOpenGLRenderWindow> window;
    window->AddRenderer(cloud_viewer->getRendererCollection()->GetFirstRenderer());
    ui->openGLWidget->setRenderWindow(window.Get());
    cloud_viewer->setupInteractor(ui->openGLWidget->interactor(), ui->openGLWidget->renderWindow());
    ui->openGLWidget->update();

    // 创建菜单栏
    QMenuBar *menu_bar = new QMenuBar(this);
    this->setMenuBar(menu_bar);
    menu_bar->setStyleSheet("font-size : 16px");

    // 1、File 下拉列表
    QMenu *file_menu = new QMenu("File", menu_bar);
    QAction *open_action = new QAction("Open File");
    QAction *save_action = new QAction("Save File");
    QAction *exit_action = new QAction("Exit");
    // 添加动作到文件菜单
    file_menu->addAction(open_action);
    file_menu->addAction(save_action);
    file_menu->addSeparator();  // 添加菜单分隔符将 exit 单独隔离开
    file_menu->addAction(exit_action);
    // 把 File 添加到菜单栏
    menu_bar->addMenu(file_menu);

    // 2、Filter 下拉列表
    QMenu *filter_menu = new QMenu("Filter", menu_bar);
    QAction *voxel_action = new QAction("Voxel Filtering");
    filter_menu->addAction(voxel_action);
    menu_bar->addMenu(filter_menu);


    // 信号与槽函数链接
    connect(open_action, SIGNAL(triggered()), this, SLOT(open_clicked()));  // 打开文件
    connect(save_action, SIGNAL(triggered()), this, SLOT(save_clicked()));  // 保存文件
    connect(exit_action, SIGNAL(triggered()), this, SLOT(close()));         // 退出
    connect(voxel_action, SIGNAL(triggered()), this, SLOT(pressBtn_voxel()));
}

MainWindow::~MainWindow() {
    delete ui;
}

PointCloudT::Ptr MainWindow::pcl_voxel_filter(PointCloudT::Ptr cloud_in, float leaf_size) {
    pcl::VoxelGrid<PointT> voxel_grid;
    voxel_grid.setLeafSize(leaf_size, leaf_size, leaf_size);
    voxel_grid.setInputCloud(cloud_in);
    PointCloudT::Ptr cloud_out (new PointCloudT()) ;
    voxel_grid.filter(*cloud_out);

    return cloud_out;
}

void MainWindow::pressBtn_voxel() {
    dialog_voxel = new voxel_filtering();
    connect(dialog_voxel, SIGNAL(sendData(QString)), this, SLOT(voxel_clicked(QString)));

    if (dialog_voxel->exec() == QDialog::Accepted){}

    delete dialog_voxel;
}

// 体素采样
void MainWindow::voxel_clicked(QString data) {
    if (cloudptr->empty()) {
        QMessageBox::warning(this, "Warning", "None point cloud!");
        return;
    } else {
        if (data.isEmpty()) {
            QMessageBox::warning(this, "Warning", "Wrong format!");
            return;
        }

        float size = data.toFloat();
        auto cloud_out = pcl_voxel_filter(cloudptr, size);
        cloudptr = cloud_out;

        int size1 = static_cast<int>(cloudptr->size());
        QString PointSize = QString("%1").arg(size1);

        ui->textBrowser_2->clear();
        ui->textBrowser_2->insertPlainText("PCD number: " + PointSize);
        ui->textBrowser_2->setFont(QFont("Arial", 9, QFont::Normal));

        cloud_viewer->removeAllPointClouds();
        cloud_viewer->removeAllShapes();
        cloud_viewer->addPointCloud<pcl::PointXYZ>(cloudptr->makeShared(), std::to_string(cloud_vec.size()-1));
        cloud_viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, point_size, std::to_string(cloud_vec.size()-1));
        cloud_viewer->resetCamera();

        // 设置颜色处理器,将点云数据添加到 cloud_viewer 中
        const std::string axis = "z";
        pcl::visualization::PointCloudColorHandlerGenericField<PointT> color_handler(cloudptr, axis);
        cloud_viewer->addPointCloud(cloudptr, color_handler, "cloud");
        cloud_viewer->addPointCloud(cloudptr, "cloud");
    }
}

void MainWindow::open_clicked() {
    // this:代表当前对话框的父对象;tr("open file"):作为对话框的标题显示在标题栏中,使用 tr 函数表示这是一个需要翻译的文本
    // "":代表对话框的初始目录,这里为空表示没有指定初始目录
    // tr("pcb files(*.pcd *.ply *.txt) ;;All files (*.*)"):过滤器,决定在对话框中只能选择指定类型的文件
    QString fileName = QFileDialog::getOpenFileName(this, tr("open file"), "", tr("point cloud files(*.pcd *.ply) ;; All files (*.*)"));

    if (fileName.isEmpty()) {
        return;
    }
    if (fileName.endsWith("ply")) {
        qDebug() << fileName;
        if (pcl::io::loadPLYFile(fileName.toStdString(), *cloudptr) == -1) {
            qDebug() << "Couldn't read .ply file \n";
            return ;
        }
    } else if (fileName.endsWith("pcd")) {
        qDebug() << fileName;
        if (pcl::io::loadPCDFile(fileName.toStdString(), *cloudptr) == -1) {
            qDebug() << "Couldn't read .pcd file \n";
            return;
        }
    } else {
        QMessageBox::warning(this, "Warning", "Wrong format!");
    }

    cloud_vec.push_back(cloudptr->makeShared());
    cloud_index.push_back(1);

    itemFolder = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_folder")], QStringLiteral("cloud%1").arg(cloud_vec.size()-1));
    itemFolder->setCheckable(true);
    itemFolder->setCheckState(Qt::Checked);  // 获取选中状态
    model->appendRow(itemFolder);

    int size = static_cast<int>(cloudptr->size());
    QString PointSize = QString("%1").arg(size);

    ui->textBrowser_2->clear();
    ui->textBrowser_2->insertPlainText("PCD number: " + PointSize);
    ui->textBrowser_2->setFont(QFont("Arial", 9, QFont::Normal));

    cloud_viewer->addPointCloud<pcl::PointXYZ>(cloudptr->makeShared(), std::to_string(cloud_vec.size()-1));
    // 设置点云大小
    cloud_viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, point_size, std::to_string(cloud_vec.size()-1));
    cloud_viewer->resetCamera();
    ui->openGLWidget->renderWindow()->Render();
    ui->openGLWidget->update();

    // 设置颜色处理器,将点云数据添加到 cloud_viewer 中
    const std::string axis = "z";
    pcl::visualization::PointCloudColorHandlerGenericField<PointT> color_handler(cloudptr, axis);
    cloud_viewer->addPointCloud(cloudptr, color_handler, "cloud");
    cloud_viewer->addPointCloud(cloudptr, "cloud");
}

void MainWindow::save_clicked() {
    int return_status;
    QString filename = QFileDialog::getSaveFileName(this, tr("Open point cloud"), "", tr("Point cloud data (*.pcd *.ply)"));

    if (cloudptr->empty()) {
        return;
    } else {
        if (filename.isEmpty()) {
            return;
        }
        if (filename.endsWith(".pcd", Qt::CaseInsensitive)) {
            return_status = pcl::io::savePCDFileBinary(filename.toStdString(), *cloudptr);
        } else if (filename.endsWith(".ply", Qt::CaseInsensitive)) {
            return_status = pcl::io::savePLYFileBinary(filename.toStdString(), *cloudptr);
        } else {
            filename.append(".ply");
            return_status = pcl::io::savePLYFileBinary(filename.toStdString(), *cloudptr);
        }
        if (return_status != 0) {
            PCL_ERROR("Error writing point cloud %s\n", filename.toStdString().c_str());
            return;
        }
    }
}

void MainWindow::view_updata(std::vector<PointCloudT::Ptr> vector_cloud, std::vector<int> index) {
    cloud_viewer.reset(new pcl::visualization::PCLVisualizer("viewer", false));
    vtkNew<vtkGenericOpenGLRenderWindow> window;
    window->AddRenderer(cloud_viewer->getRendererCollection()->GetFirstRenderer());
    ui->openGLWidget->setRenderWindow(window.Get());

    cloud_viewer->removeAllPointClouds();
    cloud_viewer->removeAllShapes();
    for (int i = 0; i<vector_cloud.size(); i++) {
        if (index[i] == 1) {
            pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZ>render(vector_cloud[i], "intensity");
            cloud_viewer->addPointCloud<pcl::PointXYZ>(vector_cloud[i], render, std::to_string(i));
            cloud_viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, point_size, std::to_string(i));
        }
    }
    cloud_viewer->resetCamera();
    ui->openGLWidget->update();
}

// 确定 index
void MainWindow::on_treeView_clicked(const QModelIndex &index) {
    QStandardItem* item = model->itemFromIndex(index);

    // 点云数量更改
    QStandardItemModel* model = static_cast<QStandardItemModel*>(ui->treeView->model());
    QModelIndex index_temp = ui->treeView->currentIndex();

    int size = static_cast<int>(cloud_vec[index_temp.row()]->size());
    QString PointSize = QString("%1").arg(size);

    ui->textBrowser_2->clear();
    ui->textBrowser_2->insertPlainText("Point cloud number: " + PointSize);
    ui->textBrowser_2->setFont(QFont("Arial", 9, QFont::Normal));

    // 可视化更改
    if (item == nullptr)
        return;
    if (item->isCheckable()) {
        //判断状态
        Qt::CheckState state = item->checkState();  // 获取当前的选择状态
        if (Qt::Checked == state) {
            cloud_index[index.row()] = 1;
        }
        if (Qt::Unchecked == state) {
             cloud_index[index.row()] = 0;
        }
        view_updata(cloud_vec, cloud_index);
    }
}

4. voxel_filtering.h

#ifndef VOXEL_FILTERING_H
#define VOXEL_FILTERING_H

#include <QDialog>
#include <QString>

namespace Ui {
class voxel_filtering;
}

class voxel_filtering : public QDialog {
    Q_OBJECT

signals:
    void sendData(QString data);

public:
    explicit voxel_filtering(QWidget *parent = nullptr);
    ~voxel_filtering();

private slots:
    void on_buttonBox_accepted();

private:
    Ui::voxel_filtering *ui;
};

#endif // VOXEL_FILTERING_H

5. voxel_filtering.cpp

#include "voxel_filtering.h"
#include "ui_voxel_filtering.h"

voxel_filtering::voxel_filtering(QWidget *parent) : QDialog(parent), ui(new Ui::voxel_filtering) {
    ui->setupUi(this);
}

voxel_filtering::~voxel_filtering() {
    delete ui;
}

void voxel_filtering::on_buttonBox_accepted() {
    emit sendData(ui->lineEdit->text());

    this->close();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1441454.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【RT-DETR改进涨点】更加聚焦的边界框损失Focaler-IoU、InnerFocalerIoU(二次创新)

一、本文介绍 本文给大家带来的改进机制是更加聚焦的边界框损失Focaler-IoU已经我进行二次创新的InnerFocalerIoU同时本文的内容支持现阶段的百分之九十以上的IoU,比如Focaler-IoU、Focaler-ShapeIoU、Inner-Focaler-ShapeIoU包含非常全的损失函数,边界框的损失函数只看这一…

MySQL-视图/储存过程/触发器

一、视图 1.介绍 视图&#xff08;View&#xff09;是一种虚拟存在的表。视图中的数据并在数据库中实际存在&#xff0c;行和列数据来自定义视图的查询中使用的表&#xff0c;并且是在使用视图时动态生成的。 通俗的讲&#xff0c;视图只保存了查询的SQL逻辑&#xff0c;不保…

幻兽帕鲁服务器搭建,阿里云和腾讯云随便选,看看哪个简单?

幻兽帕鲁官方服务器不稳定&#xff1f;自己搭建幻兽帕鲁服务器&#xff0c;低延迟、稳定不卡&#xff0c;目前阿里云和腾讯云均推出幻兽帕鲁专用服务器&#xff0c;腾讯云直接提供幻兽帕鲁镜像系统&#xff0c;阿里云通过计算巢服务&#xff0c;均可以一键部署&#xff0c;鼠标…

Redis核心技术与实战【学习笔记】 - 23.Redis 主从切换故障,有哪些坑

前言 Redis 的主从同步机制不仅可以让从库服务更多的读请求&#xff0c;分担主库的压力&#xff0c;而且还能在主库发生故障时&#xff0c;进行主从库切换&#xff0c;提供高可靠服务。 不过&#xff0c;在实际使用主从机制时会踩到一些“坑”&#xff1a;主从数据不一致、读…

数据结构第十四天(树的存储/双亲表示法)

目录 前言 概述 接口&#xff1a; 源码&#xff1a; 测试函数&#xff1a; 运行结果&#xff1a; 往期精彩内容 前言 孩子&#xff0c;一定要记得你的父母啊&#xff01;&#xff01;&#xff01; 哈哈&#xff0c;今天开始学习树结构中的双亲表示法&#xff0c;让孩…

YOLOv5独家原创改进:大核卷积涨点系列| Shift-ConvNets,稀疏/移位操作让小卷积核也能达到大卷积核效果 | 2024年最新论文

💡💡💡本文独家改进:大的卷积核设计成为使卷积神经网络(CNNs)再次强大的理想解决方案,Shift-ConvNets稀疏/移位操作让小卷积核也能达到大卷积核效果,创新十足实现涨点,助力YOLOv8 💡💡💡在多个私有数据集和公开数据集VisDrone2019、PASCAL VOC实现涨点 收录…

VSCode如何让先前打开的文件不被自动关闭,一直保持在标签栏里(关闭预览模式)

第一次接触VSCode-Huawei IDE编辑器&#xff0c;每次打开一个新的代码文件&#xff0c;旧的代码文件都会被自动关闭&#xff08;现在才知道是因为文件默认是以预览模式打开展示的&#xff09;。 那么如何才能让先前打开的文件一直保持在标签栏里呢&#xff1f; 我们需要去设置…

Mac电脑如何通过终端隐藏应用程序?

在我们使用Mac电脑的时候难免会遇到想要不想看到某个应用程序又不想卸载它们。值得庆幸的是&#xff0c;macOS具有一些强大的文件管理功能&#xff0c;允许用户轻松隐藏&#xff08;以及稍后显示&#xff09;文件甚至应用程序。 那么&#xff0c;Mac电脑如何通过终端隐藏应用程…

Composition Local

1.显示传参 package com.jmj.jetpackcomposecompositionlocalimport org.junit.Testimport org.junit.Assert.*/*** 显示传参*/ class ExplicitText {private fun Layout(){var color:String "黑色";//参数需要通过层层传递&#xff0c;比较繁琐Text(color)Grid(c…

idea: 无法创建Java Class文件(SpringBoot)已解决

第一&#xff1a;点击file-->project Sructure... 第二步&#xff1a;点击Moudules 选择自己需要创建java的文件夹&#xff08;我这里选择的是main&#xff09;右键点击Sources&#xff0c;然后点击OK即可 然后就可以创建java类了

EasyRecovery免费版2024电脑数据恢复利器

在数字化时代&#xff0c;我们的生活和工作都离不开电脑&#xff0c;电脑硬盘中的数据却时常面临丢失的风险&#xff0c;无论是因为误删除、格式化、病毒感染还是硬件故障&#xff0c;都可能让我们付出沉重的代价&#xff0c;在这种情况下&#xff0c;一款强大的数据恢复软件就…

深度学习在知识图谱问答中的革新与挑战

目录 前言1 背景知识2 基于深度学习改进问句解析模型2.1 谓词匹配2.2 问句解析2.3 逐步生成查询图 3 基于深度学习的端到端模型3.1 端到端框架3.2 简单嵌入技术 4 优势4.1 深入的问题表示4.2 实体关系表示深挖4.3 候选答案排序效果好 5 挑战5.1 依赖大量训练语料5.2 推理类问句…

Python常见的免杀方式

10.1节介绍了通过msfvenom生成shellcode &#xff0c;并通过Python程序加载执行&#xff0c;又 介绍了如何将Python的.py文件生成为exe文件。使用pyinstaller生成的可执行文件 本身就具有一定的免杀能力&#xff0c;但是在与杀毒软件对抗时&#xff0c;部分杀毒软件也可以通 过…

双非本科准备秋招(21.2)—— ReentrantLock

一、vs synchronized 可中断可以设置超时时间可以设置为公平锁支持多个条件变量 语法&#xff1a; // 获取锁reentrantLock.lock();try {// 临界区} finally {// 释放锁reentrantLock.unlock();} 二、可重入 连续三次上锁。 Slf4j(topic "c.test") public class…

软件应用实例分享,电玩计时计费怎么算,佳易王PS5游戏计时器系统程序教程

软件应用实例分享&#xff0c;电玩计时计费怎么算&#xff0c;佳易王PS5游戏计时器系统程序教程 一、前言 以下软件教程以 佳易王电玩计时计费管理系统软件V17.9为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 点击开始计时后&#xff0c;图片…

【深度学习:SegGPT】在上下文中分割所有内容 [解释]

【深度学习&#xff1a;SegGPT】在上下文中分割所有内容 [解释] SegGPT与以前的模型相比如何&#xff1f;SegGPT在实践中是如何工作的&#xff1f;SegGPT培训计划上下文着色上下文集成上下文调整SegGPT 训练参数 如何尝试 SegGPT&#xff1f;使用哪些数据集来训练 SegGPT&#…

前端页面禁止debugger调试并跳转空白页面----文心一言官网实现方式

技术点&#xff1a;setInterval定时器Object.defineProperty 背景&#xff1a; 某天打开文心一言想看看接口返回结构是怎样的&#xff0c;熟练的打开浏览器开发者工具查看网络请求。 发现出现了以下debugger断点 这难不倒我&#xff0c;去掉断点调试&#xff0c;继续下一步不…

Django模板(三)

一、标签URL 返回与给定视图和可选参数相匹配的绝对路径引用(不含域名的 URL) {% url some-url-name v1 v2 %} 第一个参数是url模式名称,后面跟着的是参数,以空格分隔可以使用关键字: {% url some-url-name arg1=v1 arg2=v2 %}如果您想检索命名空间的URL,请指定完全限定…

【前端web入门第四天】01 复合选择器与伪类选择器

文章目录: 1. 复合选择器 1.1 后代选择器 1.2 子代选择器 1.3 并集选择器1.4 交集选择器(了解) 2.伪类选择器 2.1 伪类-文本2.2 伪类-超链接&#xff08;拓展) 1. 复合选择器 什么叫复合选择器? 由两个或多个基础选择器&#xff0c;通过不同的方式组合而成。 复合选择器的作…

《动手学深度学习(PyTorch版)》笔记7.6

注&#xff1a;书中对代码的讲解并不详细&#xff0c;本文对很多细节做了详细注释。另外&#xff0c;书上的源代码是在Jupyter Notebook上运行的&#xff0c;较为分散&#xff0c;本文将代码集中起来&#xff0c;并加以完善&#xff0c;全部用vscode在python 3.9.18下测试通过&…