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

Posted

技术标签:

【中文标题】OpenCV(Python中的cv2)VideoCapture在删除后不释放相机【英文标题】:OpenCV (cv2 in Python) VideoCapture not releasing camera after deletion 【发布时间】:2013-03-05 20:06:48 【问题描述】:

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

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

当错误发生时,没有图像被读取,并且视频源没有接收到图像来更新画布。该脚本第一次和第二次正常运行,没有错误。从之前使用 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) " 到 "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() 但我如何让画布重新绘制图像(或使用此代码实现 update_idletasks())?我必须实现某种线程还是可以避免这种情况? 更新:我已经让代码在不使用 update() 方法的情况下工作,但无论如何都必须考虑实现线程,因为当我从主 GUI 的按钮开始录制视频源时,它会冻结/变得无响应。

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

提前感谢您,我们将不胜感激任何建议和/或解决方案!

问候,

S。奇亚

【问题讨论】:

我已经多次运行您的原始代码,它停止/重新启动对我来说没有错误。您可以尝试使用替代相机(即可能是相机驱动程序问题?) 我们为什么不关注第一个代码呢?我相信第二个代码至少有一个错误:root.after(0,update_video(cam,root,canvas)) 正在注册 update_video(...) 的返回值,而不是函数 update_video 本身。 仅供参考,我的帧速率也很高(可能在 20-30 左右?)。 第一个我也试过了,退出后就不会再开始了。 抱歉这么久才更新,伙计们。我在获得第三个网络摄像头时遇到了麻烦,而不必购买一个。我首先在我的网络摄像头和一个便宜的网络摄像头上进行了测试,他们给出了同样的问题。我在获得的第三个网络摄像头上进行了测试,问题仍然存在。也许这只是我的机器和/或我的 windows python 安装在起作用。我会找到另一台机器来测试代码并再次更新。 【参考方案1】:

解决了! Python 中的 OpenCV 2.4.2/ cv2

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

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

import cv2

cam=cv2.VideoCapture(0)
cam.release

【讨论】:

【参考方案2】:

在opencv中初始化camera对象之前设置环境变量

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

即使在我的代码中关闭相机对象后,它也会释放相机。

【讨论】:

【参考方案3】:

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

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

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

使用此代码,我得到 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()

【讨论】:

嗨 Kobejohn,感谢您为此付出的时间和精力!不幸的是,我仍然遇到相同的错误,并且使用您的代码获得的帧速率与使用原始代码获得的帧速率相同...=(我认为您是正确的,因为它可能是相机驱动程序(和质量)问题。try: except 块是个好主意,感谢您指出该错误!我还找到了另一种将参数传递给回调的方法:“root.after(time_to_wait, callback, arguments, master)”在看起来更干净的同时完成工作。

以上是关于OpenCV(Python中的cv2)VideoCapture在删除后不释放相机的主要内容,如果未能解决你的问题,请参考以下文章

Python,OpenCV中的图像修复——cv2.inpaint()

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

Python-OpenCV 中的绘图函数

Python-OpenCV中的图像轮廓检测

初识OpenCV-Python - 005: 识别视频中的蓝色

python中的openCV视频保存