Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 803
S. Chia Avatar asked Mar 17 '13 12:03

S. Chia


People also ask

What does cv2 VideoCapture return?

Reading ( cam. read() ) from a VideoCapture returns a tuple (return value, image) . With the first item you check wether the reading was successful, and if it was then you proceed to use the returned image .

What is cv2 VideoCapture in Python?

cv2.VideoCapture(1): Means second camera or webcam. cv2.VideoCapture("file name.mp4"): Means video file. After this, we can start reading a Video from the camera frame by frame. We do this by calling the read method on the VideoCapture object. This method takes no arguments and returns a tuple.

What does cv2 VideoCapture 0 mean?

Next, we cay cap = cv2. VideoCapture(0) . This will return video from the first webcam on your computer.

How does OpenCV VideoCapture work?

Capture Video from Camera OpenCV allows a straightforward interface to capture live stream with the camera (webcam). It converts video into grayscale and display it. We need to create a VideoCapture object to capture a video. It accepts either the device index or the name of a video file.


2 Answers

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
like image 170
S. Chia Avatar answered Nov 03 '22 02:11

S. Chia


Set the environment variable before you initialize the camera object in opencv.

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

This released the camera even after closing the camera object in my code.

like image 28
Harish L Avatar answered Nov 03 '22 02:11

Harish L