美文网首页
PyQt5笔记1:词云小程序设计后记

PyQt5笔记1:词云小程序设计后记

作者: 烟月不知 | 来源:发表于2021-06-09 17:54 被阅读0次

    词云小程序设计并不难,但在封装时却碰到了一个大坑。历经一个多小时的搜索,问题才得以解决。做个后记,以备后用。

    预期功能

    • 打开任意一个UTF-8编码的文本文件和一张图片,能按图片轮廓生成词云;
    • 生成词云时,能停用一些无用的关键词。

    开发工具

    • Python 3.8.6
    • PyCharm 2021.1.1
    • Qt Designer
    • PyUIC

    需要加载的库

    • sys 系统内置库
    • OpenCV 一款开源的计算机视觉和机器学习软件库
    • matplotlib 一款 Python 的绘图库
    • jieba 一款优秀的 Python 第三方中文分词库
    • wordcloud 一款优秀的词云展示第三方库
    • PyQt5 Python中一款优秀的GUI界面模块

    UI界面的设计

    1. 使用Qt Designer进行界面设计和布局。得到界面图如下:


      UI界面图
    1. 使用扩展工具PyUIC将UI界面文件Txt_Image.ui转为Txt_Image.py文件。代码如下:
    # -*- coding: utf-8 -*-
    
    # Form implementation generated from reading ui file 'Txt_Image.ui'
    #
    # Created by: PyQt5 UI code generator 5.15.4
    #
    # WARNING: Any manual changes made to this file will be lost when pyuic5 is
    # run again.  Do not edit this file unless you know what you are doing.
    
    
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class Ui_MainWindow(object):
        def setupUi(self, MainWindow):
            MainWindow.setObjectName("MainWindow")
            MainWindow.resize(958, 758)
            MainWindow.setMinimumSize(QtCore.QSize(800, 600))
            MainWindow.setMaximumSize(QtCore.QSize(16777215, 16777215))
            MainWindow.setStyleSheet("font: 10pt \"微软雅黑\";")
            self.centralwidget = QtWidgets.QWidget(MainWindow)
            self.centralwidget.setObjectName("centralwidget")
            self.formLayout = QtWidgets.QFormLayout(self.centralwidget)
            self.formLayout.setObjectName("formLayout")
            self.verticalLayout_2 = QtWidgets.QVBoxLayout()
            self.verticalLayout_2.setObjectName("verticalLayout_2")
            self.verticalLayout_3 = QtWidgets.QVBoxLayout()
            self.verticalLayout_3.setObjectName("verticalLayout_3")
            self.label = QtWidgets.QLabel(self.centralwidget)
            self.label.setStyleSheet("font: 57 40pt \"李旭科书法 v1.4\";\n"
    "color: rgb(85, 170, 0);")
            self.label.setAlignment(QtCore.Qt.AlignCenter)
            self.label.setObjectName("label")
            self.verticalLayout_3.addWidget(self.label)
            self.label_2 = QtWidgets.QLabel(self.centralwidget)
            self.label_2.setAlignment(QtCore.Qt.AlignCenter)
            self.label_2.setObjectName("label_2")
            self.verticalLayout_3.addWidget(self.label_2)
            self.verticalLayout_2.addLayout(self.verticalLayout_3)
            self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
            self.groupBox.setTitle("")
            self.groupBox.setAlignment(QtCore.Qt.AlignCenter)
            self.groupBox.setObjectName("groupBox")
            self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.groupBox)
            self.horizontalLayout_3.setObjectName("horizontalLayout_3")
            self.gridLayout = QtWidgets.QGridLayout()
            self.gridLayout.setObjectName("gridLayout")
            self.label_3 = QtWidgets.QLabel(self.groupBox)
            self.label_3.setObjectName("label_3")
            self.gridLayout.addWidget(self.label_3, 0, 0, 1, 1)
            self.lineEdit = QtWidgets.QLineEdit(self.groupBox)
            self.lineEdit.setObjectName("lineEdit")
            self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1)
            self.pushButton = QtWidgets.QPushButton(self.groupBox)
            self.pushButton.setObjectName("pushButton")
            self.gridLayout.addWidget(self.pushButton, 0, 2, 1, 1)
            self.label_4 = QtWidgets.QLabel(self.groupBox)
            self.label_4.setObjectName("label_4")
            self.gridLayout.addWidget(self.label_4, 1, 0, 1, 1)
            self.lineEdit_2 = QtWidgets.QLineEdit(self.groupBox)
            self.lineEdit_2.setObjectName("lineEdit_2")
            self.gridLayout.addWidget(self.lineEdit_2, 1, 1, 1, 1)
            self.pushButton_2 = QtWidgets.QPushButton(self.groupBox)
            self.pushButton_2.setObjectName("pushButton_2")
            self.gridLayout.addWidget(self.pushButton_2, 1, 2, 1, 1)
            self.horizontalLayout_3.addLayout(self.gridLayout)
            self.verticalLayout_2.addWidget(self.groupBox)
            self.groupBox_2 = QtWidgets.QGroupBox(self.centralwidget)
            self.groupBox_2.setTitle("")
            self.groupBox_2.setObjectName("groupBox_2")
            self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2)
            self.gridLayout_2.setObjectName("gridLayout_2")
            self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
            self.horizontalLayout_2.setObjectName("horizontalLayout_2")
            self.verticalLayout = QtWidgets.QVBoxLayout()
            self.verticalLayout.setObjectName("verticalLayout")
            self.textEdit = QtWidgets.QTextEdit(self.groupBox_2)
            self.textEdit.setObjectName("textEdit")
            self.verticalLayout.addWidget(self.textEdit)
            self.textEdit_2 = QtWidgets.QTextEdit(self.groupBox_2)
            self.textEdit_2.setObjectName("textEdit_2")
            self.verticalLayout.addWidget(self.textEdit_2)
            self.horizontalLayout = QtWidgets.QHBoxLayout()
            self.horizontalLayout.setObjectName("horizontalLayout")
            self.pushButton_3 = QtWidgets.QPushButton(self.groupBox_2)
            self.pushButton_3.setObjectName("pushButton_3")
            self.horizontalLayout.addWidget(self.pushButton_3)
            self.pushButton_4 = QtWidgets.QPushButton(self.groupBox_2)
            self.pushButton_4.setObjectName("pushButton_4")
            self.horizontalLayout.addWidget(self.pushButton_4)
            self.verticalLayout.addLayout(self.horizontalLayout)
            self.verticalLayout.setStretch(0, 4)
            self.verticalLayout.setStretch(1, 1)
            self.horizontalLayout_2.addLayout(self.verticalLayout)
            self.widget = QtWidgets.QWidget(self.groupBox_2)
            self.widget.setObjectName("widget")
            self.gridLayout_3 = QtWidgets.QGridLayout(self.widget)
            self.gridLayout_3.setObjectName("gridLayout_3")
            self.label_5 = QtWidgets.QLabel(self.widget)
            self.label_5.setMaximumSize(QtCore.QSize(16777215, 16777215))
            self.label_5.setScaledContents(False)
            self.label_5.setAlignment(QtCore.Qt.AlignCenter)
            self.label_5.setObjectName("label_5")
            self.gridLayout_3.addWidget(self.label_5, 0, 0, 1, 1)
            self.horizontalLayout_2.addWidget(self.widget)
            self.horizontalLayout_2.setStretch(0, 1)
            self.horizontalLayout_2.setStretch(1, 2)
            self.gridLayout_2.addLayout(self.horizontalLayout_2, 0, 0, 1, 1)
            self.verticalLayout_2.addWidget(self.groupBox_2)
            self.formLayout.setLayout(0, QtWidgets.QFormLayout.SpanningRole, self.verticalLayout_2)
            MainWindow.setCentralWidget(self.centralwidget)
            self.statusbar = QtWidgets.QStatusBar(MainWindow)
            self.statusbar.setObjectName("statusbar")
            MainWindow.setStatusBar(self.statusbar)
            self.label_3.setBuddy(self.lineEdit)
            self.label_4.setBuddy(self.lineEdit_2)
    
            self.retranslateUi(MainWindow)
            QtCore.QMetaObject.connectSlotsByName(MainWindow)
    
        def retranslateUi(self, MainWindow):
            _translate = QtCore.QCoreApplication.translate
            MainWindow.setWindowTitle(_translate("MainWindow", "酷炫词云 程序设计:王立武"))
            self.label.setText(_translate("MainWindow", "酷炫词云"))
            self.label_2.setText(_translate("MainWindow", "程序设计:王立武  2021年6月8日"))
            self.label_3.setText(_translate("MainWindow", "打开生成词云文本"))
            self.lineEdit.setPlaceholderText(_translate("MainWindow", "请打开UTF-8编码的TXT格式的文本文件"))
            self.pushButton.setText(_translate("MainWindow", "浏览"))
            self.label_4.setText(_translate("MainWindow", "打开词云轮廓图片"))
            self.lineEdit_2.setPlaceholderText(_translate("MainWindow", "建议使用白底的图片以得至更好的轮廓词云图"))
            self.pushButton_2.setText(_translate("MainWindow", "浏览"))
            self.textEdit.setPlaceholderText(_translate("MainWindow", "在这里粘贴的文字不能生成词云!"))
            self.textEdit_2.setPlaceholderText(_translate("MainWindow", "注意:每个关键词之间用空格隔开!"))
            self.pushButton_3.setText(_translate("MainWindow", "过滤文本"))
            self.pushButton_4.setText(_translate("MainWindow", "生成词云"))
            self.label_5.setText(_translate("MainWindow", "词云图片展示区"))
    

    主程序的建立

    1. 导入相关的库,代码如下:
    import sys
    import cv2  # 导入图片处理模块 需安装库OpenCV。
    import matplotlib.pyplot as plt
    import jieba  # 导入jieba分词模块
    import wordcloud  # 导入词云图模块
    from PyQt5 import QtWidgets, QtGui
    from PyQt5.QtWidgets import * 
    from Txt_Image import Ui_MainWindow  # 加载UI界面Txt_Image ,实现UI界面和代码分离
    

    使用 from Txt_Image import Ui_MainWindow 的目的是为了实现UI界面和代码的分离,以便后续的修改和维护。

    1. 搭建主程序基本框架,相关代码如下:
    class MyWindow(QtWidgets.QMainWindow, Ui_MainWindow):  # 分离时,要加载UI界面类
        def __init__(self, parent=None):
            super(MyWindow, self).__init__(parent)  # 调用父类的构造方法
            self.setupUi(self)  # 调用构建子控件的方法
    
        def view_txt(self):  # 打开文本文件
            pass
    
        def view_image(self):  # 打开背景轮廓图片文件
            pass
    
        def filter_key(self):  # 过滤不生成图云的关键词
            psss
    
        def show_tuyu(self):  # 生成图云
            pass
            
    if __name__ == "__main__":
        app = QApplication(sys.argv)  # 实例化一个QApplication对象
        myWindow = MyWindow()  # 实例化自定义的窗口类
        myWindow.show()  # 展示窗口
        sys.exit(app.exec_())  # 启动事件循环,并将退出码传递给系统退出方法
    
    1. 添加信号和槽机制,代码如下:
            self.pushButton.clicked.connect(self.view_txt)
            self.pushButton_2.clicked.connect(self.view_image)
            self.pushButton_3.clicked.connect(self.filter_key)
            self.pushButton_4.clicked.connect(self.show_tuyu)
    
    1. 完善每个槽函数的功能设计。
      a. 打开文本文件功能设计(view_txt)。因常见的文本文件编码格式有几种方式,在项目中指定打开的编码方式为UTF-8,因此在打开其他编码(如:ANSI)时,会报错,故在代码中加入异常处理,提醒要求打开的文本文件为UTF-8编码。


      编码格式不对时的提示对话框

    具体代码如下:

        def view_txt(self):  # 打开文本文件
            try:
                global file_content
                file_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, "选取文件", "./", "*.txt")
                self.lineEdit.setText(file_name)
                file_content = open(file_name, "r",encoding='utf-8').read()
                self.textEdit.setText(file_content)
                # print(file_content)
            except:
                QMessageBox.critical(self,'提示','请选择一个文本文件!\n或者将文本文件的编码改为utf-8')
    

    b. 打开图片文件功能设计(view_image)。具体代码如下:

        def view_image(self):  # 打开背景轮廓图片文件
            global image_name
            image_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, "选取文件", "./", "*.png;;*.jpg;;*.jpeg")
            self.lineEdit_2.setText(image_name)
    

    c. 过滤关键词文本功能设计(filter_key)。具体代码如下:

        def filter_key(self):  # 过滤不生成图云的关键词
            global stopwords
            stopwords = set('')
            filter_values = self.textEdit_2.toPlainText()
            update_key = filter_values.split(" ")
            # print(update_key)
            stopwords.update(update_key)
    

    这个槽函数中 stopwords 的实际为以后封装失败埋下了根源,后文再说。
    代码 update_key = filter_values.split(" ") 的目的,是将用户在文本编辑框中输入的停用关键词,利用空格作为分隔符生成一个列表,以供在生成词云时调用。
    d. 生成词云图功能设计(show_tuyu)。因用户有可能在没有打开文本文件或图像文件时,程序会报错,所以在这里也加入了异常处理,以提醒使用者。如图

    未打开文件时的异常处理

    具体代码如下:

        def show_tuyu(self):  # 生成图云
            try:
                cut_text = jieba.cut(file_content)
                word = ' '.join(cut_text)
                img = cv2.imread(image_name)  # 读取数据
                if self.textEdit_2.toPlainText() == "":
                    wd = wordcloud.WordCloud(
                        mask=img,  # 背景图形,如果根据图片绘制,则需要设置
                        font_path='simhei.ttf',  # 可以改成自己喜欢的字体
                        background_color='white',  # 词云图背景颜色可以换成自己喜欢的颜色
                    )
                else:
                    wd = wordcloud.WordCloud(
                        mask=img,  # 背景图形,如果根据图片绘制,则需要设置
                        font_path='simhei.ttf',  # 可以改成自己喜欢的字体
                        stopwords=stopwords,
                        background_color='white',  # 词云图背景颜色可以换成自己喜欢的颜色
                    )
                wd.generate(word)
                plt.imshow(wd)
                plt.axis('off')  # 关闭显示x轴、y轴下标
                # plt.show()
                plt.savefig('.\images\chiyu.png', dpi=100)
                jpg = QtGui.QPixmap("./images/chiyu.png")
                self.label_5.setScaledContents(True)
                self.label_5.setPixmap(jpg)
            except:
                QMessageBox.critical(self, "提示", "请先打开文本文件和轮廓图片文件后\n再运行生成词云!")
    

    因为这只是一个练手的项目,所以没有过多去考虑把生成的词云图片都保存下来的想法,只是简单的写了一句保存图片代码:

    plt.savefig('.\images\chiyu.png', dpi=100)
    

    实际上,在这里利用获取生成图片的当前日期和时间为文件名,更为妥当。这个代码,这里就不给出了。

    测试运行

    • 效果图如下:


      测试运行效果图

    封装小程序

    前面的工作都很顺利,没有遇到什么麻烦,但封装时却碰到了一个大问题。
    回到命令行环境,并转到相应的路径下,运行如下代码开始封装:

    pyinstaller -F -w Tuyun.py  # Tuyun.py为主程序文件名
    

    封装完成,没有报错,本以为大功告成了,但在运行程序时,却报错了,如下图:

    封装后报错信息
    出这种错的原因,大多是外加的资源文件没有添加到生成EXE文件的目录中,但此程序没有加入图标等资源文件。原因到底在哪呢?
    通过某娘搜索得知出在词云库 wordcloud 中,解决办法:
    1. 找到词云库 wordcloud 的安装目录,将文件夹中的 stopwords 文件拷贝到生成EXE的文件夹中。
    2. 找到词云库 wordcloud 的安装目录,打开文件夹中的 wordcloud.py 文件,找到第33行:
    FILE = os.path.dirname(__file__)
    

    将这句代码改为:

    FILE = os.path.dirname(sys.executable)
    

    建议在做此步操作时,对原文件做好备份,以防不测。完成这些操作后,再次进行封装,运行,终于完成了。

    相关文章

      网友评论

          本文标题:PyQt5笔记1:词云小程序设计后记

          本文链接:https://www.haomeiwen.com/subject/fiveeltx.html