Получение отдельных кадров с использованием CV_CAP_PROP_POS_FRAMES в cvSetCaptureProperty

Я пытаюсь перейти к определенному кадру, установив свойство CV_CAP_PROP_POS_FRAMES, а затем прочитав этот кадр следующим образом:

cvSetCaptureProperty( input_video, CV_CAP_PROP_POS_FRAMES, current_frame );
frame = cvQueryFrame( input_video );

Проблема, с которой я сталкиваюсь, заключается в том, что OpenCV 2.1 возвращает один и тот же кадр для 12 последовательных значений current_frame, тогда как я хочу читать каждый отдельный кадр, а не только ключевые кадры. Может кто-нибудь, пожалуйста, скажите мне, что случилось?


Я провел некоторое исследование и выяснил, что проблема вызвана алгоритмом декомпрессии.

MPEG-подобные алгоритмы (включая HD и т.д.) не сжимают каждый кадр отдельно, а время от времени сохраняют ключевой кадр, а затем только различия между последним кадром и последующими кадрами.

Проблема, о которой вы сообщили, вызвана тем, что при выборе фрейма декодер (ffmpeg, скорее всего) автоматически переходит к следующему ключевому кадру.

Итак, есть ли способ обойти это? Я не хочу только ключевые кадры, но каждый отдельный кадр.

Ответ 1

Я не знаю, будет ли это достаточно точно для вашей цели, но мне удалось достичь определенного момента в MPEG-видео, захватив частоту кадров, конвертируя номер кадра в определенное время, затем продвигаясь к тому времени. Например:

cv::VideoCapture sourceVideo("/some/file/name.mpg");
double frameRate = sourceVideo.get(CV_CAP_PROP_FPS);
double frameTime = 1000.0 * frameNumber / frameRate;
sourceVideo.set(CV_CAP_PROP_POS_MSEC, frameTime);

Ответ 2

Из-за этого ограничения в OpenCV, может быть разумно использовать FFMPEG. Moviepy - хорошая библиотека оберток.

# Get nth frame from a video
from moviepy.video.io.ffmpeg_reader import FFMPEG_VideoReader
cap = FFMPEG_VideoReader("movie.mov",True)
cap.initialize()
cap.get_frame(n/FPS)

Отличная производительность. Поиск в n-ом кадре с помощью get_frame - O (1), и ускорение используется, если запрошены (почти) последовательные кадры. Я получил результаты лучше, чем в реальном времени, загружая три видео 720p одновременно.

Ответ 3

CV_CAP_PROP_POS_FRAMES переходит к ключевому фрейму. У меня была такая же проблема, и я работал над этим, используя этот (python-) код. Вероятно, это не совсем эффективно, но выполнить работу:

def seekTo(cap, position):
  positiontoset = position
  pos = -1
  cap.set(cv.CV_CAP_PROP_POS_FRAMES, position)
  while pos < position:
    ret, image = cap.read()
    pos = cap.get(cv.CV_CAP_PROP_POS_FRAMES)
    if pos == position:
      return image
    elif pos > position:
      positiontoset -= 1
      cap.set(cv.CV_CAP_PROP_POS_FRAMES, positiontoset)
      pos = -1

Ответ 4

Я успешно использовал следующее в OpenCV 3/Python 3:

 # Skip to 150 frame then read the 151th frame
 cap.set(cv2.CAP_PROP_POS_FRAMES, 150))
 ret, frame = cap.read()

Ответ 5

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

Предыдущее решение предложило использовать свойство CV_CAP_PROP_POS_MSEC перед чтением фрейма:

cv::VideoCapture sourceVideo("/some/file/name.mpg");
const auto frameRate = sourceVideo.get(CV_CAP_PROP_FPS);

void readFrame(int frameNumber, cv::Mat& image) {
  const double frameTime = 1000.0 * frameNumber / frameRate;
  sourceVideo.set(CV_CAP_PROP_POS_MSEC, frameTime);
  sourceVideo.read(image);
}

Он возвращает ожидаемый кадр, но проблема в том, что использование CV_CAP_PROP_POS_MSEC может быть очень медленным, например, для преобразования видео.

Примечание. Для упрощения используйте глобальные переменные.


С другой стороны, если вы просто хотите прочитать видео последовательно, достаточно прочитать кадр без каких-либо поисков.

for (int frameNumber = 0; frameNumber < nFrames; ++frameNumber) {
  sourceVideo.read(image);
}

Решение исходит из объединения обоих: использование переменной для запоминания последнего запрошенного фрейма lastFrameNumber и только поиск, когда запрошенный фрейм не является следующим. Таким образом, можно увеличивать скорость в последовательном чтении, позволяя при необходимости произвольно искать.

cv::VideoCapture sourceVideo("/some/file/name.mpg");
const auto frameRate = sourceVideo.get(CV_CAP_PROP_FPS);
const int lastFrameNumber = -2; // guarantee seeking the first time

void readFrame(int frameNumber, cv::Mat& image) {
  if (lastFrameNumber + 1 != frameNumber) { // not the next frame? seek
    const double frameTime = 1000.0 * frameNumber / frameRate;
    sourceVideo.set(CV_CAP_PROP_POS_MSEC, frameTime);
  }

  sourceVideo.read(image);
  lastFrameNumber = frameNumber;
}