如何使用matplotlib在while循环中实时绘制?

我正在尝试使用OpenCV实时绘制来自摄像机的一些数据。但是,实时绘图(使用matplotlib)似乎不能正常工作。

我把这个问题隔离在这个简单的例子中:

fig = plt.figure()
plt.axis([0, 1000, 0, 1])


i = 0
x = list()
y = list()


while i < 1000:
temp_y = np.random.random()
x.append(i)
y.append(temp_y)
plt.scatter(i, temp_y)
i += 1
plt.show()

我希望这个例子能分别画出1000个点。实际发生的情况是,窗口弹出,显示第一个点(好吧),然后等待循环结束,然后填充图的其余部分。

有什么想法,为什么我没有看到点填充一个时间?

767203 次浏览

问题似乎是你期望plt.show()显示窗口,然后返回。它不会那样做。程序将在此时停止,只有在您关闭该窗口后才恢复。您应该能够测试这一点:如果您关闭窗口,然后另一个窗口将弹出。

要解决这个问题,只需在循环之后调用plt.show()一次。然后你就得到了完整的情节。(但不是“实时绘图”)

你可以尝试像这样设置关键字参数block:在开始时设置一次plt.show(block=False),然后使用.draw()进行更新。

show可能不是最好的选择。我要做的是使用pyplot.draw()代替。你可能还想在循环中包含一个小的时间延迟(例如,time.sleep(0.05)),这样你就可以看到发生的情节。如果我对你的例子做这些改变,它对我有用,我看到每个点一次出现一个。

下面是问题代码的工作版本(至少需要2011年11月14日的Matplotlib 1.1.0版本):

import numpy as np
import matplotlib.pyplot as plt


plt.axis([0, 10, 0, 1])


for i in range(10):
y = np.random.random()
plt.scatter(i, y)
plt.pause(0.05)


plt.show()

注意对plt.pause(0.05)的调用,它既绘制新数据,又运行GUI的事件循环(允许鼠标交互)。

如果你对实时绘图感兴趣,我建议你看看matplotlib的动画API。特别是,使用blit来避免在每一帧上重绘背景可以给你带来实质性的速度增益(~10倍):

#!/usr/bin/env python


import numpy as np
import time
import matplotlib
matplotlib.use('GTKAgg')
from matplotlib import pyplot as plt




def randomwalk(dims=(256, 256), n=20, sigma=5, alpha=0.95, seed=1):
""" A simple random walk with memory """


r, c = dims
gen = np.random.RandomState(seed)
pos = gen.rand(2, n) * ((r,), (c,))
old_delta = gen.randn(2, n) * sigma


while True:
delta = (1. - alpha) * gen.randn(2, n) * sigma + alpha * old_delta
pos += delta
for ii in xrange(n):
if not (0. <= pos[0, ii] < r):
pos[0, ii] = abs(pos[0, ii] % r)
if not (0. <= pos[1, ii] < c):
pos[1, ii] = abs(pos[1, ii] % c)
old_delta = delta
yield pos




def run(niter=1000, doblit=True):
"""
Display the simulation using matplotlib, optionally using blit for speed
"""


fig, ax = plt.subplots(1, 1)
ax.set_aspect('equal')
ax.set_xlim(0, 255)
ax.set_ylim(0, 255)
ax.hold(True)
rw = randomwalk()
x, y = rw.next()


plt.show(False)
plt.draw()


if doblit:
# cache the background
background = fig.canvas.copy_from_bbox(ax.bbox)


points = ax.plot(x, y, 'o')[0]
tic = time.time()


for ii in xrange(niter):


# update the xy data
x, y = rw.next()
points.set_data(x, y)


if doblit:
# restore background
fig.canvas.restore_region(background)


# redraw just the points
ax.draw_artist(points)


# fill in the axes rectangle
fig.canvas.blit(ax.bbox)


else:
# redraw everything
fig.canvas.draw()


plt.close(fig)
print "Blit = %s, average FPS: %.2f" % (
str(doblit), niter / (time.time() - tic))


if __name__ == '__main__':
run(doblit=False)
run(doblit=True)

输出:

Blit = False, average FPS: 54.37
Blit = True, average FPS: 438.27

我知道这个问题很老了,但现在在GitHub上有一个名为drawnow的包,名为"python-drawnow"。这提供了一个类似于MATLAB的drawnow的接口——你可以很容易更新一个图形。

用例示例:

import matplotlib.pyplot as plt
from drawnow import drawnow


def make_fig():
plt.scatter(x, y)  # I think you meant this


plt.ion()  # enable interactivity
fig = plt.figure()  # make a figure


x = list()
y = list()


for i in range(1000):
temp_y = np.random.random()
x.append(i)
y.append(temp_y)  # or any arbitrary update to your figure's data
i += 1
drawnow(make_fig)

python-drawnow是plt.draw的精简包装,但提供了在图形显示后确认(或调试)的能力。

这些方法对我都没用。 但我找到了这个 实时matplotlib图仍然在循环中不工作 < / p >

你所需要做的就是添加

plt.pause(0.0001)

然后你可以看到新的地块。

所以你的代码应该是这样的,它可以工作

import matplotlib.pyplot as plt
import numpy as np
plt.ion() ## Note this correction
fig=plt.figure()
plt.axis([0,1000,0,1])


i=0
x=list()
y=list()


while i <1000:
temp_y=np.random.random();
x.append(i);
y.append(temp_y);
plt.scatter(i,temp_y);
i+=1;
plt.show()
plt.pause(0.0001) #Note this correction

如果你想要绘制而不是在绘制更多点时冻结线程,你应该使用plt.pause()而不是time.sleep()

我使用下面的代码来绘制一系列的xy坐标。

import matplotlib.pyplot as plt
import math




pi = 3.14159


fig, ax = plt.subplots()


x = []
y = []


def PointsInCircum(r,n=20):
circle = [(math.cos(2*pi/n*x)*r,math.sin(2*pi/n*x)*r) for x in xrange(0,n+1)]
return circle


circle_list = PointsInCircum(3, 50)


for t in range(len(circle_list)):
if t == 0:
points, = ax.plot(x, y, marker='o', linestyle='--')
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
else:
x_coord, y_coord = circle_list.pop()
x.append(x_coord)
y.append(y_coord)
points.set_data(x, y)
plt.pause(0.01)

这是我要在我的系统上工作的一个版本。

import matplotlib.pyplot as plt
from drawnow import drawnow
import numpy as np


def makeFig():
plt.scatter(xList,yList) # I think you meant this


plt.ion() # enable interactivity
fig=plt.figure() # make a figure


xList=list()
yList=list()


for i in np.arange(50):
y=np.random.random()
xList.append(i)
yList.append(y)
drawnow(makeFig)
#makeFig()      The drawnow(makeFig) command can be replaced
#plt.draw()     with makeFig(); plt.draw()
plt.pause(0.001)

drawnow(makeFig)行可以用makeFig()代替;plt.draw()序列,它仍然可以正常工作。

我知道现在回答这个问题有点晚了。尽管如此,我在前一段时间做了一些代码来绘制实时图表,我想分享一下:

PyQt4代码:

###################################################################
#                                                                 #
#                    PLOT A LIVE GRAPH (PyQt4)                    #
#                  -----------------------------                  #
#            EMBED A MATPLOTLIB ANIMATION INSIDE YOUR             #
#            OWN GUI!                                             #
#                                                                 #
###################################################################




import sys
import os
from PyQt4 import QtGui
from PyQt4 import QtCore
import functools
import numpy as np
import random as rd
import matplotlib
matplotlib.use("Qt4Agg")
from matplotlib.figure import Figure
from matplotlib.animation import TimedAnimation
from matplotlib.lines import Line2D
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import time
import threading




def setCustomSize(x, width, height):
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(x.sizePolicy().hasHeightForWidth())
x.setSizePolicy(sizePolicy)
x.setMinimumSize(QtCore.QSize(width, height))
x.setMaximumSize(QtCore.QSize(width, height))


''''''


class CustomMainWindow(QtGui.QMainWindow):


def __init__(self):


super(CustomMainWindow, self).__init__()


# Define the geometry of the main window
self.setGeometry(300, 300, 800, 400)
self.setWindowTitle("my first window")


# Create FRAME_A
self.FRAME_A = QtGui.QFrame(self)
self.FRAME_A.setStyleSheet("QWidget { background-color: %s }" % QtGui.QColor(210,210,235,255).name())
self.LAYOUT_A = QtGui.QGridLayout()
self.FRAME_A.setLayout(self.LAYOUT_A)
self.setCentralWidget(self.FRAME_A)


# Place the zoom button
self.zoomBtn = QtGui.QPushButton(text = 'zoom')
setCustomSize(self.zoomBtn, 100, 50)
self.zoomBtn.clicked.connect(self.zoomBtnAction)
self.LAYOUT_A.addWidget(self.zoomBtn, *(0,0))


# Place the matplotlib figure
self.myFig = CustomFigCanvas()
self.LAYOUT_A.addWidget(self.myFig, *(0,1))


# Add the callbackfunc to ..
myDataLoop = threading.Thread(name = 'myDataLoop', target = dataSendLoop, daemon = True, args = (self.addData_callbackFunc,))
myDataLoop.start()


self.show()


''''''




def zoomBtnAction(self):
print("zoom in")
self.myFig.zoomIn(5)


''''''


def addData_callbackFunc(self, value):
# print("Add data: " + str(value))
self.myFig.addData(value)






''' End Class '''




class CustomFigCanvas(FigureCanvas, TimedAnimation):


def __init__(self):


self.addedData = []
print(matplotlib.__version__)


# The data
self.xlim = 200
self.n = np.linspace(0, self.xlim - 1, self.xlim)
a = []
b = []
a.append(2.0)
a.append(4.0)
a.append(2.0)
b.append(4.0)
b.append(3.0)
b.append(4.0)
self.y = (self.n * 0.0) + 50


# The window
self.fig = Figure(figsize=(5,5), dpi=100)
self.ax1 = self.fig.add_subplot(111)




# self.ax1 settings
self.ax1.set_xlabel('time')
self.ax1.set_ylabel('raw data')
self.line1 = Line2D([], [], color='blue')
self.line1_tail = Line2D([], [], color='red', linewidth=2)
self.line1_head = Line2D([], [], color='red', marker='o', markeredgecolor='r')
self.ax1.add_line(self.line1)
self.ax1.add_line(self.line1_tail)
self.ax1.add_line(self.line1_head)
self.ax1.set_xlim(0, self.xlim - 1)
self.ax1.set_ylim(0, 100)




FigureCanvas.__init__(self, self.fig)
TimedAnimation.__init__(self, self.fig, interval = 50, blit = True)


def new_frame_seq(self):
return iter(range(self.n.size))


def _init_draw(self):
lines = [self.line1, self.line1_tail, self.line1_head]
for l in lines:
l.set_data([], [])


def addData(self, value):
self.addedData.append(value)


def zoomIn(self, value):
bottom = self.ax1.get_ylim()[0]
top = self.ax1.get_ylim()[1]
bottom += value
top -= value
self.ax1.set_ylim(bottom,top)
self.draw()




def _step(self, *args):
# Extends the _step() method for the TimedAnimation class.
try:
TimedAnimation._step(self, *args)
except Exception as e:
self.abc += 1
print(str(self.abc))
TimedAnimation._stop(self)
pass


def _draw_frame(self, framedata):
margin = 2
while(len(self.addedData) > 0):
self.y = np.roll(self.y, -1)
self.y[-1] = self.addedData[0]
del(self.addedData[0])




