在Tkinter主循环中录制OpenCV视频

2 投票
1 回答
6121 浏览
提问于 2025-04-18 16:09

我正在开发一个心理实验,目的是分析用户在完成某个行为任务时的面部表情。这个应用主要是通过Tkinter来运行的,同时我使用openCV来捕捉视频。

在一个简单的情况下,我需要根据用户的反应来开始和停止录制。例如,在下面的代码中,我希望用户能通过鼠标点击一个按钮来指定何时开始和停止录制视频。

import Tkinter as tk
import cv2

# -------begin capturing and saving video
def startrecording():
    cap = cv2.VideoCapture(0)
    fourcc = cv2.cv.CV_FOURCC(*'XVID')
    out = cv2.VideoWriter('output.avi',fourcc,  20.0, (640,480))

    while(cap.isOpened()):
        ret, frame = cap.read()
        if ret==True:
            out.write(frame)
        else:
            break

# -------end video capture and stop tk
def stoprecording():
    cap.release()
    out.release()
    cv2.destroyAllWindows()

    root.quit()
    root.destroy()

# -------configure window
root = tk.Tk()
root.geometry("%dx%d+0+0" % (100, 100))
startbutton=tk.Button(root,width=10,height=1,text='START',command = startrecording)
stopbutton=tk.Button(root,width=10,height=1,text='STOP', command = stoprecording)
startbutton.pack()
stopbutton.pack()

# -------begin
root.mainloop()

问题是,OpenCV在录制视频时会进入一个循环,这个时候Tkinter就无法接收用户的反应了。程序被卡在OpenCV的循环里,用户无法继续操作。我该如何同时录制视频并监听用户的反应呢?

我考虑过并行处理(比如,在tkinter中使用多进程显示OpenCV视频),但这听起来比我需要的要复杂得多。

我也研究过使用root.after命令(例如,在TkInter中显示网络摄像头序列),但使用这个方法似乎只能捕捉到一帧,而我想要的是视频。

还有其他方法吗?我需要使用两个处理流吗?

1 个回答

4

通过 multiprocessing 来处理这个问题其实比你想的要简单:

import multiprocessing
import Tkinter as tk
import cv2

e = multiprocessing.Event()
p = None

# -------begin capturing and saving video
def startrecording(e):
    cap = cv2.VideoCapture(0)
    fourcc = cv2.cv.CV_FOURCC(*'XVID')
    out = cv2.VideoWriter('output.avi',fourcc,  20.0, (640,480))

    while(cap.isOpened()):
        if e.is_set():
            cap.release()
            out.release()
            cv2.destroyAllWindows()
            e.clear()
        ret, frame = cap.read()
        if ret==True:
            out.write(frame)
        else:
            break

def start_recording_proc():
    global p
    p = multiprocessing.Process(target=startrecording, args=(e,))
    p.start()

# -------end video capture and stop tk
def stoprecording():
    e.set()
    p.join()

    root.quit()
    root.destroy()

if __name__ == "__main__":
    # -------configure window
    root = tk.Tk()
    root.geometry("%dx%d+0+0" % (100, 100))
    startbutton=tk.Button(root,width=10,height=1,text='START',command=start_recording_proc)
    stopbutton=tk.Button(root,width=10,height=1,text='STOP', command=stoprecording)
    startbutton.pack()
    stopbutton.pack()

    # -------begin
    root.mainloop()

我们所做的就是添加了一行代码,调用了 multiprocessing.Process,这样你的视频捕捉代码就可以在一个子进程中运行。同时,我们也把捕捉完成后需要清理的代码放到了这个子进程里。与单进程版本相比,唯一多出来的就是使用了 multiprocessing.Event 来通知子进程什么时候该关闭,因为父进程无法直接访问 outcap

你也可以尝试用 threading 来替代(只需把 multiprocessing.Process 替换成 threading.Thread,把 multiprocessing.Event 替换成 threading.Event),但我怀疑 GIL(全局解释器锁)会让你遇到麻烦,影响 GUI 线程的性能。出于同样的原因,我觉得把读取/写入流的操作集成到你的事件循环中(通过 root.after)也不值得尝试——这样只会影响性能。而且因为你并不想把这些操作集成到 GUI 本身,所以没有必要把它们放在与事件循环相同的线程/进程中。

撰写回答