OpenCV(Python中的cv2)VideoCapture删除后不释放相机

时间:2013-03-17 12:30:47

标签: python opencv tkinter video-capture

我对Python比较陌生,只是在过去一个月左右的时间里学习了它,并根据我在网上找到的示例和其他代码一起攻击了它。

我已经获得了一个Tkinter GUI,可以将来自网络摄像头的Feed显示为画布上不断更新的图像循环。退出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)” “to”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_idletasks())?我是否必须实施某种线程或者我可以避免这种情况吗? 更新:我已经让代码在不使用update()方法的情况下工作,但不得不考虑实现线程化,因为当我开始从主GUI的按钮开始录制视频片时,它会冻结/变得无法响应。

完成的程序将用于Ubuntu和windows(也可能在mac上)。我正在运行Windows 7,IDE是Spyder 2.1.11(Python 2.7.3)。

提前感谢您,我们非常感谢任何建议和/或解决方案!

此致

S上。嘉

3 个答案:

答案 0 :(得分:6)

解决! python中的OpenCV 2.4.2 / cv2

由于一些奇怪的原因,我找不到'release'方法之前和其他论坛,页面明确提到python绑定到opencv没有包含release方法。也许这只适用于使用'import cv'时。我使用后者进行了初始原型设计,出于某种原因,当我在寻找ReleaseCapture方法时,错过了cv2中的'release'方法。

刚在文档中找到它:http://docs.opencv.org/modules/highgui/doc/reading_and_writing_images_and_video.html

import cv2

cam=cv2.VideoCapture(0)
cam.release

答案 1 :(得分:0)

您可以尝试使用此代码查看您获得的FPS吗?我包括一个FPS计算,所以我们可以比较笔记。 (编辑:还有什么错误。我没有得到原始代码中的错误,我在下面的代码中得到零错误)

我从头开始只是为了看看我是否想出了不同的东西。有一些差异:

  1. 有一个(小?)错误:opencv默认颜色通道是BGR而不是 RGB。因此,请从cv2.COLOR_RGB2GRAY更改您的grascale转换 - > cv2.COLOR_BGR2GRAY。您可以在VideoCapture example中看到他们做类似的事情。
  2. 我用一个简单的标签来显示图像而不是画布。我之前没有使用过画布,所以我不确定你需要做什么。使用简单的标签,您必须keep a reference to the image you are displaying,因此不会收集垃圾。你可以在update_image()。
  3. 中看到
  4. 对于回调,我使用了带参数的lambda(正如你在评论中提到的那样)。否则,当您使用参数进行函数调用时,您将立即运行回调而不是注册它。结束看起来它正在工作,但它没有做你想象的那么多。或者,如果您希望打包参数并将其作为未调用的函数发送,则可以使用functools.partial
  5. 同样对于回调,我添加了一个try:except块,用于在root被销毁后回调开始运行的情况。我不知道这是否是“正确”的方式,但据我所知,它可以正常工作。
  6. 使用此代码,我获得15 FPS并且在Windows 7上没有错误:

    from collections import deque
    import cv2
    import Image, ImageTk
    import time
    import Tkinter as tk
    
    def quit_(root):
        root.destroy()
    
    def update_image(image_label, cam):
        (readsuccessful, f) = cam.read()
        gray_im = cv2.cvtColor(f, cv2.COLOR_BGR2GRAY)
        a = Image.fromarray(gray_im)
        b = ImageTk.PhotoImage(image=a)
        image_label.configure(image=b)
        image_label._image_cache = b  # avoid garbage collection
        root.update()
    
    
    def update_fps(fps_label):
        frame_times = fps_label._frame_times
        frame_times.rotate()
        frame_times[0] = time.time()
        sum_of_deltas = frame_times[0] - frame_times[-1]
        count_of_deltas = len(frame_times) - 1
        try:
            fps = int(float(count_of_deltas) / sum_of_deltas)
        except ZeroDivisionError:
            fps = 0
        fps_label.configure(text='FPS: {}'.format(fps))
    
    
    def update_all(root, image_label, cam, fps_label):
        update_image(image_label, cam)
        update_fps(fps_label)
        root.after(20, func=lambda: update_all(root, image_label, cam, fps_label))
    
    
    if __name__ == '__main__':
        root = tk.Tk()
        # label for the video frame
        image_label = tk.Label(master=root)
        image_label.pack()
        # camera
        cam = cv2.VideoCapture(0)
        # label for fps
        fps_label = tk.Label(master=root)
        fps_label._frame_times = deque([0]*5)  # arbitrary 5 frame average FPS
        fps_label.pack()
        # quit button
        quit_button = tk.Button(master=root, text='Quit',
                                command=lambda: quit_(root))
        quit_button.pack()
        # setup the update callback
        root.after(0, func=lambda: update_all(root, image_label, cam, fps_label))
        root.mainloop()
    

答案 2 :(得分:0)

在opencv中初始化相机对象之前,请先设置环境变量。

os.environ['OPENCV_VIDEOIO_PRIORITY_MSMF'] = '0'

即使在我的代码中关闭了相机对象之后,这仍然释放了相机。