Python Raspberry Pi(型号B,版本2.0)上的数字相框程序内存不足问题
我正在开发一个Python程序,用于在Raspberry Pi(型号B修订版2.0,内存512MB)上显示照片。它使用Tk来显示图像。 程序基本完成了,但我遇到了一个问题,由于内存不足,程序被内核终止。这似乎是随机发生的 我不明白为什么会这样。我注意到,在图像切换过程中,CPU的峰值显著增加(高达90%)。因此,我认为这可能是一个CPU无法跟上两个映像之间的速度,然后落在后面并耗尽内存的问题。为了测试这一点,我将显示图像之间的超时时间增加到1分钟,但这没有帮助 我的问题是,我是否在代码中做错事/效率低下(见下文)?如果没有:我正在考虑切换到PyQt,因为它似乎可以加速OpenGL的图形(从我所读到的)。这是真的吗?或者你认为这有助于解决我面临的问题吗 这是我当前的Python代码:Python Raspberry Pi(型号B,版本2.0)上的数字相框程序内存不足问题,python,tkinter,raspberry-pi,Python,Tkinter,Raspberry Pi,我正在开发一个Python程序,用于在Raspberry Pi(型号B修订版2.0,内存512MB)上显示照片。它使用Tk来显示图像。 程序基本完成了,但我遇到了一个问题,由于内存不足,程序被内核终止。这似乎是随机发生的 我不明白为什么会这样。我注意到,在图像切换过程中,CPU的峰值显著增加(高达90%)。因此,我认为这可能是一个CPU无法跟上两个映像之间的速度,然后落在后面并耗尽内存的问题。为了测试这一点,我将显示图像之间的超时时间增加到1分钟,但这没有帮助 我的问题是,我是否在代码中做错事/
# From: https://stackoverflow.com/questions/19838972/how-to-update-an-image-on-a-canvas
import os
from pathlib import Path
from tkinter import *
from PIL import Image, ExifTags, ImageTk
import ipdb
class MainWindow():
def __init__(self, main):
self.my_images = []
self._imageDirectory = str(Path.home().joinpath("./Pictures/rpictureframe"))
self.main = main
w, h = main.winfo_screenwidth(), root.winfo_screenheight()
self.w, self.h = w, h
main.attributes("-fullscreen", True) # REF: https://stackoverflow.com/questions/45136287/python-tkinter-toggle-quit-fullscreen-image-with-double-mouse-click
main.focus_set()
self.canvas = Canvas(main, width=w, height=h)
self.canvas.configure(background="black", highlightthickness=0)
self.canvas.pack()
self.firstCall = True
# set first image on canvas
self.image_on_canvas = self.canvas.create_image(w/2, h/2, image = self.getNextImage()) ### replacing getNextImage instead of getNextImageV1 here fails
@property
def imageDirectory(self):
return self._imageDirectory
@imageDirectory.setter
def setImageDirectory(self,imageDirectory):
self._imageDirectory = imageDirectory
def getNextImage(self):
if self.my_images == []:
self.my_images = os.listdir(self.imageDirectory)
currentImagePath = self.imageDirectory + "/" + self.my_images.pop()
self.currentImage = self.readImage(currentImagePath, self.w, self.h)
return self.currentImage
def readImage(self,imagePath,w,h):
pilImage = Image.open(imagePath)
pilImage = self.rotateImage(pilImage)
pilImage = self.resizeImage(pilImage,w,h)
return ImageTk.PhotoImage(pilImage)
def rotateImage(self,image):
# REF: https://stackoverflow.com/a/26928142/653770
try:
for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation]=='Orientation':
break
exif=dict(image._getexif().items())
if exif[orientation] == 3:
image=image.rotate(180, expand=True)
elif exif[orientation] == 6:
image=image.rotate(270, expand=True)
elif exif[orientation] == 8:
image=image.rotate(90, expand=True)
except (AttributeError, KeyError, IndexError):
# cases: image don't have getexif
pass
return image
def resizeImage(self,pilImage,w,h):
imgWidth, imgHeight = pilImage.size
if imgWidth > w or imgHeight > h:
ratio = min(w/imgWidth, h/imgHeight)
imgWidth = int(imgWidth*ratio)
imgHeight = int(imgHeight*ratio)
pilImage = pilImage.resize((imgWidth,imgHeight), Image.ANTIALIAS)
return pilImage
def update_image(self):
# REF: https://stackoverflow.com/questions/7573031/when-i-use-update-with-tkinter-my-label-writes-another-line-instead-of-rewriti/7582458#
self.canvas.itemconfig(self.image_on_canvas, image = self.getNextImage()) ### replacing getNextImage instead of getNextImageV1 here fails
self.main.after(5000, self.update_image)
root = Tk()
app = MainWindow(root)
app.update_image()
root.mainloop()
更新:
在下面,您将找到仍然产生内存不足问题的当前代码
您可以在此处找到dmesg内存不足错误:
此外,这是top
的周期性(每秒)输出:
我已经绘制了top
输出的第6列和第7列(内存使用):
正如您所看到的,内存使用似乎并没有像我预期的内存泄漏那样持续增加
这是我当前的代码:
# From: https://stackoverflow.com/questions/19838972/how-to-update-an-image-on-a-canvas
import glob
from pathlib import Path
from tkinter import *
from PIL import Image, ExifTags, ImageTk
class MainWindow():
def __init__(self, main):
self.my_images = []
self._imageDirectory = str(Path.home().joinpath("Pictures/rpictureframe"))
self.main = main
w, h = main.winfo_screenwidth(), root.winfo_screenheight()
self.w, self.h = w, h
# main.attributes("-fullscreen", True) # REF: https://stackoverflow.com/questions/45136287/python-tkinter-toggle-quit-fullscreen-image-with-double-mouse-click
main.focus_set()
self.canvas = Canvas(main, width=w, height=h)
self.canvas.configure(background="black", highlightthickness=0)
self.canvas.pack()
# set first image on canvas
self.image_on_canvas = self.canvas.create_image(w / 2, h / 2,
image=self.getNextImage()) ### replacing getNextImage instead of getNextImageV1 here fails
@property
def imageDirectory(self):
return self._imageDirectory
@imageDirectory.setter
def setImageDirectory(self, imageDirectory):
self._imageDirectory = imageDirectory
def getNextImage(self):
if self.my_images == []:
# self.my_images = os.listdir(self.imageDirectory)
self.my_images = glob.glob(f"{self.imageDirectory}/*.jpg")
currentImagePath = self.my_images.pop()
self.currentImage = self.readImage(currentImagePath, self.w, self.h)
return self.currentImage
def readImage(self, imagePath, w, h):
with Image.open(imagePath) as pilImage:
pilImage = self.rotateImage(pilImage)
pilImage = self.resizeImage(pilImage, w, h)
return ImageTk.PhotoImage(pilImage)
def rotateImage(self, image):
# REF: https://stackoverflow.com/a/26928142/653770
try:
for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation] == 'Orientation':
break
exif = dict(image._getexif().items())
if exif[orientation] == 3:
image = image.rotate(180, expand=True)
elif exif[orientation] == 6:
image = image.rotate(270, expand=True)
elif exif[orientation] == 8:
image = image.rotate(90, expand=True)
except (AttributeError, KeyError, IndexError):
# cases: image don't have getexif
pass
return image
def resizeImage(self, pilImage, w, h):
imgWidth, imgHeight = pilImage.size
if imgWidth > w or imgHeight > h:
ratio = min(w / imgWidth, h / imgHeight)
imgWidth = int(imgWidth * ratio)
imgHeight = int(imgHeight * ratio)
pilImage = pilImage.resize((imgWidth, imgHeight), Image.ANTIALIAS)
return pilImage
def update_image(self):
# REF: https://stackoverflow.com/questions/7573031/when-i-use-update-with-tkinter-my-label-writes-another-line-instead-of-rewriti/7582458#
self.canvas.itemconfig(self.image_on_canvas,
image=self.getNextImage()) ### replacing getNextImage instead of getNextImageV1 here fails
self.main.after(30000, self.update_image)
root = Tk()
app = MainWindow(root)
app.update_image()
root.mainloop()
我相信当你用PIL打开图像文件而不关闭它们时,内存会泄漏
为了避免它,你必须调用<代码>图像.Cutele[()/代码>,或者更好地考虑使用<<代码> 语法。< /P>
def readImage(self,imagePath,w,h):
with Image.open(imagePath) as pilImage:
pilImage = self.rotateImage(pilImage)
pilImage = self.resizeImage(pilImage,w,h)
return ImageTk.PhotoImage(pilImage)
我相信当你用PIL打开图像文件而不关闭它们时,内存会泄漏
为了避免它,你必须调用<代码>图像.Cutele[()/代码>,或者更好地考虑使用<<代码> 语法。< /P>
def readImage(self,imagePath,w,h):
with Image.open(imagePath) as pilImage:
pilImage = self.rotateImage(pilImage)
pilImage = self.resizeImage(pilImage,w,h)
return ImageTk.PhotoImage(pilImage)
我在我的机器上运行代码,发现了类似的尖峰。 在虚拟机上进行了一些内存调整之后,我有了一个没有交换的系统(打开以使崩溃“更快”)和大约250Mb的可用内存 虽然基本内存使用量约为120Mb,但图像变化在190Mb和200Mb之间(使用文件大小为6,6Mb和5184x3456像素的图像),与绘图类似。 然后我复制了一个更大的(全景)图像(8,1Mb,20707x2406像素)到文件夹中——瞧,机器卡住了 我可以看到进程的内存使用量达到315Mb,系统变得不可用(1分钟后,我“拔掉了VM上的插头”)
因此,我认为您的问题与实际代码无关,而是与您试图加载的图片(以及系统中有限的RAM/交换)有关。也许跳过“旋转”和“调整大小”功能可以缓解您的问题…我在我的机器上运行代码,发现了类似的尖峰。 在虚拟机上进行了一些内存调整之后,我有了一个没有交换的系统(打开以使崩溃“更快”)和大约250Mb的可用内存 虽然基本内存使用量约为120Mb,但图像变化在190Mb和200Mb之间(使用文件大小为6,6Mb和5184x3456像素的图像),与绘图类似。 然后我复制了一个更大的(全景)图像(8,1Mb,20707x2406像素)到文件夹中——瞧,机器卡住了 我可以看到进程的内存使用量达到315Mb,系统变得不可用(1分钟后,我“拔掉了VM上的插头”)
因此,我认为您的问题与实际代码无关,而是与您试图加载的图片(以及系统中有限的RAM/交换)有关。跳过“旋转”和“调整大小”功能可能会缓解您的问题…很抱歉,没有回到这个问题上来或接受。原因是,虽然你的答案似乎是正确的,但不幸的是,它没有解决这个问题。不确定如何继续进行ATM调试…您确定已正确执行我的建议吗?这应该可以修复内存泄漏,除非代码中有另一个内存泄漏。你把全部代码都贴在这里了吗?我已经用我当前的代码更新了我的问题。还添加了top(带plot)的内存使用量输出和dmesg的内存不足使用量输出。很抱歉,没有返回到这里或接受。原因是,虽然你的答案似乎是正确的,但不幸的是,它没有解决这个问题。不确定如何继续进行ATM调试…您确定已正确执行我的建议吗?这应该可以修复内存泄漏,除非代码中有另一个内存泄漏。你把全部代码都贴在这里了吗?我已经用我当前的代码更新了我的问题。还添加了top(带plot)的内存使用量输出和dmesg的内存不足使用量输出。第一次尝试:只需添加更多交换空间(请参阅)。第一次尝试:只需添加更多交换空间(请参阅)。