self.line1.set_data(self.n[ 0 : self.n.size - margin ], self.y[ 0 : self.n.size - margin ])
self.line1_tail.set_data(np.append(self.n[-10:-1 - margin], self.n[-1 - margin]), np.append(self.y[-10:-1 - margin], self.y[-1 - margin]))
self.line1_head.set_data(self.n[-1 - margin], self.y[-1 - margin])
self._drawn_artists = [self.line1, self.line1_tail, self.line1_head]


''' End Class '''


# You need to setup a signal slot mechanism, to
# send data to your GUI in a thread-safe way.
# Believe me, if you don't do this right, things
# go very very wrong..
class Communicate(QtCore.QObject):
data_signal = QtCore.pyqtSignal(float)


''' End Class '''




def dataSendLoop(addData_callbackFunc):
# Setup the signal-slot mechanism.
mySrc = Communicate()
mySrc.data_signal.connect(addData_callbackFunc)


# Simulate some data
n = np.linspace(0, 499, 500)
y = 50 + 25*(np.sin(n / 8.3)) + 10*(np.sin(n / 7.5)) - 5*(np.sin(n / 1.5))
i = 0


while(True):
if(i > 499):
i = 0
time.sleep(0.1)
mySrc.data_signal.emit(y[i]) # <- Here you emit a signal!
i += 1
###
###




if __name__== '__main__':
app = QtGui.QApplication(sys.argv)
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Plastique'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())


''''''
< p >, < br > 我最近重写了PyQt5的代码 PyQt5代码:

###################################################################
#                                                                 #
#                    PLOT A LIVE GRAPH (PyQt5)                    #
#                  -----------------------------                  #
#            EMBED A MATPLOTLIB ANIMATION INSIDE YOUR             #
#            OWN GUI!                                             #
#                                                                 #
###################################################################


import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import functools
import numpy as np
import random as rd
import matplotlib
matplotlib.use("Qt5Agg")
from matplotlib.figure import Figure
from matplotlib.animation import TimedAnimation
from matplotlib.lines import Line2D
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import time
import threading


class CustomMainWindow(QMainWindow):
def __init__(self):
super(CustomMainWindow, self).__init__()
# Define the geometry of the main window
self.setGeometry(300, 300, 800, 400)
self.setWindowTitle("my first window")
# Create FRAME_A
self.FRAME_A = QFrame(self)
self.FRAME_A.setStyleSheet("QWidget { background-color: %s }" % QColor(210,210,235,255).name())
self.LAYOUT_A = QGridLayout()
self.FRAME_A.setLayout(self.LAYOUT_A)
self.setCentralWidget(self.FRAME_A)
# Place the zoom button
self.zoomBtn = QPushButton(text = 'zoom')
self.zoomBtn.setFixedSize(100, 50)
self.zoomBtn.clicked.connect(self.zoomBtnAction)
self.LAYOUT_A.addWidget(self.zoomBtn, *(0,0))
# Place the matplotlib figure
self.myFig = CustomFigCanvas()
self.LAYOUT_A.addWidget(self.myFig, *(0,1))
# Add the callbackfunc to ..
myDataLoop = threading.Thread(name = 'myDataLoop', target = dataSendLoop, daemon = True, args = (self.addData_callbackFunc,))
myDataLoop.start()
self.show()
return


def zoomBtnAction(self):
print("zoom in")
self.myFig.zoomIn(5)
return


def addData_callbackFunc(self, value):
# print("Add data: " + str(value))
self.myFig.addData(value)
return


''' End Class '''




