简体   繁体   中英

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

I'm very new to python. I'm working on a 'proof of concept' piece of code; using PiCamera on a Raspberry Pi running Jessie.

I've based my code on a tutorial code from: https://pythonprogramming.net/tkinter-adding-text-images/

Once you hit the button to show the image, the code starts PiCamera and starts to get capture_continuous, passes it to a stream, applies crosshairs to it.

It works mostly well… but after a bit over two minutes, the disk drive lights up and it starts to slow drastically. Once I get the program to break, everything is fine. I've looked at a couple of logs, but I can't for the life of me find out what cache is overflowing or why. I've tried a bunch of different ways and tried to leave those in as comments. I suspected it had something to do with having to clear the image in tkinter, but even that doesn't seem to work and makes the video flash unevenly.

Any help would be great! I've started to explore using opencv instead. Still installing that.

Thanks!

The code:

# 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()  

Each time through your loop you are creating a new image object and a new label, as well as some other objects. That is a memory leak, since you never destroy the old image or old label.

Generally speaking, you should create exactly one label, then use the_label.configure(image=the_image) every time through the loop. With that, you don't need to create new labels or call place on it.

Even better, since a label automatically updates when the associated image changes, you only need to change the the bits that are in the image object itself and the label should update automatically.

The simplest solution is to move image creation to a function so that all of those objects you are creating are local objects that can get automatically garbage collected when the function returns.

The first step is to create a single label and single image in your main thread:

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

Next, create a function that copies new data into the image. Unfortunately, tkinter's implementation of the copy method doesn't support the full power of the underlying image object. A workaround is described here: http://tkinter.unpythonic.net/wiki/PhotoImage#Copy_a_SubImage .

Note: the workaround example is a general purpose workaround that uses more arguments than we need. In the following example we can omit many of the arguments. The documentation for the underlying tk photo object copy method is here: http://tcl.tk/man/tcl8.5/TkCmd/photo.htm#M17

The implementation would look something like this (I'm guessing; I don't have a good way to test it):

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)

Finally, your loop to update the image would be much simpler:

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.

I don't know how fast new_image can run. If it can run in 200ms or less you don't even need threads. Instead, you can use after to run that function periodically.


Note: I haven't worked much with Tkinter photo images in a long time, and I have no good way to test this. Use this as a guide, rather than as a definitive solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM