Search code examples
pythonopencvtkintervideo-capture

OpenCV (cv2 in Python) VideoCapture not releasing camera after deletion


I am relatively new to Python, just having learnt it over the past month or so and have hacked this together based off examples and others' code I found online.

I have gotten a Tkinter GUI to display the feed from a webcam as a loop of continuously updated images on a canvas. Quitting the GUI and re-running the script every other time results in this error:

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

When the error happens no images get read and the videofeed recieves no images to update the canvas. The script runs normally with no errors the first time and every second time. From previous tests with the VideoCapture function in the cv2 module, I found that I had to delete the camera object to release it so that subsequent runs are able to capture the camera stream with no issue. Checks on the namespace by typing who in the console do not show cam so I know it is being deleted properly after the GUI is closed. I do not understand why cv2's read function is giving an error. I think it is only happening every second time because when the error occurs, some garbage collection or error handling deletes or frees up something to do with the camera but I do not know what this is...

Here is my code:

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

Refactoring the code like this:

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

does not display the button in the GUI and gives this error after closing the window:

RuntimeError: Too early to create image

I have 3 questions

1 - How can I prevent either exception? UPDATE: changing "root.after(0,update_video(cam,root,canvas))" to "root.after(0,lambda: update_video(cam,root,canvas))" and "update_video(cam,root,canvas)" to "update_video(cam,root,canvas,event=None)" OR passing the arguments to the callback using this format: "root.after(time_to_wait, callback, arguments, master)" fixes the second error (and others I did not post). Also as kobejohn pointed out, adding a try: except block also fixes the second error. Please see his answer for more details.

2 - Is there a faster, more efficient function than .read() in cv2? Edit: Is there a way to refactor my code to get higher framerates? The read function is the only one listed in the docs and I just read somewhere that if it is not in the docs, then it is not available. This method only gives me about 5fps, where 10-20fps would be much more acceptable. UPDATE: From the discrepancies between kobejohn's tests and mine with different cameras, the low framerate is a result of poor quality webcams. Better quality webcams yield higher framerates.

3 - I have been reading that update() should be avoided as much as possible but how do I get the canvas to redraw the image otherwise (or implement update_idletasks() with this code)?. Do I have to implement some sort of threading or can I avoid that? UPDATE: I have gotten the code to work without using the update() method but have to look at implementing threading anyway because when I start recording the videofeed from a button the main GUI, it freezes/ becomes unresponsive.

The finished program will be used in Ubuntu and windows (possibly on macs as well). I am running Windows 7, IDE is Spyder 2.1.11 (Python 2.7.3).

Thank you in advance, any advice and/or solutions will be much appreciated!

Regards,

S. Chia


Solution

  • Solved! OpenCV 2.4.2/ cv2 in python

    For some strange reason, I could not find the 'release' method before and other forums, pages specifically mentioned that the python bindings to opencv did not include the release method. Perhaps this only applied when using 'import cv'. I did my initial prototyping using the latter and for some reason missed the 'release' method in cv2 when I was looking for a ReleaseCapture method.

    Just found it in the docs: http://docs.opencv.org/modules/highgui/doc/reading_and_writing_images_and_video.html

    import cv2
    
    cam=cv2.VideoCapture(0)
    cam.release()