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

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


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 cv2cam=cv2.VideoCapture(0)cam.release


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.


Can you try this code and see what FPS you get? I included an FPS calculation so we can compare notes. (edit: also what errors. I didn't get the errors you got in the original code and I get zero errors with the code below)

I started from scratch just to see if I came up with something different. There are a few differences:

  1. There was a (minor?) bug: the opencv default color channels are BGR rather thanRGB. So change your grascale conversion from cv2.COLOR_RGB2GRAY--> cv2.COLOR_BGR2GRAY. You can see in the VideoCapture example they do something similar.
  2. I used a simple label to display the image instead of a canvas. I haven't used the canvas before, so I'm not sure what you need to do with it. With a simple label, you have to keep a reference to the image you are displaying so it doesn't get garbage collected. You can see that in update_image().
  3. For callbacks, I used lambdas with arguments (as you mentioned in your comment). Otherwise, when you make a function call with arguments, you are running the callback immediately instead of registering it. Ends up looking like it is working, but it's not doing quite what you would think. Alternately, you can use functools.partial if you prefer to package up your arguments and send that as an uncalled function.
  4. Also for the callback, I added a try: except block for the case that the callback starts running after root has been destroyed. I don't know if this is the 'right' way to do it, but it works as far as I know.

With this code, I get 15 FPS and no errors on windows 7:

from collections import dequeimport cv2import Image, ImageTkimport timeimport Tkinter as tkdef 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()