学习PySide2基于Python开发人工智能系统应用

学习PySide2基于Python开发人工智能系统应用


摘要

参加比赛或者活动,再目标检测或分类任务中,时常需要快速开发一个GUI可视化界面。今天我们学习的内容如下:

  1. PySide2学习路线分享

  2. 基于Qt Desinger快速拖动一个界面

  3. 将自己Python代码的输入输出与界面联动并展示

  4. 可视化程序完成后,发布代码,打包成exe


模型训练

Mask R-CNN数据标注和模型训练


一、学习PySide2使用

Pyside2就是基于Python开发Qt可视化界面的库,Qt是基于C++实现的;想要在Python上使用,肯定需要一个接口,我们选择的是Pyside2。

PyQt5也是基于Python开发Qt的库,可以把它理解为不同人维护的库,相互之间有不少共同点,选择一个学习即可。

第1步 环境安装

首先,肯定是先配置好环境。Anaconda使用不多说了哟:Anaconda命令行总结

安装Pyside2

pip install pyside2 -i https://pypi.douban.com/simple

第2步 配置好Qt Designer

Qt Designer是什么?是一个软件,称作它为Qt设计师,专门来设计ui;可以快速拉动控件,编辑控件属性,方便快捷!减少很多代码量,而

且支持随时预览。这样我们可以先确定好ui,然后实现功能的接口,前后端分离,可控性高。

在Pyside2中已经默认安装好Qt Designer,我们需要配置好它,然后能够快捷的在PyCharm中使用。

按照下述教程配置好Pycharm的Tools工具,方便快速访问Qt Designer

安装QtDesigne教程

配置完成后如下图,即可访问Qt设计师

在这里插入图片描述

  1. 点击pyside2_designer能够快速启动qt

  2. 点击pyuic5能够将Qt设计师生成的.ui文件转换成python代码


第3步 快速设计UI界面

打开Qt设计师,快速设计出,你所需要的界面。

在这里插入图片描述


第4步 学习Qt设计师和Pyside2

这时你肯定很陌生,不知道如何使用;所以这时候我们需要一个教程来学习。

英文文档:Pyside2官方文档

在这里插入图片描述


中文文档:白月黑羽:Qt图形界面Gui

在这里插入图片描述
网站上有常用的组件、属性、以及例子;自学完全足够的。


视频教程1:Python Qt 图形界面编程 - PySide2 PyQt5 PyQt PySide

我自己是先学习白月黑羽的视频教程到P12

在这里插入图片描述


视频教程2:Python Qt 控件 - 持续更新中 - PySide2 PyQt5 PyQt PySide Pyside6

在这里插入图片描述

然后又学习了UP主,2021年最新的视频,我感觉这个视频更加有用一点,视频看完结合博主的网站就开始开发自己的程序了。

白月黑羽:www.python3.vip

边查询组件的使用,结合课上讲的内容就开始开发程序了。


二、系统设计

我自己要做的东西,就是基于Mask R-CNN训练好了一个实例分割的模型,要做一个简单的GUI来展示成果。

所以我自己就大致进行了设计如下:

  • 加载模型的按钮、点击就能加载模型
  • 加载图像的按钮、点击就能加载图像
  • 运行按钮、点击就能运行程序
  • 保存按钮、点击就能保存结果
  • 交换界面、我希望自己的操作能够被简单记录呈现出来

努力两天的简单成果如下:

在这里插入图片描述


组件的选择

实现起来很快,视频学习完,参考网站实例就能完成。我主要用的控件如下:

  • 按钮(功能选择)
  • 文本框(用户交互)
  • label(标题,展示图像)
  • 选择文件(加载,保存图像)

三、发布程序

1.使用pyinstaller

使用pyinstaller打包程序成可执行文件,命令如下,其中main.py为你的程序入口,在里面加载ui文件(最好设为同级目录,它只能帮你把相关环境依赖打包,加载的各种文件路径,还需要放在对应的文件路径)

pyinstaller main.py --noconsole --hidden-import PySide2.QtXml