class CustomFigCanvas(FigureCanvas, TimedAnimation):
def __init__(self):
self.addedData = []
print(matplotlib.__version__)
# The data
self.xlim = 200
self.n = np.linspace(0, self.xlim - 1, self.xlim)
a = []
b = []
a.append(2.0)
a.append(4.0)
a.append(2.0)
b.append(4.0)
b.append(3.0)
b.append(4.0)
self.y = (self.n * 0.0) + 50
# The window
self.fig = Figure(figsize=(5,5), dpi=100)
self.ax1 = self.fig.add_subplot(111)
# self.ax1 settings
self.ax1.set_xlabel('time')
self.ax1.set_ylabel('raw data')
self.line1 = Line2D([], [], color='blue')
self.line1_tail = Line2D([], [], color='red', linewidth=2)
self.line1_head = Line2D([], [], color='red', marker='o', markeredgecolor='r')
self.ax1.add_line(self.line1)
self.ax1.add_line(self.line1_tail)
self.ax1.add_line(self.line1_head)
self.ax1.set_xlim(0, self.xlim - 1)
self.ax1.set_ylim(0, 100)
FigureCanvas.__init__(self, self.fig)
TimedAnimation.__init__(self, self.fig, interval = 50, blit = True)
return


def new_frame_seq(self):
return iter(range(self.n.size))


def _init_draw(self):
lines = [self.line1, self.line1_tail, self.line1_head]
for l in lines:
l.set_data([], [])
return


def addData(self, value):
self.addedData.append(value)
return


def zoomIn(self, value):
bottom = self.ax1.get_ylim()[0]
top = self.ax1.get_ylim()[1]
bottom += value
top -= value
self.ax1.set_ylim(bottom,top)
self.draw()
return


def _step(self, *args):
# Extends the _step() method for the TimedAnimation class.
try:
TimedAnimation._step(self, *args)
except Exception as e:
self.abc += 1
print(str(self.abc))
TimedAnimation._stop(self)
pass
return


def _draw_frame(self, framedata):
margin = 2
while(len(self.addedData) > 0):
self.y = np.roll(self.y, -1)
self.y[-1] = self.addedData[0]
del(self.addedData[0])


self.line1.set_data(self.n[ 0 : self.n.size - margin ], self.y[ 0 : self.n.size - margin ])
self.line1_tail.set_data(np.append(self.n[-10:-1 - margin], self.n[-1 - margin]), np.append(self.y[-10:-1 - margin], self.y[-1 - margin]))
self.line1_head.set_data(self.n[-1 - margin], self.y[-1 - margin])
self._drawn_artists = [self.line1, self.line1_tail, self.line1_head]
return


''' End Class '''




# You need to setup a signal slot mechanism, to
# send data to your GUI in a thread-safe way.
# Believe me, if you don't do this right, things
# go very very wrong..
class Communicate(QObject):
data_signal = pyqtSignal(float)


''' End Class '''






def dataSendLoop(addData_callbackFunc):
# Setup the signal-slot mechanism.
mySrc = Communicate()
mySrc.data_signal.connect(addData_callbackFunc)


# Simulate some data
n = np.linspace(0, 499, 500)
y = 50 + 25*(np.sin(n / 8.3)) + 10*(np.sin(n / 7.5)) - 5*(np.sin(n / 1.5))
i = 0


while(True):
if(i > 499):
i = 0
time.sleep(0.1)
mySrc.data_signal.emit(y[i]) # <- Here you emit a signal!
i += 1
###
###


if __name__== '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Plastique'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())

试试吧。在一个新的python文件中复制粘贴这段代码,并运行它。你应该得到一个漂亮的,平滑的移动图形:

enter image description here

顶部(和许多其他)答案是建立在plt.pause()之上的,但这是在matplotlib中动画情节的旧方法。它不仅很慢,而且还会导致在每次更新时捕获焦点(我很难停止绘图python进程)。

你可能想要使用matplotlib.animation (正如文档中提到的)。

在深入研究了各种答案和代码片段后,事实证明,这是一种平滑的方法,可以无限地绘制传入数据。

下面是快速启动的代码。它每200ms无限地用[0,100]中的随机数绘制当前时间,同时还处理视图的自动缩放:

