【问题标题】:Record Video Button in tkinter GUI python在 tkinter GUI python 中录制视频按钮
【发布时间】:2021-08-13 11:06:01
【问题描述】:

我对 python 很陌生,尤其是 tkinter 和 opencv。

我有某种方式(一点方法)来创建最终将控制显微镜和人工智能的 gui。但是我遇到了一个绊脚石,试图录制在 gui 中显示的视频,我认为这与已经在显示器中捕获的视频源有关,但我找不到解决方法。一切正常,直到我点击记录然后它崩溃并且我收到错误:打开 VIDEOIO(V4L2:/dev/video0): can't open camera by index.

抱歉,代码太长了,但我已将其缩减到尽可能多的程度。

问题出在 root.recbtn 和 def rec 部分。

import cv2
import tkinter as tk
import multiprocessing
from tkinter import *
from PIL import Image,ImageTk
from datetime import datetime
from tkinter import messagebox, filedialog

e = multiprocessing.Event()
p = None

# Defining CreateWidgets() function to create necessary tkinter widgets
def createwidgets():

    root.cameraLabel = Label(root, bg="gray25", borderwidth=3, relief="ridge")
    root.cameraLabel.grid(row=2, column=1, padx=10, pady=10, columnspan=3)

    root.browseButton = Button(root, bg="gray25", width=10, text="BROWSE", command=destBrowse)
    root.browseButton.grid(row=1, column=1, padx=10, pady=10)

    root.recbtn = Button(root, bg="gray25", width=10, text="Record", command=rec)
    root.recbtn.grid(row=1, column=5, padx=10, pady=10)

    root.saveLocationEntry = Entry(root, width=55, textvariable=destPath)
    root.saveLocationEntry.grid(row=1, column=2, padx=10, pady=10)

    # Calling ShowFeed() function
    ShowFeed()

# Defining ShowFeed() function to display webcam feed in the cameraLabel;

def ShowFeed():
    # t5  # Capturing frame by frame
    ret, frame = root.cap.read()

    if ret:
        # Flipping the frame vertically
        frame = cv2.flip(frame, 1)

        # Changing the frame color from BGR to RGB
        cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)

        # Creating an image memory from the above frame exporting array interface
        videoImg = Image.fromarray(cv2image)

        # Creating object of PhotoImage() class to display the frame
        imgtk = ImageTk.PhotoImage(image = videoImg)

        # Configuring the label to display the frame
        root.cameraLabel.configure(image=imgtk)

        # Keeping a reference
        root.cameraLabel.imgtk = imgtk

        # Calling the function after 10 milliseconds
        root.cameraLabel.after(10, ShowFeed)
    else:
        # Configuring the label to display the frame
        root.cameraLabel.configure(image='')

def destBrowse():
    # Presenting user with a pop-up for directory selection. initialdir argument is optional
    # Retrieving the user-input destination directory and storing it in destinationDirectory
    # Setting the initialdir argument is optional. SET IT TO YOUR DIRECTORY PATH
    destDirectory = filedialog.askdirectory(initialdir="YOUR DIRECTORY PATH")

    # Displaying the directory in the directory textbox
    destPath.set(destDirectory)

def rec():
    vid_name = datetime.now().strftime('%d-%m-%Y %H-%M-%S')

    # If the user has selected the destination directory, then get the directory and save it in image_path
    if destPath.get() != '':
        vid_path = destPath.get()
    # If the user has not selected any destination directory, then set the image_path to default directory
    else:
        messagebox.showerror("ERROR", "No Directory Selected!")

    # Concatenating the image_path with image_name and with .jpg extension and saving it in imgName variable
    vidName = vid_path + '/' + vid_name + ".avi"
    capture = cv2.VideoCapture(0)

    fourcc = cv2.VideoWriter_fourcc('X', 'V', 'I', 'D')
    videoWriter = cv2.VideoWriter(vidName, fourcc, 30.0, (640, 480))

    while (True):

        ret, frame = capture.read()

        if ret:
            cv2.imshow('video', frame)
            videoWriter.write(frame)

        if cv2.waitKey(1) == 27:
            break

    capture.release()
    videoWriter.release()

# Creating object of tk class
root = tk.Tk()

# Creating object of class VideoCapture with webcam index
root.cap = cv2.VideoCapture(0)

# Setting width and height
width, height = 1200, 1200
root.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
root.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)

# Setting the title, window size, background color and disabling the resizing property
root.title("Test-AI-tes")
root.geometry("1600x1024")
root.resizable(True, True)
root.configure(background = "gray18")

# Creating tkinter variables
destPath = StringVar()
imagePath = StringVar()

createwidgets()
root.mainloop()

谢谢!

【问题讨论】:

  • 你为什么不重用root.cap 而不是在这里创建一个新的:capture = cv2.VideoCapture(0)。此外,除了在使用tkinter 时不应该使用while True 循环之外,问题似乎并不集中在tkinter 上。如果是这样,请删除tkinter 标签
  • 你想在 tkinter 窗口中实时显示视频还是你想保存帧然后显示它?
  • 感谢回复,根据@TheLizzard 的评论切换到capture = root.cap,它可以录制但实时视频冻结。我希望视频保持实时,但也能够在记录命中时保存部分提要。
  • @PaulTansley 看看this。将while True 循环实现为tkinter 循环。
  • 谢谢@TheLizzard。我想我现在差不多了,只需要定义一个停止录制功能。

