参考:OpenCV-Python入门教程7-PyQt编写GUI界面
前面一直都是使用命令行运行代码,不够人性化。这篇用Python编写一个GUI界面,使用PyQt5编写图像处理程序。包括:打开、关闭摄像头,捕获图片,读取本地图片,灰度化和Otsu自动阈值分割的功能。
使用Qt Designer来设计界面。而anaconda里自带了designer.exe,我使用的就是这个。designer.exe的路径:D:\ProgramData\Anaconda3\Library\bin\,如果是普通的Python环境,则需要自行安装
pip install pyqt5-tools
安装完成后,designer.exe应该在Python的安装目录下:xxx\Lib\site-packages\pyqt5_tools
生成一个简单的界面,后面还会用到
import sys
from PyQt5.QtWidgets import QApplication, QWidget
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QWidget()
window.setWindowTitle('Hello World!')
window.show()
sys.exit(app.exec_())
一、界面设计
在D:\ProgramData\Anaconda3\Library\bin\下打开designer.exe,会弹出创建新窗体的窗口,我们直接点击"创建"(英文版是create)
界面左侧是Qt的常用控件"Widget Box",右侧有一个控件属性窗口"Property Editor"。本例中我们只用到了"Push Button"控件和"Label"控件,可以在属性窗口调整它的大小150x150(可以根据自己的需求适当调大或者缩小):
控件上显示的文字"text"属性和控件的名字"objectName"属性需要修改,便于显示和代码调用,可以按照下面的表格命名:
前面设计好了界面,接下来就是实现"打开摄像头"到"阈值分割"这五个按钮的功能,也就是给每个按钮指定一个函数,逻辑代码写在这个函数里面。这个函数就称事件,Qt中称为槽连接
点击Designer工具栏的Edit Signals/Slots按钮,进入槽函数编辑界面,点击旁边的"Edit Widgets"可以恢复正常视图:
在弹出的配置窗口中,可以看到左侧是按钮的常用事件,我们选择点击事件”clicked()”,然后添加一个名为”btnOpenCamera_Clicked()”的槽函数:
重复上面的步骤,给五个按钮添加五个槽函数,最终结果如下:
Ctrl + S保存.ui文件。我们需要将ui转py代码。
打开cmd命令行,切换到ui文件的保存目录。Windows下有个小技巧,可以在目录的地址栏输入cmd,一步切换到当前目录:
执行这条指令
pyuic5 -o mainForm.py using_pyqt_create_ui.ui
生成mainForm.py文件,里面包含一个名为”Ui_MainWindow”的类。
二、编写逻辑代码
mainForm.py是根据ui文件生成的,也就是说,一旦ui文件有所改变,需要重新生成覆盖原来的文件。
新建一个mainEntry.py存放逻辑代码,代码虽然很长,但是很简单并不难懂。有些部分有所重复,并没有将其封装成一个函数(博主能力有限),感兴趣的可以试一下
import sys
import cv2
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QFileDialog, QMainWindow
from mainForm import Ui_MainWindow
class PyQtMainEntry(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.camera = cv2.VideoCapture(0)
self.is_camera_opened = False # 摄像头有没有打开标记
# 定时器:30ms捕获一帧
self._timer = QtCore.QTimer(self)
self._timer.timeout.connect(self._queryFrame)
self._timer.setInterval(30)
def btnOpenCamera_Clicked(self):
'''
打开和关闭摄像头
'''
self.is_camera_opened = ~self.is_camera_opened
if self.is_camera_opened:
self.btnOpenCamera.setText("关闭摄像头")
self._timer.start()
else:
self.btnOpenCamera.setText("打开摄像头")
self._timer.stop()
def btnCapture_Clicked(self):
'''
捕获图片
'''
# 摄像头未打开,不执行任何操作
if not self.is_camera_opened:
return
self.captured = self.frame
# 后面这几行代码几乎都一样,可以尝试封装成一个函数
rows, cols, channels = self.captured.shape
bytesPerLine = channels * cols
# Qt显示图片时,需要先转换成QImgage类型
QImg = QImage(self.captured.data, cols, rows, bytesPerLine, QImage.Format_RGB888)
self.labelCapture.setPixmap(QPixmap.fromImage(QImg).scaled(
self.labelCapture.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
def btnReadImage_Clicked(self):
'''
从本地读取图片 文件路径不能有中文
'''
# 打开文件选取对话框
filename, _ = QFileDialog.getOpenFileName(self, '打开图片')
if filename:
self.captured = cv2.imread(str(filename))
# OpenCV图像以BGR通道存储,显示时需要从BGR转到RGB
self.captured = cv2.cvtColor(self.captured, cv2.COLOR_BGR2RGB)
rows, cols, channels = self.captured.shape
bytesPerLine = channels * cols
QImg = QImage(self.captured.data, cols, rows, bytesPerLine, QImage.Format_RGB888)
self.labelCapture.setPixmap(QPixmap.fromImage(QImg).scaled(
self.labelCapture.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
def btnGray_Clicked(self):
'''
灰度化
'''
# 如果没有捕获图片,则不执行操作
if not hasattr(self, "captured"):
return
self.cpatured = cv2.cvtColor(self.captured, cv2.COLOR_RGB2GRAY)
rows, columns = self.cpatured.shape
bytesPerLine = columns
# 灰度图是单通道,所以需要用Format_Indexed8
QImg = QImage(self.cpatured.data, columns, rows, bytesPerLine, QImage.Format_Indexed8)
self.labelResult.setPixmap(QPixmap.fromImage(QImg).scaled(
self.labelResult.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
def btnThreshold_Clicked(self):
'''
Otsu自动阈值分割
'''
if not hasattr(self, "captured"):
return
_, self.cpatured = cv2.threshold(
self.cpatured, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
rows, columns = self.cpatured.shape
bytesPerLine = columns
# 阈值分割图也是单通道,也需要用Format_Indexed8
QImg = QImage(self.cpatured.data, columns, rows, bytesPerLine, QImage.Format_Indexed8)
self.labelResult.setPixmap(QPixmap.fromImage(QImg).scaled(
self.labelResult.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
@QtCore.pyqtSlot()
def _queryFrame(self):
'''
循环捕获图片
'''
ret, self.frame = self.camera.read()
img_rows, img_cols, channels = self.frame.shape
bytesPerLine = channels * img_cols
cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB, self.frame)
QImg = QImage(self.frame.data, img_cols, img_rows, bytesPerLine, QImage.Format_RGB888)
self.labelCamera.setPixmap(QPixmap.fromImage(QImg).scaled(
self.labelCamera.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = PyQtMainEntry()
window.show()
sys.exit(app.exec_())
下面是我参考上面改的:
上面的程序可以跑,但是容错率太低了,比如你直接点击阈值化它就报错了,所有我就改了一下,无非就是属性的权限改下。。。加了个保存灰度图片的功能
第一步:
先建一个souye.ui画面
第二步:把刚刚的souye.ui转化为souye.py
可参考上面的转换方式,或去其他博客也可,转为souye.py如下:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'souye.ui'
#
# Created by: PyQt5 UI code generator 5.9.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1079, 774)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(10, 10, 161, 151))
self.label.setText("")
self.label.setPixmap(QtGui.QPixmap("图片路径"))
self.label.setScaledContents(True)
self.label.setObjectName("label")
self.frame = QtWidgets.QFrame(self.centralwidget)
self.frame.setGeometry(QtCore.QRect(70, 200, 711, 491))
self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
self.frame.setObjectName("frame")
self.label_2 = QtWidgets.QLabel(self.frame)
self.label_2.setGeometry(QtCore.QRect(40, 30, 271, 421))
self.label_2.setScaledContents(True)
self.label_2.setObjectName("label_2")
self.label_3 = QtWidgets.QLabel(self.frame)
self.label_3.setGeometry(QtCore.QRect(420, 20, 251, 411))
self.label_3.setScaledContents(True)
self.label_3.setObjectName("label_3")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(880, 170, 93, 28))
self.pushButton.setObjectName("pushButton")
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_2.setGeometry(QtCore.QRect(880, 330, 93, 28))
self.pushButton_2.setObjectName("pushButton_2")
self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_4.setGeometry(QtCore.QRect(880, 460, 93, 28))
self.pushButton_4.setObjectName("pushButton_4")
self.pushButton_5 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_5.setGeometry(QtCore.QRect(880, 240, 93, 28))
self.pushButton_5.setObjectName("pushButton_5")
self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_3.setGeometry(QtCore.QRect(872, 290, 111, 28))
self.pushButton_3.setObjectName("pushButton_3")
self.pushButton_6 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_6.setGeometry(QtCore.QRect(830, 520, 151, 28))
self.pushButton_6.setObjectName("pushButton_6")
self.btn_savehuidu = QtWidgets.QPushButton(self.centralwidget)
self.btn_savehuidu.setGeometry(QtCore.QRect(880, 380, 93, 28))
self.btn_savehuidu.setObjectName("btn_savehuidu")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1079, 26))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.pushButton.clicked.connect(MainWindow.openfile_click)
self.pushButton_2.clicked.connect(MainWindow.gray)
self.pushButton_4.clicked.connect(MainWindow.threshed_click)
self.pushButton_5.clicked.connect(MainWindow.open_camera_click)
self.pushButton_3.clicked.connect(MainWindow.camera_click)
self.btn_savehuidu.clicked.connect(MainWindow.save_huidu_click)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label_2.setText(_translate("MainWindow", "原图"))
self.label_3.setText(_translate("MainWindow", "效果图"))
self.pushButton.setText(_translate("MainWindow", "打开图片"))
self.pushButton_2.setText(_translate("MainWindow", "灰度"))
self.pushButton_4.setText(_translate("MainWindow", "阈值化"))
self.pushButton_5.setText(_translate("MainWindow", "打开摄像头"))
self.pushButton_3.setText(_translate("MainWindow", "捕获视频图片"))
self.pushButton_6.setText(_translate("MainWindow", "打开另一个页面"))
self.btn_savehuidu.setText(_translate("MainWindow", "保存灰度图"))
第三步:继承souye.py的my_souye.py:
import sys
import cv2
from PyQt5.Qt import QMessageBox #QMessage用
from PyQt5 import QtCore,QtGui,QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QFileDialog,QMainWindow
from souye import Ui_MainWindow
from my_classify import My_classify #引入另一个画面的类,可以不用
class My_souye(QMainWindow,Ui_MainWindow):
def __init__(self):
super(My_souye,self).__init__()
self.setupUi(self)
self.camera=cv2.VideoCapture(0)
self.is_camera_opened=False# 摄像头有没有打开
#定时器,30ms捕获一帧
self._timer=QtCore.QTimer(self)
self._timer.timeout.connect(self._queryFrame)
self._timer.setInterval(30)
#打开摄像头
def open_camera_click(self):
'''
打开和关闭摄像头
'''
self.is_camera_opened = ~self.is_camera_opened
if self.is_camera_opened:
self.pushButton_5.setText("关闭摄像头")
self._timer.start()
else:
self.pushButton_5.setText("打开摄像头")
self._timer.stop()
#从视频中截取图片的function
def camera_click(self):
if not self.is_camera_opened:
return
#捕获视频
temp=self.src
img_rows, img_cols, channels = temp.shape
bytePerLine = channels * img_cols
# Qt显示图片时,需要先转换成QImgage类型
QImg=QImage(temp.data,img_cols,img_rows,bytePerLine,QImage.Format_RGB888)
self.label_3.setPixmap(QPixmap.fromImage(QImg).scaled(
self.label_3.size(),Qt.KeepAspectRatio,Qt.SmoothTransformation
))
#打开文件,并显示原图
def openfile_click(self):
filename,_=QFileDialog.getOpenFileName(self,'打开图片','D:/aphoto',"JPEG Files(*.jpg);;PNG Files(*.png)")
if filename:
self.src=cv2.imread(str(filename)) #原图,以后很多要用,所有要用self
#Opencv图像以BGR存储,显示的时候要从BGR转到RGB
temp=cv2.cvtColor(self.src,cv2.COLOR_BGR2RGB)
rows,cols,channels=temp.shape
bytePerLine=channels*cols
QImg=QImage(temp.data,cols,rows,bytePerLine,QImage.Format_RGB888)
self.label_2.setPixmap(QPixmap.fromImage(QImg).scaled(
self.label_2.size(),Qt.KeepAspectRatio,Qt.SmoothTransformation
))
else:
QMessageBox.information(self,"打开失败","没有选择图片")
#从打开的图变为灰度图
def gray(self):
#hasattr判断这个对象是否有这个变量或属性
if not hasattr(self,"src"):
QMessageBox.information(self, "灰度失败", "请先打开图片")
return
self.temp=cv2.cvtColor(self.src,cv2.COLOR_BGR2GRAY)
rows,columns=self.temp.shape
bytePerLine=columns
# 灰度图是单通道,所以需要用Format_Indexed8
QImg=QImage(self.temp.data,columns,rows,bytePerLine,QImage.Format_Indexed8)
self.label_3.setPixmap(QPixmap.fromImage(QImg).scaled(
self.label_3.size(),Qt.KeepAspectRatio,Qt.SmoothTransformation
))
#保存灰度图
def save_huidu_click(self):
savefilename,_=QFileDialog.getSaveFileName(self,"保存图片",'Image','*.png *.jpg *.bmp')
if savefilename and (hasattr(self, "temp")):
cv2.imwrite(savefilename, self.temp)
QMessageBox.information(self, "保存灰度图", "保存成功")
else:
QMessageBox.information(self, "保存灰度图失败", "请选择有效路径或生成灰度图先")
return
#阈值化
def threshed_click(self):
if not hasattr(self,"src"):
QMessageBox.information(self, "阈值化失败", "请先打开图片")
return
temp = cv2.cvtColor(self.src, cv2.COLOR_BGR2GRAY)
_,thresh_img=cv2.threshold(temp,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
rows,columns=thresh_img.shape
bytesPerLine=columns
# 阈值分割图也是单通道,也需要用Format_Indexed8
QImg=QImage(thresh_img.data,columns,rows,bytesPerLine,QImage.Format_Indexed8)
self.label_3.setPixmap(QPixmap.fromImage(QImg).scaled(
self.label_3.size(),Qt.KeepAspectRatio,Qt.SmoothTransformation
))
#@QtCore.pyqtSlot()
def _queryFrame(self):
'''
循环捕获图片
'''
ret,self.src=self.camera.read()
img_rows,img_cols,channels=self.src.shape
bytePerLine=channels*img_cols
self.src=cv2.cvtColor(self.src,cv2.COLOR_BGR2RGB)
QImg=QImage(self.src.data,img_cols,img_rows,bytePerLine,QImage.Format_RGB888)
self.label_2.setPixmap(QPixmap.fromImage(QImg).scaled(
self.label_2.size(),Qt.KeepAspectRatio,Qt.SmoothTransformation
))
if __name__=="__main__":
app=QtWidgets.QApplication(sys.argv)
ex=My_souye()
ex.show()
#另一个页面对象
# child=My_classify()
# btn=ex.pushButton_6
# btn.clicked.connect(child.show)
sys.exit(app.exec_())
上面就是我改的关键啦,比转载的容错率提升点了。
在Qt开发过程中,点击按钮弹出另一个窗口:
首先就是像上面一样,先定义classify.ui---->转为classify .py---->再建立一个my_classify.py里面有个:My_classify类:(这里只放my_classify.py代码)
import sys
import cv2
from classify import Ui_MainWindow
from PyQt5.QtWidgets import QFileDialog,QMainWindow
class My_classify(QMainWindow,Ui_MainWindow):
def __init__(self):
super(My_classify,self).__init__()
self.setupUi(self)
def openphoto_click(self):
pass
def classify_click(self):
pass
然后在souye.ui上添加一个pushbutton_6按钮,再转为souye.py,再然后就是在my)souye.py上做些改动就可调用刚刚新建的classify.ui界面了:
- from my_classify import My_classify #引入另一个画面的类
- if name==“main”:
app=QtWidgets.QApplication(sys.argv)
ex=My_souye()
ex.show()
#另一个页面对象
child=My_classify()
#按钮绑定事件
btn=ex.pushButton_6
btn.clicked.connect(child.show)
sys.exit(app.exec_())
这里就是在这创建子窗口的对象,然后把刚刚定义的pushButton_6可以说是绑定一个事件把,直接调个系统有的show槽函数,不用在ui定义pushButton_6的槽函数,非常方便!
转载:https://blog.csdn.net/QLBFA/article/details/105984012