Python 3.x 在运行Jessie和Update的Raspberry Pi 2 Model B上使用python 3显示图像流时,缓存溢出

Python 3.x 在运行Jessie和Update的Raspberry Pi 2 Model B上使用python 3显示图像流时,缓存溢出,python-3.x,tkinter,raspberry-pi2,Python 3.x,Tkinter,Raspberry Pi2,我对python非常陌生。我正在编写一段“概念验证”代码;用PiCamera在运行Jessie的覆盆子Pi上 我的代码基于以下教程代码: 点击按钮显示图像后,代码将启动PiCamera并开始获取连续捕获,将其传递到流,并对其应用十字光标 它工作得很好…但在两分钟多一点后,磁盘驱动器亮起,开始急剧减速。一旦程序中断,一切都会好起来。我看了一些日志,但我一辈子都找不到什么缓存溢出或者为什么。我尝试了许多不同的方法,并试图将它们作为评论保留下来。我怀疑这与必须清除tkinter中的图像有关,但即使这样

我对python非常陌生。我正在编写一段“概念验证”代码;用PiCamera在运行Jessie的覆盆子Pi上

我的代码基于以下教程代码:

点击按钮显示图像后,代码将启动PiCamera并开始获取连续捕获,将其传递到流,并对其应用十字光标

它工作得很好…但在两分钟多一点后,磁盘驱动器亮起,开始急剧减速。一旦程序中断,一切都会好起来。我看了一些日志,但我一辈子都找不到什么缓存溢出或者为什么。我尝试了许多不同的方法,并试图将它们作为评论保留下来。我怀疑这与必须清除tkinter中的图像有关,但即使这样也似乎不起作用,使视频闪烁不均匀

任何帮助都会很好!我已经开始使用opencv进行探索。还在安装

谢谢

守则:

# Simple enough, just import everything from tkinter.
from tkinter import *
import picamera
import picamera.array
import time
import threading
import io
import numpy as np

from PIL import Image, ImageTk


# Here, we are creating our class, Window, and inheriting from the Frame
# class. Frame is a class from the tkinter module. (see Lib/tkinter/__init__)
class Window(Frame):

    # Create an array representing a 1280x720 image of
    # a cross through the center of the display. The shape of
    # the array must be of the form (height, width, color)


    # Define settings upon initialization. Here you can specify
    def __init__(self, master=None):

        # parameters that you want to send through the Frame class. 
        Frame.__init__(self, master)   

        #reference to the master widget, which is the tk window                 
        self.master = master

        #with that, we want to then run init_window, which doesn't yet exist
        self.init_window()

    #Creation of init_window
    def init_window(self):

        # changing the title of our master widget      
        self.master.title("GUI")

        # allowing the widget to take the full space of the root window
        self.pack(fill=BOTH, expand=1)

        # creating a menu instance
        menu = Menu(self.master)
        self.master.config(menu=menu)

        # create the file object)
        file = Menu(menu)

        # adds a command to the menu option, calling it exit, and the
        # command it runs on event is client_exit
        file.add_command(label="Exit", command=self.client_exit)

        #added "file" to our menu
        menu.add_cascade(label="File", menu=file)


        # create the file object)
        edit = Menu(menu)

        # adds a command to the menu option, calling it exit, and the
        # command it runs on event is client_exit
        edit.add_command(label="Show Img", command=self.showImg)
        edit.add_command(label="Show Text", command=self.showText)

        #added "file" to our menu
        menu.add_cascade(label="Edit", menu=edit)

        self.trim_running_bool = False

    def showImg(self):
        self.trim_running_bool = True
        trim_thrd_thread = threading.Thread(target=self._cam_thread_def)
        trim_thrd_thread.start()
        self.update_idletasks()


    def _cam_thread_def(self):

        img_stream = io.BytesIO()
        frame_count = 0

        with picamera.PiCamera() as camera:
            camera.resolution = (400, 300)

