pyqt显示图片_PyQt——用户界面动态绘图

在GUI用户界面上绘制实时图像这个问题是在让我头疼了好久,最开始采用Python中最常用的绘图库matplotlib,在用这个库进行动态绘图时遇到了很多问题。遇到的问题如下:
  1. 动态绘图的图像生成期间会导致界面卡顿 >>>> 引入多线程(QT界面涉及到线程安全,是不允许子线程去更改主线程界面中的任何控件状态,所以就导致了很多的麻烦)。

  2. matplotlib会生成静态图后并不能实时更新 >>>> 使用ion,开启matplotlib的动态绘制模式

  3. 图片在绘制过程中会一条一条往上叠加,不会清除原来的曲线 >>>> 一种方案是将图片的hold性质改成False,但是hold属性在较新版本的matplotlib包里面已经删掉了,想要用要回退到以前的版本;另一种方案是在绘图直线执行以下clear操作。

  4. 虽然设置了图坐标轴名称及其他文字内容,但是图片更新的过程中坐标轴会移动,而且文字会丢失 >>>> 这个应该有解决方法,但是没找到。

  5. 测试每次图片更新间隔时间为0.25s左右,也就是说1秒只能更新四张图片 >>>> 无解,狗带。

  6. 图片更新过程中有时会丢失曲线,丢失曲线后报错都是底层包的“list index out of range” >>>> 好像也是无解的。

前面一些问题还能忍,找找办法还能解决。第5点是真的不能忍,绘图更新慢就算了,还会拖累其他子线程的运行速度,这个估计是因为Python多线程并非真正意义上的多线程,底层变量还是相同的。也尝试过想用多进程来代替绘图的多线程,但是多进程还涉及到一个共享变量的问题,太难了,写到一半放弃了。 这个动态绘图前前后后折磨了我一个多月,最后受不来了,推倒重写。重写主要有两个方面:
  • 一个是将原本使用的Python自带多线程Thread替换成QT框架下的QThread,通过信号与槽这样一个机制来向界面主线程发送信息更改界面内容;

  • 另一个是用pyqtgraph库来进行动态绘图,pyqtgraph库是专门用于Qt GUI界面上的图像绘制,主打的就是轻量级的特点。虽然这个库的可参考资料实在不多,很多特性更改都是啃文档和摸索实现的,但是结果是真香!!!!

接下来将matplotlib库和pyqtgraph库写的代码贴出来解释一下,然后再附上每一种的实现 效果动图 。

1. matplotlib实现PyQt界面上的动态绘图

matplotlib动态绘图代码主要分为三个部分:初始化,更新函数,图像绘制。

1.1 界面开启时候的初始化设置

1#画图函数的初始设置
2def drawing_init(self):
3    self.layout = QHBoxLayout(self.label)
4    self.mpl = PlotCanvas(self, width=3, height=9)  # 实例化一个画布对象
5    #创建每一幅图的绘图数据
6    self.move_val1 = [0] * 50
7    self.move_val2 = [0] * 50
8    self.move_val3 = [0] * 50
9    self.move_val4 = [0] * 50

1.2 动态图像初始化及更新函数

 1#绘图线程
2class PlotCanvas(FigureCanvas):
3    def __init__(self, parent=None, width=5, height=4, dpi=100):
4        # 配置中文显示
5        plt.rcParams['font.family'] = ['SimHei']  # 用来正常显示中文标签
6        plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
7        self.fig = plt.figure(figsize=(width, height), dpi=dpi)
8        self.font1 = {'family': 'KaiTi',
9                      'weight': 'normal',
10                      'size': 13,
11                      }
12        self.axe1 = self.fig.add_subplot(411)
13        # self.axe1.hold(False)
14        self.axe1.set_xlabel("发动机1", self.font1)
15        self.axe1.set_xticks([])
16        self.axe1.set_ylabel("运动情况", self.font1)
17        self.axe1.set_yticks([])
18        plt.subplots_adjust(left=0.1, top=0.99, right=0.99, bottom=0.03, wspace=0, hspace=0.15)
19        self.axe2 = self.fig.add_subplot(412, xlim=(0, 50), ylim=(-10, 10))
20        # self.axe2.hold(False)
21        self.axe2.set_xlabel("发动机2", self.font1)
22        self.axe2.set_xticks([])
23        self.axe2.set_ylabel("运动情况", self.font1)
24        self.axe2.set_yticks([])
25        self.axe3 = self.fig.add_subplot(413, xlim=(0, 50), ylim=(-10, 10))
26        # self.axe3.hold(False)
27        self.axe3.set_xlabel("发动机3", self.font1)
28        self.axe3.set_xticks([])
29        self.axe3.set_ylabel("运动情况", self.font1)
30        self.axe3.set_yticks([])
31        self.axe4 = self.fig.add_subplot(414, xlim=(0, 50), ylim=(-10, 10))
32        # self.axe4.hold(False)
33        self.axe4.set_xlabel("发动机4", self.font1)
34        self.axe4.set_xticks([])
35        self.axe4.set_ylabel("运动情况", self.font1)
36        self.axe4.set_yticks([])
37        FigureCanvas.__init__(self, self.fig)
38        self.setParent(parent)
39        #定义FigureCanvas的尺寸策略,这部分的意思是设置FigureCanvas,使之尽可能的向外填充空间
40        FigureCanvas.setSizePolicy(self,
41                                   QSizePolicy.Expanding,
42                                   QSizePolicy.Expanding)
43        FigureCanvas.updateGeometry(self) 
44
45    def drawing(self,move_val1,move_val2,move_val3,move_val4):
46        self.axe1.clear()
47        self.axe2.clear()
48        self.axe3.clear()
49        self.axe4.clear()
50        plt.ion()
51        self.axe1.plot(move_val1)
52        self.axe2.plot(move_val2)
53        self.axe3.plot(move_val3)
54        self.axe4.plot(move_val4)

