Функция waitKey () не работает с tkinter

#python #opencv #tkinter #python-imaging-library

#python #opencv #tkinter #python-imaging-библиотека

Вопрос:

Я разработал окно с помощью tkinter и хотел показать там видеопоток, а также хотел иметь возможность приостанавливать видео с помощью клавиши «p». Я создал функцию, которая помещает изображение, снятое с помощью веб-камеры, в ярлык на окне. Затем я помещаю функцию в цикл while, чтобы вызывать ее повторно и создавать непрерывное видео в окне. И я использовал функции waitKey () в цикле для считывания нажатий клавиш. Функция waitKey () также устанавливает частоту кадров или скорость видео. Мне удалось запустить видео, но программа не реагирует на нажатия клавиш. С другой стороны, я могу изменить частоту кадров при изменении аргумента waitKey(), поэтому функция, похоже, работает, но она не считывает нажатия клавиш, и при нажатии клавиши ничего не происходит. Также нет сообщений об ошибках. Я был бы благодарен, если бы кто-нибудь показал мне, как использовать waitKey() в этом цикле, чтобы я мог управлять видеопотоком с помощью нажатий клавиш, или предложить другой способ сделать это. Это мой код, большая его часть предназначена для проектирования окна, но цикл с функциями waitKey () находится в конце:

 import tkinter as tk
from tkinter import *
from tkinter import ttk
from tkinter.ttk import *
from PIL import ImageTk, Image
from cv2 import cv2

mainwin = Tk()  #create main window

appWinWidth = int(0.6 * mainwin.winfo_screenwidth()) #set main window size (depending on the screen)
appWinHeight = int(0.5 * mainwin.winfo_screenheight())

screen_width = mainwin.winfo_screenwidth() #get screen dimensions
screen_height = mainwin.winfo_screenheight()

appWinX = int((screen_width - appWinWidth)/2) #set coordinates of the main window so that it 
appWinY = int((screen_height - appWinHeight)/2) #would be located in the middle of the screen

#create and place the frames
frame1 = tk.Frame(mainwin, background = "white", width = int(appWinWidth/2), height = appWinHeight)
frame2 = tk.Frame(mainwin, background = "black", width = int(appWinWidth/2), height = appWinHeight)

frame1.grid(row = 0, column = 0, sticky = "nsew")
frame2.grid(row = 0, column = 1, sticky = "nsew")

mainwin.grid_columnconfigure(0, weight = 1)
mainwin.grid_columnconfigure(1, weight = 1)
mainwin.grid_rowconfigure(0, weight = 1)

#set the geometry of the main window
mainwin.geometry("{}x{} {} {}".format(appWinWidth, appWinHeight, appWinX, appWinY)) 
mainwin.resizable(width = 0, height = 0)

#create labels in frames
frame2.grid_propagate(0) #labels and other widgets in frame don't change the size of the frame
frame2.grid()
labelF2 = Label(frame2)
labelF2.grid()
labelF2.place(relx=0.5, rely=0.5, anchor="center")
frame1.grid_propagate(0)
labelF1 = Label(frame1, background = "white")
labelF1.grid()
mainwin.update()

#get camera feed and frame size
cap = cv2.VideoCapture(0)
capFrameWidth = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
capFrameHeight = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
widthRelation = capFrameWidth/frame2.winfo_width()
heigthRelation = capFrameHeight/frame2.winfo_height()

#define the size of the video frame so that the video would fit into the frame of the main window
if widthRelation > 1 and widthRelation > heigthRelation:
    fittedSize = (int(capFrameWidth/widthRelation), int(capFrameHeight/widthRelation))
elif heigthRelation > 1 and heigthRelation > widthRelation:
    fittedSize = (int(capFrameWidth/heigthRelation), int(capFrameHeight/heigthRelation))
else:
    fittedSize = (capFrameWidth, capFrameHeight)

#funtion for getting a video frame, resizing it for the main window and placing it into the frame 2
def video_to_mainwin():
    chk, frame = cap.read()
    cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
    imgV = cv2.resize(cv2image, fittedSize) 
    img = Image.fromarray(imgV)
    imgtk = ImageTk.PhotoImage(image=img)
    labelF2.imgtk = imgtk
    labelF2.configure(image=imgtk)

#run the video function continuously to create a stream, and be ready to react to key presses
while True:
    key = cv2.waitKey(1)
    if key == ord('p'):  #if 'p' is pressed, pause the stream and write 'Paused' to frame 1 on the main window
        video_to_mainwin()
        labelF1.config(text = 'Paused')
        mainwin.update()
        key2 = cv2.waitKey(0) #remain paused until 'c' is pressed
        if key2 == ord('c'):
            labelF1.config(text = '') 
    else:                   #if no key is pressed, then just show the video stream
        video_to_mainwin()
        mainwin.update()
 

Комментарии:

1. если вы отображаете изображение в окне tkinter, то вам не нужно cv2.waitKey(1) — использовать tkinter для этого — root.bind("p", function) запускать function при нажатии p . И когда вы используете bind() , вам не нужно while True проверять нажатую клавишу — bind() делает это за вас. В конце концов вам может понадобиться root.after(time, function) запустить function обновление изображения в окне.

2. place() , grid() , pack() — это разные «менеджеры макетов» — для каждого виджета требуется только одно управление макетом — нет смысла делать labelF2.grid() и labelF2.place(...)

Ответ №1:

Если вы отображаете cv2 изображение в tkinter , то вам это не нужно cv2.waitKey() .

tkinter Для этого можно использовать

 mainwin.bind("p", function_name)
 