##            while True:   ### tried it this way too
            for xxx in range(0,900):
                img_stream = io.BytesIO()
                frame_count = frame_count + 1
                print(frame_count,"   ", xxx)
                if self.trim_running_bool == False:
                    print("break")
                    break
                camera.capture(img_stream, 'jpeg', use_video_port=True)
                img_stream.seek(0)
                img_load = Image.open(img_stream)



                for xl_line in range(0,196,4):
                    img_load.putpixel((xl_line, 149), (xl_line, 0, 0))
                    xll=xl_line+2
                    img_load.putpixel((xl_line, 150), (xl_line, xl_line, xl_line))
                    img_load.putpixel((xl_line, 151), (xl_line, 0, 0))
                    (xl_line)

                for xr_line in range(208,400,4):
                    clr = 400 - xr_line
                    img_load.putpixel((xr_line, 149), (clr, 0, 0))
                    img_load.putpixel((xr_line, 150), (clr, clr, clr))
                    img_load.putpixel((xr_line, 151), (clr, 0, 0))
                    (xr_line)

                for yt_line in range(0,146,4):
                    clrt = int(yt_line * 1.7)
                    img_load.putpixel((199, yt_line), (clrt, 0, 0))
                    img_load.putpixel((200, yt_line), (clrt, clrt,  clrt))
                    img_load.putpixel((201, yt_line), (clrt, 0, 0))
                    (yt_line)

                for yb_line in range(158,300,4):
                    clrb = int((300 - yb_line) * 1.7)
                    img_load.putpixel((199, yb_line), (clrb, 0, 0))
                    img_load.putpixel((200, yb_line), (clrb, clrb, clrb))
                    img_load.putpixel((201, yb_line), (clrb, 0, 0))
                    (yb_line)


                img_render = ImageTk.PhotoImage(img_load)

                # labels can be text or images
                img = Label(self, image=img_render)
                img.image = img_render
                img.place(x=0, y=0)
                self.update_idletasks()
                img_stream.seek(0)
                img_stream.truncate(0)

                # tried these:
##                img_stream.flush()
##                print("flushed ", img_stream)
##                print("2nd ",img_stream)
##                del img_load

##
##            
##            rawCapture.truncate(0)
##            

##            rawCapture.seek(0)
##            rawCapture.truncate(0)

##            del render
##            img.image = None
##            foregnd_image = None

                (xxx)
            pass



    def showText(self):
        text = Label(self, text="Hey there good lookin!")
        text.pack()


    def client_exit(self):
        self.trim_running_bool = False
        exit()


# root window created. Here, that would be the only window, but
# you can later have windows within windows.
root = Tk()

root.geometry("400x300")

#creation of an instance
app = Window(root)


#mainloop 
root.mainloop()  

每次通过循环创建一个新图像对象和一个新标签,以及一些其他对象。这是内存泄漏,因为您从未破坏旧图像或旧标签

一般来说,您应该只创建一个标签,然后在循环中每次都使用
the_label.configure(image=the_image)
。这样,您就不需要创建新标签或调用
place

更好的是,由于标签会在相关图像更改时自动更新,因此只需更改图像对象本身中的位,标签就会自动更新

最简单的解决方案是将图像创建移动到一个函数,以便您正在创建的所有对象都是本地对象,当函数返回时,这些对象可以被自动垃圾收集

第一步是在主线程中创建单个标签和单个图像:

class Window(Frame):
    def __init__(self, master=None):
        ...
        self.image = PhotoImage(width=400, height=300)
        self.label = Label(self, image=self.image)
        ...
接下来,创建一个函数,将新数据复制到图像中。不幸的是,tkinter实现的
copy
方法不支持底层图像对象的全部功能。此处介绍了一种变通方法:

注意:变通方法示例是一个通用变通方法,它使用的参数比我们需要的多。在下面的示例中,我们可以省略许多参数。下面是底层tk photo对象
copy
方法的文档:

实现看起来像这样(我猜,我没有一个好的方法来测试它):

最后,更新图像的循环会简单得多:

while True:
    self.new_image()
    # presumeably there's some sort of sleep here so you're
    # not updating the image faster than the camera can 
    # capture it.
我不知道新图像的运行速度有多快。如果它能在200毫秒或更短的时间内运行,你甚至不需要线程。相反,您可以使用
after
定期运行该函数



注意:我已经很长时间没有使用Tkinter照片图像了,我也没有好的方法来测试这一点。将此作为指导,而不是最终解决方案

我无法运行它并测试它,但可能您遇到了问题,因为在
for
循环中,您多次创建
ByteIO
Image
ImageTk
Label
而不是使用
ByteIO
Image
ImageTk
Label
的单个实例。感谢您的关注。我想我在那个地方试过了,有没有试过。我一直在尝试一些东西,所以我不确定我在这一点上尝试了什么。我也试过用“while True”这个词,还是一样的问题。谢谢!我很快就会测试这个。。我开始怀疑情况是否如此。谢谢你花时间。就像通常看起来的那样,解决方案优雅而简单,但不知何故愚蠢地难以捉摸。您让我走上了正确的轨道,这表明我在重复创建标签对象。我所要做的就是在“while True”之前创建标签,然后对名称进行一些按摩,它运行了45分钟以上,没有任何问题。谢谢你的帮助。
while True:
    self.new_image()
    # presumeably there's some sort of sleep here so you're
    # not updating the image faster than the camera can 
    # capture it.