我对Python还比较陌生,只是在过去的一个月左右学习了一些,并根据在网上找到的示例和其他人的代码拼凑出了这个程序。
我已经用Tkinter GUI显示了来自网络摄像头的视频流,作为画布上连续更新图像的循环。每隔一段时间退出GUI并重新运行脚本会导致出现以下错误:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python27\lib\lib-tk\Tkinter.py", line 1410, in __call__
return self.func(*args)
File "C:\Python27\lib\lib-tk\Tkinter.py", line 495, in callit
func(*args)
File "C:\...\cv2_cam_v8.py", line 20, in update_video
(self.readsuccessful,self.f) = self.cam.read()
SystemError: NULL object passed to Py_BuildValue
当出现错误时,没有图像被读取,视频流收不到任何图像以更新画布。脚本第一次运行和每隔一次正常运行且无错误。在使用cv2模块的VideoCapture函数进行先前的测试中,我发现必须删除相机对象以释放它,以便后续运行能够无问题地捕获相机流。在控制台中键入
who
检查命名空间时,不显示cam
,因此我知道关闭GUI后它已被正确删除。我不明白为什么cv2的read函数会产生错误。我认为这只是每隔一次才会发生,因为当错误发生时,某些垃圾回收或错误处理会删除或释放与相机有关的某些内容,但我不知道这是什么……
下面是我的代码:
import cv2
import Tkinter as tk
from PIL import Image, ImageTk
class vid():
def __init__(self,cam,root,canvas):
self.cam = cam
self.root = root
self.canvas = canvas
def update_video(self):
(self.readsuccessful,self.f) = self.cam.read()
self.gray_im = cv2.cvtColor(self.f, cv2.COLOR_RGB2GRAY)
self.a = Image.fromarray(self.gray_im)
self.b = ImageTk.PhotoImage(image=self.a)
self.canvas.create_image(0,0,image=self.b,anchor=tk.NW)
self.root.update()
self.root.after(33,self.update_video)
if __name__ == '__main__':
root = tk.Tk()
videoframe = tk.LabelFrame(root,text='Captured video')
videoframe.grid(column=0,row=0,columnspan=1,rowspan=1,padx=5, pady=5, ipadx=5, ipady=5)
canvas = tk.Canvas(videoframe, width=640,height=480)
canvas.grid(column=0,row=0)
cam = cv2.VideoCapture(2)
x = vid(cam,root,canvas)
root.after(0,x.update_video)
button = tk.Button(text='Quit',master=videoframe,command=root.destroy)
button.grid(column=0,row=1)
root.mainloop()
del cam
将代码重构为如下形式:
def update_video(cam,root,canvas):
(readsuccessful,f) = cam.read()
gray_im = cv2.cvtColor(f, cv2.COLOR_RGB2GRAY)
a = Image.fromarray(gray_im)
b = ImageTk.PhotoImage(image=a)
canvas.create_image(0,0,image=b,anchor=tk.NW)
root.update()
root.after(33,update_video(cam,root,canvas))
if __name__ == '__main__':
root = tk.Tk()
videoframe = tk.LabelFrame(root,text='Captured video')
videoframe.grid(column=0,row=0,columnspan=1,rowspan=1,padx=5, pady=5, ipadx=5, ipady=5)
canvas = tk.Canvas(videoframe, width=640,height=480)
canvas.grid(column=0,row=0)
cam = cv2.VideoCapture(2)
root.after(0,update_video(cam,root,canvas))
button = tk.Button(text='Quit',master=videoframe,command=root.destroy)
button.grid(column=0,row=1)
root.mainloop()
del cam
在 GUI 中不显示按钮,并在关闭窗口后给出此错误:
RuntimeError: Too early to create image
我有3个问题
1-如何避免这两种异常情况? 更新:将“root.after(0,update_video(cam,root,canvas))”更改为“root.after(0,lambda:update_video(cam,root,canvas))”,并将“update_video(cam,root,canvas)”更改为“update_video(cam,root,canvas,event = None)”或使用此格式将参数传递给回调:“root.after(time_to_wait,callback,arguments,master)”可以解决第二个错误(以及其他我没有发布的错误)。 此外,正如kobejohn指出的那样,添加try:except块也可以解决第二个错误。 请参阅他的答案以获得更多详细信息。
2-是否有比cv2中的.read()更快,更有效的函数?编辑:是否有一种重构我的代码以获得更高帧率的方法?读取功能是唯一列在文档中的功能,并且我刚刚在某个地方读到,如果它不在文档中,则不可用。该方法仅给我提供约5fps,而10-20fps将更加可接受。 更新:根据kobejohn对不同摄像头进行测试的差异,低帧速率是质量较差的网络摄像头的结果。 更好质量的网络摄像头具有更高的帧速率。
3-我一直在阅读应尽可能避免update(),但我如何使画布重新绘制图像(或在此代码中实现update_idletasks())? 我必须实施某种线程,还是可以避免这种情况? 更新:我已成功使代码工作而不使用update()方法,但仍需考虑实施线程,因为当我从按钮启动视频源的录制时,主GUI会冻结/变得无响应。
成品程序将用于Ubuntu和Windows(可能也适用于Mac)。 我正在运行Windows 7,IDE是Spyder 2.1.11(Python 2.7.3)。
提前感谢您的建议和/或解决方案!
问候,
S. Chia
root.after(0,update_video(cam,root,canvas))
注册的是 update_video(...) 的返回值,而不是函数 update_video 本身。 - KobeJohn