以前运行 Python 程序时,我们通常需要在控制台中输入数据,再查看输出结果。这样的程序虽然能实现功能,但是操作不够直观,也不方便展示给其他人。
PyQt5 可以帮助我们给 Python 程序添加窗口、按钮、输入框和图片显示区域。这样,用户不需要了解代码,只需要填写数据并点击按钮,就能使用程序。
这次学习主要包括以下内容:
- 安装和配置 PyQt5;
- 使用 Qt Designer 设计界面;
- 理解常用控件和信号槽;
- 将机器学习模型接入 PyQt5;
- 在 PyQt5 窗口中显示 OpenCV 摄像头画面;
- 使用 dlib 实现人脸框和68个人脸关键点检测。
整体流程可以概括为:
Qt Designer设计界面,将.ui文件转换成.py文件,编写业务逻辑,绑定按钮和槽函数,接入机器学习或OpenCV,形成可以操作的桌面应用
一、PyQt5的介绍
PyQt5 是 Python 的图形用户界面开发库。通过它,我们可以创建窗口、按钮、标签、输入框、菜单栏以及消息提示框等控件。
一个最基本的 PyQt5 程序如下:
python
import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("我的第一个PyQt5窗口")
self.resize(600, 400)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
其中,QApplication 负责管理整个应用程序,QMainWindow 表示主窗口,show() 用来显示窗口,app.exec_() 则让程序进入事件循环。
所谓事件循环,就是程序一直等待用户操作。例如,用户点击按钮、输入文字或者关闭窗口时,程序会根据不同事件执行相应功能。
二、使用 Qt Designer 设计界面
如果完全使用 Python 代码设置控件的位置,代码会比较长。因此,我们可以使用 Qt Designer 通过拖拽的方式设计界面。
常用控件包括:
QLabel:显示文字或图片;
QPushButton:按钮;
QLineEdit:单行输入框;
QTextEdit:多行文本框;
QComboBox:下拉选择框;
QCheckBox:复选框;
QMessageBox:提示框;
QFileDialog:文件选择框。
Qt Designer 保存的文件后缀是 .ui。例如:
untitled.ui
win1.ui
win3.ui
它们还不能直接作为普通 Python 模块使用,一般需要转换成 .py 文件:
pyuic5 untitled.ui -o untitled.py
pyuic5 win1.ui -o win1.py
pyuic5 win3.ui -o win3.py
转换后会生成 Ui_MainWindow 类:
from untitled import Ui_MainWindow
需要注意,生成的 Python 文件中通常会写着:
不要手动修改该文件,否则重新执行pyuic5时修改会丢失
因此,比较合理的结构是:
untitled.ui 负责保存界面设计
untitled.py 由pyuic5自动生成
my窗口.py 编写按钮事件和业务逻辑
三、继承界面并启动程序
项目中的主窗口类同时继承了 QMainWindow 和 Ui_MainWindow:
class PyQtMainEntry(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
QMainWindow 提供窗口功能,Ui_MainWindow 提供在 Qt Designer 中设计的控件。
self.setupUi(self) 会把输入框、按钮和标签等控件安装到当前窗口中。如果忘记调用它,窗口可能会显示出来,但设计好的控件不会出现。
程序入口如下:
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = PyQtMainEntry()
window.show()
sys.exit(app.exec_())
这一部分可以理解为:
创建应用程序
→ 创建主窗口
→ 显示主窗口
→ 等待用户操作
四、将机器学习模型接入 PyQt5
项目中的机器学习部分使用玉米价格和豆粕价格预测目标价格。实现过程分为模型训练和界面预测两步。
1. 训练决策树模型
首先读取 Excel 数据:
import pandas as pd
data = pd.read_excel("均价数据.xlsx")
然后划分特征和预测目标:
x = data.iloc[:, 1:-1]
y = data.iloc[:, -1]
这里的 x 是模型的输入特征,例如玉米和豆粕价格;y 是模型需要预测的目标值。
再划分训练集和测试集:
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(
x,
y,
test_size=0.2,
random_state=1
)
创建并训练决策树回归模型:
from sklearn.tree import DecisionTreeRegressor
tree = DecisionTreeRegressor()
tree.fit(x_train, y_train)
最后保存模型:
import joblib
joblib.dump(tree, "tree_model.pkl")
这样,界面程序不需要每次启动都重新训练模型,只需要加载保存好的文件。
2. 在界面中加载模型
窗口初始化时加载模型:
self.model = joblib.load("tree_model.pkl")
当用户点击"预测"按钮时,读取两个输入框中的内容:
def predict_price(self):
try:
corn_price = int(self.lineEdit.text())
soybean_price = int(self.lineEdit_2.text())
predicted_price = self.model.predict(
np.array([[corn_price, soybean_price]])
)
self.lineEdit_3.setText(
str(predicted_price[0])
)
except ValueError:
QMessageBox.warning(
self,
"错误",
"请输入有效的价格"
)
这段代码的执行过程为:
读取玉米价格,读取豆粕价格,组成模型需要的二维数组,调用predict进行预测,把结果显示到第三个输入框
这里要注意:
np.array([[corn_price, soybean_price]])
使用了两层方括号,因为机器学习模型接收的通常是二维数据:
[样本数量, 特征数量]
即使只预测一条数据,也需要保持二维格式。
3. 输入错误提示
如果用户输入"abc"等非数字内容:
int(self.lineEdit.text())
就会产生 ValueError。程序通过 try...except 捕获错误,再使用消息框提示用户,而不是让整个程序崩溃。
QMessageBox.warning(
self,
"错误",
"请输入有效的价格"
)
这体现了图形化程序与普通脚本的区别:除了实现功能,还需要考虑用户可能进行的错误操作。
五、在窗口中显示图片
项目中还有一个"打开对照表"按钮。点击后,把图片显示到 QLabel 中:
def showbiao(self):
pixmap = QPixmap("Figure_1.png")
self.label_4.setPixmap(pixmap)
QPixmap 是 PyQt5 中常用的图片对象,setPixmap() 可以把图片放入标签控件。
为了防止图片尺寸和标签不匹配,也可以进行缩放:
pixmap = pixmap.scaled(
self.label_4.size(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
self.label_4.setPixmap(pixmap)
其中,KeepAspectRatio 表示保持原图宽高比例,避免图片变形。
六、PyQt5 与 OpenCV 结合
机器学习模块处理的是数值,而 OpenCV 模块处理的是图像和视频。项目中使用 OpenCV 打开摄像头:
self.camera1 = cv2.VideoCapture(0)
参数 0 表示默认摄像头。如果电脑连接了多个摄像头,也可能使用 1 或 2。
但是,不能在主线程中直接使用死循环持续读取摄像头,否则 PyQt5 窗口可能会卡死。项目中使用 QTimer 定时读取画面:
self._timer = QtCore.QTimer(self)
self._timer.timeout.connect(self._queryFrame)
self._timer.setInterval(30)
这表示定时器每隔30毫秒触发一次 _queryFrame(),大约每秒刷新33次。
打开和关闭摄像头的槽函数如下:
def slot1(self):
self.is_camera_opened = not self.is_camera_opened
if self.is_camera_opened:
self.pushButton.setText("关闭")
self._timer.start()
else:
self.pushButton.setText("打开")
self._timer.stop()
每次点击按钮,摄像头状态都会取反。按钮文字也会同步改变,让用户知道下一次点击会执行什么操作。
七、把 OpenCV 图像转换成 PyQt5 图像
OpenCV 读取的画面是 NumPy 数组,并且颜色顺序是 BGR;PyQt5 显示图片时通常使用 RGB。
因此需要先转换颜色:
rgb_img = cv2.cvtColor(
frame,
cv2.COLOR_BGR2RGB
)
然后转换成 QImage:
h, w, ch = rgb_img.shape
bytes_per_line = ch * w
q_img = QImage(
rgb_img.data,
w,
h,
bytes_per_line,
QImage.Format_RGB888
)
再转换成 QPixmap:
pixmap = QPixmap.fromImage(q_img)
最后显示到 QLabel:
self.label.setPixmap(pixmap)
完整过程是:
OpenCV读取BGR图像,转换成RGB,NumPy数组转换成QImage,QImage转换成QPixmap,显示到QLabel
八、加入人脸检测和68个关键点
在摄像头画面正常显示后,项目又加入了 dlib 人脸检测:
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(
"shape_predictor_68_face_landmarks.dat"
)
detector 用于检测人脸位置,predictor 用于定位人脸上的68个关键点。
窗口中使用两个布尔变量控制功能:
self.draw_face_box = False
self.draw_landmarks = False
点击按钮后切换状态:
def toggle_face_box(self):
self.draw_face_box = not self.draw_face_box
def toggle_landmarks(self):
self.draw_landmarks = not self.draw_landmarks
这样,用户可以独立控制:
是否显示人脸矩形框;
是否显示68个人脸关键点;
也可以同时打开两项功能。
1. 绘制人脸框
python
faces = detector(draw_img, 0)
for face in faces:
x1, y1 = face.left(), face.top()
x2, y2 = face.right(), face.bottom()
cv2.rectangle( draw_img,(x1, y1),(x2, y2),(0, 255, 0),2 )
检测器返回人脸区域,rectangle() 根据左上角和右下角坐标画出绿色矩形框。
2. 绘制68个关键点
python
shape = predictor(draw_img, face)
landmarks = np.array([[p.x, p.y] for p in shape.parts()])
然后依次画出关键点:
python
for idx, point in enumerate(landmarks):
pos = (point[0], point[1])
cv2.circle(
draw_img,
pos,
2,
(0, 255, 0),
-1
)
cv2.putText(
draw_img,
str(idx),
pos,
cv2.FONT_HERSHEY_SIMPLEX,
0.4,
(255, 255, 255),
1
)
68个关键点主要分布在:
0~16:脸部轮廓
17~26:眉毛
27~35:鼻子
36~47:眼睛
48~67:嘴巴
这些关键点以后还可以用于眨眼检测、张嘴检测、换脸和表情分析。
九、关闭窗口时释放摄像头
如果直接关闭窗口而不释放摄像头,摄像头可能仍然被占用,导致下次运行程序时无法打开。
因此,需要重写窗口关闭事件:
def closeEvent(self, event):
if self.timer.isActive():
self.timer.stop()
if self.cap.isOpened():
self.cap.release()
event.accept()
这段代码会:
停止定时器
释放摄像头
正常关闭窗口
资源释放是摄像头程序中非常重要的一步。
十、程序运行时常见的问题
1. 找不到 UI 模块
如果出现:
ModuleNotFoundError: No module named 'win3'
需要确认 win3.py 与主程序在同一目录,或者重新执行:
pyuic5 win3.ui -o win3.py
2. 找不到模型文件
如果出现:
FileNotFoundError: tree_model.pkl
需要确认模型文件与程序工作目录一致,也可以使用绝对路径。
3. 摄像头无法打开
可以先检查:
cap = cv2.VideoCapture(0)
print(cap.isOpened())
如果输出 False,可能是摄像头编号不正确,或者摄像头正被其他软件占用。
十一、学习总结
这次实践把 PyQt5、机器学习和 OpenCV 连接到了一起。
1.机器学习部分的流程是:
读取Excel数据,训练决策树回归模型,保存模型,PyQt5加载模型,用户输入数据,点击按钮完成预测,在界面中显示结果
2.OpenCV 部分的流程是:
打开摄像头,QTimer定时读取画面,OpenCV处理图像,转换成QImage和QPixmap,在QLabel中显示,根据按钮状态绘制人脸框和关键点
这次学习最重要的地方,是理解了界面和算法之间的关系:
PyQt5 负责与用户交互,机器学习负责处理数值预测,OpenCV 和 dlib 负责处理图像,信号槽机制负责把按钮操作与具体功能连接起来。
以前的程序只能在控制台运行,而加入 PyQt5 后,算法就变成了用户可以直接操作的小应用。这也是从"代码能够运行"走向"程序能够使用"的重要一步。