使用Tkinter搭配Pi Camera Module 3导致Raspberry Pi 5长时间运行后卡顿
问题分析与解决方案
你的核心问题是Xwayland CPU占用持续攀升,根源在于Tkinter事件循环被阻塞、Canvas图像更新方式低效,以及摄像头配置不够优化,长时间运行后导致Xwayland渲染任务积压。以下是具体问题点和修复方案:
关键问题点
- 不必要的阻塞:
Camera.py的update_frame中使用time.sleep(0.01),会阻塞Tkinter的事件循环,导致Xwayland无法及时处理渲染事件,累积后CPU占用飙升。 - 低效的Canvas更新:每次更新图像时删除旧的Canvas元素再创建新元素,频繁的创建/销毁操作会大幅增加Xwayland的渲染负担。
- 冗余代码:
Camera.__init__中错误的线程初始化代码(直接调用update_frame()而非作为线程目标),不仅没用还会导致方法提前执行。 - 未正确释放摄像头资源:没有显式停止Picamera2的捕获,可能存在隐性资源泄漏。
- 摄像头配置不匹配:使用
create_preview_configuration适合临时预览,长时间视频捕获应使用更高效的create_video_configuration。
修复后的代码
App.py
import tkinter from Camera import Camera class App: def __init__(self, window, window_title, video_name): self.window = window self.window.title(window_title) self.source = Camera(self.window, video_name, 1024, 768) self.source.grid(row=0, column=0, sticky=tkinter.N) self.window.protocol("WM_DELETE_WINDOW", self.delete) self.window.mainloop() def delete(self): print('[App] stopping camera and threads') self.source.stop() print('[App] exit') self.window.destroy() if __name__ == '__main__': App(tkinter.Tk(), "DAC Gas Loader Cameras", 'PI Camera')
Camera.py
import tkinter as tk import PIL.Image, PIL.ImageTk from VideoCapture import * class Camera(tk.Frame): def __init__(self, window, name="", width=400, height=300, fps=24): super().__init__(window) self.window = window self.vid = PicamVideoCapture(width, height) self.canvas = tk.Canvas(self, width=width, height=height) self.canvas.pack() self.fps = fps self.delay = int(1000/self.fps) print('[Camera] source:', 'fps:', self.fps, 'delay:', self.delay) self.photo = None self.imageID = None self.running = True # 启动帧更新 self.update_frame() def update_frame(self): if not self.running: return ret, frame = self.vid.get_frame() if ret: image = PIL.Image.fromarray(frame) self.photo = PIL.ImageTk.PhotoImage(image=image) if self.imageID is None: self.imageID = self.canvas.create_image(0, 0, image=self.photo, anchor='nw') else: # 直接更新已有图像元素,避免删除重建 self.canvas.itemconfig(self.imageID, image=self.photo) # 调度下一次更新 self.window.after(self.delay, self.update_frame) def stop(self): self.running = False self.vid.stop()
VideoCapture.py
import time import os from picamera2 import Picamera2 class PicamVideoCapture: def __init__(self, width=400, height=300): self.vid = Picamera2() # 改用视频捕获配置,比预览配置更高效稳定 self.vid.configure(self.vid.create_video_configuration(main={"size": (width, height)})) self.vid.start() def get_frame(self): return True, self.vid.capture_array() def take_picture(self): os.makedirs(os.path.dirname("./images/"), exist_ok=True) self.vid.capture_file(time.strftime("images/picam-%d-%m-%Y-%H-%M-%S.jpg")) def stop(self): # 显式停止摄像头捕获并释放资源 if self.vid.is_recording(): self.vid.stop() self.vid.close()
额外优化建议
- 降低分辨率或帧率:如果1024x768@24fps不是必需,适当降低可以进一步减轻Xwayland的渲染压力。
- 启用Picamera2的硬件加速:确保系统已开启硬件加速(Raspberry Pi OS默认已开启),让摄像头捕获和图像转换更高效。
- 定期清理:虽然修复后已解决主要问题,但可以考虑每小时重启一次帧更新循环(如果允许),避免极端情况下的隐性资源累积。
内容的提问来源于stack exchange,提问作者Alex StAubin
相关产品推荐
相关产品推荐

