Kivy应用内存占满后图片无法显示(仅显示黑框)且内存未释放
Hey there! Let's dig into this slideshow issue you're facing on your Raspberry Pi. The black frames after the 7th image (and worse behavior with larger images) absolutely points to memory management problems—Kivy's default image handling can be greedy on resource-limited devices like the Pi, especially if you're not cleaning up old resources.
First: Verify the Memory Leak
Before jumping to fixes, let's confirm that memory is indeed the culprit. Here are two simple ways to track it:
1. Use psutil for in-code monitoring
Install the psutil library to track your app's memory usage in real-time:
pip install psutil
Add this snippet to your test code to log memory every second:
import psutil import os from kivy.clock import Clock def monitor_memory(self, dt): process = psutil.Process(os.getpid()) print(f"Memory Used: {process.memory_info().rss / 1024 / 1024:.2f} MB") # In your app's build method, schedule the monitor: Clock.schedule_interval(self.monitor_memory, 1)
If you see memory climbing steadily with each image and never dropping, you've got a leak on your hands.
2. System-level monitoring
Open a terminal and run htop (install it with sudo apt install htop if needed). Watch the Python process's memory column as your slideshow runs—if it keeps growing until it hits a wall, that's your smoking gun.
The Root Cause: Kivy's Image Cache & Unreleased Textures
By default, Kivy caches every image texture you load to speed up future access. On the Pi, this cache can quickly eat up available RAM, especially with large images. Additionally, if you're creating new Image widgets each time or not explicitly releasing old textures, those resources stay in memory forever.
Fixes to Get Your Slideshow Running Smoothly
Let's implement these changes step by step:
1. Explicitly Release Old Textures
Before loading a new image, make sure to free up the memory used by the previous one. Add this code right before loading your next image:
if self.image_widget.texture: self.image_widget.texture.release() # Free the GPU/CPU memory self.image_widget.texture = None # Clear the reference
This tells Kivy you're done with that texture, so it can garbage collect it.
2. Scale Images to Screen Size
There's no need to load a full-resolution image if your Pi's touchscreen is smaller (e.g., 800x480). Resize images before loading them to cut down memory usage drastically:
from kivy.core.image import CoreImage def load_scaled_image(self, img_path): # Load the image with CoreImage ci = CoreImage(img_path) # Get screen dimensions screen_w, screen_h = self.root.width, self.root.height # Calculate scaling factor to fit the screen (maintain aspect ratio) scale_w = screen_w / ci.texture.width scale_h = screen_h / ci.texture.height scale = min(scale_w, scale_h) # Scale the texture scaled_texture = ci.texture.get_region(0, 0, ci.texture.width, ci.texture.height) scaled_texture.scale(scale, scale) return scaled_texture
Use this function to load your images instead of letting Kivy handle it directly.
3. Disable Unnecessary Image Caching
If you don't need Kivy's cache at all (since you're only showing each image once), you can disable it globally:
from kivy.config import Config Config.set('graphics', 'cache_size', 0)
Put this at the very top of your script, before importing any other Kivy modules.
4. Optimized Test Code Example
Here's a revised version of your test script incorporating all these fixes:
from kivy.app import App from kivy.uix.image import Image from kivy.clock import Clock from kivy.core.image import CoreImage import psutil import os from kivy.config import Config # Disable image cache Config.set('graphics', 'cache_size', 0) class SlideshowTestApp(App): def build(self): self.image_widget = Image() self.image_paths = [f"test_img_{i}.jpg" for i in range(10)] self.current_idx = 0 # Start memory monitoring Clock.schedule_interval(self.monitor_memory, 1) # Load first image immediately Clock.schedule_once(self.load_next_image, 0) return self.image_widget def load_next_image(self, dt): if self.current_idx >= len(self.image_paths): self.current_idx = 0 # Release previous texture if self.image_widget.texture: self.image_widget.texture.release() self.image_widget.texture = None # Load and scale new image img_path = self.image_paths[self.current_idx] scaled_texture = self.load_scaled_image(img_path) self.image_widget.texture = scaled_texture self.current_idx += 1 # Schedule next image in 3 seconds Clock.schedule_once(self.load_next_image, 3) def load_scaled_image(self, img_path): ci = CoreImage(img_path) screen_w, screen_h = self.root.width, self.root.height scale_w = screen_w / ci.texture.width scale_h = screen_h / ci.texture.height scale = min(scale_w, scale_h) texture = ci.texture.get_region(0, 0, ci.texture.width, ci.texture.height) texture.scale(scale, scale) return texture def monitor_memory(self, dt): process = psutil.Process(os.getpid()) print(f"Current Memory: {process.memory_info().rss / 1024 / 1024:.2f} MB") if __name__ == "__main__": SlideshowTestApp().run()
Extra Pi-Specific Optimizations
- Use the SDL2 Backend: Some Pi users report better memory management with the SDL2 OpenGL backend. Add this to the top of your script:
os.environ['KIVY_GL_BACKEND'] = 'sdl2' - Close Unnecessary Background Apps: Free up RAM by closing any unused processes on your Pi (like browsers, file managers, etc.).
- Overclock (Carefully): If you're comfortable, a small CPU/GPU overclock can help with image processing, but only do this if you have proper cooling.
With these changes, your slideshow should no longer hit memory limits, and those black frames should disappear. Let me know if you run into any other snags!
内容的提问来源于stack exchange,提问作者Victor Van Hee

