在 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 照片图像了,我也没有很好的方法来测试它。将其用作指南,而不是最终的解决方案。
我对 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 照片图像了,我也没有很好的方法来测试它。将其用作指南,而不是最终的解决方案。