安装 PyQt5 第三方库开发环境

下面直接使用 pip 来安装 PyQt5,此处可能是 pip/pip3,或者两者皆可,后面不再重复

直接 pip 安装 PyQt5,当然也可以加镜像加快安装

pip install PyQt5

由于 Qt Designer 已经在 Python3.5 版本从 PyQt5 转移到了 tools,因此我们还需要安装 pyqt5-tools

pip install pyqt5-tools

然后键盘按下 Win+S 呼出 Cornata 主面板(搜索框),输入 designer,如果看到跟下图类似的结果说明 PyQt Designer 已经被安装。
在 cmd 中输入 pyuic5,如果返回 “Error: one input ui-file must be specified” 也能够说明安装成功。

C:\Users\16204>pyuic5
Error: one input ui-file must be specified

初始化创建桌面应用程序

使用 Pycharm,做简单的测试,创建 Widget 窗口对象,进行展示

import sys
from PyQt5.QtWidgets import QApplication, QWidget

if __name__ == '__main__':
    app = QApplication(sys.argv)
    
    widget = QWidget() 
    # create the obj of widget
    widget.resize(400, 200)  
    # the size of widget window
    widget.move(300, 300)  
    # the position of widget window
    widget.setWindowTitle("第一个唤醒手腕桌面应用程序")  
    # the title of widget window
    widget.show()  
    # show widget window
    sys.exit(app.exec_())  
    # 进入程序的主循环、并通过exit函数确保主循环安全结束

运行的结果展示如下所示:

Pycharm 添加 Qt designer 扩展工具

在使用 PyQT5 创建 GUI 图形用户界面程序时,会生成扩展名为. ui 的文件,该文件需要转换为. py 文件后才可以被 Python 识别,所以需要配置 Pycharm。

名称(Name):Qt Designer [外部工具的名称 可自定义]
组(Group):使用默认值
程序(Program):C:\\Python\Python38\Lib\site-packages\QtDesigner\designer.exe [输入自己的PyQt5开发工具安装路径]
工作目录(Working directory):$ProjectFileDir$ [表示自己文件所在的项目路径]

图示操作:

当然将 ui 文件转换成 py 文件也可以采用如下命令:

xxx.ui所在目录 > pyuic5 -o XXX.py xxx.ui

main 函数加载 ui 文件运行

使用 Qt Designer 创建 English.ui,首先将 English.ui 文件转换成 English.py 文件,在 main 进行调用,展示如下:

import sys
from PyQt5.QtWidgets import QApplication, QWidget

import English

if __name__ == "__main__":
    app = QApplication(sys.argv)
    widget = QWidget()
    ui = English.Ui_Form() # 加载ui对象
    ui.setupUi(widget)
    widget.show() # 窗口展示
    sys.exit(app.exec_())

创建 Qwidget 子类进行调用

自定义继承 QWidget 的子类,进行调用测试,也可以进行窗口 logo 的设置,展示如下:

import sys

from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QWidget

class UsingTest(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setWindowTitle("唤醒手腕")
        self.setGeometry(300, 300, 350, 150)

if __name__ == '__main__':  # 程序的入口
    app = QApplication(sys.argv)
    win = UsingTest()
    win.setWindowIcon(QIcon("./excel_wrist.ico"))
    win.show()
    sys.exit(app.exec_())

如何设置窗口的居中显示:

def setCenterPosition(self):
    # 获取屏幕坐标系
    screen = QDesktopWidget().screenGeometry()
    # 获取自身的窗口坐标系
    size = self.geometry()
    newLeft = (screen.width() - size.width()) / 2
    newTop = (screen.height() - size.height()) / 2
    self.move(newLeft, newTop)

MainWindow 窗口显示图片

首先我们设计好 ui,转换成 py 文件:

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(500, 300)
        MainWindow.setMinimumSize(QtCore.QSize(500, 300))
        MainWindow.setMaximumSize(QtCore.QSize(500, 300))
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(30, 30, 240, 240))
        self.label_2.setText("")
        self.label_2.setPixmap(QtGui.QPixmap("apple.webp"))
        self.label_2.setObjectName("label_2")
        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_2.setGeometry(QtCore.QRect(310, 220, 151, 51))
        font = QtGui.QFont()
        font.setPointSize(13)
        self.pushButton_2.setFont(font)
        self.pushButton_2.setObjectName("pushButton_2")
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton_2.setText(_translate("MainWindow", "Choose Image"))

然后进行设计主 main 调用,展示如下所示:

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog
from chooseMyImage import *


class MyClass(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MyClass, self).__init__(parent)
        self.setupUi(self)
        self.pushButton_2.clicked.connect(self.openimage)

    def openimage(self):
        imgName, imgType = QFileDialog.getOpenFileName(self, "打开图片", "", "*.jpg;;*.png;;All Files(*)")

        jpg = QtGui.QPixmap(imgName).scaled(240, 240)
        self.label_2.setPixmap(jpg)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    myWin = MyClass()
    myWin.show()
    sys.exit(app.exec_())

运行结果展示如下:

调整图片显示尺寸的函数 scaled():

从源码可以看出,我们也可以根据高度与宽度进行自适应的尺寸表示。

结合 opencv 实现图片模糊处理

需要使用 PyQt5、numpy、cv2 等第三方库,代码展示:

import sys
import cv2
import numpy as np
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QGridLayout, QLabel, QPushButton


class Window(QDialog):
    def __init__(self):
        # 初始化一个img的ndarry,用于存储图像
        self.img = np.ndarray(())
        super().__init__()
        self.initUI()

    def initUI(self):
        self.resize(500, 400)
        self.btnOpen = QPushButton('Open', self)
        self.btnSave = QPushButton('Save', self)
        self.btnProcess = QPushButton('Process', self)
        self.btnQuit = QPushButton('Quit', self)
        self.label = QLabel()

        # 布局设定
        layout = QGridLayout(self)
        layout.addWidget(self.label, 0, 1, 3, 4)
        layout.addWidget(self.btnOpen, 4, 1, 1, 1)
        layout.addWidget(self.btnSave, 4, 2, 1, 1)
        layout.addWidget(self.btnProcess, 4, 3, 1, 1)
        layout.addWidget(self.btnQuit, 4, 4, 1, 1)

        # 信号与槽进行连接,信号可绑定普通成员函数
        self.btnOpen.clicked.connect(self.openSlot)
        self.btnSave.clicked.connect(self.saveSlot)
        self.btnProcess.clicked.connect(self.processSlot)
        self.btnQuit.clicked.connect(self.close)

    def openSlot(self):
        # 调用存储文件
        fileName, tmp = QFileDialog.getOpenFileName(self, 'Open Image', 'Image', '*.png *.jpg *.bmp')
        # 采用OpenCV函数读取数据
        self.img = cv2.imread(fileName, -1)
        self.refreshShow()

    def saveSlot(self):
        # 调用存储文件dialog
        fileName, tmp = QFileDialog.getSaveFileName(self, 'Save Image', 'Image', '*.png *.jpg *.bmp')
        # 调用OpenCV写入函数
        cv2.imwrite(fileName, self.img)

    def processSlot(self):
        # 对图像做模糊处理,窗口设定为5*5
        self.img = cv2.blur(self.img, (5, 5))
        self.refreshShow()

    def refreshShow(self):
        # 提取图像的通道和尺寸,用于将OpenCV下的image转换成Qimage
        height, width, channel = self.img.shape
        bytesPerline = 3 * width
        self.qImg = QImage(self.img.data, width, height, bytesPerline, QImage.Format_RGB888).rgbSwapped()
        # 将QImage显示出来
        self.label.setPixmap(QPixmap.fromImage(self.qImg).scaledToHeight(400))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = Window()
    win.show()
    sys.exit(app.exec_())

运行展示如下所示:

调用摄像头外设进行图像存储

