Отслеживание частоты кадров в графическом интерфейсе Tkinter - PullRequest
0 голосов
/ 23 октября 2019

Моя цель - отображать в режиме реального времени канал с USB-камеры в окне Tkinter. Моя проблема в том, что я не могу обновить графический интерфейс достаточно быстро, чтобы не отставать от частоты кадров камеры. Я подключаюсь к камере с помощью оболочки uvclite python вокруг библиотеки libuvc C. uvclite - это легкая оболочка ctypes для базовой библиотеки C, поэтому я не думаю, что этот кусок - мое узкое место. Вот мой код:

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")

Каждый кадр представляет собой полное изображение JPEG, сохраненное в bytearray. Функция frame_callback получает вызов для каждого кадра, сгенерированного камерой. Я вижу "Сброшенная рамка!"печатается довольно часто, это означает, что мой код GUI недостаточно быстро вытягивает кадры из очереди, и frame_callback встречает исключение queue.Full при попытке добавить новые кадры в очередь. Я пытался поиграть с задержкой для запланированной функции window.after (первый целочисленный аргумент, единицы миллисекунд), но мне не повезло.

Итак, мой вопрос: Что я могу сделать, чтобы оптимизировать мой код графического интерфейса, чтобы быстрее выводить кадры из очереди? Я что-то упускаю из виду?

Спасибо!

1 Ответ

0 голосов
/ 23 октября 2019

Я публикую это как ответ, вдохновленный комментариями @ stovfl по этому вопросу, но мне все еще интересно посмотреть, как другие люди подойдут к этому.

В его комментарии указывалось, что мой frame_queue, вероятно, не может доставить кадры при вызове frame_queue.get() в течение 1 миллисекунды, поэтому я просто полностью удалил очередь из кода обновления графического интерфейса. Вместо этого я вызываю код обновления графического интерфейса непосредственно из обратного вызова. Вот новый код:

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")

Это работает довольно хорошо, а графический интерфейс очень отзывчив и фиксирует движение в реальном времени. Я пока не буду отмечать это как ответ, я хотел бы увидеть, что другие люди придумают первыми.

...