【问题标题】:Matplotlib equivalent of pygame flipMatplotlib 相当于 pygame 翻转
【发布时间】:2018-05-17 19:23:20
【问题描述】:

我有一个可以在 pygame 下完美运行的快速动画程序,出于技术原因,我只需要使用 matplotlib 或其他广泛使用的模块来做同样的事情。

程序结构大致如下:

pygame.init()        
SURF = pygame.display.set_mode((500, 500))
arr = pygame.surfarray.pixels2d(SURF) # a view for numpy, as a 2D array
while ok:
    # modify some pixels of arr
    pygame.display.flip()
pygame.quit()

我没有低级 matplotlib 经验,但我认为可以用 matplotlib 做类似的事情。换句话说:

如何分享图形的位图,修改部分像素,刷新屏幕?

这是一个最小的工作示例,它在我的计算机上每秒翻转 250 帧(超过屏幕......):

import pygame,numpy,time
pygame.init()
size=(400,400)        
SURF = pygame.display.set_mode(size)
arr = pygame.surfarray.pixels2d(SURF) # buffer pour numpy   
t0=time.clock()

for counter in range(1000):
        arr[:]=numpy.random.randint(0,0xfffff,size)
        pygame.display.flip()      
pygame.quit()

print(counter/(time.clock()-t0))

编辑

我在答案中的指示尝试:

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

fig = plt.figure()


def f(x, y):
    return np.sin(x) + np.cos(y)

x = np.linspace(0, 2 * np.pi, 400)
y = np.linspace(0, 2 * np.pi, 400).reshape(-1, 1)

im = plt.imshow(f(x, y), animated=True)

count=0
t0=time.clock()+1
def updatefig(*args):
    global x, y,count,t0
    x += np.pi / 15.
    y += np.pi / 20.
    im.set_array(f(x, y))
    if time.clock()<t0:
        count+=1
    else:
        print (count)
        count=0
        t0=time.clock()+1     
    return im,

ani = animation.FuncAnimation(fig, updatefig, interval=50, blit=True)
plt.show()

但这仅提供 20 fps....

【问题讨论】:

  • 也许this 对你有帮助。
  • 这不涉及编辑画布位图,但为了实时显示画布,答案是plt.draw(); plt.pause(0.000001)。其中plt 来自from matplotlib import pyplot as plt。可能与您的问题相关的帖子是:stackoverflow.com/questions/40126176/…
  • Matplotlib 真的不是为高性能动画设计的。 “我只需要使用 matplotlib 或其他广泛使用的模块来做同样的事情。” 您能否更准确地说明您的要求?你认为什么是“广泛的模块”?您是否针对特定平台?
  • 您将帧速率限制为每帧延迟 50 毫秒 (interval=50)。也许减少它就足以获得您想要的性能...顺便说一句,time.clock() 作为 unix 上的挂钟并不真正可靠,time.time()timeit.default_timer 可能是更好的选择,或者最近的time.perf_counter()蟒蛇

标签: python numpy matplotlib pygame


【解决方案1】:

应该注意的是,人脑能够“看到”高达 ~25 fps 的帧速率。更快的更新实际上并没有得到解决。

Matplotlib

使用 matplotlib 及其 animation 模块,问题中的示例在我的计算机上以 84 fps 的速度运行。

import time
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

fig, ax = plt.subplots()


def f(x, y):
    return np.sin(x) + np.cos(y)

x = np.linspace(0, 2 * np.pi, 400)
y = np.linspace(0, 2 * np.pi, 400).reshape(-1, 1)

im = ax.imshow(f(x, y), animated=True)
text = ax.text(200,200, "")

class FPS():
    def __init__(self, avg=10):
        self.fps = np.empty(avg)
        self.t0 = time.clock()
    def tick(self):
        t = time.clock()
        self.fps[1:] = self.fps[:-1]
        self.fps[0] = 1./(t-self.t0)
        self.t0 = t
        return self.fps.mean()

fps = FPS(100)

def updatefig(i):
    global x, y
    x += np.pi / 15.
    y += np.pi / 20.
    im.set_array(f(x, y))
    tx = 'Mean Frame Rate:\n {fps:.3f}FPS'.format(fps= fps.tick() ) 
    text.set_text(tx)     
    return im, text,

ani = animation.FuncAnimation(fig, updatefig, interval=1, blit=True)
plt.show()

PyQtGraph

在 pyqtgraph 中获得了更高的帧率,它会在我的计算机上以 295 fps 的速度运行。

import sys
import time
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import pyqtgraph as pg

class FPS():
    def __init__(self, avg=10):
        self.fps = np.empty(avg)
        self.t0 = time.clock()
    def tick(self):
        t = time.clock()
        self.fps[1:] = self.fps[:-1]
        self.fps[0] = 1./(t-self.t0)
        self.t0 = t
        return self.fps.mean()

fps = FPS(100)

class App(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(App, self).__init__(parent)

        #### Create Gui Elements ###########
        self.mainbox = QtGui.QWidget()
        self.setCentralWidget(self.mainbox)
        self.mainbox.setLayout(QtGui.QVBoxLayout())

        self.canvas = pg.GraphicsLayoutWidget()
        self.mainbox.layout().addWidget(self.canvas)

        self.label = QtGui.QLabel()
        self.mainbox.layout().addWidget(self.label)

        self.view = self.canvas.addViewBox()
        self.view.setAspectLocked(True)
        self.view.setRange(QtCore.QRectF(0,0, 100, 100))

        #  image plot
        self.img = pg.ImageItem(border='w')
        self.view.addItem(self.img)

        #### Set Data  #####################
        self.x = np.linspace(0, 2 * np.pi, 400)
        self.y = np.linspace(0, 2 * np.pi, 400).reshape(-1, 1)

        #### Start  #####################
        self._update()
        
    def f(self, x, y):
            return np.sin(x) + np.cos(y)
        
    def _update(self):

        self.x += np.pi / 15.
        self.y += np.pi / 20.
        self.img.setImage(self.f(self.x, self.y))

        tx = 'Mean Frame Rate:\n {fps:.3f}FPS'.format(fps= fps.tick() ) 
        self.label.setText(tx)
        QtCore.QTimer.singleShot(1, self._update)


if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)
    thisapp = App()
    thisapp.show()
    sys.exit(app.exec_())

【讨论】:

  • 感谢您的帖子。当然 25 fps 就足够了,但是对于复杂的实时动画,构建每个图像都需要时间,“视频”方面一定不能成为瓶颈。
  • 这就是我的观点。只要您的动画运行速度超过 25 fps,“视频”就不是瓶颈。
【解决方案2】:

如果您想为绘图设置动画,那么您可以查看 matplotlib 中 matplotlib.animation.Animation 下的动画功能。这是一个很棒的教程 - https://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial

如果您只想定期更新临时位图,我不确定 matplotlib 是否适用于您想要实现的目标。来自 matplotlib 文档:

Matplotlib 是一个 Python 2D 绘图库,它以各种硬拷贝格式和跨平台的交互式环境生成出版质量图形。

如果您想定期更新屏幕上的临时图像,您可能需要查看 Python 的 GUI 库。以下是可用选项的简短摘要 - https://docs.python.org/3/faq/gui.html。 Tkinter 是一个非常标准的工具,并且随 python 一起提供。您可以使用 pillow 中的 ImageTk 模块来创建/修改图像以通过 Tkinter - http://pillow.readthedocs.io/en/4.2.x/reference/ImageTk.html 显示。

【讨论】:

  • 感谢您的回答。但是 ImageTk 文档说“如果显示照片图像,这可能会非常慢”。那是我的问题....
【解决方案3】:

如果您只需要为matplotlib 画布制作动画,那么动画框架就是答案。有一个简单的示例 here 基本上可以满足您的要求。

如果这将成为更复杂应用程序的一部分,您可能希望更好地控制特定后端。

这是基于this matplotlib example 松散地使用Qt 的快速尝试。

它使用QTimer 进行更新,Qt 中可能还有一些空闲回调,您可以附加到。

import sys

import numpy as np
import matplotlib as mpl
mpl.use('qt5agg')
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt5 import QtWidgets, QtCore

size = (400, 400)