для запуска function_name() при нажатии p .

И когда вы используете bind() then, вам не нужно while True проверять нажатую клавишу, потому tkinter что mainloop сделает это за вас.

Вам нужно только root.after(milliseconds, function_name) запустить функцию, которая будет обновлять изображение в окне каждые несколько миллисекунд.


Рабочий код

 #from tkinter import *      # PEP8: `import *` is not preferred
#from tkinter.ttk import *  # PEP8: `import *` is not preferred

import tkinter as tk
from tkinter import ttk
from PIL import ImageTk, Image
from cv2 import cv2

# --- constants --- (PEP8: UPPER_CASE_NAMES)

#FPS = 25

# --- classes --- (PEP8: CamelCaseNames)

# ... empty ...

# --- functions --- (PEP8: lower_case_names)

def video_to_mainwin():
    """Getting a video frame, resizing it for the main window and placing it into the frame 2."""

    if not paused:
        chk, frame = cap.read()
        cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
        imgV = cv2.resize(cv2image, fitted_size) 
        img = Image.fromarray(imgV)
        imgtk = ImageTk.PhotoImage(image=img)
        labelF2.imgtk = imgtk
        labelF2.configure(image=imgtk)

    # run it again after `1000/FPS` milliseconds
    #mainwin.after(int(1000/FPS), video_to_mainwin)
    mainwin.after(int(1000/cap_fps), video_to_mainwin)

def set_pause(event):
    global paused
    
    paused = True
    labelF1.config(text = 'Paused')

def unset_pause(event):
    global paused
    
    paused = False
    labelF1.config(text = '') 
    
# --- main ---- (PEP8: lower_case_names)
    
paused = False  # default value at start

# - cap - 

cap = cv2.VideoCapture(0)
cap_frame_width  = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
cap_frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
cap_fps = int(cap.get(cv2.CAP_PROP_FPS))

# - tk -

mainwin = tk.Tk()  # create main window

app_win_width = int(0.6 * mainwin.winfo_screenwidth()) # set main window size (depending on the screen)
app_win_height = int(0.5 * mainwin.winfo_screenheight())

screen_width = mainwin.winfo_screenwidth() # get screen dimensions
screen_height = mainwin.winfo_screenheight()

app_win_x = int((screen_width - app_win_width)/2) # set coordinates of the main window so that it 
app_win_y = int((screen_height - app_win_height)/2) # would be located in the middle of the screen

#create and place the frames
frame1 = tk.Frame(mainwin, background="white", width=int(app_win_width/2), height=app_win_height)
frame2 = tk.Frame(mainwin, background="black", width=int(app_win_width/2), height=app_win_height)

frame1.grid(row=0, column=0, sticky="nsew")   # PEP8: without spaces around `=`
frame2.grid(row=0, column=1, sticky="nsew")   # PEP8: without spaces around `=`

mainwin.grid_columnconfigure(0, weight=1)   # PEP8: without spaces around `=`
mainwin.grid_columnconfigure(1, weight=1)   # PEP8: without spaces around `=`
mainwin.grid_rowconfigure(0, weight=1)

#set the geometry of the main window
mainwin.geometry("{}x{} {} {}".format(app_win_width, app_win_height, app_win_x, app_win_y)) 
mainwin.resizable(width=0, height=0)

# create labels in frames
frame2.grid_propagate(0) # labels and other widgets in frame don't change the size of the frame
frame2.grid()

labelF2 = tk.Label(frame2)
labelF2.place(relx=0.5, rely=0.5, anchor="center")

frame1.grid_propagate(0)

labelF1 = tk.Label(frame1, background = "white")
labelF1.grid()

mainwin.update() # to create window and correctly calculate sizes

# get camera feed and frame size
width_relation  = cap_frame_width/frame2.winfo_width()
heigth_relation = cap_frame_height/frame2.winfo_height()


# define the size of the video frame so that the video would fit into the frame of the main window
if width_relation > 1 and width_relation > heigth_relation:
    fitted_size = (int(cap_frame_width/width_relation), int(cap_frame_height/width_relation))
elif heigth_relation > 1 and heigth_relation > width_relation:
    fitted_size = (int(cap_frame_width/heigth_relation), int(cap_frame_height/heigth_relation))
else:
    fitted_size = (cap_frame_width, cap_frame_height)

# assing functions to buttons
mainwin.bind("p", set_pause)
mainwin.bind("c", unset_pause)

# star video
video_to_mainwin()

# start tkinter mainloop (event loop)
mainwin.mainloop()

# - end -

cap.release()
 

PEP 8 — Руководство по стилю для кода Python


У меня есть несколько примеров с cv2 и tkinter на Github.

Например: python-cv2-streams-viewer использует cv2 in separted thread для чтения с веб-камеры или файла (даже из файла в Интернете). Он также используется tk.Frame для создания виджета , из которого отображается поток cv2 . Он использует этот виджет много раз для одновременного отображения множества потоков.

Другие примеры в python-examples cv2 или tkinter

Комментарии:

1. Этот код работает именно так, как я хотел! Большое вам спасибо! На самом деле я тоже пытался использовать функцию привязки, но я мог просто распечатать текст с помощью bind(), я не нашел способа приостановить видео с помощью этой функции. Но идея использовать флаг типа «приостановлено» действительно хороша, она работает. Что касается функции waitKey(), то я не знаю, почему она не сработала, но, возможно, она создала отдельный поток, так что даже если задержка, установленная в аргументе waitKey(), сработала, значение ключа было недоступно.