锯齿状的tkinter主循环帧时长是什么?

12

尝试使用tkinter动画一系列PIL图像。我的帧延迟图表(以毫秒为单位)看起来像这样:

sawtooth frame duration

有人知道是什么原因导致这种锯齿状的模式吗?

以下是用于复现的脚本:

from PIL import Image, ImageTk
import Tkinter

import time
import sys

def generate_frames(n):
    """
    keep n under 101 * 101
    """
    out = []
    last_pil = None
    for i in range(n):
        if last_pil:
            pil_image = last_pil.copy()
        else:
            pil_image = Image.new('L', (101, 101), 255)   
        x = i / 101
        y = i % 101
        pil_image.load()[x, y] = 0
        out.append(ImageTk.PhotoImage(pil_image))
        last_pil = pil_image

    return out

def draw():
    FRAME_COUNT =5000

    master = Tkinter.Tk()

    w = Tkinter.Canvas(master, width=302, height=302)
    w.create_rectangle(49, 49, 252, 252)
    w.pack()

    frames = generate_frames(FRAME_COUNT)

    def draw_frame(f, canvas_image):
        print repr(time.time())
        frame = frames[f]
        if canvas_image is None:
            canvas_image = w.create_image((151, 151), image=frame, anchor='center')
        else:
            w.itemconfigure(canvas_image, image=frame)

        w.current_frame = frame  # save a reference
        next_frame = f + 1
        if next_frame < FRAME_COUNT:
            master.after(1, draw_frame, next_frame, canvas_image)
        else:
            sys.exit(0)

    master.after(10, draw_frame, 0, None)
    master.mainloop()


draw()

要查看图形,请通过管道输出

import sys

last = None
for line in sys.stdin:
    value = float(line.strip()) * 1000
    if last is None:
        pass
    else:
        print (value - last)
    last = value

然后通过

from matplotlib import pyplot
import sys

X = []
Y = []

for index, line in enumerate(sys.stdin):
    line = line.strip()
    X.append(index)
    Y.append(float(line))

pyplot.plot(X, Y, '-')
pyplot.show()

多线程并不能改善这个问题:

在此处输入图片描述

class AnimationThread(threading.Thread):

    FRAME_COUNT = 5000

    def __init__(self, canvas):
        threading.Thread.__init__(self)
        self.canvas = canvas
        self.frames = generate_frames(self.FRAME_COUNT)

    def run(self):
        w = self.canvas
        frames = self.frames
        canvas_image = None
        for i in range(self.FRAME_COUNT):
            print repr(time.time())
            frame = frames[i]
            if canvas_image is None:
                canvas_image = w.create_image((151, 151), image=frame, anchor='center')
            else:
                w.itemconfigure(canvas_image, image=frame)
            w.current_frame = frame
            time.sleep(1 * .001)

def draw_threaded():
    FRAME_COUNT = 5000
    master = Tkinter.Tk()

    w = Tkinter.Canvas(master, width=302, height=302)
    w.create_rectangle(49, 49, 252, 252)
    w.pack()

    animation_thread = AnimationThread(w)
    animation_thread.start()

    master.mainloop()

    animation_thread.join()

draw_threaded()

我会尝试在单独的线程中运行动画,而不是在tkinter主循环中运行,看看会发生什么。 - Joel Cornett
我没有得到相同的行为,参见http://i.imgur.com/O1p3w.png和http://i.imgur.com/i91t5.png(第二个使用`after` 20毫秒)。你在这里非常激进,对于Tk的这种通用实现来说,每1毫秒都要完成这样的巨大任务。因此,我认为这完全是在Tcl/Tk领域处理,而与Python无关。此外,众所周知,使用itemconfigure与图像是缓慢的。例如,我将代码更改为删除并重新创建图像,在绘图中没有太多变化(尽管有人可能会正确地期望性能更差)。 - mmgp
2
你是否考虑过改变动画循环执行帧的方式?与其使用“执行需要可变时间的操作 => 等待固定时间”,不如尝试使用“异步执行操作 => 如果时间合适则继续,否则跳过”。 - Joel Cornett
1
这很可能是由于垃圾回收周期 - 通常会出现这种模式。不过我不确定你能做什么来帮助它。 - Wayne Werner
我在使用 wx.glcanvas.GLCanvas 以高帧率绘制时遇到了类似的问题。你的显示适配器是否设置为垂直刷新同步? - ali_m
显示剩余3条评论
1个回答

2
这与当60Hz和50Hz的样本混合时产生的干扰图案非常相似:

Interference Pattern of competing 60 Hz and 50 Hz samples

(原始的 Wolfram|Alpha 图表)

这很可能是由于两个东西具有不同(但接近)的刷新率所致。当您尝试拍摄电视屏幕并且看起来像黑色条纹一直在图像中移动,或者汽车轮子在汽车广告中向后旋转时,就会发生类似的事情。这本质上是莫尔条纹效应的延伸。

我不知道它是否由视频驱动程序和/或硬件引起,但它几乎肯定是由相互干扰的周期性模式引起的。它看起来非常像应该是 GC 循环与您的for循环相互干扰(因此随着内存被释放并可以分配,锯齿状波形突然下降)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接