Keeping Up With Camera Frame Rate in Tkinter GUI

只谈情不闲聊 提交于 2021-01-29 12:19:55

问题


My goal is to display a real time feed from a USB camera in a Tkinter Window. My problem is that I can't seem to update the GUI fast enough to keep up with the frame rate of the camera. I'm interfacing with the camera using the uvclite python wrapper around the libuvc C library. uvclite is a lightweight ctypes wrapper around the underlying C library, so I don't think that piece is my bottleneck. Here is my code:

import tkinter as tk
from PIL import ImageTk, Image
import uvclite
import io
import queue

frame_queue = queue.Queue(maxsize=5)
# frame_queue = queue.LifoQueue(maxsize=5)
user_check = True

def frame_callback(in_frame, user):
    global user_check
    if user_check:
        print("User id: %d" % user)
        user_check = False
    try:
        # Dont block in the callback!
        frame_queue.put(in_frame, block=False)
    except queue.Full:
        print("Dropped frame!")
        pass

def update_img():
    print('getting frame')
    frame = frame_queue.get(block=True, timeout=None)
    img = ImageTk.PhotoImage(Image.open(io.BytesIO(frame.data)))
    panel.configure(image=img)
    panel.image = img
    print("image updated!")
    frame_queue.task_done()
    window.after(1, update_img)


if __name__ == "__main__":

    with uvclite.UVCContext() as context:
        cap_dev = context.find_device()
        cap_dev.set_callback(frame_callback, 12345)
        cap_dev.open()
        cap_dev.start_streaming()

        window = tk.Tk()
        window.title("Join")
        window.geometry("300x300")
        window.configure(background="grey")

        frame = frame_queue.get(block=True, timeout=None)
        # Creates a Tkinter-compatible photo image, which can be used everywhere Tkinter expects an image object.
        img = ImageTk.PhotoImage(Image.open(io.BytesIO(frame.data)))
        panel = tk.Label(window, image=img)
        frame_queue.task_done()
        panel.pack(side="bottom", fill="both", expand="yes")
        window.after(1, update_img)
        window.mainloop()

        print("Exiting...")
        cap_dev.stop_streaming()
        print("Closing..")
        cap_dev.close()
        print("Clear Context")

Each frame is a complete JPEG image, stored in a bytearray. The frame_callback function gets call for every frame generated by the camera. I see "Dropped frame!" printed quite frequently, meaning my GUI code isn't pulling frames off of the queue fast enough and frame_callback encounters the queue.Full exception when trying to put new frames onto the queue. I've tried playing with the delay on the window.after scheduled function (first integer argument, units of milliseconds), but haven't had much luck.

So my question is: What can I do to optimize my GUI code to pull frames off of the queue faster? Am I missing something obvious?

Thanks!


回答1:


I'm posting this as an answer inspired by @stovfl's comment on the question, but I'm still curious to see how other people would approach this.

His comment pointed out that my frame_queue probably cannot deliver frames when calling frame_queue.get() within 1 millisecond, so I just removed the queue from the GUI update code entirely. Instead, I call the GUI updating code from the callback directly. Here is the new code:

import tkinter as tk
from PIL import ImageTk, Image
import uvclite
import io

user_check = True

def frame_callback(in_frame, user):
    global user_check
    if user_check:
        print("User id: %d" % user)
        user_check = False
    img = ImageTk.PhotoImage(Image.open(io.BytesIO(in_frame.data)))
    panel.configure(image=img)
    panel.image = img

if __name__ == "__main__":

    with uvclite.UVCContext() as context:
        cap_dev = context.find_device()
        cap_dev.set_callback(frame_callback, 12345)
        cap_dev.open()
        cap_dev.start_streaming()

        window = tk.Tk()
        window.title("Join")
        window.geometry("300x300")
        window.configure(background="grey")
        panel = tk.Label(window)
        panel.pack(side="bottom", fill="both", expand="yes")

        window.mainloop()
        print("Exiting...")
        cap_dev.stop_streaming()
        print("Closing..")
        cap_dev.close()
        print("Clear Context")

This works quite well, and the GUI is very responsive and captures motion in real time. I'm not going to mark this as an answer just yet, I would like to see what other people come up with first.



来源:https://stackoverflow.com/questions/58525958/keeping-up-with-camera-frame-rate-in-tkinter-gui

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!