最近在做一个系统。对代码进行主线程和子线程的分离,将比较耗时的数据处理操作放在子线程里面操作,防止卡死。由于最后系统使用的环境很杂,pyqt5版本过高,在一些xp的低版本操作系统是运行会出问题,所以使用的pyqt4,但原理一样。
原理参考博文最后链接,这里记录自己的使用方法
from PyQt4 import QtCore
from PyQt4.QtCore import pyqtSignal
class WorkThread(QtCore.QThread):#定义一个工作线程,后面会调用和重写
# 使用信号和UI主线程通讯,参数是发送信号时附带参数的数据类型,可以是str、int、list等
finishSignal = pyqtSignal(int)
def run(self):#线程启动后会自动执行,这里是逻辑实现的代码
self.finishSignal.emit(int) #发射信号
WorkThread是创建的一个线程类,后面会重写他的run方法
实现制作数据集的操作:
class dealThread(WorkThread): #1.制作数据集
finishSignal = pyqtSignal(int,int)
# 带3个参数
def __init__(self,dataset_param,total_label,tf_record):
super(dealThread, self).__init__()
self.datasetparam_ui = dataset_param
self.totalnum_label = total_label
self.Tfrecord_ui = tf_record
#处理逻辑业务
def run(self):
try:
if self.datasetparam_ui.tfrecordstart_radioButton.isChecked():# Tfrecord文件
saveTfrecord_path = self.datasetparam_ui.datasetsavepath_label.text() # 保存路径
ftrecordfilename = ("train.tfrecords") # tfrecords格式文件名
# 通过这一句将数据写入到TFRecord文件,参数为TFrecord的文件路径,入口
writer = tf.io.TFRecordWriter(saveTfrecord_path + "\\" + ftrecordfilename)
# 标签
with open(self.datasetparam_ui.csvlabel_label.text(), 'r') as ff:
reader = csv.reader(ff)
images_list = []
labels = []
for row in reader:
images_list.append(row[0])
labels.append(row[1])
ff.close()
lines = 1
bad = 0
contents = self.get_file(self.datasetparam_ui.datasetfile_label.text(), [])
line_total = len(contents)
# print("总数:",line_total)
for filename in contents:
lines += 1
img_ok = imghdr.what(filename) # 判断图片是否损坏
if img_ok is None:
bad += 1
continue
path_pic = os.path.abspath(filename)[:-12]
# print(path_pic)
Cam_name = os.path.abspath(path_pic).split('\\')[-1] # 相机名称
Cam_name = Cam_name + "\t" # 以0开头的以文本形式保留
# print(Cam_name)
img = Image.open(filename, "r")
exif_data = img._getexif()
date_time = exif_data.setdefault(306) # 得到日期时间格式,如2016:03:24 13:07:36
img_date = date_time.replace(":", "/", 2)
# print(img_date)
width = int(self.Tfrecord_ui.tfpicwidth_lineEdit.text())
height = int(self.Tfrecord_ui.tfpicheight_lineEdit.text())
img = img.resize((width, height))
img_raw = img.tobytes() # 将图片转化为二进制格式
label = 2 # 原始标记
for im_id in range(1,len(images_list)):
if filename[-23:] == images_list[im_id][-23:]:
label = int(labels[im_id])
# print(label)
break
else:
continue
# 通过该方法实现格式存储,非常重要
example = tf.train.Example(
# 初始化Features对象,一般传入一个字典,字典的键是一个字符串,表示名字,字典的值是一个tf.train.Feature对象.
features=tf.train.Features(feature={
'path': tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw])),
'name': tf.train.Feature(
bytes_list=tf.train.BytesList(value=[bytes(filename[-12:], encoding='utf8')])),
'Cam': tf.train.Feature(
bytes_list=tf.train.BytesList(value=[bytes(Cam_name, encoding='utf8')])),
'YY': tf.train.Feature(int64_list=tf.train.Int64List(value=[int(img_date[-19:-15])])),
'MM': tf.train.Feature(int64_list=tf.train.Int64List(value=[int(img_date[-14:-12])])),
'DD': tf.train.Feature(int64_list=tf.train.Int64List(value=[int(img_date[-11:-9])])),
'HH': tf.train.Feature(int64_list=tf.train.Int64List(value=[int(img_date[-9:-6])])),
'mm': tf.train.Feature(int64_list=tf.train.Int64List(value=[int(img_date[-5:-3])])),
'SS': tf.train.Feature(int64_list=tf.train.Int64List(value=[int(img_date[-2:])])),
'date': tf.train.Feature(
bytes_list=tf.train.BytesList(value=[bytes(img_date[-19:-9], encoding='utf8')])),
'label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label])),
}))
writer.write(example.SerializeToString())
self.finishSignal.emit(lines, line_total) # 发射信号,捕获后显示
writer.close()
self.totalnum_label.setText(str(lines - bad)) # 显示数据总数
except:
pass
(1)定义了一个信号:finishSignal = pyqtSignal(int,int),包含两个参数,这两个参数需要发射出去,用于新窗口的数据显示等操作。
(2)def init(self,dataset_param,total_label,tf_record):
这里传入了新的参数,这些参数会在run方法里面用到,所以需要外部传入。dataset_param表示的是一个窗口,用于创建数据集的参数设置,total_label表示主窗口的显示标签。tf_record表示tfrecord文件的图片大小设置的参数,即宽和高。
最后还有加上:
self.finishSignal.emit(lines, line_total)
将信号发射出去,其中包含了run方法执行后得到的变量值,类型是开始定义的信号类型。需要一一对应。
明确信号控制的操作 也就是槽函数。信号发出之后希望进行的操作,通过信号所在类实例.信号名.connect(槽函数)实现。
# 1.创建数据集
def makeFilecsv_TFrecord(self):
try:
self.make_thread = dealThread(self.datasetparam_ui, self.totalnum_label, self.Tfrecord_ui)
except:
self.make_thread = dealThread(self.datasetparam_ui, self.totalnum_label, [])
self.make_thread.start() # 执行run函数
self.make_thread.finishSignal.connect(self.show_data_makedataset)
# 线程执行后显示
def show_data_makedataset(self,lines,line_total):
#数据写入后显示进度
if self.datasetparam_ui.csvstart_radioButton.isChecked():
self.label.setText("正在制作.csv格式数据集...")
if self.datasetparam_ui.tfrecordstart_radioButton.isChecked():
self.label.setText("正在制作TFRecords格式的数据集...")
self.totalnum_label.setText(str(lines)) # 显示数据总数
self.main_progressBar.setValue(lines * (100 / (line_total)))
if self.main_progressBar.value() ==100:
self.label.setText("数据集制作完成!")
QtGui.QMessageBox.about(self, '提示', "数据集制作完成!")
(1)makeFilecsv_TFrecord函数里面需要对创建的线程类进行实例化,即:
self.make_thread = dealThread(self.datasetparam_ui, self.totalnum_label, self.Tfrecord_ui)
其中,需要传入参数,也就是前面说到的那几个窗体的内容。因为这些数据是外部传入的,可以理解为在平常做开发的时候,把run里面的代码直接粘贴到这个函数里面就可以执行了,但是为了避免执行过程中较长的处理时间而出现主线程界面的卡顿(界面无响应),所以我们把这部分数据的处理放在了run方法里面,但是run方法里面的代码,有些参数就需要外部传递过去,才可以实现。
(2)我这里用了异常处理是因为我还进行了csv文件的制作。制作csv文件时,没有tfrecord的事情,所以传入了一个空值。
(3)绑定自己定义的信号,代码如下,这部分表示run方法执行完成后,需要做什么操作,这里我们可以通过connect来绑定槽函数,该槽函数就可以是我们用来显示的页面了,这样就把处理和显示分开了。
self.make_thread.finishSignal.connect(self.show_data_makedataset)
其中槽函数show_data_makedataset()表示的就是需要显示的数据内容,由于该数据是emit发射出来的,这里需要接收参数用于显示,所以带了几个参数。
(4)self.make_thread.start() # 执行run函数
self.datasetstart_action.triggered.connect(self.makeFilecsv_TFrecord) # 创建数据集
点击创建执行makeFilecsv_TFrecord函数,即多线程触发。
上图的49就表示run执行后发射回来用于显示的值。由于是会动态的变化,所以把它单独分离处理。这样就不会出现页面无响应这样的问题。
注:run函数内的代码是对tfrecord文件的制作,具体制作过程可以参考其他博客。
参考链接1
参考链接2