Как получить последний кадр с устройства захвата (камеры) в opencv

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

cap = cv2.VideoCapture(device_id)

while True:
    if event:
        img = cap.read()
        preprocess(img)

    process(img)
    cv.Waitkey(10)

Однако cap.read, похоже, захватывает только следующий кадр в очереди, а не последний. Я много искал в Интернете, и, кажется, есть много вопросов по этому поводу, но нет однозначного ответа. Только некоторые грязные хаки, которые включают открытие и закрытие устройства захвата непосредственно перед и после захвата (что не сработает для меня, так как мое событие может запускаться несколько раз в секунду); или предполагая фиксированную частоту кадров и считывая фиксированное n раз для каждого события (что не сработает для меня, поскольку мое событие непредсказуемо и может произойти с любым интервалом).

Хорошим решением будет:

while True:
    if event:
        while capture_has_frames:
            img = cap.read()
        preprocess(img)

    process(img)
    cv.Waitkey(10)

Но что такое capture_has_frames? Можно ли получить эту информацию? Я пытался заглянуть в CV_CAP_PROP_POS_FRAMES, но всегда -1.

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

(Я использую Ubuntu 16.04, кстати, но я думаю, это не имеет значения. Я также использую pyqtgraph для отображения)


person memo    schedule 27.04.2017    source источник


Ответы (3)


Я думаю, что решение, упомянутое в вопросе, а именно наличие отдельного потока, который очищает буфер, является самым простым нехрупким решением для этого. Вот достаточно хороший (я думаю) код для этого:

import cv2, queue, threading, time

# bufferless VideoCapture
class VideoCapture:

  def __init__(self, name):
    self.cap = cv2.VideoCapture(name)
    self.q = queue.Queue()
    t = threading.Thread(target=self._reader)
    t.daemon = True
    t.start()

  # read frames as soon as they are available, keeping only most recent one
  def _reader(self):
    while True:
      ret, frame = self.cap.read()
      if not ret:
        break
      if not self.q.empty():
        try:
          self.q.get_nowait()   # discard previous (unprocessed) frame
        except queue.Empty:
          pass
      self.q.put(frame)

  def read(self):
    return self.q.get()

cap = VideoCapture(0)
while True:
  time.sleep(.5)   # simulate time between events
  frame = cap.read()
  cv2.imshow("frame", frame)
  if chr(cv2.waitKey(1)&255) == 'q':
    break

Поток считывателя кадров инкапсулируется внутри настраиваемого класса VideoCapture, а связь с основным потоком осуществляется через очередь.

Я разместил очень похожий код для вопроса node.js, где решение JavaScript было бы лучше. В моих комментариях к другому ответу на этот вопрос подробно рассказывается, почему нехрупкое решение без отдельного потока кажется трудным.

Альтернативное решение, которое проще, но поддерживается только для некоторых бэкэндов OpenCV, использует CAP_PROP_BUFFERSIZE. В 2.4 docs говорится, что он поддерживается только В настоящее время серверная часть DC1394 [Firewire] v 2.x. Для серверной части Linux V4L, согласно комментарию в Код 3.4.5, поддержка была добавлена ​​9 марта 2018 г., но я получил VIDEOIO ERROR: V4L: Property <unknown property string>(38) not supported by device именно для этого бэкэнда. Возможно, сначала стоит попробовать; код такой простой:

cap.set(cv2.CAP_PROP_BUFFERSIZE, 0)
person Ulrich Stern    schedule 18.02.2019
comment
Хотя в идеальном мире был бы более элегантный способ получить самые свежие данные кадра, это своеобразный трюк! - person Woohoojin; 19.02.2019
comment
@ChristianScillitoe, в идеальном мире можно использовать все возможности камеры через API производителя и использовать предоставленный триггер камеры для фактического захвата кадра, когда происходит событие. cv2.VideoCapture вместо этого представляет собой компактный кроссплатформенный универсальный класс, предназначенный для удобства и быстрого создания прототипов. - person mainactual; 19.02.2019
comment
@mainactual, я поклонник кроссплатформенного независимого от устройства API, такого как OpenCV, для обработки захвата видео. Кроме того, хотя модуль OpenCV HighGUI был разработан для прототипирования, Модуля видеоввода / вывода (VideoCapture и др.) Не было. - person Ulrich Stern; 19.02.2019
comment
@UlrichStern, как сообщает ваша ссылка, VideoCapture раньше была частью Highgui-модуля, подобного Matlab, до 3.0, и хотя я не уверен, насколько он изменился после этого, и насколько я его поклонник, это по-прежнему реализует только подмножество возможностей базового сервера, например таковые из FFMPEG. - person mainactual; 19.02.2019
comment
Спасибо @UlrichStern. Могу ли я установить серверную часть на DC1394 [Firewire] v 2.x backend в python-opencv 4.1.2 на Ubuntu 18.04? - person fisakhan; 06.02.2020
comment
@mainactual, я понимаю, что вы говорите, но если VideoCapture не предназначался для обеспечения точного контроля времени, то какой цели служат методы grab () и retrieve ()? Разве они не должны обеспечивать именно такой контроль? Но что в них хорошего, если вы не можете быть уверены, что полученное вами изображение - это то же изображение, которое вы захватили? Я чувствую, что что-то неправильно понимаю. - person TrespassersW; 12.02.2020
comment
cap.set (cv2.CAP_PROP_BUFFERSIZE, 0) у меня не работает. Похоже, это вообще не влияет. Кажется, это немного помогает, когда я устанавливаю его на 1, но полностью не решает проблему. Интересно, нельзя ли обнуление на некоторых камерах? - person TrespassersW; 12.02.2020
comment
@TrespassersW есть некоторые возможности дизайна для бэкэндов видеозахвата, например, MSMF grab вызывает асинхронный ReadSample, поэтому имеет смысл разделить их и рекомендовать использовать их отдельно. Но в любом случае я измеряю точное время в микросекундах, и для достижения такого уровня синхронизации требуется явное управление триггером камеры вместо произвольного захвата. - person mainactual; 12.02.2020
comment
@mainactual В моем случае мне не нужна такая точность. Мне просто нужен способ убедиться, что изображение, которое я получаю, было в определенное время или после него. Для этого я не уверен, что мне нужно управлять спуском камеры. Я думаю, что было бы достаточно знать, что запрос на извлечение совпадает с захватом. (Надеюсь, я не сталкиваюсь с жалобами на замечательную работу, проделанную над OpenCV. Я просто пытаюсь понять это, чтобы увидеть, соответствует ли это моим потребностям) - person TrespassersW; 12.02.2020
comment
@TrespassersW, насколько хорошо он соответствует вашим целям, зависит от бэкэнда захвата. Моя текущая камера имеет время считывания 24 мс и время передачи 6 мс (USB3.0). Кроме того, драйверу требуется значительное время для заполнения буфера. Было бы трудно управлять этими накладными расходами, не отправляя каждый триггер явно, а просто запрашивая последний захваченный кадр. Также с дополнительной очередью, описанной в этом ответе. - person mainactual; 12.02.2020
comment
Для python 3 вы хотите сделать import queue as Queue вместо import Queue - person warriorUSP; 15.04.2020
comment
Я думаю, что это очень неэффективно для процессора ... Возможно, лучше было бы несколько раз вызвать cap.grab () (я думаю, 5 - это размер буфера), а затем вызвать cap.retrieve (), docs.opencv.org/2.4/modules/highgui/doc/ - person Bersan; 25.04.2020
comment
Зачем использовать Python 2? Это устарело! - person Elia Iliashenko; 13.07.2020