class GameCanvas(FigureCanvas):
    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)

        self.axes = fig.gca()
        self.init_figure()

        FigureCanvas.__init__(self, fig)
        self.setParent(parent)

        timer = QtCore.QTimer(self)
        timer.timeout.connect(self.update_figure)
        timer.start(10)

    def gen_frame(self):
        return np.random.randint(0,0xfffff,size)

    def init_figure(self):
        self.img = self.axes.imshow(self.gen_frame())

    def update_figure(self):
        self.img.set_data(self.gen_frame())
        self.draw()

class ApplicationWindow(QtWidgets.QMainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        self.main_widget = QtWidgets.QWidget(self)

        dc = GameCanvas(self.main_widget, width=5, height=4, dpi=100)
        self.setCentralWidget(dc)

    def fileQuit(self):
        self.close()

    def closeEvent(self, ce):
        self.fileQuit()

app = QtWidgets.QApplication(sys.argv)
appw = ApplicationWindow()
appw.show()
sys.exit(app.exec_())

您应该注意的一点是imshow 在第一帧计算图像标准化。在随后的帧中,它调用set_data,因此标准化保持不变。如果你想更新它,你可以打电话给imshow(可能更慢)。或者您可以在第一个 imshow 调用中使用 vminvmax 手动修复它,并提供正确的标准化帧。

【讨论】:

  • 感谢您的回答,但我的计算机上有 Qt5,它与 Qt4 不兼容。我不想嵌入我的项目,所以我正在寻找具有基本画布操作的解决方案。 FuncAnimation 暂时放慢速度(请参阅我的编辑)。
  • @B.M.查看我的编辑,将其移植到Qt5。您使用interval=50 将帧速率限制为20 fps,该interval=50 设置帧之间的毫秒延迟。 interval=1 我得到 100 fps
  • 顺便说一句,这是为了证明您可以直接使用后端做什么,如果您只是想为画布制作动画,我怀疑您是否可以比 matplotlib 自己的框架更好。 100 fps 对我来说似乎很合理,即使使用 blitting,你几乎在每一帧都重绘了整个窗口。 Matplotlib 旨在生成发布质量的图,而不是性能...
【解决方案4】:

鉴于您谈到了使用广泛使用的模块,这里有一个使用 OpenCV 的概念证明。它在这里运行得非常快,每秒最多生成 250-300 帧。这没什么太花哨的,只是为了表明如果你不使用任何绘图功能matplotlib 不应该真的是你的首选。

import sys                                                                                 
import time                                                                                
import numpy as np                                                                         
import cv2                                                                                 

if sys.version_info >= (3, 3):                                                             
    timer = time.perf_counter                                                              
else:                                                                                      
    timer = time.time                                                                      

def f(x, y):                                                                               
    return np.sin(x) + np.cos(y)                                                           

# ESC, q or Q to quit                                                                      
quitkeys = 27, 81, 113                                                                     
# delay between frames                                                                     
delay = 1                                                                                  
# framerate debug init                                                                     
counter = 0                                                                                
overflow = 1                                                                               
start = timer()                                                                            

x = np.linspace(0, 2 * np.pi, 400)                                                         
y = np.linspace(0, 2 * np.pi, 400).reshape(-1, 1)                                          

while True:                                                                                
    x += np.pi / 15.                                                                       
    y += np.pi / 20.                                                                       

    cv2.imshow("animation", f(x, y))                                                       

    if cv2.waitKey(delay) & 0xFF in quitkeys:                                              
        cv2.destroyAllWindows()                                                            
        break                                                                              

    counter += 1                                                                           
    elapsed = timer() - start                                                              
    if elapsed > overflow:                                                                 
        print("FPS: {:.01f}".format(counter / elapsed))                                    
        counter = 0                                                                        
        start = timer()                                                                                                

【讨论】:

  • 是的,这似乎是一个简单、快速的解决方案。在我的测试中比 pygame 慢 2 倍。可能不共享内存。我一直梦想着这个grall!再次感谢您的贡献。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-30
  • 2015-07-06
  • 2013-05-15
  • 2023-04-08
  • 1970-01-01
相关资源
最近更新 更多