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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15460706/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 20:12:03  来源:igfitidea点击:

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

pythonopencvtkintervideo-capture

提问by S. Chia

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.

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

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:

我已经获得了一个 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

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 whoin the console do not show camso 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...

当错误发生时,不会读取任何图像,视频源也不会收到任何图像来更新画布。脚本第一次和第二次都正常运行,没有错误。从之前使用 cv2 模块中的 VideoCapture 函数进行的测试中,我发现我必须删除相机对象才能释放它,以便后续运行能够毫无问题地捕获相机流。通过who在控制台中键入来检查命名空间没有显示,cam所以我知道它在 GUI 关闭后被正确删除。我不明白为什么 cv2 的 read 函数会出错。我认为它只会每两次发生一次,因为当错误发生时,一些垃圾收集或错误处理会删除或释放与相机有关的东西,但我不知道这是什么......

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:

不在 GUI 中显示按钮并在关闭窗口后出现此错误:

RuntimeError: Too early to create image

I have 3 questions

我有3个问题

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.

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 - 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.

2 - cv2 中是否有比 .read() 更快、更有效的函数?编辑:有没有办法重构我的代码以获得更高的帧率?read 函数是文档中唯一列出的函数,我只是在某处读到,如果它不在文档中,则它不可用。这种方法只给我大约 5fps,其中 10-20fps 会更容易接受。更新:从 kobejohn 的测试和我的不同相机的测试之间的差异来看,低帧率是网络摄像头质量差的结果。质量更好的网络摄像头产生更高的帧率。

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.

3 -我一直在读应该尽可能避免 update() 但是我如何让画布重新绘制图像(或使用此代码实现 update_idletasks() )?我必须实现某种线程还是可以避免这种情况?更新:我已经让代码在不使用 update() 方法的情况下工作,但无论如何必须考虑实现线程,因为当我从主 GUI 的按钮开始录制视频时,它会冻结/变得无响应。

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).

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

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

在此先感谢您,任何建议和/或解决方案将不胜感激!

Regards,

问候,

S. Chia

S. Chia

采纳答案by S. Chia

Solved! OpenCV 2.4.2/ cv2 in python

解决了!Python 中的 OpenCV 2.4.2/ cv2

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.

由于一些奇怪的原因,我之前和其他论坛都找不到'release'方法,页面特别提到opencv的python绑定不包含release方法。也许这仅在使用“导入 cv”时适用。我使用后者进行了最初的原型设计,但由于某种原因,我在寻找 ReleaseCapture 方法时错过了 cv2 中的“release”方法。

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

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

import cv2

cam=cv2.VideoCapture(0)
cam.release

回答by KobeJohn

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)

你能试试这个代码,看看你得到了多少 FPS?我包括了 FPS 计算,以便我们可以比较笔记。(编辑:还有什么错误。我没有得到你在原始代码中得到的错误,我在下面的代码中得到零错误)

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 than RGB. So change your grascale conversion from cv2.COLOR_RGB2GRAY--> cv2.COLOR_BGR2GRAY. You can see in the VideoCapture examplethey 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 displayingso 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.partialif 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.
  1. 有一个(小?)错误:opencv 默认颜色通道是 BGR 而不是 RGB。因此,将您的 Grascale 转换从cv2.COLOR_RGB2GRAY-->更改为cv2.COLOR_BGR2GRAY。您可以在VideoCapture 示例中看到它们做了类似的事情。
  2. 我使用一个简单的标签来显示图像而不是画布。我以前没有使用过画布,所以我不确定你需要用它做什么。使用简单的标签,您必须保留对正在显示的图像的引用,这样它就不会被垃圾收集。您可以在 update_image() 中看到这一点。
  3. 对于回调,我使用了带参数的 lambdas(如您在评论中提到的)。否则,当您使用参数进行函数调用时,您将立即运行回调而不是注册它。最终看起来它正在工作,但它并没有像你想象的那样做。或者,如果您更喜欢打包参数并将其作为未调用的函数发送,则可以使用functools.partial
  4. 同样对于回调,我添加了一个 try: except 块,用于在 root 被销毁后回调开始运行的情况。我不知道这是否是“正确”的方法,但据我所知它有效。

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

使用此代码,我在 Windows 7 上获得 15 FPS 且没有错误:

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()