Получение отдельных кадров с использованием 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;
}