1、对图片进行数据增强
虽然用opencv和PIL也可以完成数据增强,但还是在网上找了两个方法,相较于opencv确实能快很多,参考的博客如下:
tensorflow数据增强:https://zhuanlan.zhihu.com/p/35938791 Augmentor数据增强库:
tensorflow来增强数据
(由于我用的是tensorflow2.0的版本,所以用起来有点别扭,但不影响)
import tensorflow as tf
import onnx
import keras
from keras.backend.tensorflow_backend import set_session
tf.compat.v1.disable_eager_execution()
message = tf.constant('start translate')
with tf.compat.v1.Session() as sess:
print(sess.run(message).decode())
#数据增强****************************************************************************
#import cv2.cv2 as cv
import matplotlib.pyplot as plt
def show(img): #想要显示增强后的图片,调用这个函数
with tf.compat.v1.Session() as sess:
img_ = img.eval()
plt.figure(1)
plt.imshow(img_)
plt.show()
path = 'C:/Users/Administrator/Desktop/jcy/2.jpg'
#读取图像
img = plt.imread(path)
#转为指针
tf_img = tf.convert_to_tensor(img)
#水平翻转
brght_img = tf.image.flip_left_right(tf_img)
#垂直翻转
brght_img = tf.image.flip_up_down(tf_img)
#随机翻转,翻转或不翻转概率1/2
#1/2概率左右翻转
brght_img = tf.image.random_flip_left_right(tf_img)
#1/2概率上下翻转
brght_img = tf.image.random_flip_up_down(tf_img)
#沿x轴或y轴翻转
brght_img = tf.reverse(tf_img, axis =[0]) #上下翻转
brght_img = tf.reverse(tf_img, axis =[1]) #左右翻转
#逆时针旋转90度
brght_img = tf.image.rot90(tf_img, k = 3) #k=1逆时针90度,k=3顺时针90度(逆时针270度)
#调节亮度,delta数值越大则图像越亮
brght_img = tf.image.adjust_brightness(tf_img, delta= 0.5) #-1到1,负数调暗,正数调亮
#随机调节亮度
brght_img = tf.image.random_brightness(tf_img, max_delta=0.5)
#修剪图片,central_crop函数保留从中心到指定距离的图像,并剪切掉外围的像素
brght_img = tf.image.central_crop(tf_img, 0.5)
#crop_to_bounding_box 函数指定偏移量和图像的宽度、高度,从而准确控制剪切区域
brght_img = tf.image.crop_to_bounding_box(tf_img,offset_height = 100, offset_width = 100, target_height = 400, target_width = 800)
#其他方法
'''
tf.image.adjust_contrast #调整RGB或灰度图像的对比度
tf.image.random_contrast #随机因子调整RGB或灰度图像的对比度
tf.image.adjust_hue #调节色调
tf.image.random_hue #随机因子调整RGB图像的色调
tf.image.adjust_gamma #Gamma校正
tf.image.adjust_saturation #调节饱和度
tf.image.random_saturation #随机因子调整RGB图像的饱和度
'''
#数据增强*******************************************************************************
使用Augmentor增强数据
先安装Augmentor模块,直接cmd执行pip3 install Augmentor
就行了。
import Augmentor
#自动在该目录下新建一个output文件夹,并扫描这个目录下的图片数量
p=Augmentor.Pipeline("C:/Users/Administrator/Desktop/jcy")
#顺时针旋转90度
#0.5指一般旋转一半不旋转,要生成的图片全部都旋转时令probability = 1
p.rotate270(probability=0.5)
#逆时针旋转90度
p.rotate90(probability=0.5)
#180度
p.rotate180(probability=0.5)
#不固定小角度旋转
#角度取值0-25
p.rotate(probability=0.5,max_left_rotation=25,max_right_rotation=10)
#透视形变-垂直方向形变:magnitude取(0,1),指的是形变程度
p.skew_tilt(probability=0.5,magnitude=1)
#透视形变-斜四角形变形变:magnitude取(0,1),指的是形变程度
p.skew_corner(probability=0.5,magnitude=1)
#弹性扭曲,类似区域扭曲的感觉
p.random_distortion(probability=1,grid_height=5,grid_width=16,magnitude=8)
#错切变换
p.shear(probability=1,max_shear_left=15,max_shear_right=15)
#随即区域擦除
p.random_erasing(probability=1,rectangle_area=0.5)
#以上所有操作的图片共生成200张,图片会存储在output文件夹中
#相当于先生成200张图片,再对这200张图片进行上方的变换,只不过所有的都是随机的,包括产生的200张图。
#比如原始9张图,生成20张增强的,但是由于是随机生成的,所以不是每张图片都有生成的图片。
p.sample(200)
比较之下,使用tensorflow时需要加载,加载的时间也不短,Augmentor就比较快速度,tensorflow还没加载完,这边两百张图片已经出来了,而且Augmentor也很方便。但是似乎Augmentor中功能不够全面,调亮度,三通道切换什么的好像还没有。
2、为pyqt添加线程
假如数据增强生成的图片比较多,程序执行时间比较长时,就会使软件界面卡住。所以这里我选择开启线程来处理数据增强。
新建一个线程类
class Thread_1(QtCore.QThread):
signal = QtCore.pyqtSignal(str) #触发信号的数据类型
def __init__(self):
super(Thread_1, self).__init__()
def run(self):
#将数据增强的代码写到线程类的run函数中
p = Augmentor.Pipeline("C:/Users/Administrator/Desktop/jcy")
p.rotate270(probability=0.5)
p.rotate90(probability=0.5)
p.rotate180(probability=0.5)
p.rotate(probability=0.5,max_left_rotation=25,max_right_rotation=25)
p.skew_tilt(probability=0.7,magnitude=1)
p.skew_corner(probability=0.7,magnitude=1)
p.random_distortion(probability=1,grid_height=5,grid_width=16,magnitude=8)
p.shear(probability=1,max_shear_left=15,max_shear_right=15)
p.random_erasing(probability=1,rectangle_area=0.5)
p.sample(1000)
触发线程时会自动执行线程的run函数。
实例化线程并start
#data_improve为数据增强按钮绑定的槽函数
def data_improve(self):
self.ui.log_show_label.setText('开始数据增强...')
self.thread_1 = Thread_1()
self.thread_1.start()
接着运行程序,点击按钮,在执行过程中,界面就不会卡住了,数据增强的功能也在后台执行着。
运行结果
执行数据增强产生的图片:
3、多线程主线程和子线程通信
主线程传参给子线程
数据增强需要使用自己配置的路径和一些选项,因此,我还需要把选择的文件夹路径和一些选项传到子线程中进行数据增强。
解决方法:
在实例化线程时夹带一个参数,然后在子线程的初始化中进行接收,子线程就可以使用这个参数了。
线程类代码:
class Thread_1(QtCore.QThread):
#接收主线程传来的参数
def __init__(self,augmen_path, is_rotate90, is_rotate270, is_rotate180, is_rotate, is_skew_tilt, is_skew_corner, is_distortion, is_shear, is_erasing, probability_line, improve_nm):
super(Thread_1, self).__init__()
self.path = augmen_path
self.is_rotate90 = is_rotate90
self.is_rotate270 = is_rotate270
self.is_rotate180 = is_rotate180
self.is_rotate = is_rotate
self.is_skew_tilt = is_skew_tilt
self.is_skew_corner = is_skew_corner
self.is_distortion = is_distortion
self.is_shear = is_shear
self.is_erasing = is_erasing
self.probability_line = probability_line
self.improve_nm = improve_nm
def run(self):
#接收到的路径
p = Augmentor.Pipeline(self.path)
#接收到的复选框状态
if self.is_rotate270:
p.rotate270(probability=self.probability_line)
if self.is_rotate90:
p.rotate90(probability=self.probability_line)
if self.is_rotate180:
p.rotate180(probability=self.probability_line)
if self.is_rotate:
p.rotate(probability=self.probability_line,max_left_rotation=25,max_right_rotation=25)
if self.is_skew_tilt:
p.skew_tilt(probability=self.probability_line,magnitude=1)
if self.is_skew_corner:
p.skew_corner(probability=self.probability_line,magnitude=1)
if self.is_distortion:
p.random_distortion(probability=self.probability_line,grid_height=5,grid_width=16,magnitude=8)
if self.is_shear:
p.shear(probability=self.probability_line,max_shear_left=15,max_shear_right=15)
if self.is_erasing:
p.random_erasing(probability=self.probability_line,rectangle_area=0.5)
#接收到的生成图片总数
p.sample(self.improve_nm)
按钮的槽函数触发线程代码:
def data_improve(self):
self.ui.log_show_label.setText('开始数据增强...')
probability_line = 0.5 #数据增强比率
improve_nm = 1000 #数据增强生成图片总数
#获取选择文件夹的路径
improve_path = self.improve_data_dir
#判断复选框是否被点击
is_rotate90 = self.ui.rotate90_check_btn_A.isChecked()
is_rotate270 = self.ui.rotate270_check_btn_A.isChecked():
is_rotate180 = self.ui.rotate180_check_btn_A.isChecked():
is_rotate = self.ui.rotate_check_btn_A.isChecked():
is_skew_tilt = self.ui.skew_tilt_check_btn_A.isChecked():
is_skew_corner = self.ui.skew_corner_check_btn_A.isChecked():
is_distortion = self.ui.distortion_check_btn_A.isChecked():
is_shear = self.ui.shear_check_btn_A.isChecked():
is_erasing = self.ui.erasing_check_btn_A.isChecked():
#获取QlineEdit的内容
if self.ui.probability_line_ed_A.text() != "":
probability_line = float(self.ui.probability_line_ed_A.text())
if self.ui.improve_nm_line_ed_A.text() != "":
improve_nm = int(self.ui.improve_nm_line_ed_A.text())
#实例化线程,并将参数传入
self.thread_1 = Thread_1(improve_path, is_rotate90, is_rotate270, is_rotate180, is_rotate, is_skew_tilt, is_skew_corner, is_distortion, is_shear, is_erasing, probability_line, improve_nm)
#启动线程
self.thread_1.start()
除此之外,也可以使用信号来传递变量,具体可以参考我写的C++中Qt的使用,其实用法与下面的子线程通过信号传递信号给主线程一样,任意两个类之间都可以通过信号来传递信息。
2021.5.25:
现在再回来看这样的代码,就感觉挺傻的,这其实就是通过构造函数参数来给类传参。
子线程传递信息给主线程
两种方法:
子线程发送信息给主线程
简单粗暴的:
(这里也可以通过信号来传参,主线程获取子线程的可以像下面这样直接获取类中的对象,子线程获取主线程的最好还是通过信号来传参,具体的参考qt中信号的使用就行了)
#在线程中定义变量:
self.send_to_main = "12345"
#那么在mainwindow类中可以直接使用,如:
saas = self.thread_1.send_to_main
self.ui.label.setText(saas)
但是,它可不会管线程跑的怎样,按照这段代码,主线程把子线程调起以后,会立马让label显示12345,更不会等待线程的完成,所以想要等到线程完成再传递变量,结合下面的发送信号的方法使用。
子线程通过信号控制ui界面的控件
线程中创建一个信号,将这个信号绑定到槽函数,通过槽函数来控制ui控件。
放上部分代码:
#线程中创建信号(为了直观展示,我把添加的代码的上下的代码也放上来了,真正添加的就那么几句):
class Thread_1(QtCore.QThread):
signal = QtCore.pyqtSignal(str) #新建一个信号(不要放到__init__里面,会报错)
def __init__(self,augmen_path, is_rotate90, is_rotate270, is_rotate180, is_rotate, is_skew_tilt, is_skew_corner, is_distortion, is_shear, is_erasing, probability_line, improve_nm):
super(Thread_1, self).__init__()
#在线程的run函数末尾添加:
msg = "完成!" #msg写啥都无所谓,我这里的主线程接受的信号不接受信号的内容
self.signal.emit(msg) #将名为signal的信号发送出去
#在主线程mainwindow的按钮中绑定信号和槽函数:
self.thread_1 = Thread_1(improve_path, is_rotate90, is_rotate270, is_rotate180, is_rotate, is_skew_tilt, is_skew_corner, is_distortion, is_shear, is_erasing, probability_line, improve_nm)
self.thread_1.start()
self.thread_1.signal.connect(self.send_complete)
#在mainwindow类中写槽函数的功能:
def send_complete(self): #放在mainwindow的任意地方
self.ui.log_show_label.setText('开始数据增强...'+'\n'+'完成!')
槽函数不一定是让label显示文字,可以是任意功能,包括变量或者内容的传递等。
想要传递变量给主进程,只要在进程结束的时候,用一个原有的变量来接受需要传递的内容,然后通过线程的信号绑定的槽函数中来将变量传给主线程,主线程使用一个变量来接收即可。
比如:
#在线程类中定义变量:
self.sys_text = ""
#在线程的run函数最后,即线程执行完以后添加:
self.sys_text = str(sys.stdout) #控制台的内容
#改一下线程的槽函数的内容,使用mainwindow类的变量来接收
def send_complete(self):
self.mes_from_thread_1 = self.thread_1.sys_text #主线程变量接收
self.ui.log_show_label.setText('开始数据增强...'+'\n'+self.mes_from_thread_1+'\n'+'完成!')
self.thread_1.quit() #最后的最后,不要忘了关闭线程
虽然输出来的控制台信息不知道是个什么东东,不要在意!
那么,主线程与子线程信息的双向传递就完成了!