在 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 视频的主要内容,如果未能解决你的问题,请参考以下文章