这里函数里面给出了matplotlib绘图基础设置的一些代码,有文字正常显示、字体、多个图像组成一幅图、尺寸设置等。在drawing函数中,50行的plt.ion()开启了动态绘图。

1.3 图像绘制

 1#更改绘图数据
2del self.move_val1[0]
3del self.move_val2[0]
4del self.move_val3[0]
5del self.move_val4[0]
6self.move_val1.append(img_val[0])
7self.move_val2.append(img_val[1])
8self.move_val3.append(img_val[2])
9self.move_val4.append(img_val[3])
10#每次绘制之前布局中移除原本图像,再添加新的图像
11self.layout.removeWidget(self.mpl)
12self.mpl.drawing(self.move_val1, self.move_val2, self.move_val3, self.move_val4)
13self.layout.addWidget(self.mpl)
效果如下:

pyqt显示图片_PyQt——用户界面动态绘图_第1张图片 

可以看出图像更新有些卡顿,而且有时候图像会没有曲线,还有坐标轴是真的丑。

2. pyqtgraph实现PyQt界面上的动态绘图 2.1 绘图函数初始化
 1#画图功能画布初始化
2def drawing_init(self):
3    self.move_val_list = [[], [], [], []]
4    self.data_val_list = [[], [], [], []]
5
6    self.pen1 = QPen(QColor(110, 242, 255))  #Qpen的画笔
7    self.pen1.setWidth(0.03)
8    self.pen1.setStyle(Qt.SolidLine)
9    self.pen2 = QPen(QColor(207, 155, 255))
10    self.pen2.setWidth(0.03)
11    self.pen2.setStyle(Qt.SolidLine)
12    self.pen3 = pg.mkPen({'color': (110, 242, 255), 'width': 2})  #mkPen的画笔
13    self.pen4 = pg.mkPen({'color': (207, 155, 255), 'width': 2})
14    pg.setConfigOption('background', (15, 64, 123))  #设置背景色
15    pg.setConfigOption('foreground', (148, 209, 224))  #设置坐标轴颜色
16    self.plot_layout1 = QGridLayout(self.drawlabel1)  #创建网格Layout
17    self.plot_plt1 = pg.PlotWidget()  #初始化绘图板
18    self.plot_plt1.setYRange(max=-1.3, min=1.3)  #设置纵坐标区域
19    self.plot_layout1.addWidget(self.plot_plt1)  #将绘图板置于布局中
20    # self.plot_plt1.setLabel('bottom', "开始运动时间", units='s')
21    # pltItem = self.plot_plt1.getPlotItem()
22    self.plot_plt1.showGrid(x=True, y=True, alpha=0.8)  #设置网格及其透明度
23    self.plot_plt1.setMouseEnabled(x=True, y=False)  #设置能够通过鼠标滚轮或者拖动图片
24    self.plot_moveval1 = self.plot_plt1.plot(pen=self.pen3)  #设置绘图曲线1画笔
25    self.plot_dataval1 = self.plot_plt1.plot(pen=self.pen4)  #设置绘图曲线2画笔

解释点:

  1. 第6行和第12行设置了Qpen和mkpen两种绘图画笔,感觉还是mkpen好用一些。Qpen线宽较大时,感觉就是一个一个点组成的,视觉效果不行。
  2. 第14行和15行中设置了图的背景色和坐标轴颜色,一般界面风格统一,多幅图都是统一背景色,后续图像便不需再设置这一特性,只要将上面关于plot_plt1的设置重新写一份就行了

2.2 图像更新

1try:
2    self.plot_moveval1.setData(move_x_val, graph_move_val_list[0])
3    self.plot_dataval1.setData(data_x_val, graph_data_val_list[0])
4except Exception:
5    pass
解释点:
  1. 用setData就能更新图像,setData的机制应该是只重绘新的数据点,且更新对象是曲线,因此不会存在上一条曲线仍然留在图像中的问题。

  2. 经过测试图像更新一次时间在10ms以下,比起matplotlib动态图的250ms性能提升很多。

  3. 尽量将绘图数据更新置于其他线程,以提高绘图速度。数据的更新与绘图时数据的获取可以考虑采取一个线程锁,保护数据的一致性。为了线程安全,设置了一个子线程每隔一秒钟发射信号让绘图函数执行一次。

  4. 这里用了try....except的异常机制是因为x轴的数据是通过np.arrange对一个区间进行均分的,在少数情况下x轴数据与y轴数据会差1个,这时候就会报错。因此引入异常机制,如果位数不等这次图像更新就不执行了,等待下一秒一起更新。

效果:

pyqt显示图片_PyQt——用户界面动态绘图_第2张图片

pyqtgraph好用的地方在于 轻量级 ,而且内嵌了 鼠标事件 ,左下角还有一个A的按钮,可以让图像回到最适合的大小。右键图片还自带图片保存,鼠标事件设置等。 缺点就是资料比较少,主要资料有:
  • pyqtgraph中文文档:https://blog.csdn.net/Eppley/article/details/82999998?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1

  • 官方文档:https://pyqtgraph.readthedocs.io/en/latest/index.html

  • 大佬教程:https://zmister.com/archives/category/data-apply/pyqtgtaph-tutorial/

你可能感兴趣的:(pyqt显示图片)