标签: python tkinter opencv-python


【解决方案1】:

这个答案类似于@Art 的答案,但我删除了after_idqueue

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


def stop_rec():
    global running
    running = False

    start_button.config(state="normal")
    stop_button.config(state="disabled")

def start_capture():
    global capture, last_frame

    capture = cv2.VideoCapture(0)
    
    fourcc = cv2.VideoWriter_fourcc("X", "V", "I", "D")
    video_writer = cv2.VideoWriter(r"sample.avi", fourcc, 30.0, (640, 480))

    while running:
        rect, frame =  capture.read()

        if rect:
            cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
            last_frame = Image.fromarray(cv2image)
            video_writer.write(frame)

    capture.release()
    video_writer.release()

def update_frame():
    if last_frame is not None:
        tk_img = ImageTk.PhotoImage(master=video_label, image=last_frame)
        video_label.config(image=tk_img)
        video_label.tk_img = tk_img

    if running:
        root.after(10, update_frame)

def start_rec():
    global running

    running = True
    thread = threading.Thread(target=start_capture, daemon=True)
    thread.start()
    update_frame()

    start_button.config(state="disabled")
    stop_button.config(state="normal")

def closeWindow():
    stop_rec()
    root.destroy()


running = False
after_id = None
last_frame = None

root = tk.Tk()
root.protocol("WM_DELETE_WINDOW", closeWindow)

video_label = tk.Label()
video_label.pack(expand=True, fill="both")

start_button = tk.Button(text="Start", command=start_rec)
start_button.pack()
stop_button = tk.Button(text="Stop", command=stop_rec, state="disabled")
stop_button.pack()

root.mainloop()

它使用布尔标志 running 而不是使用 after_id。另外,我没有将图像存储在队列中然后显示它,而是只保留最后一张图像。这样它就可以在我的电脑上实时运行。不用担心所有帧仍然存储在视频文件中。

【讨论】:

    【解决方案2】:

    您不能在 GUI 的循环中使用无限的 while 循环。每当您需要完成 IO 操作时,您都应该使用线程。

    示例代码:

    import cv2
    import threading
    import tkinter as tk
    from PIL import Image, ImageTk
    from queue import Queue
    
    def stop_rec():
        global running, after_id
        running = False
    
        if after_id:
            root.after_cancel(after_id)
            after_id = None
            
        with frame_queue.mutex:
            frame_queue.queue.clear()
    
    def start_capture():
        global capture
    
        capture = cv2.VideoCapture(0)
        
        fourcc = cv2.VideoWriter_fourcc('X', 'V', 'I', 'D')
        video_writer = cv2.VideoWriter(r"sample.avi", fourcc, 30.0, (640, 480))
    
        while running:
            
            rect, frame =  capture.read()
    
            if rect:
                cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
                videoImg = Image.fromarray(cv2image)
                # current_frame = ImageTk.PhotoImage(image = videoImg)
                frame_queue.put(videoImg)
                video_writer.write(frame)
                
        capture.release()
        video_writer.release()
    
    def update_frame():
        global after_id
    
        if not frame_queue.empty():
            video_label.image_frame = ImageTk.PhotoImage(frame_queue.get_nowait())
            video_label.config(image=video_label.image_frame)
        
        after_id = root.after(10, update_frame)
    
    def start_rec():
        global running
    
        stop_rec()
    
        running = True
        thread = threading.Thread(target=start_capture, daemon=True)
        thread.start()
        update_frame()
    
    
    def closeWindow():
        stop_rec()
        root.destroy()
    
    
    running = False
    after_id = None
    frame_queue = Queue()
    
    root = tk.Tk()
    root.protocol("WM_DELETE_WINDOW", closeWindow)
    
    video_label = tk.Label()
    video_label.pack(expand=True, fill="both")
    
    tk.Button(text="Start", command=start_rec).pack()
    tk.Button(text="stop", command=stop_rec).pack()
    
    root.mainloop()
    

    快速解释:

    • 在新线程中启动记录功能。
    • 使用队列存储帧。
    • 然后利用[widget].after 定期更新标签。
    • 要停止录制,请使用[widget].after_cancel(after_id)(使用.after 方法时返回after_id)并将运行变量设置为False 以停止循环。

    【讨论】:

    • 使用像recording 这样的布尔变量比使用.after_cancel 更直观吗?
    • @TheLizzard 我不太明白。你的意思是问是否最好删除after_cancel并在after方法之前使用running=True作为检查?
    • 不是 100% 确定你的意思,但我认为是的。我会写我自己的答案。你也不应该从另一个线程调用ImageTk.PhotoImage。它算作tkinter 函数调用,tkinter 不是线程安全的。
    • @TheLizzard 谢谢你的信息,我不知道。
    • 这对我来说也不是实时运行的。有一个显着的滞后随着时间的推移而增加。我认为tkinter 从队列中提取图像时太慢了。如果这是真的,如果内存使用量也不断增加,我不会感到惊讶。
    猜你喜欢
    • 1970-01-01
    • 2020-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-28
    相关资源
    最近更新 更多