【问题标题】:Record OpenCV video during Tkinter mainloop在 Tkinter 主循环期间录制 OpenCV 视频
【发布时间】: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),但使用它似乎只能捕获一个帧,而我想要一个视频。

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

【问题讨论】:

    标签: python opencv video 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
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-12
    • 1970-01-01
    • 1970-01-01
    • 2021-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多