在 Tkinter 主循环期间录制 OpenCV 视频

Posted

技术标签:

【中文标题】在 Tkinter 主循环期间录制 OpenCV 视频【英文标题】:Record OpenCV video during Tkinter mainloop 【发布时间】:2014-09-28 17:57:57 【问题描述】:

我正在开发一项心理学实验,用于分析用户在完成行为任务时的面部表情。该应用程序主要通过 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 循环中,用户无法继续。 我怎样才能同时录制视频和听取用户的反应?

我研究过并行处理(例如,Display an OpenCV video in tkinter using multiprocessing),但这听起来比看起来需要的更大。

我也研究过使用 root.after 命令(例如,Show webcam sequence TkInter),但使用它似乎只能捕获一个帧,而我想要一个视频。

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

【问题讨论】:

【参考方案1】:

通过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 来通知子进程何时关闭,这是必要的,因为父进程无权访问out 或@987654329 @。

您可以尝试改用threading(只需将multiprocessing.Process 替换为threading.Thread,并将multiprocessing.Event 替换为threading.Event),但我怀疑GIL 会绊倒您并损害GUI 线程的性能。出于同样的原因,我认为不值得尝试通过root.after 将流的读/写集成到您的事件循环中——它只会损害性能,并且因为您没有尝试集成您正在做的事情进入 GUI 本身,没有理由尝试将其与事件循环保持在同一个线程/进程中。

【讨论】:

谢谢,我会试试这个!虽然我仍然希望有一个单一的流程解决方案 @nolanconaway 没问题。正如我在回答中提到的,我的解决方案可以通过使用来自threading 模块而不是multiprocessing 模块的对象转换为单进程(但多线程)解决方案。在测试multiprocessing 方法时试一试。我担心 GIL 会使性能变差,但是您的工作人员看起来受 I/O 限制,这意味着 GIL 应该在线程运行的大部分时间被释放。 我不得不对沙盒脚本进行大量编辑以使其与 Windows 兼容,但它现在可以完美运行。然而,在我更大、更复杂的应用程序中实现多处理要困难得多。具体来说,我发现我的目标进程总是被跳过,而是第二个进程变成了第一个进程的故障版本。通过在 p.start() 而不是 root.mainloop 之前放置“if name == 'main'...”,我可以在玩具脚本中模拟这个过程(),但在其他方面我一无所知。你有直觉吗? @nolanconaway 这可能是因为您需要使用if __name__ == "__main__": 保护来保护脚本的入口点。我实际上忘记将它包含在我的答案中(对不起!)。因为 Windows 缺少 os.fork,它需要重新导入您的主模块以生成您通过 multiprocessing.Process 创建的子进程。如果您不使用if __name__ == "__main__": 保护脚本的入口点,所有这些代码最终都会在子进程中再次执行。这听起来很像“第二个过程变成了第一个过程的故障版本”。 @nolanconaway 有关如何保持 Windows 与 multiprocessing 兼容的更多信息,请参阅 here。

以上是关于在 Tkinter 主循环期间录制 OpenCV 视频的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的主循环在 tkinter 中不起作用?

Python D-Bus 和 Tkinter 主循环集成

Tkinter 嵌套主循环

锯齿 tkinter 主循环帧持续时间?

Tkinter/Matplotlib 后端冲突导致无限主循环

如何在不中断 tkinter 主循环的情况下运行一个函数,同时将该函数的信息发送到我的主循环中的小部件?