read - OpenCV(cv2 в Python) VideoCapture не освобождает камеру после удаления



python opencv mp4 (2)

Я относительно новичок в Python, просто узнав об этом за последний месяц или около того, и взломал это вместе на основе примеров и кода других, которые я нашел в Интернете.

Я получил графический интерфейс Tkinter для отображения фида с веб-камеры в виде цикла постоянно обновляемых изображений на холсте. Выход из графического интерфейса и повторный запуск сценария каждый раз приводит к этой ошибке:

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

Когда ошибка не происходит, изображения не считываются, и видеозапись не получает изображений для обновления холста. Скрипт работает нормально без ошибок в первый раз и каждый второй раз. Из предыдущих тестов с функцией VideoCapture в модуле cv2 я обнаружил, что мне пришлось удалить объект камеры, чтобы освободить его, чтобы последующие прогоны могли захватывать поток камеры без проблем. Проверяет в пространстве имен, набрав, who в консоли не показывает cam поэтому я знаю, что он удаляется должным образом после закрытия графического интерфейса. Я не понимаю, почему функция чтения 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 - Как я могу предотвратить любое исключение? UPDATE: изменение "root.after (0, update_video (cam, root, canvas))" до "root.after (0, lambda: update_video (cam, root, canvas))" и "update_video (cam, root, canvas) «to» update_video (cam, root, canvas, event = None) »ИЛИ передача аргументов обратному вызову с использованием этого формата:« root.after (time_to_wait, callback, arguments, master) »исправляет вторую ошибку (и другие, которые я сделал не почта). Также, как указал кобеджон, добавление попытки: кроме блока также исправляется вторая ошибка. Более подробную информацию см. В его ответе.

2 - Есть ли более быстрая и эффективная функция, чем .read () в cv2? Изменить: есть ли способ реорганизовать мой код, чтобы получить более высокие частоты кадров? Функция чтения является единственной, указанной в документах, и я просто где-то читал, что, если ее нет в документах, она недоступна. Этот метод дает мне примерно 5 кадров в секунду, где 10-20 кадров в секунду будет гораздо более приемлемым. ОБНОВЛЕНИЕ: из-за расхождений между тестами Кобеджона и моей камеры с разными камерами низкая частота кадров является результатом веб-камер низкого качества. Веб-камеры лучшего качества дают более высокие частоты кадров.

3 - Я читал, что update () следует избегать как можно больше, но как я могу заставить холст перерисовать изображение в противном случае (или реализовать update_idletasks () с помощью этого кода) ?. Должен ли я выполнять какую-то нить, или я могу избежать этого? ОБНОВЛЕНИЕ: я получил код для работы без использования метода update (), но в любом случае должен смотреть на реализацию потоковой передачи, потому что, когда я начинаю записывать видеофайлы с одной кнопки на главный графический интерфейс, он зависает / становится невосприимчивым.

Готовая программа будет использоваться в Ubuntu и Windows (возможно, и для macs). Я запускаю Windows 7, IDE - Spyder 2.1.11 (Python 2.7.3).

Заранее спасибо, любые советы и / или решения будут высоко оценены!

С Уважением,

С. Чиа


Answer #1

Можете ли вы попробовать этот код и посмотреть, что FPS вы получаете? Я включил расчет FPS, чтобы мы могли сравнивать заметки. (редактирование: также какие ошибки. Я не получил ошибок, которые вы получили в исходном коде, и я получаю нулевые ошибки с кодом ниже)

Я начал с нуля, чтобы посмотреть, не придумал ли я что-то другое. Есть несколько отличий:

  1. Была ошибка (незначительная?): Цветные каналы по умолчанию opencv - это BGR, а не RGB. Поэтому измените преобразование cv2.COLOR_RGB2GRAY из cv2.COLOR_RGB2GRAY -> cv2.COLOR_BGR2GRAY . В примере VideoCapture вы можете увидеть что-то подобное.
  2. Я использовал простую метку для отображения изображения вместо холста. Раньше я не использовал холст, поэтому я не уверен, что вам нужно делать с ним. С простой меткой вам нужно сохранить ссылку на изображение, которое вы показываете, чтобы он не собирал мусор. Вы можете видеть это в update_image ().
  3. Для обратных вызовов я использовал lambdas с аргументами (как вы упомянули в своем комментарии). В противном случае, когда вы вызываете вызов функции с помощью аргументов, вы немедленно вызываете обратный вызов, а не регистрируете его. В конце концов, похоже, что он работает, но это не совсем то, что вы думаете. В качестве альтернативы вы можете использовать functools.partial, если вы предпочитаете упаковывать свои аргументы и отправлять их как невостребованную функцию.
  4. Также для обратного вызова я добавил попытку: кроме блока для случая, когда обратный вызов запускается после того, как 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()

Answer #2

Решено! OpenCV 2.4.2 / cv2 в python

По какой-то странной причине я не мог найти метод «выпуска» до и других форумов, на страницах, где конкретно упоминалось, что привязки python к opencv не включают метод release. Возможно, это применимо только при использовании «import cv». Я сделал свое первоначальное прототипирование с использованием последнего и почему-то пропустил метод «release» в cv2, когда искал метод ReleaseCapture.

Просто нашел это в документах: http://docs.opencv.org/modules/highgui/doc/reading_and_writing_images_and_video.html

import cv2

cam=cv2.VideoCapture(0)
cam.release