Python Raspberry Pi(型号B,版本2.0)上的数字相框程序内存不足问题

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分钟,但这没有帮助 我的问题是,我是否在代码中做错事/

我正在开发一个Python程序,用于在Raspberry Pi(型号B修订版2.0,内存512MB)上显示照片。它使用Tk来显示图像。 程序基本完成了,但我遇到了一个问题,由于内存不足,程序被内核终止。这似乎是随机发生的

我不明白为什么会这样。我注意到,在图像切换过程中,CPU的峰值显著增加(高达90%)。因此,我认为这可能是一个CPU无法跟上两个映像之间的速度,然后落在后面并耗尽内存的问题。为了测试这一点,我将显示图像之间的超时时间增加到1分钟,但这没有帮助

我的问题是,我是否在代码中做错事/效率低下(见下文)?如果没有:我正在考虑切换到PyQt,因为它似乎可以加速OpenGL的图形(从我所读到的)。这是真的吗?或者你认为这有助于解决我面临的问题吗

这是我当前的Python代码:

# 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的内存不足使用量输出。第一次尝试:只需添加更多交换空间(请参阅)。第一次尝试:只需添加更多交换空间(请参阅)。