发布程序教程:https://www.byhy.net/tut/py/gui/qt_04/

打包完成后就像下图所示,运行main.exe即可启动GUI。当然,在打包过程中你可能会遇见许多坑,一个一个问题解决就好。
在这里插入图片描述


2.Enigma Virtual Box封装成单独的exe

当然,往往你还想再精简一下自己的程序;直接封装成一个exe可执行程序,点击即可运行,那么请使用封包工具

Enigma Virtual Box封包工具介绍

程序就长这个样子:
在这里插入图片描述

我的程序大概4个G,不建议封包,特别慢;轻量化的小程序可以封装,感觉很便捷,如果程序过大不建议使用。


踩坑记录

PNG 32位深度问题

PNG图像带有一个透明的alpha通道,怎么生成的目前不是很了解;作用也不是太清楚,但是在OpenCv读取的时候会自动屏蔽掉这个通道吧;Qt显示的时候有个BUG只显示32位深度的图像。

解决办法:读取图像,保存成PNG图像,然后在读取该图像。


参考文献

整个学习过程中,博客参考如下:


代码Code

提供源码给大家参考,可以参考部分写法,全部代码及UI文件会上传到百度云。

百度云

百度云:链接: https://pan.baidu.com/s/1iPI37yabtSGRb0XmjFLj4A?pwd=kjbu 提取码: kjbu 
--来自百度网盘超级会员v6的分享

CSDN:

CSDN:
"""
Author: yida
Time is: 2022/6/27 10:36 
this Code: 调用mask_rcnn实现土壤分割系统
"""

import json
import os
import time

import cv2
import matplotlib.pyplot as plt
import numpy as np
import torch
from PIL import Image
from PySide2 import QtCore
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication  # 加载窗口
from PySide2.QtWidgets import QFileDialog  # 加载文件
from torchvision import transforms

from backbone import resnet50_fpn_backbone
from draw_box_utils import draw_objs
from network_files import MaskRCNN

os.environ['QT_MAC_WANTS_LAYER'] = '1'

os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"


class Mask_RCNN:
    def __init__(self, ui, img_path, weights_path='./best.pth'):
        self.ui = ui
        self.num_classes = 1  # 不包含背景
        self.box_thresh = 0.9
        self.weights_path = weights_path  # 模型权重
        self.img_path = img_path  # 图像路径
        self.label_json_path = 'coco91_indices.json'

    def master(self):
        num_classes = self.num_classes  # 不包含背景
        box_thresh = self.box_thresh
        weights_path = self.weights_path
        img_path = self.img_path
        label_json_path = self.label_json_path
        # get devices
        device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
        print("using {} device.".format(device))
        self.ui.textEdit.append("using {} device.".format(device))

        # create model
        model = self.create_model(num_classes=num_classes + 1, box_thresh=box_thresh)

        # load train weights
        assert os.path.exists(weights_path), "{} file dose not exist.".format(weights_path)
        model.load_state_dict(torch.load(weights_path, map_location='cpu')["model"])
        model.to(device)
        # read class_indict
        assert os.path.exists(label_json_path), "json file {} dose not exist.".format(label_json_path)
        with open(label_json_path, 'r') as json_file:
            category_index = json.load(json_file)

        # load image
        assert os.path.exists(img_path), f"{img_path} does not exits."
        original_img = Image.open(img_path).convert('RGB')

        # from pil image to tensor, do not normalize image
        data_transform = transforms.Compose([transforms.ToTensor()])
        img = data_transform(original_img)
        # expand batch dimension
        img = torch.unsqueeze(img, dim=0)

        model.eval()  # 进入验证模式
        with torch.no_grad():
            # init
            img_height, img_width = img.shape[-2:]
            init_img = torch.zeros((1, 3, img_height, img_width), device=device)
            model(init_img)

            t_start = self.time_synchronized()
            predictions = model(img.to(device))[0]
            t_end = self.time_synchronized()
            print("inference+NMS time: {}".format(t_end - t_start))
            local_ui.textEdit.append("inference+NMS time: {}".format(t_end - t_start))

            predict_boxes = predictions["boxes"].to("cpu").numpy()
            predict_classes = predictions["labels"].to("cpu").numpy()
            predict_scores = predictions["scores"].to("cpu").numpy()
            predict_mask = predictions["masks"].to("cpu").numpy()
            predict_mask = np.squeeze(predict_mask, axis=1)  # [batch, 1, h, w] -> [batch, h, w]
            if len(predict_boxes) == 0:
                print("没有检测到任何目标!")
                self.ui.textEdit.append("没有检测到任何目标!")

                return
            print("类别: {}  置信度: {} ".format(category_index, predict_scores))
            self.ui.textEdit.append("类别: {}  置信度: {} ".format(category_index, predict_scores))

            plt.figure(figsize=(40, 30))
            plot_img = draw_objs(original_img,
                                 boxes=predict_boxes,
                                 classes=predict_classes,
                                 scores=predict_scores,
                                 masks=predict_mask,
                                 category_index=category_index,
                                 line_thickness=10,  # 3
                                 font='font.sans-serif.ttf',
                                 font_size=200)  # 20
            # plt.imshow(plot_img)
            # plt.show()
            # 保存预测的图片结果
            # plot_img.save("./test_result.jpg")
            # 保存mask图像

            img = np.array(original_img)
            # img = cv2.imread(img_path)
            predict_mask = np.array(np.squeeze(predict_mask, axis=0))
            predict_mask = np.where(predict_mask > 0.5, 1, 0)  # 这个mask是对每个类别都有一个,输出的值是0-1之间的概率, 所以要加一个判断

            img[:, :, 0] = img[:, :, 0] * predict_mask
            img[:, :, 1] = img[:, :, 1] * predict_mask
            img[:, :, 2] = img[:, :, 2] * predict_mask
            # img[img == 0] = 255
            b, g, r = cv2.split(img)
            factor = np.where((b == r) & (r == g) & (b == 0))
            b[factor] = 255
            g[factor] = 255
            r[factor] = 255
            img = cv2.merge([b, g, r])
            img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
            # 左上 右下 四个点
            point_l, point_t, point_r, point_b = int(predict_boxes[0][0]), int(predict_boxes[0][1]), int(predict_boxes[0][2]), int(predict_boxes[0][3])
            # 仅获取外接矩形区域
            test_cut = img[point_t:point_b, point_l:point_r]
            # cv2.imwrite("./test_cut.jpg", test_cut)
            # mask结果 + 最小外接矩形
            # 绘制最小外接矩形, 注释掉就不要红框
            # cv2.rectangle(img, (point_l, point_t), (point_r, point_b), (0, 0, 255), 10)
            # cv2.imwrite("./test_maskResult.jpg", img)
            # 保存mask二值图
            predict_mask = np.where(predict_mask == 1, 255, 0)
            # cv2.imwrite("./test_mask.jpg", predict_mask)
            return plot_img, img, test_cut

    @staticmethod
    def create_model(num_classes, box_thresh=0.5):
        backbone = resnet50_fpn_backbone()
        model = MaskRCNN(backbone,
                         num_classes=num_classes,
                         rpn_score_thresh=box_thresh,
                         box_score_thresh=box_thresh)

        return model

    @staticmethod
    def time_synchronized():
        torch.cuda.synchronize() if torch.cuda.is_available() else None
        return time.time()

    def save_maskResult(self, mask, path_save):
        """
        保存最终分割结
        :param mask:
        :param path_save:
        :return:
        """
        cv2.imwrite(path_save, mask)


