引言
学习多线程最典型的问题就是如何在多个线程之间传递消息与写作,PyQT5的线程支持在不同线程之间传递信号触发事件,实现多个线程之间的协助,完成诸如生产者-消费者这样经典的多线程协作。本文将通过QThread与信号槽机制构建一个生产者-消费者模型,演示多个线程之间的协作。
应用程序概述
这里演示了一个从图像采集(用本地图像数据集替代)到图像分析处理(简单二值化+形态学处理)、到主界面更新的应用程序。主界面是UI线程、图像采集跟图像分析分别在两个不同的工作线程中,通过信号与槽机制协作工作,相互配合实现图像采集到分析到结果更新到界面线程。
多线程协作信号触发示意图
代码实现
这样实现了三个类
ImageFetchThread // 图像采集 ImageAnalysisThread // 图像分析 ContentPanel // 界面显示与更新
这三个类的代码分别,模拟图像采集线程
1class ImageFetchThread(QtCore.QThread): 2 fire_stats_signal = QtCore.pyqtSignal(dict) 3 4 def __init__(self, images_dir): 5 super(ImageFetchThread, self).__init__() 6 self.images_dir = images_dir 7 self.read_next = True 8 9 def request_image(self): 10 self.read_next = True 11 12 def run(self): 13 if len(self.images_dir) == 0: 14 return 15 files = os.listdir(self.images_dir) 16 idx = 0 17 while True: 18 if idx == len(files): 19 break 20 if self.read_next is True: 21 print("grab one image...") 22 image = cv.imread(os.path.join(self.images_dir, files[idx])) 23 gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) 24 idx += 1 25 self.read_next = False 26 self.fire_stats_signal.emit({"im_data": gray}) 27 self.fire_stats_signal.emit({"done": "done"}) 28 return
处理图像线程
1class ImageAnalysisThread(QtCore.QThread): 2 request_image_signal = QtCore.pyqtSignal() 3 update_result_signal = QtCore.pyqtSignal(dict) 4 5 def __init__(self): 6 super(ImageAnalysisThread, self).__init__() 7 self.image_data = None 8 self.stop = False 9 10 def process_im(self, results): 11 self.image_data = results.get("im_data") 12 if results.get("done") is not None: 13 self.stop = True 14 15 def run(self): 16 while True: 17 if self.stop is True: 18 break 19 if self.image_data is None: 20 continue 21 print("started to process one image...") 22 # ret, binary = cv.threshold(self.image_data, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU) 23 binary = cv.adaptiveThreshold(self.image_data, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, 24 cv.THRESH_BINARY_INV, 25, 10) 25 se = cv.getStructuringElement(cv.MORPH_RECT, (7, 7)) 26 resutl = cv.morphologyEx(binary, cv.MORPH_DILATE, se) 27 self.request_image_signal.emit() 28 self.update_result_signal.emit({"im_data": resutl}) 29 self.image_data = None 30 self.update_result_signal.emit({"done": "done"}) 31 return
界面线程
1class ContentPanel(QtWidgets.QWidget): 2 def __init__(self, parent=None): 3 super().__init__(parent) 4 fileBtn = QtWidgets.QPushButton("目录...") 5 self.image_files_dir= QtWidgets.QLineEdit() 6 self.image_files_dir.setMinimumWidth(100) 7 self.image_files_dir.setEnabled(False) 8 self.processBtn = QtWidgets.QPushButton("开始处理") 9 hbox_layout = QtWidgets.QHBoxLayout() 10 hbox_layout.addWidget(fileBtn) 11 hbox_layout.addWidget(self.image_files_dir) 12 hbox_layout.addWidget(self.processBtn) 13 panel1 = QtWidgets.QGroupBox("目录选择") 14 panel1.setLayout(hbox_layout) 15 16 # 图像标签 17 self.imgLabel = QtWidgets.QLabel() 18 self.imgLabel.setMinimumSize(800, 600) 19 self.imgLabel.setStyleSheet("background-color:black; color: deeppink") 20 self.imgLabel.setAlignment(QtCore.Qt.AlignCenter) 21 22 # 添加到布局管理器中 23 vbox_layout = QtWidgets.QVBoxLayout() 24 vbox_layout.addWidget(panel1) 25 vbox_layout.addWidget(self.imgLabel) 26 vbox_layout.addStretch(1) 27 28 # 面板容器 29 self.setLayout(vbox_layout) 30 31 # setup listener 32 fileBtn.clicked.connect(self.on_select_image_dir) 33 self.processBtn.clicked.connect(self.on_process) 34 35 self.fetch_thread = None 36 self.analysis_thread = None 37 38 def on_select_image_dir(self): 39 img_dir = QtWidgets.QFileDialog.getExistingDirectory(self, "图像文件夹", ".") 40 self.image_files_dir.setText(img_dir)
演示部分
全部0条评论
快来发表一下你的评论吧 !