首先是建立 ui 文件:

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 400)
        MainWindow.setMinimumSize(QtCore.QSize(800, 400))
        MainWindow.setMaximumSize(QtCore.QSize(800, 400))
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.SaveButton = QtWidgets.QPushButton(self.centralwidget)
        self.SaveButton.setGeometry(QtCore.QRect(570, 160, 200, 51))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.SaveButton.setFont(font)
        self.SaveButton.setObjectName("SaveButton")
        self.VideoLabel = QtWidgets.QLabel(self.centralwidget)
        self.VideoLabel.setText("监控展示区域")
        self.VideoLabel.setGeometry(QtCore.QRect(30, 20, 500, 360))
        self.VideoLabel.setMinimumSize(QtCore.QSize(480, 360))
        self.VideoLabel.setObjectName("VideoLabel")
        self.ImageLabel = QtWidgets.QLabel(self.centralwidget)
        self.ImageLabel.setGeometry(QtCore.QRect(570, 230, 200, 150))
        self.ImageLabel.setObjectName("ImageLabel")
        self.ImageLabel.setText("图像截取展示区域")
        self.GetButton = QtWidgets.QPushButton(self.centralwidget)
        self.GetButton.setGeometry(QtCore.QRect(570, 90, 200, 51))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.GetButton.setFont(font)
        self.GetButton.setObjectName("GetButton")
        self.PauseButton = QtWidgets.QPushButton(self.centralwidget)
        self.PauseButton.setGeometry(QtCore.QRect(570, 20, 200, 51))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.PauseButton.setFont(font)
        self.PauseButton.setObjectName("PauseButton")
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.SaveButton.setText(_translate("MainWindow", "保存最近一次画面"))
        self.VideoLabel.setText(_translate("MainWindow", "TextLabel"))
        self.ImageLabel.setText(_translate("MainWindow", "TextLabel"))
        self.GetButton.setText(_translate("MainWindow", "截取当前监控画面"))
        self.PauseButton.setText(_translate("MainWindow", "开启自动跟踪监控"))

然后是 main 文件的代码,展示如下:

import sys
from datetime import datetime

from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog
from wristcamera import *
import cv2

capture = cv2.VideoCapture(0)


class MyClass(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MyClass, self).__init__(parent)
        self.setupUi(self)
        self.isStartState = False
        self.isImageExist = False

        self.PauseButton.clicked.connect(self.startVideo)
        self.GetButton.clicked.connect(self.getImage)
        self.SaveButton.clicked.connect(self.saveImage)

    def startVideo(self):
        self.isStartState = ~self.isStartState
        while self.isStartState:
            self.VideoLabel.setPixmap(self.nowImage()[0])
            cv2.waitKey(50)

    def getImage(self):
        if self.isStartState == False:
            return
        self.isImageExist = True
        self.ImageLabel.setPixmap(self.nowImage()[0].scaled(200, 150))

    def saveImage(self):
        if self.isImageExist == False:
            return
        FileName = datetime.now().strftime('%Y-%m-%d_%H_%M_%S')
        file, tmp = QFileDialog.getSaveFileName(self, 'Save Image', FileName, '*.png *.jpg *.bmp')
        cv2.imwrite(file, self.nowImage()[1])
    # 当前的画面
    def nowImage(self):
        ret, frame = capture.read()
        # 摄像头读取, ret为是否成功打开摄像头, true, false:frame为视频的每一帧图像
        self.img = cv2.flip(frame, 1)
        # 摄像头是和人对立的,将图像左右调换回来正常显示。

        # 提取图像的通道和尺寸,用于将OpenCV下的image转换成Qimage
        height, width, channel = self.img.shape
        bytesPerline = 3 * width
        self.qImg = QImage(self.img.data, width, height, bytesPerline, QImage.Format_RGB888).rgbSwapped()
        # 将QImage显示出来
        return QPixmap.fromImage(self.qImg.scaled(480, 360)), self.img


if __name__ == '__main__':
    app = QApplication(sys.argv)
    myWin = MyClass()
    myWin.setWindowTitle("唤醒手腕 - 猫头鹰智能监控光棱2000")
    myWin.show()
    sys.exit(app.exec_())

运行的结果展示:马赛克是为了保护博主丑陋的模样,谢谢配合

点击保存最近一次画面就会弹窗,文件保存的路径提示:

MainWindow 窗口嵌套 web 页面

在新版本中的需要自行下载 QtWebEngine,展示如下:

 pip install PyQtWebEngine

具体实现代码展示:

import sys

from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QIcon
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication, QDesktopWidget
from PyQt5.QtWidgets import QMainWindow