class QtSegmentation:
    def __init__(self):
        # --------------------------ui------------------------------

        # 从文件中加载UI定义
        self.ui = QUiLoader().load('../ui/mask_rcnn.ui')

        # 按钮1: 加载模型
        self.ui.pushButton_1.clicked.connect(self.button1_loadModel)

        # 按钮2: 加载图像
        self.ui.pushButton_2.clicked.connect(self.button2_loadImage)
        self.ui.pushButton_2.setEnabled(False)

        # 按钮3: 图像分割
        self.ui.pushButton_3.clicked.connect(self.button3_segImage)
        self.ui.pushButton_3.setEnabled(False)

        # 按钮4: 保存图像
        self.ui.pushButton_4.clicked.connect(self.button4_saveImage)
        self.ui.pushButton_4.setEnabled(False)


        # 进度条
        # self.ui.progressBar.setRange(0, 5)        # 这玩意不起作用

        # ---------------------------model-------------------------------
        # 初始化模型参数
        self.model_path = './best.pth'  # 默认模型, 可加载
        self.img_path = None  # 图像路径
        # 分割结果保存
        self.seg_img = './seg_img.jpg'
        # mask图像
        self.mask = None
        # model
        self.model = None
        # 最小外接矩形
        self.cut = None

    def button1_loadModel(self):
        """
        按钮事件1
        :return:
        """
        # 加载模型
        filePath, _ = QFileDialog.getOpenFileName(
            self.ui,  # 父窗口对象
            "加载训练模型",  # 标题
            r"",  # 起始目录
            "模型 (*.pth)"  # 选择类型过滤项,过滤内容在括号中
        )
        if filePath is None:
            self.ui.textEdit.append("请正确输入模型路径...", filePath)
        else:
            self.ui.textEdit.append("加载模型: {}".format(filePath))
            self.model_path = filePath
        # 情况label1和label2的幕布
        self.ui.label1.clear()
        self.ui.label2.clear()

        self.ui.pushButton_2.setEnabled(True)


    def button2_loadImage(self):
        """
        按钮事件2
        :return:
        """
        # 选择图像
        filePath, _ = QFileDialog.getOpenFileName(
            self.ui,  # 父窗口对象
            "选择土壤图像",  # 标题
            r"",  # 起始目录
            "图片类型 (*.png *.jpg , *.jpeg)"  # 选择类型过滤项,过滤内容在括号中
        )
        if filePath is None:
            self.ui.textEdit.append("请正确加载图像路径...", filePath)

        else:
            self.ui.textEdit.append("加载图像路径: {}".format(filePath))
            # 显示图像
            self.ui.label1.setPixmap(filePath)
            self.ui.label1.setFixedSize(500, 375)
            # 先清空label2
            self.ui.label2.clear()
            # 获取图像路径
            self.img_path = filePath
        self.ui.pushButton_3.setEnabled(True)
        self.ui.pushButton_2.setEnabled(False)

    def button3_segImage(self):
        """
        按钮事件3
        :return:
        """
        img_path = self.img_path
        if img_path is None:
            self.ui.textEdit.append("请加载正确的图像...")
        else:
            model_path = self.model_path
            # 打开图像
            model = Mask_RCNN(ui=self.ui, img_path=img_path, weights_path=model_path)
            self.model = model
            plt_img, self.mask, self.cut = model.master()
            plt_img = np.array(plt_img)
            plt_img = cv2.cvtColor(plt_img, cv2.COLOR_RGB2BGR)
            # 保存图像
            cv2.imwrite(self.seg_img, plt_img)
            self.ui.textEdit.append("图像分割已完成...")

            # 显示图像
            self.ui.label2.setPixmap(self.seg_img)
            self.ui.label2.setFixedSize(500, 375)
        self.ui.pushButton_2.setEnabled(True)
        self.ui.pushButton_3.setEnabled(False)
        self.ui.pushButton_4.setEnabled(True)

    def button4_saveImage(self):
        """
        按钮事件4: 保存图像
        :return:
        """
        model = self.model
        filePath, _ = QFileDialog.getSaveFileName(
            self.ui,  # 父窗口对象
            "保存土壤图像",  # 标题
            r"",  # 起始目录
            "图片类型 (*.png *.jpg , *.jpeg)"  # 选择类型过滤项,过滤内容在括号中
        )
        if filePath is None:
            self.ui.textEdit.append("请输入正确路径...")
        else:
            model.save_maskResult(self.mask, filePath)
            # 获取新路径
            filePath_cut = filePath.split('.')[0] + '_cut' + '.png'
            # 保存最小矩形内的区域
            # model.save_maskResult(self.cut, filePath_cut)
            self.ui.textEdit.append("成功保存图像路径为: {}".format(filePath))
            # 注释掉对应输出
            # self.ui.textEdit.append("成功保存图像外接矩形路径为: {}".format(filePath_cut))
        self.ui.pushButton_2.setEnabled(True)