from datetime import datetime
from matplotlib import pyplot
from matplotlib.animation import FuncAnimation
from random import randrange


x_data, y_data = [], []


figure = pyplot.figure()
line, = pyplot.plot_date(x_data, y_data, '-')


def update(frame):
x_data.append(datetime.now())
y_data.append(randrange(0, 100))
line.set_data(x_data, y_data)
figure.gca().relim()
figure.gca().autoscale_view()
return line,


animation = FuncAnimation(figure, update, interval=200)


pyplot.show()

你也可以探索blit以获得更好的性能。

一个来自blit文档的例子:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = plt.plot([], [], 'ro')


def init():
ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1, 1)
return ln,


def update(frame):
xdata.append(frame)
ydata.append(np.sin(frame))
ln.set_data(xdata, ydata)
return ln,


ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
init_func=init, blit=True)
plt.show()

另一个选项是使用散景。在我看来,至少对于实时情节来说,这是一个不错的选择。下面是问题中代码的散景版本:

from bokeh.plotting import curdoc, figure
import random
import time


def update():
global i
temp_y = random.random()
r.data_source.stream({'x': [i], 'y': [temp_y]})
i += 1


i = 0
p = figure()
r = p.circle([], [])
curdoc().add_root(p)
curdoc().add_periodic_callback(update, 100)

对于运行它:

pip3 install bokeh
bokeh serve --show test.py

Bokeh通过websocket通信在web浏览器中显示结果。当数据由远程无头服务器进程生成时,它尤其有用。

bokeh sample plot

实时绘制CPU使用情况的示例用例。

import time
import psutil
import matplotlib.pyplot as plt


fig = plt.figure()
ax = fig.add_subplot(111)


i = 0
x, y = [], []


while True:
x.append(i)
y.append(psutil.cpu_percent())


ax.plot(x, y, color='b')


fig.canvas.draw()


ax.set_xlim(left=max(0, i - 50), right=i + 50)
fig.show()
plt.pause(0.05)
i += 1

这是使用while循环绘制动态实时matplot动画的正确方法

也有一篇关于这方面的中型文章:

PIP安装赛璐珞#这将捕获图像/动画

import matplotlib.pyplot as plt
import numpy as np
from celluloid import Camera # getting the camera
import matplotlib.animation as animation
from IPython import display
import time
from IPython.display import HTML


import warnings
%matplotlib notebook
warnings.filterwarnings('ignore')
warnings.simplefilter('ignore')


fig = plt.figure() #Empty fig object
ax = fig.add_subplot() #Empty axis object
camera = Camera(fig) # Camera object to capture the snap


def f(x):
''' function to create a sine wave'''
return np.sin(x) + np.random.normal(scale=0.1, size=len(x))


l = []


while True:
value = np.random.randint(9) #random number generator
l.append(value) # appneds each time number is generated
X = np.linspace(10, len(l)) # creates a line space for x axis, Equal to the length of l


for i in range(10): #plots 10 such lines
plt.plot(X, f(X))


fig.show() #shows the figure object
fig.canvas.draw()
camera.snap() # camera object to capture teh animation
time.sleep(1)

以及用于储蓄等:

animation = camera.animate(interval = 200, repeat = True, repeat_delay = 500)
HTML(animation.to_html5_video())
animation.save('abc.mp4') # to save


输出是:

enter image description here

带有保留线条风格的圆形缓冲区的活动情节:

import os
import time
import psutil
import collections


import matplotlib.pyplot as plt


pts_n = 100
x = collections.deque(maxlen=pts_n)
y = collections.deque(maxlen=pts_n)
(line, ) = plt.plot(x, y, linestyle="--")


my_process = psutil.Process(os.getpid())
t_start = time.time()
while True:
x.append(time.time() - t_start)
y.append(my_process.cpu_percent())


line.set_xdata(x)
line.set_ydata(y)
plt.gca().relim()
plt.gca().autoscale_view()
plt.pause(0.1)