python - PyQt5 Thread 退出时关闭 GUI
问题描述
我正在尝试从 opencv 视频流在 pyqt5 应用程序中运行视频,我可以在其中使用 2 个按钮启动和停止视频。
这个问题与This高度相关。问题是,如果我想取消视频,主窗口也会关闭。然而,我不确定为什么会这样。
编辑:此问题出现在 Windows 10 上,而线程在 Mac 上运行时没有任何问题。
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
import cv2
class MainWindow(QWidget):
def __init__(self) -> None:
super(MainWindow, self).__init__()
self.VBL = QVBoxLayout()
self.FeedLabel = QLabel()
self.VBL.addWidget(self.FeedLabel)
self.playBTN = QPushButton('Play')
self.playBTN.clicked.connect(self.play)
self.VBL.addWidget(self.playBTN)
self.cancelBTN.clicked.connect(self.Cancel)
self.VBL.addWidget(self.cancelBTN)
self.setLayout(self.VBL)
def ImageUpdateSlot(self, Image):
Image = Image.scaled(640, 480, Qt.KeepAspectRatio)
self.FeedLabel.setPixmap(QPixmap.fromImage(Image))
def Cancel(self):
self.worker1.stop()
def play(self):
self.worker1 = CompleteVideoRunner("Path/to/some/video")
self.worker1.start()
self.worker1.sigImage.connect(self.ImageUpdateSlot)
self.cancelBTN.clicked.connect(self.Cancel)
class VideoSignal(QObject):
Image = pyqtSignal(QImage)
imageCount = pyqtSignal(int)
class CompleteVideoRunner(QThread):
#signal = VideoSignal()
sigImage = pyqtSignal(QImage)
sigCount =pyqtSignal(int)
def __init__(self, path, curFrame = 0):
super(CompleteVideoRunner, self).__init__()
self.cap = cv2.VideoCapture(path)
#self.signal = VideoSignal()
self.curFrame = curFrame
self.is_killed = False
def run(self):
self.is_killed = False
j = self.curFrame
self.cap.set(1, j)
while (self.cap.isOpened() and not self.is_killed):
rep, frame = self.cap.read()
self.curFrame = j
if not rep:
break
height, width, channel = frame.shape
bytesPerLine = 3 * width
jpg = frame.tobytes()
jpg = QByteArray(jpg)
QImg= QImage(frame.data, width, height, bytesPerLine,
QImage.Format_BGR888)
self.sigImage.emit(QImg)
self.sigCount.emit(j)
j+=1
def stop(self):
self.is_killed = True
print('finished thread')
self.quit()
if __name__ == "__main__":
App = QApplication(sys.argv)
Root = MainWindow()
Root.show()
sys.exit(App.exec())
解决方案
为了解决这个问题,它就像在PyQt 中显示来自 opencv线程的视频流一样。重要的一步是在发出信号之前在线程的 run() 函数中调整 QImage 的大小,而不是在主线程中。
一个后续问题是:为什么我需要在发出信号之前重新调整 QImage?
对于重现能力,请考虑下面的代码。
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
import cv2
class MainWindow(QWidget):
def __init__(self) -> None:
super(MainWindow, self).__init__()
self.VBL = QVBoxLayout()
self.FeedLabel = QLabel()
self.VBL.addWidget(self.FeedLabel)
self.playBTN = QPushButton('Play')
self.playBTN.clicked.connect(self.play)
self.VBL.addWidget(self.playBTN)
self.cancelBTN.clicked.connect(self.Cancel)
self.VBL.addWidget(self.cancelBTN)
self.setLayout(self.VBL)
def ImageUpdateSlot(self, Image):
self.FeedLabel.setPixmap(QPixmap.fromImage(Image))
def Cancel(self):
self.worker1.stop()
def play(self):
self.worker1 = CompleteVideoRunner("Path/to/some/video")
self.worker1.start()
self.worker1.sigImage.connect(self.ImageUpdateSlot)
self.cancelBTN.clicked.connect(self.Cancel)
class VideoSignal(QObject):
Image = pyqtSignal(QImage)
imageCount = pyqtSignal(int)
class CompleteVideoRunner(QThread):
#signal = VideoSignal()
sigImage = pyqtSignal(QImage)
sigCount =pyqtSignal(int)
def __init__(self, path, curFrame = 0):
super(CompleteVideoRunner, self).__init__()
self.cap = cv2.VideoCapture(path)
#self.signal = VideoSignal()
self.curFrame = curFrame
self.is_killed = False
def run(self):
self.is_killed = False
j = self.curFrame
self.cap.set(1, j)
while (self.cap.isOpened() and not self.is_killed):
rep, frame = self.cap.read()
self.curFrame = j
if not rep:
break
height, width, channel = frame.shape
bytesPerLine = 3 * width
QImg= QImage(frame.data, width, height, bytesPerLine,
QImage.Format_BGR888)
QImg = QImg.scaled(640, 480, Qt.KeepAspectRatio)
self.sigImage.emit(QImg)
self.sigCount.emit(j)
j+=1
def stop(self):
self.is_killed = True
print('finished thread')
self.quit()
if __name__ == "__main__":
App = QApplication(sys.argv)
Root = MainWindow()
Root.show()
sys.exit(App.exec())
推荐阅读
- r - 带有分组数据的滚动窗口滑块::slide()
- python - ValueError:没有足够的值来解包(预期 2,得到 1),底图
- apache-spark - Spark结构化流:ClassCastException:.streaming.SerializedOffset不能转换为类.spark.sql.streaming.CouchbaseSourceOffset
- javascript - 将模板文字作为道具传递不会呈现
- python - 解析数据框中的多索引以获得更好的清晰度
- android - 活动可以在 onCreate() 完成之前处理 onBackPressed() 吗?
- python - 如何将 DataFrame 的字典转换为单独的 DataFrame(Python、Pandas)
- azure - 是否可以在 AzureAppService 接收电子邮件:[用户名]@[appserviceUrl 域]?
- c# - 下拉 asp.net core 设置选中项
- git - 我可以从两个或多个位置以同一用户的身份开发 git repo 吗?