class UsingTest(QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setWindowTitle("唤醒手腕")
        self.setMaximumSize(1500, 900)
        self.setMinimumSize(1500, 900)
        self.browser = QWebEngineView()
        self.browser.load(QUrl("http://wrist.cc/"))
        self.setCentralWidget(self.browser)

    def setCenterPosition(self):
        # 获取屏幕坐标系
        screen = QDesktopWidget().screenGeometry()
        # 获取自身的窗口坐标系
        size = self.geometry()
        newLeft = (screen.width() - size.width()) / 2
        newTop = (screen.height() - size.height()) / 2
        self.move(newLeft, newTop)


if __name__ == '__main__':  # 程序的入口
    app = QApplication(sys.argv)
    win = UsingTest()
    win.setWindowIcon(QIcon("./excel_wrist.ico"))
    win.setCenterPosition()
    win.show()
    sys.exit(app.exec_())

代码运行结果:

结合 pyautogui 远程控制 PC

首先搭建客户端网页 HTML

想法是这样的,那么借助 js 我们可以获取鼠标在网页的位置,那么将网页呈现的桌面画面与实际桌面相称,进行从而远程控制。

$(document).click(
    function (event) {
        event = event || window.event;
        var x = event.offsetX || event.originalEvent.layerX;
        var y = event.offsetY || event.originalEvent.layerY;
        var x_rate = x / document.body.clientWidth;
        var y_rate = y / document.body.clientHeight;
    }
);

如上的 Javascript 代码就是获取鼠标点击网页页面时候,鼠标位于网页页面的位置坐标(坐标百分比表示),那么我们可以把这个坐标的值转发到服务器端,通过 pyautogui 进行对应的操作即可。

发送的时候,采用 Ajax 发送模式,就是不刷新网页就进行发送数据:

function sendPointerPosition(xrate, yrate) {
    $.ajax({
        url: "/pointer?xrate=" + xrate + "&yrate=" + yrate,
        type: "get",
        success: function (data) {
            console.log(data)
        },
        error: function (error) {
            alert(error)
        }
    })
}

客户端页面的代码完整展示:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta >
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.js"></script>
    <title>唤醒手腕</title>
</head>
<body>
<img src="{{ url_for('video_feed') }}">
<style>
    * { padding: 0; margin: 0 }
    img { width: 100%; }
</style>
<script>
    $(document).click(
        function (event) {
            event = event || window.event;
            var x = event.offsetX || event.originalEvent.layerX;
            var y = event.offsetY || event.originalEvent.layerY;
            var x_rate = x / document.body.clientWidth;
            var y_rate = y / document.body.clientHeight;
            sendPointerPosition(x_rate, y_rate)
        }
    );

    function sendPointerPosition(xrate, yrate) {
        $.ajax({
            url: "/pointer?xrate=" + xrate + "&yrate=" + yrate,
            type: "get",
            success: function (data) {
                console.log(data)
            },
            error: function (error) {
                alert(error)
            }
        })
    }

</script>
</body>
</html>

首先搭建服务端媒体流传输,传输电脑实时画面

这边就是采用 pyautogui 进行捕捉电脑的画面,并且服务器要对接收到的坐标数据进行执行的操作,展示如下所示:

import pyautogui
from flask import Flask, render_template, Response, request
import io

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/pointer')
def pointer():
    x = int(float(request.args["xrate"]) * 1920)
    y = int(float(request.args["yrate"]) * 1080)
    # 执行点击操作
    pyautogui.click(x, y)
    return "success"
	

def gen():
    while True:
        screenShotImg = pyautogui.screenshot()

        imgByteArr = io.BytesIO()
        screenShotImg.save(imgByteArr, format='JPEG')
        imgByteArr = imgByteArr.getvalue()
        frame = imgByteArr
        yield (b'--frame\r\n Content-Type: image/jpeg\r\n\r\n' + frame)


@app.route('/video_feed')
def video_feed():
    return Response(gen(), mimetype='multipart/x-mixed-replace; boundary=frame')


if __name__ == '__main__':
    app.run(host='192.168.43.247', debug=True, threaded=True)

构建远程控制桌面 gui,将网页嵌入 gui 应用程序

import sys

from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QIcon
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication, QDesktopWidget
from PyQt5.QtWidgets import QMainWindow


class UsingTest(QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setWindowTitle("唤醒手腕")
        self.setMaximumSize(1600, 900)
        self.setMinimumSize(1600, 900)
        self.browser = QWebEngineView()
        self.browser.load(QUrl("http://192.168.1.102:5000/"))
        self.setCentralWidget(self.browser)

    def setCenterPosition(self):
        # 获取屏幕坐标系
        screen = QDesktopWidget().screenGeometry()
        # 获取自身的窗口坐标系
        size = self.geometry()
        newLeft = (screen.width() - size.width()) / 2
        newTop = (screen.height() - size.height()) / 2
        self.move(newLeft, newTop)


if __name__ == '__main__':  # 程序的入口
    app = QApplication(sys.argv)
    win = UsingTest()
    win.setWindowIcon(QIcon("./excel_wrist.ico"))
    win.setCenterPosition()
    win.show()
    sys.exit(app.exec_())

当然可以把 gui 桌面应用进行打包成 exe,在另一台电脑上运行。我这边没有另一台电脑,只能在扩展展示:

可以发现电脑的实时状态已经呈现,我们点击 gui 上的对应位置,其实在真实电脑中也会同样的执行操作。

就比如我们用鼠标点击 gui 画面中的 windows 菜单图标,js 会把我们的点击位置传给真实电脑的服务器应用中,然后执行对应的操作,展示如下:

总结:我们采用的同一台电脑上测试,其实建议用另一台笔记本进行操作,这样的话效果会更加的明显。当前这仅仅是单击事件而已,如果加强功能,也可以增加双击,或者长按拖拽的事件。

实现 python gui 程序打包成 exe

安装第三方库 pyinstaller

pip install pyinstaller

然后打包 exe,若需将 xxx.py 文件打包,只需在终端执行:

pyinstaller xxx.py

特别注意:终端需切换至 xxx.py 文件所在目录下

pyinstaller 常用可选项及说明:

-F:打包后只生成单个exe格式文件;
-D:默认选项,创建一个目录,包含exe文件以及大量依赖文件;
-c:默认选项,使用控制台(就是类似cmd的黑框);
-w:不使用控制台;
-p:添加搜索路径,让其找到对应的库;
-i:改变生成程序的icon图标。

然后我们可以发现在目录下生成 dist 文件夹:

然后我们运行 main.exe 文件:

解决打包 py 文件运行 exe 闪退

这部分借鉴白金之星 1717 博主:解决 pyinstaller 打包 py 文件运行 exe 闪退等诸多疑难杂症

在打包之前,你需要安装 pyinstaller,如果你是使用的虚拟环境,在打包前需要在命令行里激活你安装了 pyinstaller 的那个虚拟环境。

在使用 pyinstaller 打包 python 程序的时候大概分为两种请况:

  • 要打包的程序为单个 py 文件
  • 要打包的文件为多个 py 文件

当要打包的文件为多个 py 文件,这种情况一般你的代码较多,项目较大,可能你写了一个 GUI 界面 py 文件,但这个文件调用了其他文件的函数什么的。

这个时候你需要生成 spec 文件来打包,这里假设你的要打包的主文件为 test.py,首先在命令行中,cd 到项目的相对目录下,然后输入:

pyi-makespec test.py

之后会在当前目录下生成一个 test.spec,你可以在 pycharm 中打开这个文件,里面的内容大概是这样的,展示如下所示:

你需要把你的除了主文件以外的其他 py 文件也写到 a = Analysis([‘GUI.py’], 的这个列表中,像这样

Analysis(['GUI.py', 'test1.py', 'E:\\a\\test2.py']

如果你的其他 py 文件和你要打包的主文件 test.py 不在同一个目录,那么你在 list 中需要输入这个 py 文件的绝对地址。

如果你的项目调用了一些图片、dll 等二进制文件,你也需要将他们打包进去,不然程序无法正常运行,你可以在 spec 文件中的 datas 这个 list 中添加,方法和上面那个一样,不过最好填绝对地址,也可以等你项目打包完了在 copy 到你的那个 exe 文件的目录下。

这个种情况,你在程序中调用这些资源的时候最好用相对地址,不然程序到了别人电脑上就找不到地址了,大致只需要配置这些东西啦,其他东西都不是必须的。

然后在刚刚的命令行中输入:pyinstaller -D test.spec 就开始打包了,在这里 - D 是打包成一个文件夹,-F 是打包成一整个文件(传说打包成一整个文件打包缓慢,容易出错,因此不推荐使用,我也没试过)

argsinformation
-D–onedir Create a one-folder bundle containing an executable (default)
-F–onefile Create a one-file bundled executable.

同理打包完成后可以在 dist 目录下找到打包好的 exe 文件

运行 exe 文件刹那闪退如何解决?

最常见的问题就是闪退,这种错误让人最烦,因为报错都没看到就结束了,想解决都无从下手,之前我还想过用 qq 截图,当他报错的一瞬间按下截图快捷键,对手速的要求极高,十分考验程序猿的年龄、婚否等等综合素质,然后 2 分钟后我想了一个简单的方法,首先打开命令行,然后再命令行中输入你的 exe 文件的地址:

这样就能够展示出相应的报错的信息了。