На моем Raspberry Pi 4

cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)

действительно работает, и это все, что мне нужно, чтобы моя пи-камера выдавала мне последний кадр с постоянной задержкой в ​​3+ секунды между сценой перед камерой и отображением этой сцены в изображении предварительного просмотра. Мой код обрабатывает изображение за 1,3 секунды, поэтому я не уверен, почему присутствуют остальные 2 секунды задержки, но он согласован и работает.

Боковое примечание: поскольку мой код занимает секунду для обработки изображения, я также добавил

cap.set( cv2.CAP_PROP_FPS, 2 )

на случай, если это уменьшит ненужную активность, так как я не могу получить кадр в секунду. Когда я установил cv2.CAP_PROP_FPS на 1, я получил странный вывод, что все мои кадры были почти полностью темными, поэтому слишком низкая установка FPS может вызвать проблему.

person RealHandy    schedule 23.07.2020
comment
Пожалуйста, не добавляйте один и тот же ответ на несколько вопросов. Ответьте на лучший, а остальные отметьте как дубликаты. См. Допустимо ли добавлять дублирующийся ответ на несколько вопросов? - person Machavity♦; 24.07.2020
comment
@Machavity Да, я знаю, что обычно этого не следует делать, но в этом случае я разместил дубликат в другом вопросе, указав на этот вопрос, ответ Ульриха Стерна здесь и тот факт, что другой может быть обманщиком, потому что другой Возможно, это не дурак: в другом вопросе конкретно используется IP-камера, а не локальная камера, и я не могу сказать, работают ли здесь ответы для IP-камеры. Вот почему я думаю, что другие, которые думали, что это может быть обман, оставили его открытым. - person RealHandy; 24.07.2020

Если вы не хотите захватывать кадр при отсутствии события, зачем вы предварительно обрабатываете / обрабатываете свой кадр? Если вы не обработаете свой фрейм, вы можете просто отказаться от него, если событие не произойдет. Ваша программа должна иметь возможность захватывать, оценивать ваше состояние и отбрасывать на достаточной скорости, то есть достаточно быстро по сравнению с частотой захвата кадров вашей камеры, чтобы всегда получать последний кадр в очереди.

Если я не разбираюсь в python, потому что я использую OpenCV на C ++, но он должен выглядеть примерно так:

vidcap = cv.VideoCapture(   filename    )

while True:
    success, frame = vidcap.read()
    If Not success:
         break
    If cv.waitKey(1):
         process(frame)

Согласно ссылке OpenCV, vidcap.read () возвращает bool. Если кадр прочитан правильно, он будет True. Затем захваченный кадр сохраняется в переменной frame. Если кнопки не нажимаются, цикл продолжается. Когда нажата клавиша, вы обрабатываете свой последний захваченный кадр.

person imtheref    schedule 18.02.2019
comment
Вы неправильно истолковываете вопрос. Проблема, с которой сталкивается OP, заключается в том, что vidcap.read() возвращает следующий кадр в буфере, поэтому, если они проводят много времени с предыдущим кадром, vidcap.read() не даст последний кадр, а вместо этого даст следующий кадр в буфере. - person Woohoojin; 18.02.2019
comment
@Christian Scillitoe: Возможно, я был. Возможно, Memo следует использовать графический процессор для ускорения обработки изображений. Это будет происходить одновременно с захватом и без необходимости создавать дополнительный поток вручную (гетерогенные вычисления). - person imtheref; 19.02.2019