我对 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 的读取功能会出错。我认为它只会每秒钟发生一次,因为当错误发生时,一些垃圾收集或错误处理会删除或释放与相机有关的东西,但我不知道这是什么......
这是我的代码:
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() 更快、更高效的函数?编辑:有没有办法重构我的代码以获得更高的帧速率?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