if __name__ == '__main__':
    # 解决mac下的问题提示
    QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
    # 初始化
    app = QApplication([])
    # 实例化类
    local_ui = QUiLoader().load('./mask_rcnn.ui')  # 初始化全局ui变量
    seg = QtSegmentation()
    # 显示
    seg.ui.show()
    # 关闭
    app.exec_()

更新记录

2022年6月29日01:12:56,到今天学习GUI就告一段落啦;研究生开题、兆易杯比赛够忙活了,加油吧!

凌晨1点,顶不住了。睡觉去,晚安!

算了,还是整体格式修改一下,哈哈哈。

2022年09月27日21:44:27:新增代码以及UI文件


### 关于 PySide6 项目实战教程及实例代码 PySide6 是 Qt 的官方 Python 绑定库,提供了丰富的 GUI 开发工具集。通过学习和实践 PySide6,开发者能够构建跨平台的应用程序。以下是关于 PySide6 项目实战的相关内容。 #### 1. 教程概述 本课程涵盖了从基础到高级的全方位知识[^1],适合不同层次的学习者。具体涉及的内容包括但不限于: - 安装配置开发环境 (Windows 11, Python 3.11, PyCharm, Anaconda)[^1] - 基础控件与界面布局的设计方法 - 使用 `QWidget`、`QMainWindow` 和 `QDialog` 创建窗口[^2] #### 2. 实战案例分析 以下是一个简单的 PySide6 应用程序示例,展示了如何创建一个带有按钮和标签的基础窗体: ```python import sys from PySide6.QtCore import Slot from PySide6.QtGui import QAction from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() # 设置窗口标题 self.setWindowTitle("PySide6 实战案例") # 添加一个标签 label = QLabel("点击按钮改变文字") self.setCentralWidget(label) # 添加一个按钮 button = QPushButton("点击我!") button.clicked.connect(self.on_button_click) # 连接信号槽 # 将按钮放置在主窗口中央 layout = QVBoxLayout() layout.addWidget(label) layout.addWidget(button) central_widget = QWidget() central_widget.setLayout(layout) self.setCentralWidget(central_widget) @Slot() def on_button_click(self): """按钮点击后的处理逻辑""" current_text = self.centralWidget().children()[0].text() new_text = f"{current_text} 被点击了!" self.centralWidget().children()[0].setText(new_text) if __name__ == "__main__": app = QApplication(sys.argv) main_window = MainWindow() main_window.show() sys.exit(app.exec()) ``` 此代码实现了如下功能: - 创建了一个基于 `QMainWindow` 的主窗口。 - 主窗口包含一个标签 (`QLabel`) 和一个按钮 (`QPushButton`)。 - 当用户点击按钮时,触发 `on_button_click()` 方法,动态修改标签的文字内容。 #### 3. 高级特性展示 除了基本组件外,还可以利用更复杂的模块完成特定任务。例如: - 数据表格视图:使用 `QTableView` 显示数据模型 - 文件对话框:调用 `QFileDialog` 打开文件选择器 - 动画效果:借助 `QPropertyAnimation` 实现平滑过渡动画[^3] #### 4. 发布与部署注意事项 完成应用程序后,可以通过打包工具将其转换成独立可执行文件: - 使用 `pyinstaller` 或其他类似工具简化分发流程。 - 确保目标平台上已安装必要的依赖项。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈嘿萌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值