好久没有写博客了,最近一直在学习pyqt,感觉Qt的功能比WinForm的强大了许多,再加上原来学习了一点opencv,就想着使用qt显示图片,opencv读取摄像头,顺便再做一个实时灰度化处理。这里使用了qt的QThread多线程处理,详细代码可以参考: Github.
这里的UI设计我是是由pyqt自带的designer设计其设计的,直接是拖拉拽就完事了,然后ui文件生成py文件。生成的代码如下:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'C:\Users\Administrator\PycharmProjects\-\QT显示图片\qt使用摄像头.ui'
#
# Created by: PyQt5 UI code generator 5.13.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(790, 463)
self.layoutWidget = QtWidgets.QWidget(Form)
self.layoutWidget.setGeometry(QtCore.QRect(20, 10, 751, 331))
self.layoutWidget.setObjectName("layoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.lbl_sourceImage = QtWidgets.QLabel(self.layoutWidget)
self.lbl_sourceImage.setObjectName("lbl_sourceImage")
self.horizontalLayout.addWidget(self.lbl_sourceImage)
self.lbl_dealedImage = QtWidgets.QLabel(self.layoutWidget)
self.lbl_dealedImage.setObjectName("lbl_dealedImage")
self.horizontalLayout.addWidget(self.lbl_dealedImage)
self.layoutWidget1 = QtWidgets.QWidget(Form)
self.layoutWidget1.setGeometry(QtCore.QRect(180, 390, 401, 25))
self.layoutWidget1.setObjectName("layoutWidget1")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.layoutWidget1)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.btn_open = QtWidgets.QPushButton(self.layoutWidget1)
self.btn_open.setObjectName("btn_open")
self.horizontalLayout_2.addWidget(self.btn_open)
self.btn_close = QtWidgets.QPushButton(self.layoutWidget1)
self.btn_close.setObjectName("btn_close")
self.horizontalLayout_2.addWidget(self.btn_close)
self.btn_openGarry = QtWidgets.QPushButton(self.layoutWidget1)
self.btn_openGarry.setObjectName("btn_openGarry")
self.horizontalLayout_2.addWidget(self.btn_openGarry)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.lbl_sourceImage.setText(_translate("Form", ""))
self.lbl_dealedImage.setText(_translate("Form", ""))
self.btn_open.setText(_translate("Form", "打开摄像头"))
self.btn_close.setText(_translate("Form", "关闭摄像头"))
self.btn_openGarry.setText(_translate("Form", "打开灰度图"))
然后就是主界面的设计了,这里我使用了后端线程处理数据,然后信号通知主界面做相应的显示工作。具体代码如下:
import sys
from PyQt5.QtCore import Qt, QThread, pyqtSignal,QDateTime
from PyQt5.QtGui import QIcon,QImage,QPixmap
from PyQt5.QtWidgets import QDialog, QApplication, QWidget,QMessageBox,QMenu,QLabel,QGraphicsItem
from QT显示图片.qt使用摄像头 import Ui_Form
import cv2 as cv
#创建新线程
class DealImgThread(QThread):
#设置一个原图像信号,一个灰度转换后的图
singoutSource=pyqtSignal(QImage)
singoutGarry=pyqtSignal(QImage)
def __init__(self,parent=None):
super(DealImgThread,self).__init__(parent)
self.cv=cv
self.cvCap=self.cv.VideoCapture(0)
#设置灰度转换是否打开
self.garryIsOpen=False
self.threadIsOpen=True
def openGarry(self):
if(self.garryIsOpen==False):
self.garryIsOpen=True
# def start(self):
# self.threadIsOpen=True
def end(self):
if(self.threadIsOpen):
self.threadIsOpen=False
def run(self):
self.threadIsOpen=True
while self.threadIsOpen:
ret,frame=self.cvCap.read()
frame=self.cv.flip(frame,1)
h,w,ch=frame.shape
bytesPerLine=3*w
qImg=QImage(frame.data, w, h, bytesPerLine,QImage.Format_RGB888).rgbSwapped()
self.singoutSource.emit(qImg)
#打开灰度转换功能
if(self.garryIsOpen==True):
#这里不太知道怎么把QImage转换为灰度图,就用了个折中的办法,先转化为灰度图,再转化为三通道的BGR图
garryImg = self.cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
garryImg=self.cv.cvtColor(garryImg, cv.COLOR_GRAY2BGR)
gImg = QImage(garryImg.data, w, h, bytesPerLine, QImage.Format_RGB888).rgbSwapped()
self.singoutGarry.emit(gImg)
self.cv.waitKey(20)
class MianWindow(QWidget):
def __init__(self,parent=None):
super(MianWindow,self).__init__(parent)
self.ui=Ui_Form()
self.ui.setupUi(self)
self.cvThread=DealImgThread()
#原始图片信号连接
self.cvThread.singoutSource.connect(self.showImg)
#灰度转换后信号连接
self.cvThread.singoutGarry.connect(self.showGarry)
self.ui.btn_open.clicked.connect(self.openScarme)
self.ui.btn_openGarry.clicked.connect(self.cvThread.openGarry)
self.ui.btn_close.clicked.connect(self.cvThread.end)
#一般图标在初始化的时候就要设置,要不然后面的显示不了
self.setWindowIcon(QIcon(r'ico\Qt.ico'))
#线程结束通知
self.cvThread.finished.connect(self.CameraThreadIsClose)
def showImg(self,img):
#先取的原来lable的尺寸,然后再转换一下
temp = self.ui.lbl_sourceImage.size()
img=img.scaled(temp)
self.ui.lbl_sourceImage.setPixmap(QPixmap.fromImage(img))
# now = QDateTime.currentDateTime().toString('hh:mm:ss.zzz')
# print(now + ':原图触发!')
def CameraThreadIsClose(self):
self.msgBox=QMessageBox()
self.msgBox.setWindowIcon(QIcon(r'ico\Qt.ico'))
self.msgBox.information(self,'信息提示框','线程执行结束!!!',buttons=QMessageBox.Yes)
def openScarme(self):
if(self.cvThread.isRunning()==False):
self.cvThread.start()
def showGarry(self,img):
temp = self.ui.lbl_dealedImage.size()
img=img.scaled(temp)
self.ui.lbl_dealedImage.setPixmap(QPixmap.fromImage(img))
# now=QDateTime.currentDateTime().toString('hh:mm:ss.zzz')
# print(now+':Garry触发!')
if __name__=='__main__':
app = QApplication(sys.argv)
form = MianWindow()
form.show()
sys.exit(app.exec_())
这次仔细想了一下Qthread,感觉它的新线程可以直接通过pyqtSignal与主线程通讯,这不是违背了那个新线程无法直接控制主线程创建的控件这个规律吗?(因为C#的就是这样说的)。后来想想可能是Qt对QThread进行了高层封装吧,已经帮我们解决了那个问题。还有我发现再主线程调用QThread里面的sleep和wait都是针对主线程操作的,并没有影响所创建的线程。我的理解是线程任务一旦run起来,就没有办法人为干涉,主线程和子线程是相互独立的,就和我们主线程里面运行的一个任务,我们只能在代码中修改,不能再运行时候修改。也许这和子线程一个道理,所以我们就得用一些bool量来进行子线程任务的开始结束操作了。附一张运行时的照片:(照片里的人属实帅!)