在 Raspberry Pi 2 Model B 运行 Jessie 上使用 python 3 显示图像流时缓存溢出

Cache overflowing while showing an image stream using python 3 on a Raspberry Pi 2 Model B running Jessie and up to date

我对 python 很陌生。我正在研究一段“概念验证”代码;在 Raspberry Pi 运行 Jessie.

上使用 PiCamera

我的代码基于以下教程代码:https://pythonprogramming.net/tkinter-adding-text-images/

点击按钮显示图像后,代码将启动 PiCamera 并开始获取 capture_continuous,将其传递到流,对其应用十字准线。

大部分情况下运行良好……但两分多钟后,磁盘驱动器亮起并开始急剧变慢。一旦我让程序中断,一切都很好。我查看了一些日志,但我终其一生都无法找出缓存溢出的原因或原因。我尝试了很多不同的方式,并试图将它们作为评论保留下来。我怀疑这与必须在 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

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

最简单的解决方案是将图像创建移动到一个函数中,这样您正在创建的所有这些对象都是本地对象,可以在函数 returns 时自动进行垃圾回收。

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

class Window(Frame):
    def __init__(self, master=None):
        ...
        self.image = PhotoImage(width=400, height=300)
        self.label = Label(self, image=self.image)
        ...

接下来,创建一个将新数据复制到图像中的函数。不幸的是,tkinter 对 copy 方法的实现不支持底层图像对象的全部功能。此处描述了一种解决方法:http://tkinter.unpythonic.net/wiki/PhotoImage#Copy_a_SubImage.

注意:解决方法示例是一个通用的解决方法,它使用的参数比我们需要的多。在下面的示例中,我们可以省略许多参数。底层 tk 照片对象 copy 方法的文档在这里:http://tcl.tk/man/tcl8.5/TkCmd/photo.htm#M17

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

def new_image(self):
    # all your code to create the new image goes here...
    ...
    img_render = ImageTk.PhotoImage(img_load)

    # copy the new image bits to the existing image object
    self.tk.call(self.image, 'copy', img_render)

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

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.

不知道new_image能运行多快。如果它可以在 200 毫秒或更短的时间内 运行,你甚至不需要线程。相反,您可以使用 after 到 运行 定期运行。


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